refactor: 移除 DOWN 機台明細表,簡化 Dashboard

- 移除 resource_status.html 中的 DOWN 機台明細表區塊
- 移除相關 CSS 樣式和 JavaScript 函數 (loadDetail, formatDownTime, getStatusClass)
- 保留 KPI 卡片和工站卡片視覺化呈現
- 為後續新增稼動率趨勢圖和熱力圖做準備

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
ymirliu
2026-01-16 16:23:09 +08:00
parent b71dc9f6f8
commit cac2017e9f
2 changed files with 8 additions and 235 deletions

View File

@@ -1519,8 +1519,9 @@ def query_resource_detail_with_job(filters=None, limit=200, offset=0):
where_clause = " AND ".join(where_conditions) if where_conditions else "1=1"
# Left join with JOB table for SDT/UDT details
# JOB 匹配邏輯: RESOURCEID + CREATEDATE = LASTSTATUSCHANGEDATE (等值匹配)
# PJ_LOTID 來自 RESOURCE 表
# SYMPTOMCODENAME, CAUSECODENAME 來自 JOB 表
# SYMPTOMCODENAME, CAUSECODENAME, JOBID 等來自 JOB 表
# DOWN_MINUTES: 使用全體最大 LASTSTATUSCHANGEDATE - 每台機台自己的時間
# 注意: 將所有 CTE 放在同一層級,避免巢狀 WITH 子句 (Oracle 不支援)
start_row = offset + 1
@@ -1591,7 +1592,7 @@ def query_resource_detail_with_job(filters=None, limit=200, offset=0):
rs.PJ_ISPRODUCTION,
rs.PJ_ISKEY,
rs.PJ_ISMONITOR,
rs.JOBID,
j.JOBID,
rs.PJ_LOTID,
j.JOBORDERNAME,
j.JOBSTATUS,
@@ -1613,7 +1614,8 @@ def query_resource_detail_with_job(filters=None, limit=200, offset=0):
) AS rn
FROM base_data rs
CROSS JOIN max_time mt
LEFT JOIN DW_MES_JOB j ON rs.JOBID = j.JOBID
LEFT JOIN DW_MES_JOB j ON j.RESOURCEID = rs.RESOURCEID
AND j.CREATEDATE = rs.LASTSTATUSCHANGEDATE
WHERE {where_clause}
) WHERE rn BETWEEN {start_row} AND {end_row}
"""

View File

@@ -252,108 +252,6 @@
margin-top: 8px;
}
/* Detail Table */
.detail-section {
background: var(--card-bg);
border-radius: 10px;
padding: 16px;
border: 1px solid var(--border);
box-shadow: var(--shadow);
}
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.detail-title {
font-size: 16px;
color: var(--primary);
}
.detail-count {
color: var(--muted);
font-size: 13px;
}
.table-container {
overflow-x: auto;
max-height: 350px;
overflow-y: auto;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
}
thead {
position: sticky;
top: 0;
background: #eef2ff;
z-index: 1;
}
th {
padding: 10px 8px;
text-align: left;
color: #3f4aa7;
font-weight: 600;
white-space: nowrap;
border-bottom: 1px solid var(--border);
}
td {
padding: 8px;
border-bottom: 1px solid var(--border);
color: var(--text);
}
tbody tr:hover {
background: #f1f5ff;
}
.status-badge {
display: inline-block;
padding: 3px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 700;
}
.status-prd { background: #bbf7d0; color: #166534; }
.status-sby { background: #bfdbfe; color: #1e40af; }
.status-udt { background: #fecaca; color: #991b1b; }
.status-sdt { background: #fef3c7; color: #92400e; }
.status-egt { background: #e2e8f0; color: #475569; }
.status-nst { background: #e9d5ff; color: #6b21a8; }
.status-other { background: #e5e7eb; color: #374151; }
.flag-badge {
display: inline-block;
padding: 2px 6px;
border-radius: 8px;
font-size: 10px;
margin-right: 3px;
}
.flag-prod { background: #dcfce7; color: #166534; }
.flag-key { background: #fee2e2; color: #991b1b; }
.flag-monitor { background: #dbeafe; color: #1e40af; }
.down-time {
color: var(--danger);
font-weight: bold;
}
.job-info {
font-size: 11px;
color: var(--muted);
}
/* Loading */
.loading-spinner {
display: inline-block;
@@ -486,34 +384,6 @@
</div>
</div>
<!-- Detail Table -->
<div class="detail-section" style="position: relative;">
<div class="detail-header">
<div class="detail-title" id="detailTitle">DOWN 機台明細 (UDT/SDT)</div>
<div class="detail-count" id="detailCount"></div>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th>機台</th>
<th>工作中心</th>
<th>狀態</th>
<th>原因</th>
<th>最後狀態時間</th>
<th>Down Time</th>
<th>批號 (PJ_LOTID)</th>
<th>症狀 (JOB)</th>
<th>原因碼 (JOB)</th>
<th>標記</th>
</tr>
</thead>
<tbody id="detailTableBody">
<tr><td colspan="10" class="placeholder">請點擊「查詢」載入資料</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<script>
@@ -549,32 +419,12 @@
return num.toLocaleString('zh-TW');
}
function getStatusClass(status) {
if (!status) return 'status-other';
const s = status.toUpperCase();
if (s === 'PRD') return 'status-prd';
if (s === 'SBY') return 'status-sby';
if (s === 'UDT') return 'status-udt';
if (s === 'SDT') return 'status-sdt';
if (s === 'EGT') return 'status-egt';
if (s === 'NST') return 'status-nst';
return 'status-other';
}
function getOuClass(ouPct) {
if (ouPct >= 80) return 'high';
if (ouPct >= 50) return 'medium';
return 'low';
}
function formatDownTime(minutes) {
if (!minutes || minutes <= 0) return '-';
if (minutes < 60) return `${Math.round(minutes)}m`;
const hours = Math.floor(minutes / 60);
const mins = Math.round(minutes % 60);
return `${hours}h ${mins}m`;
}
async function loadKPI() {
try {
const response = await fetch('/api/dashboard/kpi', {
@@ -828,84 +678,6 @@
});
}
loadDetail();
}
async function loadDetail() {
const tbody = document.getElementById('detailTableBody');
const title = document.getElementById('detailTitle');
const count = document.getElementById('detailCount');
tbody.innerHTML = '<tr><td colspan="10" class="placeholder"><span class="loading-spinner"></span>載入中...</td></tr>';
const filters = getFilters() || {};
if (selectedWorkcenter) {
filters.workcenter = selectedWorkcenter;
// 傳遞原始工站列表,讓後端可以用 IN 查詢
if (selectedOriginalWcs && selectedOriginalWcs.length > 0) {
filters.original_wcs = selectedOriginalWcs;
}
title.textContent = `DOWN 機台明細 - ${selectedWorkcenter}`;
} else {
title.textContent = 'DOWN 機台明細 (全部)';
}
try {
const response = await fetch('/api/dashboard/detail', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ filters: Object.keys(filters).length > 0 ? filters : null, limit: 200, offset: 0 })
});
const result = await response.json();
if (result.success) {
count.textContent = `(${result.count} 筆)`;
// 更新 Last Update 使用 API 返回的最新狀態時間
if (result.max_status_time) {
document.getElementById('lastUpdate').textContent = `Last Update: ${result.max_status_time}`;
}
if (result.data.length === 0) {
tbody.innerHTML = '<tr><td colspan="10" class="placeholder">查無資料</td></tr>';
return;
}
let html = '';
result.data.forEach(row => {
const statusClass = getStatusClass(row.NEWSTATUSNAME);
const downTime = formatDownTime(row.DOWN_MINUTES);
let flags = '';
if (row.PJ_ISPRODUCTION === 1) flags += '<span class="flag-badge flag-prod">生產</span>';
if (row.PJ_ISKEY === 1) flags += '<span class="flag-badge flag-key">關鍵</span>';
if (row.PJ_ISMONITOR === 1) flags += '<span class="flag-badge flag-monitor">監控</span>';
// PJ_LOTID 來自 DW_MES_RESOURCE
// SYMPTOMCODENAME, CAUSECODENAME 來自 DW_MES_JOB (透過 JOBID 關聯)
// DOWN_MINUTES 使用最新 LASTSTATUSCHANGEDATE - 每台機台自己的時間
html += `
<tr>
<td><strong>${row.RESOURCENAME || '-'}</strong></td>
<td>${row.WORKCENTERNAME || '-'}</td>
<td><span class="status-badge ${statusClass}">${row.NEWSTATUSNAME || '-'}</span></td>
<td>${row.NEWREASONNAME || '-'}</td>
<td>${row.LASTSTATUSCHANGEDATE || '-'}</td>
<td class="${row.DOWN_MINUTES > 0 ? 'down-time' : ''}">${downTime}</td>
<td class="job-info">${row.PJ_LOTID || '-'}</td>
<td class="job-info">${row.SYMPTOMCODENAME || '-'}</td>
<td class="job-info">${row.CAUSECODENAME || '-'}</td>
<td>${flags || '-'}</td>
</tr>
`;
});
tbody.innerHTML = html;
} else {
tbody.innerHTML = `<tr><td colspan="10" class="placeholder">${result.error}</td></tr>`;
}
} catch (error) {
tbody.innerHTML = `<tr><td colspan="10" class="placeholder">載入失敗: ${error.message}</td></tr>`;
}
}
async function loadDashboard() {
@@ -916,14 +688,13 @@
btn.disabled = true;
btn.innerHTML = '<span class="loading-spinner"></span>查詢中...';
// Last Update 會由 loadDetail() 中的 API 回應更新
try {
await Promise.all([
loadKPI(),
loadWorkcenterCards(),
loadDetail()
loadWorkcenterCards()
]);
// 更新 Last Update
document.getElementById('lastUpdate').textContent = `Last Update: ${new Date().toLocaleString('zh-TW')}`;
} finally {
isLoading = false;
btn.disabled = false;