Files
DashBoard/frontend/src/qc-gate/App.vue

171 lines
4.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { computed, ref } from 'vue';
import QcGateChart from './components/QcGateChart.vue';
import LotTable from './components/LotTable.vue';
import { useQcGateData } from './composables/useQcGateData.js';
const {
stations,
cacheTime,
loading,
refreshing,
errorMessage,
allLots,
refreshNow,
} = useQcGateData();
const activeFilter = ref(null);
const BUCKET_LABELS = {
lt_6h: '<6hr',
'6h_12h': '6-12hr',
'12h_24h': '12-24hr',
gt_24h: '>24hr',
};
const hasStations = computed(() => stations.value.length > 0);
const totalLots = computed(() => {
return stations.value.reduce((sum, station) => sum + Number(station.total || 0), 0);
});
const filteredLots = computed(() => {
if (!activeFilter.value) {
return allLots.value;
}
return allLots.value.filter((lot) => {
return (
lot.step === activeFilter.value.station &&
lot.bucket === activeFilter.value.bucket
);
});
});
const formattedCacheTime = computed(() => {
if (!cacheTime.value) {
return '--';
}
const date = new Date(cacheTime.value);
if (Number.isNaN(date.getTime())) {
return String(cacheTime.value);
}
return date.toLocaleString('zh-TW', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
});
});
const activeFilterLabel = computed(() => {
if (!activeFilter.value) {
return '';
}
const bucketLabel = BUCKET_LABELS[activeFilter.value.bucket] || activeFilter.value.bucket;
return `${activeFilter.value.station} / ${bucketLabel}`;
});
function handleChartSelect(filter) {
if (!filter?.station || !filter?.bucket) {
return;
}
if (
activeFilter.value &&
activeFilter.value.station === filter.station &&
activeFilter.value.bucket === filter.bucket
) {
activeFilter.value = null;
return;
}
activeFilter.value = filter;
}
function clearFilter() {
activeFilter.value = null;
}
function handleManualRefresh() {
void refreshNow();
}
</script>
<template>
<div class="qc-gate-page">
<header class="qc-gate-header">
<div>
<h1>QC-GATE 狀態</h1>
<p class="header-subtitle">即時監控各 QC-GATE 站點的在製 LOT 與等待時間</p>
</div>
<div class="header-meta">
<div class="meta-row">
<span class="meta-label">快照時間</span>
<span class="meta-value">{{ formattedCacheTime }}</span>
</div>
<div class="meta-row">
<span class="meta-label"> LOT</span>
<span class="meta-value">{{ totalLots.toLocaleString('zh-TW') }}</span>
</div>
<button
type="button"
class="refresh-button"
:disabled="loading || refreshing"
@click="handleManualRefresh"
>
{{ refreshing ? '更新中...' : '重新整理' }}
</button>
</div>
</header>
<div v-if="errorMessage" class="error-banner">{{ errorMessage }}</div>
<main class="qc-gate-content">
<section class="panel chart-panel" :class="{ 'is-refreshing': refreshing }">
<div class="panel-header">
<h2>站點等待時間分布</h2>
<span class="panel-hint">點擊圖表區段可篩選下方 LOT 清單</span>
</div>
<div v-if="loading" class="loading-state">資料載入中...</div>
<template v-else>
<QcGateChart
:stations="stations"
:active-filter="activeFilter"
@select-segment="handleChartSelect"
/>
<div v-if="!hasStations" class="empty-state">目前無 QC-GATE LOT</div>
</template>
</section>
<section class="panel table-panel" :class="{ 'is-refreshing': refreshing }">
<div class="panel-header">
<h2>LOT 明細</h2>
<button
v-if="activeFilter"
type="button"
class="filter-indicator"
@click="clearFilter"
>
篩選中{{ activeFilterLabel }}點擊清除
</button>
</div>
<LotTable
:lots="filteredLots"
:active-filter="activeFilter"
@clear-filter="clearFilter"
/>
</section>
</main>
</div>
</template>