1차완료
This commit is contained in:
@@ -1,8 +1,40 @@
|
||||
<!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>
|
||||
<!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>
|
||||
.imp-grid { display:grid; grid-template-columns:1fr 1fr; gap:18px; }
|
||||
.info-dl { display:grid; grid-template-columns:80px 1fr; gap:8px 14px; font-size:13px; align-items:start; }
|
||||
.info-dl dt { color:var(--gray4); font-weight:600; padding-top:1px; }
|
||||
.info-dl dd { word-break:break-word; }
|
||||
.report-link { display:flex; align-items:center; gap:10px; padding:10px 12px;
|
||||
border:1px solid var(--gray2); border-radius:8px; margin-bottom:6px;
|
||||
cursor:pointer; font-size:13px; color:var(--navy); text-decoration:none; transition:background .15s; }
|
||||
.report-link:hover { background:var(--gray1); }
|
||||
.report-link-num { font-weight:700; color:var(--blue); flex-shrink:0; }
|
||||
.file-link { display:flex; align-items:center; gap:8px; padding:9px 12px;
|
||||
border:1px solid var(--gray2); border-radius:8px; margin-bottom:6px;
|
||||
color:var(--navy); text-decoration:none; font-size:13px; transition:background .15s; overflow:hidden; }
|
||||
.file-link:hover { background:var(--gray1); }
|
||||
.file-link span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.page-header { display:flex; align-items:center; gap:10px; margin-bottom:18px; }
|
||||
.page-header h2 { font-size:17px; font-weight:700; color:var(--navy); flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.status-form { display:grid; grid-template-columns:1fr 1fr; gap:14px; }
|
||||
@media(max-width:768px) {
|
||||
.imp-grid { grid-template-columns:1fr; }
|
||||
.status-form { grid-template-columns:1fr; }
|
||||
.info-dl { grid-template-columns:72px 1fr; gap:7px 10px; }
|
||||
}
|
||||
</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>
|
||||
@@ -14,105 +46,139 @@
|
||||
<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">
|
||||
<div style="margin-bottom:14px;display:flex;gap:10px;align-items:center;">
|
||||
<a href="/pages/admin/improvements.html" class="btn btn-outline btn-sm">← 목록</a>
|
||||
<h2 id="pageTitle" style="font-size:17px;font-weight:700;color:var(--navy)">개선항목 상세</h2>
|
||||
<div class="page-header">
|
||||
<a href="/pages/admin/improvements.html" class="btn btn-outline btn-sm" style="flex-shrink:0">← 목록</a>
|
||||
<h2 id="pageTitle">개선항목 상세</h2>
|
||||
</div>
|
||||
<div id="content"></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');
|
||||
}
|
||||
|
||||
const id = new URLSearchParams(location.search).get('id');
|
||||
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 STATUS_OPTIONS = ['registered','reviewing','developing','deployed','done'];
|
||||
const STATUS_LABEL = {registered:'등록',reviewing:'검토중',developing:'개발중',deployed:'배포완료',done:'완료'};
|
||||
const STATUS_LABEL = {registered:'등록',reviewing:'검토중',developing:'개발중',deployed:'배포완료',done:'완료'};
|
||||
|
||||
async function load() {
|
||||
const imp = await API.get('/improvements/'+id);
|
||||
document.getElementById('pageTitle').textContent = `개선항목 #${imp.id}`;
|
||||
document.getElementById('pageTitle').textContent = `#${imp.id} ${imp.title}`;
|
||||
|
||||
document.getElementById('content').innerHTML = `
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:18px;">
|
||||
<div class="imp-grid">
|
||||
<!-- 기본 정보 -->
|
||||
<div class="card">
|
||||
<div class="card-title">📋 기본 정보</div>
|
||||
<table class="no-hover" style="font-size:13px">
|
||||
<tr><td style="color:var(--gray4);width:90px">제목</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><strong>${imp.manufacturer_company||'-'}</strong><br>${imp.manufacturer_name||''}</td></tr>
|
||||
<tr><td style="color:var(--gray4)">등록자</td><td>${imp.created_by_name||'-'}</td></tr>
|
||||
<tr><td style="color:var(--gray4)">등록일시</td><td>${Auth.fmtDt(imp.created_at)}</td></tr>
|
||||
<tr><td style="color:var(--gray4)">배포 목표일</td><td>${imp.sw_deploy_target||'-'}</td></tr>
|
||||
<tr><td style="color:var(--gray4)">실제 배포일</td><td>${imp.sw_deployed_at||'-'}</td></tr>
|
||||
<tr><td style="color:var(--gray4)">현재 상태</td><td>${Auth.statusBadge(imp.status)}</td></tr>
|
||||
</table>
|
||||
<div style="margin-top:12px">
|
||||
<dl class="info-dl">
|
||||
<dt>제목</dt> <dd><strong>${imp.title}</strong></dd>
|
||||
<dt>분류</dt> <dd>${CAT[imp.category]||imp.category}</dd>
|
||||
<dt>우선순위</dt> <dd>${PRI[imp.priority]||imp.priority}</dd>
|
||||
<dt>관련 부품</dt> <dd>${imp.part_name||'-'}</dd>
|
||||
<dt>담당 업체</dt> <dd><strong>${imp.manufacturer_name||'-'}</strong></dd>
|
||||
<dt>등록자</dt> <dd>${imp.created_by_name||'-'}</dd>
|
||||
<dt>등록일시</dt> <dd>${Auth.fmtDt(imp.created_at)}</dd>
|
||||
<dt>배포 목표일</dt><dd>${imp.sw_deploy_target||'-'}</dd>
|
||||
<dt>실제 배포일</dt><dd>${imp.sw_deployed_at||'-'}</dd>
|
||||
<dt>상태</dt> <dd>${Auth.statusBadge(imp.status)}</dd>
|
||||
</dl>
|
||||
<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:12px;border-radius:6px;font-size:13px;white-space:pre-wrap">${imp.description}</div>
|
||||
<div style="background:var(--gray1);padding:12px;border-radius:6px;font-size:13px;white-space:pre-wrap;line-height:1.7">${imp.description}</div>
|
||||
</div>
|
||||
${imp.manufacturer_memo?`<div style="margin-top:12px"><div style="font-size:12px;font-weight:700;color:var(--orange);margin-bottom:6px">제조사 메모</div><div style="background:#FFF5E6;padding:12px;border-radius:6px;font-size:13px">${imp.manufacturer_memo}</div></div>`:''}
|
||||
${imp.manufacturer_memo ? `
|
||||
<div style="margin-top:12px">
|
||||
<div style="font-size:12px;font-weight:700;color:var(--orange);margin-bottom:6px">제조사 메모</div>
|
||||
<div style="background:#FFF5E6;padding:12px;border-radius:6px;font-size:13px;line-height:1.6">${imp.manufacturer_memo}</div>
|
||||
</div>` : ''}
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-title">📎 연결된 AS 신고</div>
|
||||
${imp.report_ids.length ? imp.report_ids.map(rid=>`
|
||||
<div onclick="location.href='/pages/admin/report-detail.html?id=${rid}'"
|
||||
style="padding:8px;border:1px solid var(--gray2);border-radius:6px;margin-bottom:6px;cursor:pointer;font-size:13px">
|
||||
신고 #${rid}
|
||||
</div>`).join('') : '<div class="alert alert-info">연결된 신고 없음</div>'}
|
||||
|
||||
<div class="card-title" style="margin-top:16px">📁 첨부 파일</div>
|
||||
${imp.attachments.length ? imp.attachments.map(a=>`
|
||||
<a href="${a.path}" target="_blank" class="btn btn-outline btn-sm" style="margin-bottom:6px;display:block">
|
||||
📄 ${a.name||a.path.split('/').pop()}
|
||||
</a>`).join('') : '<div style="font-size:13px;color:var(--gray4)">첨부 파일 없음</div>'}
|
||||
<!-- 연결 AS + 첨부 -->
|
||||
<div>
|
||||
<div class="card">
|
||||
<div class="card-title">📎 연결된 AS 신고</div>
|
||||
${(imp.report_links||[]).length
|
||||
? (imp.report_links||[]).map(r => `
|
||||
<a class="report-link" href="/pages/admin/report-detail.html?id=${r.id}">
|
||||
<span class="report-link-num">#${r.seq}</span>
|
||||
<span style="color:var(--gray4);font-size:12px">신고 상세 보기 →</span>
|
||||
</a>`).join('')
|
||||
: '<div class="alert alert-info" style="margin:0">연결된 신고 없음</div>'}
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">📁 첨부 파일</div>
|
||||
${imp.attachments.length
|
||||
? imp.attachments.map(a => `
|
||||
<a class="file-link" href="${a.path}" target="_blank">
|
||||
<span style="flex-shrink:0">📄</span>
|
||||
<span>${a.name||a.path.split('/').pop()}</span>
|
||||
</a>`).join('')
|
||||
: '<div style="font-size:13px;color:var(--gray4)">첨부 파일 없음</div>'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 상태 변경 -->
|
||||
<div class="card" style="margin-top:0">
|
||||
<div class="card">
|
||||
<div class="card-title">🔄 상태 변경</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>상태 변경</label>
|
||||
<div class="status-form">
|
||||
<div class="form-group" style="margin:0">
|
||||
<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>
|
||||
<div class="form-group" style="margin:0">
|
||||
<label>SW 실제 배포일 <span style="color:var(--gray4);font-weight:400">(배포완료 시)</span></label>
|
||||
<input type="date" id="deployedAt" value="${imp.sw_deployed_at||''}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group"><label>변경 메모</label><input type="text" id="changeMemo" placeholder="상태 변경 사유 또는 메모"></div>
|
||||
<button class="btn btn-primary" onclick="changeStatus()">상태 저장</button>
|
||||
<div class="form-group" style="margin-top:12px">
|
||||
<label>변경 메모</label>
|
||||
<input type="text" id="changeMemo" placeholder="상태 변경 사유 또는 메모">
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="changeStatus()">저장</button>
|
||||
</div>
|
||||
|
||||
<!-- 이력 로그 -->
|
||||
<div class="card" style="margin-top:0">
|
||||
<!-- 변경 이력 -->
|
||||
<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>'}
|
||||
${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 changeStatus() {
|
||||
const status = document.getElementById('newStatus').value;
|
||||
const memo = document.getElementById('changeMemo').value;
|
||||
const date = document.getElementById('deployedAt').value;
|
||||
const fd = new FormData();
|
||||
fd.append('status', status); fd.append('memo', memo);
|
||||
fd.append('status', document.getElementById('newStatus').value);
|
||||
fd.append('memo', document.getElementById('changeMemo').value);
|
||||
const date = document.getElementById('deployedAt').value;
|
||||
if (date) fd.append('sw_deployed_at', date);
|
||||
await API.patch('/improvements/'+id+'/status', fd);
|
||||
load();
|
||||
}
|
||||
|
||||
load();
|
||||
</script></div></div></body></html>
|
||||
</script>
|
||||
</body></html>
|
||||
|
||||
Reference in New Issue
Block a user