feat: 진행 중 탭 카드 형식으로 개편 - 입력 편의성 대폭 향상

🎯 Card-Based Interface for In-Progress Issues:
- 테이블 형태에서 카드 형태로 완전 변경
- 입력하기 편한 사용자 친화적 인터페이스 구현
- 진행 중/완료됨 탭별 다른 레이아웃 적용

📋 Enhanced Card Layout:
- 2컬럼 그리드 레이아웃 (기본정보 vs 편집정보)
- 카드 헤더: No. + 프로젝트명 + 액션 버튼들
- 왼쪽: 읽기전용 정보 (내용, 원인, 업로드사진)
- 오른쪽: 편집가능 정보 (해결방안, 담당부서/담당자, 예상일)

🎨 Visual Improvements:
- 아이콘과 색상으로 필드 구분
- 호버 효과 (카드 상승, 입력 필드 확대)
- 상태 배지 및 진행 상황 표시
- 사진 썸네일 개선 (테두리, 호버 효과)

✏️ Input Field Enhancements:
- 해결방안: 3줄 textarea with placeholder
- 담당부서: 아이콘이 있는 select 드롭다운
- 담당자: placeholder가 있는 text input
- 조치예상일: date picker with 아이콘
- 모든 필드에 focus 효과 적용

🔄 Tab-Specific Rendering:
- 진행 중: 카드 형식 (space-y-4 컨테이너)
- 완료됨: 테이블 형식 (기존 유지)
- displayIssues()에서 currentTab에 따른 조건부 렌더링

🎯 User Experience Focus:
- 입력하기 편한 넓은 필드들
- 시각적으로 구분되는 정보 영역
- 직관적인 아이콘과 라벨링
- 부드러운 애니메이션 효과

🔧 Technical Implementation:
- createInProgressRow(): 카드 HTML 생성
- CSS 애니메이션 및 호버 효과
- 반응형 그리드 레이아웃 (lg:grid-cols-2)
- 아이콘 정렬 및 스타일링

Expected Result:
 진행 중 이슈 입력이 훨씬 편리해짐
 시각적으로 구분되는 정보 영역
 카드별 독립적인 작업 공간 제공
 완료됨은 기존 테이블 형태 유지
This commit is contained in:
Hyungi Ahn
2025-10-26 10:11:36 +09:00
parent 05cf494da8
commit 642685a7c7

View File

