diff --git a/DEPLOYMENT_GUIDE_20251028.md b/DEPLOYMENT_GUIDE_20251028.md new file mode 100644 index 0000000..627ca46 --- /dev/null +++ b/DEPLOYMENT_GUIDE_20251028.md @@ -0,0 +1,240 @@ +# 배포 가이드 - 2025.10.28 업데이트 + +## 📋 **변경사항 요약** + +### 🎯 **주요 기능 개선** +- **보고서 시스템** 구현 (일일/주간/월간 보고서) +- **품질팀용 일일보고서 엑셀 내보내기** 기능 +- **Project 모델 속성명 수정** (name → project_name) +- **API URL 절대 경로 사용** 개선 + +--- + +## 🗄️ **데이터베이스 변경사항** + +### **⚠️ 중요: 스키마 불일치 수정** +- `Project` 모델에서 `name` 속성이 `project_name`으로 변경됨 +- 백엔드 코드에서 `project.name` → `project.project_name` 수정 필요 + +### **새로운 의존성** +```txt +# backend/requirements.txt에 추가됨 +openpyxl==3.1.2 # 엑셀 파일 생성용 +``` + +--- + +## 🔧 **백엔드 변경사항** + +### **1. 스키마 업데이트** +- `backend/database/schemas.py` + - `DailyReportRequest` 클래스 추가 + - `DailyReportStats` 클래스 추가 + +### **2. API 엔드포인트 추가** +- `backend/routers/reports.py` + - `POST /api/reports/daily-export` 엔드포인트 추가 +- `backend/routers/management.py` + - `GET /api/management/stats` 엔드포인트 추가 + +### **3. 중요 수정사항** +- `backend/routers/reports.py`에서 `project.name` → `project.project_name` 수정 (3곳) + +--- + +## 🎨 **프론트엔드 변경사항** + +### **1. 새로운 페이지 추가** +- `frontend/reports.html` - 보고서 메인 페이지 +- `frontend/reports-daily.html` - 일일보고서 생성 페이지 +- `frontend/reports-weekly.html` - 주간보고서 페이지 (준비중) +- `frontend/reports-monthly.html` - 월간보고서 페이지 (준비중) + +### **2. 공통 헤더 개선** +- `frontend/static/js/components/common-header.js` + - 보고서 서브메뉴 추가 (일일/주간/월간) + +### **3. API URL 절대 경로 수정** +- 모든 프론트엔드 파일에서 `window.API_BASE_URL` 사용 +- 상대 경로 `/api/` → 절대 경로 `http://localhost:16080/api` 변경 + +### **4. 캐시 무효화** +- `frontend/sw.js` 버전 업데이트 (`v1.0.0` → `v1.0.1`) + +--- + +## 🚀 **배포 절차** + +### **1. 사전 준비** +```bash +# 1. 현재 데이터베이스 백업 +docker-compose exec postgres pg_dump -U postgres -d m_project > backup_$(date +%Y%m%d_%H%M%S).sql + +# 2. Git 최신 코드 pull +git pull origin master +``` + +### **2. 백엔드 배포** +```bash +# 1. Docker 컨테이너 중지 +docker-compose down + +# 2. 이미지 재빌드 (새로운 의존성 포함) +docker-compose build backend + +# 3. 컨테이너 시작 +docker-compose up -d + +# 4. 백엔드 로그 확인 +docker-compose logs backend --tail=20 +``` + +### **3. 프론트엔드 배포** +```bash +# 1. Nginx 재시작 (캐시 무효화) +docker-compose restart nginx + +# 2. 브라우저 캐시 강제 새로고침 안내 +# 사용자들에게 Ctrl+Shift+F5 또는 Cmd+Shift+R 안내 +``` + +### **4. 배포 후 검증** +```bash +# 1. 백엔드 상태 확인 +docker-compose logs backend --tail=20 + +# 2. 새로운 API 엔드포인트 테스트 +curl -X GET "http://localhost:16080/api/management/stats?project_id=1" \ + -H "Authorization: Bearer YOUR_TOKEN" + +# 3. 엑셀 내보내기 테스트 +curl -X POST "http://localhost:16080/api/reports/daily-export" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -d '{"project_id": 1}' \ + --output test_report.xlsx +``` + +--- + +## ✅ **기능 테스트 체크리스트** + +### **보고서 시스템 테스트** +- [ ] 보고서 메인 페이지 접근 확인 +- [ ] 일일보고서 페이지 로드 확인 +- [ ] 프로젝트 목록 정상 표시 확인 +- [ ] 프로젝트 선택 시 통계 표시 확인 +- [ ] 엑셀 파일 다운로드 정상 작동 확인 +- [ ] 엑셀 파일 내용 및 형식 확인 + +### **기존 기능 회귀 테스트** +- [ ] 대시보드 정상 로드 확인 +- [ ] 관리함 정상 작동 확인 +- [ ] 수신함 정상 작동 확인 +- [ ] 일일 공수 정상 작동 확인 + +--- + +## 🔍 **트러블슈팅** + +### **일반적인 문제들** + +#### **1. Project.name 속성 오류** +```bash +# 증상: "Project object has no attribute 'name'" 오류 +# 원인: 백엔드에서 project.name 사용 +# 해결: 백엔드 재시작 후 확인 +docker-compose restart backend +docker-compose logs backend --tail=20 +``` + +#### **2. 프로젝트 목록 표시 안됨** +```bash +# 증상: 드롭다운에 프로젝트가 나타나지 않음 +# 원인: API 경로 문제 또는 캐시 문제 +# 해결: +# 1. 브라우저 강제 새로고침 (Ctrl+Shift+F5) +# 2. 개발자 도구에서 네트워크 탭 확인 +# 3. API 호출 URL이 올바른지 확인 +``` + +#### **3. 엑셀 파일 생성 실패** +```bash +# 증상: 500 Internal Server Error +# 원인: openpyxl 모듈 누락 +# 해결: 백엔드 이미지 재빌드 +docker-compose build backend +docker-compose up -d backend +``` + +#### **4. 공통 헤더 로드 실패** +```bash +# 증상: TypeError: window.commonHeader.init is not a function +# 원인: 스크립트 로드 순서 문제 +# 해결: 페이지 새로고침 또는 캐시 클리어 +``` + +#### **5. API 연결 실패 (CORS 오류)** +```bash +# 증상: "Fetch API cannot load http://localhost/api/" +# 원인: API URL 설정 문제 +# 해결: +# 1. 브라우저 캐시 클리어 +# 2. 서비스 워커 캐시 무효화 확인 +# 3. nginx 재시작 +docker-compose restart nginx +``` + +--- + +## 📊 **성능 모니터링** + +### **모니터링 포인트** +```bash +# 1. 백엔드 메모리 사용량 확인 +docker stats m-project-backend + +# 2. 데이터베이스 연결 상태 확인 +docker-compose exec postgres psql -U postgres -d m_project -c "SELECT count(*) FROM pg_stat_activity;" + +# 3. 엑셀 생성 성능 확인 (대용량 데이터) +time curl -X POST "http://localhost:16080/api/reports/daily-export" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -d '{"project_id": 1}' \ + --output performance_test.xlsx +``` + +--- + +## 📞 **지원 연락처** +- 개발자: [개발자 연락처] +- 배포 담당자: [배포 담당자 연락처] + +--- + +**⚠️ 주의사항:** +1. **반드시 데이터베이스 백업 후 배포 진행** +2. **백엔드 이미지 재빌드 필수** (새로운 의존성 포함) +3. **브라우저 캐시 무효화 안내** 필요 +4. **Project.name → project_name 수정사항 확인** +5. **배포 후 보고서 기능 전체 테스트 필수** + +--- + +## 🔄 **롤백 절차** +문제 발생 시 다음 순서로 롤백: + +```bash +# 1. 이전 버전으로 코드 롤백 +git checkout [이전_커밋_해시] + +# 2. 백엔드 이미지 재빌드 +docker-compose build backend + +# 3. 컨테이너 재시작 +docker-compose up -d + +# 4. 데이터베이스 롤백 (필요시) +# 백업 파일에서 복원 +``` diff --git a/backend/__pycache__/main.cpython-311.pyc b/backend/__pycache__/main.cpython-311.pyc index 693e096..ba44a63 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__/database.cpython-311.pyc b/backend/database/__pycache__/database.cpython-311.pyc index 44c2a05..8e59265 100644 Binary files a/backend/database/__pycache__/database.cpython-311.pyc and b/backend/database/__pycache__/database.cpython-311.pyc differ diff --git a/backend/database/__pycache__/models.cpython-311.pyc b/backend/database/__pycache__/models.cpython-311.pyc index dd97620..b9ca9e2 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 1576070..267a671 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/schemas.py b/backend/database/schemas.py index 3f54649..650625a 100644 --- a/backend/database/schemas.py +++ b/backend/database/schemas.py @@ -324,3 +324,13 @@ class ProjectDailyWork(ProjectDailyWorkBase): class Config: from_attributes = True + +# 일일보고서 관련 스키마 +class DailyReportRequest(BaseModel): + project_id: int + +class DailyReportStats(BaseModel): + total_count: int = 0 + management_count: int = 0 # 관리처리 현황 (진행 중) + completed_count: int = 0 + delayed_count: int = 0 diff --git a/backend/requirements.txt b/backend/requirements.txt index 12c373f..d2106d5 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -11,3 +11,4 @@ pydantic-settings==2.1.0 pillow==10.1.0 pillow-heif==0.13.0 reportlab==4.0.7 +openpyxl==3.1.2 diff --git a/backend/routers/__pycache__/auth.cpython-311.pyc b/backend/routers/__pycache__/auth.cpython-311.pyc index a70b93a..dcd7677 100644 Binary files a/backend/routers/__pycache__/auth.cpython-311.pyc and b/backend/routers/__pycache__/auth.cpython-311.pyc differ diff --git a/backend/routers/__pycache__/daily_work.cpython-311.pyc b/backend/routers/__pycache__/daily_work.cpython-311.pyc index 1963909..2b3e23f 100644 Binary files a/backend/routers/__pycache__/daily_work.cpython-311.pyc and b/backend/routers/__pycache__/daily_work.cpython-311.pyc differ diff --git a/backend/routers/__pycache__/inbox.cpython-311.pyc b/backend/routers/__pycache__/inbox.cpython-311.pyc index e0b9b15..71b14d9 100644 Binary files a/backend/routers/__pycache__/inbox.cpython-311.pyc and b/backend/routers/__pycache__/inbox.cpython-311.pyc differ diff --git a/backend/routers/__pycache__/issues.cpython-311.pyc b/backend/routers/__pycache__/issues.cpython-311.pyc index 6440f8a..067221c 100644 Binary files a/backend/routers/__pycache__/issues.cpython-311.pyc and b/backend/routers/__pycache__/issues.cpython-311.pyc differ diff --git a/backend/routers/__pycache__/management.cpython-311.pyc b/backend/routers/__pycache__/management.cpython-311.pyc index 0d8402c..d652427 100644 Binary files a/backend/routers/__pycache__/management.cpython-311.pyc and b/backend/routers/__pycache__/management.cpython-311.pyc differ diff --git a/backend/routers/__pycache__/page_permissions.cpython-311.pyc b/backend/routers/__pycache__/page_permissions.cpython-311.pyc index 28b228b..185d095 100644 Binary files a/backend/routers/__pycache__/page_permissions.cpython-311.pyc and b/backend/routers/__pycache__/page_permissions.cpython-311.pyc differ diff --git a/backend/routers/__pycache__/projects.cpython-311.pyc b/backend/routers/__pycache__/projects.cpython-311.pyc index 9cdddd0..94c4787 100644 Binary files a/backend/routers/__pycache__/projects.cpython-311.pyc and b/backend/routers/__pycache__/projects.cpython-311.pyc differ diff --git a/backend/routers/__pycache__/reports.cpython-311.pyc b/backend/routers/__pycache__/reports.cpython-311.pyc index 030083c..1eaf81a 100644 Binary files a/backend/routers/__pycache__/reports.cpython-311.pyc and b/backend/routers/__pycache__/reports.cpython-311.pyc differ diff --git a/backend/routers/management.py b/backend/routers/management.py index 021a508..069b0ce 100644 --- a/backend/routers/management.py +++ b/backend/routers/management.py @@ -6,7 +6,7 @@ from typing import List from database.database import get_db from database.models import Issue, User, ReviewStatus from database.schemas import ( - ManagementUpdateRequest, AdditionalInfoUpdateRequest, Issue as IssueSchema + ManagementUpdateRequest, AdditionalInfoUpdateRequest, Issue as IssueSchema, DailyReportStats ) from routers.auth import get_current_user from routers.page_permissions import check_page_access @@ -172,3 +172,41 @@ async def get_additional_info( "additional_info_updated_at": issue.additional_info_updated_at, "additional_info_updated_by_id": issue.additional_info_updated_by_id } + +@router.get("/stats", response_model=DailyReportStats) +async def get_management_stats( + project_id: int, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """ + 프로젝트별 관리함 통계 조회 (보고서용) + """ + # 관리함 페이지 권한 확인 + if not check_page_access(current_user.id, 'issues_management', db): + raise HTTPException(status_code=403, detail="관리함 접근 권한이 없습니다.") + + # 해당 프로젝트의 관리함 이슈들 조회 + issues = db.query(Issue).filter( + Issue.project_id == project_id, + Issue.review_status.in_([ReviewStatus.in_progress, ReviewStatus.completed]) + ).all() + + # 통계 계산 + stats = DailyReportStats() + today = datetime.now().date() + + for issue in issues: + stats.total_count += 1 + + if issue.review_status == ReviewStatus.in_progress: + stats.management_count += 1 + + # 지연 여부 확인 + if issue.expected_completion_date and issue.expected_completion_date < today: + stats.delayed_count += 1 + + elif issue.review_status == ReviewStatus.completed: + stats.completed_count += 1 + + return stats diff --git a/backend/routers/reports.py b/backend/routers/reports.py index e3dbad5..69bc373 100644 --- a/backend/routers/reports.py +++ b/backend/routers/reports.py @@ -1,11 +1,16 @@ from fastapi import APIRouter, Depends, HTTPException +from fastapi.responses import StreamingResponse from sqlalchemy.orm import Session -from sqlalchemy import func -from datetime import datetime +from sqlalchemy import func, and_, or_ +from datetime import datetime, date from typing import List +import io +from openpyxl import Workbook +from openpyxl.styles import Font, PatternFill, Alignment, Border, Side +from openpyxl.utils import get_column_letter from database.database import get_db -from database.models import Issue, DailyWork, IssueStatus, IssueCategory, User, UserRole +from database.models import Issue, DailyWork, IssueStatus, IssueCategory, User, UserRole, Project, ReviewStatus from database import schemas from routers.auth import get_current_user @@ -128,3 +133,222 @@ async def get_report_daily_works( "overtime_total": work.overtime_total, "total_hours": work.total_hours } for work in works] + +@router.post("/daily-export") +async def export_daily_report( + request: schemas.DailyReportRequest, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """품질팀용 일일보고서 엑셀 내보내기""" + + # 권한 확인 (품질팀만 접근 가능) + if current_user.role != UserRole.admin: + raise HTTPException(status_code=403, detail="품질팀만 접근 가능합니다") + + # 프로젝트 확인 + project = db.query(Project).filter(Project.id == request.project_id).first() + if not project: + raise HTTPException(status_code=404, detail="프로젝트를 찾을 수 없습니다") + + # 관리함 데이터 조회 (진행 중 + 완료됨) + issues_query = db.query(Issue).filter( + Issue.project_id == request.project_id, + Issue.review_status.in_([ReviewStatus.in_progress, ReviewStatus.completed]) + ).order_by(Issue.report_date.desc()) + + issues = issues_query.all() + + # 통계 계산 + stats = calculate_project_stats(issues) + + # 엑셀 파일 생성 + wb = Workbook() + ws = wb.active + ws.title = "일일보고서" + + # 스타일 정의 + header_font = Font(bold=True, color="FFFFFF") + header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid") + stats_font = Font(bold=True, size=12) + stats_fill = PatternFill(start_color="E7E6E6", end_color="E7E6E6", fill_type="solid") + border = Border( + left=Side(style='thin'), + right=Side(style='thin'), + top=Side(style='thin'), + bottom=Side(style='thin') + ) + center_alignment = Alignment(horizontal='center', vertical='center') + + # 제목 및 기본 정보 + ws.merge_cells('A1:L1') + ws['A1'] = f"{project.project_name} - 일일보고서" + ws['A1'].font = Font(bold=True, size=16) + ws['A1'].alignment = center_alignment + + ws.merge_cells('A2:L2') + ws['A2'] = f"생성일: {datetime.now().strftime('%Y년 %m월 %d일')}" + ws['A2'].alignment = center_alignment + + # 프로젝트 통계 (4행부터) + ws.merge_cells('A4:L4') + ws['A4'] = "프로젝트 현황" + ws['A4'].font = stats_font + ws['A4'].fill = stats_fill + ws['A4'].alignment = center_alignment + + # 통계 데이터 + stats_row = 5 + ws[f'A{stats_row}'] = "총 신고 수량" + ws[f'B{stats_row}'] = stats.total_count + ws[f'D{stats_row}'] = "관리처리 현황" + ws[f'E{stats_row}'] = stats.management_count + ws[f'G{stats_row}'] = "완료 현황" + ws[f'H{stats_row}'] = stats.completed_count + ws[f'J{stats_row}'] = "지연 중" + ws[f'K{stats_row}'] = stats.delayed_count + + # 통계 스타일 적용 + for col in ['A', 'D', 'G', 'J']: + ws[f'{col}{stats_row}'].font = Font(bold=True) + ws[f'{col}{stats_row}'].fill = PatternFill(start_color="FFF2CC", end_color="FFF2CC", fill_type="solid") + + # 데이터 테이블 헤더 (7행부터) + headers = [ + "번호", "프로젝트", "부적합명", "상세내용", "원인분류", + "해결방안", "담당부서", "담당자", "마감일", "상태", + "신고일", "완료일" + ] + + header_row = 7 + for col, header in enumerate(headers, 1): + cell = ws.cell(row=header_row, column=col, value=header) + cell.font = header_font + cell.fill = header_fill + cell.alignment = center_alignment + cell.border = border + + # 데이터 입력 + current_row = header_row + 1 + + for issue in issues: + # 완료됨 항목의 첫 내보내기 여부 확인 (실제로는 DB에 플래그를 저장해야 함) + # 지금은 모든 완료됨 항목을 포함 + + ws.cell(row=current_row, column=1, value=issue.id) + ws.cell(row=current_row, column=2, value=project.project_name) + ws.cell(row=current_row, column=3, value=issue.nonconformity_name or "") + ws.cell(row=current_row, column=4, value=issue.detail_notes or "") + ws.cell(row=current_row, column=5, value=get_category_text(issue.category)) + ws.cell(row=current_row, column=6, value=issue.solution or "") + ws.cell(row=current_row, column=7, value=get_department_text(issue.responsible_department)) + ws.cell(row=current_row, column=8, value=issue.responsible_person or "") + ws.cell(row=current_row, column=9, value=issue.expected_completion_date.strftime('%Y-%m-%d') if issue.expected_completion_date else "") + ws.cell(row=current_row, column=10, value=get_status_text(issue.review_status)) + ws.cell(row=current_row, column=11, value=issue.report_date.strftime('%Y-%m-%d') if issue.report_date else "") + ws.cell(row=current_row, column=12, value=issue.completion_date.strftime('%Y-%m-%d') if issue.completion_date else "") + + # 상태별 색상 적용 + status_color = get_status_color(issue.review_status) + if status_color: + for col in range(1, len(headers) + 1): + ws.cell(row=current_row, column=col).fill = PatternFill( + start_color=status_color, end_color=status_color, fill_type="solid" + ) + + # 테두리 적용 + for col in range(1, len(headers) + 1): + ws.cell(row=current_row, column=col).border = border + ws.cell(row=current_row, column=col).alignment = Alignment(vertical='center') + + current_row += 1 + + # 열 너비 자동 조정 + for col in range(1, len(headers) + 1): + column_letter = get_column_letter(col) + ws.column_dimensions[column_letter].width = 15 + + # 특정 열 너비 조정 + ws.column_dimensions['C'].width = 20 # 부적합명 + ws.column_dimensions['D'].width = 30 # 상세내용 + ws.column_dimensions['F'].width = 25 # 해결방안 + + # 엑셀 파일을 메모리에 저장 + excel_buffer = io.BytesIO() + wb.save(excel_buffer) + excel_buffer.seek(0) + + # 파일명 생성 + today = date.today().strftime('%Y%m%d') + filename = f"{project.project_name}_일일보고서_{today}.xlsx" + + return StreamingResponse( + io.BytesIO(excel_buffer.read()), + media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + headers={"Content-Disposition": f"attachment; filename={filename}"} + ) + +def calculate_project_stats(issues: List[Issue]) -> schemas.DailyReportStats: + """프로젝트 통계 계산""" + stats = schemas.DailyReportStats() + + today = date.today() + + for issue in issues: + stats.total_count += 1 + + if issue.review_status == ReviewStatus.in_progress: + stats.management_count += 1 + + # 지연 여부 확인 + if issue.expected_completion_date and issue.expected_completion_date < today: + stats.delayed_count += 1 + + elif issue.review_status == ReviewStatus.completed: + stats.completed_count += 1 + + return stats + +def get_category_text(category: IssueCategory) -> str: + """카테고리 한글 변환""" + category_map = { + IssueCategory.material_missing: "자재 누락", + IssueCategory.design_error: "설계 미스", + IssueCategory.incoming_defect: "입고 불량", + IssueCategory.inspection_miss: "검사 미스", + IssueCategory.etc: "기타" + } + return category_map.get(category, str(category)) + +def get_department_text(department) -> str: + """부서 한글 변환""" + if not department: + return "" + + department_map = { + "production": "생산", + "quality": "품질", + "purchasing": "구매", + "design": "설계", + "sales": "영업" + } + return department_map.get(department, str(department)) + +def get_status_text(status: ReviewStatus) -> str: + """상태 한글 변환""" + status_map = { + ReviewStatus.pending_review: "검토 대기", + ReviewStatus.in_progress: "진행 중", + ReviewStatus.completed: "완료됨", + ReviewStatus.disposed: "폐기됨" + } + return status_map.get(status, str(status)) + +def get_status_color(status: ReviewStatus) -> str: + """상태별 색상 반환""" + color_map = { + ReviewStatus.in_progress: "FFF2CC", # 연한 노랑 + ReviewStatus.completed: "E2EFDA", # 연한 초록 + ReviewStatus.disposed: "F2F2F2" # 연한 회색 + } + return color_map.get(status, None) diff --git a/backend/services/__pycache__/auth_service.cpython-311.pyc b/backend/services/__pycache__/auth_service.cpython-311.pyc index fa1fa55..0e0ad90 100644 Binary files a/backend/services/__pycache__/auth_service.cpython-311.pyc and b/backend/services/__pycache__/auth_service.cpython-311.pyc differ diff --git a/backend/services/__pycache__/file_service.cpython-311.pyc b/backend/services/__pycache__/file_service.cpython-311.pyc index 73dfb69..204bcbc 100644 Binary files a/backend/services/__pycache__/file_service.cpython-311.pyc and b/backend/services/__pycache__/file_service.cpython-311.pyc differ diff --git a/frontend/daily-work.html b/frontend/daily-work.html index 427561f..8ff4897 100644 --- a/frontend/daily-work.html +++ b/frontend/daily-work.html @@ -339,7 +339,8 @@ async function loadProjects() { try { // API에서 최신 프로젝트 데이터 가져오기 - const response = await fetch('/api/projects/', { + const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const response = await fetch(`${apiUrl}/projects/`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, 'Content-Type': 'application/json' diff --git a/frontend/issues-archive.html b/frontend/issues-archive.html index a7a2b3b..f8231ae 100644 --- a/frontend/issues-archive.html +++ b/frontend/issues-archive.html @@ -287,7 +287,8 @@ // 프로젝트 로드 async function loadProjects() { try { - const response = await fetch('/api/projects/', { + const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const response = await fetch(`${apiUrl}/projects/`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, 'Content-Type': 'application/json' diff --git a/frontend/issues-dashboard.html b/frontend/issues-dashboard.html index db4c719..1e57e24 100644 --- a/frontend/issues-dashboard.html +++ b/frontend/issues-dashboard.html @@ -323,7 +323,8 @@ // 데이터 로드 함수들 async function loadProjects() { try { - const response = await fetch('/api/projects/', { + const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const response = await fetch(`${apiUrl}/projects/`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, 'Content-Type': 'application/json' diff --git a/frontend/issues-inbox.html b/frontend/issues-inbox.html index dbc69d3..34f4e8e 100644 --- a/frontend/issues-inbox.html +++ b/frontend/issues-inbox.html @@ -668,7 +668,8 @@ async function loadProjects() { console.log('🔄 프로젝트 로드 시작'); try { - const response = await fetch('/api/projects/', { + const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const response = await fetch(`${apiUrl}/projects/`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, 'Content-Type': 'application/json' diff --git a/frontend/issues-management.html b/frontend/issues-management.html index 1a44b4c..49915bc 100644 --- a/frontend/issues-management.html +++ b/frontend/issues-management.html @@ -465,7 +465,8 @@ // 프로젝트 로드 async function loadProjects() { try { - const response = await fetch('/api/projects/', { + const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const response = await fetch(`${apiUrl}/projects/`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, 'Content-Type': 'application/json' @@ -2548,5 +2549,6 @@ + diff --git a/frontend/reports-daily.html b/frontend/reports-daily.html new file mode 100644 index 0000000..af91ffb --- /dev/null +++ b/frontend/reports-daily.html @@ -0,0 +1,383 @@ + + + + + + 일일보고서 - 작업보고서 + + + + + + + + + + + + + + +
+ +
+
+
+

