feat: 현황판을 관리함 진행 중 카드 형식으로 완전 변경

🎨 UI Format Change:
-  가로 테이블 형식 (정보 압축, 가독성 저하)
-  관리함 진행 중 카드 형식 (직관적, 모바일 친화적)

📋 Card Layout Features:
- 헤더: No., 긴급 표시, 등록일
- 기본 정보: 프로젝트, 부적합 내용, 원인 분류
- 관리 정보: 해결방안, 담당부서, 담당자, 조치 예상일, 원인부서
- 업로드 사진: 클릭 시 모달 확대
- 진행 중 표시: 애니메이션 점 + 상태 표시

🎯 UX Improvements:
- 3컬럼 반응형 그리드 (데스크톱/태블릿/모바일)
- 카드 hover 효과: 위로 이동 + 그림자 + 좌측 파란 테두리
- 긴급 표시: 예상완료일 3일 이내 시 빨간 배지
- 부드러운 애니메이션: 0.3초 transition
- 사진 hover 효과: 확대 + 색상 변경

📱 Mobile Optimization:
- 1컬럼 레이아웃으로 모바일 최적화
- 터치 친화적 카드 인터페이스
- 스크롤 가능한 세로 배치
- 적절한 패딩과 간격

🎨 Visual Enhancements:
- 진행 중 상태 애니메이션 (pulse 효과)
- 일관된 색상 체계 (파란색 강조)
- 깔끔한 카드 디자인
- 정보 계층 구조 명확화

🔧 Code Structure:
- createIssueRow → createIssueCard 함수 변경
- 테이블 HTML → 카드 그리드 HTML
- 테이블 CSS → 카드 CSS 스타일
- 반응형 그리드 시스템 적용

Expected Result:
 관리함과 일관된 UI/UX
 대폭 향상된 가독성
 모바일 최적화
 직관적인 정보 표시
This commit is contained in:
Hyungi Ahn
2025-10-26 10:47:42 +09:00
parent 1907eddcf0
commit b4fb461a32

View File

