diff --git a/API_DOCUMENTATION.md b/API_DOCUMENTATION.md new file mode 100644 index 0000000..b28c412 --- /dev/null +++ b/API_DOCUMENTATION.md @@ -0,0 +1,402 @@ +# M-Project API 문서 + +## 개요 +작업보고서 시스템의 FastAPI 백엔드 API 엔드포인트 문서 + +**Base URL:** `http://localhost:16000` + +--- + +## 인증 (Authentication) + +### JWT 토큰 기반 인증 +- **토큰 타입:** Bearer Token +- **만료 시간:** 7일 (10080분) +- **헤더:** `Authorization: Bearer ` + +--- + +## API 엔드포인트 + +### 🔐 인증 관련 API (`/api/auth`) + +#### 1. 로그인 +```http +POST /api/auth/login +Content-Type: application/x-www-form-urlencoded + +username=hyungi&password=djg3-jj34-X3Q3 +``` + +**응답:** +```json +{ + "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...", + "token_type": "bearer", + "user": { + "id": 1, + "username": "hyungi", + "full_name": "관리자", + "role": "admin", + "is_active": true, + "created_at": "2024-01-01T00:00:00" + } +} +``` + +#### 2. 현재 사용자 정보 +```http +GET /api/auth/me +Authorization: Bearer +``` + +#### 3. 사용자 생성 (관리자만) +```http +POST /api/auth/users +Authorization: Bearer +Content-Type: application/json + +{ + "username": "user1", + "password": "password123", + "full_name": "사용자1", + "role": "user" +} +``` + +#### 4. 사용자 목록 조회 (관리자만) +```http +GET /api/auth/users?skip=0&limit=100 +Authorization: Bearer +``` + +#### 5. 사용자 수정 (관리자만) +```http +PUT /api/auth/users/{user_id} +Authorization: Bearer +Content-Type: application/json + +{ + "full_name": "수정된 이름", + "role": "admin" +} +``` + +#### 6. 사용자 삭제 (관리자만) +```http +DELETE /api/auth/users/{username} +Authorization: Bearer +``` + +#### 7. 비밀번호 변경 +```http +POST /api/auth/change-password +Authorization: Bearer +Content-Type: application/json + +{ + "current_password": "old_password", + "new_password": "new_password" +} +``` + +--- + +### 📁 프로젝트 관리 API (`/api/projects`) + +#### 1. 프로젝트 생성 (관리자만) +```http +POST /api/projects +Authorization: Bearer +Content-Type: application/json + +{ + "job_no": "JOB-2024-001", + "project_name": "신규 프로젝트" +} +``` + +**응답:** +```json +{ + "id": 1, + "job_no": "JOB-2024-001", + "project_name": "신규 프로젝트", + "created_by_id": 1, + "created_by": { + "id": 1, + "username": "hyungi", + "full_name": "관리자", + "role": "admin" + }, + "created_at": "2024-01-01T00:00:00", + "is_active": true +} +``` + +#### 2. 프로젝트 목록 조회 +```http +GET /api/projects?skip=0&limit=100&active_only=true +Authorization: Bearer +``` + +#### 3. 특정 프로젝트 조회 +```http +GET /api/projects/{project_id} +Authorization: Bearer +``` + +#### 4. 프로젝트 수정 (관리자만) +```http +PUT /api/projects/{project_id} +Authorization: Bearer +Content-Type: application/json + +{ + "project_name": "수정된 프로젝트명", + "is_active": true +} +``` + +#### 5. 프로젝트 삭제 (관리자만) +```http +DELETE /api/projects/{project_id} +Authorization: Bearer +``` + +--- + +### 🚨 부적합 사항 API (`/api/issues`) + +#### 1. 부적합 사항 생성 +```http +POST /api/issues +Authorization: Bearer +Content-Type: application/json + +{ + "category": "material_missing", + "description": "자재 누락 발견", + "photo": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ...", + "photo2": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..." +} +``` + +**카테고리 값:** +- `material_missing`: 자재누락 +- `design_error`: 설계미스 +- `incoming_defect`: 입고자재 불량 +- `inspection_miss`: 검사미스 + +#### 2. 부적합 사항 목록 조회 +```http +GET /api/issues?skip=0&limit=100 +Authorization: Bearer +``` + +#### 3. 특정 부적합 사항 조회 +```http +GET /api/issues/{issue_id} +Authorization: Bearer +``` + +#### 4. 부적합 사항 수정 +```http +PUT /api/issues/{issue_id} +Authorization: Bearer +Content-Type: application/json + +{ + "category": "design_error", + "description": "수정된 설명", + "work_hours": 2.5, + "status": "complete", + "detail_notes": "해결 완료" +} +``` + +**상태 값:** +- `new`: 신규 +- `progress`: 진행중 +- `complete`: 완료 + +#### 5. 부적합 사항 삭제 +```http +DELETE /api/issues/{issue_id} +Authorization: Bearer +``` + +--- + +### 📊 일일 공수 API (`/api/daily-work`) + +#### 1. 일일 공수 생성 +```http +POST /api/daily-work +Authorization: Bearer +Content-Type: application/json + +{ + "date": "2024-01-01T00:00:00", + "worker_count": 10, + "overtime_workers": 3, + "overtime_hours": 4.0 +} +``` + +#### 2. 일일 공수 목록 조회 +```http +GET /api/daily-work?skip=0&limit=100 +Authorization: Bearer +``` + +#### 3. 특정 일일 공수 조회 +```http +GET /api/daily-work/{work_id} +Authorization: Bearer +``` + +#### 4. 일일 공수 수정 +```http +PUT /api/daily-work/{work_id} +Authorization: Bearer +Content-Type: application/json + +{ + "worker_count": 12, + "overtime_workers": 5, + "overtime_hours": 6.0 +} +``` + +#### 5. 일일 공수 삭제 +```http +DELETE /api/daily-work/{work_id} +Authorization: Bearer +``` + +--- + +### 📈 보고서 API (`/api/reports`) + +#### 1. 보고서 생성 +```http +POST /api/reports/generate +Authorization: Bearer +Content-Type: application/json + +{ + "start_date": "2024-01-01T00:00:00", + "end_date": "2024-01-31T23:59:59" +} +``` + +**응답:** +```json +{ + "start_date": "2024-01-01T00:00:00", + "end_date": "2024-01-31T23:59:59", + "total_hours": 156.5, + "total_issues": 25, + "category_stats": { + "material_missing": 10, + "dimension_defect": 8, + "incoming_defect": 7 + }, + "completed_issues": 20, + "average_resolution_time": 2.3 +} +``` + +--- + +## 헬스체크 + +### 서버 상태 확인 +```http +GET /api/health +``` + +**응답:** +```json +{ + "status": "healthy" +} +``` + +--- + +## 에러 응답 형식 + +### 인증 오류 (401) +```json +{ + "detail": "Could not validate credentials" +} +``` + +### 권한 오류 (403) +```json +{ + "detail": "관리자 권한이 필요합니다." +} +``` + +### 리소스 없음 (404) +```json +{ + "detail": "프로젝트를 찾을 수 없습니다." +} +``` + +### 유효성 검사 오류 (422) +```json +{ + "detail": [ + { + "loc": ["body", "job_no"], + "msg": "field required", + "type": "value_error.missing" + } + ] +} +``` + +--- + +## 파일 업로드 + +### 이미지 업로드 +- **형식:** Base64 인코딩된 이미지 데이터 +- **지원 형식:** JPEG, PNG, GIF, WebP +- **최대 크기:** 10MB +- **저장 위치:** `/app/uploads/` + +### Base64 형식 예시 +``` +data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k= +``` + +--- + +## 개발 환경 설정 + +### 환경 변수 +```env +DATABASE_URL=postgresql://mproject:mproject2024@db:5432/mproject +SECRET_KEY=your-secret-key-here-change-in-production +ALGORITHM=HS256 +ACCESS_TOKEN_EXPIRE_MINUTES=10080 +ADMIN_USERNAME=hyungi +ADMIN_PASSWORD=djg3-jj34-X3Q3 +TZ=Asia/Seoul +``` + +### Docker 컨테이너 실행 +```bash +docker-compose up -d +``` + +### API 문서 확인 +- **Swagger UI:** http://localhost:16000/docs +- **ReDoc:** http://localhost:16000/redoc diff --git a/DATABASE_SCHEMA.md b/DATABASE_SCHEMA.md new file mode 100644 index 0000000..5d78e52 --- /dev/null +++ b/DATABASE_SCHEMA.md @@ -0,0 +1,173 @@ +# M-Project 데이터베이스 스키마 문서 + +## 개요 +작업보고서 시스템의 PostgreSQL 데이터베이스 스키마 정의 + +--- + +## 테이블 구조 + +### 1. users (사용자) +사용자 계정 정보를 저장하는 테이블 + +| 컬럼명 | 타입 | 제약조건 | 설명 | +|--------|------|----------|------| +| id | INTEGER | PRIMARY KEY, AUTO_INCREMENT | 사용자 고유 ID | +| username | VARCHAR | UNIQUE, NOT NULL, INDEX | 로그인 아이디 | +| hashed_password | VARCHAR | NOT NULL | 암호화된 비밀번호 | +| full_name | VARCHAR | NULL | 사용자 실명 | +| role | ENUM | DEFAULT 'user' | 사용자 권한 (admin, user) | +| is_active | BOOLEAN | DEFAULT TRUE | 계정 활성화 상태 | +| created_at | TIMESTAMP | DEFAULT NOW() | 계정 생성일시 | + +**인덱스:** +- `idx_users_username` ON username +- `idx_users_role` ON role + +**ENUM 값:** +- role: 'admin', 'user' + +--- + +### 2. projects (프로젝트) +프로젝트 정보를 저장하는 테이블 + +| 컬럼명 | 타입 | 제약조건 | 설명 | +|--------|------|----------|------| +| id | INTEGER | PRIMARY KEY, AUTO_INCREMENT | 프로젝트 고유 ID | +| job_no | VARCHAR(50) | UNIQUE, NOT NULL, INDEX | Job 번호 | +| project_name | VARCHAR(200) | NOT NULL | 프로젝트 이름 | +| created_by_id | INTEGER | FOREIGN KEY → users(id) | 생성자 ID | +| created_at | TIMESTAMP | DEFAULT NOW() | 생성일시 | +| is_active | BOOLEAN | DEFAULT TRUE | 활성 상태 | + +**인덱스:** +- `idx_projects_job_no` ON job_no +- `idx_projects_created_by_id` ON created_by_id +- `idx_projects_is_active` ON is_active + +**외래키:** +- created_by_id → users(id) + +--- + +### 3. issues (부적합 사항) +부적합 사항 정보를 저장하는 테이블 + +| 컬럼명 | 타입 | 제약조건 | 설명 | +|--------|------|----------|------| +| id | INTEGER | PRIMARY KEY, AUTO_INCREMENT | 부적합 사항 고유 ID | +| photo_path | VARCHAR | NULL | 첫 번째 사진 경로 | +| photo_path2 | VARCHAR | NULL | 두 번째 사진 경로 | +| category | ENUM | NOT NULL | 부적합 카테고리 | +| description | TEXT | NOT NULL | 부적합 사항 설명 | +| status | ENUM | DEFAULT 'new' | 처리 상태 | +| reporter_id | INTEGER | FOREIGN KEY → users(id) | 보고자 ID | +| report_date | TIMESTAMP | DEFAULT NOW() | 보고일시 | +| work_hours | FLOAT | DEFAULT 0 | 작업 시간 | +| detail_notes | TEXT | NULL | 상세 메모 | + +**인덱스:** +- `idx_issues_reporter_id` ON reporter_id +- `idx_issues_status` ON status +- `idx_issues_category` ON category +- `idx_issues_report_date` ON report_date + +**ENUM 값:** +- category: 'material_missing', 'design_error', 'incoming_defect', 'inspection_miss' +- status: 'new', 'progress', 'complete' + +**외래키:** +- reporter_id → users(id) + +--- + +### 4. daily_works (일일 공수) +일일 작업 공수 정보를 저장하는 테이블 + +| 컬럼명 | 타입 | 제약조건 | 설명 | +|--------|------|----------|------| +| id | INTEGER | PRIMARY KEY, AUTO_INCREMENT | 일일 공수 고유 ID | +| date | TIMESTAMP | NOT NULL, INDEX | 작업 날짜 | +| worker_count | INTEGER | NOT NULL | 작업자 수 | +| regular_hours | FLOAT | NOT NULL | 정규 시간 | +| overtime_workers | INTEGER | DEFAULT 0 | 야근 작업자 수 | +| overtime_hours | FLOAT | DEFAULT 0 | 야근 시간 | +| overtime_total | FLOAT | DEFAULT 0 | 야근 총 시간 | +| total_hours | FLOAT | NOT NULL | 총 작업 시간 | +| created_by_id | INTEGER | FOREIGN KEY → users(id) | 생성자 ID | +| created_at | TIMESTAMP | DEFAULT NOW() | 생성일시 | + +**인덱스:** +- `idx_daily_works_date` ON date +- `idx_daily_works_created_by_id` ON created_by_id + +**외래키:** +- created_by_id → users(id) + +--- + +## 관계도 (Relationships) + +``` +users (1) ←→ (N) projects + ↓ +users (1) ←→ (N) issues + ↓ +users (1) ←→ (N) daily_works +``` + +--- + +## 마이그레이션 파일 목록 + +1. `001_init.sql` - 초기 테이블 생성 +2. `002_add_second_photo.sql` - issues 테이블에 두 번째 사진 필드 추가 +3. `003_update_categories.sql` - 카테고리 업데이트 +4. `004_fix_category_values.sql` - 카테고리 값 수정 +5. `005_recreate_enum_type.sql` - ENUM 타입 재생성 +6. `006_add_projects_table.sql` - projects 테이블 추가 + +--- + +## 기본 데이터 + +### 관리자 계정 +- **Username:** hyungi +- **Password:** djg3-jj34-X3Q3 +- **Role:** admin +- **Full Name:** 관리자 + +--- + +## 데이터베이스 연결 정보 + +- **Host:** localhost (Docker 내부: db) +- **Port:** 16432 (외부), 5432 (내부) +- **Database:** mproject +- **Username:** mproject +- **Password:** mproject2024 + +--- + +## 주의사항 + +1. **비밀번호 암호화:** bcrypt 해시 사용 +2. **시간대:** 모든 TIMESTAMP는 Asia/Seoul (KST) 기준 +3. **파일 업로드:** 사진 파일은 `/app/uploads` 디렉토리에 저장 +4. **소프트 삭제:** projects 테이블은 is_active 필드로 소프트 삭제 구현 +5. **인덱스 최적화:** 자주 조회되는 컬럼에 인덱스 설정 + +--- + +## 백업 및 복구 + +### 백업 +```bash +docker-compose exec db pg_dump -U mproject mproject > backup.sql +``` + +### 복구 +```bash +docker-compose exec -T db psql -U mproject mproject < backup.sql +``` diff --git a/PROJECT_OVERVIEW.md b/PROJECT_OVERVIEW.md new file mode 100644 index 0000000..9b27424 --- /dev/null +++ b/PROJECT_OVERVIEW.md @@ -0,0 +1,328 @@ +# M-Project 프로젝트 개요 + +## 📋 프로젝트 소개 +작업보고서 시스템 - 부적합 사항 관리 및 공수 계산을 위한 웹 애플리케이션 + +--- + +## 🏗️ 시스템 아키텍처 + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Frontend │ │ Backend │ │ Database │ +│ (HTML/JS) │◄──►│ (FastAPI) │◄──►│ (PostgreSQL) │ +│ Port: 16080 │ │ Port: 16000 │ │ Port: 16432 │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + ▲ ▲ ▲ + │ │ │ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Nginx │ │ Docker │ │ File Storage │ +│ (Reverse Proxy)│ │ (Container) │ │ (/uploads) │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ +``` + +--- + +## 📁 프로젝트 구조 + +``` +M-Project/ +├── backend/ # FastAPI 백엔드 +│ ├── database/ # 데이터베이스 관련 +│ │ ├── database.py # DB 연결 설정 +│ │ ├── models.py # SQLAlchemy 모델 +│ │ └── schemas.py # Pydantic 스키마 +│ ├── migrations/ # DB 마이그레이션 파일 +│ │ ├── 001_init.sql +│ │ ├── 002_add_second_photo.sql +│ │ ├── 003_update_categories.sql +│ │ ├── 004_fix_category_values.sql +│ │ ├── 005_recreate_enum_type.sql +│ │ └── 006_add_projects_table.sql +│ ├── routers/ # API 라우터 +│ │ ├── auth.py # 인증 관련 API +│ │ ├── projects.py # 프로젝트 관리 API +│ │ ├── issues.py # 부적합 사항 API +│ │ ├── daily_work.py # 일일 공수 API +│ │ └── reports.py # 보고서 API +│ ├── services/ # 비즈니스 로직 +│ │ ├── auth_service.py # 인증 서비스 +│ │ └── file_service.py # 파일 처리 서비스 +│ ├── uploads/ # 업로드된 파일 저장소 +│ ├── main.py # FastAPI 애플리케이션 진입점 +│ ├── requirements.txt # Python 의존성 +│ └── Dockerfile # 백엔드 Docker 설정 +├── frontend/ # 프론트엔드 +│ ├── static/ # 정적 파일 +│ │ ├── css/ # 스타일시트 +│ │ └── js/ # JavaScript 파일 +│ │ ├── api.js # API 통신 +│ │ ├── date-utils.js # 날짜 유틸리티 +│ │ └── image-utils.js # 이미지 처리 +│ ├── admin.html # 관리자 페이지 +│ ├── index.html # 메인 페이지 +│ ├── daily-work.html # 일일 공수 페이지 +│ ├── chart.html # 차트 페이지 +│ └── issue-view.html # 부적합 사항 상세 페이지 +├── nginx/ # Nginx 설정 +│ ├── nginx.conf # Nginx 설정 파일 +│ └── Dockerfile # Nginx Docker 설정 +├── docker-compose.yml # Docker Compose 설정 +├── DATABASE_SCHEMA.md # 데이터베이스 스키마 문서 +├── API_DOCUMENTATION.md # API 문서 +├── PROJECT_OVERVIEW.md # 프로젝트 개요 (이 파일) +└── README.md # 프로젝트 설명 +``` + +--- + +## 🚀 시작하기 + +### 1. 시스템 요구사항 +- Docker & Docker Compose +- Git + +### 2. 프로젝트 클론 및 실행 +```bash +# 프로젝트 클론 +git clone +cd M-Project + +# Docker 컨테이너 실행 +docker-compose up -d + +# 로그 확인 +docker-compose logs -f +``` + +### 3. 접속 정보 +- **프론트엔드:** http://localhost:16080 +- **백엔드 API:** http://localhost:16000 +- **API 문서:** http://localhost:16000/docs +- **데이터베이스:** localhost:16432 + +### 4. 기본 계정 +- **관리자:** hyungi / djg3-jj34-X3Q3 +- **일반 사용자:** inspector1 / pass123, inspector2 / pass456 + +--- + +## 👥 사용자 권한 + +### 관리자 (admin) +- ✅ 프로젝트 생성/수정/삭제 +- ✅ 사용자 계정 관리 +- ✅ 모든 부적합 사항 조회/수정 +- ✅ 보고서 생성 +- ✅ 시스템 설정 + +### 일반 사용자 (user) +- ✅ 부적합 사항 등록 +- ✅ 자신이 등록한 부적합 사항 수정 +- ✅ 일일 공수 입력 +- ✅ 보고서 조회 +- ❌ 프로젝트 관리 +- ❌ 사용자 관리 + +--- + +## 🔧 주요 기능 + +### 1. 프로젝트 관리 (신규 추가) +- **Job No.** 기반 프로젝트 생성 +- 관리자만 접근 가능한 프로젝트 생성 배너 +- 프로젝트 목록 조회 및 관리 + +### 2. 부적합 사항 관리 +- 사진 업로드 (최대 2장) +- 카테고리별 분류 + - 자재누락 (material_missing) + - 설계미스 (design_error) + - 입고자재 불량 (incoming_defect) + - 검사미스 (inspection_miss) +- 상태 관리 (신규/진행중/완료) +- 작업 시간 기록 + +### 3. 일일 공수 관리 +- 작업자 수 입력 +- 정규/야근 시간 계산 +- 날짜별 공수 추적 + +### 4. 보고서 생성 +- 기간별 통계 +- 카테고리별 분석 +- 작업 시간 집계 +- 인쇄 기능 + +--- + +## 🛠️ 개발 가이드 + +### 백엔드 개발 +```bash +# 백엔드 컨테이너 접속 +docker-compose exec backend bash + +# 의존성 설치 +pip install -r requirements.txt + +# 개발 서버 실행 +uvicorn main:app --reload --host 0.0.0.0 --port 8000 +``` + +### 프론트엔드 개발 +- 정적 파일은 `frontend/` 디렉토리에 위치 +- Nginx를 통해 서빙됨 +- 실시간 수정 가능 (볼륨 마운트) + +### 데이터베이스 관리 +```bash +# DB 접속 +docker-compose exec db psql -U mproject -d mproject + +# 마이그레이션 실행 +docker-compose exec db psql -U mproject -d mproject -f /docker-entrypoint-initdb.d/새파일.sql + +# 백업 +docker-compose exec db pg_dump -U mproject mproject > backup.sql + +# 복구 +docker-compose exec -T db psql -U mproject mproject < backup.sql +``` + +--- + +## 🔍 트러블슈팅 + +### 1. 백엔드가 시작되지 않는 경우 +```bash +# 로그 확인 +docker-compose logs backend + +# 컨테이너 재시작 +docker-compose restart backend +``` + +### 2. 데이터베이스 연결 오류 +```bash +# DB 상태 확인 +docker-compose ps db + +# DB 로그 확인 +docker-compose logs db + +# DB 재시작 +docker-compose restart db +``` + +### 3. 마이그레이션 오류 +```bash +# 수동 마이그레이션 실행 +docker-compose exec db psql -U mproject -d mproject -f /docker-entrypoint-initdb.d/파일명.sql +``` + +### 4. 파일 업로드 오류 +```bash +# uploads 디렉토리 권한 확인 +docker-compose exec backend ls -la /app/uploads + +# 권한 수정 +docker-compose exec backend chmod 755 /app/uploads +``` + +--- + +## 📊 모니터링 + +### 헬스체크 +```bash +# 백엔드 상태 확인 +curl http://localhost:16000/api/health + +# 프론트엔드 상태 확인 +curl http://localhost:16080 +``` + +### 로그 모니터링 +```bash +# 전체 로그 +docker-compose logs -f + +# 특정 서비스 로그 +docker-compose logs -f backend +docker-compose logs -f db +docker-compose logs -f nginx +``` + +--- + +## 🔐 보안 고려사항 + +### 1. 인증 +- JWT 토큰 기반 인증 +- 토큰 만료 시간: 7일 +- bcrypt 해시를 사용한 비밀번호 암호화 + +### 2. 권한 관리 +- 역할 기반 접근 제어 (RBAC) +- API 레벨에서 권한 검증 +- 프론트엔드 UI 레벨에서 권한별 표시/숨김 + +### 3. 데이터 보호 +- SQL 인젝션 방지 (SQLAlchemy ORM 사용) +- XSS 방지 (입력값 검증) +- 파일 업로드 검증 + +--- + +## 📈 성능 최적화 + +### 1. 데이터베이스 +- 자주 조회되는 컬럼에 인덱스 설정 +- 페이지네이션 구현 +- 연결 풀링 사용 + +### 2. API +- 응답 캐싱 +- 압축 전송 +- 비동기 처리 + +### 3. 프론트엔드 +- 이미지 최적화 +- 지연 로딩 +- 로컬 스토리지 활용 + +--- + +## 🚀 배포 가이드 + +### 프로덕션 환경 설정 +1. 환경 변수 수정 (`docker-compose.yml`) + - `SECRET_KEY` 변경 + - `ADMIN_PASSWORD` 변경 + - 데이터베이스 비밀번호 변경 + +2. SSL 인증서 설정 +3. 방화벽 설정 +4. 백업 스케줄 설정 + +### 업데이트 절차 +1. 코드 업데이트 +2. 데이터베이스 백업 +3. 마이그레이션 실행 +4. 컨테이너 재시작 +5. 헬스체크 확인 + +--- + +## 📞 지원 및 문의 + +### 개발자 정보 +- **개발자:** M-Project Team +- **이메일:** support@m-project.com +- **문서 업데이트:** 2024-10-24 + +### 버전 정보 +- **현재 버전:** 1.0.0 +- **최종 업데이트:** 2024-10-24 +- **호환성:** Docker 20.10+, Python 3.11+ diff --git a/README.md b/README.md index 49741c0..688a78b 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,12 @@ ## 🚀 빠른 시작 -1. 웹 서버 실행: +### Docker를 사용한 실행 (권장) + +1. Docker 컨테이너 실행: ```bash cd M-Project -python3 -m http.server 16080 +docker-compose up -d ``` 2. 브라우저에서 접속: @@ -16,26 +18,43 @@ http://localhost:16080 ``` 3. 로그인: +- **관리자**: `hyungi` / `djg3-jj34-X3Q3` - 검사자1: `inspector1` / `pass123` - 검사자2: `inspector2` / `pass456` -- 관리자: `admin` / `admin123` + +### 간단한 웹 서버 실행 (개발용) + +```bash +cd M-Project +python3 -m http.server 16080 +``` ## 📱 주요 기능 -### 1. 부적합 등록 (모바일 최적화) -- 사진 촬영 또는 업로드 -- 위치, 카테고리, 설명 입력 -- 긴급도 선택 (낮음/보통/높음) +### 1. 프로젝트 관리 (신규 추가 ✨) +- **Job No.** 기반 프로젝트 생성 +- 관리자 전용 프로젝트 생성 배너 +- 프로젝트 목록 조회 및 관리 -### 2. 목록 관리 +### 2. 부적합 등록 (모바일 최적화) +- 사진 촬영 또는 업로드 (최대 2장) +- 카테고리별 분류 (자재누락, 설계미스, 입고자재 불량, 검사미스) +- 상세 설명 입력 + +### 3. 목록 관리 - 등록된 부적합 사항 조회 - 작업 시간 입력 - 상태 변경 (신규→진행중→완료) - 추가 메모 작성 -### 3. 보고서 +### 4. 일일 공수 관리 +- 작업자 수 및 정규/야근 시간 입력 +- 날짜별 공수 추적 +- 자동 계산 기능 + +### 5. 보고서 - 작업 기간 및 총 공수 자동 계산 -- 긴급도별 통계 +- 카테고리별 통계 - 부적합 사항 상세 내역 - 인쇄 가능한 형식 @@ -50,10 +69,13 @@ M-Project/ ## 🛠️ 기술 스택 +- **Backend**: FastAPI (Python), SQLAlchemy, PostgreSQL - **Frontend**: HTML5, Tailwind CSS, JavaScript (Vanilla) -- **Storage**: LocalStorage (브라우저 로컬 저장) +- **Database**: PostgreSQL 15 +- **Container**: Docker & Docker Compose +- **Web Server**: Nginx +- **Authentication**: JWT (JSON Web Token) - **Icons**: Font Awesome -- **Charts**: Chart.js ## 📋 데이터 구조 @@ -79,16 +101,23 @@ M-Project/ ## ⚡ 특징 - **모바일 우선**: 현장에서 스마트폰으로 쉽게 입력 -- **오프라인 작동**: 인터넷 연결 없이도 사용 가능 -- **간단한 설치**: 별도의 서버나 데이터베이스 불필요 -- **즉시 사용**: 웹 서버만 실행하면 바로 사용 +- **실시간 데이터**: PostgreSQL 데이터베이스로 실시간 동기화 +- **권한 관리**: 관리자/일반 사용자 역할 기반 접근 제어 +- **Docker 기반**: 간편한 배포 및 확장성 +- **RESTful API**: 표준 API 설계로 확장 가능 -## 🔒 보안 주의사항 +## 🔒 보안 기능 -현재 버전은 프로토타입으로: -- 사용자 인증이 간단함 (하드코딩된 계정) -- 데이터가 브라우저에만 저장됨 -- 실제 운영 환경에서는 백엔드 서버 구축 필요 +- **JWT 인증**: 토큰 기반 보안 인증 시스템 +- **비밀번호 암호화**: bcrypt 해시를 사용한 안전한 비밀번호 저장 +- **권한 기반 접근**: 역할별 API 접근 제어 +- **SQL 인젝션 방지**: SQLAlchemy ORM 사용 + +## 📚 문서 + +- **[DATABASE_SCHEMA.md](DATABASE_SCHEMA.md)**: 데이터베이스 스키마 상세 문서 +- **[API_DOCUMENTATION.md](API_DOCUMENTATION.md)**: API 엔드포인트 문서 +- **[PROJECT_OVERVIEW.md](PROJECT_OVERVIEW.md)**: 프로젝트 전체 구조 및 가이드 ## 📝 라이선스 @@ -96,4 +125,5 @@ M-Project/ --- -작성일: 2025-09-17 +**최종 업데이트**: 2024-10-24 +**버전**: 1.0.0 (프로젝트 관리 기능 추가) diff --git a/backend/__pycache__/main.cpython-311.pyc b/backend/__pycache__/main.cpython-311.pyc index c495f2e..2533e85 100644 Binary files a/backend/__pycache__/main.cpython-311.pyc and b/backend/__pycache__/main.cpython-311.pyc differ diff --git a/backend/database/__pycache__/models.cpython-311.pyc b/backend/database/__pycache__/models.cpython-311.pyc index 093e30d..930b851 100644 Binary files a/backend/database/__pycache__/models.cpython-311.pyc and b/backend/database/__pycache__/models.cpython-311.pyc differ diff --git a/backend/database/__pycache__/schemas.cpython-311.pyc b/backend/database/__pycache__/schemas.cpython-311.pyc index 597b32c..ea07441 100644 Binary files a/backend/database/__pycache__/schemas.cpython-311.pyc and b/backend/database/__pycache__/schemas.cpython-311.pyc differ diff --git a/backend/database/models.py b/backend/database/models.py index 77943b2..c1a17a7 100644 --- a/backend/database/models.py +++ b/backend/database/models.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, Integer, String, DateTime, Float, Boolean, Text, ForeignKey, Enum +from sqlalchemy import Column, Integer, BigInteger, String, DateTime, Float, Boolean, Text, ForeignKey, Enum from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship from datetime import datetime, timezone, timedelta @@ -42,6 +42,7 @@ class User(Base): # Relationships issues = relationship("Issue", back_populates="reporter") daily_works = relationship("DailyWork", back_populates="created_by") + projects = relationship("Project", back_populates="created_by") class Issue(Base): __tablename__ = "issues" @@ -53,12 +54,28 @@ class Issue(Base): description = Column(Text, nullable=False) status = Column(Enum(IssueStatus), default=IssueStatus.new) reporter_id = Column(Integer, ForeignKey("users.id")) + project_id = Column(BigInteger, ForeignKey("projects.id")) report_date = Column(DateTime, default=get_kst_now) work_hours = Column(Float, default=0) detail_notes = Column(Text) # Relationships reporter = relationship("User", back_populates="issues") + project = relationship("Project", back_populates="issues") + +class Project(Base): + __tablename__ = "projects" + + id = Column(BigInteger, primary_key=True, index=True) + job_no = Column(String, unique=True, nullable=False, index=True) + project_name = Column(String, nullable=False) + created_by_id = Column(Integer, ForeignKey("users.id")) + created_at = Column(DateTime, default=get_kst_now) + is_active = Column(Boolean, default=True) + + # Relationships + created_by = relationship("User", back_populates="projects") + issues = relationship("Issue", back_populates="project") class DailyWork(Base): __tablename__ = "daily_works" diff --git a/backend/database/schemas.py b/backend/database/schemas.py index d5d3014..a26dad7 100644 --- a/backend/database/schemas.py +++ b/backend/database/schemas.py @@ -62,6 +62,7 @@ class LoginRequest(BaseModel): class IssueBase(BaseModel): category: IssueCategory description: str + project_id: Optional[int] = None class IssueCreate(IssueBase): photo: Optional[str] = None # Base64 encoded image @@ -70,6 +71,7 @@ class IssueCreate(IssueBase): class IssueUpdate(BaseModel): category: Optional[IssueCategory] = None description: Optional[str] = None + project_id: Optional[int] = None work_hours: Optional[float] = None detail_notes: Optional[str] = None status: Optional[IssueStatus] = None @@ -83,6 +85,8 @@ class Issue(IssueBase): status: IssueStatus reporter_id: int reporter: User + project_id: Optional[int] = None + # project: Optional['Project'] = None # 순환 참조 방지를 위해 제거 report_date: datetime work_hours: float detail_notes: Optional[str] = None @@ -90,6 +94,29 @@ class Issue(IssueBase): class Config: from_attributes = True +# Project schemas +class ProjectBase(BaseModel): + job_no: str = Field(..., min_length=1, max_length=50) + project_name: str = Field(..., min_length=1, max_length=200) + +class ProjectCreate(ProjectBase): + pass + +class ProjectUpdate(BaseModel): + project_name: Optional[str] = Field(None, min_length=1, max_length=200) + is_active: Optional[bool] = None + +class Project(ProjectBase): + id: int + created_by_id: int + created_by: User + created_at: datetime + is_active: bool + # issues: Optional[List['Issue']] = None # 순환 참조 방지를 위해 제거 + + class Config: + from_attributes = True + # Daily Work schemas class DailyWorkBase(BaseModel): date: datetime diff --git a/backend/main.py b/backend/main.py index ba57295..d28307f 100644 --- a/backend/main.py +++ b/backend/main.py @@ -5,7 +5,7 @@ import uvicorn from database.database import engine, get_db from database.models import Base -from routers import auth, issues, daily_work, reports +from routers import auth, issues, daily_work, reports, projects from services.auth_service import create_admin_user # 데이터베이스 테이블 생성 @@ -34,6 +34,7 @@ app.include_router(auth.router) app.include_router(issues.router) app.include_router(daily_work.router) app.include_router(reports.router) +app.include_router(projects.router) # 시작 시 관리자 계정 생성 @app.on_event("startup") diff --git a/backend/migrations/006_add_projects_table.sql b/backend/migrations/006_add_projects_table.sql new file mode 100644 index 0000000..1746f87 --- /dev/null +++ b/backend/migrations/006_add_projects_table.sql @@ -0,0 +1,14 @@ +-- 프로젝트 테이블 생성 +CREATE TABLE IF NOT EXISTS projects ( + id SERIAL PRIMARY KEY, + job_no VARCHAR(50) UNIQUE NOT NULL, + project_name VARCHAR(200) NOT NULL, + created_by_id INTEGER REFERENCES users(id), + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + is_active BOOLEAN DEFAULT TRUE +); + +-- 인덱스 생성 +CREATE INDEX IF NOT EXISTS idx_projects_job_no ON projects(job_no); +CREATE INDEX IF NOT EXISTS idx_projects_created_by_id ON projects(created_by_id); +CREATE INDEX IF NOT EXISTS idx_projects_is_active ON projects(is_active); diff --git a/backend/migrations/007_add_project_id_to_issues.sql b/backend/migrations/007_add_project_id_to_issues.sql new file mode 100644 index 0000000..725354b --- /dev/null +++ b/backend/migrations/007_add_project_id_to_issues.sql @@ -0,0 +1,15 @@ +-- 부적합 사항 테이블에 프로젝트 ID 컬럼 추가 +ALTER TABLE issues ADD COLUMN project_id INTEGER; + +-- 외래키 제약조건 추가 +ALTER TABLE issues ADD CONSTRAINT fk_issues_project_id + FOREIGN KEY (project_id) REFERENCES projects(id); + +-- 인덱스 생성 +CREATE INDEX IF NOT EXISTS idx_issues_project_id ON issues(project_id); + +-- 기존 부적합 사항들을 첫 번째 프로젝트로 할당 (있는 경우) +UPDATE issues +SET project_id = (SELECT id FROM projects ORDER BY created_at LIMIT 1) +WHERE project_id IS NULL +AND EXISTS (SELECT 1 FROM projects LIMIT 1); diff --git a/backend/migrations/008_fix_project_id_bigint.sql b/backend/migrations/008_fix_project_id_bigint.sql new file mode 100644 index 0000000..5401534 --- /dev/null +++ b/backend/migrations/008_fix_project_id_bigint.sql @@ -0,0 +1,13 @@ +-- project_id 컬럼을 BIGINT로 변경 +ALTER TABLE issues ALTER COLUMN project_id TYPE BIGINT; + +-- projects 테이블의 id도 BIGINT로 변경 (일관성을 위해) +ALTER TABLE projects ALTER COLUMN id TYPE BIGINT; + +-- 외래키 제약조건 재생성 (타입 변경으로 인해 필요) +ALTER TABLE issues DROP CONSTRAINT IF EXISTS fk_issues_project_id; +ALTER TABLE issues ADD CONSTRAINT fk_issues_project_id + FOREIGN KEY (project_id) REFERENCES projects(id); + +-- 다른 테이블들도 확인하여 project_id 참조하는 곳이 있으면 수정 +-- (현재는 issues 테이블만 project_id를 가지고 있음) diff --git a/backend/routers/__pycache__/projects.cpython-311.pyc b/backend/routers/__pycache__/projects.cpython-311.pyc new file mode 100644 index 0000000..1005bdd Binary files /dev/null and b/backend/routers/__pycache__/projects.cpython-311.pyc differ diff --git a/backend/routers/projects.py b/backend/routers/projects.py new file mode 100644 index 0000000..7896290 --- /dev/null +++ b/backend/routers/projects.py @@ -0,0 +1,126 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from typing import List +from database.database import get_db +from database.models import Project, User, UserRole +from database.schemas import ProjectCreate, ProjectUpdate, Project as ProjectSchema +from routers.auth import get_current_user + +router = APIRouter( + prefix="/api/projects", + tags=["projects"] +) + +def check_admin_permission(current_user: User = Depends(get_current_user)): + """관리자 권한 확인""" + if current_user.role != UserRole.admin: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="관리자 권한이 필요합니다." + ) + return current_user + +@router.post("/", response_model=ProjectSchema) +async def create_project( + project: ProjectCreate, + db: Session = Depends(get_db), + current_user: User = Depends(check_admin_permission) +): + """프로젝트 생성 (관리자만)""" + # Job No. 중복 확인 + existing_project = db.query(Project).filter(Project.job_no == project.job_no).first() + if existing_project: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="이미 존재하는 Job No.입니다." + ) + + # 프로젝트 생성 + db_project = Project( + job_no=project.job_no, + project_name=project.project_name, + created_by_id=current_user.id + ) + + db.add(db_project) + db.commit() + db.refresh(db_project) + + return db_project + +@router.get("/", response_model=List[ProjectSchema]) +async def get_projects( + skip: int = 0, + limit: int = 100, + active_only: bool = True, + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user) +): + """프로젝트 목록 조회""" + query = db.query(Project) + + if active_only: + query = query.filter(Project.is_active == True) + + projects = query.offset(skip).limit(limit).all() + return projects + +@router.get("/{project_id}", response_model=ProjectSchema) +async def get_project( + project_id: int, + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user) +): + """특정 프로젝트 조회""" + project = db.query(Project).filter(Project.id == project_id).first() + if not project: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="프로젝트를 찾을 수 없습니다." + ) + return project + +@router.put("/{project_id}", response_model=ProjectSchema) +async def update_project( + project_id: int, + project_update: ProjectUpdate, + db: Session = Depends(get_db), + current_user: User = Depends(check_admin_permission) +): + """프로젝트 수정 (관리자만)""" + project = db.query(Project).filter(Project.id == project_id).first() + if not project: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="프로젝트를 찾을 수 없습니다." + ) + + # 업데이트할 필드만 수정 + update_data = project_update.dict(exclude_unset=True) + for field, value in update_data.items(): + setattr(project, field, value) + + db.commit() + db.refresh(project) + + return project + +@router.delete("/{project_id}") +async def delete_project( + project_id: int, + db: Session = Depends(get_db), + current_user: User = Depends(check_admin_permission) +): + """프로젝트 삭제 (비활성화) (관리자만)""" + project = db.query(Project).filter(Project.id == project_id).first() + if not project: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="프로젝트를 찾을 수 없습니다." + ) + + # 실제 삭제 대신 비활성화 + project.is_active = False + db.commit() + + return {"message": "프로젝트가 삭제되었습니다."} diff --git a/frontend/create-project-api.html b/frontend/create-project-api.html new file mode 100644 index 0000000..9525173 --- /dev/null +++ b/frontend/create-project-api.html @@ -0,0 +1,193 @@ + + + + + + API 프로젝트 생성 - M Project + + + + + +
+
+

