초기 커밋 - EV AS 관리 시스템
This commit is contained in:
59
frontend/static/pages/manufacturer/dashboard.html
Normal file
59
frontend/static/pages/manufacturer/dashboard.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!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"></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">메뉴</div>
|
||||
<a href="/pages/manufacturer/dashboard.html" class="active">📋 개선항목 목록</a>
|
||||
</div>
|
||||
<div class="main">
|
||||
<h2 style="font-size:18px;font-weight:700;color:var(--navy);margin-bottom:18px">배정된 개선항목</h2>
|
||||
<div class="stats" id="stats"></div>
|
||||
<div class="card">
|
||||
<div style="display:flex;gap:10px;margin-bottom:14px;flex-wrap:wrap;">
|
||||
<select id="fStatus" style="width:auto">
|
||||
<option value="">전체</option>
|
||||
<option value="registered">등록</option><option value="reviewing">검토중</option>
|
||||
<option value="developing">개발중</option><option value="deployed">배포완료</option>
|
||||
<option value="done">완료</option>
|
||||
</select>
|
||||
<button class="btn btn-outline btn-sm" onclick="load()">검색</button>
|
||||
</div>
|
||||
<div class="tbl-wrap"><table>
|
||||
<thead><tr><th>#</th><th>제목</th><th>분류</th><th>우선순위</th><th>연결AS</th><th>상태</th><th>SW배포목표일</th><th>등록일</th></tr></thead>
|
||||
<tbody id="tbody"></tbody>
|
||||
</table></div>
|
||||
<div id="empty" class="alert alert-info" style="display:none">배정된 개선항목이 없습니다.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/js/api.js"></script><script src="/js/auth.js"></script>
|
||||
<script>
|
||||
Auth.require(['manufacturer']); Auth.renderNav(document.getElementById('navUser'));
|
||||
const CAT={sw:'SW',hw:'HW',ui:'UI',firmware:'펌웨어',other:'기타'};
|
||||
const PRI={urgent:'🔴 긴급',high:'🟠 높음',normal:'🟡 보통',low:'🟢 낮음'};
|
||||
|
||||
async function load() {
|
||||
const imps = await API.get('/improvements?status='+document.getElementById('fStatus').value);
|
||||
const counts = {registered:0,reviewing:0,developing:0,deployed:0,done:0};
|
||||
imps.forEach(i => { if (counts[i.status]!==undefined) counts[i.status]++; });
|
||||
document.getElementById('stats').innerHTML = `
|
||||
<div class="stat"><div class="stat-num">${imps.length}</div><div class="stat-label">전체</div></div>
|
||||
<div class="stat warn"><div class="stat-num">${counts.registered+counts.reviewing}</div><div class="stat-label">검토 필요</div></div>
|
||||
<div class="stat warn"><div class="stat-num">${counts.developing}</div><div class="stat-label">개발중</div></div>
|
||||
<div class="stat good"><div class="stat-num">${counts.deployed+counts.done}</div><div class="stat-label">완료/배포</div></div>`;
|
||||
document.getElementById('empty').style.display = imps.length ? 'none' : 'block';
|
||||
document.getElementById('tbody').innerHTML = imps.map(i=>`
|
||||
<tr onclick="location.href='/pages/manufacturer/improvement.html?id=${i.id}'">
|
||||
<td>#${i.id}</td>
|
||||
<td><strong>${i.title}</strong></td>
|
||||
<td>${CAT[i.category]||i.category}</td>
|
||||
<td>${PRI[i.priority]||i.priority}</td>
|
||||
<td><span class="badge s-pending">${i.report_count}건</span></td>
|
||||
<td>${Auth.statusBadge(i.status)}</td>
|
||||
<td>${i.sw_deploy_target||'-'}</td>
|
||||
<td>${Auth.fmtDt(i.created_at)}</td>
|
||||
</tr>`).join('');
|
||||
}
|
||||
load();
|
||||
</script></body></html>
|
||||
102
frontend/static/pages/manufacturer/improvement.html
Normal file
102
frontend/static/pages/manufacturer/improvement.html
Normal file
@@ -0,0 +1,102 @@
|
||||
<!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"></head>
|
||||
<body>
|
||||
<nav class="nav"><span class="nav-brand">⚡ EV AS — 제조사</span><div id="navUser"></div></nav>
|
||||
<div class="main" style="max-width:760px;margin:0 auto;">
|
||||
<div style="margin-bottom:14px;display:flex;gap:10px;align-items:center;">
|
||||
<a href="/pages/manufacturer/dashboard.html" class="btn btn-outline btn-sm">← 목록</a>
|
||||
<h2 id="pageTitle" style="font-size:17px;font-weight:700;color:var(--navy)">개선항목 상세</h2>
|
||||
</div>
|
||||
<div id="content"></div>
|
||||
</div>
|
||||
<script src="/js/api.js"></script><script src="/js/auth.js"></script>
|
||||
<script>
|
||||
Auth.require(['manufacturer']); Auth.renderNav(document.getElementById('navUser'));
|
||||
const id = new URLSearchParams(location.search).get('id');
|
||||
const CAT={sw:'SW개선',hw:'HW개선',ui:'UI개선',firmware:'펌웨어',other:'기타'};
|
||||
const STATUS_OPTIONS=['reviewing','developing','deployed'];
|
||||
const STATUS_LABEL={registered:'등록',reviewing:'검토중',developing:'개발중',deployed:'배포완료',done:'완료'};
|
||||
|
||||
async function load() {
|
||||
const imp = await API.get('/improvements/'+id);
|
||||
document.getElementById('pageTitle').textContent = `개선항목 #${imp.id} — ${imp.title}`;
|
||||
document.getElementById('content').innerHTML = `
|
||||
<div class="card">
|
||||
<div class="card-title">📋 개선 요청 내용</div>
|
||||
<table class="no-hover" style="font-size:13px">
|
||||
<tr><td style="color:var(--gray4);width:100px">제목</td><td><strong>${imp.title}</strong></td></tr>
|
||||
<tr><td style="color:var(--gray4)">분류</td><td>${CAT[imp.category]||imp.category}</td></tr>
|
||||
<tr><td style="color:var(--gray4)">우선순위</td><td>${imp.priority}</td></tr>
|
||||
<tr><td style="color:var(--gray4)">관련 부품</td><td>${imp.part_name||'-'}</td></tr>
|
||||
<tr><td style="color:var(--gray4)">현재 상태</td><td>${Auth.statusBadge(imp.status)}</td></tr>
|
||||
<tr><td style="color:var(--gray4)">배포 목표일</td><td>${imp.sw_deploy_target||'-'}</td></tr>
|
||||
<tr><td style="color:var(--gray4)">등록일시</td><td>${Auth.fmtDt(imp.created_at)}</td></tr>
|
||||
</table>
|
||||
<div style="margin-top:14px">
|
||||
<div style="font-size:12px;font-weight:700;color:var(--navy2);margin-bottom:6px">개선 요청 내용</div>
|
||||
<div style="background:var(--gray1);padding:14px;border-radius:6px;font-size:13px;white-space:pre-wrap;line-height:1.7">${imp.description}</div>
|
||||
</div>
|
||||
${imp.attachments.length?`
|
||||
<div style="margin-top:14px">
|
||||
<div style="font-size:12px;font-weight:700;color:var(--navy2);margin-bottom:6px">첨부 보고서</div>
|
||||
${imp.attachments.map(a=>`<a href="${a.path}" target="_blank" class="btn btn-outline btn-sm" style="margin-right:6px;margin-bottom:6px">📄 ${a.name||'파일'}</a>`).join('')}
|
||||
</div>`:''}
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">🔗 관련 AS 신고</div>
|
||||
${imp.report_ids.length?imp.report_ids.map(rid=>`
|
||||
<div style="padding:8px;border:1px solid var(--gray2);border-radius:6px;margin-bottom:6px;font-size:13px">
|
||||
신고 #${rid}
|
||||
</div>`).join(''):'<div style="color:var(--gray4);font-size:13px">연결된 신고 없음</div>'}
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">📝 진행 상태 업데이트</div>
|
||||
<div class="alert alert-info" style="margin-bottom:14px">
|
||||
아래에서 진행 상태를 업데이트하고 저장해 주세요. 변경 이력이 자동으로 기록됩니다.
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>진행 상태 변경</label>
|
||||
<select id="newStatus">
|
||||
${STATUS_OPTIONS.map(s=>`<option value="${s}" ${imp.status===s?'selected':''}>${STATUS_LABEL[s]}</option>`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>SW 실제 배포일 (배포완료 선택 시)</label>
|
||||
<input type="date" id="deployedAt" value="${imp.sw_deployed_at||''}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>진행 메모</label>
|
||||
<textarea id="memo" rows="3" placeholder="현재 진행 상황, 예상 완료일, 이슈 등을 기재해 주세요.">${imp.manufacturer_memo||''}</textarea>
|
||||
</div>
|
||||
<div id="saveOk" class="alert alert-success" style="display:none">저장되었습니다.</div>
|
||||
<button class="btn btn-primary" onclick="save()">저장</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">📜 변경 이력</div>
|
||||
${imp.logs.length?`<div class="timeline">${imp.logs.map(l=>`
|
||||
<div class="tl-item">
|
||||
<div class="tl-time">${Auth.fmtDt(l.changed_at)} — ${l.by||'시스템'}</div>
|
||||
<div class="tl-text">${l.old_status?`${STATUS_LABEL[l.old_status]||l.old_status} → `:''}${STATUS_LABEL[l.new_status]||l.new_status}${l.memo?` / ${l.memo}`:''}</div>
|
||||
</div>`).join('')}</div>`:'<div style="color:var(--gray4);font-size:13px">이력 없음</div>'}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function save() {
|
||||
const fd = new FormData();
|
||||
fd.append('status', document.getElementById('newStatus').value);
|
||||
fd.append('memo', document.getElementById('memo').value);
|
||||
fd.append('manufacturer_memo', document.getElementById('memo').value);
|
||||
const d = document.getElementById('deployedAt').value;
|
||||
if (d) fd.append('sw_deployed_at', d);
|
||||
await API.patch('/improvements/'+id+'/status', fd);
|
||||
const ok = document.getElementById('saveOk');
|
||||
ok.style.display = 'block';
|
||||
setTimeout(() => { ok.style.display='none'; load(); }, 1200);
|
||||
}
|
||||
load();
|
||||
</script></body></html>
|
||||
Reference in New Issue
Block a user