feat: 현황판 UI를 관리함 진행 중 스타일로 개선

🎨 UI Redesign:
- 기존 프로젝트별 그룹 카드 → 개별 이슈 카드로 변경
- 관리함 진행 중 페이지와 동일한 카드 형식 적용
- 읽기 전용으로 구현 (수정 기능 없음)

📋 Card Content:
- 헤더: No., 긴급 표시, 등록일
- 기본 정보: 프로젝트, 부적합 내용, 카테고리
- 관리 정보: 해결방안, 담당부서, 담당자, 예상완료일, 원인부서
- 사진 정보: 업로드 사진 미리보기 (클릭 시 확대)

🔧 Features:
- 내용이 없는 부분은 '-'로 표시
- 사진 모달 확대 기능 (ESC 키 지원)
- 긴급도 표시 (예상완료일 3일 이내)
- 반응형 그리드 레이아웃 (1/2/3 컬럼)

🎯 Layout:
- 데스크톱: 3컬럼 그리드
- 태블릿: 2컬럼 그리드
- 모바일: 1컬럼 그리드

Expected Result:
 관리함과 일관된 UI/UX
 상세 정보 한눈에 확인 가능
 사진 확대 기능으로 편의성 향상
 읽기 전용으로 안전한 조회
This commit is contained in:
Hyungi Ahn
2025-10-26 10:33:45 +09:00
parent f7fa20605c
commit 591f3307a9

View File