+ API 프로젝트 생성 +

+ +
+
+

🎯 작업 내용

+

+ 백엔드 API를 통해 TKR-25009R M Project를 생성합니다.
+ 데이터베이스에 실제 프로젝트 레코드가 생성됩니다. +

+
+ +
+ +
+ + + + + API 데이터 수정 도구로 이동 + + + + 디버그 도구로 이동 + +
+
+
+ + + + diff --git a/frontend/daily-work.html b/frontend/daily-work.html index 2feb851..8e9aa7f 100644 --- a/frontend/daily-work.html +++ b/frontend/daily-work.html @@ -98,11 +98,14 @@

- 작업보고서 시스템 + 작업보고서

- +
+ + +
@@ -126,6 +129,9 @@ + @@ -152,65 +158,34 @@ id="workDate" class="input-field w-full px-4 py-3 rounded-lg text-lg" required + onchange="loadExistingData()" > - +
- - -

기본 근무시간: 8시간/인

-
- - -
- - +
`; - container.appendChild(div); - }); + + return div; } // 수정 상태 표시 @@ -1013,17 +1310,25 @@ } try { - // 작업 시간만 업데이트 + // 작업 시간 업데이트 및 검토 완료 상태로 변경 await IssuesAPI.update(issueId, { - work_hours: workHours + work_hours: workHours, + status: 'complete' // 검토 완료 상태로 변경 }); - // 성공 시 데이터 다시 로드 - await loadIssues(); + // 로컬 데이터도 업데이트 + const issue = issues.find(i => i.id === issueId); + if (issue) { + issue.work_hours = workHours; + issue.status = 'complete'; + issue.reviewed_at = new Date().toISOString(); // 검토 완료 시간 기록 + } + + // 성공 시 목록 다시 표시 displayIssueList(); // 성공 메시지 - showToastMessage(`${workHours}시간 저장 완료!`, 'success'); + showToastMessage(`${workHours}시간 저장 및 검토 완료!`, 'success'); } catch (error) { alert(error.message || '저장에 실패했습니다.'); @@ -1151,39 +1456,81 @@ async function generateReport() { const container = document.getElementById('reportContent'); + // 선택된 프로젝트 가져오기 + const selectedProjectId = document.getElementById('reportProjectFilter').value; + + // 프로젝트별 필터링된 부적합 사항 + let filteredIssues = issues; + if (selectedProjectId) { + filteredIssues = issues.filter(issue => { + const issueProjectId = issue.project_id || issue.projectId; + return issueProjectId && (issueProjectId == selectedProjectId || issueProjectId.toString() === selectedProjectId.toString()); + }); + } + // 날짜 범위 계산 - const dates = issues.map(i => new Date(i.report_date)); + const dates = filteredIssues.map(i => new Date(i.report_date)); const startDate = dates.length > 0 ? new Date(Math.min(...dates)) : new Date(); const endDate = new Date(); - // 일일 공수 데이터 가져오기 + // 프로젝트별 일일 공수 데이터 계산 let dailyWorkTotal = 0; - try { - const dailyWorks = await DailyWorkAPI.getAll({ - start_date: startDate.toISOString().split('T')[0], - end_date: endDate.toISOString().split('T')[0] + const dailyWorkData = JSON.parse(localStorage.getItem('daily-work-data') || '[]'); + + console.log('일일공수 데이터:', dailyWorkData); + console.log('선택된 프로젝트 ID:', selectedProjectId); + + if (selectedProjectId) { + // 선택된 프로젝트의 일일 공수만 합계 + dailyWorkData.forEach(dayWork => { + console.log('일일공수 항목:', dayWork); + if (dayWork.projects) { + dayWork.projects.forEach(project => { + console.log('프로젝트:', project, '매칭 확인:', project.projectId == selectedProjectId); + if (project.projectId == selectedProjectId || project.projectId.toString() === selectedProjectId.toString()) { + dailyWorkTotal += project.hours || 0; + console.log('시간 추가:', project.hours, '누적:', dailyWorkTotal); + } + }); + } + }); + } else { + // 전체 프로젝트의 일일 공수 합계 + dailyWorkData.forEach(dayWork => { + console.log('전체 일일공수 항목:', dayWork); + dailyWorkTotal += dayWork.totalHours || 0; }); - dailyWorkTotal = dailyWorks.reduce((sum, work) => sum + work.total_hours, 0); - } catch (error) { - console.error('일일 공수 데이터 로드 실패:', error); } - // 부적합 사항 해결 시간 계산 - const issueHours = issues.reduce((sum, issue) => sum + issue.work_hours, 0); + console.log('최종 일일공수 합계:', dailyWorkTotal); + + // 부적합 사항 해결 시간 계산 (필터링된 이슈만) + const issueHours = filteredIssues.reduce((sum, issue) => sum + (issue.work_hours || 0), 0); const categoryCount = {}; - issues.forEach(issue => { + filteredIssues.forEach(issue => { categoryCount[issue.category] = (categoryCount[issue.category] || 0) + 1; }); // 부적합 시간 비율 계산 const issuePercentage = dailyWorkTotal > 0 ? ((issueHours / dailyWorkTotal) * 100).toFixed(1) : 0; + // 선택된 프로젝트 정보 + let projectInfo = '전체 프로젝트'; + if (selectedProjectId) { + const projects = JSON.parse(localStorage.getItem('work-report-projects') || '[]'); + const selectedProject = projects.find(p => p.id == selectedProjectId); + if (selectedProject) { + projectInfo = `${selectedProject.jobNo} - ${selectedProject.projectName}`; + } + } + container.innerHTML = `
-

