feat(plant_kanban): polish KPI strip + chips + toolbar buttons

User feedback after first deploy: 7 KPI tiles wrapped to second line
(grid was repeat(5, 1fr) but I had added 2 new ones), and the
controls felt cramped.

Layout fix:
  - .kpi-strip grid: repeat(5, 1fr) → repeat(8, 1fr) so the row stays
    one line and there's room for the new Awaiting QC tile.

Missing KPI added:
  - Awaiting QC — fp.job.card_state='awaiting_qc' count. Operators
    couldn't see when QC was blocking job close from the KPI strip
    (only visible inside the column). Server-side count + filter
    clause + matching filter chip.

Visual polish (all light + dark via existing token system):
  - KPI tiles: padding 6→10px, value font 20→26px, label font 9→10px,
    subtle 135deg linear-gradient bg per kind (urgent/warn/good/qc),
    hover lifts the tile with translateY + shadow.
  - Filter chips: padding 4/12→7/16px, font 11→13px, gradient bg,
    active state has gradient blue + shadow.
  - Search input: padding 5/10→9/14px, font 12→14px, focus ring.
  - Toolbar buttons (Station/All Plant/Manager/Scan QR/Hand Off):
    padding 5/10→8/14px, font 12→14px, gradients, hover lift.

Dark mode handled automatically — all gradients reference
$plant-* tokens which already have @if $o-webclient-color-scheme ==
dark global overrides in _plant_tokens.scss.

