208 lines
7.3 KiB
Cheetah
208 lines
7.3 KiB
Cheetah
{{define "lots"}}
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
{{template "head" .}}
|
|
<body>
|
|
{{template "topbar" .}}
|
|
{{template "breadcrumbs" .}}
|
|
|
|
<main class="container">
|
|
<section class="card">
|
|
<h2>Create or Update Mapping</h2>
|
|
<div class="grid cols-3">
|
|
<div>
|
|
<label for="modelInput">Model</label>
|
|
<input id="modelInput" class="input" type="text" placeholder="e.g. ST12000NM0008" />
|
|
</div>
|
|
<div>
|
|
<label for="lotSelect">LOT (existing)</label>
|
|
<select id="lotSelect" class="input">
|
|
<option value="">Select LOT</option>
|
|
{{range .Lots}}
|
|
<option value="{{.Code}}">{{.Code}}</option>
|
|
{{end}}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="lotCodeInput">LOT (manual code)</label>
|
|
<input id="lotCodeInput" class="input" type="text" placeholder="e.g. LOT-HDD-12TB" />
|
|
</div>
|
|
</div>
|
|
<div class="controls" style="margin-top: 12px;">
|
|
<button id="saveMapping" class="button" type="button">Save Mapping</button>
|
|
</div>
|
|
<div id="statusMessage" class="meta" style="margin-top: 12px;"></div>
|
|
</section>
|
|
|
|
<section class="card">
|
|
<h2>Current Mappings</h2>
|
|
{{if .LotMappings}}
|
|
<table class="table">
|
|
<thead>
|
|
<tr>
|
|
<th>Model</th>
|
|
<th>LOT code</th>
|
|
<th>Updated</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{range .LotMappings}}
|
|
<tr>
|
|
<td><button class="button ghost" type="button" onclick='openEditModal({{printf "%q" .Model}}, {{printf "%q" .LotCode}})'>{{.Model}}</button></td>
|
|
<td><button class="button ghost" type="button" onclick='openEditModal({{printf "%q" .Model}}, {{printf "%q" .LotCode}})'>{{.LotCode}}</button></td>
|
|
<td title="{{formatTimeFull .UpdatedAt}}">{{formatTime .UpdatedAt}}</td>
|
|
<td>
|
|
<button class="button ghost" type="button" onclick='openEditModal({{printf "%q" .Model}}, {{printf "%q" .LotCode}})'>Edit</button>
|
|
<button class="button ghost" type="button" onclick='deleteMapping({{printf "%q" .Model}})'>Delete</button>
|
|
</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
{{else}}
|
|
<div class="meta">No mappings yet.</div>
|
|
{{end}}
|
|
</section>
|
|
</main>
|
|
|
|
<div id="editModal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.35); z-index:1000;">
|
|
<div class="card" style="max-width:980px; margin:60px auto; position:relative;">
|
|
<h2>Edit Mapping</h2>
|
|
<div class="grid cols-2">
|
|
<div>
|
|
<label for="modalModelInput">Model</label>
|
|
<input id="modalModelInput" class="input" type="text" style="width:100%; min-height:52px; font-size:28px; line-height:1.2;" />
|
|
</div>
|
|
<div>
|
|
<label for="modalLotCodeInput">LOT code</label>
|
|
<input id="modalLotCodeInput" class="input" type="text" style="width:100%; min-height:52px; font-size:28px; line-height:1.2;" />
|
|
</div>
|
|
</div>
|
|
<div class="controls" style="margin-top:12px;">
|
|
<button class="button" type="button" onclick="saveModalMapping()">Save</button>
|
|
<button class="button ghost" type="button" onclick="closeEditModal()">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let editingOriginalModel = "";
|
|
function normalizeValue(value) {
|
|
const trimmed = (value || "").trim();
|
|
if (trimmed.length >= 2) {
|
|
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
return trimmed.slice(1, -1).trim();
|
|
}
|
|
}
|
|
return trimmed;
|
|
}
|
|
|
|
function mappingModel() {
|
|
return normalizeValue(document.getElementById("modelInput").value);
|
|
}
|
|
|
|
function mappingLotCode() {
|
|
const manual = normalizeValue(document.getElementById("lotCodeInput").value);
|
|
if (manual !== "") return manual;
|
|
return normalizeValue(document.getElementById("lotSelect").value);
|
|
}
|
|
|
|
function setStatus(text, isError) {
|
|
const el = document.getElementById("statusMessage");
|
|
el.textContent = text;
|
|
el.style.color = isError ? "#c81d25" : "#4f772d";
|
|
}
|
|
|
|
async function saveMapping() {
|
|
const model = mappingModel();
|
|
const lotCode = mappingLotCode();
|
|
if (!model) {
|
|
setStatus("Model is required.", true);
|
|
return;
|
|
}
|
|
if (!lotCode) {
|
|
setStatus("LOT code is required.", true);
|
|
return;
|
|
}
|
|
|
|
const response = await fetch("/lot-mappings/" + encodeURIComponent(model), {
|
|
method: "PUT",
|
|
headers: {"Content-Type": "application/json"},
|
|
body: JSON.stringify({lot_code: lotCode})
|
|
});
|
|
const payload = await response.json().catch(() => ({}));
|
|
if (!response.ok) {
|
|
setStatus(payload.error || "Failed to save mapping.", true);
|
|
return;
|
|
}
|
|
setStatus("Saved. Updated parts: " + (payload.affected_parts_count || 0), false);
|
|
window.location.reload();
|
|
}
|
|
|
|
function openEditModal(model, lotCode) {
|
|
editingOriginalModel = normalizeValue(model);
|
|
document.getElementById("modalModelInput").value = normalizeValue(model);
|
|
document.getElementById("modalLotCodeInput").value = normalizeValue(lotCode);
|
|
document.getElementById("editModal").style.display = "block";
|
|
}
|
|
|
|
function closeEditModal() {
|
|
document.getElementById("editModal").style.display = "none";
|
|
editingOriginalModel = "";
|
|
}
|
|
|
|
async function saveModalMapping() {
|
|
const nextModel = normalizeValue(document.getElementById("modalModelInput").value);
|
|
const nextLotCode = normalizeValue(document.getElementById("modalLotCodeInput").value);
|
|
if (!nextModel || !nextLotCode) {
|
|
alert("Model and LOT code are required.");
|
|
return;
|
|
}
|
|
|
|
const putResp = await fetch("/lot-mappings/" + encodeURIComponent(nextModel), {
|
|
method: "PUT",
|
|
headers: {"Content-Type": "application/json"},
|
|
body: JSON.stringify({lot_code: nextLotCode})
|
|
});
|
|
const putPayload = await putResp.json().catch(() => ({}));
|
|
if (!putResp.ok) {
|
|
alert(putPayload.error || "Failed to save mapping.");
|
|
return;
|
|
}
|
|
|
|
if (editingOriginalModel && editingOriginalModel !== nextModel) {
|
|
const delResp = await fetch("/lot-mappings/" + encodeURIComponent(editingOriginalModel), {
|
|
method: "DELETE"
|
|
});
|
|
if (!delResp.ok) {
|
|
const delPayload = await delResp.json().catch(() => ({}));
|
|
alert(delPayload.error || "Failed to replace old mapping.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
closeEditModal();
|
|
window.location.reload();
|
|
}
|
|
|
|
async function deleteMapping(model) {
|
|
if (!confirm("Delete mapping for model '" + model + "'?")) {
|
|
return;
|
|
}
|
|
const response = await fetch("/lot-mappings/" + encodeURIComponent(model), {method: "DELETE"});
|
|
const payload = await response.json().catch(() => ({}));
|
|
if (!response.ok) {
|
|
setStatus(payload.error || "Failed to delete mapping.", true);
|
|
return;
|
|
}
|
|
setStatus("Deleted. Updated parts: " + (payload.affected_parts_count || 0), false);
|
|
window.location.reload();
|
|
}
|
|
|
|
document.getElementById("saveMapping").addEventListener("click", saveMapping);
|
|
</script>
|
|
</body>
|
|
</html>
|
|
{{end}}
|