- 워크플로우 개요 페이지에 플로우차트 형태 추가 (3x2 그리드) - 각 워크플로우 단계별 독립 HTML 페이지 생성 (1-4단계) - 클릭 가능한 워크플로우 박스와 상세 페이지 연결 - DevonThink 스타일 적용 및 반응형 디자인 - 구매/물류팀 용어 통일 및 프레젠테이션 업데이트
833 lines
25 KiB
JavaScript
833 lines
25 KiB
JavaScript
// TK Project Demo - Main JavaScript
|
|
|
|
// 전역 변수
|
|
let currentPage = 'project-management';
|
|
let selectedProject = null;
|
|
let currentUser = {
|
|
name: '김그룹장',
|
|
role: '생산팀',
|
|
avatar: '김'
|
|
};
|
|
|
|
// 하드코딩된 데이터
|
|
const demoData = {
|
|
projects: [
|
|
{
|
|
jobNo: 'TK-2024-015',
|
|
name: 'ABC 공장 배관공사',
|
|
client: 'ABC 케미칼',
|
|
contractAmount: '150,000,000',
|
|
orderDate: '2024-01-15',
|
|
deliveryDate: '2024-03-30',
|
|
deliveryMethod: '현장납품',
|
|
productionType: '자체제작',
|
|
status: '승인완료'
|
|
}
|
|
],
|
|
|
|
processChart: {
|
|
design: { progress: 100, status: '완료', dueDate: '2024-02-15', responsible: '박설계' },
|
|
procurement: { progress: 85, status: '진행중', dueDate: '2024-02-28', responsible: '김구매' },
|
|
production: { progress: 60, status: '진행중', dueDate: '2024-03-20', responsible: '이생산' },
|
|
inspection: { progress: 0, status: '대기', dueDate: '2024-03-25', responsible: '최품질' },
|
|
delivery: { progress: 0, status: '대기', dueDate: '2024-03-30', responsible: '박PM' }
|
|
},
|
|
|
|
schedules: [
|
|
{
|
|
date: '09/20',
|
|
type: '외주출고',
|
|
title: '도장 작업 출고',
|
|
memo: 'A구역 파이프 20본, B구역 밸브 5개\n업체: 대한도장\n연락처: 010-1234-5678',
|
|
responsible: '김구매',
|
|
urgency: 'urgent'
|
|
},
|
|
{
|
|
date: '09/22',
|
|
type: '검사일정',
|
|
title: '압력시험',
|
|
memo: '시험압력: 15bar, 30분간 유지\n검사자: 최품질, 이생산',
|
|
responsible: '최품질',
|
|
urgency: 'normal'
|
|
}
|
|
],
|
|
|
|
deliveries: [
|
|
{
|
|
item: '파이프 4인치 x 50EA',
|
|
date: '입고완료 - 09/10',
|
|
status: 'completed'
|
|
},
|
|
{
|
|
item: '밸브 2인치 x 10EA',
|
|
date: '입고예정 - 09/16',
|
|
status: 'warning'
|
|
},
|
|
{
|
|
item: '엘보 4인치 x 20EA',
|
|
date: '지연 - 09/12 → 09/18',
|
|
status: 'delayed'
|
|
}
|
|
],
|
|
|
|
followUps: [
|
|
{
|
|
priority: '긴급',
|
|
title: '밸브 A 납기 지연 대응',
|
|
description: '주 공급업체 생산 지연으로 대체 업체 검토 필요',
|
|
responsible: '김구매',
|
|
registeredDate: '09/10',
|
|
expectedResolution: '09/17',
|
|
status: '진행중',
|
|
level: 'high'
|
|
},
|
|
{
|
|
priority: '높음',
|
|
title: '용접 검사 일정 조정',
|
|
description: '고객사 일정 변경으로 검사일 재조정 필요',
|
|
responsible: '최품질',
|
|
registeredDate: '09/12',
|
|
expectedResolution: '09/20',
|
|
status: '검토중',
|
|
level: 'medium'
|
|
}
|
|
],
|
|
|
|
purchaseOrders: [
|
|
{
|
|
poNumber: 'PO-2024-0156',
|
|
project: 'TK-2024-015',
|
|
item: '스테인리스 파이프 4인치 SCH40',
|
|
qty: 50,
|
|
unit: 'EA',
|
|
supplier: '대한파이프',
|
|
orderDate: '2024-09-01',
|
|
expectedDate: '2024-09-15',
|
|
buyer: '김구매',
|
|
status: 'pending'
|
|
},
|
|
{
|
|
poNumber: 'PO-2024-0157',
|
|
project: 'TK-2024-015',
|
|
item: '게이트밸브 2인치 150LB',
|
|
qty: 10,
|
|
unit: 'EA',
|
|
supplier: '코리아밸브',
|
|
orderDate: '2024-09-05',
|
|
expectedDate: '2024-09-16',
|
|
buyer: '김구매',
|
|
status: 'inspecting'
|
|
}
|
|
],
|
|
|
|
materials: [
|
|
{
|
|
item: '파이프 4인치 SCH40',
|
|
required: 50,
|
|
status: 'available',
|
|
location: 'A-3-상단',
|
|
availableQty: 45,
|
|
lastUpdate: '2024-09-14 09:00'
|
|
},
|
|
{
|
|
item: '엘보 4인치 150LB',
|
|
required: 20,
|
|
status: 'ordered',
|
|
currentStage: '구매 진행 중',
|
|
expectedDate: '2024-09-18',
|
|
availableQty: 0
|
|
},
|
|
{
|
|
item: '플랜지 4인치 150LB',
|
|
required: 15,
|
|
status: 'not-requested',
|
|
currentStage: '설계팀 구매 요청 대기',
|
|
availableQty: 0,
|
|
note: '설계 변경으로 사양 확정 대기'
|
|
},
|
|
{
|
|
item: '밸브 2인치 150LB',
|
|
required: 5,
|
|
status: 'ready',
|
|
location: 'B-2-중단',
|
|
availableQty: 5,
|
|
note: '인수 대기 중'
|
|
}
|
|
],
|
|
|
|
issues: [
|
|
{
|
|
time: '14:30',
|
|
type: '자재부족',
|
|
description: '엘보 4인치 10EA 부족으로 작업 중단',
|
|
urgency: '높음',
|
|
solution: '구매팀에 긴급 요청, 대체재 검토',
|
|
responsible: '김그룹장',
|
|
photos: ['issue_001.jpg']
|
|
},
|
|
{
|
|
time: '10:15',
|
|
type: '품질이슈',
|
|
description: '용접부 기공 발견, 재작업 필요',
|
|
urgency: '보통',
|
|
solution: '해당 부위 그라인딩 후 재용접',
|
|
responsible: '이용접사',
|
|
photos: ['quality_001.jpg']
|
|
}
|
|
]
|
|
};
|
|
|
|
// DOM이 로드되면 초기화
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initializeApp();
|
|
setupEventListeners();
|
|
showPage(currentPage);
|
|
});
|
|
|
|
// 앱 초기화
|
|
function initializeApp() {
|
|
console.log('TK Project Demo 초기화 중...');
|
|
|
|
// 사용자 정보 업데이트
|
|
updateUserInfo();
|
|
|
|
// 데이터 로드
|
|
loadDemoData();
|
|
|
|
console.log('TK Project Demo 초기화 완료');
|
|
}
|
|
|
|
// 이벤트 리스너 설정
|
|
function setupEventListeners() {
|
|
// 네비게이션 클릭 이벤트
|
|
document.querySelectorAll('.nav-item').forEach(item => {
|
|
item.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
const pageId = this.getAttribute('onclick').match(/'([^']+)'/)[1];
|
|
showPage(pageId);
|
|
});
|
|
});
|
|
|
|
// 진행률 슬라이더 이벤트
|
|
const progressSlider = document.querySelector('.progress-slider');
|
|
if (progressSlider) {
|
|
progressSlider.addEventListener('input', function() {
|
|
const value = this.value;
|
|
const display = document.querySelector('.progress-value');
|
|
if (display) {
|
|
display.textContent = value + '%';
|
|
}
|
|
});
|
|
}
|
|
|
|
// 버튼 클릭 이벤트
|
|
setupButtonEvents();
|
|
|
|
// 폼 이벤트
|
|
setupFormEvents();
|
|
}
|
|
|
|
// 버튼 이벤트 설정
|
|
function setupButtonEvents() {
|
|
// 프로젝트 승인 버튼
|
|
const approveBtn = document.querySelector('.btn-primary');
|
|
if (approveBtn && approveBtn.textContent.includes('프로젝트 승인')) {
|
|
approveBtn.addEventListener('click', function() {
|
|
showNotification('프로젝트가 성공적으로 승인되었습니다!', 'success');
|
|
});
|
|
}
|
|
|
|
// 검수 시작 버튼
|
|
document.querySelectorAll('.btn').forEach(btn => {
|
|
if (btn.textContent.includes('검수 시작')) {
|
|
btn.addEventListener('click', function() {
|
|
showNotification('검수 프로세스를 시작합니다.', 'info');
|
|
// 버튼 상태 변경
|
|
this.textContent = '🔄 검수 진행중';
|
|
this.classList.remove('btn-primary');
|
|
this.classList.add('btn-warning');
|
|
});
|
|
}
|
|
|
|
if (btn.textContent.includes('인수 처리')) {
|
|
btn.addEventListener('click', function() {
|
|
showNotification('자재 인수가 완료되었습니다.', 'success');
|
|
this.textContent = '✅ 인수완료';
|
|
this.classList.remove('btn-primary');
|
|
this.classList.add('btn-success');
|
|
this.disabled = true;
|
|
});
|
|
}
|
|
|
|
if (btn.textContent.includes('데일리 체크에 기록')) {
|
|
btn.addEventListener('click', function() {
|
|
showNotification('데일리 체크에 이슈가 등록되었습니다.', 'warning');
|
|
});
|
|
}
|
|
});
|
|
|
|
// 선반 클릭 이벤트
|
|
document.querySelectorAll('.shelf.available').forEach(shelf => {
|
|
shelf.addEventListener('click', function() {
|
|
// 기존 선택 해제
|
|
document.querySelectorAll('.shelf').forEach(s => s.classList.remove('selected'));
|
|
|
|
// 현재 선택
|
|
this.classList.add('selected');
|
|
|
|
// 선택된 위치 업데이트
|
|
const locationDisplay = document.querySelector('.selected-location .location');
|
|
if (locationDisplay) {
|
|
locationDisplay.textContent = this.textContent + '-중단';
|
|
}
|
|
|
|
showNotification(`${this.textContent} 선반이 선택되었습니다.`, 'info');
|
|
});
|
|
});
|
|
}
|
|
|
|
// 폼 이벤트 설정
|
|
function setupFormEvents() {
|
|
// BOM 조회 버튼
|
|
const bomSearchBtn = document.querySelector('.search-input .btn-primary');
|
|
if (bomSearchBtn && bomSearchBtn.textContent.includes('BOM 조회')) {
|
|
bomSearchBtn.addEventListener('click', function() {
|
|
const input = document.querySelector('.search-input .form-input');
|
|
const jobNo = input.value || 'TK-2024-015';
|
|
|
|
showNotification(`${jobNo} 프로젝트의 BOM 정보를 조회했습니다.`, 'success');
|
|
|
|
// 자재 리스트 업데이트 (이미 하드코딩되어 있음)
|
|
animateElements('.material-item');
|
|
});
|
|
}
|
|
}
|
|
|
|
// 페이지 표시
|
|
function showPage(pageId) {
|
|
// 모든 페이지 숨기기
|
|
document.querySelectorAll('.page').forEach(page => {
|
|
page.classList.remove('active');
|
|
});
|
|
|
|
// 모든 네비게이션 아이템 비활성화
|
|
document.querySelectorAll('.nav-item').forEach(item => {
|
|
item.classList.remove('active');
|
|
});
|
|
|
|
// 선택된 페이지 표시
|
|
const targetPage = document.getElementById(pageId);
|
|
if (targetPage) {
|
|
targetPage.classList.add('active');
|
|
targetPage.classList.add('fade-in');
|
|
|
|
// 네비게이션 아이템 활성화
|
|
const navItem = document.querySelector(`[onclick*="${pageId}"]`);
|
|
if (navItem) {
|
|
navItem.classList.add('active');
|
|
}
|
|
|
|
currentPage = pageId;
|
|
|
|
// 페이지별 초기화
|
|
initializePage(pageId);
|
|
}
|
|
}
|
|
|
|
// 페이지별 초기화
|
|
function initializePage(pageId) {
|
|
switch (pageId) {
|
|
case 'project-registration':
|
|
initializeProjectRegistration();
|
|
break;
|
|
case 'production-meeting':
|
|
initializeProductionMeeting();
|
|
break;
|
|
case 'incoming-inspection':
|
|
initializeIncomingInspection();
|
|
break;
|
|
case 'production-work':
|
|
initializeProductionWork();
|
|
break;
|
|
case 'project-management':
|
|
initializeProjectManagement();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 프로젝트 관리 페이지 초기화
|
|
function initializeProjectManagement() {
|
|
console.log('프로젝트 관리 페이지 초기화');
|
|
|
|
// 애니메이션 효과
|
|
animateElements('.simple-project-selector');
|
|
}
|
|
|
|
// 프로젝트 선택 (간단한 select 박스용)
|
|
function selectProject(jobNo) {
|
|
if (!jobNo) {
|
|
// 선택 해제
|
|
document.getElementById('project-actions').style.display = 'none';
|
|
selectedProject = null;
|
|
return;
|
|
}
|
|
|
|
// 프로젝트 선택됨
|
|
selectedProject = jobNo;
|
|
|
|
// 액션 버튼들 표시
|
|
document.getElementById('project-actions').style.display = 'flex';
|
|
|
|
// 세션 스토리지에 저장 (간단한 정보만)
|
|
sessionStorage.setItem('selectedProject', JSON.stringify({
|
|
jobNo: jobNo,
|
|
name: getProjectName(jobNo)
|
|
}));
|
|
|
|
showNotification(`${getProjectName(jobNo)} 프로젝트가 선택되었습니다.`, 'success');
|
|
}
|
|
|
|
// 프로젝트 이름 가져오기
|
|
function getProjectName(jobNo) {
|
|
const projectNames = {
|
|
'TK-2024-015': 'ABC 공장 배관공사',
|
|
'TK-2024-016': 'DEF 플랜트 배관 설치',
|
|
'TK-2024-017': 'GHI 정유 공장 개보수',
|
|
'TK-2024-012': 'JKL 화학 공장 신설'
|
|
};
|
|
return projectNames[jobNo] || '알 수 없는 프로젝트';
|
|
}
|
|
|
|
// 참고: 실제 구현시에는 다음과 같이 필터링할 예정
|
|
// function getActiveProjects() {
|
|
// // 납기가 지나지 않았고 완료 처리가 안된 프로젝트만 반환
|
|
// return projects.filter(project => {
|
|
// const today = new Date();
|
|
// const deadline = new Date(project.deadline);
|
|
// return deadline >= today && project.status !== 'completed';
|
|
// });
|
|
// }
|
|
|
|
// 프로젝트 등록 페이지 초기화
|
|
function initializeProjectRegistration() {
|
|
console.log('프로젝트 등록 페이지 초기화');
|
|
|
|
// 폼 이벤트 리스너 설정
|
|
setupProjectForm();
|
|
|
|
// Job No. 미리보기 업데이트
|
|
updateJobNoPreview();
|
|
|
|
// 요청 리스트 애니메이션
|
|
animateElements('.request-item');
|
|
}
|
|
|
|
// 프로젝트 폼 설정
|
|
function setupProjectForm() {
|
|
const form = document.querySelector('.project-form');
|
|
if (!form) return;
|
|
|
|
// 폼 제출 이벤트
|
|
form.addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
handleProjectSubmission();
|
|
});
|
|
|
|
// 입력 필드 변경시 Job No. 미리보기 업데이트
|
|
const inputs = form.querySelectorAll('input, select');
|
|
inputs.forEach(input => {
|
|
input.addEventListener('input', updateJobNoPreview);
|
|
});
|
|
}
|
|
|
|
// 프로젝트 제출 처리
|
|
function handleProjectSubmission() {
|
|
const form = document.querySelector('.project-form');
|
|
const formData = new FormData(form);
|
|
|
|
// 입력값 검증
|
|
const projectName = document.getElementById('project-name').value;
|
|
const endUser = document.getElementById('end-user').value;
|
|
const deliveryDate = document.getElementById('delivery-date').value;
|
|
const deliveryMethod = document.getElementById('delivery-method').value;
|
|
|
|
if (!projectName || !endUser || !deliveryDate || !deliveryMethod) {
|
|
showNotification('필수 항목을 모두 입력해주세요.', 'warning');
|
|
return;
|
|
}
|
|
|
|
// 제출 버튼 상태 변경
|
|
const submitBtn = document.getElementById('submit-btn');
|
|
const originalText = submitBtn.textContent;
|
|
submitBtn.textContent = '📤 요청 중...';
|
|
submitBtn.disabled = true;
|
|
|
|
// 가상 제출 처리 (2초 후 완료)
|
|
setTimeout(() => {
|
|
// 새로운 Job No. 생성
|
|
const newJobNo = generateJobNo();
|
|
|
|
// 성공 메시지
|
|
showNotification(`프로젝트 등록 요청이 완료되었습니다! (예상 Job No: ${newJobNo})`, 'success');
|
|
|
|
// 요청 리스트에 새 항목 추가
|
|
addNewRequestToList(projectName, endUser, deliveryDate, deliveryMethod);
|
|
|
|
// 폼 초기화
|
|
clearForm();
|
|
|
|
// 버튼 복원
|
|
submitBtn.textContent = originalText;
|
|
submitBtn.disabled = false;
|
|
|
|
}, 2000);
|
|
}
|
|
|
|
// Job No. 생성
|
|
function generateJobNo() {
|
|
const year = new Date().getFullYear();
|
|
const randomNum = Math.floor(Math.random() * 900) + 100; // 100-999
|
|
return `TK-${year}-${randomNum}`;
|
|
}
|
|
|
|
// Job No. 미리보기 업데이트
|
|
function updateJobNoPreview() {
|
|
const projectName = document.getElementById('project-name')?.value;
|
|
const previewElement = document.getElementById('preview-job-no');
|
|
|
|
if (!previewElement) return;
|
|
|
|
if (projectName && projectName.length > 0) {
|
|
const year = new Date().getFullYear();
|
|
const nextNum = String(Math.floor(Math.random() * 900) + 100);
|
|
previewElement.textContent = `TK-${year}-${nextNum}`;
|
|
previewElement.style.color = 'var(--dt-primary)';
|
|
} else {
|
|
previewElement.textContent = 'TK-2024-XXX';
|
|
previewElement.style.color = 'var(--dt-gray-500)';
|
|
}
|
|
}
|
|
|
|
// 새 요청을 리스트에 추가
|
|
function addNewRequestToList(projectName, endUser, deliveryDate, deliveryMethod) {
|
|
const requestList = document.querySelector('.request-list');
|
|
if (!requestList) return;
|
|
|
|
const epcCustomer = document.getElementById('epc-customer')?.value;
|
|
const customerText = epcCustomer ? `${endUser} / ${epcCustomer}` : endUser;
|
|
|
|
const newItem = document.createElement('div');
|
|
newItem.className = 'request-item status-pending';
|
|
newItem.innerHTML = `
|
|
<div class="request-info">
|
|
<div class="request-title">${projectName}</div>
|
|
<div class="request-details">
|
|
<span class="customer">${customerText}</span>
|
|
<span class="delivery">${deliveryMethod}</span>
|
|
<span class="date">납기: ${deliveryDate}</span>
|
|
</div>
|
|
</div>
|
|
<div class="request-status">
|
|
<span class="status-badge status-pending">검토중</span>
|
|
<span class="job-no">대기중</span>
|
|
</div>
|
|
`;
|
|
|
|
// 리스트 맨 위에 추가
|
|
requestList.insertBefore(newItem, requestList.firstChild);
|
|
|
|
// 애니메이션
|
|
newItem.style.opacity = '0';
|
|
newItem.style.transform = 'translateY(-20px)';
|
|
|
|
setTimeout(() => {
|
|
newItem.style.transition = 'all 0.3s ease';
|
|
newItem.style.opacity = '1';
|
|
newItem.style.transform = 'translateY(0)';
|
|
}, 100);
|
|
}
|
|
|
|
// 폼 초기화
|
|
function clearForm() {
|
|
const form = document.querySelector('.project-form');
|
|
if (form) {
|
|
form.reset();
|
|
updateJobNoPreview();
|
|
}
|
|
}
|
|
|
|
// 생산회의록 페이지 초기화
|
|
function initializeProductionMeeting() {
|
|
console.log('생산회의록 페이지 초기화');
|
|
|
|
// 공정표 애니메이션
|
|
animateElements('.process-item');
|
|
|
|
// 일정 및 배송 아이템 애니메이션
|
|
setTimeout(() => {
|
|
animateElements('.schedule-item');
|
|
animateElements('.delivery-item');
|
|
}, 300);
|
|
|
|
// Follow-up 아이템 애니메이션
|
|
setTimeout(() => {
|
|
animateElements('.followup-item');
|
|
}, 600);
|
|
}
|
|
|
|
// 입고 검수 페이지 초기화
|
|
function initializeIncomingInspection() {
|
|
console.log('입고 검수 페이지 초기화');
|
|
|
|
// 구매 아이템 애니메이션
|
|
animateElements('.purchase-item');
|
|
|
|
// 검수 단계 애니메이션
|
|
setTimeout(() => {
|
|
animateElements('.step');
|
|
}, 300);
|
|
|
|
// 창고 구역 애니메이션
|
|
setTimeout(() => {
|
|
animateElements('.zone-item');
|
|
}, 600);
|
|
}
|
|
|
|
// 생산팀 작업 페이지 초기화
|
|
function initializeProductionWork() {
|
|
console.log('생산팀 작업 페이지 초기화');
|
|
|
|
// 이슈 아이템 애니메이션
|
|
animateElements('.issue-item');
|
|
|
|
// 자재 아이템 애니메이션
|
|
setTimeout(() => {
|
|
animateElements('.material-item');
|
|
}, 300);
|
|
}
|
|
|
|
// 사용자 정보 업데이트
|
|
function updateUserInfo() {
|
|
const userAvatar = document.querySelector('.user-avatar');
|
|
const userName = document.querySelector('.user-name');
|
|
const userRole = document.querySelector('.user-role');
|
|
|
|
if (userAvatar) userAvatar.textContent = currentUser.avatar;
|
|
if (userName) userName.textContent = currentUser.name;
|
|
if (userRole) userRole.textContent = currentUser.role;
|
|
}
|
|
|
|
// 데모 데이터 로드
|
|
function loadDemoData() {
|
|
console.log('데모 데이터 로드 중...');
|
|
|
|
// 여기서 필요한 경우 동적으로 데이터를 DOM에 삽입할 수 있습니다.
|
|
// 현재는 HTML에 하드코딩되어 있으므로 추가 작업 불필요
|
|
|
|
console.log('데모 데이터 로드 완료');
|
|
}
|
|
|
|
// 요소 애니메이션
|
|
function animateElements(selector) {
|
|
const elements = document.querySelectorAll(selector);
|
|
elements.forEach((element, index) => {
|
|
setTimeout(() => {
|
|
element.classList.add('slide-in');
|
|
}, index * 100);
|
|
});
|
|
}
|
|
|
|
// 알림 표시
|
|
function showNotification(message, type = 'info') {
|
|
// 기존 알림 제거
|
|
const existingNotification = document.querySelector('.notification');
|
|
if (existingNotification) {
|
|
existingNotification.remove();
|
|
}
|
|
|
|
// 새 알림 생성
|
|
const notification = document.createElement('div');
|
|
notification.className = `notification alert alert-${type}`;
|
|
notification.textContent = message;
|
|
|
|
// 스타일 설정
|
|
notification.style.position = 'fixed';
|
|
notification.style.top = '20px';
|
|
notification.style.right = '20px';
|
|
notification.style.zIndex = '9999';
|
|
notification.style.minWidth = '300px';
|
|
notification.style.maxWidth = '500px';
|
|
notification.style.boxShadow = 'var(--dt-shadow-lg)';
|
|
notification.style.transform = 'translateX(100%)';
|
|
notification.style.transition = 'transform 0.3s ease';
|
|
|
|
// DOM에 추가
|
|
document.body.appendChild(notification);
|
|
|
|
// 애니메이션
|
|
setTimeout(() => {
|
|
notification.style.transform = 'translateX(0)';
|
|
}, 100);
|
|
|
|
// 자동 제거
|
|
setTimeout(() => {
|
|
notification.style.transform = 'translateX(100%)';
|
|
setTimeout(() => {
|
|
if (notification.parentNode) {
|
|
notification.remove();
|
|
}
|
|
}, 300);
|
|
}, 3000);
|
|
}
|
|
|
|
// 모달 관련 함수들
|
|
function showModal(title, content) {
|
|
let modal = document.querySelector('.modal-overlay');
|
|
|
|
if (!modal) {
|
|
modal = createModal();
|
|
document.body.appendChild(modal);
|
|
}
|
|
|
|
const modalTitle = modal.querySelector('.modal-title');
|
|
const modalBody = modal.querySelector('.modal-body');
|
|
|
|
modalTitle.textContent = title;
|
|
modalBody.innerHTML = content;
|
|
|
|
modal.classList.add('active');
|
|
}
|
|
|
|
function hideModal() {
|
|
const modal = document.querySelector('.modal-overlay');
|
|
if (modal) {
|
|
modal.classList.remove('active');
|
|
}
|
|
}
|
|
|
|
function createModal() {
|
|
const modal = document.createElement('div');
|
|
modal.className = 'modal-overlay';
|
|
modal.innerHTML = `
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h3 class="modal-title"></h3>
|
|
</div>
|
|
<div class="modal-body"></div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" onclick="hideModal()">닫기</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// 오버레이 클릭시 모달 닫기
|
|
modal.addEventListener('click', function(e) {
|
|
if (e.target === modal) {
|
|
hideModal();
|
|
}
|
|
});
|
|
|
|
return modal;
|
|
}
|
|
|
|
// 유틸리티 함수들
|
|
function formatDate(dateString) {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString('ko-KR');
|
|
}
|
|
|
|
function formatNumber(number) {
|
|
return number.toLocaleString('ko-KR');
|
|
}
|
|
|
|
function formatCurrency(amount) {
|
|
return new Intl.NumberFormat('ko-KR', {
|
|
style: 'currency',
|
|
currency: 'KRW'
|
|
}).format(amount);
|
|
}
|
|
|
|
// 검색 기능
|
|
function searchItems(query, items, searchFields) {
|
|
if (!query) return items;
|
|
|
|
const lowercaseQuery = query.toLowerCase();
|
|
return items.filter(item => {
|
|
return searchFields.some(field => {
|
|
const value = item[field];
|
|
return value && value.toString().toLowerCase().includes(lowercaseQuery);
|
|
});
|
|
});
|
|
}
|
|
|
|
// 정렬 기능
|
|
function sortItems(items, field, direction = 'asc') {
|
|
return items.sort((a, b) => {
|
|
const aValue = a[field];
|
|
const bValue = b[field];
|
|
|
|
if (direction === 'asc') {
|
|
return aValue > bValue ? 1 : -1;
|
|
} else {
|
|
return aValue < bValue ? 1 : -1;
|
|
}
|
|
});
|
|
}
|
|
|
|
// 필터 기능
|
|
function filterItems(items, filters) {
|
|
return items.filter(item => {
|
|
return Object.entries(filters).every(([key, value]) => {
|
|
if (!value) return true;
|
|
return item[key] === value;
|
|
});
|
|
});
|
|
}
|
|
|
|
// 로컬 스토리지 관련
|
|
function saveToLocalStorage(key, data) {
|
|
try {
|
|
localStorage.setItem(key, JSON.stringify(data));
|
|
} catch (error) {
|
|
console.error('로컬 스토리지 저장 실패:', error);
|
|
}
|
|
}
|
|
|
|
function loadFromLocalStorage(key, defaultValue = null) {
|
|
try {
|
|
const data = localStorage.getItem(key);
|
|
return data ? JSON.parse(data) : defaultValue;
|
|
} catch (error) {
|
|
console.error('로컬 스토리지 로드 실패:', error);
|
|
return defaultValue;
|
|
}
|
|
}
|
|
|
|
// 키보드 단축키
|
|
document.addEventListener('keydown', function(e) {
|
|
// Ctrl + 숫자키로 페이지 전환
|
|
if (e.ctrlKey && e.key >= '1' && e.key <= '4') {
|
|
e.preventDefault();
|
|
const pages = ['project-registration', 'production-meeting', 'incoming-inspection', 'production-work'];
|
|
const pageIndex = parseInt(e.key) - 1;
|
|
if (pages[pageIndex]) {
|
|
showPage(pages[pageIndex]);
|
|
}
|
|
}
|
|
|
|
// ESC로 모달 닫기
|
|
if (e.key === 'Escape') {
|
|
hideModal();
|
|
}
|
|
});
|
|
|
|
// 전역 함수로 노출 (HTML onclick에서 사용)
|
|
window.showPage = showPage;
|
|
window.showModal = showModal;
|
|
window.hideModal = hideModal;
|
|
window.showNotification = showNotification;
|
|
window.clearForm = clearForm;
|
|
window.selectProject = selectProject;
|
|
|
|
console.log('TK Project Demo JavaScript 로드 완료');
|