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>
This commit is contained in:
Hyungi Ahn
2026-02-02 14:27:22 +09:00
parent b6485e3140
commit 74d3a78aa3
116 changed files with 23117 additions and 294 deletions

View File

@@ -36,8 +36,9 @@ async function processNavbarDom(doc) {
async function filterMenuByPageAccess(doc, currentUser) {
const userRole = (currentUser.role || '').toLowerCase();
// Admin은 모든 메뉴 표시
if (userRole === 'admin' || userRole === 'system') {
// Admin은 모든 메뉴 표시 + .admin-only 요소 활성화
if (userRole === 'admin' || userRole === 'system admin') {
doc.querySelectorAll('.admin-only').forEach(el => el.classList.add('visible'));
return;
}
@@ -56,7 +57,7 @@ async function filterMenuByPageAccess(doc, currentUser) {
// 캐시가 없으면 API 호출
if (!accessiblePages) {
const response = await fetch(`${window.API_BASE_URL}/api/users/${currentUser.user_id}/page-access`, {
const response = await fetch(`${window.API_BASE_URL}/users/${currentUser.user_id}/page-access`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
@@ -153,14 +154,118 @@ function setupNavbarEvents() {
}
/**
* 현재 시간을 업데이트하는 함수
* 현재 날짜와 시간을 업데이트하는 함수
*/
function updateTime() {
const timeElement = document.getElementById('timeValue');
if (timeElement) {
const now = new Date();
timeElement.textContent = now.toLocaleTimeString('ko-KR', { hour12: false });
function updateDateTime() {
const now = new Date();
// 시간 업데이트
const timeElement = document.getElementById('timeValue');
if (timeElement) {
timeElement.textContent = now.toLocaleTimeString('ko-KR', { hour12: false });
}
// 날짜 업데이트
const dateElement = document.getElementById('dateValue');
if (dateElement) {
const days = ['일', '월', '화', '수', '목', '금', '토'];
const month = now.getMonth() + 1;
const date = now.getDate();
const day = days[now.getDay()];
dateElement.textContent = `${month}${date}일 (${day})`;
}
}
// 날씨 아이콘 매핑
const WEATHER_ICONS = {
clear: '☀️',
rain: '🌧️',
snow: '❄️',
heat: '🔥',
cold: '🥶',
wind: '💨',
fog: '🌫️',
dust: '😷',
cloudy: '⛅',
overcast: '☁️'
};
// 날씨 조건명
const WEATHER_NAMES = {
clear: '맑음',
rain: '비',
snow: '눈',
heat: '폭염',
cold: '한파',
wind: '강풍',
fog: '안개',
dust: '미세먼지',
cloudy: '구름많음',
overcast: '흐림'
};
/**
* 날씨 정보를 가져와서 업데이트하는 함수
*/
async function updateWeather() {
try {
const token = localStorage.getItem('token');
if (!token) return;
const response = await fetch(`${window.API_BASE_URL}/tbm/weather/current`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
}
});
if (!response.ok) {
throw new Error('날씨 API 호출 실패');
}
const result = await response.json();
if (result.success && result.data) {
const { temperature, conditions, weatherData } = result.data;
// 온도 표시
const tempElement = document.getElementById('weatherTemp');
if (tempElement && temperature !== null && temperature !== undefined) {
tempElement.textContent = `${Math.round(temperature)}°C`;
}
// 날씨 아이콘 및 설명
const iconElement = document.getElementById('weatherIcon');
const descElement = document.getElementById('weatherDesc');
if (conditions && conditions.length > 0) {
const primaryCondition = conditions[0];
if (iconElement) {
iconElement.textContent = WEATHER_ICONS[primaryCondition] || '🌤️';
}
if (descElement) {
descElement.textContent = WEATHER_NAMES[primaryCondition] || '맑음';
}
} else {
if (iconElement) iconElement.textContent = '☀️';
if (descElement) descElement.textContent = '맑음';
}
// 날씨 섹션 표시
const weatherSection = document.getElementById('weatherSection');
if (weatherSection) {
weatherSection.style.opacity = '1';
}
}
} catch (error) {
console.warn('날씨 정보 로드 실패:', error.message);
// 실패해도 기본값 표시
const descElement = document.getElementById('weatherDesc');
if (descElement) {
descElement.textContent = '날씨 정보 없음';
}
}
}
// 메인 로직: DOMContentLoaded 시 실행
@@ -168,12 +273,16 @@ document.addEventListener('DOMContentLoaded', async () => {
if (getUser()) {
// 1. 컴포넌트 로드 및 DOM 수정
await loadComponent('navbar', '#navbar-container', processNavbarDom);
// 2. DOM에 삽입된 후에 이벤트 리스너 설정
setupNavbarEvents();
// 3. 실시간 시간 업데이트 시작
updateTime();
setInterval(updateTime, 1000);
// 3. 실시간 날짜/시간 업데이트 시작
updateDateTime();
setInterval(updateDateTime, 1000);
// 4. 날씨 정보 로드 (10분마다 갱신)
updateWeather();
setInterval(updateWeather, 10 * 60 * 1000);
}
});