@@ -27,34 +27,46 @@
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
/* 이슈 테이블 스타일 (가로 테이블 레이아웃) */
.dashboard-table {
min-width: 1200px;
/* 이슈 카드 스타일 (관리함 진행 중 스타일) */
.issue-card {
transition: all 0.3s ease;
border-left: 4px solid transparent;
}
.dashboard-table th {
position: sticky;
top: 0;
.issue-card:hover {
transform: translateY(-4px);
border-left-color: #3b82f6;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.issue-card label {
font-weight: 600;
color: #374151;
}
.issue-card .bg-gray-50 {
background-color: #f9fafb;
z-index: 10;
border: 1px solid #e5e7eb;
transition: all 0.2s ease;
}
.dashboard-table td {
vertical-align: middle;
.issue-card .bg-gray-50:hover {
background-color: #f3f4f6;
}
.dashboard-table tr:hover {
background-color: #f8fafc;
.issue-card .fas.fa-image:hover {
transform: scale(1.2);
color: #3b82f6;
}
.truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
/* 진행 중 애니메이션 */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.dashboard-table .fas.fa-image:hover {
transform: scale(1.1);
.animate-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
.progress-bar {
@@ -369,7 +381,7 @@
document.getElementById('activeProjects').textContent = activeProjectIds.size;
}
// 이슈 테이블 업데이트 (가로 테이블 스타일)
// 이슈 카드 업데이트 (관리함 진행 중 카드 스타일)
function updateProjectCards() {
const container = document.getElementById('projectDashboard');
const emptyState = document.getElementById('emptyState');
@@ -382,39 +394,18 @@
emptyState.classList.add('hidden');
// 가로 스크롤 테이블 생성
const tableHTML = `
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200 dashboard-table">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider min-w-[80px]">No.</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider min-w-[120px]">프로젝트</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider min-w-[200px]">부적합 내용</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider min-w-[80px]">카테고리</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider min-w-[150px]">해결방안</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider min-w-[100px]">담당부서</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider min-w-[100px]">담당자</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider min-w-[120px]">예상완료일</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider min-w-[100px]">원인부서</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider min-w-[80px]">사진</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider min-w-[100px]">등록일</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
${filteredIssues.map(issue => createIssueRow(issue)).join('')}
</tbody>
</table>
</div>
// 카드 그리드 생성
const cardsHTML = `
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
${filteredIssues.map(issue => createIssueCard(issue)).join('')}
</div>
`;
container.innerHTML = tableHTML;
container.innerHTML = cardsHTML;
}
// 이슈 테이블 행 생성 (가로 테이블 스타일, 읽기 전용)
function createIssueRow(issue) {
// 이슈 카드 생성 (관리함 진행 중 스타일, 읽기 전용)
function createIssueCard(issue) {
const project = projects.find(p => p.id === issue.project_id);
const projectName = project ? project.project_name : '미지정';
@@ -464,72 +455,93 @@
return diffDays <= 3; // 3일 이내 또는 지연
};
// 사진 아이콘 생성
const getPhotoIcons = () => {
const photos = [];
if (issue.photo_path) photos.push(issue.photo_path);
if (issue.photo_path2) photos.push(issue.photo_path2);
if (photos.length === 0) return '-';
return photos.map(photo =>
`<i class="fas fa-image text-blue-500 cursor-pointer hover:text-blue-700 mr-1"
onclick="openPhotoModal('${photo}')" title="사진 보기"></i>`
).join('');
};
// 긴급 표시가 있는 No. 생성
const getNoWithUrgent = () => {
const noText = issue.project_sequence_no || '-';
if (isUrgent()) {
return `<div class="flex items-center space-x-2">
<span class="font-medium text-blue-600">${noText}</span>
<span class="bg-red-100 text-red-800 text-xs px-2 py-1 rounded">긴급</span>
</div>`;
}
return `<span class="font-medium text-blue-600">${noText}</span>`;
};
return `
<tr class="hover:bg-gray-50 transition-colors duration-150">
<td class="px-4 py-4 whitespace-nowrap text-sm">${getNoWithUrgent()}</td>
<td class="px-4 py-4 text-sm text-gray-900">
<div class="max-w-[120px] truncate" title="${projectName}">${projectName}</div>
</td>
<td class="px-4 py-4 text-sm text-gray-900">
<div class="max-w-[200px] truncate" title="${issue.final_description || issue.description || '-'}">
${issue.final_description || issue.description || '-'}
<div class="issue-card bg-white rounded-lg border border-gray-200 p-6 hover:shadow-lg transition-all duration-200">
<!-- 헤더 -->
<div class="flex justify-between items-start mb-4">
<div class="flex items-center space-x-2">
<span class="text-lg font-bold text-blue-600">No.${issue.project_sequence_no || '-'}</span>
${isUrgent() ? '<span class="bg-red-100 text-red-800 text-xs font-medium px-2 py-1 rounded">긴급</span>' : ''}
</div>
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
${getCategoryText(issue.final_category || issue.category)}
</td>
<td class="px-4 py-4 text-sm text-gray-900">
<div class="max-w-[150px] truncate" title="${issue.solution || '-'}">
${issue.solution || '-'}
<span class="text-sm text-gray-500">${formatKSTDate(issue.report_date)}</span>
</div>
<!-- 기본 정보 -->
<div class="space-y-3 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">프로젝트</label>
<div class="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded">${projectName}</div>
</div>
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
${getDepartmentText(issue.responsible_department)}
</td>
<td class="px-4 py-4 text-sm text-gray-900">
<div class="max-w-[100px] truncate" title="${issue.responsible_person || '-'}">
${issue.responsible_person || '-'}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">부적합 내용</label>
<div class="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded min-h-[60px]">${issue.final_description || issue.description || '-'}</div>
</div>
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900 ${isUrgent() ? 'text-red-600 font-medium' : ''}">
${formatKSTDate(issue.expected_completion_date)}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-900">
${getDepartmentText(issue.cause_department)}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm">
${getPhotoIcons()}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
${formatKSTDate(issue.report_date)}
</td>
</tr>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">원인 분류</label>
<div class="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded">${getCategoryText(issue.final_category || issue.category)}</div>
</div>
</div>
<!-- 관리 정보 -->
<div class="space-y-3 border-t pt-4">
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">해결방안</label>
<div class="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded min-h-[40px]">${issue.solution || '-'}</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">담당부서</label>
<div class="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded">${getDepartmentText(issue.responsible_department)}</div>
</div>
</div>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">담당자</label>
<div class="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded">${issue.responsible_person || '-'}</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">조치 예상일</label>
<div class="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded ${isUrgent() ? 'text-red-600 font-medium' : ''}">${formatKSTDate(issue.expected_completion_date)}</div>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">원인부서</label>
<div class="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded">${getDepartmentText(issue.cause_department)}</div>
</div>
</div>
<!-- 업로드 사진 -->
${issue.photo_path || issue.photo_path2 ? `
<div class="border-t pt-4 mt-4">
<label class="block text-sm font-medium text-gray-700 mb-2">업로드 사진</label>
<div class="flex space-x-2">
${issue.photo_path ? `
<div class="w-16 h-16 bg-gray-100 rounded border flex items-center justify-center cursor-pointer hover:bg-gray-200 transition-colors" onclick="openPhotoModal('${issue.photo_path}')">
<i class="fas fa-image text-gray-400"></i>
</div>
` : ''}
${issue.photo_path2 ? `
<div class="w-16 h-16 bg-gray-100 rounded border flex items-center justify-center cursor-pointer hover:bg-gray-200 transition-colors" onclick="openPhotoModal('${issue.photo_path2}')">
<i class="fas fa-image text-gray-400"></i>
</div>
` : ''}
</div>
</div>
` : ''}
<!-- 진행 중 표시 -->
<div class="border-t pt-4 mt-4 flex items-center justify-between">
<div class="flex items-center space-x-2">
<div class="w-2 h-2 bg-blue-500 rounded-full animate-pulse"></div>
<span class="text-sm font-medium text-blue-600">진행 중</span>
</div>
<span class="text-xs text-gray-500">신고일: ${formatKSTDate(issue.report_date)}</span>
</div>
</div>
`;
}