+ + 일일보고서 +

+

품질팀용 관리함 데이터를 엑셀 형태로 내보내세요

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

+ + 선택한 프로젝트의 관리함 데이터만 포함됩니다. +

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

+ 보고서 포함 항목 +

+
+
+
+ + 진행 중 항목 +
+

무조건 포함됩니다

+
+
+
+ + 완료됨 항목 +
+

첫 내보내기에만 포함, 이후 자동 제외

+
+
+
+ + 프로젝트 통계 +
+

상단에 요약 정보 포함

+
+
+
+ + +
+

+ 최근 생성된 일일보고서 +

+
+
+ +

아직 생성된 일일보고서가 없습니다.

+

프로젝트를 선택하고 보고서를 생성해보세요!

+
+
+
+
+ + + + + + + + + + diff --git a/frontend/reports-monthly.html b/frontend/reports-monthly.html new file mode 100644 index 0000000..d98e22b --- /dev/null +++ b/frontend/reports-monthly.html @@ -0,0 +1,111 @@ + + + + + + 월간보고서 - 작업보고서 + + + + + + + + + + + + + + +
+ +
+
+
+

+ + 월간보고서 +

+

월간 부적합 발생 현황, 처리 성과 및 개선사항을 종합적으로 분석하세요

+
+
+
+ + +
+
+
+ +
+

