feat: 목록 관리 및 보고서 페이지 개선
- 목록 관리 페이지에 고급 필터링 시스템 추가 - 프로젝트별, 검토상태별, 날짜별 필터링 - 검토 완료/필요 항목 시각적 구분 및 정렬 - 해결 시간 입력 + 확인 버튼으로 검토 완료 처리 - 부적합 조회 페이지에 동일한 필터링 기능 적용 - 검토 상태에 따른 카드 스타일링 (음영 처리) - JavaScript 템플릿 리터럴 오류 수정 - 보고서 페이지 프로젝트별 분석 기능 추가 - 프로젝트 선택 드롭다운 추가 - 총 작업 공수를 프로젝트별 일일공수 데이터로 계산 - 부적합 처리 시간, 카테고리 분석, 상세 목록 모두 프로젝트별 필터링 - localStorage 키 이름 통일 (daily-work-data)
This commit is contained in:
@@ -191,6 +191,7 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
<!-- 네비게이션 -->
|
||||
<nav class="bg-white border-b">
|
||||
<div class="container mx-auto px-4">
|
||||
@@ -210,6 +211,9 @@
|
||||
<button class="nav-link" onclick="showSection('summary')" style="display:none;" id="summaryBtn">
|
||||
<i class="fas fa-chart-bar mr-2"></i>보고서
|
||||
</button>
|
||||
<a href="project-management.html" class="nav-link" style="display:none;" id="projectBtn">
|
||||
<i class="fas fa-folder-open mr-2"></i>프로젝트 관리
|
||||
</a>
|
||||
<a href="admin.html" class="nav-link" style="display:none;" id="adminBtn">
|
||||
<i class="fas fa-users-cog mr-2"></i>관리
|
||||
</a>
|
||||
@@ -284,6 +288,17 @@
|
||||
<input type="file" id="galleryInput" accept="image/*" class="hidden" multiple>
|
||||
</div>
|
||||
|
||||
<!-- 프로젝트 선택 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<i class="fas fa-folder-open mr-1"></i>프로젝트
|
||||
</label>
|
||||
<select id="projectSelect" class="input-field w-full px-4 py-2 rounded-lg" required>
|
||||
<option value="">프로젝트를 선택하세요</option>
|
||||
<!-- 활성 프로젝트들이 여기에 로드됩니다 -->
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 카테고리 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">카테고리</label>
|
||||
@@ -318,7 +333,56 @@
|
||||
<!-- 목록 관리 섹션 -->
|
||||
<section id="listSection" class="hidden container mx-auto px-4 py-6">
|
||||
<div class="bg-white rounded-xl shadow-sm p-6">
|
||||
<h2 class="text-lg font-semibold text-gray-800 mb-4">부적합 사항 목록</h2>
|
||||
<div class="mb-4">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-lg font-semibold text-gray-800">부적합 사항 목록</h2>
|
||||
<button onclick="displayIssueList()" class="text-blue-600 hover:text-blue-800 text-sm">
|
||||
<i class="fas fa-refresh mr-1"></i>새로고침
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 필터 섹션 -->
|
||||
<div class="bg-gray-50 p-4 rounded-lg mb-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<!-- 프로젝트 필터 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">프로젝트</label>
|
||||
<select id="listProjectFilter" class="w-full px-3 py-2 border border-gray-300 rounded text-sm" onchange="displayIssueList()">
|
||||
<option value="">전체 프로젝트</option>
|
||||
<!-- 프로젝트 옵션들이 여기에 로드됩니다 -->
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 검토 상태 필터 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">검토 상태</label>
|
||||
<select id="reviewStatusFilter" class="w-full px-3 py-2 border border-gray-300 rounded text-sm" onchange="displayIssueList()">
|
||||
<option value="">전체</option>
|
||||
<option value="pending">검토 필요</option>
|
||||
<option value="completed">검토 완료</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 날짜 필터 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">날짜</label>
|
||||
<select id="dateFilter" class="w-full px-3 py-2 border border-gray-300 rounded text-sm" onchange="displayIssueList()">
|
||||
<option value="">전체</option>
|
||||
<option value="today">오늘</option>
|
||||
<option value="week">이번 주</option>
|
||||
<option value="month">이번 달</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- 사용자 정의 날짜 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">특정 날짜</label>
|
||||
<input type="date" id="customDateFilter" class="w-full px-3 py-2 border border-gray-300 rounded text-sm" onchange="displayIssueList()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="issueList" class="space-y-4">
|
||||
<!-- 목록이 여기에 표시됩니다 -->
|
||||
</div>
|
||||
@@ -330,9 +394,19 @@
|
||||
<div class="bg-white rounded-xl shadow-sm p-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-lg font-semibold text-gray-800">작업 보고서</h2>
|
||||
<button onclick="printReport()" class="btn-primary px-4 py-2 rounded-lg text-sm">
|
||||
<i class="fas fa-print mr-2"></i>인쇄
|
||||
</button>
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- 프로젝트 선택 -->
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm font-medium text-gray-700">프로젝트:</label>
|
||||
<select id="reportProjectFilter" class="px-3 py-2 border border-gray-300 rounded text-sm" onchange="generateReport()">
|
||||
<option value="">전체 프로젝트</option>
|
||||
<!-- 프로젝트 옵션들이 여기에 로드됩니다 -->
|
||||
</select>
|
||||
</div>
|
||||
<button onclick="printReport()" class="btn-primary px-4 py-2 rounded-lg text-sm">
|
||||
<i class="fas fa-print mr-2"></i>인쇄
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="reportContent">
|
||||
<!-- 보고서 내용이 여기에 표시됩니다 -->
|
||||
@@ -340,7 +414,7 @@
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="/static/js/api.js?v=20250917"></script>
|
||||
<script src="/static/js/image-utils.js?v=20250917"></script>
|
||||
<script src="/static/js/date-utils.js?v=20250917"></script>
|
||||
@@ -361,6 +435,9 @@
|
||||
// 권한에 따른 메뉴 표시/숨김
|
||||
updateNavigation();
|
||||
|
||||
// 프로젝트 로드
|
||||
loadProjects();
|
||||
|
||||
loadIssues();
|
||||
|
||||
// URL 해시 처리
|
||||
@@ -404,16 +481,20 @@
|
||||
const summaryBtn = document.getElementById('summaryBtn');
|
||||
const adminBtn = document.getElementById('adminBtn');
|
||||
|
||||
const projectBtn = document.getElementById('projectBtn');
|
||||
|
||||
if (currentUser.role === 'admin') {
|
||||
// 관리자는 모든 메뉴 표시
|
||||
listBtn.style.display = '';
|
||||
summaryBtn.style.display = '';
|
||||
projectBtn.style.display = '';
|
||||
adminBtn.style.display = '';
|
||||
adminBtn.innerHTML = '<i class="fas fa-users-cog mr-2"></i>사용자 관리';
|
||||
} else {
|
||||
// 일반 사용자는 제한된 메뉴만 표시
|
||||
listBtn.style.display = 'none';
|
||||
summaryBtn.style.display = 'none';
|
||||
projectBtn.style.display = 'none';
|
||||
adminBtn.style.display = '';
|
||||
adminBtn.innerHTML = '<i class="fas fa-key mr-2"></i>비밀번호 변경';
|
||||
}
|
||||
@@ -664,6 +745,12 @@
|
||||
const submitBtn = e.target.querySelector('button[type="submit"]');
|
||||
const originalBtnContent = submitBtn.innerHTML;
|
||||
const description = document.getElementById('description').value.trim();
|
||||
const projectId = document.getElementById('projectSelect').value;
|
||||
|
||||
if (!projectId) {
|
||||
alert('프로젝트를 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!description) {
|
||||
alert('설명을 입력해주세요.');
|
||||
@@ -714,10 +801,14 @@
|
||||
updateLoadingMessage('서버로 전송 중...', '네트워크 상태에 따라 시간이 걸릴 수 있습니다');
|
||||
updateProgress(60);
|
||||
|
||||
// 선택된 프로젝트 정보 가져오기
|
||||
const selectedProject = getSelectedProject(projectId);
|
||||
|
||||
const issueData = {
|
||||
photos: currentPhotos, // 배열로 전달
|
||||
category: document.getElementById('category').value,
|
||||
description: description
|
||||
description: description,
|
||||
project_id: parseInt(projectId)
|
||||
};
|
||||
|
||||
const startTime = Date.now();
|
||||
@@ -781,22 +872,203 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 프로젝트 로드
|
||||
function loadProjects() {
|
||||
const saved = localStorage.getItem('work-report-projects');
|
||||
if (saved) {
|
||||
const projects = JSON.parse(saved);
|
||||
const activeProjects = projects.filter(p => p.isActive);
|
||||
|
||||
// 부적합 등록 폼의 프로젝트 선택 (활성 프로젝트만)
|
||||
const projectSelect = document.getElementById('projectSelect');
|
||||
if (projectSelect) {
|
||||
projectSelect.innerHTML = '<option value="">프로젝트를 선택하세요</option>';
|
||||
|
||||
activeProjects.forEach(project => {
|
||||
const option = document.createElement('option');
|
||||
option.value = project.id;
|
||||
option.textContent = `${project.jobNo} - ${project.projectName}`;
|
||||
projectSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// 목록 관리의 프로젝트 필터 (모든 프로젝트)
|
||||
const listProjectFilter = document.getElementById('listProjectFilter');
|
||||
if (listProjectFilter) {
|
||||
listProjectFilter.innerHTML = '<option value="">전체 프로젝트</option>';
|
||||
|
||||
projects.forEach(project => {
|
||||
const option = document.createElement('option');
|
||||
option.value = project.id;
|
||||
option.textContent = `${project.jobNo} - ${project.projectName}${!project.isActive ? ' (비활성)' : ''}`;
|
||||
listProjectFilter.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// 보고서의 프로젝트 필터 (모든 프로젝트)
|
||||
const reportProjectFilter = document.getElementById('reportProjectFilter');
|
||||
if (reportProjectFilter) {
|
||||
reportProjectFilter.innerHTML = '<option value="">전체 프로젝트</option>';
|
||||
|
||||
projects.forEach(project => {
|
||||
const option = document.createElement('option');
|
||||
option.value = project.id;
|
||||
option.textContent = `${project.jobNo} - ${project.projectName}${!project.isActive ? ' (비활성)' : ''}`;
|
||||
reportProjectFilter.appendChild(option);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 선택된 프로젝트 정보 가져오기
|
||||
function getSelectedProject(projectId) {
|
||||
const saved = localStorage.getItem('work-report-projects');
|
||||
if (saved) {
|
||||
const projects = JSON.parse(saved);
|
||||
return projects.find(p => p.id == projectId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 프로젝트 정보 표시용 함수
|
||||
function getProjectInfo(projectId) {
|
||||
if (!projectId) {
|
||||
return '<span class="text-gray-500">프로젝트 미지정</span>';
|
||||
}
|
||||
|
||||
const project = getSelectedProject(projectId);
|
||||
if (project) {
|
||||
return `${project.jobNo} - ${project.projectName}`;
|
||||
} else {
|
||||
return `<span class="text-red-500">프로젝트 ID: ${projectId} (정보 없음)</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
// LocalStorage 관련 함수는 더 이상 사용하지 않음
|
||||
function saveIssues() {
|
||||
// Deprecated - API 사용
|
||||
}
|
||||
|
||||
// 검토 상태 확인 함수
|
||||
function isReviewCompleted(issue) {
|
||||
return issue.status === 'complete' && issue.work_hours && issue.work_hours > 0;
|
||||
}
|
||||
|
||||
// 날짜 필터링 함수
|
||||
function filterByDate(issues, dateFilter, customDate) {
|
||||
if (customDate) {
|
||||
const targetDate = new Date(customDate);
|
||||
return issues.filter(issue => {
|
||||
const issueDate = new Date(issue.report_date);
|
||||
return issueDate.toDateString() === targetDate.toDateString();
|
||||
});
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
|
||||
switch (dateFilter) {
|
||||
case 'today':
|
||||
return issues.filter(issue => {
|
||||
const issueDate = new Date(issue.report_date);
|
||||
return issueDate >= today;
|
||||
});
|
||||
case 'week':
|
||||
const weekStart = new Date(today);
|
||||
weekStart.setDate(today.getDate() - today.getDay());
|
||||
return issues.filter(issue => {
|
||||
const issueDate = new Date(issue.report_date);
|
||||
return issueDate >= weekStart;
|
||||
});
|
||||
case 'month':
|
||||
const monthStart = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
return issues.filter(issue => {
|
||||
const issueDate = new Date(issue.report_date);
|
||||
return issueDate >= monthStart;
|
||||
});
|
||||
default:
|
||||
return issues;
|
||||
}
|
||||
}
|
||||
|
||||
// 목록 표시
|
||||
function displayIssueList() {
|
||||
const container = document.getElementById('issueList');
|
||||
container.innerHTML = '';
|
||||
|
||||
if (issues.length === 0) {
|
||||
container.innerHTML = '<p class="text-gray-500 text-center py-8">등록된 부적합 사항이 없습니다.</p>';
|
||||
// 필터 값 가져오기
|
||||
const selectedProjectId = document.getElementById('listProjectFilter').value;
|
||||
const reviewStatusFilter = document.getElementById('reviewStatusFilter').value;
|
||||
const dateFilter = document.getElementById('dateFilter').value;
|
||||
const customDate = document.getElementById('customDateFilter').value;
|
||||
|
||||
let filteredIssues = [...issues];
|
||||
|
||||
// 프로젝트 필터 적용
|
||||
if (selectedProjectId) {
|
||||
filteredIssues = filteredIssues.filter(issue => {
|
||||
const issueProjectId = issue.project_id || issue.projectId;
|
||||
return issueProjectId && (issueProjectId == selectedProjectId || issueProjectId.toString() === selectedProjectId.toString());
|
||||
});
|
||||
}
|
||||
|
||||
// 검토 상태 필터 적용
|
||||
if (reviewStatusFilter) {
|
||||
filteredIssues = filteredIssues.filter(issue => {
|
||||
const isCompleted = isReviewCompleted(issue);
|
||||
return reviewStatusFilter === 'completed' ? isCompleted : !isCompleted;
|
||||
});
|
||||
}
|
||||
|
||||
// 날짜 필터 적용
|
||||
if (dateFilter || customDate) {
|
||||
filteredIssues = filterByDate(filteredIssues, dateFilter, customDate);
|
||||
}
|
||||
|
||||
if (filteredIssues.length === 0) {
|
||||
container.innerHTML = `<p class="text-gray-500 text-center py-8">조건에 맞는 부적합 사항이 없습니다.</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
issues.forEach(issue => {
|
||||
// 검토 상태별로 분류 및 정렬
|
||||
const pendingIssues = filteredIssues.filter(issue => !isReviewCompleted(issue));
|
||||
const completedIssues = filteredIssues.filter(issue => isReviewCompleted(issue));
|
||||
|
||||
// 검토 필요 항목을 먼저 표시
|
||||
if (pendingIssues.length > 0) {
|
||||
const pendingHeader = document.createElement('div');
|
||||
pendingHeader.className = 'mb-4';
|
||||
pendingHeader.innerHTML = `
|
||||
<h3 class="text-md font-semibold text-orange-700 flex items-center">
|
||||
<i class="fas fa-exclamation-triangle mr-2"></i>검토 필요 (${pendingIssues.length}건)
|
||||
</h3>
|
||||
`;
|
||||
container.appendChild(pendingHeader);
|
||||
|
||||
pendingIssues.forEach(issue => {
|
||||
container.appendChild(createIssueCard(issue, false));
|
||||
});
|
||||
}
|
||||
|
||||
// 검토 완료 항목을 아래에 표시
|
||||
if (completedIssues.length > 0) {
|
||||
const completedHeader = document.createElement('div');
|
||||
completedHeader.className = 'mb-4 mt-8';
|
||||
completedHeader.innerHTML = `
|
||||
<h3 class="text-md font-semibold text-green-700 flex items-center">
|
||||
<i class="fas fa-check-circle mr-2"></i>검토 완료 (${completedIssues.length}건)
|
||||
</h3>
|
||||
`;
|
||||
container.appendChild(completedHeader);
|
||||
|
||||
completedIssues.forEach(issue => {
|
||||
container.appendChild(createIssueCard(issue, true));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 부적합 사항 카드 생성 함수
|
||||
function createIssueCard(issue, isCompleted) {
|
||||
const categoryNames = {
|
||||
material_missing: '자재누락',
|
||||
design_error: '설계미스',
|
||||
@@ -805,10 +1077,31 @@
|
||||
};
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.className = 'border rounded-lg p-6 bg-gray-50';
|
||||
// 검토 완료 상태에 따른 스타일링
|
||||
const baseClasses = 'border rounded-lg p-6 transition-all duration-200';
|
||||
const statusClasses = isCompleted
|
||||
? 'bg-gray-100 opacity-75 border-gray-300'
|
||||
: 'bg-gray-50 border-gray-200 hover:shadow-md';
|
||||
div.className = `${baseClasses} ${statusClasses}`;
|
||||
div.id = `issue-card-${issue.id}`;
|
||||
// 프로젝트 정보 가져오기
|
||||
const projectInfo = getProjectInfo(issue.project_id || issue.projectId);
|
||||
|
||||
div.innerHTML = `
|
||||
<div class="space-y-4">
|
||||
<!-- 프로젝트 정보 및 상태 (오른쪽 상단) -->
|
||||
<div class="flex justify-between items-start mb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
${isCompleted ?
|
||||
'<div class="px-2 py-1 bg-green-100 text-green-800 rounded-full text-xs font-medium"><i class="fas fa-check-circle mr-1"></i>검토완료</div>' :
|
||||
'<div class="px-2 py-1 bg-orange-100 text-orange-800 rounded-full text-xs font-medium"><i class="fas fa-exclamation-triangle mr-1"></i>검토필요</div>'
|
||||
}
|
||||
</div>
|
||||
<div class="px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-xs font-medium">
|
||||
<i class="fas fa-folder-open mr-1"></i>${projectInfo}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 사진과 기본 정보 -->
|
||||
<div class="flex gap-4">
|
||||
<!-- 사진들 표시 -->
|
||||
@@ -896,15 +1189,19 @@
|
||||
id="workHours-confirm-${issue.id}"
|
||||
onclick="confirmWorkHours(${issue.id})"
|
||||
class="px-3 py-1 rounded transition-colors text-sm font-medium ${
|
||||
issue.work_hours
|
||||
? 'bg-green-100 text-green-700 cursor-default'
|
||||
: 'bg-blue-500 text-white hover:bg-blue-600'
|
||||
isCompleted
|
||||
? 'bg-green-500 text-white cursor-default'
|
||||
: issue.work_hours
|
||||
? 'bg-blue-500 text-white hover:bg-blue-600'
|
||||
: 'bg-gray-300 text-gray-500 cursor-not-allowed'
|
||||
}"
|
||||
${issue.work_hours ? 'disabled' : ''}
|
||||
${isCompleted || !issue.work_hours ? 'disabled' : ''}
|
||||
>
|
||||
${issue.work_hours
|
||||
? '<i class="fas fa-check-circle mr-1"></i>완료'
|
||||
: '<i class="fas fa-clock mr-1"></i>확인'
|
||||
${isCompleted
|
||||
? '<i class="fas fa-check-circle mr-1"></i>검토완료'
|
||||
: issue.work_hours
|
||||
? '<i class="fas fa-clock mr-1"></i>확인'
|
||||
: '<i class="fas fa-clock mr-1"></i>확인'
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
@@ -950,8 +1247,8 @@
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
});
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
// 수정 상태 표시
|
||||
@@ -1013,17 +1310,25 @@
|
||||
}
|
||||
|
||||
try {
|
||||
// 작업 시간만 업데이트
|
||||
// 작업 시간 업데이트 및 검토 완료 상태로 변경
|
||||
await IssuesAPI.update(issueId, {
|
||||
work_hours: workHours
|
||||
work_hours: workHours,
|
||||
status: 'complete' // 검토 완료 상태로 변경
|
||||
});
|
||||
|
||||
// 성공 시 데이터 다시 로드
|
||||
await loadIssues();
|
||||
// 로컬 데이터도 업데이트
|
||||
const issue = issues.find(i => i.id === issueId);
|
||||
if (issue) {
|
||||
issue.work_hours = workHours;
|
||||
issue.status = 'complete';
|
||||
issue.reviewed_at = new Date().toISOString(); // 검토 완료 시간 기록
|
||||
}
|
||||
|
||||
// 성공 시 목록 다시 표시
|
||||
displayIssueList();
|
||||
|
||||
// 성공 메시지
|
||||
showToastMessage(`${workHours}시간 저장 완료!`, 'success');
|
||||
showToastMessage(`${workHours}시간 저장 및 검토 완료!`, 'success');
|
||||
|
||||
} catch (error) {
|
||||
alert(error.message || '저장에 실패했습니다.');
|
||||
@@ -1151,39 +1456,81 @@
|
||||
async function generateReport() {
|
||||
const container = document.getElementById('reportContent');
|
||||
|
||||
// 선택된 프로젝트 가져오기
|
||||
const selectedProjectId = document.getElementById('reportProjectFilter').value;
|
||||
|
||||
// 프로젝트별 필터링된 부적합 사항
|
||||
let filteredIssues = issues;
|
||||
if (selectedProjectId) {
|
||||
filteredIssues = issues.filter(issue => {
|
||||
const issueProjectId = issue.project_id || issue.projectId;
|
||||
return issueProjectId && (issueProjectId == selectedProjectId || issueProjectId.toString() === selectedProjectId.toString());
|
||||
});
|
||||
}
|
||||
|
||||
// 날짜 범위 계산
|
||||
const dates = issues.map(i => new Date(i.report_date));
|
||||
const dates = filteredIssues.map(i => new Date(i.report_date));
|
||||
const startDate = dates.length > 0 ? new Date(Math.min(...dates)) : new Date();
|
||||
const endDate = new Date();
|
||||
|
||||
// 일일 공수 데이터 가져오기
|
||||
// 프로젝트별 일일 공수 데이터 계산
|
||||
let dailyWorkTotal = 0;
|
||||
try {
|
||||
const dailyWorks = await DailyWorkAPI.getAll({
|
||||
start_date: startDate.toISOString().split('T')[0],
|
||||
end_date: endDate.toISOString().split('T')[0]
|
||||
const dailyWorkData = JSON.parse(localStorage.getItem('daily-work-data') || '[]');
|
||||
|
||||
console.log('일일공수 데이터:', dailyWorkData);
|
||||
console.log('선택된 프로젝트 ID:', selectedProjectId);
|
||||
|
||||
if (selectedProjectId) {
|
||||
// 선택된 프로젝트의 일일 공수만 합계
|
||||
dailyWorkData.forEach(dayWork => {
|
||||
console.log('일일공수 항목:', dayWork);
|
||||
if (dayWork.projects) {
|
||||
dayWork.projects.forEach(project => {
|
||||
console.log('프로젝트:', project, '매칭 확인:', project.projectId == selectedProjectId);
|
||||
if (project.projectId == selectedProjectId || project.projectId.toString() === selectedProjectId.toString()) {
|
||||
dailyWorkTotal += project.hours || 0;
|
||||
console.log('시간 추가:', project.hours, '누적:', dailyWorkTotal);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 전체 프로젝트의 일일 공수 합계
|
||||
dailyWorkData.forEach(dayWork => {
|
||||
console.log('전체 일일공수 항목:', dayWork);
|
||||
dailyWorkTotal += dayWork.totalHours || 0;
|
||||
});
|
||||
dailyWorkTotal = dailyWorks.reduce((sum, work) => sum + work.total_hours, 0);
|
||||
} catch (error) {
|
||||
console.error('일일 공수 데이터 로드 실패:', error);
|
||||
}
|
||||
|
||||
// 부적합 사항 해결 시간 계산
|
||||
const issueHours = issues.reduce((sum, issue) => sum + issue.work_hours, 0);
|
||||
console.log('최종 일일공수 합계:', dailyWorkTotal);
|
||||
|
||||
// 부적합 사항 해결 시간 계산 (필터링된 이슈만)
|
||||
const issueHours = filteredIssues.reduce((sum, issue) => sum + (issue.work_hours || 0), 0);
|
||||
const categoryCount = {};
|
||||
|
||||
issues.forEach(issue => {
|
||||
filteredIssues.forEach(issue => {
|
||||
categoryCount[issue.category] = (categoryCount[issue.category] || 0) + 1;
|
||||
});
|
||||
|
||||
// 부적합 시간 비율 계산
|
||||
const issuePercentage = dailyWorkTotal > 0 ? ((issueHours / dailyWorkTotal) * 100).toFixed(1) : 0;
|
||||
|
||||
// 선택된 프로젝트 정보
|
||||
let projectInfo = '전체 프로젝트';
|
||||
if (selectedProjectId) {
|
||||
const projects = JSON.parse(localStorage.getItem('work-report-projects') || '[]');
|
||||
const selectedProject = projects.find(p => p.id == selectedProjectId);
|
||||
if (selectedProject) {
|
||||
projectInfo = `${selectedProject.jobNo} - ${selectedProject.projectName}`;
|
||||
}
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="space-y-6">
|
||||
<!-- 요약 페이지 -->
|
||||
<div class="border-b pb-6">
|
||||
<h3 class="text-2xl font-bold text-center mb-6">작업 보고서</h3>
|
||||
<h3 class="text-2xl font-bold text-center mb-2">작업 보고서</h3>
|
||||
<p class="text-center text-gray-600 mb-6">${projectInfo}</p>
|
||||
|
||||
<div class="grid md:grid-cols-3 gap-4">
|
||||
<div class="bg-gray-50 rounded-lg p-4">
|
||||
@@ -1254,7 +1601,7 @@
|
||||
<!-- 상세 내역 -->
|
||||
<div>
|
||||
<h4 class="font-semibold text-gray-700 mb-4">부적합 사항 상세</h4>
|
||||
${issues.map(issue => {
|
||||
${filteredIssues.map(issue => {
|
||||
const categoryNames = {
|
||||
material_missing: '자재누락',
|
||||
design_error: '설계미스',
|
||||
|
||||
Reference in New Issue
Block a user