feat(overview): Temperprozess rename, order-count badge, amber transit flow, brighter labels

- Rename line "Temper-Linie" → "Temperprozess" (tab title + i18n de/en)
- Per-press order display: chip row → single colored order-count badge (.ocount)
- New 5th flow colour --transit (amber) for "Auf dem Weg"; --way (cyan) reserved
  for cart/verladen; per-press amber arrows drop to the oven; header "Maschinenhalle"
- Brighter --ink-faint labels to match temper_rejects (dark #7f8b9a, light #4b586b)
- Grid width tweaks (col floor 96px, wrap max-width 1300px) + hide matrix scrollbar
- Rebuilt dist

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
This commit is contained in:
Erik 2026-07-02 20:42:54 +02:00
parent a24f7d2c1e
commit 84253f7dcc
14 changed files with 166 additions and 81 deletions

1
dist/assets/index-B4JBFZH3.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

36
dist/index.html vendored
View File

@ -1,18 +1,18 @@
<!doctype html>
<!--
Dark-first board (class="dark" → @sp-ui-kit token dark values + PrimeVue dark
mode, whose darkModeSelector is ".dark"). data-density="comfortable": this is a
view-only wall board, not a finger-touch panel (the reject STATION uses "touch").
-->
<html lang="de" class="dark" data-density="comfortable">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Temper-Linie · Live-Übersicht</title>
<script type="module" crossorigin src="/module/temper_overview/assets/index-C3BK_Qev.js"></script>
<link rel="stylesheet" crossorigin href="/module/temper_overview/assets/index-BxZ3b12T.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
<!doctype html>
<!--
Dark-first board (class="dark" → @sp-ui-kit token dark values + PrimeVue dark
mode, whose darkModeSelector is ".dark"). data-density="comfortable": this is a
view-only wall board, not a finger-touch panel (the reject STATION uses "touch").
-->
<html lang="de" class="dark" data-density="comfortable">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Temperprozess · Live-Übersicht</title>
<script type="module" crossorigin src="/module/temper_overview/assets/index-Vt9ocbug.js"></script>
<link rel="stylesheet" crossorigin href="/module/temper_overview/assets/index-B4JBFZH3.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@ -8,7 +8,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Temper-Linie · Live-Übersicht</title>
<title>Temperprozess · Live-Übersicht</title>
</head>
<body>
<div id="app"></div>

View File

@ -11,7 +11,8 @@ const { t } = useI18n()
<span class="sw" :style="{ background: 'var(--press)' }"></span>
<span><span class="nm">{{ t('flow.presses') }}</span> <span class="sub">{{ t('flow.pressesSub') }}</span></span>
</div>
<div class="node"><span class="sw" :style="{ background: 'var(--way)' }"></span><span class="nm">{{ t('flow.onWay') }}</span></div>
<div class="node"><span class="sw" :style="{ background: 'var(--way)' }"></span><span class="nm">{{ t('flow.loaded') }}</span></div>
<div class="node"><span class="sw" :style="{ background: 'var(--transit)' }"></span><span class="nm">{{ t('flow.onWay') }}</span></div>
<div class="node"><span class="sw" :style="{ background: 'var(--oven)' }"></span><span class="nm">{{ t('flow.oven') }}</span></div>
<div class="node"><span class="sw" :style="{ background: 'var(--done)' }"></span><span class="nm">{{ t('flow.finished') }}</span></div>
<span class="orders">

View File

@ -70,7 +70,7 @@ const scrapRate = computed(() => {
border-left-color: var(--press);
}
.kpi.way {
border-left-color: var(--way);
border-left-color: var(--transit);
}
.kpi.oven {
border-left-color: var(--oven);
@ -95,7 +95,7 @@ const scrapRate = computed(() => {
color: var(--press);
}
.kpi.way .kv {
color: var(--way);
color: var(--transit);
}
.kpi.oven .kv {
color: var(--oven);

View File

@ -24,11 +24,15 @@ const statusText = computed(() =>
<div v-else class="press" :class="press.status">
<div class="top">
<span class="pn">{{ press.name }}</span>
<span
v-if="press.orders.length"
class="ocount"
:style="{ background: orderColor(press.orders.length - 1) }"
:title="t('press.orderCount', { n: press.orders.length })"
>{{ press.orders.length }}</span
>
<span class="stled" :style="{ background: ledColor }"></span>
</div>
<div class="chips">
<span v-for="(o, i) in press.orders" :key="i" class="chip" :style="{ background: orderColor(o.colorIndex) }"></span>
</div>
<template v-if="press.status === 'run'">
<div class="good num">{{ de(press.good) }}</div>
<div class="sub">{{ t('press.good') }} · {{ press.cycleTime }}s</div>
@ -91,15 +95,19 @@ const statusText = computed(() =>
border-radius: 50%;
margin-left: auto;
}
.chips {
display: flex;
gap: 3px;
min-height: 12px;
}
.chip {
width: 12px;
height: 12px;
border-radius: 3px;
.ocount {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 17px;
height: 17px;
padding: 0 4px;
border-radius: 4px;
font-family: var(--mono);
font-size: 0.7rem;
font-weight: 800;
line-height: 1;
color: var(--ground);
}
.good {
font-family: var(--mono);

View File

@ -12,34 +12,50 @@ const partsOf = (c: CartOnWay) => c.orders.reduce((s, o) => s + o.qty, 0)
</script>
<template>
<div class="lane" :class="{ active }">
<span class="conn"></span>
<span class="lc" :class="active ? 'on' : 'off'">
{{ active ? t('lane.wagen', { n: carts.length }) : t('lane.none') }}
</span>
<div v-for="cart in carts" :key="cart.cartId" class="cart">
<div class="chd">
<span class="cid">{{ cart.cartId }}</span>
<span class="cp">{{ t('lane.parts', { n: partsOf(cart) }) }}</span>
<div class="lane-wrap">
<div class="lane" :class="{ active }">
<span class="conn"></span>
<span class="lc" :class="active ? 'on' : 'off'">
{{ active ? t('lane.wagen', { n: carts.length }) : t('lane.none') }}
</span>
<div v-for="cart in carts" :key="cart.cartId" class="cart">
<div class="chd">
<span class="cid">{{ cart.cartId }}</span>
<span class="cp">{{ t('lane.parts', { n: partsOf(cart) }) }}</span>
</div>
<div class="cbody">
<OrderLine v-for="(o, i) in cart.orders" :key="i" :order-no="o.orderNo" :qty="o.qty" :color-index="o.colorIndex" />
</div>
<div class="cfoot">{{ t('lane.ovenIn', { n: cart.etaMin }) }}</div>
</div>
<div class="cbody">
<OrderLine v-for="(o, i) in cart.orders" :key="i" :order-no="o.orderNo" :qty="o.qty" :color-index="o.colorIndex" />
</div>
<div class="cfoot">{{ t('lane.ovenIn', { n: cart.etaMin }) }}</div>
</div>
<!-- Connector: bridges the on-the-way lane down to the oven. `.drop` grows to
fill the space below the cart list, so every active column's arrow lands on
the same line and touches the oven box (like the ovenFertig connector). -->
<div v-if="active" class="drop" :title="t('flow.onWay')">
<span class="dropline"></span>
<span class="arrow"></span>
</div>
</div>
</template>
<style scoped>
.lane-wrap {
display: flex;
flex-direction: column;
}
.lane {
position: relative;
flex: 0 0 auto;
width: 100%;
min-height: 132px;
max-height: 236px;
overflow-y: auto;
padding-top: 22px;
padding-bottom: 10px;
display: flex;
flex-direction: column;
gap: 8px;
gap: 12px;
scrollbar-width: thin;
}
.conn {
@ -50,7 +66,7 @@ const partsOf = (c: CartOnWay) => c.orders.reduce((s, o) => s + o.qty, 0)
width: 2px;
transform: translateX(-50%);
z-index: 0;
background: repeating-linear-gradient(180deg, var(--way) 0 3px, transparent 3px 11px);
background: repeating-linear-gradient(180deg, var(--transit) 0 3px, transparent 3px 11px);
background-size: 2px 11px;
opacity: 0.3;
}
@ -73,11 +89,42 @@ const partsOf = (c: CartOnWay) => c.orders.reduce((s, o) => s + o.qty, 0)
z-index: 3;
}
.lc.on {
color: var(--way);
color: var(--transit);
}
.lc.off {
color: var(--ink-faint);
}
/* Bridge from the cart lane down to the oven — mirrors the oven→Fertig `.link`. */
.drop {
position: relative;
flex: 1 1 auto;
min-height: 26px;
align-self: stretch;
}
.dropline {
position: absolute;
left: 50%;
top: 0;
bottom: 7px;
width: 2px;
transform: translateX(-50%);
background: repeating-linear-gradient(180deg, var(--transit) 0 3px, transparent 3px 11px);
background-size: 2px 11px;
opacity: 0.95;
animation: flowdown 1.05s linear infinite;
box-shadow: 0 0 6px var(--transit);
}
.arrow {
position: absolute;
left: 50%;
bottom: 0;
transform: translateX(-50%);
color: var(--transit);
font-size: 0.8rem;
line-height: 1;
text-shadow: 0 0 6px var(--transit);
animation: blink 1.1s ease-in-out infinite;
}
.cart {
position: relative;
z-index: 2;

View File

@ -25,6 +25,10 @@ function cartsFor(pressName: string): CartOnWay[] {
<div class="stagehd">
<span class="sw" :style="{ background: 'var(--press)' }"></span>
{{ t('line.header') }}
<span v-if="wayCarts > 0" class="way-badge">
<span class="dot"></span>
{{ t('kpi.onWaySub', { n: wayCarts }) }}
</span>
<span class="c">{{ t('line.active', { running, total }) }}</span>
</div>
@ -35,10 +39,6 @@ function cartsFor(pressName: string): CartOnWay[] {
</div>
</div>
<div class="link" :style="{ '--lc': 'var(--way)' }">
<span class="lbl"><b>{{ t('line.toOven') }}</b> {{ t('line.toOvenTail') }} · {{ t('line.wagen', { n: wayCarts }) }}</span>
</div>
<OvenBand :batches="payload.oven" :temp-c="payload.ovenTempC ?? null" :avg-min="payload.ovenAvgMin" />
<div class="link" :style="{ '--lc': 'var(--done)' }">
@ -78,18 +78,45 @@ html:not(.dark) .production {
height: 9px;
border-radius: var(--r);
}
.way-badge {
display: flex;
align-items: center;
gap: 6px;
color: var(--transit);
font-weight: 700;
letter-spacing: 0.04em;
}
.dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--transit);
box-shadow: 0 0 6px var(--transit);
animation: blink 1.2s ease-in-out infinite;
}
.c {
margin-left: auto;
color: var(--ink-dim);
}
.matrix {
overflow-x: auto;
/* No gap: the per-press amber connectors (PressLane `.drop`) grow to the bottom of
this grid, so their arrows land flush on the oven band's top edge. Hide the
horizontal scrollbar with the gap gone, a stray/near-miss scrollbar otherwise
rendered as a gray line sitting on the oven's top edge. Hiding it also reclaims
the scrollbar's ~15px, which clears the near-miss overflow so the grid just fits. */
margin-bottom: 0;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* legacy Edge */
}
.matrix::-webkit-scrollbar {
display: none; /* Chromium / WebKit (the Grafana embed) */
}
.grid {
display: grid;
grid-template-columns: repeat(10, minmax(102px, 1fr));
grid-template-columns: repeat(10, minmax(96px, 1fr));
gap: 8px;
min-width: 1080px;
min-width: 1032px;
}
.link {
position: relative;

View File

@ -2,7 +2,7 @@ export default {
app: {
brand: 'PressV',
plant: 'Werk',
line: 'Temper-Linie',
line: 'Temperprozess',
subtitle: 'Live-Übersicht',
live: 'Live',
demo: 'Demo-Daten',
@ -10,6 +10,7 @@ export default {
flow: {
presses: 'Pressen',
pressesSub: 'inkl. nicht verladen',
loaded: 'Auf Wagen verladen',
onWay: 'Auf dem Weg',
oven: 'Ofen',
finished: 'Fertig',
@ -29,13 +30,10 @@ export default {
scrapRateSub: 'Ausschuss / Gesamt',
},
line: {
header: 'Pressenhalle → auf dem Weg',
header: 'Maschinenhalle',
active: '{running}/{total} aktiv · Wagen hängen unter ihrer Presse',
toOven: 'Auf dem Weg',
toOvenTail: 'in den Ofen',
toStorage: 'Aus dem Ofen',
toStorageTail: 'Fertig / Lager',
wagen: '{n} Wagen',
},
press: {
good: 'Gut',
@ -44,6 +42,7 @@ export default {
statusRun: 'Läuft',
statusStop: 'Störung',
statusIdle: 'Leerlauf',
orderCount: '{n} aktive Aufträge',
},
lane: {
wagen: '{n} Wagen ↓',

View File

@ -3,7 +3,7 @@ export default {
app: {
brand: 'PressV',
plant: 'Plant',
line: 'Temper Line',
line: 'Temperprozess',
subtitle: 'Live Overview',
live: 'Live',
demo: 'Sample data',
@ -11,6 +11,7 @@ export default {
flow: {
presses: 'Presses',
pressesSub: 'incl. not yet loaded',
loaded: 'Loaded onto cart',
onWay: 'On the way',
oven: 'Oven',
finished: 'Finished',
@ -30,13 +31,10 @@ export default {
scrapRateSub: 'Scrap / total',
},
line: {
header: 'Press hall → on the way',
header: 'Machine hall',
active: '{running}/{total} active · carts hang under their press',
toOven: 'On the way',
toOvenTail: 'into the oven',
toStorage: 'Out of the oven',
toStorageTail: 'finished / storage',
wagen: '{n} carts',
},
press: {
good: 'Good',
@ -45,6 +43,7 @@ export default {
statusRun: 'Running',
statusStop: 'Fault',
statusIdle: 'Idle',
orderCount: '{n} active orders',
},
lane: {
wagen: '{n} carts ↓',

View File

@ -16,7 +16,8 @@
Neutral surfaces are the locked prototype's board palette (dark, layered,
with a radial glow). The FLOW ACCENTS match PressV's --c-* process colours
verbatim so the temper process reads the same across every SPRO app
(press=violet, on-the-way/cart=cyan, oven=orange, hot=red, finished=emerald).
(press=violet, cart/verladen=cyan, in-transit=amber, oven=orange, hot=red,
finished=emerald same 5-station palette as the Ausschuss-Station board).
Font families come from @sp-ui-kit/tokens (Inter Variable + mono).
========================================================================== */
:root {
@ -29,10 +30,11 @@
--ink: #e8edf2;
--ink-dim: #97a3b1;
--ink-faint: #5a6573;
--ink-faint: #7f8b9a;
--press: #a78bfa; /* violet — Pressen / not yet loaded */
--way: #22d3ee; /* cyan — Auf dem Weg / cart */
--way: #22d3ee; /* cyan — Auf Wagen verladen / cart */
--transit: #fbbf24; /* amber — Auf dem Weg (unterwegs) */
--oven: #f97316; /* orange — Ofen */
--hot: #ef4444; /* red — Ofen, kurz vor raus */
--done: #34d399; /* emerald — Fertig / Lager */
@ -63,10 +65,11 @@ html:not(.dark) {
--ink: #0f172a;
--ink-dim: #475569;
--ink-faint: #64748b;
--ink-faint: #4b586b;
--press: #6d28d9;
--way: #0e7490;
--transit: #a16207;
--oven: #c2410c;
--hot: #dc2626;
--done: #15803d;

View File

@ -1,8 +1,9 @@
<!--
TemperOverviewPage.vue the whole-line temper board (design #4 "Shop-floor
Schematic", locked). One connected line: Pressen (cyan ) Ofen (green )
Fertig. Carts hang under their origin press; the oven & finished bands scroll
horizontally. The time-range picker scopes ONLY the Fertig figures.
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). With no backend the
store falls back to bundled sample data, so `npm run dev` renders the design.
@ -48,7 +49,7 @@ onUnmounted(() => store.stopPolling())
<style scoped>
.wrap {
max-width: 1240px;
max-width: 1300px;
margin: 0 auto;
padding: 20px 22px 80px;
}