feat: 수신함 상태 결정 모달 개선 - 완료 시 추가 정보 입력 기능

🔄 Status Modal Improvements:
- 진행 중 / 완료됨 2개 옵션으로 단순화
- 진행 중 선택 시 바로 관리함으로 이동
- 완료됨 선택 시 추가 정보 입력 섹션 표시

📝 Completion Information Fields:
- 완료 사진 업로드 (1장, 선택사항)
- 해결방안 입력 (어떻게 해결했는지)
- 해결한 부서 선택 (담당부서)
- 해결한 사람 입력 (담당자)
- 모든 필드는 선택사항으로 관리함에서 나중에 입력 가능

🎨 Frontend Implementation:
- completionSection으로 통합된 완료 관련 UI
- 상태 선택에 따른 동적 섹션 표시/숨김
- 부서 선택 드롭다운 (생산, 품질, 구매, 설계, 영업)
- 직관적인 아이콘과 색상으로 필드 구분
- 사용자 안내 메시지로 UX 개선

🔧 Backend API Updates:
- IssueStatusUpdateRequest에 solution, responsible_department, responsible_person 필드 추가
- update_issue_status API에서 완료 상태 시 추가 필드 처리
- 해결방안, 담당부서, 담당자 정보 자동 저장

🚀 User Experience:
- 진행 중: 클릭 한 번으로 바로 관리함 이동
- 완료됨: 필요한 정보를 한 번에 입력하고 완료 처리
- 선택사항 필드로 유연한 워크플로우 지원
- 관리함에서 나중에 추가/수정 가능한 구조

💡 Workflow Optimization:
- 수신함에서 완료 처리 시 필요한 정보를 미리 입력
- 관리함 작업량 감소 및 효율성 향상
- 완료 확인일 자동 기록으로 추적 개선

Expected Result:
 진행 중 선택 시 바로 관리함 이동
 완료됨 선택 시 해결방안, 담당부서, 담당자 입력 가능
 완료 사진과 함께 종합적인 완료 정보 수집
 선택사항 필드로 유연한 사용 가능
 관리함에서 추가 편집 가능한 구조
This commit is contained in:
Hyungi Ahn
2025-10-25 14:27:36 +09:00
parent d77a71493a
commit b4a0bc19d3
5 changed files with 108 additions and 30 deletions

View File

@@ -163,6 +163,9 @@ class IssueStatusUpdateRequest(BaseModel):
review_status: ReviewStatus
notes: Optional[str] = None
completion_photo: Optional[str] = None # 완료 사진 (Base64)
solution: Optional[str] = None # 해결방안
responsible_department: Optional[DepartmentType] = None # 담당부서
responsible_person: Optional[str] = None # 담당자
class ManagementUpdateRequest(BaseModel):
"""관리함에서 사용할 필드 업데이트 요청"""

View File