@@ -27,24 +27,31 @@
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.project-card {
/* 이슈 카드 스타일 (관리함 진행 중 스타일) */
.issue-card {
transition: all 0.2s ease;
border-left: 4px solid transparent;
}
.project-card:hover {
transform: translateX(5px);
.issue-card:hover {
transform: translateY(-2px);
border-left-color: #3b82f6;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.issue-mini-card {
.issue-card label {
font-weight: 600;
color: #374151;
}
.issue-card .bg-gray-50 {
background-color: #f9fafb;
border: 1px solid #e5e7eb;
transition: all 0.2s ease;
}
.issue-mini-card:hover {
transform: scale(1.02);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
.issue-card .bg-gray-50:hover {
background-color: #f3f4f6;
}
.progress-bar {
@@ -359,7 +366,7 @@
document.getElementById('activeProjects').textContent = activeProjectIds.size;
}
// 프로젝트 카드 업데이트
// 이슈 카드 업데이트 (관리함 진행 중 스타일)
function updateProjectCards() {
const container = document.getElementById('projectDashboard');
const emptyState = document.getElementById('emptyState');
@@ -372,101 +379,182 @@
emptyState.classList.add('hidden');
// 프로젝트별 그룹화
const projectGroups = {};
filteredIssues.forEach(issue => {
const projectId = issue.project_id || 'unassigned';
if (!projectGroups[projectId]) {
projectGroups[projectId] = [];
}
projectGroups[projectId].push(issue);
});
// 프로젝트 카드 생성
const projectCards = Object.keys(projectGroups).map(projectId => {
const issues = projectGroups[projectId];
const project = projects.find(p => p.id == projectId);
const projectName = project ? project.project_name : '프로젝트 미지정';
return createProjectCard(projectName, issues);
}).join('');
container.innerHTML = projectCards;
// 이슈 카드들을 생성 (관리함 진행 중 스타일)
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>`;
}
// 프로젝트 카드 생성
function createProjectCard(projectName, issues) {
const urgentCount = issues.filter(issue => {
// 이슈 카드 생성 (관리함 진행 중 스타일, 읽기 전용)
function createIssueCard(issue) {
const project = projects.find(p => p.id === issue.project_id);
const projectName = project ? project.project_name : '미지정';
// 부서 텍스트 변환
const getDepartmentText = (dept) => {
const deptMap = {
'production': '생산',
'quality': '품질',
'purchasing': '구매',
'design': '설계',
'sales': '영업'
};
return dept ? deptMap[dept] || dept : '-';
};
// 카테고리 텍스트 변환
const getCategoryText = (category) => {
const categoryMap = {
'quality': '품질',
'safety': '안전',
'environment': '환경',
'process': '공정',
'equipment': '장비',
'material': '자재',
'etc': '기타'
};
return category ? categoryMap[category] || category : '-';
};
// 날짜 포맷팅
const formatKSTDate = (dateStr) => {
if (!dateStr) return '-';
const date = new Date(dateStr);
return date.toLocaleDateString('ko-KR', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
};
// 긴급도 체크 (예상완료일 기준)
const isUrgent = () => {
if (!issue.expected_completion_date) return false;
const expectedDate = new Date(issue.expected_completion_date);
const now = new Date();
const diffDays = (expectedDate - now) / (1000 * 60 * 60 * 24);
return diffDays <= 3;
}).length;
return diffDays <= 3; // 3일 이내 또는 지연
};
return `
<div class="project-card bg-white rounded-xl shadow-sm p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-900">${projectName}</h3>
<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="bg-blue-100 text-blue-800 px-3 py-1 rounded-full text-sm font-medium">
${issues.length}
</span>
${urgentCount > 0 ? `<span class="bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm font-medium">
긴급 ${urgentCount}
</span>` : ''}
<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>
<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>
<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>
<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="dashboard-grid">
${issues.map(issue => createIssueMiniCard(issue)).join('')}
<!-- 관리 정보 -->
<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>
`;
}
// 이슈 미니 카드 생성
function createIssueMiniCard(issue) {
const isUrgent = issue.expected_completion_date &&
(new Date(issue.expected_completion_date) - new Date()) / (1000 * 60 * 60 * 24) <= 3;
// 사진 모달 열기
function openPhotoModal(photoPath) {
let modal = document.getElementById('photoModal');
const urgentClass = isUrgent ? 'border-red-200 bg-red-50' : 'border-gray-200 bg-white';
if (!modal) {
// 모달이 없으면 생성
const modalHTML = `
<div id="photoModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="relative max-w-4xl max-h-full p-4">
<img id="modalPhoto" src="" alt="부적합 사진" class="max-w-full max-h-full object-contain rounded-lg">
<button onclick="closePhotoModal()" class="absolute top-4 right-4 text-white text-2xl hover:text-gray-300">
<i class="fas fa-times"></i>
</button>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHTML);
modal = document.getElementById('photoModal');
}
return `
<div class="issue-mini-card ${urgentClass} border rounded-lg p-4 cursor-pointer"
onclick="viewIssueDetail(${issue.id})">
<div class="flex items-start justify-between mb-2">
<span class="bg-blue-100 text-blue-800 px-2 py-1 rounded text-xs font-medium">
No.${issue.project_sequence_no || '-'}
</span>
${isUrgent ? '<i class="fas fa-exclamation-triangle text-red-500"></i>' : ''}
</div>
<p class="text-sm text-gray-800 mb-2 line-clamp-2">
${issue.final_description || issue.description}
</p>
<div class="flex items-center justify-between text-xs text-gray-500">
<span>${new Date(issue.report_date).toLocaleDateString('ko-KR')}</span>
${issue.expected_completion_date ?
`<span class="${isUrgent ? 'text-red-600 font-medium' : ''}">
마감: ${new Date(issue.expected_completion_date).toLocaleDateString('ko-KR')}
</span>` :
'<span>마감일 미정</span>'
}
</div>
${issue.responsible_department ?
`<div class="mt-2">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs bg-gray-100 text-gray-800">
${getDepartmentText(issue.responsible_department)}
</span>
</div>` : ''
}
</div>
`;
document.getElementById('modalPhoto').src = photoPath;
modal.classList.remove('hidden');
}
// 사진 모달 닫기
function closePhotoModal() {
const modal = document.getElementById('photoModal');
if (modal) {
modal.classList.add('hidden');
}
}
// ESC 키로 모달 닫기
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closePhotoModal();
}
});
// 필터 및 정렬 함수들
function filterByProject() {
const projectId = document.getElementById('projectFilter').value;