기능 추가 및 버그 수정 — 처리시간 지표, 대시보드 차트, UI 개선

## 처리시간 지표
- 업무시간 기준(09-18 평일) / 공휴일 제외 24h / 달력 기준 3가지 모드 선택
- 공휴일 DB 관리 (holidays 테이블, 수동 등록·삭제·일괄 추가)
- 2026년 공휴일 등록 지원
- 설정 페이지에서 라디오 버튼으로 모드 선택

## 대시보드 차트
- 월별 평균 처리시간 막대 차트 추가
- 월별 신고 접수 건수 누적 막대 차트 추가
- 월별 → 일별 드릴다운 (막대 클릭 시 해당 월의 일별 차트로 전환)
- 일별 막대 클릭 시 처리 완료/신고 접수 상세 내역 모달
- 충전기별 누적 고장 건수 Top 10 수평 막대 차트 추가

## 신고 목록
- # 컬럼을 DB PK 대신 현재 목록 순서(1, 2, 3…)로 표시
- 엑셀 export 접수번호도 순차번호로 변경

## 모바일 네비게이션 버그 수정
- 모바일에서 가로 오버플로우 시 nav가 body 넓이로 늘어나
  햄버거 버튼이 화면 밖으로 밀리는 문제 수정
- nav를 position:fixed + body padding-top:54px 로 변경 (전체 페이지 적용)
- 충전기 관리·신고 목록 페이지 지도 컨테이너에 isolation:isolate 적용

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
byun
2026-05-31 06:52:56 +09:00
parent 05b478372a
commit 2e8751ea6c
35 changed files with 5541 additions and 353 deletions

View File

@@ -66,7 +66,7 @@ const API = (() => {
post: (path, body) => req('POST', path, body, body instanceof FormData),
put: (path, body) => req('PUT', path, body, body instanceof FormData),
patch: (path, body) => req('PATCH', path, body, body instanceof FormData),
delete: (path) => req('DELETE', path),
delete: (path, body) => req('DELETE', path, body ?? null),
download,
};
})();

View File

@@ -33,10 +33,41 @@ const Auth = (() => {
function renderNav(el) {
if (!el) return;
el.innerHTML = `
<button class="nav-hamburger" onclick="Auth.toggleMobileNav()" aria-label="메뉴">☰</button>
<span class="nav-user">
<span>${name()} <small style="color:var(--accent)">[${role()}]</small></span>
<a onclick="Auth.logout()">로그아웃</a>
</span>`;
// 모바일 오버레이 삽입 (중복 방지)
if (!document.getElementById('mobileNavOverlay')) {
const ov = document.createElement('div');
ov.id = 'mobileNavOverlay';
ov.className = 'mobile-nav-overlay';
ov.addEventListener('click', closeMobileNav);
document.body.appendChild(ov);
}
// 사이드바 링크 클릭 시 드로어 닫기
setTimeout(() => {
document.querySelectorAll('.sidebar a').forEach(a => {
a.addEventListener('click', closeMobileNav);
});
}, 0);
}
function toggleMobileNav() {
const sidebar = document.querySelector('.sidebar');
const overlay = document.getElementById('mobileNavOverlay');
if (!sidebar) return;
const opening = !sidebar.classList.contains('mobile-open');
sidebar.classList.toggle('mobile-open', opening);
overlay?.classList.toggle('show', opening);
}
function closeMobileNav() {
document.querySelector('.sidebar')?.classList.remove('mobile-open');
document.getElementById('mobileNavOverlay')?.classList.remove('show');
}
function statusBadge(status) {
@@ -59,5 +90,6 @@ const Auth = (() => {
return new Date(dt).toLocaleString('ko-KR', {year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'});
}
return { save, token, role, name, uid, logout, require, renderNav, statusBadge, costStatusBadge, fmtDt };
return { save, token, role, name, uid, logout, require, renderNav,
toggleMobileNav, closeMobileNav, statusBadge, costStatusBadge, fmtDt };
})();