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
|
review_status: ReviewStatus
|
||||||
notes: Optional[str] = None
|
notes: Optional[str] = None
|
||||||
completion_photo: Optional[str] = None # 완료 사진 (Base64)
|
completion_photo: Optional[str] = None # 완료 사진 (Base64)
|
||||||
|
solution: Optional[str] = None # 해결방안
|
||||||
|
responsible_department: Optional[DepartmentType] = None # 담당부서
|
||||||
|
responsible_person: Optional[str] = None # 담당자
|
||||||
|
|
||||||
class ManagementUpdateRequest(BaseModel):
|
class ManagementUpdateRequest(BaseModel):
|
||||||
"""관리함에서 사용할 필드 업데이트 요청"""
|
"""관리함에서 사용할 필드 업데이트 요청"""
|
||||||
|
|||||||
Binary file not shown.
@@ -269,9 +269,21 @@ async def update_issue_status(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=400, detail=f"완료 사진 저장 실패: {str(e)}")
|
raise HTTPException(status_code=400, detail=f"완료 사진 저장 실패: {str(e)}")
|
||||||
|
|
||||||
# 완료 상태로 변경 시 완료 확인일 설정
|
# 완료 상태로 변경 시 추가 정보 처리
|
||||||
if status_request.review_status == ReviewStatus.completed:
|
if status_request.review_status == ReviewStatus.completed:
|
||||||
issue.actual_completion_date = datetime.now().date()
|
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에 추가
|
# 노트가 있으면 detail_notes에 추가
|
||||||
if status_request.notes:
|
if status_request.notes:
|
||||||
|
|||||||
@@ -427,19 +427,62 @@
|
|||||||
placeholder="처리 내용이나 특이사항을 입력하세요..."></textarea>
|
placeholder="처리 내용이나 특이사항을 입력하세요..."></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 완료 사진 업로드 (완료 상태 선택 시에만 표시) -->
|
<!-- 완료 관련 추가 정보 (완료 상태 선택 시에만 표시) -->
|
||||||
<div id="completionPhotoSection" class="hidden">
|
<div id="completionSection" class="hidden space-y-4">
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
<!-- 완료 사진 업로드 -->
|
||||||
<i class="fas fa-camera text-green-500 mr-1"></i>완료 사진 (1장)
|
<div>
|
||||||
</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
<input type="file" id="completionPhotoInput" accept="image/*"
|
<i class="fas fa-camera text-green-500 mr-1"></i>완료 사진 (1장, 선택사항)
|
||||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
|
</label>
|
||||||
onchange="handleCompletionPhotoSelect(event)">
|
<input type="file" id="completionPhotoInput" accept="image/*"
|
||||||
<div id="completionPhotoPreview" class="mt-2 hidden">
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm"
|
||||||
<img id="completionPhotoImg" src="" alt="완료 사진 미리보기"
|
onchange="handleCompletionPhotoSelect(event)">
|
||||||
class="w-full max-h-40 object-cover rounded-lg border">
|
<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>
|
</div>
|
||||||
<p class="text-xs text-gray-500 mt-1">완료 상태로 변경 시 완료 사진을 업로드할 수 있습니다.</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-end space-x-3">
|
<div class="flex justify-end space-x-3">
|
||||||
@@ -1225,25 +1268,31 @@
|
|||||||
function closeStatusModal() {
|
function closeStatusModal() {
|
||||||
currentIssueId = null;
|
currentIssueId = null;
|
||||||
document.getElementById('statusModal').classList.add('hidden');
|
document.getElementById('statusModal').classList.add('hidden');
|
||||||
// 완료 사진 관련 초기화
|
// 완료 관련 필드 초기화
|
||||||
document.getElementById('completionPhotoSection').classList.add('hidden');
|
document.getElementById('completionSection').classList.add('hidden');
|
||||||
document.getElementById('completionPhotoInput').value = '';
|
document.getElementById('completionPhotoInput').value = '';
|
||||||
document.getElementById('completionPhotoPreview').classList.add('hidden');
|
document.getElementById('completionPhotoPreview').classList.add('hidden');
|
||||||
|
document.getElementById('solutionInput').value = '';
|
||||||
|
document.getElementById('responsibleDepartmentInput').value = '';
|
||||||
|
document.getElementById('responsiblePersonInput').value = '';
|
||||||
completionPhotoBase64 = null;
|
completionPhotoBase64 = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 완료 사진 섹션 토글
|
// 완료 섹션 토글
|
||||||
function toggleCompletionPhotoSection() {
|
function toggleCompletionPhotoSection() {
|
||||||
const selectedStatus = document.querySelector('input[name="finalStatus"]:checked');
|
const selectedStatus = document.querySelector('input[name="finalStatus"]:checked');
|
||||||
const photoSection = document.getElementById('completionPhotoSection');
|
const completionSection = document.getElementById('completionSection');
|
||||||
|
|
||||||
if (selectedStatus && selectedStatus.value === 'completed') {
|
if (selectedStatus && selectedStatus.value === 'completed') {
|
||||||
photoSection.classList.remove('hidden');
|
completionSection.classList.remove('hidden');
|
||||||
} else {
|
} else {
|
||||||
photoSection.classList.add('hidden');
|
completionSection.classList.add('hidden');
|
||||||
// 완료 사진 초기화
|
// 완료 관련 필드 초기화
|
||||||
document.getElementById('completionPhotoInput').value = '';
|
document.getElementById('completionPhotoInput').value = '';
|
||||||
document.getElementById('completionPhotoPreview').classList.add('hidden');
|
document.getElementById('completionPhotoPreview').classList.add('hidden');
|
||||||
|
document.getElementById('solutionInput').value = '';
|
||||||
|
document.getElementById('responsibleDepartmentInput').value = '';
|
||||||
|
document.getElementById('responsiblePersonInput').value = '';
|
||||||
completionPhotoBase64 = null;
|
completionPhotoBase64 = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1296,22 +1345,36 @@
|
|||||||
const reviewStatus = selectedStatus.value;
|
const reviewStatus = selectedStatus.value;
|
||||||
const notes = document.getElementById('statusNotes').value.trim();
|
const notes = document.getElementById('statusNotes').value.trim();
|
||||||
|
|
||||||
// 완료 상태인데 완료 사진이 없으면 확인
|
|
||||||
if (reviewStatus === 'completed' && !completionPhotoBase64) {
|
|
||||||
if (!confirm('완료 사진을 업로드하지 않고 완료 처리하시겠습니까?')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const requestBody = {
|
const requestBody = {
|
||||||
review_status: reviewStatus,
|
review_status: reviewStatus,
|
||||||
notes: notes || null
|
notes: notes || null
|
||||||
};
|
};
|
||||||
|
|
||||||
// 완료 사진이 있으면 추가
|
// 완료 상태일 때 추가 정보 수집
|
||||||
if (reviewStatus === 'completed' && completionPhotoBase64) {
|
if (reviewStatus === 'completed') {
|
||||||
requestBody.completion_photo = completionPhotoBase64;
|
// 완료 사진
|
||||||
|
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`, {
|
const response = await fetch(`/api/inbox/${currentIssueId}/status`, {
|
||||||
|
|||||||
Reference in New Issue
Block a user