@@ -181,6 +181,36 @@
max-height: 0;
}
/* 진행 중 카드 스타일 */
.issue-card {
transition: all 0.2s ease;
}
.issue-card:hover {
transform: translateY(-2px);
}
.issue-card label {
font-weight: 500;
}
.issue-card input:focus,
.issue-card select:focus,
.issue-card textarea:focus {
transform: scale(1.01);
transition: transform 0.1s ease;
}
.issue-card .bg-gray-50 {
border-left: 4px solid #e5e7eb;
}
/* 카드 내 아이콘 스타일 */
.issue-card i {
width: 16px;
text-align: center;
}
.badge-new { background: #dbeafe; color: #1e40af; }
.badge-processing { background: #fef3c7; color: #92400e; }
.badge-pending { background: #ede9fe; color: #7c3aed; }
@@ -605,18 +635,25 @@
</div>
<div class="collapse-content" id="${groupId}">
<div class="issue-table-container">
<table class="issue-table">
<thead>
<tr>
${createTableHeader()}
</tr>
</thead>
<tbody>
${issues.map(issue => createIssueRow(issue)).join('')}
</tbody>
</table>
</div>
${currentTab === 'in_progress' ?
// 진행 중: 카드 형식
`<div class="space-y-4 mt-4">
${issues.map(issue => createIssueRow(issue)).join('')}
</div>` :
// 완료됨: 테이블 형식
`<div class="issue-table-container">
<table class="issue-table">
<thead>
<tr>
${createTableHeader()}
</tr>
</thead>
<tbody>
${issues.map(issue => createIssueRow(issue)).join('')}
</tbody>
</table>
</div>`
}
</div>
</div>
`;
@@ -640,40 +677,105 @@
}
}
// 진행 중 생성
// 진행 중 카드 생성
function createInProgressRow(issue, project) {
return `
<tr data-issue-id="${issue.id}">
<td class="col-no font-medium">${issue.project_sequence_no || '-'}</td>
<td class="col-project">${project ? project.project_name : '-'}</td>
<td class="col-content text-wrap">${issue.final_description || issue.description}</td>
<td class="col-cause">${getCategoryText(issue.final_category || issue.category)}</td>
<td class="col-solution">
${createEditableField('solution', issue.solution || '', 'textarea', issue.id, true)}
</td>
<td class="col-department">
${createEditableField('responsible_department', issue.responsible_department || '', 'select', issue.id, true, getDepartmentOptions())}
</td>
<td class="col-person">
${createEditableField('responsible_person', issue.responsible_person || '', 'text', issue.id, true)}
</td>
<td class="col-date">
${createEditableField('expected_completion_date', issue.expected_completion_date ? issue.expected_completion_date.split('T')[0] : '', 'date', issue.id, true)}
</td>
<td class="col-completion">
<button onclick="completeIssue(${issue.id})" class="btn-sm bg-green-500 text-white hover:bg-green-600 whitespace-nowrap">완료처리</button>
</td>
<td class="col-photos">
<div class="photo-container">
${issue.photo_path ? `<img src="${issue.photo_path}" class="issue-photo" onclick="openPhotoModal('${issue.photo_path}')" alt="업로드 사진 1">` : ''}
${issue.photo_path2 ? `<img src="${issue.photo_path2}" class="issue-photo" onclick="openPhotoModal('${issue.photo_path2}')" alt="업로드 사진 2">` : ''}
${!issue.photo_path && !issue.photo_path2 ? '-' : ''}
<div class="issue-card bg-white border border-gray-200 rounded-xl p-6 mb-4 shadow-sm hover:shadow-md transition-shadow" data-issue-id="${issue.id}">
<!-- 카드 헤더 -->
<div class="flex items-center justify-between mb-4">
<div class="flex items-center space-x-3">
<span class="bg-blue-100 text-blue-800 px-3 py-1 rounded-full text-sm font-medium">No.${issue.project_sequence_no || '-'}</span>
<h3 class="text-lg font-semibold text-gray-900">${project ? project.project_name : '프로젝트 미지정'}</h3>
</div>
</td>
<td class="col-actions">
<button onclick="saveIssueChanges(${issue.id})" class="btn-sm bg-blue-500 text-white hover:bg-blue-600">저장</button>
</td>
</tr>
<div class="flex space-x-2">
<button onclick="saveIssueChanges(${issue.id})" class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
<i class="fas fa-save mr-1"></i>저장
</button>
<button onclick="completeIssue(${issue.id})" class="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition-colors">
<i class="fas fa-check mr-1"></i>완료처리
</button>
</div>
</div>
<!-- 카드 내용 -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- 왼쪽: 기본 정보 -->
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">부적합 내용</label>
<div class="p-3 bg-gray-50 rounded-lg text-gray-800 min-h-[80px]">
${issue.final_description || issue.description}
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">원인 분류</label>
<div class="p-3 bg-gray-50 rounded-lg text-gray-800">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
${getCategoryText(issue.final_category || issue.category)}
</span>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">업로드 사진</label>
<div class="flex gap-2">
${issue.photo_path ? `<img src="${issue.photo_path}" class="w-20 h-20 object-cover rounded-lg cursor-pointer border-2 border-gray-200 hover:border-blue-400 transition-colors" onclick="openPhotoModal('${issue.photo_path}')" alt="업로드 사진 1">` : '<div class="w-20 h-20 bg-gray-100 rounded-lg flex items-center justify-center text-gray-400 text-xs border-2 border-dashed border-gray-300">사진 없음</div>'}
${issue.photo_path2 ? `<img src="${issue.photo_path2}" class="w-20 h-20 object-cover rounded-lg cursor-pointer border-2 border-gray-200 hover:border-blue-400 transition-colors" onclick="openPhotoModal('${issue.photo_path2}')" alt="업로드 사진 2">` : '<div class="w-20 h-20 bg-gray-100 rounded-lg flex items-center justify-center text-gray-400 text-xs border-2 border-dashed border-gray-300">사진 없음</div>'}
</div>
</div>
</div>
<!-- 오른쪽: 편집 가능한 정보 -->
<div class="space-y-4">
<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="solution_${issue.id}" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none" placeholder="해결 방안을 입력하세요...">${issue.solution || ''}</textarea>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<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="responsible_department_${issue.id}" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
${getDepartmentOptions().map(opt =>
`<option value="${opt.value}" ${opt.value === (issue.responsible_department || '') ? 'selected' : ''}>${opt.text}</option>`
).join('')}
</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="responsible_person_${issue.id}" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" placeholder="담당자 이름" value="${issue.responsible_person || ''}">
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">
<i class="fas fa-calendar-alt text-red-500 mr-1"></i>조치 예상일
</label>
<input type="date" id="expected_completion_date_${issue.id}" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" value="${issue.expected_completion_date ? issue.expected_completion_date.split('T')[0] : ''}">
</div>
<!-- 진행 상태 표시 -->
<div class="mt-4 p-3 bg-blue-50 rounded-lg">
<div class="flex items-center justify-between">
<span class="text-sm text-blue-700 font-medium">
<i class="fas fa-clock mr-1"></i>진행 중
</span>
<span class="text-xs text-blue-600">
신고일: ${new Date(issue.report_date).toLocaleDateString('ko-KR')}
</span>
</div>
</div>
</div>
</div>
</div>
`;
}
@@ -951,6 +1053,9 @@
let value = element.value.trim();
if (value === '' || value === '선택하세요') {
value = null;
} else if (field === 'expected_completion_date' && value) {
// 날짜 필드는 ISO datetime 형식으로 변환
value = value + 'T00:00:00';
}
updates[field] = value;
}