Files
TK-FB-Project/api.hyungi.net/db/migrations/20260202200000_reorganize_pages.js
Hyungi Ahn 74d3a78aa3 feat: 페이지 구조 재구성 및 사이드바 네비게이션 구현
- 페이지 폴더 재구성: safety/, attendance/ 폴더 신규 생성
  - work/ → safety/: 이슈 신고, 출입 신청 관련 페이지 이동
  - common/ → attendance/: 근태/휴가 관련 페이지 이동
  - admin/ 정리: safety-* 파일들을 safety/로 이동

- 사이드바 네비게이션 메뉴 구현
  - 카테고리별 메뉴: 작업관리, 안전관리, 근태관리, 시스템관리
  - 접기/펼치기 기능 및 상태 저장
  - 관리자 전용 메뉴 자동 표시/숨김

- 날씨 API 연동 (기상청 단기예보)
  - TBM 및 navbar에 현재 날씨 표시
  - weatherService.js 추가

- 안전 체크리스트 확장
  - 기본/날씨별/작업별 체크 유형 추가
  - checklist-manage.html 페이지 추가

- 이슈 신고 시스템 구현
  - workIssueController, workIssueModel, workIssueRoutes 추가

- DB 마이그레이션 파일 추가 (실행 대기)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 14:27:22 +09:00

320 lines
11 KiB
JavaScript