월간보고서 준비중

+

+ 월간 부적합 발생 현황, 처리 성과 및 개선사항을 종합한 보고서 기능을 준비하고 있습니다. +

+
+

예정 기능

+
    +
  • • 월간 부적합 발생 현황
  • +
  • • 월간 처리 완료 현황
  • +
  • • 부서별 성과 분석
  • +
  • • 월간 트렌드 및 개선사항
  • +
  • • 경영진 보고용 요약
  • +
+
+
+ +
+
+
+
+ + + + + + + + + + diff --git a/frontend/reports-weekly.html b/frontend/reports-weekly.html new file mode 100644 index 0000000..76bbaed --- /dev/null +++ b/frontend/reports-weekly.html @@ -0,0 +1,110 @@ + + + + + + 주간보고서 - 작업보고서 + + + + + + + + + + + + + + +
+ +
+
+
+

+ + 주간보고서 +

+

주간 단위로 집계된 부적합 현황 및 처리 결과를 확인하세요

+
+
+
+ + +
+
+
+ +
+

주간보고서 준비중

+

+ 주간 단위로 집계된 부적합 현황 및 처리 결과를 정리한 보고서 기능을 준비하고 있습니다. +

+
+

예정 기능

+
    +
  • • 주간 부적합 발생 현황
  • +
  • • 주간 처리 완료 현황
  • +
  • • 부서별 처리 성과
  • +
  • • 주간 트렌드 분석
  • +