@@ -269,9 +269,21 @@ async def update_issue_status(
except Exception as e:
raise HTTPException(status_code=400, detail=f"완료 사진 저장 실패: {str(e)}")
# 완료 상태로 변경 시 완료 확인일 설정
# 완료 상태로 변경 시 추가 정보 처리
if status_request.review_status == ReviewStatus.completed:
issue.actual_completion_date = datetime.now().date()
# 해결방안 저장
if status_request.solution:
issue.solution = status_request.solution
# 담당부서 저장
if status_request.responsible_department:
issue.responsible_department = status_request.responsible_department
# 담당자 저장
if status_request.responsible_person:
issue.responsible_person = status_request.responsible_person
# 노트가 있으면 detail_notes에 추가
if status_request.notes:

View File

@@ -427,19 +427,62 @@
placeholder="처리 내용이나 특이사항을 입력하세요..."></textarea>
</div>
<!-- 완료 사진 업로드 (완료 상태 선택 시에만 표시) -->
<div id="completionPhotoSection" class="hidden">
<label class="block text-sm font-medium text-gray-700 mb-2">
<i class="fas fa-camera text-green-500 mr-1"></i>완료 사진 (1장)
</label>
<input type="file" id="completionPhotoInput" accept="image/*"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
onchange="handleCompletionPhotoSelect(event)">
<div id="completionPhotoPreview" class="mt-2 hidden">
<img id="completionPhotoImg" src="" alt="완료 사진 미리보기"
class="w-full max-h-40 object-cover rounded-lg border">
<!-- 완료 관련 추가 정보 (완료 상태 선택 시에만 표시) -->
<div id="completionSection" class="hidden space-y-4">
<!-- 완료 사진 업로드 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
<i class="fas fa-camera text-green-500 mr-1"></i>완료 사진 (1장, 선택사항)
</label>
<input type="file" id="completionPhotoInput" accept="image/*"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
onchange="handleCompletionPhotoSelect(event)">
<div id="completionPhotoPreview" class="mt-2 hidden">
<img id="completionPhotoImg" src="" alt="완료 사진 미리보기"
class="w-full max-h-40 object-cover rounded-lg border">
</div>
</div>
<!-- 해결방안 입력 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
<i class="fas fa-lightbulb text-yellow-500 mr-1"></i>해결방안 (선택사항)
</label>
<textarea id="solutionInput" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
placeholder="어떻게 해결하였는지 입력하세요... (빈칸으로 두고 관리함에서 입력해도 됩니다)"></textarea>
</div>
<!-- 해결한 부서 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
<i class="fas fa-building text-blue-500 mr-1"></i>상기 문제를 해결한 부서 (선택사항)
</label>
<select id="responsibleDepartmentInput" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
<option value="">부서 선택 (관리함에서 입력 가능)</option>
<option value="production">생산</option>
<option value="quality">품질</option>
<option value="purchasing">구매</option>
<option value="design">설계</option>
<option value="sales">영업</option>
</select>
</div>
<!-- 해결한 사람 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
<i class="fas fa-user text-purple-500 mr-1"></i>해결한 사람 (선택사항)
</label>
<input type="text" id="responsiblePersonInput"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
placeholder="담당자 이름 입력 (관리함에서 입력 가능)">
</div>
<div class="bg-blue-50 p-3 rounded-lg">
<p class="text-xs text-blue-600">
<i class="fas fa-info-circle mr-1"></i>
위 정보들은 선택사항입니다. 빈칸으로 두고 관리함에서 나중에 입력하셔도 됩니다.
</p>
</div>
<p class="text-xs text-gray-500 mt-1">완료 상태로 변경 시 완료 사진을 업로드할 수 있습니다.</p>
</div>
<div class="flex justify-end space-x-3">
@@ -1225,25 +1268,31 @@
function closeStatusModal() {
currentIssueId = null;
document.getElementById('statusModal').classList.add('hidden');
// 완료 사진 관련 초기화
document.getElementById('completionPhotoSection').classList.add('hidden');
// 완료 관련 필드 초기화
document.getElementById('completionSection').classList.add('hidden');
document.getElementById('completionPhotoInput').value = '';
document.getElementById('completionPhotoPreview').classList.add('hidden');
document.getElementById('solutionInput').value = '';
document.getElementById('responsibleDepartmentInput').value = '';
document.getElementById('responsiblePersonInput').value = '';
completionPhotoBase64 = null;
}
// 완료 사진 섹션 토글
// 완료 섹션 토글
function toggleCompletionPhotoSection() {
const selectedStatus = document.querySelector('input[name="finalStatus"]:checked');
const photoSection = document.getElementById('completionPhotoSection');
const completionSection = document.getElementById('completionSection');
if (selectedStatus && selectedStatus.value === 'completed') {
photoSection.classList.remove('hidden');
completionSection.classList.remove('hidden');
} else {
photoSection.classList.add('hidden');
// 완료 사진 초기화
completionSection.classList.add('hidden');
// 완료 관련 필드 초기화
document.getElementById('completionPhotoInput').value = '';
document.getElementById('completionPhotoPreview').classList.add('hidden');
document.getElementById('solutionInput').value = '';
document.getElementById('responsibleDepartmentInput').value = '';
document.getElementById('responsiblePersonInput').value = '';
completionPhotoBase64 = null;
}
}
@@ -1296,22 +1345,36 @@
const reviewStatus = selectedStatus.value;
const notes = document.getElementById('statusNotes').value.trim();
// 완료 상태인데 완료 사진이 없으면 확인
if (reviewStatus === 'completed' && !completionPhotoBase64) {
if (!confirm('완료 사진을 업로드하지 않고 완료 처리하시겠습니까?')) {
return;
}
}
try {
const requestBody = {
review_status: reviewStatus,
notes: notes || null
};
// 완료 사진이 있으면 추가
if (reviewStatus === 'completed' && completionPhotoBase64) {
requestBody.completion_photo = completionPhotoBase64;
// 완료 상태일 때 추가 정보 수집
if (reviewStatus === 'completed') {
// 완료 사진
if (completionPhotoBase64) {
requestBody.completion_photo = completionPhotoBase64;
}
// 해결방안
const solution = document.getElementById('solutionInput').value.trim();
if (solution) {
requestBody.solution = solution;
}
// 담당부서
const responsibleDepartment = document.getElementById('responsibleDepartmentInput').value;
if (responsibleDepartment) {
requestBody.responsible_department = responsibleDepartment;
}
// 담당자
const responsiblePerson = document.getElementById('responsiblePersonInput').value.trim();
if (responsiblePerson) {
requestBody.responsible_person = responsiblePerson;
}
}
const response = await fetch(`/api/inbox/${currentIssueId}/status`, {