/**
* 페이지 구조 재구성 마이그레이션
* - 페이지 경로 업데이트 (safety/, attendance/ 폴더로 이동)
* - 카테고리 재분류
* - 역할별 기본 페이지 권한 테이블 생성
*/
exports.up = async function(knex) {
// 1. 페이지 경로 업데이트 - safety 폴더로 이동된 페이지들
const safetyPageUpdates = [
{
old_key: 'issue-report',
new_key: 'safety.issue_report',
new_path: '/pages/safety/issue-report.html',
new_category: 'safety',
new_name: '이슈 신고'
},
{
old_key: 'issue-list',
new_key: 'safety.issue_list',
new_path: '/pages/safety/issue-list.html',
new_category: 'safety',
new_name: '이슈 목록'
},
{
old_key: 'issue-detail',
new_key: 'safety.issue_detail',
new_path: '/pages/safety/issue-detail.html',
new_category: 'safety',
new_name: '이슈 상세'
},
{
old_key: 'visit-request',
new_key: 'safety.visit_request',
new_path: '/pages/safety/visit-request.html',
new_category: 'safety',
new_name: '방문 요청'
},
{
old_key: 'safety-management',
new_key: 'safety.management',
new_path: '/pages/safety/management.html',
new_category: 'safety',
new_name: '안전 관리'
},
{
old_key: 'safety-training-conduct',
new_key: 'safety.training_conduct',
new_path: '/pages/safety/training-conduct.html',
new_category: 'safety',
new_name: '안전교육 진행'
}
];
// 2. 페이지 경로 업데이트 - attendance 폴더로 이동된 페이지들
const attendancePageUpdates = [
{
old_key: 'daily-attendance',
new_key: 'attendance.daily',
new_path: '/pages/attendance/daily.html',
new_category: 'attendance',
new_name: '일일 출퇴근'
},
{
old_key: 'monthly-attendance',
new_key: 'attendance.monthly',
new_path: '/pages/attendance/monthly.html',
new_category: 'attendance',
new_name: '월간 근태'
},
{
old_key: 'annual-vacation-overview',
new_key: 'attendance.annual_overview',
new_path: '/pages/attendance/annual-overview.html',
new_category: 'attendance',
new_name: '연간 휴가 현황'
},
{
old_key: 'vacation-request',
new_key: 'attendance.vacation_request',
new_path: '/pages/attendance/vacation-request.html',
new_category: 'attendance',
new_name: '휴가 신청'
},
{
old_key: 'vacation-management',
new_key: 'attendance.vacation_management',
new_path: '/pages/attendance/vacation-management.html',
new_category: 'attendance',
new_name: '휴가 관리'
},
{
old_key: 'vacation-allocation',
new_key: 'attendance.vacation_allocation',
new_path: '/pages/attendance/vacation-allocation.html',
new_category: 'attendance',
new_name: '휴가 발생 입력'
}
];
// 3. admin 폴더 내 파일명 변경
const adminPageUpdates = [
{
old_key: 'attendance-report-comparison',
new_key: 'admin.attendance_report',
new_path: '/pages/admin/attendance-report.html',
new_category: 'admin',
new_name: '출퇴근-보고서 대조'
}
];
// 모든 업데이트 실행
const allUpdates = [...safetyPageUpdates, ...attendancePageUpdates, ...adminPageUpdates];
for (const update of allUpdates) {
await knex('pages')
.where('page_key', update.old_key)
.update({
page_key: update.new_key,
page_path: update.new_path,
category: update.new_category,
page_name: update.new_name
});
}
// 4. 안전 체크리스트 관리 페이지 추가 (새로 생성된 페이지)
const existingChecklistPage = await knex('pages')
.where('page_key', 'safety.checklist_manage')
.orWhere('page_key', 'safety-checklist-manage')
.first();
if (!existingChecklistPage) {
await knex('pages').insert({
page_key: 'safety.checklist_manage',
page_name: '안전 체크리스트 관리',
page_path: '/pages/safety/checklist-manage.html',
category: 'safety',
description: '안전 체크리스트 항목 관리',
is_admin_only: 1,
display_order: 50
});
}
// 5. 휴가 승인/직접입력 페이지 추가 (새로 생성된 페이지인 경우)
const vacationPages = [
{
page_key: 'attendance.vacation_approval',
page_name: '휴가 승인 관리',
page_path: '/pages/attendance/vacation-approval.html',
category: 'attendance',
description: '휴가 신청 승인/거부',
is_admin_only: 1,
display_order: 65
},
{
page_key: 'attendance.vacation_input',
page_name: '휴가 직접 입력',
page_path: '/pages/attendance/vacation-input.html',
category: 'attendance',
description: '관리자 휴가 직접 입력',
is_admin_only: 1,
display_order: 66
}
];
for (const page of vacationPages) {
const existing = await knex('pages').where('page_key', page.page_key).first();
if (!existing) {
await knex('pages').insert(page);
}
}
// 6. role_default_pages 테이블 생성 (역할별 기본 페이지 권한)
const tableExists = await knex.schema.hasTable('role_default_pages');
if (!tableExists) {
await knex.schema.createTable('role_default_pages', (table) => {
table.integer('role_id').unsigned().notNullable()
.references('id').inTable('roles').onDelete('CASCADE');
table.integer('page_id').unsigned().notNullable()
.references('id').inTable('pages').onDelete('CASCADE');
table.primary(['role_id', 'page_id']);
table.timestamps(true, true);
});
}
// 7. 기본 역할-페이지 매핑 데이터 삽입
// 역할 조회
const roles = await knex('roles').select('id', 'name');
const pages = await knex('pages').select('id', 'page_key', 'category');
const roleMap = {};
roles.forEach(r => { roleMap[r.name] = r.id; });
const pageMap = {};
pages.forEach(p => { pageMap[p.page_key] = p.id; });
// Worker 역할 기본 페이지 (대시보드, 작업보고서, 휴가신청)
const workerPages = [
'dashboard',
'work.report_create',
'work.report_view',
'attendance.vacation_request'
];
// Leader 역할 기본 페이지 (Worker + TBM, 안전, 근태 일부)
const leaderPages = [
...workerPages,
'work.tbm',
'work.analysis',
'safety.issue_report',
'safety.issue_list',
'attendance.daily',
'attendance.monthly'
];
// SafetyManager 역할 기본 페이지 (Leader + 안전 전체)
const safetyManagerPages = [
...leaderPages,
'safety.issue_detail',
'safety.visit_request',
'safety.management',
'safety.training_conduct',
'safety.checklist_manage'
];
// 역할별 페이지 매핑 삽입
const rolePageMappings = [];
if (roleMap['Worker']) {
workerPages.forEach(pageKey => {
if (pageMap[pageKey]) {
rolePageMappings.push({ role_id: roleMap['Worker'], page_id: pageMap[pageKey] });
}
});
}
if (roleMap['Leader']) {
leaderPages.forEach(pageKey => {
if (pageMap[pageKey]) {
rolePageMappings.push({ role_id: roleMap['Leader'], page_id: pageMap[pageKey] });
}
});
}
if (roleMap['SafetyManager']) {
safetyManagerPages.forEach(pageKey => {
if (pageMap[pageKey]) {
rolePageMappings.push({ role_id: roleMap['SafetyManager'], page_id: pageMap[pageKey] });
}
});
}
// 중복 제거 후 삽입
for (const mapping of rolePageMappings) {
const existing = await knex('role_default_pages')
.where('role_id', mapping.role_id)
.where('page_id', mapping.page_id)
.first();
if (!existing) {
await knex('role_default_pages').insert(mapping);
}
}
console.log('페이지 구조 재구성 완료');
console.log(`- 업데이트된 페이지: ${allUpdates.length}`);
console.log(`- 역할별 기본 페이지 매핑: ${rolePageMappings.length}`);
};
exports.down = async function(knex) {
// 1. role_default_pages 테이블 삭제
await knex.schema.dropTableIfExists('role_default_pages');
// 2. 페이지 경로 원복 - safety → work/admin
const safetyRevert = [
{ new_key: 'safety.issue_report', old_key: 'issue-report', old_path: '/pages/work/issue-report.html', old_category: 'work' },
{ new_key: 'safety.issue_list', old_key: 'issue-list', old_path: '/pages/work/issue-list.html', old_category: 'work' },
{ new_key: 'safety.issue_detail', old_key: 'issue-detail', old_path: '/pages/work/issue-detail.html', old_category: 'work' },
{ new_key: 'safety.visit_request', old_key: 'visit-request', old_path: '/pages/work/visit-request.html', old_category: 'work' },
{ new_key: 'safety.management', old_key: 'safety-management', old_path: '/pages/admin/safety-management.html', old_category: 'admin' },
{ new_key: 'safety.training_conduct', old_key: 'safety-training-conduct', old_path: '/pages/admin/safety-training-conduct.html', old_category: 'admin' },
];
// 3. 페이지 경로 원복 - attendance → common
const attendanceRevert = [
{ new_key: 'attendance.daily', old_key: 'daily-attendance', old_path: '/pages/common/daily-attendance.html', old_category: 'common' },
{ new_key: 'attendance.monthly', old_key: 'monthly-attendance', old_path: '/pages/common/monthly-attendance.html', old_category: 'common' },
{ new_key: 'attendance.annual_overview', old_key: 'annual-vacation-overview', old_path: '/pages/common/annual-vacation-overview.html', old_category: 'common' },
{ new_key: 'attendance.vacation_request', old_key: 'vacation-request', old_path: '/pages/common/vacation-request.html', old_category: 'common' },
{ new_key: 'attendance.vacation_management', old_key: 'vacation-management', old_path: '/pages/common/vacation-management.html', old_category: 'common' },
{ new_key: 'attendance.vacation_allocation', old_key: 'vacation-allocation', old_path: '/pages/common/vacation-allocation.html', old_category: 'common' },
];
// 4. admin 파일명 원복
const adminRevert = [
{ new_key: 'admin.attendance_report', old_key: 'attendance-report-comparison', old_path: '/pages/admin/attendance-report-comparison.html', old_category: 'admin' }
];
const allReverts = [...safetyRevert, ...attendanceRevert, ...adminRevert];
for (const revert of allReverts) {
await knex('pages')
.where('page_key', revert.new_key)
.update({
page_key: revert.old_key,
page_path: revert.old_path,
category: revert.old_category
});
}
// 5. 새로 추가된 페이지 삭제
await knex('pages').whereIn('page_key', [
'safety.checklist_manage',
'attendance.vacation_approval',
'attendance.vacation_input'
]).del();
console.log('페이지 구조 재구성 롤백 완료');
};