+
+
+ +
+
+
+
+ + + + + + + + + + diff --git a/frontend/reports.html b/frontend/reports.html new file mode 100644 index 0000000..f79e1b1 --- /dev/null +++ b/frontend/reports.html @@ -0,0 +1,212 @@ + + + + + + 보고서 - 작업보고서 + + + + + + + + + + + + +
+ + +
+ +
+
+
+

+ + 보고서 +

+

다양한 보고서를 생성하고 관리할 수 있습니다

+
+
+
+ + +
+

+ 보고서 유형 선택 +

+ +
+ + +
+

+ 보고서 이용 안내 +

+
+
+

📊 일일보고서

+
    +
  • • 관리함의 진행 중 항목 무조건 포함
  • +
  • • 완료됨 항목은 첫 내보내기에만 포함
  • +
  • • 프로젝트별 개별 생성
  • +
  • • 엑셀 형태로 다운로드
  • +
+
+
+

🚀 향후 계획

+
    +
  • • 주간보고서: 주간 집계 및 트렌드 분석
  • +
  • • 월간보고서: 월간 성과 및 개선사항
  • +
  • • 자동 이메일 발송 기능
  • +
  • • 대시보드 형태의 실시간 리포트
  • +
+
+
+
+
+ + + + + + + + + + + diff --git a/frontend/static/js/components/common-header.js b/frontend/static/js/components/common-header.js index 7d2ac69..5fbfcf1 100644 --- a/frontend/static/js/components/common-header.js +++ b/frontend/static/js/components/common-header.js @@ -93,7 +93,33 @@ class CommonHeader { url: '/reports.html', pageName: 'reports', color: 'text-red-600', - bgColor: 'bg-red-50 hover:bg-red-100' + bgColor: 'bg-red-50 hover:bg-red-100', + subMenus: [ + { + id: 'reports_daily', + title: '일일보고서', + icon: 'fas fa-file-excel', + url: '/reports-daily.html', + pageName: 'reports_daily', + color: 'text-green-600' + }, + { + id: 'reports_weekly', + title: '주간보고서', + icon: 'fas fa-calendar-week', + url: '/reports-weekly.html', + pageName: 'reports_weekly', + color: 'text-blue-600' + }, + { + id: 'reports_monthly', + title: '월간보고서', + icon: 'fas fa-calendar-alt', + url: '/reports-monthly.html', + pageName: 'reports_monthly', + color: 'text-purple-600' + } + ] }, { id: 'projects_manage', diff --git a/frontend/static/js/core/permissions.js b/frontend/static/js/core/permissions.js index 1ac642c..663cb97 100644 --- a/frontend/static/js/core/permissions.js +++ b/frontend/static/js/core/permissions.js @@ -46,7 +46,8 @@ class PagePermissionManager { try { // API에서 사용자별 페이지 권한 가져오기 - const response = await fetch(`/api/users/${this.currentUser.id}/page-permissions`, { + const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const response = await fetch(`${apiUrl}/users/${this.currentUser.id}/page-permissions`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}` } @@ -198,7 +199,8 @@ class PagePermissionManager { } try { - const response = await fetch('/api/page-permissions/grant', { + const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const response = await fetch(`${apiUrl}/page-permissions/grant`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -230,7 +232,8 @@ class PagePermissionManager { */ async getUserPagePermissions(userId) { try { - const response = await fetch(`/api/users/${userId}/page-permissions`, { + const apiUrl = window.API_BASE_URL || 'http://localhost:16080/api'; + const response = await fetch(`${apiUrl}/users/${userId}/page-permissions`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('access_token')}` } diff --git a/frontend/sw.js b/frontend/sw.js index ddbc9ff..e51942a 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -3,9 +3,9 @@ * M-Project 작업보고서 시스템 */ -const CACHE_NAME = 'mproject-v1.0.0'; -const STATIC_CACHE = 'mproject-static-v1.0.0'; -const DYNAMIC_CACHE = 'mproject-dynamic-v1.0.0'; +const CACHE_NAME = 'mproject-v1.0.1'; +const STATIC_CACHE = 'mproject-static-v1.0.1'; +const DYNAMIC_CACHE = 'mproject-dynamic-v1.0.1'; // 캐시할 정적 리소스 const STATIC_ASSETS = [