feat: 5장 사진 지원 및 엑셀 내보내기 UI 개선

- 신고 및 완료 사진 5장 지원 (photo_path3, photo_path4, photo_path5 추가)
- 엑셀 일일 리포트 개선:
  - 사진 5장 모두 한 행에 일렬 배치 (A, C, E, G, I 열)
  - 상태별 색상 구분 (지연중: 빨강, 진행중: 노랑, 완료: 진한 초록)
  - 우선순위 기반 정렬 (지연중 → 진행중 → 완료됨)
  - 프로젝트 현황 통계 박스 UI 개선 (색상 구분)
- 프론트엔드 모든 페이지 5장 사진 표시 (flex-wrap 레이아웃)
  - 관리함, 수신함, 현황판, 신고내용 확인 페이지

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2025-11-08 14:44:39 +09:00
parent 2fc7d4bc2c
commit 637b690eda
13 changed files with 1563 additions and 515 deletions

View File

@@ -271,17 +271,17 @@
</div>
<form id="reportForm" class="space-y-4">
<!-- 사진 업로드 (선택사항, 최대 2장) -->
<!-- 사진 업로드 (선택사항, 최대 5장) -->
<div class="space-y-3">
<div class="flex items-center justify-between">
<label class="text-sm font-medium text-gray-700">
📸 사진 첨부
</label>
<span class="text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded-full">
선택사항 • 최대 2
선택사항 • 최대 5
</span>
</div>
<!-- 사진 미리보기 영역 -->
<div id="photoPreviewContainer" class="grid grid-cols-2 gap-2 mb-3" style="display: none;">
<!-- 첫 번째 사진 -->
@@ -298,6 +298,27 @@
<i class="fas fa-times"></i>
</button>
</div>
<!-- 세 번째 사진 -->
<div id="photo3Container" class="relative hidden">
<img id="previewImg3" class="w-full h-24 object-cover rounded-xl border-2 border-gray-200">
<button type="button" onclick="removePhoto(2)" class="absolute -top-1 -right-1 w-6 h-6 bg-red-500 text-white rounded-full text-xs hover:bg-red-600 flex items-center justify-center shadow-lg">
<i class="fas fa-times"></i>
</button>
</div>
<!-- 네 번째 사진 -->
<div id="photo4Container" class="relative hidden">
<img id="previewImg4" class="w-full h-24 object-cover rounded-xl border-2 border-gray-200">
<button type="button" onclick="removePhoto(3)" class="absolute -top-1 -right-1 w-6 h-6 bg-red-500 text-white rounded-full text-xs hover:bg-red-600 flex items-center justify-center shadow-lg">
<i class="fas fa-times"></i>
</button>
</div>
<!-- 다섯 번째 사진 -->
<div id="photo5Container" class="relative hidden">
<img id="previewImg5" class="w-full h-24 object-cover rounded-xl border-2 border-gray-200">
<button type="button" onclick="removePhoto(4)" class="absolute -top-1 -right-1 w-6 h-6 bg-red-500 text-white rounded-full text-xs hover:bg-red-600 flex items-center justify-center shadow-lg">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<!-- 업로드 버튼들 -->
@@ -329,7 +350,7 @@
<!-- 현재 상태 표시 -->
<div class="text-center mt-2">
<p class="text-sm text-gray-500" id="photoUploadText">사진 추가 (0/2)</p>
<p class="text-sm text-gray-500" id="photoUploadText">사진 추가 (0/5)</p>
</div>
<!-- 숨겨진 입력 필드들 -->
@@ -880,23 +901,23 @@
// 사진 업로드 처리 함수
async function handlePhotoUpload(files) {
const filesArray = Array.from(files);
// 현재 사진 개수 확인
if (currentPhotos.length >= 2) {
alert('최대 2장까지 업로드 가능합니다.');
if (currentPhotos.length >= 5) {
alert('최대 5장까지 업로드 가능합니다.');
return;
}
// 추가 가능한 개수만큼만 처리
const availableSlots = 2 - currentPhotos.length;
const availableSlots = 5 - currentPhotos.length;
const filesToProcess = filesArray.slice(0, availableSlots);
// 로딩 표시
showUploadProgress(true);
try {
for (const file of filesToProcess) {
if (currentPhotos.length >= 2) break;
if (currentPhotos.length >= 5) break;
// 원본 파일 크기 확인
const originalSize = file.size;
@@ -923,18 +944,18 @@
const cameraBtn = document.getElementById('cameraUpload');
const galleryBtn = document.getElementById('galleryUpload');
const uploadText = document.getElementById('photoUploadText');
if (show) {
cameraBtn.classList.add('opacity-50', 'cursor-not-allowed');
galleryBtn.classList.add('opacity-50', 'cursor-not-allowed');
uploadText.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>이미지 처리 중...';
uploadText.classList.add('text-blue-600');
} else {
if (currentPhotos.length < 2) {
if (currentPhotos.length < 5) {
cameraBtn.classList.remove('opacity-50', 'cursor-not-allowed');
galleryBtn.classList.remove('opacity-50', 'cursor-not-allowed');
}
uploadText.textContent = `사진 추가 (${currentPhotos.length}/2)`;
uploadText.textContent = `사진 추가 (${currentPhotos.length}/5)`;
uploadText.classList.remove('text-blue-600');
}
}
@@ -964,43 +985,37 @@
// 사진 미리보기 업데이트
function updatePhotoPreview() {
const container = document.getElementById('photoPreviewContainer');
const photo1Container = document.getElementById('photo1Container');
const photo2Container = document.getElementById('photo2Container');
const uploadText = document.getElementById('photoUploadText');
const cameraUpload = document.getElementById('cameraUpload');
const galleryUpload = document.getElementById('galleryUpload');
// 텍스트 업데이트
uploadText.textContent = `사진 추가 (${currentPhotos.length}/2)`;
// 첫 번째 사진
if (currentPhotos[0]) {
document.getElementById('previewImg1').src = currentPhotos[0];
photo1Container.classList.remove('hidden');
container.style.display = 'grid';
} else {
photo1Container.classList.add('hidden');
uploadText.textContent = `사진 추가 (${currentPhotos.length}/5)`;
// 모든 사진 미리보기 업데이트 (최대 5장)
for (let i = 0; i < 5; i++) {
const photoContainer = document.getElementById(`photo${i + 1}Container`);
const previewImg = document.getElementById(`previewImg${i + 1}`);
if (currentPhotos[i]) {
previewImg.src = currentPhotos[i];
photoContainer.classList.remove('hidden');
container.style.display = 'grid';
} else {
photoContainer.classList.add('hidden');
}
}
// 두 번째 사진
if (currentPhotos[1]) {
document.getElementById('previewImg2').src = currentPhotos[1];
photo2Container.classList.remove('hidden');
container.style.display = 'grid';
} else {
photo2Container.classList.add('hidden');
}
// 미리보기 컨테이너 표시/숨김
if (currentPhotos.length === 0) {
container.style.display = 'none';
}
// 2장이 모두 업로드되면 업로드 버튼 스타일 변경
if (currentPhotos.length >= 2) {
// 5장이 모두 업로드되면 업로드 버튼 스타일 변경
if (currentPhotos.length >= 5) {
cameraUpload.classList.add('opacity-50', 'cursor-not-allowed');
galleryUpload.classList.add('opacity-50', 'cursor-not-allowed');
uploadText.textContent = '최대 2장 업로드 완료';
uploadText.textContent = '최대 5장 업로드 완료';
uploadText.classList.add('text-green-600', 'font-medium');
} else {
cameraUpload.classList.remove('opacity-50', 'cursor-not-allowed');