1차완료

This commit is contained in:
byun
2026-06-02 19:34:36 +09:00
parent 9f0f4326fe
commit b6863cd260
28 changed files with 1667 additions and 460 deletions

View File

@@ -6,9 +6,10 @@
#btnDelete { display:none; }
</style></head>
<body>
<nav class="nav"><span class="nav-brand">⚡ EV AS 관리 — 관리자</span><div id="navUser"></div></nav>
<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">
<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>
@@ -20,6 +21,7 @@
<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">📥 데이터 내보내기</a>
<a href="/pages/admin/settings.html">⚙️ 설정</a>
</div>
<div class="main">
@@ -62,9 +64,9 @@
<div class="form-group">
<label>분류 <span class="req">*</span></label>
<select id="mCat">
<option value="sw">SW 개선</option><option value="hw">HW 개선</option>
<option value="ui">UI 개선</option><option value="firmware">펌웨어</option>
<option value="other">기타</option>
<option value="hardware">하드웨어</option><option value="software">소프트웨어</option>
<option value="firmware">펌웨어</option><option value="installation">설치환경</option>
<option value="ui">UI 개선</option><option value="other">기타</option>
</select>
</div>
<div class="form-group">
@@ -123,20 +125,25 @@ async function bulkDelete() {
catch(e) { alert('삭제 중 오류가 발생했습니다: ' + e.message); }
}
const CAT = {sw:'SW',hw:'HW',ui:'UI',firmware:'펌웨어',other:'기타'};
const CAT = {hardware:'하드웨어',software:'소프트웨어',firmware:'펌웨어',installation:'설치환경',ui:'UI',other:'기타'};
const PRI = {urgent:'🔴 긴급',high:'🟠 높음',normal:'🟡 보통',low:'🟢 낮음'};
const selectedReports = new Set();
let allReports = [];
async function load() {
const st = document.getElementById('fStatus').value;
const mfr = document.getElementById('fMfr').value;
let impUrl = '/improvements?';
if (st) impUrl += 'status=' + st + '&';
if (mfr) impUrl += 'manufacturer_id=' + mfr + '&';
const [mfrs, imps] = await Promise.all([
API.get('/accounts?role=manufacturer'),
API.get('/improvements?status='+document.getElementById('fStatus').value+'&manufacturer_id='+document.getElementById('fMfr').value)
API.get('/manufacturers'),
API.get(impUrl)
]);
// 제조사 필터 드롭다운
const mfrSel = document.getElementById('fMfr');
if (mfrSel.options.length <= 1)
mfrs.forEach(m => { const o=document.createElement('option'); o.value=m.id; o.textContent=`${m.company||''} / ${m.name}`; mfrSel.appendChild(o); });
mfrs.forEach(m => { const o=document.createElement('option'); o.value=m.id; o.textContent=m.name; mfrSel.appendChild(o); });
document.getElementById('chkAll').checked = false;
updateDeleteBtn();
@@ -151,7 +158,7 @@ async function load() {
<td onclick="location.href='/pages/admin/improvement-detail.html?id=${i.id}'" style="cursor:pointer;max-width:200px"><strong>${i.title}</strong></td>
<td onclick="location.href='/pages/admin/improvement-detail.html?id=${i.id}'" style="cursor:pointer">${CAT[i.category]||i.category}</td>
<td onclick="location.href='/pages/admin/improvement-detail.html?id=${i.id}'" style="cursor:pointer">${PRI[i.priority]||i.priority}</td>
<td onclick="location.href='/pages/admin/improvement-detail.html?id=${i.id}'" style="cursor:pointer">${i.manufacturer_company||'-'}<br><small>${i.manufacturer_name||''}</small></td>
<td onclick="location.href='/pages/admin/improvement-detail.html?id=${i.id}'" style="cursor:pointer">${i.manufacturer_name||'-'}</td>
<td onclick="location.href='/pages/admin/improvement-detail.html?id=${i.id}'" style="cursor:pointer"><span class="badge s-pending">${i.report_count}건</span></td>
<td onclick="location.href='/pages/admin/improvement-detail.html?id=${i.id}'" style="cursor:pointer">${Auth.statusBadge(i.status)}</td>
<td onclick="location.href='/pages/admin/improvement-detail.html?id=${i.id}'" style="cursor:pointer">${Auth.fmtDt(i.created_at)}</td>
@@ -161,9 +168,9 @@ async function load() {
async function openModal() {
document.getElementById('modal').classList.remove('hidden');
const mfrs = await API.get('/accounts?role=manufacturer');
document.getElementById('mMfr').innerHTML = '<option value="">제조사 선택</option>' +
mfrs.map(m=>`<option value="${m.id}">${m.company||''} / ${m.name}</option>`).join('');
const mfrs = await API.get('/manufacturers');
document.getElementById('mMfr').innerHTML = '<option value="">업체 선택</option>' +
mfrs.map(m=>`<option value="${m.id}">${m.name}</option>`).join('');
allReports = await API.get('/reports');
renderReportList('');
}
@@ -171,12 +178,12 @@ function closeModal() { document.getElementById('modal').classList.add('hidden')
function searchReports() { renderReportList(document.getElementById('mReportSearch').value.toLowerCase()); }
function renderReportList(q) {
const filtered = allReports.filter(r => !q || String(r.id).includes(q) || (r.charger_id||'').toLowerCase().includes(q)).slice(0,20);
const filtered = allReports.filter(r => !q || String(r.seq).includes(q) || (r.charger_id||'').toLowerCase().includes(q)).slice(0,20);
document.getElementById('mReportList').innerHTML = filtered.map(r => `
<label style="display:flex;gap:8px;align-items:center;padding:5px;cursor:pointer;${selectedReports.has(r.id)?'background:#E3EDFF;border-radius:4px':''}">
<input type="checkbox" ${selectedReports.has(r.id)?'checked':''} value="${r.id}" style="accent-color:var(--accent);flex-shrink:0"
onchange="${selectedReports.has(r.id)?'selectedReports.delete':'selectedReports.add'}(${r.id}); renderReportList('${q}')">
<span><strong>#${r.id}</strong> ${r.charger_id||''}${(r.issue_types||[]).join(', ')}</span>
<span><strong>#${r.seq}</strong> ${r.charger_id||''}${(r.issue_types||[]).join(', ')}</span>
</label>`).join('') || '<div style="color:var(--gray4)">검색 결과 없음</div>';
}