harden released pages and archive openspec change

This commit is contained in:
egg
2026-02-23 17:48:32 +08:00
parent 6e2ff9813e
commit e5d7700b36
47 changed files with 2126 additions and 141 deletions

View File

@@ -11,10 +11,27 @@ window.__FIELD_CONTRACTS__['job_query:txn_table'] = getPageContract('job_query',
const jobTableFields = getPageContract('job_query', 'jobs_table');
const txnTableFields = getPageContract('job_query', 'txn_table');
function toDataToken(value) {
return encodeURIComponent(safeText(value));
}
function fromDataToken(value) {
if (!value) {
return '';
}
try {
return decodeURIComponent(value);
} catch (_error) {
return value;
}
}
function renderJobCell(job, apiKey) {
if (apiKey === 'JOBSTATUS') {
const value = safeText(job[apiKey]);
return `<span class="status-badge ${value}">${value}</span>`;
const classToken = safeText(value).replace(/[^A-Za-z0-9_-]/g, '_');
const escaped = escapeHtml(value);
return `<span class="status-badge ${classToken}">${escaped}</span>`;
}
if (apiKey === 'CREATEDATE' || apiKey === 'COMPLETEDATE') {
return formatDate(job[apiKey]);
@@ -25,7 +42,9 @@ function renderJobCell(job, apiKey) {
function renderTxnCell(txn, apiKey) {
if (apiKey === 'FROMJOBSTATUS' || apiKey === 'JOBSTATUS') {
const value = safeText(txn[apiKey], '-');
return `<span class="status-badge ${escapeHtml(value)}">${escapeHtml(value)}</span>`;
const classToken = safeText(value).replace(/[^A-Za-z0-9_-]/g, '_');
const escaped = escapeHtml(value);
return `<span class="status-badge ${classToken}">${escaped}</span>`;
}
if (apiKey === 'TXNDATE') {
return formatDate(txn[apiKey]);
@@ -48,6 +67,16 @@ function renderTxnCell(txn, apiKey) {
loadEquipments();
setLast90Days();
const equipmentList = document.getElementById('equipmentList');
if (equipmentList) {
equipmentList.addEventListener('click', handleEquipmentListClick);
}
const resultSection = document.getElementById('resultSection');
if (resultSection) {
resultSection.addEventListener('click', handleResultSectionClick);
}
// Close dropdown when clicking outside
document.addEventListener('click', (e) => {
const dropdown = document.getElementById('equipmentDropdown');
@@ -94,20 +123,22 @@ function renderTxnCell(txn, apiKey) {
const allSelected = selectedInGroup === groupIds.length;
const someSelected = selectedInGroup > 0 && !allSelected;
const escapedName = escapeHtml(workcenterName);
html += `<div class="workcenter-group-header" onclick="toggleWorkcenterGroup('${escapedName}')">
<input type="checkbox" ${allSelected ? 'checked' : ''} ${someSelected ? 'class="indeterminate"' : ''} onclick="event.stopPropagation(); toggleWorkcenterGroup('${escapedName}')">
const workcenterToken = toDataToken(workcenterName);
html += `<div class="workcenter-group-header" data-action="toggle-workcenter-group" data-workcenter="${workcenterToken}">
<input type="checkbox" ${allSelected ? 'checked' : ''} ${someSelected ? 'class="indeterminate"' : ''} data-action="toggle-workcenter-group" data-workcenter="${workcenterToken}">
<span class="workcenter-group-name">${escapedName}</span>
<span class="workcenter-group-count">${selectedInGroup}/${groupIds.length}</span>
</div>`;
groupEquipments.forEach((eq) => {
const isSelected = selectedEquipments.has(eq.RESOURCEID);
const resourceId = escapeHtml(safeText(eq.RESOURCEID));
const resourceId = safeText(eq.RESOURCEID);
const resourceIdToken = toDataToken(resourceId);
const resourceName = escapeHtml(safeText(eq.RESOURCENAME));
const familyName = escapeHtml(safeText(eq.RESOURCEFAMILYNAME));
html += `
<div class="equipment-item ${isSelected ? 'selected' : ''}" onclick="toggleEquipment('${resourceId}')">
<input type="checkbox" ${isSelected ? 'checked' : ''} onclick="event.stopPropagation(); toggleEquipment('${resourceId}')">
<div class="equipment-item ${isSelected ? 'selected' : ''}" data-action="toggle-equipment" data-resource-id="${resourceIdToken}">
<input type="checkbox" ${isSelected ? 'checked' : ''} data-action="toggle-equipment" data-resource-id="${resourceIdToken}">
<div class="equipment-info">
<div class="equipment-name">${resourceName}</div>
<div class="equipment-workcenter">${familyName}</div>
@@ -120,6 +151,30 @@ function renderTxnCell(txn, apiKey) {
container.innerHTML = html;
}
function handleEquipmentListClick(event) {
const trigger = event.target.closest('[data-action]');
if (!trigger) {
return;
}
if (trigger.dataset.action === 'toggle-workcenter-group') {
const workcenterName = fromDataToken(trigger.dataset.workcenter);
if (!workcenterName) {
return;
}
toggleWorkcenterGroup(workcenterName);
return;
}
if (trigger.dataset.action === 'toggle-equipment') {
const resourceId = fromDataToken(trigger.dataset.resourceId);
if (!resourceId) {
return;
}
toggleEquipment(resourceId);
}
}
// Toggle equipment dropdown
function toggleEquipmentDropdown() {
const dropdown = document.getElementById('equipmentDropdown');
@@ -299,8 +354,8 @@ function renderTxnCell(txn, apiKey) {
<div class="result-header">
<div class="result-info">共 ${jobsData.length} 筆工單</div>
<div class="result-actions">
<button class="btn btn-secondary btn-sm" onclick="expandAll()">全部展開</button>
<button class="btn btn-secondary btn-sm" onclick="collapseAll()">全部收合</button>
<button type="button" class="btn btn-secondary btn-sm" data-action="expand-all">全部展開</button>
<button type="button" class="btn btn-secondary btn-sm" data-action="collapse-all">全部收合</button>
</div>
</div>
<div class="table-container">
@@ -316,13 +371,14 @@ function renderTxnCell(txn, apiKey) {
jobsData.forEach((job, idx) => {
const isExpanded = expandedJobs.has(job.JOBID);
const jobIdToken = toDataToken(job.JOBID);
const jobCells = jobTableFields
.map((field) => `<td>${renderJobCell(job, field.api_key)}</td>`)
.join('');
html += `
<tr class="job-row ${isExpanded ? 'expanded' : ''}" id="job-row-${idx}">
<td>
<button class="expand-btn" onclick="toggleJobHistory('${escapeHtml(safeText(job.JOBID))}', ${idx})">
<button type="button" class="expand-btn" data-action="toggle-job-history" data-job-id="${jobIdToken}" data-row-index="${idx}">
<span class="arrow-icon ${isExpanded ? 'rotated' : ''}">▶</span>
</button>
</td>
@@ -355,6 +411,31 @@ function renderTxnCell(txn, apiKey) {
void loadHistoriesBatched(pendingLoads);
}
function handleResultSectionClick(event) {
const trigger = event.target.closest('[data-action]');
if (!trigger) {
return;
}
const action = trigger.dataset.action;
if (action === 'expand-all') {
expandAll();
return;
}
if (action === 'collapse-all') {
collapseAll();
return;
}
if (action === 'toggle-job-history') {
const idx = Number.parseInt(trigger.dataset.rowIndex || '', 10);
const jobId = fromDataToken(trigger.dataset.jobId);
if (!Number.isInteger(idx) || !jobId) {
return;
}
void toggleJobHistory(jobId, idx);
}
}
// Toggle job history
async function toggleJobHistory(jobId, idx) {
const txnRow = document.getElementById(`txn-row-${idx}`);