Files
M-Project/frontend/migrate-data.html
hyungi b024a178d0 feat: 목록 관리 및 보고서 페이지 개선
- 목록 관리 페이지에 고급 필터링 시스템 추가
  - 프로젝트별, 검토상태별, 날짜별 필터링
  - 검토 완료/필요 항목 시각적 구분 및 정렬
  - 해결 시간 입력 + 확인 버튼으로 검토 완료 처리

- 부적합 조회 페이지에 동일한 필터링 기능 적용
  - 검토 상태에 따른 카드 스타일링 (음영 처리)
  - JavaScript 템플릿 리터럴 오류 수정

- 보고서 페이지 프로젝트별 분석 기능 추가
  - 프로젝트 선택 드롭다운 추가
  - 총 작업 공수를 프로젝트별 일일공수 데이터로 계산
  - 부적합 처리 시간, 카테고리 분석, 상세 목록 모두 프로젝트별 필터링
  - localStorage 키 이름 통일 (daily-work-data)
2025-10-24 10:13:32 +09:00

308 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>데이터 마이그레이션 - M Project</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="/static/js/api.js?v=20250917"></script>
</head>
<body class="bg-gray-50">
<div class="container mx-auto px-4 py-8 max-w-2xl">
<div class="bg-white rounded-xl shadow-sm p-6">
<h1 class="text-2xl font-bold text-gray-800 mb-6">
<i class="fas fa-database text-blue-500 mr-2"></i>데이터 마이그레이션
</h1>
<div class="space-y-4">
<div class="p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
<h3 class="font-semibold text-yellow-800 mb-2">⚠️ 주의사항</h3>
<p class="text-yellow-700 text-sm">
이 작업은 기존 데이터를 "M Project"로 마이그레이션합니다.<br>
관리자(hyungi) 계정으로 로그인한 상태에서만 실행하세요.
</p>
</div>
<div id="status" class="space-y-2">
<!-- 상태 메시지가 여기에 표시됩니다 -->
</div>
<button
id="migrateBtn"
onclick="startMigration()"
class="w-full bg-blue-500 text-white py-3 px-4 rounded-lg hover:bg-blue-600 transition-colors font-medium"
>
<i class="fas fa-play mr-2"></i>마이그레이션 시작
</button>
<a href="index.html" class="block text-center text-gray-600 hover:text-gray-800 mt-4">
<i class="fas fa-arrow-left mr-1"></i>메인으로 돌아가기
</a>
</div>
</div>
</div>
<script>
let currentUser = null;
// 페이지 로드 시 사용자 확인
window.addEventListener('DOMContentLoaded', () => {
const userData = localStorage.getItem('currentUser');
if (!userData) {
alert('로그인이 필요합니다.');
window.location.href = 'index.html';
return;
}
try {
currentUser = JSON.parse(userData);
} catch (e) {
currentUser = { username: userData };
}
const username = currentUser.username || currentUser;
if (username !== 'hyungi' && currentUser.role !== 'admin') {
alert('관리자만 접근 가능합니다.');
window.location.href = 'index.html';
return;
}
addStatus('✅ 관리자 권한 확인됨', 'text-green-600');
});
function addStatus(message, className = 'text-gray-600') {
const statusDiv = document.getElementById('status');
const p = document.createElement('p');
p.className = `text-sm ${className}`;
p.innerHTML = `<i class="fas fa-info-circle mr-2"></i>${message}`;
statusDiv.appendChild(p);
}
async function startMigration() {
const btn = document.getElementById('migrateBtn');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>마이그레이션 중...';
try {
// 1. M Project 생성
await createMProject();
// 2. 기존 부적합 사항 마이그레이션
await migrateIssues();
// 3. 기존 368시간 데이터 생성
await createSampleHours();
addStatus('🎉 모든 마이그레이션이 완료되었습니다!', 'text-green-600 font-bold');
btn.innerHTML = '<i class="fas fa-check mr-2"></i>완료';
btn.className = 'w-full bg-green-500 text-white py-3 px-4 rounded-lg font-medium';
} catch (error) {
addStatus(`❌ 오류 발생: ${error.message}`, 'text-red-600');
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-redo mr-2"></i>다시 시도';
}
}
async function createMProject() {
addStatus('기존 M Project 확인 중...');
// 기존 프로젝트 확인
let projects = [];
const saved = localStorage.getItem('work-report-projects');
if (saved) {
projects = JSON.parse(saved);
}
// 기존 TKR-25009R M Project 찾기
let existingMProject = projects.find(p => p.jobNo === 'TKR-25009R');
if (existingMProject) {
addStatus('✅ 기존 TKR-25009R M Project 발견', 'text-green-600');
return existingMProject.id;
}
// M-2024-001로 생성된 잘못된 프로젝트 찾기
const wrongProject = projects.find(p => p.jobNo === 'M-2024-001');
if (wrongProject) {
// 잘못된 프로젝트를 올바른 Job No.로 수정
wrongProject.jobNo = 'TKR-25009R';
wrongProject.projectName = 'M Project';
addStatus('✅ 기존 프로젝트를 TKR-25009R로 수정 완료', 'text-green-600');
localStorage.setItem('work-report-projects', JSON.stringify(projects));
return wrongProject.id;
}
// 새로 생성 (기존 프로젝트가 없는 경우)
const mProject = {
id: Date.now(),
jobNo: 'TKR-25009R',
projectName: 'M Project',
createdBy: 'hyungi',
createdByName: '관리자',
createdAt: new Date().toISOString(),
isActive: true
};
projects.push(mProject);
localStorage.setItem('work-report-projects', JSON.stringify(projects));
addStatus('✅ TKR-25009R M Project 생성 완료', 'text-green-600');
return mProject.id;
}
async function migrateIssues() {
addStatus('기존 부적합 사항 마이그레이션 중...');
// 기존 부적합 사항 로드 (localStorage와 API 모두 확인)
let issues = [];
// 먼저 localStorage에서 확인
const savedLocal = localStorage.getItem('work-report-issues');
if (savedLocal) {
issues = JSON.parse(savedLocal);
addStatus('localStorage에서 부적합 사항 발견', 'text-blue-600');
}
// API에서도 확인 (현재 시스템이 API 기반이므로)
try {
if (typeof IssuesAPI !== 'undefined') {
const apiIssues = await IssuesAPI.getAll();
if (apiIssues && apiIssues.length > 0) {
issues = apiIssues;
addStatus('API에서 부적합 사항 발견', 'text-blue-600');
}
}
} catch (error) {
addStatus('API 조회 실패, localStorage 데이터 사용', 'text-yellow-600');
}
if (issues.length === 0) {
addStatus('마이그레이션할 부적합 사항이 없습니다.', 'text-yellow-600');
return;
}
// TKR-25009R M Project ID 가져오기
const projects = JSON.parse(localStorage.getItem('work-report-projects') || '[]');
const mProject = projects.find(p => p.jobNo === 'TKR-25009R');
if (!mProject) {
throw new Error('TKR-25009R M Project를 찾을 수 없습니다.');
}
// 모든 부적합 사항에 프로젝트 ID 추가
let migratedCount = 0;
for (let issue of issues) {
if (!issue.project_id && !issue.projectId) {
// API 방식과 localStorage 방식 모두 지원
issue.project_id = mProject.id;
issue.projectId = mProject.id;
issue.project_name = 'TKR-25009R - M Project';
issue.projectName = 'TKR-25009R - M Project';
migratedCount++;
// API로 업데이트 시도
try {
if (typeof IssuesAPI !== 'undefined' && issue.id) {
await IssuesAPI.update(issue.id, {
project_id: mProject.id,
project_name: 'TKR-25009R - M Project'
});
}
} catch (error) {
console.log('API 업데이트 실패, localStorage만 업데이트');
}
}
}
// localStorage에도 저장
localStorage.setItem('work-report-issues', JSON.stringify(issues));
addStatus(`${migratedCount}개 부적합 사항 마이그레이션 완료`, 'text-green-600');
}
async function createSampleHours() {
addStatus('368시간 샘플 데이터 생성 중...');
// TKR-25009R M Project ID 가져오기
const projects = JSON.parse(localStorage.getItem('work-report-projects') || '[]');
const mProject = projects.find(p => p.jobNo === 'TKR-25009R');
if (!mProject) {
throw new Error('TKR-25009R M Project를 찾을 수 없습니다.');
}
// 기존 일일 공수 데이터 로드
let dailyWorkData = [];
const saved = localStorage.getItem('daily-work-data');
if (saved) {
dailyWorkData = JSON.parse(saved);
}
// 기존 데이터 중 잘못된 프로젝트로 등록된 것들 수정
let updatedCount = 0;
dailyWorkData.forEach(dayData => {
if (dayData.projects) {
dayData.projects.forEach(project => {
if (project.projectName && project.projectName.includes('M-2024-001')) {
project.projectId = mProject.id;
project.projectName = 'TKR-25009R - M Project';
updatedCount++;
}
});
}
});
if (updatedCount > 0) {
addStatus(`✅ 기존 ${updatedCount}개 프로젝트 데이터를 TKR-25009R로 수정`, 'text-green-600');
}
// 368시간을 여러 날짜에 분산해서 생성 (기존 데이터가 없는 경우만)
const workDays = [
{ date: '2024-10-01', hours: 48 },
{ date: '2024-10-02', hours: 52 },
{ date: '2024-10-03', hours: 44 },
{ date: '2024-10-04', hours: 40 },
{ date: '2024-10-07', hours: 56 },
{ date: '2024-10-08', hours: 48 },
{ date: '2024-10-09', hours: 36 },
{ date: '2024-10-10', hours: 44 }
];
let addedDays = 0;
workDays.forEach(workDay => {
// 해당 날짜에 이미 데이터가 있는지 확인
const existingData = dailyWorkData.find(d => d.date === workDay.date);
if (!existingData) {
const newData = {
date: workDay.date,
projects: [{
projectId: mProject.id,
projectName: 'TKR-25009R - M Project',
hours: workDay.hours
}],
totalHours: workDay.hours,
createdAt: new Date().toISOString(),
createdBy: 'hyungi'
};
dailyWorkData.push(newData);
addedDays++;
}
});
// 저장
localStorage.setItem('daily-work-data', JSON.stringify(dailyWorkData));
const totalCreatedHours = workDays.reduce((sum, day) => sum + day.hours, 0);
if (addedDays > 0) {
addStatus(`${addedDays}일간 총 ${totalCreatedHours}시간 데이터 생성 완료`, 'text-green-600');
} else {
addStatus('✅ 기존 시간 데이터 수정 완료', 'text-green-600');
}
}
</script>
</body>
</html>