- Stamp the origin press (machine) number on every cart across the line: on-the-way, in-oven, finished tiles, and the finished by-order list. New shared PressTag.vue badge (icon-free, ink/white text on a violet box). - Thread `press` through the payload (CartOnWay/OvenBatch/FinishedCart/ FinishedByOrder) — backend derives distinct press(es) per cart/order. - Header: pill now "Temperprozess" (was PressV); "Live-Uebersicht" moved into the blue accent slot; dropped the duplicate subtitle. - Fuse the header bar and flow legend into one denser two-row panel (flat prop on both + a shared .toppanel wrapper). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
90 lines
2.7 KiB
Vue
90 lines
2.7 KiB
Vue
<!--
|
|
TemperOverviewPage.vue — the whole-line temper board (design #4 "Shop-floor
|
|
Schematic", locked). One connected line: Pressen → (per-press amber ↓ arrows,
|
|
only under presses with a cart on the way) → Ofen → (green ↓) → Fertig. Carts
|
|
hang under their origin press; the oven & finished bands scroll horizontally.
|
|
The time-range picker scopes ONLY the Fertig figures.
|
|
|
|
Data comes from the store (GET …/api/overview, polled). On connection loss the
|
|
board keeps the last real data (or shows a reconnect spinner on a cold start) and
|
|
retries — it never shows demo data in production. Sample data renders only under
|
|
`npm run dev` (no gateway).
|
|
-->
|
|
<script setup lang="ts">
|
|
import { onMounted, onUnmounted } from 'vue'
|
|
import { storeToRefs } from 'pinia'
|
|
import { useI18n } from 'vue-i18n'
|
|
import OverviewHeader from '@/components/OverviewHeader.vue'
|
|
import FlowLegend from '@/components/FlowLegend.vue'
|
|
import KpiStrip from '@/components/KpiStrip.vue'
|
|
import ProductionLine from '@/components/ProductionLine.vue'
|
|
import { useTemperOverviewStore } from '@/stores/temperOverview'
|
|
|
|
const { t } = useI18n()
|
|
const store = useTemperOverviewStore()
|
|
const { payload, range, embedded, hasData, usingSample, disconnected } = storeToRefs(store)
|
|
|
|
onMounted(async () => {
|
|
await store.hydrate()
|
|
store.startPolling(12000)
|
|
})
|
|
onUnmounted(() => store.stopPolling())
|
|
</script>
|
|
|
|
<template>
|
|
<div class="wrap">
|
|
<template v-if="hasData && payload">
|
|
<!-- Header bar + flow legend fused into one denser panel (two rows). -->
|
|
<div class="toppanel">
|
|
<OverviewHeader
|
|
flat
|
|
:plant="payload.plant"
|
|
:range="range"
|
|
:sample="usingSample"
|
|
:disconnected="disconnected"
|
|
:embedded="embedded"
|
|
@update:range="store.setRange"
|
|
/>
|
|
<FlowLegend flat />
|
|
</div>
|
|
<KpiStrip :payload="payload" />
|
|
<ProductionLine :payload="payload" />
|
|
</template>
|
|
|
|
<!-- Cold start / no data yet: never blank, never fake — spinner that keeps retrying. -->
|
|
<div v-else class="state">
|
|
<span class="spinner" aria-hidden="true"></span>
|
|
<span class="msg">{{ disconnected ? t('conn.lost') : t('conn.connecting') }}</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.wrap {
|
|
max-width: 1300px;
|
|
margin: 0 auto;
|
|
padding: 20px 22px 80px;
|
|
}
|
|
.toppanel {
|
|
border: 1px solid var(--line);
|
|
background: var(--panel);
|
|
border-radius: var(--r);
|
|
margin-bottom: 14px;
|
|
overflow: hidden;
|
|
}
|
|
.state {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 16px;
|
|
min-height: 60vh;
|
|
color: var(--ink-dim);
|
|
font-family: var(--mono);
|
|
}
|
|
.state .msg {
|
|
font-size: 0.85rem;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
</style>
|