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:
Binary file not shown.
@@ -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):
|
||||
"""관리함에서 사용할 필드 업데이트 요청"""
|
||||
|
||||
Binary file not shown.
@@ -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:
|
||||
|
||||
@@ -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`, {
|
||||
|
||||
Reference in New Issue
Block a user