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:
@@ -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);
|
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;
|
transition: all 0.2s ease;
|
||||||
border-left: 4px solid transparent;
|
border-left: 4px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-card:hover {
|
.issue-card:hover {
|
||||||
transform: translateX(5px);
|
transform: translateY(-2px);
|
||||||
border-left-color: #3b82f6;
|
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;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.issue-mini-card:hover {
|
.issue-card .bg-gray-50:hover {
|
||||||
transform: scale(1.02);
|
background-color: #f3f4f6;
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-bar {
|
.progress-bar {
|
||||||
@@ -359,7 +366,7 @@
|
|||||||
document.getElementById('activeProjects').textContent = activeProjectIds.size;
|
document.getElementById('activeProjects').textContent = activeProjectIds.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 프로젝트 카드 업데이트
|
// 이슈 카드 업데이트 (관리함 진행 중 스타일)
|
||||||
function updateProjectCards() {
|
function updateProjectCards() {
|
||||||
const container = document.getElementById('projectDashboard');
|
const container = document.getElementById('projectDashboard');
|
||||||
const emptyState = document.getElementById('emptyState');
|
const emptyState = document.getElementById('emptyState');
|
||||||
@@ -372,101 +379,182 @@
|
|||||||
|
|
||||||
emptyState.classList.add('hidden');
|
emptyState.classList.add('hidden');
|
||||||
|
|
||||||
// 프로젝트별 그룹화
|
// 이슈 카드들을 생성 (관리함 진행 중 스타일)
|
||||||
const projectGroups = {};
|
const issueCards = filteredIssues.map(issue => createIssueCard(issue)).join('');
|
||||||
filteredIssues.forEach(issue => {
|
container.innerHTML = `<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">${issueCards}</div>`;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 프로젝트 카드 생성
|
// 이슈 카드 생성 (관리함 진행 중 스타일, 읽기 전용)
|
||||||
function createProjectCard(projectName, issues) {
|
function createIssueCard(issue) {
|
||||||
const urgentCount = issues.filter(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;
|
if (!issue.expected_completion_date) return false;
|
||||||
const expectedDate = new Date(issue.expected_completion_date);
|
const expectedDate = new Date(issue.expected_completion_date);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const diffDays = (expectedDate - now) / (1000 * 60 * 60 * 24);
|
const diffDays = (expectedDate - now) / (1000 * 60 * 60 * 24);
|
||||||
return diffDays <= 3;
|
return diffDays <= 3; // 3일 이내 또는 지연
|
||||||
}).length;
|
};
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="project-card bg-white rounded-xl shadow-sm p-6">
|
<div class="issue-card bg-white rounded-lg border border-gray-200 p-6 hover:shadow-md transition-all duration-200">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<!-- 헤더 -->
|
||||||
<h3 class="text-lg font-semibold text-gray-900">${projectName}</h3>
|
<div class="flex justify-between items-start mb-4">
|
||||||
<div class="flex items-center space-x-2">
|
<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">
|
<span class="text-lg font-bold text-blue-600">No.${issue.project_sequence_no || '-'}</span>
|
||||||
${issues.length}건
|
${isUrgent() ? '<span class="bg-red-100 text-red-800 text-xs font-medium px-2 py-1 rounded">긴급</span>' : ''}
|
||||||
</span>
|
</div>
|
||||||
${urgentCount > 0 ? `<span class="bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm font-medium">
|
<span class="text-sm text-gray-500">${formatKSTDate(issue.report_date)}</span>
|
||||||
긴급 ${urgentCount}건
|
</div>
|
||||||
</span>` : ''}
|
|
||||||
|
<!-- 기본 정보 -->
|
||||||
|
<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>
|
</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>
|
</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>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 이슈 미니 카드 생성
|
// 사진 모달 열기
|
||||||
function createIssueMiniCard(issue) {
|
function openPhotoModal(photoPath) {
|
||||||
const isUrgent = issue.expected_completion_date &&
|
let modal = document.getElementById('photoModal');
|
||||||
(new Date(issue.expected_completion_date) - new Date()) / (1000 * 60 * 60 * 24) <= 3;
|
|
||||||
|
|
||||||
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 `
|
document.getElementById('modalPhoto').src = photoPath;
|
||||||
<div class="issue-mini-card ${urgentClass} border rounded-lg p-4 cursor-pointer"
|
modal.classList.remove('hidden');
|
||||||
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>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 사진 모달 닫기
|
||||||
|
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() {
|
function filterByProject() {
|
||||||
const projectId = document.getElementById('projectFilter').value;
|
const projectId = document.getElementById('projectFilter').value;
|
||||||
|
|||||||
Reference in New Issue
Block a user