225 lines
11 KiB
HTML
225 lines
11 KiB
HTML
<!DOCTYPE html><html lang="ko"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>데이터 내보내기</title><link rel="stylesheet" href="/css/style.css">
|
|
<style>
|
|
.export-card { background:white; border-radius:10px; padding:24px; box-shadow:0 2px 8px rgba(0,0,0,.06); margin-bottom:20px; }
|
|
.sheet-badge { display:inline-flex; align-items:center; gap:6px; padding:5px 14px; border-radius:20px; font-size:12px; font-weight:700; margin:3px; }
|
|
.date-row { display:flex; gap:14px; align-items:flex-end; flex-wrap:wrap; margin-top:18px; }
|
|
.date-row .form-group { margin:0; min-width:140px; }
|
|
.quick-btns { display:flex; gap:8px; flex-wrap:wrap; margin-top:10px; }
|
|
.quick-btn { padding:4px 12px; border:1px solid var(--gray3); border-radius:6px; background:white; font-size:12px; color:var(--navy); cursor:pointer; }
|
|
.quick-btn:hover { background:var(--gray1); border-color:var(--accent); }
|
|
.download-btn { padding:12px 28px; font-size:15px; font-weight:700; border-radius:8px; border:none;
|
|
background:var(--blue); color:white; cursor:pointer; display:flex; align-items:center; gap:8px;
|
|
transition:background .15s; min-width:220px; justify-content:center; }
|
|
.download-btn:hover { background:#1251A3; }
|
|
.download-btn:disabled { background:var(--gray3); cursor:not-allowed; }
|
|
.ind-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(200px,1fr)); gap:12px; margin-top:16px; }
|
|
.ind-item { border:1px solid var(--gray2); border-radius:8px; padding:14px 16px; }
|
|
.ind-item .ind-title { font-size:13px; font-weight:700; color:var(--navy); margin-bottom:8px; }
|
|
.ind-item .ind-desc { font-size:11px; color:var(--gray4); margin-bottom:10px; line-height:1.5; }
|
|
.status-msg { padding:10px 14px; border-radius:6px; font-size:13px; margin-top:12px; display:none; }
|
|
@media(max-width:768px){
|
|
.date-row { flex-direction:column; align-items:stretch; }
|
|
.download-btn { width:100%; }
|
|
.ind-grid { grid-template-columns:1fr; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<nav class="nav">
|
|
<div style="display:flex;align-items:center;gap:2px;">
|
|
<button class="nav-hamburger" onclick="toggleSidebar()">☰</button>
|
|
<span class="nav-brand">⚡ EV AS 관리 — 관리자</span>
|
|
</div>
|
|
<div id="navUser"></div>
|
|
</nav>
|
|
<div class="mobile-nav-overlay" id="navOverlay" onclick="toggleSidebar()"></div>
|
|
<div class="layout">
|
|
<div class="sidebar" id="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">📷 QR 생성</a>
|
|
<a href="/pages/admin/accounts.html">👥 계정 관리</a>
|
|
<a href="/pages/admin/export.html" class="active">📥 데이터 내보내기</a>
|
|
<a href="/pages/admin/settings.html">⚙️ 설정</a>
|
|
</div>
|
|
<div class="main">
|
|
<div style="display:flex;align-items:center;gap:10px;margin-bottom:18px;flex-wrap:wrap;">
|
|
<h2 style="font-size:18px;font-weight:700;color:var(--navy)">📥 데이터 내보내기</h2>
|
|
</div>
|
|
|
|
<!-- 통합 다운로드 -->
|
|
<div class="export-card">
|
|
<div class="card-title">📊 기간별 통합 다운로드</div>
|
|
<p style="font-size:13px;color:var(--gray4);margin-bottom:8px;line-height:1.7;">
|
|
설정한 기간의 모든 데이터를 <strong>하나의 엑셀 파일(4개 시트)</strong>로 다운로드합니다.
|
|
</p>
|
|
<div style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:4px;">
|
|
<span class="sheet-badge" style="background:#0B1E3D;color:white;">① AS 신고이력</span>
|
|
<span class="sheet-badge" style="background:#1B5E20;color:white;">② 조치이력</span>
|
|
<span class="sheet-badge" style="background:#4A148C;color:white;">③ 개선항목</span>
|
|
<span class="sheet-badge" style="background:#E65100;color:white;">④ 출장비정산</span>
|
|
<span class="sheet-badge" style="background:#37474F;color:white;">+ 요약</span>
|
|
</div>
|
|
|
|
<div class="date-row">
|
|
<div class="form-group">
|
|
<label>시작일</label>
|
|
<input type="date" id="dateFrom">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>종료일</label>
|
|
<input type="date" id="dateTo">
|
|
</div>
|
|
<button class="download-btn" id="fullBtn" onclick="downloadFull()">
|
|
📥 통합 엑셀 다운로드
|
|
</button>
|
|
</div>
|
|
|
|
<div class="quick-btns">
|
|
<span style="font-size:11px;color:var(--gray4);align-self:center;">빠른 선택:</span>
|
|
<button class="quick-btn" onclick="setRange(7)">최근 7일</button>
|
|
<button class="quick-btn" onclick="setRange(30)">최근 30일</button>
|
|
<button class="quick-btn" onclick="setRange(90)">최근 3개월</button>
|
|
<button class="quick-btn" onclick="setThisMonth()">이번 달</button>
|
|
<button class="quick-btn" onclick="setLastMonth()">지난 달</button>
|
|
<button class="quick-btn" onclick="setThisYear()">올해 전체</button>
|
|
<button class="quick-btn" onclick="clearRange()">전체 기간</button>
|
|
</div>
|
|
|
|
<div id="statusMsg" class="status-msg"></div>
|
|
</div>
|
|
|
|
<!-- 개별 다운로드 -->
|
|
<div class="export-card">
|
|
<div class="card-title">📄 항목별 개별 다운로드</div>
|
|
<p style="font-size:13px;color:var(--gray4);margin-bottom:4px;">위 기간 설정이 동일하게 적용됩니다.</p>
|
|
<div class="ind-grid">
|
|
<div class="ind-item">
|
|
<div class="ind-title" style="color:#0B1E3D;">📋 AS 신고이력</div>
|
|
<div class="ind-desc">신고 접수일 기준 · 충전기/신고자/상태/<br>정비사/조치내용/출장비 포함</div>
|
|
<button class="btn btn-outline btn-sm" onclick="downloadIndividual('reports')">📥 다운로드</button>
|
|
</div>
|
|
<div class="ind-item">
|
|
<div class="ind-title" style="color:#1B5E20;">🔧 조치이력</div>
|
|
<div class="ind-desc">조치 완료일 기준 · 정비사/조치유형/<br>소요시간/승인 여부 포함</div>
|
|
<button class="btn btn-outline btn-sm" onclick="downloadIndividual('repairs')">📥 다운로드</button>
|
|
</div>
|
|
<div class="ind-item">
|
|
<div class="ind-title" style="color:#4A148C;">🔧 개선항목</div>
|
|
<div class="ind-desc">등록일 기준 · 분류/우선순위/<br>담당업체/진행상태 포함</div>
|
|
<button class="btn btn-outline btn-sm" onclick="downloadIndividual('improvements')">📥 다운로드</button>
|
|
</div>
|
|
<div class="ind-item">
|
|
<div class="ind-title" style="color:#E65100;">💰 출장비 정산</div>
|
|
<div class="ind-desc">조치 완료일 기준 · 부담/수급 주체/<br>금액/정산상태 포함</div>
|
|
<button class="btn btn-outline btn-sm" onclick="downloadIndividual('costs')">📥 다운로드</button>
|
|
</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'));
|
|
|
|
function toggleSidebar() {
|
|
const s = document.getElementById('sidebar');
|
|
const o = document.getElementById('navOverlay');
|
|
if (s) s.classList.toggle('mobile-open');
|
|
if (o) o.classList.toggle('show');
|
|
}
|
|
|
|
function pad(n) { return String(n).padStart(2,'0'); }
|
|
function fmtDate(d) { return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`; }
|
|
|
|
function setRange(days) {
|
|
const to = new Date();
|
|
const from = new Date(); from.setDate(from.getDate() - days + 1);
|
|
document.getElementById('dateFrom').value = fmtDate(from);
|
|
document.getElementById('dateTo').value = fmtDate(to);
|
|
}
|
|
function setThisMonth() {
|
|
const now = new Date();
|
|
document.getElementById('dateFrom').value = `${now.getFullYear()}-${pad(now.getMonth()+1)}-01`;
|
|
document.getElementById('dateTo').value = fmtDate(now);
|
|
}
|
|
function setLastMonth() {
|
|
const now = new Date();
|
|
const y = now.getMonth() === 0 ? now.getFullYear()-1 : now.getFullYear();
|
|
const m = now.getMonth() === 0 ? 12 : now.getMonth();
|
|
const last = new Date(y, m, 0);
|
|
document.getElementById('dateFrom').value = `${y}-${pad(m)}-01`;
|
|
document.getElementById('dateTo').value = fmtDate(last);
|
|
}
|
|
function setThisYear() {
|
|
const now = new Date();
|
|
document.getElementById('dateFrom').value = `${now.getFullYear()}-01-01`;
|
|
document.getElementById('dateTo').value = fmtDate(now);
|
|
}
|
|
function clearRange() {
|
|
document.getElementById('dateFrom').value = '';
|
|
document.getElementById('dateTo').value = '';
|
|
}
|
|
|
|
function buildQuery() {
|
|
const from = document.getElementById('dateFrom').value;
|
|
const to = document.getElementById('dateTo').value;
|
|
const p = [];
|
|
if (from) p.push('date_from=' + from);
|
|
if (to) p.push('date_to=' + to);
|
|
return p.length ? '?' + p.join('&') : '';
|
|
}
|
|
|
|
function showStatus(msg, type='info') {
|
|
const el = document.getElementById('statusMsg');
|
|
el.textContent = msg;
|
|
el.style.display = 'block';
|
|
el.className = 'status-msg alert alert-' + type;
|
|
}
|
|
function hideStatus() { document.getElementById('statusMsg').style.display = 'none'; }
|
|
|
|
async function downloadFull() {
|
|
const btn = document.getElementById('fullBtn');
|
|
btn.disabled = true;
|
|
btn.textContent = '⏳ 생성 중...';
|
|
showStatus('엑셀 파일을 생성 중입니다. 데이터량에 따라 수 초가 걸릴 수 있습니다.', 'info');
|
|
try {
|
|
const from = document.getElementById('dateFrom').value;
|
|
const to = document.getElementById('dateTo').value;
|
|
const period = (from || to) ? `${from||'전체'}~${to||'전체'}` : '전체기간';
|
|
await API.download('/export/full' + buildQuery(), `EV_AS_통합이력_${period}.xlsx`);
|
|
showStatus('✅ 다운로드가 완료되었습니다.', 'success');
|
|
} catch(e) {
|
|
showStatus('❌ 오류: ' + e.message, 'danger');
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.innerHTML = '📥 통합 엑셀 다운로드';
|
|
setTimeout(hideStatus, 5000);
|
|
}
|
|
}
|
|
|
|
async function downloadIndividual(type) {
|
|
const names = {
|
|
reports: 'AS신고이력',
|
|
repairs: '조치이력',
|
|
improvements: '개선항목',
|
|
costs: '출장비정산',
|
|
};
|
|
try {
|
|
await API.download('/export/' + type + buildQuery(), `${names[type]}.xlsx`);
|
|
} catch(e) { alert('다운로드 오류: ' + e.message); }
|
|
}
|
|
|
|
// 기본값: 이번 달
|
|
setThisMonth();
|
|
</script>
|
|
</body></html>
|