작업 보고서

+

작업 보고서

+

${projectInfo}

@@ -1254,7 +1601,7 @@

부적합 사항 상세

- ${issues.map(issue => { + ${filteredIssues.map(issue => { const categoryNames = { material_missing: '자재누락', design_error: '설계미스', diff --git a/frontend/issue-view.html b/frontend/issue-view.html index 73722a9..bd090b9 100644 --- a/frontend/issue-view.html +++ b/frontend/issue-view.html @@ -45,41 +45,72 @@ -webkit-line-clamp: 2; -webkit-box-orient: vertical; } + + .nav-link { + color: #6b7280; + padding: 0.5rem 1rem; + border-radius: 0.5rem; + transition: all 0.2s; + text-decoration: none; + } + + .nav-link:hover { + background-color: #f3f4f6; + color: #3b82f6; + } + + .nav-link.active { + background-color: #3b82f6; + color: white; + } - -
+
+
+ + + + diff --git a/frontend/project-management.html b/frontend/project-management.html new file mode 100644 index 0000000..8ecf77e --- /dev/null +++ b/frontend/project-management.html @@ -0,0 +1,350 @@ + + + + + + 프로젝트 관리 - 작업보고서 시스템 + + + + + + +
+
+
+

+ 프로젝트 관리 +

+ +
+
+
+ + +
+ +
+

+ 새 프로젝트 생성 +

+ +
+
+ + +
+ +
+ + +
+ +
+ +
+
+
+ + +
+
+

프로젝트 목록

+ +
+ +
+ +
+
+
+ + + + diff --git a/frontend/static/js/api.js b/frontend/static/js/api.js index e4da282..844eb8b 100644 --- a/frontend/static/js/api.js +++ b/frontend/static/js/api.js @@ -239,3 +239,27 @@ function checkAdminAuth() { } return user; } + +// 프로젝트 API +const ProjectsAPI = { + getAll: (activeOnly = false) => { + const params = activeOnly ? '?active_only=true' : ''; + return apiRequest(`/projects${params}`); + }, + + get: (id) => apiRequest(`/projects/${id}`), + + create: (projectData) => apiRequest('/projects', { + method: 'POST', + body: JSON.stringify(projectData) + }), + + update: (id, projectData) => apiRequest(`/projects/${id}`, { + method: 'PUT', + body: JSON.stringify(projectData) + }), + + delete: (id) => apiRequest(`/projects/${id}`, { + method: 'DELETE' + }) +};