diff --git a/backend/migrations/019_add_completion_request_fields.sql b/backend/migrations/019_add_completion_request_fields.sql new file mode 100644 index 0000000..7b9d144 --- /dev/null +++ b/backend/migrations/019_add_completion_request_fields.sql @@ -0,0 +1,37 @@ +-- 완료 신청 관련 필드 추가 +-- 마이그레이션: 019_add_completion_request_fields.sql + +DO $$ +BEGIN + -- 완료 신청 관련 필드들 추가 + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'issues' AND column_name = 'completion_requested_at') THEN + ALTER TABLE issues ADD COLUMN completion_requested_at TIMESTAMP WITH TIME ZONE; + END IF; + + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'issues' AND column_name = 'completion_requested_by_id') THEN + ALTER TABLE issues ADD COLUMN completion_requested_by_id INTEGER REFERENCES users(id); + END IF; + + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'issues' AND column_name = 'completion_photo_path') THEN + ALTER TABLE issues ADD COLUMN completion_photo_path VARCHAR(500); + END IF; + + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'issues' AND column_name = 'completion_comment') THEN + ALTER TABLE issues ADD COLUMN completion_comment TEXT; + END IF; + + -- 마이그레이션 로그 기록 + INSERT INTO migration_log (migration_file, executed_at, status, notes) + VALUES ('019_add_completion_request_fields.sql', NOW(), 'SUCCESS', 'Added completion request fields: completion_requested_at, completion_requested_by_id, completion_photo_path, completion_comment'); + + RAISE NOTICE '✅ 완료 신청 관련 필드 추가 완료'; + RAISE NOTICE '📝 완료 신청 필드 마이그레이션 완료 - 019_add_completion_request_fields.sql'; + +EXCEPTION + WHEN OTHERS THEN + -- 오류 발생 시 로그 기록 + INSERT INTO migration_log (migration_file, executed_at, status, notes) + VALUES ('019_add_completion_request_fields.sql', NOW(), 'FAILED', 'Error: ' || SQLERRM); + + RAISE EXCEPTION '❌ 마이그레이션 실패: %', SQLERRM; +END $$; diff --git a/frontend/issues-dashboard.html b/frontend/issues-dashboard.html index 7e3ef7e..0765a20 100644 --- a/frontend/issues-dashboard.html +++ b/frontend/issues-dashboard.html @@ -517,15 +517,63 @@ }); }; - // 긴급도 체크 (예상완료일 기준) - const isUrgent = () => { - if (!issue.expected_completion_date) return false; - const expectedDate = new Date(issue.expected_completion_date); - const now = new Date(); - const diffDays = (expectedDate - now) / (1000 * 60 * 60 * 24); - return diffDays <= 3; // 3일 이내 또는 지연 + // 상태 체크 함수들 + const getIssueStatus = () => { + if (issue.review_status === 'completed') return 'completed'; + if (issue.completion_requested_at) return 'pending_completion'; // 완료 대기 + + if (issue.expected_completion_date) { + const expectedDate = new Date(issue.expected_completion_date); + const now = new Date(); + const diffDays = (expectedDate - now) / (1000 * 60 * 60 * 24); + + if (diffDays < 0) return 'overdue'; // 지연됨 + if (diffDays <= 3) return 'urgent'; // 긴급 + } + + return 'in_progress'; // 진행 중 }; + const getStatusConfig = (status) => { + const configs = { + 'in_progress': { + text: '진행 중', + bgColor: 'bg-gradient-to-r from-blue-500 to-blue-600', + icon: 'fas fa-cog fa-spin', + dotColor: 'bg-white' + }, + 'urgent': { + text: '긴급', + bgColor: 'bg-gradient-to-r from-orange-500 to-orange-600', + icon: 'fas fa-exclamation-triangle', + dotColor: 'bg-white' + }, + 'overdue': { + text: '지연됨', + bgColor: 'bg-gradient-to-r from-red-500 to-red-600', + icon: 'fas fa-clock', + dotColor: 'bg-white' + }, + 'pending_completion': { + text: '완료 대기', + bgColor: 'bg-gradient-to-r from-purple-500 to-purple-600', + icon: 'fas fa-hourglass-half', + dotColor: 'bg-white' + }, + 'completed': { + text: '완료됨', + bgColor: 'bg-gradient-to-r from-green-500 to-green-600', + icon: 'fas fa-check-circle', + dotColor: 'bg-white' + } + }; + return configs[status] || configs['in_progress']; + }; + + const currentStatus = getIssueStatus(); + const statusConfig = getStatusConfig(currentStatus); + const isUrgent = () => currentStatus === 'urgent'; + return `
@@ -542,11 +590,10 @@
- ${isUrgent() ? '🔥 긴급' : ''} -
-
- 진행 중 - +
+
+ ${statusConfig.text} +
발생: ${formatKSTDate(issue.report_date)} @@ -635,12 +682,18 @@
${issue.responsible_person || '-'}
-
+
마감시간
${formatKSTDate(issue.expected_completion_date)}
+ ${currentStatus === 'in_progress' || currentStatus === 'urgent' || currentStatus === 'overdue' ? ` + + ` : ''}
@@ -775,11 +828,185 @@ console.log('✅ API 스크립트 로드 완료 (issues-dashboard.html)'); } + // 완료 신청 관련 함수들 + let selectedCompletionIssueId = null; + let completionPhotoBase64 = null; + + function openCompletionRequestModal(issueId) { + selectedCompletionIssueId = issueId; + document.getElementById('completionRequestModal').classList.remove('hidden'); + + // 폼 초기화 + document.getElementById('completionRequestForm').reset(); + document.getElementById('photoPreview').classList.add('hidden'); + document.getElementById('photoUploadArea').classList.remove('hidden'); + completionPhotoBase64 = null; + } + + function closeCompletionRequestModal() { + selectedCompletionIssueId = null; + completionPhotoBase64 = null; + document.getElementById('completionRequestModal').classList.add('hidden'); + } + + function handleCompletionPhotoUpload(event) { + const file = event.target.files[0]; + if (!file) return; + + // 파일 크기 체크 (5MB 제한) + if (file.size > 5 * 1024 * 1024) { + alert('파일 크기는 5MB 이하여야 합니다.'); + return; + } + + // 이미지 파일 체크 + if (!file.type.startsWith('image/')) { + alert('이미지 파일만 업로드 가능합니다.'); + return; + } + + const reader = new FileReader(); + reader.onload = function(e) { + completionPhotoBase64 = e.target.result; + + // 미리보기 표시 + document.getElementById('previewImage').src = e.target.result; + document.getElementById('photoUploadArea').classList.add('hidden'); + document.getElementById('photoPreview').classList.remove('hidden'); + }; + reader.readAsDataURL(file); + } + + // 완료 신청 폼 제출 처리 + document.addEventListener('DOMContentLoaded', function() { + const completionForm = document.getElementById('completionRequestForm'); + if (completionForm) { + completionForm.addEventListener('submit', async function(e) { + e.preventDefault(); + + if (!selectedCompletionIssueId) { + alert('선택된 이슈가 없습니다.'); + return; + } + + if (!completionPhotoBase64) { + alert('완료 사진을 업로드해주세요.'); + return; + } + + const comment = document.getElementById('completionComment').value.trim(); + + try { + const response = await fetch(`/api/issues/${selectedCompletionIssueId}/completion-request`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${localStorage.getItem('access_token')}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + completion_photo: completionPhotoBase64, + completion_comment: comment + }) + }); + + if (response.ok) { + alert('완료 신청이 성공적으로 제출되었습니다.'); + closeCompletionRequestModal(); + + // 현황판 새로고침 + refreshDashboard(); + } else { + const error = await response.json(); + alert(`완료 신청 실패: ${error.detail || '알 수 없는 오류'}`); + } + } catch (error) { + console.error('완료 신청 오류:', error); + alert('완료 신청 중 오류가 발생했습니다.'); + } + }); + } + }); + // API 스크립트 동적 로드 const script = document.createElement('script'); script.src = '/static/js/api.js?v=' + Date.now(); script.onload = initializeDashboardApp; document.body.appendChild(script); + + +