Files
ev-charger-as/frontend/static/pages/admin/qr.html
byun 2e8751ea6c 기능 추가 및 버그 수정 — 처리시간 지표, 대시보드 차트, 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>
2026-05-31 06:52:56 +09:00

79 lines
4.4 KiB
HTML

<!DOCTYPE html><html lang="ko"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>QR 생성</title><link rel="stylesheet" href="/css/style.css"></head>
<body>
<nav class="nav"><span class="nav-brand">⚡ EV AS 관리 — 관리자</span><div id="navUser"></div></nav>
<div class="layout">
<div class="sidebar">
<div class="sidebar-section">AS 관리</div>
<a href="/pages/admin/dashboard.html">📊 대시보드</a>
<a href="/pages/admin/reports.html">📋 신고 목록</a>
<a href="/pages/admin/costs.html">💰 출장비 관리</a>
<div class="sidebar-section">시스템</div>
<a href="/pages/admin/improvements.html">🔧 개선항목</a>
<a href="/pages/admin/chargers.html">⚡ 충전기 관리</a>
<a href="/pages/admin/charger-types.html">🏷 충전기 종류</a>
<a href="/pages/admin/issue-types.html">📝 유형관리</a>
<a href="/pages/admin/qr.html" class="active">📷 QR 생성</a>
<a href="/pages/admin/accounts.html">👥 계정 관리</a>
<a href="/pages/admin/settings.html">⚙️ 설정</a>
</div>
<div class="main">
<h2 style="font-size:18px;font-weight:700;color:var(--navy);margin-bottom:18px">QR 코드 생성</h2>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;max-width:800px">
<div class="card">
<div class="card-title">충전기 선택</div>
<div class="form-group"><label>등록된 충전기 선택</label>
<select id="chargerSel" onchange="onSelect()"><option value="">직접 입력</option></select>
</div>
<div class="form-group"><label>충전기 ID</label><input type="text" id="cId" placeholder="CG-003"></div>
<button class="btn btn-primary" onclick="genQR()">📷 QR 생성</button>
<div id="err" class="alert alert-danger" style="display:none;margin-top:10px"></div>
</div>
<div class="card" id="qrResult" style="display:none">
<div class="card-title">생성된 QR 코드</div>
<div style="text-align:center">
<img id="qrImg" style="width:200px;height:200px;border:1px solid var(--gray3);border-radius:8px">
<div id="qrInfo" style="margin-top:10px;font-size:13px;color:var(--text2)"></div>
<button class="btn btn-success" style="margin-top:12px" onclick="printQR()">🖨 인쇄</button>
<a id="qrDownload" class="btn btn-outline" style="margin-top:8px;display:block">⬇ 이미지 저장</a>
</div>
</div>
</div>
</div>
</div>
<script src="/js/api.js"></script><script src="/js/auth.js"></script>
<script>
Auth.require(['admin']); Auth.renderNav(document.getElementById('navUser'));
let chargerMap = {};
async function load() {
const chargers = await API.get('/chargers');
chargers.forEach(c => chargerMap[c.id] = c);
document.getElementById('chargerSel').innerHTML = '<option value="">직접 입력</option>' +
chargers.map(c=>`<option value="${c.id}">${c.id}${c.name} (${c.station_name})</option>`).join('');
const urlId = new URLSearchParams(location.search).get('id');
if (urlId) { document.getElementById('chargerSel').value = urlId; onSelect(); genQR(); }
}
function onSelect() {
const id = document.getElementById('chargerSel').value;
document.getElementById('cId').value = id;
}
async function genQR() {
const id = document.getElementById('cId').value.trim();
if (!id) { const el=document.getElementById('err'); el.textContent='충전기 ID를 입력하세요.'; el.style.display='block'; return; }
document.getElementById('err').style.display='none';
try {
const res = await API.post('/chargers/'+id+'/qr');
const c = chargerMap[id] || {};
document.getElementById('qrResult').style.display='block';
document.getElementById('qrImg').src = res.qr_path + '?t=' + Date.now();
document.getElementById('qrInfo').innerHTML = `<strong>${id}</strong><br>${c.name||''} / ${c.station_name||''}`;
const dl = document.getElementById('qrDownload');
dl.href = res.qr_path; dl.download = id + '_QR.png';
} catch(e) { const el=document.getElementById('err'); el.textContent=e.message; el.style.display='block'; }
}
function printQR() {
const img = document.getElementById('qrImg').src;
const w = window.open(''); w.document.write(`<html><body style="text-align:center"><img src="${img}" style="width:300px"><br><script>window.print()<\/script></body></html>`); w.document.close();
}
load();
</script></body></html>