초기 커밋 - EV AS 관리 시스템
This commit is contained in:
70
frontend/static/pages/mechanic/dashboard.html
Normal file
70
frontend/static/pages/mechanic/dashboard.html
Normal file
@@ -0,0 +1,70 @@
|
||||
<!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/mechanic/dashboard.html" class="active">📋 AS 목록</a>
|
||||
<a href="/pages/mechanic/scan.html">📷 QR 스캔</a>
|
||||
<a href="/pages/mechanic/history.html">🗂 처리 이력</a>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;">
|
||||
<h2 style="font-size:18px;font-weight:700;color:var(--navy)">AS 처리 목록</h2>
|
||||
<a href="/pages/mechanic/scan.html" class="btn btn-accent">📷 QR 스캔하여 조치 시작</a>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div style="display:flex;gap:10px;margin-bottom:14px;flex-wrap:wrap;">
|
||||
<select id="filterStatus" style="width:auto">
|
||||
<option value="">전체 상태</option>
|
||||
<option value="pending">접수</option>
|
||||
<option value="in_progress">처리중</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>문제유형</th><th>신고일시</th><th>상태</th><th>조치</th></tr></thead>
|
||||
<tbody id="tbody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="empty" class="alert alert-info" style="display:none">처리 대기 중인 AS가 없습니다.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/js/api.js"></script><script src="/js/auth.js"></script>
|
||||
<script>
|
||||
Auth.require(['mechanic','admin']);
|
||||
Auth.renderNav(document.getElementById('navUser'));
|
||||
|
||||
async function load() {
|
||||
const status = document.getElementById('filterStatus').value;
|
||||
const rows = await API.get('/repairs/pending' + (status ? '?status='+status : ''));
|
||||
const tbody = document.getElementById('tbody');
|
||||
if (!rows.length) { tbody.innerHTML=''; document.getElementById('empty').style.display='block'; return; }
|
||||
document.getElementById('empty').style.display='none';
|
||||
tbody.innerHTML = rows.map(r => `
|
||||
<tr onclick="location.href='/pages/mechanic/repair.html?charger_id=${r.charger_id}&report_id=${r.id}'">
|
||||
<td>#${r.id}</td>
|
||||
<td><strong>${r.charger_id}</strong><br><small>${r.charger_name||''}</small></td>
|
||||
<td>${r.station_name||'-'}</td>
|
||||
<td>${r.charger_type||'-'}</td>
|
||||
<td>${(r.issue_types||[]).join(', ')}</td>
|
||||
<td>${Auth.fmtDt(r.reported_at)}</td>
|
||||
<td>${Auth.statusBadge(r.status)}</td>
|
||||
<td><a class="btn btn-primary btn-sm" href="/pages/mechanic/repair.html?charger_id=${r.charger_id}&report_id=${r.id}" onclick="event.stopPropagation()">조치</a></td>
|
||||
</tr>`).join('');
|
||||
}
|
||||
load();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
184
frontend/static/pages/mechanic/repair.html
Normal file
184
frontend/static/pages/mechanic/repair.html
Normal file
@@ -0,0 +1,184 @@
|
||||
<!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>
|
||||
.upload-area{border:2px dashed var(--gray3);border-radius:8px;padding:12px;text-align:center;cursor:pointer;color:var(--gray4);font-size:12px;transition:border-color .15s;margin-bottom:6px;}
|
||||
.upload-area:hover{border-color:var(--accent);}
|
||||
.photo-preview{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;}
|
||||
.photo-preview img{width:72px;height:72px;object-fit:cover;border-radius:6px;border:1px solid var(--gray3);}
|
||||
.photo-info{font-size:11px;margin-top:4px;min-height:14px;color:var(--gray4);}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="nav">
|
||||
<span class="nav-brand">⚡ EV AS 관리</span>
|
||||
<div id="navUser"></div>
|
||||
</nav>
|
||||
<div class="main" style="max-width:640px;margin:0 auto;">
|
||||
<div style="margin-bottom:14px;">
|
||||
<a href="/pages/mechanic/dashboard.html" class="btn btn-outline btn-sm">← 목록으로</a>
|
||||
</div>
|
||||
<div id="chargerCard" class="card"></div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">📋 동일 충전기 신고 목록 (중복 선택 가능)</div>
|
||||
<div id="reportList"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">🔧 조치 내역 입력</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>조치 유형 <span class="req">*</span></label>
|
||||
<div class="check-group" id="repairTypes">
|
||||
<label class="check-item"><input type="checkbox" value="부품교체"> 부품 교체</label>
|
||||
<label class="check-item"><input type="checkbox" value="재시작"> 재시작</label>
|
||||
<label class="check-item"><input type="checkbox" value="설정변경"> 설정 변경</label>
|
||||
<label class="check-item"><input type="checkbox" value="기타"> 기타</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>조치 상세 내용 <span class="req">*</span></label>
|
||||
<textarea id="description" rows="4" placeholder="조치한 내용을 상세히 입력하세요."></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 조치 전 사진 -->
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>📷 조치 전 사진 <span style="font-size:11px;color:var(--gray4);font-weight:400">(여러 장 가능)</span></label>
|
||||
<label class="upload-area" for="photosBefore">📷 촬영 또는 앨범 선택</label>
|
||||
<input type="file" id="photosBefore" accept="image/*" multiple style="display:none">
|
||||
<div class="photo-preview" id="previewBefore"></div>
|
||||
<div class="photo-info" id="infoBefore"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>📷 조치 후 사진 <span style="font-size:11px;color:var(--gray4);font-weight:400">(여러 장 가능)</span></label>
|
||||
<label class="upload-area" for="photosAfter">📷 촬영 또는 앨범 선택</label>
|
||||
<input type="file" id="photosAfter" accept="image/*" multiple style="display:none">
|
||||
<div class="photo-preview" id="previewAfter"></div>
|
||||
<div class="photo-info" id="infoAfter"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>처리 상태 <span class="req">*</span></label>
|
||||
<select id="resultStatus">
|
||||
<option value="done">✅ 처리 완료</option>
|
||||
<option value="waiting">⏳ 부품 대기</option>
|
||||
<option value="revisit">🔄 재방문 필요</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info" style="margin-bottom:14px;">
|
||||
🕐 조치 시작 시간: <strong id="startedAt"></strong> (자동 기록)
|
||||
</div>
|
||||
|
||||
<div id="formErr" class="alert alert-danger" style="display:none"></div>
|
||||
<button class="btn btn-primary btn-lg" id="submitBtn">조치 완료 저장</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/api.js"></script>
|
||||
<script src="/js/auth.js"></script>
|
||||
<script src="/js/imageCompress.js"></script>
|
||||
<script>
|
||||
Auth.require(['mechanic','admin']);
|
||||
Auth.renderNav(document.getElementById('navUser'));
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
const chargerId = params.get('charger_id');
|
||||
const initReportId = params.get('report_id');
|
||||
const startTime = new Date();
|
||||
document.getElementById('startedAt').textContent = startTime.toLocaleString('ko-KR');
|
||||
|
||||
const selectedReports = new Set();
|
||||
if (initReportId) selectedReports.add(parseInt(initReportId));
|
||||
|
||||
async function load() {
|
||||
const charger = await API.get('/chargers/' + chargerId);
|
||||
document.getElementById('chargerCard').innerHTML = `
|
||||
<div class="card-title">⚡ 충전기 정보</div>
|
||||
<div class="form-row">
|
||||
<div><label style="font-size:11px;color:var(--gray4)">충전기ID</label><strong>${charger.id}</strong></div>
|
||||
<div><label style="font-size:11px;color:var(--gray4)">충전기명</label><strong>${charger.name}</strong></div>
|
||||
<div><label style="font-size:11px;color:var(--gray4)">충전소</label><strong>${charger.station_name}</strong></div>
|
||||
<div><label style="font-size:11px;color:var(--gray4)">CPO</label><strong>${charger.cpo_name || '-'}</strong></div>
|
||||
</div>`;
|
||||
|
||||
const reports = await API.get('/repairs/charger/' + chargerId + '/open');
|
||||
const list = document.getElementById('reportList');
|
||||
if (!reports.length) {
|
||||
list.innerHTML = '<div class="alert alert-info">미처리 신고가 없습니다.</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = reports.map(r => `
|
||||
<label style="display:flex;gap:10px;align-items:flex-start;padding:10px;border:1px solid var(--gray2);border-radius:7px;margin-bottom:8px;cursor:pointer;background:${selectedReports.has(r.id)?'#E3EDFF':'white'}">
|
||||
<input type="checkbox" ${selectedReports.has(r.id)?'checked':''}
|
||||
value="${r.id}"
|
||||
style="accent-color:var(--accent);margin-top:2px;flex-shrink:0"
|
||||
onchange="toggleReport(${r.id}, this.checked, this.closest('label'))">
|
||||
<div>
|
||||
<div><strong>#${r.id}</strong> ${Auth.statusBadge(r.status)}</div>
|
||||
<div style="font-size:12px;color:var(--text2);margin-top:2px">${(r.issue_types||[]).join(', ')}</div>
|
||||
<div style="font-size:11px;color:var(--gray4)">${Auth.fmtDt(r.reported_at)}</div>
|
||||
${r.photos.length
|
||||
? `<div class="photo-preview">${r.photos.map(p=>`<img src="${p}">`).join('')}</div>`
|
||||
: ''}
|
||||
</div>
|
||||
</label>`).join('');
|
||||
}
|
||||
|
||||
function toggleReport(id, checked, label) {
|
||||
if (checked) { selectedReports.add(id); label.style.background = '#E3EDFF'; }
|
||||
else { selectedReports.delete(id); label.style.background = 'white'; }
|
||||
}
|
||||
|
||||
// 이미지 압축 + 다중 선택 프리뷰
|
||||
ImageCompressor.setupPreview('photosBefore', 'previewBefore', 'infoBefore');
|
||||
ImageCompressor.setupPreview('photosAfter', 'previewAfter', 'infoAfter');
|
||||
|
||||
document.getElementById('submitBtn').addEventListener('click', async () => {
|
||||
const rids = [...selectedReports];
|
||||
if (!rids.length) { showErr('신고를 1건 이상 선택해 주세요.'); return; }
|
||||
|
||||
const types = [...document.querySelectorAll('#repairTypes input:checked')].map(c => c.value);
|
||||
if (!types.length) { showErr('조치 유형을 1개 이상 선택해 주세요.'); return; }
|
||||
|
||||
const desc = document.getElementById('description').value.trim();
|
||||
if (!desc) { showErr('조치 상세 내용을 입력해 주세요.'); return; }
|
||||
|
||||
document.getElementById('submitBtn').disabled = true;
|
||||
document.getElementById('submitBtn').textContent = '저장 중...';
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('report_ids', JSON.stringify(rids));
|
||||
fd.append('repair_types', JSON.stringify(types));
|
||||
fd.append('description', desc);
|
||||
fd.append('result_status', document.getElementById('resultStatus').value);
|
||||
Array.from(document.getElementById('photosBefore').files).forEach(f => fd.append('photos_before', f));
|
||||
Array.from(document.getElementById('photosAfter').files).forEach(f => fd.append('photos_after', f));
|
||||
|
||||
try {
|
||||
await API.post('/repairs', fd);
|
||||
alert('✅ 조치 완료 저장되었습니다.');
|
||||
location.href = '/pages/mechanic/dashboard.html';
|
||||
} catch(e) {
|
||||
showErr(e.message);
|
||||
document.getElementById('submitBtn').disabled = false;
|
||||
document.getElementById('submitBtn').textContent = '조치 완료 저장';
|
||||
}
|
||||
});
|
||||
|
||||
function showErr(msg) {
|
||||
const el = document.getElementById('formErr');
|
||||
el.textContent = msg; el.style.display = 'block';
|
||||
}
|
||||
|
||||
load();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
160
frontend/static/pages/mechanic/repair.html.bak
Normal file
160
frontend/static/pages/mechanic/repair.html.bak
Normal file
@@ -0,0 +1,160 @@
|
||||
<!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:640px;margin:0 auto;">
|
||||
<div style="margin-bottom:14px;">
|
||||
<a href="/pages/mechanic/dashboard.html" class="btn btn-outline btn-sm">← 목록으로</a>
|
||||
</div>
|
||||
<div id="chargerCard" class="card"></div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">📋 동일 충전기 신고 목록 (중복 선택 가능)</div>
|
||||
<div id="reportList"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">🔧 조치 내역 입력</div>
|
||||
<div class="form-group">
|
||||
<label>조치 유형 <span class="req">*</span></label>
|
||||
<div class="check-group" id="repairTypes">
|
||||
<label class="check-item"><input type="checkbox" value="부품교체"> 부품 교체</label>
|
||||
<label class="check-item"><input type="checkbox" value="재시작"> 재시작</label>
|
||||
<label class="check-item"><input type="checkbox" value="설정변경"> 설정 변경</label>
|
||||
<label class="check-item"><input type="checkbox" value="기타"> 기타</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>조치 상세 내용 <span class="req">*</span></label>
|
||||
<textarea id="description" rows="4" placeholder="조치한 내용을 상세히 입력하세요."></textarea>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>📷 조치 전 사진</label>
|
||||
<input type="file" id="photosBefore" accept="image/*" capture="environment" multiple>
|
||||
<div class="photo-preview" id="previewBefore"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>📷 조치 후 사진</label>
|
||||
<input type="file" id="photosAfter" accept="image/*" capture="environment" multiple>
|
||||
<div class="photo-preview" id="previewAfter"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>처리 상태 <span class="req">*</span></label>
|
||||
<select id="resultStatus">
|
||||
<option value="done">✅ 처리 완료</option>
|
||||
<option value="waiting">⏳ 부품 대기</option>
|
||||
<option value="revisit">🔄 재방문 필요</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="alert alert-info" style="margin-bottom:14px;">
|
||||
🕐 조치 시작 시간: <strong id="startedAt"></strong> (자동 기록)
|
||||
</div>
|
||||
<div id="formErr" class="alert alert-danger" style="display:none"></div>
|
||||
<button class="btn btn-primary btn-lg" id="submitBtn">조치 완료 저장</button>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/js/api.js"></script><script src="/js/auth.js"></script>
|
||||
<script>
|
||||
Auth.require(['mechanic','admin']);
|
||||
Auth.renderNav(document.getElementById('navUser'));
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
const chargerId = params.get('charger_id');
|
||||
const initReportId = params.get('report_id');
|
||||
const startTime = new Date();
|
||||
document.getElementById('startedAt').textContent = startTime.toLocaleString('ko-KR');
|
||||
|
||||
const selectedReports = new Set();
|
||||
if (initReportId) selectedReports.add(parseInt(initReportId));
|
||||
|
||||
async function load() {
|
||||
const charger = await API.get('/chargers/' + chargerId);
|
||||
document.getElementById('chargerCard').innerHTML = `
|
||||
<div class="card-title">⚡ 충전기 정보</div>
|
||||
<div class="form-row">
|
||||
<div><label style="font-size:11px;color:var(--gray4)">충전기ID</label><strong>${charger.id}</strong></div>
|
||||
<div><label style="font-size:11px;color:var(--gray4)">충전기명</label><strong>${charger.name}</strong></div>
|
||||
<div><label style="font-size:11px;color:var(--gray4)">충전소</label><strong>${charger.station_name}</strong></div>
|
||||
<div><label style="font-size:11px;color:var(--gray4)">CPO</label><strong>${charger.cpo_name||'-'}</strong></div>
|
||||
</div>`;
|
||||
|
||||
const reports = await API.get('/repairs/charger/' + chargerId + '/open');
|
||||
const list = document.getElementById('reportList');
|
||||
if (!reports.length) { list.innerHTML='<div class="alert alert-info">미처리 신고가 없습니다.</div>'; return; }
|
||||
list.innerHTML = reports.map(r => `
|
||||
<label style="display:flex;gap:10px;align-items:flex-start;padding:10px;border:1px solid var(--gray2);border-radius:7px;margin-bottom:8px;cursor:pointer;background:${selectedReports.has(r.id)?'#E3EDFF':'white'}">
|
||||
<input type="checkbox" ${selectedReports.has(r.id)?'checked':''} value="${r.id}"
|
||||
style="accent-color:var(--accent);margin-top:2px;flex-shrink:0"
|
||||
onchange="toggleReport(${r.id}, this.checked, this.closest('label'))">
|
||||
<div>
|
||||
<div><strong>#${r.id}</strong> ${Auth.statusBadge(r.status)}</div>
|
||||
<div style="font-size:12px;color:var(--text2);margin-top:2px">${(r.issue_types||[]).join(', ')}</div>
|
||||
<div style="font-size:11px;color:var(--gray4)">${Auth.fmtDt(r.reported_at)}</div>
|
||||
${r.photos.length?`<div class="photo-preview">${r.photos.map(p=>`<img src="${p}">`).join('')}</div>`:''}
|
||||
</div>
|
||||
</label>`).join('');
|
||||
}
|
||||
|
||||
function toggleReport(id, checked, label) {
|
||||
if (checked) { selectedReports.add(id); label.style.background='#E3EDFF'; }
|
||||
else { selectedReports.delete(id); label.style.background='white'; }
|
||||
}
|
||||
|
||||
['photosBefore','photosAfter'].forEach(id => {
|
||||
const prevId = id === 'photosBefore' ? 'previewBefore' : 'previewAfter';
|
||||
document.getElementById(id).addEventListener('change', function() {
|
||||
const prev = document.getElementById(prevId);
|
||||
prev.innerHTML = '';
|
||||
Array.from(this.files).forEach(f => {
|
||||
const img = document.createElement('img'); img.src = URL.createObjectURL(f); prev.appendChild(img);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('submitBtn').addEventListener('click', async () => {
|
||||
const rids = [...selectedReports];
|
||||
if (!rids.length) { showErr('신고를 1건 이상 선택해 주세요.'); return; }
|
||||
const types = [...document.querySelectorAll('#repairTypes input:checked')].map(c => c.value);
|
||||
if (!types.length) { showErr('조치 유형을 1개 이상 선택해 주세요.'); return; }
|
||||
const desc = document.getElementById('description').value.trim();
|
||||
if (!desc) { showErr('조치 상세 내용을 입력해 주세요.'); return; }
|
||||
|
||||
document.getElementById('submitBtn').disabled = true;
|
||||
document.getElementById('submitBtn').textContent = '저장 중...';
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('report_ids', JSON.stringify(rids));
|
||||
fd.append('repair_types', JSON.stringify(types));
|
||||
fd.append('description', desc);
|
||||
fd.append('result_status', document.getElementById('resultStatus').value);
|
||||
Array.from(document.getElementById('photosBefore').files).forEach(f => fd.append('photos_before', f));
|
||||
Array.from(document.getElementById('photosAfter').files).forEach(f => fd.append('photos_after', f));
|
||||
|
||||
try {
|
||||
await API.post('/repairs', fd);
|
||||
alert('✅ 조치 완료 저장되었습니다.');
|
||||
location.href = '/pages/mechanic/dashboard.html';
|
||||
} catch(e) {
|
||||
showErr(e.message);
|
||||
document.getElementById('submitBtn').disabled = false;
|
||||
document.getElementById('submitBtn').textContent = '조치 완료 저장';
|
||||
}
|
||||
});
|
||||
|
||||
function showErr(msg) {
|
||||
const el = document.getElementById('formErr'); el.textContent = msg; el.style.display = 'block';
|
||||
}
|
||||
load();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
66
frontend/static/pages/mechanic/scan.html
Normal file
66
frontend/static/pages/mechanic/scan.html
Normal file
@@ -0,0 +1,66 @@
|
||||
<!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">
|
||||
<style>
|
||||
#reader{width:100%;max-width:400px;margin:0 auto;border-radius:10px;overflow:hidden;}
|
||||
.scan-wrap{max-width:480px;margin:0 auto;padding:20px;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="nav">
|
||||
<span class="nav-brand">⚡ QR 스캔</span>
|
||||
<div id="navUser"></div>
|
||||
</nav>
|
||||
<div class="scan-wrap">
|
||||
<div class="alert alert-info" style="margin-bottom:16px;">충전기의 QR 코드를 카메라로 인식해 주세요.</div>
|
||||
<div id="reader"></div>
|
||||
<div id="result" class="alert alert-success" style="display:none;margin-top:14px;"></div>
|
||||
<div style="margin-top:16px;">
|
||||
<div class="form-group">
|
||||
<label>충전기 ID 직접 입력</label>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<input type="text" id="manualId" placeholder="예: CG-003">
|
||||
<button class="btn btn-primary" onclick="goManual()">이동</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://unpkg.com/html5-qrcode/minified/html5-qrcode.min.js"></script>
|
||||
<script src="/js/api.js"></script><script src="/js/auth.js"></script>
|
||||
<script>
|
||||
Auth.require(['mechanic','admin']);
|
||||
Auth.renderNav(document.getElementById('navUser'));
|
||||
|
||||
const scanner = new Html5Qrcode("reader");
|
||||
scanner.start({ facingMode: "environment" },
|
||||
{ fps: 10, qrbox: { width: 250, height: 250 } },
|
||||
qrText => {
|
||||
scanner.stop();
|
||||
document.getElementById('result').style.display = 'block';
|
||||
document.getElementById('result').textContent = '인식됨: ' + qrText + ' — 이동 중...';
|
||||
// URL에서 charger_id 추출
|
||||
try {
|
||||
const url = new URL(qrText);
|
||||
const parts = url.pathname.split('/');
|
||||
const chargerId = parts[parts.length - 1];
|
||||
setTimeout(() => location.href = `/pages/mechanic/repair.html?charger_id=${chargerId}`, 800);
|
||||
} catch {
|
||||
setTimeout(() => location.href = `/pages/mechanic/repair.html?charger_id=${qrText}`, 800);
|
||||
}
|
||||
},
|
||||
() => {}
|
||||
).catch(() => {
|
||||
document.getElementById('reader').innerHTML = '<div class="alert alert-warn">카메라 접근이 거부되었습니다. 직접 입력을 이용해 주세요.</div>';
|
||||
});
|
||||
|
||||
function goManual() {
|
||||
const id = document.getElementById('manualId').value.trim();
|
||||
if (!id) return;
|
||||
location.href = `/pages/mechanic/repair.html?charger_id=${id}`;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user