diff --git a/backend/routers/__pycache__/issues.cpython-311.pyc b/backend/routers/__pycache__/issues.cpython-311.pyc index 3a3a98b..aa947ac 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/issues.py b/backend/routers/issues.py index 45cee33..e8b86f5 100644 --- a/backend/routers/issues.py +++ b/backend/routers/issues.py @@ -7,6 +7,7 @@ from database.database import get_db from database.models import Issue, IssueStatus, User, UserRole from database import schemas from routers.auth import get_current_user, get_current_admin +from routers.page_permissions import check_page_access from services.file_service import save_base64_image, delete_file router = APIRouter(prefix="/api/issues", tags=["issues"]) @@ -224,3 +225,45 @@ async def get_issue_stats( "progress": progress, "complete": complete } + +@router.put("/{issue_id}/management") +async def update_issue_management( + issue_id: int, + management_update: schemas.ManagementUpdateRequest, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """ + 관리함에서 이슈의 관리 관련 필드들을 업데이트합니다. + """ + # 관리함 페이지 권한 확인 + if not (current_user.role == UserRole.admin or check_page_access(current_user.id, 'issues_management', db)): + raise HTTPException(status_code=403, detail="관리함 접근 권한이 없습니다.") + + # 이슈 조회 + issue = db.query(Issue).filter(Issue.id == issue_id).first() + if not issue: + raise HTTPException(status_code=404, detail="부적합을 찾을 수 없습니다.") + + # 관리함에서만 수정 가능한 필드들만 업데이트 + update_data = management_update.dict(exclude_unset=True) + + for field, value in update_data.items(): + if field == 'completion_photo' and value: + # 완료 사진 업로드 처리 + try: + completion_photo_path = save_base64_image(value, "completion") + setattr(issue, 'completion_photo_path', completion_photo_path) + except Exception as e: + raise HTTPException(status_code=400, detail=f"완료 사진 저장 실패: {str(e)}") + elif field != 'completion_photo': # completion_photo는 위에서 처리됨 + setattr(issue, field, value) + + db.commit() + db.refresh(issue) + + return { + "message": "관리 정보가 업데이트되었습니다.", + "issue_id": issue.id, + "updated_fields": list(update_data.keys()) + } diff --git a/frontend/issues-management.html b/frontend/issues-management.html index 5c4b4d0..68c67db 100644 --- a/frontend/issues-management.html +++ b/frontend/issues-management.html @@ -85,7 +85,7 @@ } .issue-table { - min-width: 1200px; + min-width: 2000px; /* 더 넓은 최소 너비 설정 */ width: 100%; border-collapse: collapse; } @@ -95,7 +95,7 @@ padding: 0.75rem; text-align: left; border-bottom: 1px solid #f3f4f6; - white-space: nowrap; + vertical-align: top; } .issue-table th { @@ -103,24 +103,70 @@ font-weight: 600; color: #374151; font-size: 0.875rem; + white-space: nowrap; } .issue-table tbody tr:hover { background-color: #f9fafb; } + /* 컬럼별 너비 조정 */ + .col-no { min-width: 60px; } + .col-project { min-width: 120px; } + .col-content { min-width: 250px; max-width: 300px; } + .col-cause { min-width: 100px; } + .col-solution { min-width: 200px; max-width: 250px; } + .col-department { min-width: 100px; } + .col-person { min-width: 120px; } + .col-date { min-width: 120px; } + .col-confirmer { min-width: 120px; } + .col-comment { min-width: 200px; max-width: 250px; } + .col-status { min-width: 100px; } + .col-photos { min-width: 150px; } + .col-completion { min-width: 80px; } + .col-actions { min-width: 120px; } + .issue-photo { width: 60px; height: 40px; object-fit: cover; border-radius: 0.375rem; cursor: pointer; + margin: 2px; } - .issue-description { - max-width: 200px; - overflow: hidden; - text-overflow: ellipsis; + .photo-container { + display: flex; + flex-wrap: wrap; + gap: 4px; + } + + /* 편집 가능한 필드 스타일 */ + .editable-field { + min-width: 100%; + padding: 4px 8px; + border: 1px solid #d1d5db; + border-radius: 4px; + font-size: 0.875rem; + } + + .editable-field:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 1px #3b82f6; + } + + .text-wrap { + white-space: normal; + word-wrap: break-word; + line-height: 1.4; + } + + .btn-sm { + padding: 4px 8px; + font-size: 0.75rem; + border-radius: 4px; + margin: 2px; } .collapse-content { @@ -367,9 +413,30 @@ if (response.ok) { const allIssues = await response.json(); // 관리함에서는 진행 중(in_progress)과 완료됨(completed) 상태만 표시 - issues = allIssues.filter(issue => + let filteredIssues = allIssues.filter(issue => issue.review_status === 'in_progress' || issue.review_status === 'completed' ); + + // 수신함에서 넘어온 순서대로 No. 재할당 (reviewed_at 기준) + filteredIssues.sort((a, b) => new Date(a.reviewed_at) - new Date(b.reviewed_at)); + + // 프로젝트별로 그룹화하여 No. 재할당 + const projectGroups = {}; + filteredIssues.forEach(issue => { + if (!projectGroups[issue.project_id]) { + projectGroups[issue.project_id] = []; + } + projectGroups[issue.project_id].push(issue); + }); + + // 각 프로젝트별로 순번 재할당 + Object.keys(projectGroups).forEach(projectId => { + projectGroups[projectId].forEach((issue, index) => { + issue.project_sequence_no = index + 1; + }); + }); + + issues = filteredIssues; filterIssues(); } else { throw new Error('부적합 목록을 불러올 수 없습니다.'); @@ -506,25 +573,26 @@
| No. | -프로젝트 | -내용 | -원인 | -해결방안 | -담당부서 | -담당자 | -조치예상일 | -완료확인일 | -확인자 | -원인부서 | -의견 | -조치결과 | -업로드 사진 | -완료 사진 | -|||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| No. | +프로젝트 | +내용 | +원인 | +해결방안 | +담당부서 | +담당자 | +조치예상일 | +${currentTab === 'in_progress' ? '완료 확인' : '완료확인일'} | +확인자 | +원인부서 | +의견 | +조치결과 | +업로드 사진 | +완료 사진 | +작업 | +||||||||||||
| ${issue.project_sequence_no || '-'} | -${project ? project.project_name : '-'} | -- ${issue.final_description || issue.description} + | |||||||||||||||||||||||||
| ${issue.project_sequence_no || '-'} | +${project ? project.project_name : '-'} | +${issue.final_description || issue.description} | +${getCategoryText(issue.final_category || issue.category)} | ++ ${createEditableField('solution', issue.solution || '', 'textarea', issue.id, true)} | -${getCategoryText(issue.final_category || issue.category)} | -- ${issue.solution || '-'} + | + ${createEditableField('responsible_department', issue.responsible_department || '', 'select', issue.id, true, getDepartmentOptions())} | -${getDepartmentText(issue.responsible_department) || '-'} | -${issue.responsible_person || '-'} | -${issue.expected_completion_date ? new Date(issue.expected_completion_date).toLocaleDateString('ko-KR') : '-'} | -${issue.actual_completion_date ? new Date(issue.actual_completion_date).toLocaleDateString('ko-KR') : '-'} | -${getReporterNames(issue)} | -${getDepartmentText(issue.cause_department) || '-'} | -- ${issue.management_comment || '-'} + | + ${createEditableField('responsible_person', issue.responsible_person || '', 'text', issue.id, true)} | -${statusText} | -
- ${issue.photo_path ? ` | + ${createEditableField('expected_completion_date', issue.expected_completion_date ? issue.expected_completion_date.split('T')[0] : '', 'date', issue.id, true)} | -+ | + ${isInProgress ? + `` : + (issue.actual_completion_date ? new Date(issue.actual_completion_date).toLocaleDateString('ko-KR') : '-') + } + | +${getReporterNames(issue)} | ++ ${createEditableField('cause_department', issue.cause_department || '', 'select', issue.id, true, getDepartmentOptions())} + | ++ ${createEditableField('management_comment', issue.management_comment || '', 'textarea', issue.id, true)} + | +${statusText} | +
+
+ ${issue.photo_path ? `
+ |
+
${issue.completion_photo_path ? ` |
+ + + |