feat: 현황판 UI를 가로 테이블 형식으로 완전 개선

🔄 Major UI Redesign:
-  세로 카드 레이아웃 (가독성 저하)
-  가로 테이블 레이아웃 (한눈에 정보 확인)

📊 Table Structure:
- 11개 컬럼: No., 프로젝트, 부적합내용, 카테고리, 해결방안, 담당부서, 담당자, 예상완료일, 원인부서, 사진, 등록일
- 가로 스크롤 지원으로 모든 정보 표시
- 각 컬럼별 최적화된 너비 설정

🎯 UX Improvements:
- 긴급 표시: 예상완료일 3일 이내 시 빨간 배지
- 텍스트 truncate: 긴 내용은 말줄임표로 처리 (hover시 전체 내용 표시)
- 사진 아이콘: 클릭 시 모달로 확대
- 행 hover 효과: 마우스 오버 시 배경색 변경

📱 Responsive Features:
- 가로 스크롤로 모바일에서도 모든 정보 확인 가능
- Sticky 헤더: 스크롤 시 헤더 고정
- 최소 너비 보장으로 정보 압축 방지

🎨 Visual Enhancements:
- 깔끔한 테이블 디자인
- 적절한 패딩과 간격
- 일관된 색상 체계
- 부드러운 hover 애니메이션

Expected Result:
 대폭 향상된 가독성
 한 화면에서 모든 정보 확인
 효율적인 공간 활용
 관리함과 일관된 UX
This commit is contained in:
Hyungi Ahn
2025-10-26 10:36:58 +09:00
parent 591f3307a9
commit d9cdaf622e

View File

@@ -27,31 +27,34 @@
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
/* 이슈 카드 스타일 (관리함 진행 중 스타일) */
.issue-card {
transition: all 0.2s ease;
border-left: 4px solid transparent;
/* 이슈 테이블 스타일 (가로 테이블 레이아웃) */
.dashboard-table {
min-width: 1200px;
}
.issue-card:hover {
transform: translateY(-2px);
border-left-color: #3b82f6;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.issue-card label {
font-weight: 600;
color: #374151;
}
.issue-card .bg-gray-50 {
.dashboard-table th {
position: sticky;
top: 0;
background-color: #f9fafb;
border: 1px solid #e5e7eb;
transition: all 0.2s ease;
z-index: 10;
}
.issue-card .bg-gray-50:hover {
background-color: #f3f4f6;
.dashboard-table td {
vertical-align: middle;
}
.dashboard-table tr:hover {
background-color: #f8fafc;
}
.truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dashboard-table .fas.fa-image:hover {
transform: scale(1.1);
}
.progress-bar {
@@ -366,7 +369,7 @@
document.getElementById('activeProjects').textContent = activeProjectIds.size;
}
// 이슈 카드 업데이트 (관리함 진행 중 스타일)
// 이슈 테이블 업데이트 (가로 테이블 스타일)
function updateProjectCards() {
const container = document.getElementById('projectDashboard');
const emptyState = document.getElementById('emptyState');
@@ -379,13 +382,39 @@
emptyState.classList.add('hidden');
// 이슈 카드들을 생성 (관리함 진행 중 스타일)
const issueCards = filteredIssues.map(issue => createIssueCard(issue)).join('');
container.innerHTML = `<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">${issueCards}</div>`;
// 가로 스크롤 테이블 생성
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>
</div>
`;
container.innerHTML = tableHTML;
}
// 이슈 카드 생성 (관리함 진행 중 스타일, 읽기 전용)
function createIssueCard(issue) {
// 이슈 테이블 행 생성 (가로 테이블 스타일, 읽기 전용)
function createIssueRow(issue) {
const project = projects.find(p => p.id === issue.project_id);
const projectName = project ? project.project_name : '미지정';
@@ -435,84 +464,72 @@
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 `
<div class="issue-card bg-white rounded-lg border border-gray-200 p-6 hover:shadow-md 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>' : ''}
<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>
<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>
</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 || '-'}
</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 min-h-[60px]">${issue.final_description || issue.description || '-'}</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>
<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">${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" 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" onclick="openPhotoModal('${issue.photo_path2}')">
<i class="fas fa-image text-gray-400"></i>
</div>
` : ''}
</div>
</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>
`;
}