Version bump fusion_plating_shopfloor 19.0.34.0.0 → 19.0.34.1.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-25 10:58:11 -04:00
parent a2e254b934
commit caeba27846
6 changed files with 114 additions and 42 deletions

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Shop Floor',
'version': '19.0.34.0.0',
'version': '19.0.34.1.0',
'category': 'Manufacturing/Plating',
'summary': 'Shop-floor tablet stations, QR scanning, bake window enforcer.',
'description': """

View File

@@ -95,6 +95,8 @@ class PlantKanbanController(http.Controller):
)))
if filters.get('mine'):
domain.append(('card_state', 'in', ('ready_mine', 'running_mine')))
if filters.get('awaiting_qc'):
domain.append(('card_state', '=', 'awaiting_qc'))
# Spec 2026-05-25 — post-shop state filter chips
if filters.get('awaiting_cert'):
domain.append(('state', '=', 'awaiting_cert'))
@@ -145,6 +147,9 @@ class PlantKanbanController(http.Controller):
'on_hold': sum(
1 for j in jobs if j.card_state == 'on_hold'
),
'awaiting_qc': sum(
1 for j in jobs if j.card_state == 'awaiting_qc'
),
# Spec 2026-05-25 — post-shop state KPIs
'awaiting_cert': sum(
1 for j in jobs if j.state == 'awaiting_cert'

View File

@@ -1,20 +1,27 @@
// _filter_chip.scss — depends on _plant_tokens.scss
// 2026-05-25: bigger touch target + gradient bg.
.o_fp_filter_chip {
padding: 4px 12px;
font-size: 11px;
background: $plant-card-bg;
padding: 7px 16px;
font-size: 13px;
font-weight: 500;
background: linear-gradient(135deg, $plant-card-bg 0%, $plant-bg 100%);
border: 1px solid $plant-card-border;
border-radius: 14px;
border-radius: 18px;
color: $plant-muted;
cursor: pointer;
font-family: inherit;
transition: transform 0.1s ease, box-shadow 0.1s ease;
&.active {
background: #1d4ed8;
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
border-color: #1d4ed8;
color: #fff;
font-weight: 600;
box-shadow: 0 2px 4px rgba(29, 78, 216, 0.25);
}
&:hover:not(.active) {
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0,0,0,0.06);
}
&:hover:not(.active) { background: $plant-bg; }
}

View File

@@ -1,34 +1,66 @@
// _kpi_tile.scss — depends on _plant_tokens.scss
//
// 2026-05-25: redesigned for the 8-tile row. Narrower individual width
// (grid handles that), more vertical presence via padding + larger
// typography, subtle 135deg gradients per kind that work in both light
// and dark modes (gradient stops use $plant-* tokens — dark variants
// flip automatically via the @if $o-webclient-color-scheme branch).
.o_fp_kpi_tile {
padding: 6px 10px;
background: $plant-card-bg;
border-radius: 6px;
padding: 10px 12px;
background: linear-gradient(135deg, $plant-card-bg 0%, $plant-bg 100%);
border-radius: 8px;
border: 1px solid $plant-card-border;
display: flex; flex-direction: column; gap: 1px;
display: flex; flex-direction: column; gap: 3px;
cursor: pointer;
transition: background 0.1s;
transition: transform 0.1s ease, box-shadow 0.1s ease, border-color 0.1s ease;
text-align: left;
color: $plant-text;
font-family: inherit;
box-shadow: 0 1px 2px rgba(0,0,0,0.04);
min-width: 0; // grid-track minmax handles sizing; let label truncate
&:hover { background: $plant-bg; }
&:hover {
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
}
&.active {
border-color: $plant-mine-border;
background: $plant-mine-bg;
background: linear-gradient(135deg, $plant-mine-bg 0%, $plant-card-bg 100%);
box-shadow: 0 2px 6px rgba(0,0,0,0.10);
}
// Kind-specific subtle gradients. Each pulls from existing tokens
// so dark mode auto-flips via the @if $o-webclient-color-scheme
// == dark global override in _plant_tokens.scss.
&.urgent {
background: linear-gradient(135deg, $plant-card-bg 0%, $plant-hold-bg 100%);
.kpi-val { color: $plant-hold-border; }
}
&.warn {
background: linear-gradient(135deg, $plant-card-bg 0%, $plant-bake-bg 100%);
.kpi-val { color: $plant-idle-border; }
}
&.good {
background: linear-gradient(135deg, $plant-card-bg 0%, $plant-done-bg 100%);
.kpi-val { color: $plant-done-border; }
}
&.qc {
background: linear-gradient(135deg, $plant-card-bg 0%, $plant-qc-bg 100%);
.kpi-val { color: $plant-qc-border; }
}
&.urgent .kpi-val { color: $plant-hold-border; }
&.warn .kpi-val { color: $plant-idle-border; }
&.good .kpi-val { color: $plant-done-border; }
.kpi-val {
font-size: 20px; font-weight: 700;
font-size: 26px; font-weight: 700;
color: $plant-text; line-height: 1;
font-variant-numeric: tabular-nums;
}
.kpi-lbl {
font-size: 9px; font-weight: 600;
font-size: 10px; font-weight: 600;
color: $plant-muted;
text-transform: uppercase; letter-spacing: 0.04em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}

View File

@@ -34,67 +34,87 @@
.floor-title { font-size: 16px; font-weight: 700; }
.floor-controls { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
// 2026-05-25 — toolbar buttons bigger + gradients for visual weight
.station-picker {
padding: 5px 10px;
background: $plant-mine-bg;
padding: 8px 14px;
background: linear-gradient(135deg, $plant-mine-bg 0%, $plant-card-bg 100%);
border: 1px solid $plant-mine-border;
border-radius: 6px;
font-size: 12px;
border-radius: 7px;
font-size: 14px;
font-weight: 600;
color: #856404;
color: $plant-text;
cursor: pointer;
font-family: inherit;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
.mode-toggle {
display: inline-flex;
border: 1px solid $plant-card-border;
border-radius: 6px;
border-radius: 7px;
overflow: hidden;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
.mode-btn {
padding: 5px 12px;
font-size: 12px;
padding: 8px 16px;
font-size: 14px;
font-weight: 600;
background: $plant-card-bg;
background: linear-gradient(135deg, $plant-card-bg 0%, $plant-bg 100%);
color: $plant-muted;
border: 0;
cursor: pointer;
border-right: 1px solid $plant-card-border;
font-family: inherit;
&:last-child { border-right: 0; }
&.active { background: #1d4ed8; color: #fff; }
&.active {
background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
color: #fff;
}
&:hover:not(.active) { background: $plant-bg; }
}
}
.toolbar-btn {
padding: 5px 10px;
font-size: 12px;
background: $plant-card-bg;
padding: 8px 14px;
font-size: 14px;
font-weight: 500;
background: linear-gradient(135deg, $plant-card-bg 0%, $plant-bg 100%);
border: 1px solid $plant-card-border;
border-radius: 6px;
border-radius: 7px;
cursor: pointer;
color: $plant-text;
font-family: inherit;
&:hover { background: $plant-bg; }
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
transition: transform 0.1s ease, box-shadow 0.1s ease;
&:hover {
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0,0,0,0.08);
}
&.handoff {
background: #ffc107;
background: linear-gradient(135deg, #ffd966 0%, #ffc107 100%);
border-color: #d39e00;
color: #856404;
color: #5e4400;
font-weight: 700;
}
}
.kpi-strip { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; }
// 8 tiles — Work Orders, At My Station, Bakes Due, On Hold,
// Awaiting QC, Awaiting CoC, Ready to Ship, Overdue.
.kpi-strip { display: grid; grid-template-columns: repeat(8, 1fr); gap: 8px; }
.search-row { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.search-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.search-input {
flex: 1; min-width: 200px;
padding: 5px 10px;
flex: 1; min-width: 220px;
padding: 9px 14px;
border: 1px solid $plant-card-border;
border-radius: 6px;
border-radius: 8px;
background: $plant-card-bg;
color: $plant-text;
font-size: 12px;
font-size: 14px;
font-family: inherit;
box-shadow: 0 1px 2px rgba(0,0,0,0.04) inset;
&:focus {
outline: none;
border-color: #1d4ed8;
box-shadow: 0 0 0 3px rgba(29, 78, 216, 0.15);
}
}
// Board: fixed-width columns with horizontal scroll on smaller

View File

@@ -47,6 +47,11 @@
kind="'urgent'"
active="!!state.filters.on_hold"
onClick="() => this.toggleFilter('on_hold')"/>
<FpKpiTile value="state.data.kpis.awaiting_qc"
label="'Awaiting QC'"
kind="'qc'"
active="!!state.filters.awaiting_qc"
onClick="() => this.toggleFilter('awaiting_qc')"/>
<!-- Spec 2026-05-25 — post-shop state tiles -->
<FpKpiTile value="state.data.kpis.awaiting_cert"
label="'Awaiting CoC'"
@@ -89,6 +94,9 @@
<FpFilterChip label="'FAIR'"
active="!!state.filters.fair"
onToggle="() => this.toggleFilter('fair')"/>
<FpFilterChip label="'Awaiting QC'"
active="!!state.filters.awaiting_qc"
onToggle="() => this.toggleFilter('awaiting_qc')"/>
<!-- Spec 2026-05-25 — post-shop state chips -->
<FpFilterChip label="'Awaiting CoC'"
active="!!state.filters.awaiting_cert"