/**
* issue-view.js — 부적합 조회 페이지 스크립트
*/
let currentUser = null;
let issues = [];
let projects = []; // 프로젝트 데이터 캐시
let currentRange = 'week'; // 기본값: 이번 주
// 애니메이션 함수들
function animateHeaderAppearance() {
console.log('헤더 애니메이션 시작');
// 헤더 요소 찾기 (공통 헤더가 생성한 요소)
const headerElement = document.querySelector('header') || document.querySelector('[class*="header"]') || document.querySelector('nav');
if (headerElement) {
headerElement.classList.add('header-fade-in');
setTimeout(() => {
headerElement.classList.add('visible');
// 헤더 애니메이션 완료 후 본문 애니메이션
setTimeout(() => {
animateContentAppearance();
}, 200);
}, 50);
} else {
// 헤더를 찾지 못했으면 바로 본문 애니메이션
animateContentAppearance();
}
}
// 본문 컨텐츠 애니메이션
function animateContentAppearance() {
// 모든 content-fade-in 요소들을 순차적으로 애니메이션
const contentElements = document.querySelectorAll('.content-fade-in');
contentElements.forEach((element, index) => {
setTimeout(() => {
element.classList.add('visible');
}, index * 100); // 100ms씩 지연
});
}
// API 로드 후 초기화 함수
async function initializeIssueView() {
const token = TokenManager.getToken();
if (!token) {
window.location.href = '/index.html';
return;
}
try {
const user = await AuthAPI.getCurrentUser();
currentUser = user;
localStorage.setItem('sso_user', JSON.stringify(user));
// 공통 헤더 초기화
await window.commonHeader.init(user, 'issues_view');
// 헤더 초기화 후 부드러운 애니메이션 시작
setTimeout(() => {
animateHeaderAppearance();
}, 100);
// 사용자 역할에 따른 페이지 제목 설정
updatePageTitle(user);
// 페이지 접근 권한 체크 (부적합 조회 페이지)
setTimeout(() => {
if (!canAccessPage('issues_view')) {
alert('부적합 조회 페이지에 접근할 권한이 없습니다.');
window.location.href = '/index.html';
return;
}
}, 500);
} catch (error) {
console.error('인증 실패:', error);
TokenManager.removeToken();
TokenManager.removeUser();
window.location.href = '/index.html';
return;
}
// 프로젝트 로드
await loadProjects();
// 기본 날짜 설정 (이번 주)
setDefaultDateRange();
// 기본값: 이번 주 데이터 로드
await loadIssues();
setDateRange('week');
}
// showImageModal은 photo-modal.js에서 제공됨
// 기본 날짜 범위 설정
function setDefaultDateRange() {
const today = new Date();
const weekStart = new Date(today);
weekStart.setDate(today.getDate() - today.getDay()); // 이번 주 일요일
// 날짜 입력 필드에 기본값 설정
document.getElementById('startDateInput').value = formatDateForInput(weekStart);
document.getElementById('endDateInput').value = formatDateForInput(today);
}
// 날짜를 input[type="date"] 형식으로 포맷
function formatDateForInput(date) {
return date.toISOString().split('T')[0];
}
// 날짜 필터 적용
function applyDateFilter() {
const startDate = document.getElementById('startDateInput').value;
const endDate = document.getElementById('endDateInput').value;
if (!startDate || !endDate) {
alert('시작날짜와 끝날짜를 모두 선택해주세요.');
return;
}
if (new Date(startDate) > new Date(endDate)) {
alert('시작날짜는 끝날짜보다 이전이어야 합니다.');
return;
}
// 필터 적용
filterIssues();
}
// 사용자 역할에 따른 페이지 제목 업데이트
function updatePageTitle(user) {
const titleElement = document.getElementById('pageTitle');
const descriptionElement = document.getElementById('pageDescription');
if (user.role === 'admin') {
titleElement.innerHTML = `
전체 부적합 조회
`;
descriptionElement.textContent = '모든 사용자가 등록한 부적합 사항을 관리할 수 있습니다';
} else {
titleElement.innerHTML = `
내 부적합 조회
`;
descriptionElement.textContent = '내가 등록한 부적합 사항을 확인할 수 있습니다';
}
}
// 프로젝트 로드 (API 기반)
async function loadProjects() {
try {
// 모든 프로젝트 로드 (활성/비활성 모두 - 기존 데이터 조회를 위해)
projects = await ProjectsAPI.getAll(false);
const projectFilter = document.getElementById('projectFilter');
// 기존 옵션 제거 (전체 프로젝트 옵션 제외)
projectFilter.innerHTML = '';
// 모든 프로젝트 추가
projects.forEach(project => {
const option = document.createElement('option');
option.value = project.id;
option.textContent = `${project.job_no} / ${project.project_name}${!project.is_active ? ' (비활성)' : ''}`;
projectFilter.appendChild(option);
});
} catch (error) {
console.error('프로젝트 로드 실패:', error);
}
}
// 이슈 필터링
// 검토 상태 확인 함수
function isReviewCompleted(issue) {
return issue.status === 'complete' && issue.work_hours && issue.work_hours > 0;
}
// 날짜 필터링 함수
function filterByDate(issues, dateFilter) {
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 filterByDateRange(issues, range) {
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
switch (range) {
case 'today':
return issues.filter(issue => {
const issueDate = new Date(issue.created_at);
const issueDay = new Date(issueDate.getFullYear(), issueDate.getMonth(), issueDate.getDate());
return issueDay.getTime() === today.getTime();
});
case 'week':
const weekStart = new Date(today);
weekStart.setDate(today.getDate() - today.getDay());
const weekEnd = new Date(weekStart);
weekEnd.setDate(weekStart.getDate() + 6);
weekEnd.setHours(23, 59, 59, 999);
return issues.filter(issue => {
const issueDate = new Date(issue.created_at);
return issueDate >= weekStart && issueDate <= weekEnd;
});
case 'month':
const monthStart = new Date(today.getFullYear(), today.getMonth(), 1);
const monthEnd = new Date(today.getFullYear(), today.getMonth() + 1, 0);
monthEnd.setHours(23, 59, 59, 999);
return issues.filter(issue => {
const issueDate = new Date(issue.created_at);
return issueDate >= monthStart && issueDate <= monthEnd;
});
default:
return issues;
}
}
function filterIssues() {
// 필터 값 가져오기
const selectedProjectId = document.getElementById('projectFilter').value;
const reviewStatusFilter = document.getElementById('reviewStatusFilter').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 => {
// 새로운 워크플로우 시스템 사용
if (issue.review_status) {
return issue.review_status === reviewStatusFilter;
}
// 기존 데이터 호환성을 위한 폴백
else {
const isCompleted = isReviewCompleted(issue);
if (reviewStatusFilter === 'pending_review') return !isCompleted;
if (reviewStatusFilter === 'completed') return isCompleted;
return false;
}
});
}
// 날짜 범위 필터 적용 (입력 필드에서 선택된 범위)
const startDateInput = document.getElementById('startDateInput').value;
const endDateInput = document.getElementById('endDateInput').value;
if (startDateInput && endDateInput) {
filteredIssues = filteredIssues.filter(issue => {
const issueDate = new Date(issue.report_date);
const startOfDay = new Date(startDateInput);
startOfDay.setHours(0, 0, 0, 0);
const endOfDay = new Date(endDateInput);
endOfDay.setHours(23, 59, 59, 999);
return issueDate >= startOfDay && issueDate <= endOfDay;
});
}
// 전역 변수에 필터링된 결과 저장
window.filteredIssues = filteredIssues;
displayResults();
}
// 프로젝트 정보 표시용 함수
function getProjectInfo(projectId) {
if (!projectId) {
return '프로젝트 미지정';
}
// 전역 projects 배열에서 찾기
const project = projects.find(p => p.id == projectId);
if (project) {
return `${project.job_no} / ${project.project_name}`;
}
return `프로젝트 ID: ${projectId} (정보 없음)`;
}
// 날짜 범위 설정 및 자동 조회
function setDateRange(range) {
currentRange = range;
const today = new Date();
let startDate, endDate;
switch (range) {
case 'today':
startDate = new Date(today);
endDate = new Date(today);
break;
case 'week':
startDate = new Date(today);
startDate.setDate(today.getDate() - today.getDay()); // 이번 주 일요일
endDate = new Date(today);
break;
case 'month':
startDate = new Date(today.getFullYear(), today.getMonth(), 1); // 이번 달 1일
endDate = new Date(today);
break;
case 'all':
startDate = new Date(2020, 0, 1); // 충분히 과거 날짜
endDate = new Date(today);
break;
default:
return;
}
// 날짜 입력 필드 업데이트
document.getElementById('startDateInput').value = formatDateForInput(startDate);
document.getElementById('endDateInput').value = formatDateForInput(endDate);
// 필터 적용
filterIssues();
}
// 부적합 사항 로드 (자신이 올린 내용만)
async function loadIssues() {
const container = document.getElementById('issueResults');
container.innerHTML = `
`;
try {
// 모든 이슈 가져오기
const allIssues = await IssuesAPI.getAll();
// 자신이 올린 이슈만 필터링
issues = allIssues
.filter(issue => issue.reporter_id === currentUser.id)
.sort((a, b) => new Date(b.report_date) - new Date(a.report_date));
// 결과 표시
filterIssues();
} catch (error) {
console.error('부적합 사항 로드 실패:', error);
container.innerHTML = `
`;
}
}
// 결과 표시 (시간순 나열)
function displayResults() {
const container = document.getElementById('issueResults');
// 필터링된 결과 사용 (filterIssues에서 설정됨)
const filteredIssues = window.filteredIssues || issues;
if (filteredIssues.length === 0) {
const emptyMessage = currentUser.role === 'admin'
? '조건에 맞는 부적합 사항이 없습니다.'
: '아직 등록한 부적합 사항이 없습니다.
부적합 등록 페이지에서 새로운 부적합을 등록해보세요.';
container.innerHTML = `
${emptyMessage}
${currentUser.role !== 'admin' ? `
` : ''}
`;
return;
}
// 워크플로우 상태별로 분류 및 정렬
const groupedIssues = {
pending_review: filteredIssues.filter(issue =>
issue.review_status === 'pending_review' || (!issue.review_status && !isReviewCompleted(issue))
),
in_progress: filteredIssues.filter(issue => issue.review_status === 'in_progress'),
completed: filteredIssues.filter(issue =>
issue.review_status === 'completed' || (!issue.review_status && isReviewCompleted(issue))
),
disposed: filteredIssues.filter(issue => issue.review_status === 'disposed')
};
container.innerHTML = '';
// 각 상태별로 표시
const statusConfig = [
{ key: 'pending_review', title: '수신함 (검토 대기)', icon: 'fas fa-inbox', color: 'text-orange-700' },
{ key: 'in_progress', title: '관리함 (진행 중)', icon: 'fas fa-cog', color: 'text-blue-700' },
{ key: 'completed', title: '관리함 (완료됨)', icon: 'fas fa-check-circle', color: 'text-green-700' },
{ key: 'disposed', title: '폐기함 (폐기됨)', icon: 'fas fa-trash', color: 'text-gray-700' }
];
statusConfig.forEach((config, index) => {
const issues = groupedIssues[config.key];
if (issues.length > 0) {
const header = document.createElement('div');
header.className = index > 0 ? 'mb-4 mt-8' : 'mb-4';
header.innerHTML = `
${config.title} (${issues.length}건)
`;
container.appendChild(header);
issues.forEach(issue => {
container.appendChild(createIssueCard(issue, config.key === 'completed'));
});
}
});
}
// 워크플로우 상태 표시 함수
function getWorkflowStatusBadge(issue) {
const status = issue.review_status || (isReviewCompleted(issue) ? 'completed' : 'pending_review');
const statusConfig = {
'pending_review': { text: '검토 대기', class: 'bg-orange-100 text-orange-700', icon: 'fas fa-inbox' },
'in_progress': { text: '진행 중', class: 'bg-blue-100 text-blue-700', icon: 'fas fa-cog' },
'completed': { text: '완료됨', class: 'bg-green-100 text-green-700', icon: 'fas fa-check-circle' },
'disposed': { text: '폐기됨', class: 'bg-gray-100 text-gray-700', icon: 'fas fa-trash' }
};
const config = statusConfig[status] || statusConfig['pending_review'];
return `
${config.text}
`;
}
// 부적합 사항 카드 생성 함수 (조회용)
function createIssueCard(issue, isCompleted) {
const categoryNames = {
material_missing: '자재누락',
design_error: '설계미스',
incoming_defect: '입고자재 불량',
inspection_miss: '검사미스'
};
const categoryColors = {
material_missing: 'bg-yellow-100 text-yellow-700 border-yellow-300',
design_error: 'bg-blue-100 text-blue-700 border-blue-300',
incoming_defect: 'bg-red-100 text-red-700 border-red-300',
inspection_miss: 'bg-purple-100 text-purple-700 border-purple-300'
};
const div = document.createElement('div');
// 검토 완료 상태에 따른 스타일링
const baseClasses = 'rounded-lg transition-colors border-l-4 mb-4';
const statusClasses = isCompleted
? 'bg-gray-100 opacity-75'
: 'bg-gray-50 hover:bg-gray-100';
const borderColor = categoryColors[issue.category]?.split(' ')[2] || 'border-gray-300';
div.className = `${baseClasses} ${statusClasses} ${borderColor}`;
const dateStr = DateUtils.formatKST(issue.report_date, true);
const relativeTime = DateUtils.getRelativeTime(issue.report_date);
const projectInfo = getProjectInfo(issue.project_id || issue.projectId);
// 수정/삭제 권한 확인 (본인이 등록한 부적합만)
const canEdit = issue.reporter_id === currentUser.id;
const canDelete = issue.reporter_id === currentUser.id || currentUser.role === 'admin';
div.innerHTML = `
${getWorkflowStatusBadge(issue)}
${projectInfo}
${(() => {
const photos = [
issue.photo_path,
issue.photo_path2,
issue.photo_path3,
issue.photo_path4,
issue.photo_path5
].filter(p => p);
if (photos.length === 0) {
return `
`;
}
return photos.map(path => `

`).join('');
})()}
${categoryNames[issue.category] || issue.category}
${issue.work_hours ?
`
${issue.work_hours}시간
` :
'시간 미입력'
}
${issue.description}
${issue.location_info ? `
${issue.location_info}
` : ''}
${issue.reporter?.full_name || issue.reporter?.username || '알 수 없음'}
${dateStr}
${relativeTime}
${(canEdit || canDelete) ? `
${canEdit ? `
` : ''}
${canDelete ? `
` : ''}
` : ''}
`;
return div;
}
// 관리 버튼 클릭 처리
function handleAdminClick() {
if (currentUser.role === 'admin') {
// 관리자: 사용자 관리 페이지로 이동
window.location.href = 'admin.html';
}
}
// 비밀번호 변경 모달 표시
function showPasswordChangeModal() {
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
modal.onclick = (e) => {
if (e.target === modal) modal.remove();
};
modal.innerHTML = `
`;
document.body.appendChild(modal);
// 폼 제출 이벤트 처리
document.getElementById('passwordChangeForm').addEventListener('submit', handlePasswordChange);
}
// 비밀번호 변경 처리
async function handlePasswordChange(e) {
e.preventDefault();
const currentPassword = document.getElementById('currentPassword').value;
const newPassword = document.getElementById('newPassword').value;
const confirmPassword = document.getElementById('confirmPassword').value;
// 새 비밀번호 확인
if (newPassword !== confirmPassword) {
alert('새 비밀번호가 일치하지 않습니다.');
return;
}
// 현재 비밀번호 확인 (localStorage 기반)
let users = JSON.parse(localStorage.getItem('work-report-users') || '[]');
// 임시 비밀번호 생성 (클라이언트에 하드코딩하지 않음)
function generateTempPassword() {
const arr = new Uint8Array(12);
crypto.getRandomValues(arr);
return Array.from(arr, b => b.toString(36).padStart(2, '0')).join('').slice(0, 16);
}
// 기본 사용자가 없으면 생성
if (users.length === 0) {
users = [
{
username: 'hyungi',
full_name: '관리자',
password: generateTempPassword(),
role: 'admin'
}
];
localStorage.setItem('work-report-users', JSON.stringify(users));
}
let user = users.find(u => u.username === currentUser.username);
// 사용자가 없으면 기본값으로 생성
if (!user) {
const username = currentUser.username;
user = {
username: username,
full_name: username === 'hyungi' ? '관리자' : username,
password: generateTempPassword(),
role: username === 'hyungi' ? 'admin' : 'user'
};
users.push(user);
localStorage.setItem('work-report-users', JSON.stringify(users));
}
if (user.password !== currentPassword) {
alert('현재 비밀번호가 올바르지 않습니다.');
return;
}
try {
// 비밀번호 변경
user.password = newPassword;
localStorage.setItem('work-report-users', JSON.stringify(users));
// 현재 사용자 정보도 업데이트
currentUser.password = newPassword;
localStorage.setItem('sso_user', JSON.stringify(currentUser));
alert('비밀번호가 성공적으로 변경되었습니다.');
document.querySelector('.fixed').remove(); // 모달 닫기
} catch (error) {
alert('비밀번호 변경에 실패했습니다: ' + error.message);
}
}
// 로그아웃 함수
function logout() {
TokenManager.removeToken();
TokenManager.removeUser();
window.location.href = 'index.html';
}
// 수정 모달 표시
function showEditModal(issue) {
const categoryNames = {
material_missing: '자재누락',
design_error: '설계미스',
incoming_defect: '입고자재 불량',
inspection_miss: '검사미스'
};
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4';
modal.onclick = (e) => {
if (e.target === modal) modal.remove();
};
modal.innerHTML = `
`;
document.body.appendChild(modal);
// 폼 제출 이벤트 처리
document.getElementById('editIssueForm').addEventListener('submit', async (e) => {
e.preventDefault();
const updateData = {
category: document.getElementById('editCategory').value,
description: document.getElementById('editDescription').value,
project_id: parseInt(document.getElementById('editProject').value)
};
try {
await IssuesAPI.update(issue.id, updateData);
alert('수정되었습니다.');
modal.remove();
// 목록 새로고침
await loadIssues();
} catch (error) {
console.error('수정 실패:', error);
alert('수정에 실패했습니다: ' + error.message);
}
});
}
// 삭제 확인 다이얼로그
function confirmDelete(issueId) {
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
modal.onclick = (e) => {
if (e.target === modal) modal.remove();
};
modal.innerHTML = `
부적합 삭제
이 부적합 사항을 삭제하시겠습니까?
삭제된 데이터는 로그로 보관되지만 복구할 수 없습니다.
`;
document.body.appendChild(modal);
}
// 삭제 처리
async function handleDelete(issueId) {
try {
await IssuesAPI.delete(issueId);
alert('삭제되었습니다.');
// 모달 닫기
const modal = document.querySelector('.fixed');
if (modal) modal.remove();
// 목록 새로고침
await loadIssues();
} catch (error) {
console.error('삭제 실패:', error);
alert('삭제에 실패했습니다: ' + error.message);
}
}
// API 스크립트 동적 로딩
const script = document.createElement('script');
script.src = '/static/js/api.js?v=20260213';
script.onload = function() {
console.log('API 스크립트 로드 완료 (issue-view.html)');
// API 로드 후 초기화 시작
initializeIssueView();
};
script.onerror = function() {
console.error('API 스크립트 로드 실패');
};
document.head.appendChild(script);