From 298f5942ebc877ca43c74c62b9ee3e343f234824 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Sat, 18 Apr 2026 18:30:47 -0400 Subject: [PATCH] refactor(shopfloor): modern redesign w/ gradients, theme-safe tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shop-floor operators and managers live on these screens all day — the old look worked but felt like a spec sheet. Pass through all 5 pages with one design language: gradient KPI cards, hero banners, soft shadows, rounded corners, large touch targets, friendly empty states. Dark mode and light mode both look deliberate, not inverted. New shared file: _fp_shopfloor_tokens.scss A single source of truth for radii, elevation (shadows that respect dark mode via color-mix on foreground), typography scale (tabular numerics for KPIs, 18px base for shop-floor readability), animation easings, semantic gradients (@mixin fp-grad), tone helpers (@mixin fp-tone), focus ring, and the 44px touch-min token. Every other SCSS file imports this — no duplicated colour math. Gradients are built on color-mix(in srgb, var(--bs-foo) X%, transparent) so they layer naturally on either the light or dark page background. No @media-prefers-color-scheme forks needed. Tablet Station (fusion_plating_shopfloor.scss): * Hero banner with dual radial-gradient wash (brand + success), live station chip, gradient focus ring on the picker. * KPI cards (6-up on desktop, 2x3 on phone) get a subtle top accent line, coloured gradient overlay, 40px headline number, faded icon at corner. Tone variants (info/success/warning/danger/muted) drive colour without extra CSS. * Active WO banner is a green gradient pill with a breathing-dot pulse — unmissable when something is running. * Panels get top accents, queue rows get priority pills (HI/M/·), bake/gate/hold rows get colour-coded left accent bars. * Tiles have a 4px left stripe keyed to state + hover lift. * Status chips are uppercase, pill-shaped, tone-tinted with color-mix so they respect theme. * Empty states now have a large 36px icon + friendly copy instead of a one-liner. * Focus rings use the shared @mixin fp-focus-ring. Manager Desk (manager_dashboard.scss): * Same hero treatment with radial gradient + live-dot pulse. * 3 panels carry a coloured top accent bar — amber (Unassigned), green (In Progress), blue (Team). Instant visual routing. * KPI strip matches tablet. * MO cards get a left priority stripe (red for HOT, amber for Urgent), lift on hover, expand cleanly. * Team avatars get a border + subtle tint background for depth. * Worker/tank pickers have custom focus rings. Plant Overview (plant_overview.scss): * Header is now a gradient wash tied to the brand colour. * Work-centre columns get a thin gradient top-stripe and pill-style count badges. * Cards have real depth (layered shadow), lift harder on hover, change border colour on hover. All three files share the same design tokens, so colours/shadows/ radii are identical across pages. Edit one place, everything updates. Verified: backend asset bundle compiles clean (no SCSS errors), zero warnings on module upgrade, asset cache cleared for fresh delivery. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../static/src/scss/_fp_shopfloor_tokens.scss | 115 +++ .../src/scss/fusion_plating_shopfloor.scss | 794 +++++++++++------- .../static/src/scss/manager_dashboard.scss | 524 ++++++++---- .../static/src/scss/plant_overview.scss | 121 +-- .../static/src/xml/manager_dashboard.xml | 10 +- .../static/src/xml/shopfloor_tablet.xml | 13 +- 6 files changed, 1055 insertions(+), 522 deletions(-) create mode 100644 fusion_plating/fusion_plating_shopfloor/static/src/scss/_fp_shopfloor_tokens.scss diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/scss/_fp_shopfloor_tokens.scss b/fusion_plating/fusion_plating_shopfloor/static/src/scss/_fp_shopfloor_tokens.scss new file mode 100644 index 00000000..4f601aea --- /dev/null +++ b/fusion_plating/fusion_plating_shopfloor/static/src/scss/_fp_shopfloor_tokens.scss @@ -0,0 +1,115 @@ +// ============================================================================= +// Fusion Plating — Shop-Floor Design Tokens +// Copyright 2026 Nexa Systems Inc. +// License OPL-1 (Odoo Proprietary License v1.0) +// +// Single source of truth for the look-and-feel of every shop-floor OWL page. +// All values use CSS custom properties (--bs-*, --o-*) so the tokens adapt +// automatically between Odoo's light and dark themes — no media queries or +// duplicate palettes. +// +// Gradients use `color-mix(in srgb, var(--bs-foo) X%, transparent)` because +// transparent mixes with the page background, which already obeys the theme. +// ============================================================================= + + +// ---------- Radii ------------------------------------------------------------ +$fp-radius-sm : 8px; +$fp-radius-md : 12px; +$fp-radius-lg : 16px; +$fp-radius-xl : 20px; +$fp-radius-pill : 999px; + + +// ---------- Elevation (shadows that respect dark mode) ----------------------- +// Shadows are built on the page's foreground colour so they darken in light +// mode and deepen (but never go pure black) in dark mode. +$fp-shadow-xs : 0 1px 2px color-mix(in srgb, var(--bs-body-color) 6%, transparent); +$fp-shadow-sm : 0 2px 6px color-mix(in srgb, var(--bs-body-color) 9%, transparent); +$fp-shadow-md : 0 4px 14px color-mix(in srgb, var(--bs-body-color) 10%, transparent), + 0 1px 3px color-mix(in srgb, var(--bs-body-color) 6%, transparent); +$fp-shadow-lg : 0 10px 28px color-mix(in srgb, var(--bs-body-color) 14%, transparent), + 0 2px 6px color-mix(in srgb, var(--bs-body-color) 6%, transparent); +$fp-shadow-glow-primary : 0 0 0 4px color-mix(in srgb, var(--o-action) 12%, transparent); + + +// ---------- Surface layering ------------------------------------------------- +// A "raised" surface tint — sits slightly above the base background. Uses +// the foreground colour to tint so it deepens correctly in dark mode. +$fp-surface : var(--o-view-background-color, var(--bs-body-bg)); +$fp-surface-raised : color-mix(in srgb, + var(--bs-body-color) 2%, + var(--o-view-background-color, var(--bs-body-bg))); +$fp-surface-sunken : color-mix(in srgb, + var(--bs-body-color) 4%, + var(--o-view-background-color, var(--bs-body-bg))); + + +// ---------- Border-tints ----------------------------------------------------- +$fp-border : var(--bs-border-color); +$fp-border-strong : color-mix(in srgb, var(--bs-body-color) 18%, transparent); +$fp-border-accent : color-mix(in srgb, var(--o-action) 40%, var(--bs-border-color)); + + +// ---------- Typography ------------------------------------------------------- +// Shop-floor tablets and phones get read from 18" away — bumps over Odoo's +// default body size. Numbers are tabular so KPIs don't jitter on refresh. +$fp-font-base : 1rem; // 16px +$fp-font-sm : 0.875rem; // 14px +$fp-font-xs : 0.78rem; // 12.5px +$fp-font-lg : 1.125rem; // 18px +$fp-font-xl : 1.375rem; // 22px +$fp-font-2xl : 1.75rem; // 28px +$fp-font-3xl : 2.25rem; // 36px +$fp-font-kpi : 2.5rem; // 40px — headline number +$fp-font-hero : clamp(1.5rem, 3.5vw, 2.25rem); + + +// ---------- Animation -------------------------------------------------------- +$fp-ease : cubic-bezier(0.22, 1, 0.36, 1); +$fp-ease-snap : cubic-bezier(0.34, 1.56, 0.64, 1); +$fp-dur-fast : 140ms; +$fp-dur : 220ms; +$fp-dur-slow : 360ms; + + +// ---------- Semantic gradients (tone-based, theme-safe) ---------------------- +// Each tone mixes the named Bootstrap token with transparent so it lays +// naturally over whatever page background it's on — light OR dark. +@mixin fp-grad($color-var, $strong: 18%, $soft: 6%) { + background-image: linear-gradient( + 135deg, + color-mix(in srgb, var(#{$color-var}) #{$strong}, transparent) 0%, + color-mix(in srgb, var(#{$color-var}) #{$soft}, transparent) 100% + ); +} + +@mixin fp-grad-solid($color-var, $from: 100%, $to: 75%) { + background-image: linear-gradient( + 135deg, + color-mix(in srgb, var(#{$color-var}) #{$from}, transparent) 0%, + color-mix(in srgb, var(#{$color-var}) #{$to}, transparent) 100% + ); + color: var(--o-we-text-on-action, #fff); +} + + +// ---------- Tonal helpers ---------------------------------------------------- +// Border + tint combo used on banners / pills — reads as "slightly +// saturated panel" in both themes. +@mixin fp-tone($color-var, $strength: 14%) { + background-color: color-mix(in srgb, var(#{$color-var}) #{$strength}, transparent); + color: var(#{$color-var}); + border: 1px solid color-mix(in srgb, var(#{$color-var}) 38%, transparent); +} + + +// ---------- Focus ring ------------------------------------------------------- +@mixin fp-focus-ring() { + outline: none; + box-shadow: 0 0 0 3px color-mix(in srgb, var(--o-action) 35%, transparent); +} + + +// ---------- Touch target ----------------------------------------------------- +$fp-touch-min : 44px; // Apple HIG minimum diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/scss/fusion_plating_shopfloor.scss b/fusion_plating/fusion_plating_shopfloor/static/src/scss/fusion_plating_shopfloor.scss index ab778885..8d2c68bc 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/scss/fusion_plating_shopfloor.scss +++ b/fusion_plating/fusion_plating_shopfloor/static/src/scss/fusion_plating_shopfloor.scss @@ -1,39 +1,20 @@ // ============================================================================= -// Fusion Plating — Shop Floor backend / tablet styles +// Fusion Plating — Shop-Floor Tablet Station // Copyright 2026 Nexa Systems Inc. // License OPL-1 (Odoo Proprietary License v1.0) // -// THEME AWARENESS -// --------------- -// All colours come from CSS custom properties (Bootstrap / Odoo tokens) so -// the tablet view renders correctly in BOTH light and dark mode without any -// duplication or media queries. Status tints use color-mix() against the -// theme token so green/yellow/red adapt to the surface. -// -// background: var(--bs-body-bg) -// surface: var(--o-view-background-color) -// foreground: var(--bs-body-color) -// muted text: var(--bs-secondary-color) -// border: var(--bs-border-color) -// primary: var(--o-action) +// Modernised 2026-04 design: hero banner, gradient KPI cards, tap-first +// action rows. Works in Odoo's light + dark themes because every colour +// resolves from CSS custom properties or color-mix'd tokens. // ============================================================================= - -// ----------------------------------------------------------------------------- -// Local mixin — semantic tint that respects light/dark mode -// ----------------------------------------------------------------------------- -@mixin fp-shop-tint($color-var, $amount: 14%) { - background-color: color-mix(in srgb, var(#{$color-var}) #{$amount}, transparent); - color: var(#{$color-var}); - border: 1px solid color-mix(in srgb, var(#{$color-var}) 35%, transparent); -} +@import "./fp_shopfloor_tokens"; -// ----------------------------------------------------------------------------- -// Global touch tweaks — apply to every shop-floor page -// ----------------------------------------------------------------------------- +// ============================================================================= +// Global touch / hover suppression (kept from previous rev) +// ============================================================================= @media (hover: none) { - // Hover highlights stick on tap — disable them on touch devices. .o_fp_tablet .o_fp_queue_row:hover, .o_fp_tablet .o_fp_tile:hover, .o_fp_tablet .o_fp_tablet_card:hover { @@ -44,442 +25,643 @@ } -// ----------------------------------------------------------------------------- -// Tablet root container — large touch targets, generous whitespace -// ----------------------------------------------------------------------------- +// ============================================================================= +// Root container +// ============================================================================= .o_fp_tablet { - background-color: var(--o-view-background-color, var(--bs-body-bg)); + background-color: $fp-surface-sunken; color: var(--bs-body-color); min-height: 100%; - padding: 20px 24px; - font-size: 1rem; + padding: 24px 28px; + font-size: $fp-font-base; display: flex; flex-direction: column; - gap: 14px; + gap: 18px; - // iPad portrait / small tablet - @media (max-width: 900px) { padding: 14px 12px; gap: 10px; } - // Phone - @media (max-width: 600px) { padding: 10px 8px; gap: 8px; font-size: 0.95rem; } + @media (max-width: 900px) { padding: 16px; gap: 14px; } + @media (max-width: 600px) { padding: 10px 10px 16px; gap: 10px; } - // ---------- Header ------------------------------------------------------- + + // ========================================================================= + // Hero banner — sits at the top, shows who's logged in + station + // ========================================================================= .o_fp_tablet_header { - display: flex; - justify-content: space-between; - align-items: center; + position: relative; + overflow: hidden; + border-radius: $fp-radius-lg; + padding: 20px 24px; + background-color: $fp-surface-raised; + border: 1px solid $fp-border; + box-shadow: $fp-shadow-sm; + + // Brand gradient wash — primary + success blend reads as + // "productive activity". Low opacity so text stays readable. + &::before { + content: ""; + position: absolute; + inset: 0; + background-image: + radial-gradient(circle at 0% 0%, + color-mix(in srgb, var(--o-action) 22%, transparent) 0%, + transparent 55%), + radial-gradient(circle at 100% 100%, + color-mix(in srgb, var(--bs-success) 20%, transparent) 0%, + transparent 55%); + pointer-events: none; + z-index: 0; + } + + display: grid; + grid-template-columns: 1fr auto; gap: 16px; - flex-wrap: wrap; + align-items: center; + > * { position: relative; z-index: 1; } @media (max-width: 600px) { - flex-direction: column; - align-items: stretch; - gap: 8px; - - .o_fp_tablet_header_actions { - width: 100%; - flex-wrap: wrap; - > * { flex: 1; min-width: 0; } - } + padding: 14px 14px 12px; + grid-template-columns: 1fr; + gap: 10px; } } .o_fp_tablet_title { - font-size: 1.5rem; - font-weight: 600; - @media (max-width: 600px) { font-size: 1.15rem; } + font-size: $fp-font-hero; + font-weight: 700; + line-height: 1.1; + letter-spacing: -0.01em; + color: var(--bs-body-color); + display: flex; align-items: center; gap: 10px; } .o_fp_tablet_subtitle { - font-size: 0.95rem; + margin-top: 4px; + font-size: $fp-font-sm; color: var(--bs-secondary-color); + display: flex; flex-wrap: wrap; gap: 10px; } .o_fp_tablet_chip { display: inline-flex; align-items: center; - gap: 4px; - padding: 2px 10px; - background-color: color-mix(in srgb, var(--o-action) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--o-action) 35%, transparent); - color: var(--o-action); - border-radius: 999px; - font-size: 0.9rem; + gap: 6px; + padding: 4px 12px; + border-radius: $fp-radius-pill; + font-size: $fp-font-xs; + font-weight: 600; + letter-spacing: 0.02em; + @include fp-tone(--o-action, 14%); } .o_fp_tablet_header_actions { - display: flex; - gap: 8px; - align-items: center; + display: flex; gap: 8px; align-items: center; + @media (max-width: 600px) { + width: 100%; flex-wrap: wrap; + > * { flex: 1; min-width: 0; } + } } .o_fp_station_picker { min-width: 240px; - max-width: 320px; - min-height: 38px; - @media (max-width: 600px) { - min-width: 0; - max-width: 100%; - } + max-width: 340px; + min-height: $fp-touch-min; + padding: 8px 14px; + border-radius: $fp-radius-md; + background-color: $fp-surface; + border: 1px solid $fp-border; + color: var(--bs-body-color); + font-size: $fp-font-base; + transition: border-color $fp-dur $fp-ease, box-shadow $fp-dur $fp-ease; + &:focus { @include fp-focus-ring; border-color: var(--o-action); } + @media (max-width: 600px) { min-width: 0; max-width: 100%; } } .o_fp_scan_toggle { - white-space: nowrap; - min-height: 44px; + min-height: $fp-touch-min; + padding: 8px 18px; + border-radius: $fp-radius-md; + font-weight: 600; + transition: transform $fp-dur-fast $fp-ease; + &:active { transform: scale(0.97); } } - // ---------- Scan drawer -------------------------------------------------- + + // ========================================================================= + // Scan drawer + // ========================================================================= .o_fp_scan_drawer { - display: flex; - gap: 10px; - padding: 12px; - background-color: color-mix(in srgb, var(--o-action) 6%, var(--o-view-background-color, var(--bs-body-bg))); - border: 1px dashed color-mix(in srgb, var(--o-action) 40%, transparent); - border-radius: 10px; + display: flex; gap: 10px; + padding: 14px; + border-radius: $fp-radius-lg; + background-color: $fp-surface-raised; + border: 1px dashed $fp-border-accent; + box-shadow: $fp-shadow-xs; } - // ---------- Flash message ------------------------------------------------ + + // ========================================================================= + // Flash message + // ========================================================================= .o_fp_tablet_message { - padding: 10px 14px; - border-radius: 8px; - font-size: 1rem; - &.o_fp_msg_info { @include fp-shop-tint(--bs-info); } - &.o_fp_msg_success { @include fp-shop-tint(--bs-success); } - &.o_fp_msg_warning { @include fp-shop-tint(--bs-warning); } - &.o_fp_msg_danger { @include fp-shop-tint(--bs-danger); } + padding: 12px 16px; + border-radius: $fp-radius-md; + font-size: $fp-font-base; + font-weight: 500; + display: flex; align-items: center; gap: 10px; + box-shadow: $fp-shadow-xs; + + &.o_fp_msg_info { @include fp-tone(--bs-info); } + &.o_fp_msg_success { @include fp-tone(--bs-success); } + &.o_fp_msg_warning { @include fp-tone(--bs-warning); } + &.o_fp_msg_danger { @include fp-tone(--bs-danger); } } - // ---------- KPI strip ---------------------------------------------------- + + // ========================================================================= + // KPI strip — gradient tiles, big numbers + // ========================================================================= .o_fp_kpi_strip { display: grid; - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); - gap: 10px; + grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); + gap: 12px; + @media (max-width: 600px) { - // 2 columns on phone so 6 tiles wrap to 3 rows instead of 6 stacked grid-template-columns: repeat(2, 1fr); - gap: 6px; + gap: 8px; } } .o_fp_kpi { position: relative; - padding: 12px 14px; - border: 1px solid var(--bs-border-color); - border-radius: 10px; - background-color: var(--o-view-background-color, var(--bs-body-bg)); + overflow: hidden; + padding: 18px 18px 16px; + border-radius: $fp-radius-lg; + background-color: $fp-surface-raised; + border: 1px solid $fp-border; + box-shadow: $fp-shadow-sm; + transition: transform $fp-dur $fp-ease, + box-shadow $fp-dur $fp-ease, + border-color $fp-dur $fp-ease; display: flex; flex-direction: column; - gap: 2px; - transition: border-color 120ms ease, box-shadow 120ms ease; - @media (max-width: 600px) { - padding: 8px 10px; - .o_fp_kpi_value { font-size: 1.4rem !important; } - .o_fp_kpi_label { font-size: 0.72rem !important; } - > .fa { font-size: 1rem !important; top: 8px; right: 8px; } + gap: 4px; + min-height: 104px; + + // Accent bar along the top edge + &::before { + content: ""; + position: absolute; + top: 0; left: 0; right: 0; height: 3px; + background-image: linear-gradient(90deg, transparent, currentColor, transparent); + opacity: 0.7; } + // Soft tone overlay + &::after { + content: ""; + position: absolute; + inset: 0; + opacity: 0.35; + pointer-events: none; + z-index: 0; + } + + @media (hover: hover) { + &:hover { + transform: translateY(-2px); + box-shadow: $fp-shadow-md; + } + } + > * { position: relative; z-index: 1; } > .fa { position: absolute; - right: 12px; - top: 12px; - font-size: 1.4rem; - opacity: 0.45; + right: 14px; top: 14px; + font-size: 1.5rem; + opacity: 0.28; + color: currentColor; } - .o_fp_kpi_value { font-size: 1.8rem; font-weight: 700; line-height: 1; } - .o_fp_kpi_label { font-size: 0.85rem; color: var(--bs-secondary-color); text-transform: uppercase; letter-spacing: 0.03em; } - &.o_fp_kpi_info { border-left: 4px solid var(--bs-info); } - &.o_fp_kpi_success { border-left: 4px solid var(--bs-success); } - &.o_fp_kpi_warning { border-left: 4px solid var(--bs-warning); } + .o_fp_kpi_value { + font-size: $fp-font-kpi; + font-weight: 800; + line-height: 1; + letter-spacing: -0.02em; + font-variant-numeric: tabular-nums; + color: var(--bs-body-color); + } + .o_fp_kpi_label { + font-size: $fp-font-xs; + color: var(--bs-secondary-color); + text-transform: uppercase; + letter-spacing: 0.06em; + font-weight: 600; + } + + &.o_fp_kpi_info { color: var(--bs-info); &::after { @include fp-grad(--bs-info); } } + &.o_fp_kpi_success { color: var(--bs-success); &::after { @include fp-grad(--bs-success); } } + &.o_fp_kpi_warning { color: var(--bs-warning); &::after { @include fp-grad(--bs-warning); } } &.o_fp_kpi_danger { - border-left: 4px solid var(--bs-danger); - background-color: color-mix(in srgb, var(--bs-danger) 5%, var(--o-view-background-color, var(--bs-body-bg))); + color: var(--bs-danger); + border-color: color-mix(in srgb, var(--bs-danger) 45%, var(--bs-border-color)); + &::after { @include fp-grad(--bs-danger, 22%, 8%); } .o_fp_kpi_value { color: var(--bs-danger); } } - &.o_fp_kpi_muted { border-left: 4px solid var(--bs-border-color); opacity: 0.8; } + &.o_fp_kpi_muted { color: var(--bs-secondary-color); &::after { opacity: 0; } } + + @media (max-width: 600px) { + min-height: 84px; + padding: 12px 12px 10px; + .o_fp_kpi_value { font-size: 1.6rem; } + .o_fp_kpi_label { font-size: 0.68rem; } + > .fa { font-size: 1rem; top: 10px; right: 10px; } + } } - // ---------- Active WO banner -------------------------------------------- + + // ========================================================================= + // Active WO banner — green gradient "you're running this right now" + // ========================================================================= .o_fp_active_wo { + position: relative; + overflow: hidden; display: flex; justify-content: space-between; align-items: center; gap: 14px; - padding: 12px 16px; - border: 1px solid color-mix(in srgb, var(--bs-success) 40%, var(--bs-border-color)); - background-color: color-mix(in srgb, var(--bs-success) 7%, var(--o-view-background-color, var(--bs-body-bg))); - border-radius: 10px; + padding: 16px 20px; + border-radius: $fp-radius-lg; + border: 1px solid color-mix(in srgb, var(--bs-success) 45%, $fp-border); + box-shadow: $fp-shadow-sm; + @include fp-grad(--bs-success, 14%, 4%); + @media (max-width: 600px) { - flex-direction: column; - align-items: stretch; - gap: 8px; - padding: 10px 12px; - > .btn { width: 100%; min-height: 44px; } + flex-direction: column; align-items: stretch; gap: 10px; + padding: 14px; border-radius: $fp-radius-md; + > .btn { width: 100%; min-height: $fp-touch-min; } } } .o_fp_active_wo_left { - display: flex; - align-items: center; - gap: 12px; + display: flex; align-items: center; gap: 14px; + } + .o_fp_active_wo_title { + font-weight: 700; font-size: $fp-font-lg; letter-spacing: -0.01em; + } + .o_fp_active_wo_meta { + color: var(--bs-secondary-color); font-size: $fp-font-sm; margin-top: 2px; } - .o_fp_active_wo_title { font-weight: 600; font-size: 1.05rem; } - .o_fp_active_wo_meta { color: var(--bs-secondary-color); font-size: 0.9rem; } .o_fp_active_wo_pulse { - width: 12px; height: 12px; border-radius: 50%; + flex-shrink: 0; + width: 14px; height: 14px; border-radius: 50%; background-color: var(--bs-success); - box-shadow: 0 0 0 0 color-mix(in srgb, var(--bs-success) 60%, transparent); - animation: o_fp_pulse 1.4s infinite; + animation: o_fp_pulse 1.4s ease-in-out infinite; } @keyframes o_fp_pulse { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--bs-success) 60%, transparent); } - 70% { box-shadow: 0 0 0 10px color-mix(in srgb, var(--bs-success) 0%, transparent); } + 70% { box-shadow: 0 0 0 12px color-mix(in srgb, var(--bs-success) 0%, transparent); } 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--bs-success) 0%, transparent); } } - // ---------- Dashboard layout -------------------------------------------- + + // ========================================================================= + // Dashboard layout + // ========================================================================= .o_fp_tablet_dashboard { display: grid; - grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); - gap: 14px; + grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); + gap: 16px; @media (max-width: 1100px) { grid-template-columns: 1fr; } - @media (max-width: 600px) { gap: 8px; } + @media (max-width: 600px) { gap: 10px; } } .o_fp_right_col { - display: flex; - flex-direction: column; - gap: 14px; - @media (max-width: 600px) { gap: 8px; } + display: flex; flex-direction: column; gap: 16px; + @media (max-width: 600px) { gap: 10px; } } - // ---------- Panel (reusable card) --------------------------------------- + + // ========================================================================= + // Panels (reusable card) + // ========================================================================= .o_fp_panel { - background-color: var(--o-view-background-color, var(--bs-body-bg)); - border: 1px solid var(--bs-border-color); - border-radius: 12px; - padding: 14px 16px; + background-color: $fp-surface-raised; + border: 1px solid $fp-border; + border-radius: $fp-radius-lg; + padding: 18px 20px; + box-shadow: $fp-shadow-xs; + @media (max-width: 600px) { - padding: 10px 12px; - border-radius: 10px; + padding: 12px 14px; border-radius: $fp-radius-md; } } .o_fp_panel_head { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 10px; - padding-bottom: 8px; - border-bottom: 1px dashed var(--bs-border-color); - h3 { font-size: 1.05rem; font-weight: 600; margin: 0; } + display: flex; justify-content: space-between; align-items: center; + margin-bottom: 14px; + padding-bottom: 10px; + border-bottom: 1px solid $fp-border; + + h3 { + font-size: $fp-font-lg; + font-weight: 700; + margin: 0; + display: inline-flex; align-items: center; gap: 10px; + letter-spacing: -0.01em; + } } .o_fp_panel_count { - background-color: color-mix(in srgb, var(--bs-body-color) 6%, transparent); - color: var(--bs-body-color); - border-radius: 999px; - padding: 1px 10px; - font-size: 0.85rem; - font-weight: 600; - } - .o_fp_empty { - padding: 14px; - text-align: center; - color: var(--bs-secondary-color); - font-size: 0.95rem; + min-width: 34px; + padding: 3px 12px; + border-radius: $fp-radius-pill; + font-weight: 700; + font-size: $fp-font-sm; + font-variant-numeric: tabular-nums; + @include fp-tone(--bs-body-color, 8%); } - // ---------- My Queue list ----------------------------------------------- + + // ========================================================================= + // Empty state + // ========================================================================= + .o_fp_empty { + padding: 32px 16px; + text-align: center; + color: var(--bs-secondary-color); + font-size: $fp-font-base; + + i.fa { + display: block; + font-size: 2.25rem; + margin-bottom: 10px; + opacity: 0.55; + } + } + + + // ========================================================================= + // My Queue + // ========================================================================= .o_fp_queue_list { - list-style: none; - margin: 0; padding: 0; - display: flex; - flex-direction: column; - gap: 8px; + list-style: none; margin: 0; padding: 0; + display: flex; flex-direction: column; gap: 10px; } .o_fp_queue_row { display: grid; - grid-template-columns: 40px 1fr auto; + grid-template-columns: 44px 1fr auto; align-items: center; - gap: 10px; - padding: 10px 12px; - border: 1px solid var(--bs-border-color); - border-radius: 8px; - transition: background-color 120ms ease, border-color 120ms ease; - min-height: 56px; // comfortable tap zone + gap: 12px; + padding: 12px 14px; + border: 1px solid $fp-border; + border-radius: $fp-radius-md; + background-color: $fp-surface; + min-height: 64px; + transition: background-color $fp-dur $fp-ease, + border-color $fp-dur $fp-ease, + transform $fp-dur-fast $fp-ease, + box-shadow $fp-dur $fp-ease; + @media (hover: hover) { &:hover { - background-color: color-mix(in srgb, var(--o-action) 7%, var(--o-view-background-color, var(--bs-body-bg))); - border-color: color-mix(in srgb, var(--o-action) 40%, var(--bs-border-color)); + transform: translateY(-1px); + background-color: color-mix(in srgb, var(--o-action) 4%, $fp-surface); + border-color: $fp-border-accent; + box-shadow: $fp-shadow-sm; } } @media (max-width: 600px) { - grid-template-columns: 32px 1fr; - // Action buttons drop to their own row + grid-template-columns: 36px 1fr; + padding: 10px 12px; + .o_fp_queue_actions { grid-column: 1 / -1; - justify-content: flex-end; - margin-top: 4px; + justify-content: stretch; + margin-top: 6px; > .btn { flex: 1; } } } } - .o_fp_queue_body { cursor: pointer; } - .o_fp_queue_actions { - display: flex; - gap: 6px; - align-items: center; - .btn { min-height: 44px; } // touch-friendly - } - .o_fp_queue_label { font-weight: 600; } - .o_fp_queue_desc { font-size: 0.88rem; color: var(--bs-secondary-color); } - .o_fp_queue_pri { - display: flex; - align-items: center; - justify-content: center; - width: 36px; height: 36px; - border-radius: 8px; + .o_fp_queue_body { cursor: pointer; min-width: 0; } + .o_fp_queue_label { font-weight: 700; - font-size: 0.82rem; - &[data-priority="high"] { background-color: color-mix(in srgb, var(--bs-danger) 15%, transparent); color: var(--bs-danger); } - &[data-priority="med"] { background-color: color-mix(in srgb, var(--bs-warning) 15%, transparent); color: var(--bs-warning); } - &[data-priority="low"] { background-color: color-mix(in srgb, var(--bs-body-color) 8%, transparent); color: var(--bs-secondary-color); } + font-size: $fp-font-base; + color: var(--bs-body-color); } + .o_fp_queue_desc { + font-size: $fp-font-sm; + color: var(--bs-secondary-color); + margin-top: 2px; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + } + .o_fp_queue_actions { + display: flex; gap: 6px; align-items: center; + .btn { min-height: $fp-touch-min; font-weight: 600; border-radius: $fp-radius-md; } + } + .o_fp_queue_pri { + display: flex; align-items: center; justify-content: center; + width: 40px; height: 40px; border-radius: $fp-radius-md; + font-weight: 800; + font-size: 0.78rem; + letter-spacing: 0.02em; - // ---------- Bath tiles -------------------------------------------------- - .o_fp_tile_grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - gap: 10px; - @media (max-width: 600px) { - grid-template-columns: 1fr 1fr; - gap: 6px; + &[data-priority="high"] { + @include fp-grad(--bs-danger, 30%, 12%); + color: var(--bs-danger); + border: 1px solid color-mix(in srgb, var(--bs-danger) 40%, transparent); + } + &[data-priority="med"] { + @include fp-grad(--bs-warning, 28%, 10%); + color: var(--bs-warning); + border: 1px solid color-mix(in srgb, var(--bs-warning) 40%, transparent); + } + &[data-priority="low"] { + background-color: color-mix(in srgb, var(--bs-body-color) 5%, transparent); + color: var(--bs-secondary-color); + border: 1px solid $fp-border; } } - .o_fp_tile { - border: 1px solid var(--bs-border-color); - border-radius: 10px; - padding: 10px 12px; - cursor: pointer; - transition: border-color 120ms ease, background-color 120ms ease; - &:hover { border-color: color-mix(in srgb, var(--o-action) 40%, var(--bs-border-color)); } - &[data-tone="danger"] { border-left: 4px solid var(--bs-danger); } - &[data-tone="warning"] { border-left: 4px solid var(--bs-warning); } - &[data-tone="success"] { border-left: 4px solid var(--bs-success); } - &[data-tone="info"] { border-left: 4px solid var(--bs-info); } + + // ========================================================================= + // Bath / Gate / Hold tiles + rows + // ========================================================================= + .o_fp_tile_grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 12px; + @media (max-width: 600px) { grid-template-columns: 1fr 1fr; gap: 8px; } } - .o_fp_tile_title { font-weight: 600; margin-bottom: 2px; } - .o_fp_tile_meta { font-size: 0.85rem; color: var(--bs-secondary-color); margin-bottom: 6px; } - .o_fp_tile_chips { display: flex; flex-wrap: wrap; gap: 4px; } + .o_fp_tile { + position: relative; overflow: hidden; + border: 1px solid $fp-border; + border-radius: $fp-radius-md; + padding: 14px 16px; + background-color: $fp-surface; + cursor: pointer; + transition: border-color $fp-dur $fp-ease, + transform $fp-dur-fast $fp-ease, + box-shadow $fp-dur $fp-ease; - // ---------- Chips ------------------------------------------------------- + @media (hover: hover) { + &:hover { + transform: translateY(-2px); + border-color: $fp-border-accent; + box-shadow: $fp-shadow-sm; + } + } + + // Accent stripe by tone + &::before { + content: ""; + position: absolute; + left: 0; top: 0; bottom: 0; width: 4px; + } + &[data-tone="success"]::before { background: var(--bs-success); } + &[data-tone="info"]::before { background: var(--bs-info); } + &[data-tone="warning"]::before { background: var(--bs-warning); } + &[data-tone="danger"]::before { background: var(--bs-danger); } + &[data-tone="muted"]::before { background: $fp-border-strong; } + } + .o_fp_tile_title { + font-weight: 700; font-size: $fp-font-base; + margin-bottom: 4px; padding-left: 6px; + } + .o_fp_tile_meta { + font-size: $fp-font-sm; color: var(--bs-secondary-color); + margin-bottom: 8px; padding-left: 6px; + } + .o_fp_tile_chips { + display: flex; flex-wrap: wrap; gap: 6px; padding-left: 6px; + } + + + // ========================================================================= + // Status chips + // ========================================================================= .o_fp_chip { - display: inline-block; - padding: 1px 8px; - border-radius: 999px; - font-size: 0.78rem; - font-weight: 600; + display: inline-flex; + align-items: center; + gap: 4px; + padding: 3px 10px; + border-radius: $fp-radius-pill; + font-size: 0.72rem; + font-weight: 700; text-transform: uppercase; - letter-spacing: 0.03em; + letter-spacing: 0.04em; + line-height: 1.4; - &.o_fp_chip_info { @include fp-shop-tint(--bs-info); } - &.o_fp_chip_success { @include fp-shop-tint(--bs-success); } - &.o_fp_chip_warning { @include fp-shop-tint(--bs-warning); } - &.o_fp_chip_danger { @include fp-shop-tint(--bs-danger); } + &.o_fp_chip_info { @include fp-tone(--bs-info); } + &.o_fp_chip_success { @include fp-tone(--bs-success); } + &.o_fp_chip_warning { @include fp-tone(--bs-warning); } + &.o_fp_chip_danger { @include fp-tone(--bs-danger); } &.o_fp_chip_muted { background-color: color-mix(in srgb, var(--bs-body-color) 6%, transparent); color: var(--bs-secondary-color); - border: 1px solid var(--bs-border-color); + border: 1px solid $fp-border; } } - // ---------- Bake / Gate / Hold rows ------------------------------------- + + // ========================================================================= + // Bake / Gate / Hold rows — accent-left banding by state + // ========================================================================= .o_fp_bake_list { - list-style: none; - margin: 0; padding: 0; - display: flex; - flex-direction: column; - gap: 8px; + list-style: none; margin: 0; padding: 0; + display: flex; flex-direction: column; gap: 10px; } .o_fp_bake_row { + position: relative; display: grid; grid-template-columns: 1fr auto auto; align-items: center; - gap: 10px; - padding: 8px 10px; - border: 1px solid var(--bs-border-color); - border-radius: 8px; - min-height: 56px; + gap: 12px; + padding: 12px 14px 12px 18px; + border: 1px solid $fp-border; + border-radius: $fp-radius-md; + background-color: $fp-surface; + min-height: 64px; + transition: transform $fp-dur-fast $fp-ease, box-shadow $fp-dur $fp-ease; + + @media (hover: hover) { &:hover { box-shadow: $fp-shadow-sm; } } + + // Left accent bar by state + &::before { + content: ""; + position: absolute; + top: 8px; bottom: 8px; left: 6px; + width: 4px; border-radius: 4px; + background-color: $fp-border-strong; + } + &[data-state="awaiting_bake"]::before, &[data-state="pending"]::before { + background-color: var(--bs-warning); + } + &[data-state="bake_in_progress"]::before, &[data-state="under_review"]::before { + background-color: var(--bs-info); + } + &[data-state="missed_window"]::before, &[data-state="fail"]::before, &[data-state="on_hold"]::before { + background-color: var(--bs-danger); + } + &[data-state="missed_window"], &[data-state="fail"], &[data-state="on_hold"] { + background-color: color-mix(in srgb, var(--bs-danger) 6%, $fp-surface); + } + &[data-state="baked"]::before, &[data-state="pass"]::before { + background-color: var(--bs-success); + } @media (max-width: 600px) { grid-template-columns: 1fr; - gap: 6px; - .o_fp_bake_time, .o_fp_bake_actions { - justify-self: stretch; - } + gap: 8px; + .o_fp_bake_time, .o_fp_bake_actions { justify-self: stretch; } .o_fp_bake_actions { - display: flex; - gap: 6px; - .btn { flex: 1; min-height: 44px; } + display: flex; gap: 6px; + .btn { flex: 1; min-height: $fp-touch-min; } } } - - &[data-state="awaiting_bake"], &[data-state="pending"] { - border-left: 4px solid var(--bs-warning); - } - &[data-state="bake_in_progress"], &[data-state="under_review"] { - border-left: 4px solid var(--bs-info); - } - &[data-state="missed_window"], &[data-state="fail"], &[data-state="on_hold"] { - border-left: 4px solid var(--bs-danger); - background-color: color-mix(in srgb, var(--bs-danger) 4%, transparent); - } - &[data-state="baked"], &[data-state="pass"] { - border-left: 4px solid var(--bs-success); - } } - .o_fp_bake_name { font-weight: 600; } - .o_fp_bake_meta { font-size: 0.85rem; } - .o_fp_bake_actions { display: flex; gap: 6px; } + .o_fp_bake_name { font-weight: 700; font-size: $fp-font-base; } + .o_fp_bake_meta { font-size: $fp-font-sm; margin-top: 2px; } + .o_fp_bake_actions { + display: flex; gap: 6px; + .btn { + min-height: $fp-touch-min; font-weight: 600; + border-radius: $fp-radius-md; padding: 6px 14px; + } + } - // ---------- Footer ------------------------------------------------------ + + // ========================================================================= + // Footer + // ========================================================================= .o_fp_tablet_footer { text-align: right; - padding-top: 8px; - border-top: 1px dashed var(--bs-border-color); + padding-top: 10px; + border-top: 1px dashed $fp-border; + color: var(--bs-secondary-color); + font-size: $fp-font-xs; } } -// ----------------------------------------------------------------------------- -// Large QR scan input — friendly to tablet keyboards / wedge scanners -// ----------------------------------------------------------------------------- +// ============================================================================= +// Big QR scan input + action button — shared outside .o_fp_tablet scope too +// ============================================================================= .o_fp_scan_input { flex: 1 1 auto; - min-height: 44px; - padding: 8px 14px; - font-size: 1.05rem; + min-height: 48px; + padding: 10px 16px; + font-size: $fp-font-base; border: 2px solid var(--bs-border-color); - border-radius: 8px; + border-radius: $fp-radius-md; background-color: var(--bs-body-bg); color: var(--bs-body-color); + transition: border-color $fp-dur $fp-ease, box-shadow $fp-dur $fp-ease; - &:focus { - outline: none; - border-color: var(--o-action); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--o-action) 25%, transparent); - } + &:focus { @include fp-focus-ring; border-color: var(--o-action); } &::placeholder { color: var(--bs-secondary-color); } } - -// ----------------------------------------------------------------------------- -// Big touch-friendly action button -// ----------------------------------------------------------------------------- .o_fp_big_button { - min-height: 44px; - min-width: 100px; - padding: 8px 18px; - font-size: 1rem; - font-weight: 500; - border-radius: 8px; + min-height: 48px; + min-width: 110px; + padding: 10px 22px; + font-size: $fp-font-base; + font-weight: 600; + border-radius: $fp-radius-md; border: 1px solid var(--o-action); background-color: var(--o-action); color: var(--o-we-text-on-action, #fff); cursor: pointer; - transition: filter 120ms ease, transform 80ms ease; + transition: filter $fp-dur-fast $fp-ease, transform $fp-dur-fast $fp-ease; - &:hover:not(:disabled) { filter: brightness(1.05); } - &:active:not(:disabled) { transform: translateY(1px); } - &:disabled { opacity: 0.6; cursor: not-allowed; } + &:hover:not(:disabled) { filter: brightness(1.06); } + &:active:not(:disabled) { transform: scale(0.97); } + &:disabled { opacity: 0.55; cursor: not-allowed; } } diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/scss/manager_dashboard.scss b/fusion_plating/fusion_plating_shopfloor/static/src/scss/manager_dashboard.scss index 5d366ff2..153f45c2 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/scss/manager_dashboard.scss +++ b/fusion_plating/fusion_plating_shopfloor/static/src/scss/manager_dashboard.scss @@ -1,269 +1,483 @@ // ============================================================================= -// Fusion Plating — Manager Dashboard styles +// Fusion Plating — Manager Desk // Copyright 2026 Nexa Systems Inc. // License OPL-1 (Odoo Proprietary License v1.0) // -// Inherits the tablet's theme variables (--bs-*, --o-action) so it flips -// light/dark without media queries. Shares .o_fp_kpi, .o_fp_chip, -// .o_fp_panel, .o_fp_empty, .o_fp_tablet_message from the tablet SCSS. +// Shares design tokens + panel / KPI / chip classes from the Tablet Station +// SCSS. Only the manager-specific components live here: hero banner with +// live dot, 3-column workload grid, richer MO cards, gradient avatars. // ============================================================================= -// Touch device — stop hover states from sticking on tap +@import "./fp_shopfloor_tokens"; + + +// Touch-device hover suppression @media (hover: none) { .o_fp_manager .o_fp_mgr_card:hover, .o_fp_manager .o_fp_team_card:hover { background-color: inherit !important; border-color: var(--bs-border-color) !important; + transform: none !important; } } + .o_fp_manager { - background-color: var(--o-view-background-color, var(--bs-body-bg)); + background-color: $fp-surface-sunken; color: var(--bs-body-color); min-height: 100%; - padding: 20px 24px; + padding: 24px 28px; display: flex; flex-direction: column; - gap: 14px; + gap: 18px; - // iPad portrait & small tablet - @media (max-width: 900px) { - padding: 14px 12px; - gap: 10px; - } - // Phone - @media (max-width: 600px) { - padding: 10px 8px; - gap: 8px; - } + @media (max-width: 900px) { padding: 16px; gap: 14px; } + @media (max-width: 600px) { padding: 10px 10px 16px; gap: 10px; } + + // ========================================================================= + // Hero banner — gradient wash, live dot, action cluster + // ========================================================================= .o_fp_manager_header { - display: flex; - justify-content: space-between; - align-items: center; + position: relative; + overflow: hidden; + border-radius: $fp-radius-lg; + padding: 20px 24px; + background-color: $fp-surface-raised; + border: 1px solid $fp-border; + box-shadow: $fp-shadow-sm; + + &::before { + content: ""; + position: absolute; + inset: 0; + background-image: + radial-gradient(circle at 0% 50%, + color-mix(in srgb, var(--o-action) 22%, transparent) 0%, + transparent 55%), + radial-gradient(circle at 100% 0%, + color-mix(in srgb, var(--bs-info) 18%, transparent) 0%, + transparent 55%); + pointer-events: none; + } + + display: grid; + grid-template-columns: 1fr auto; gap: 16px; - flex-wrap: wrap; + align-items: center; + > * { position: relative; z-index: 1; } @media (max-width: 600px) { - gap: 8px; - // Stack title and actions on phone - flex-direction: column; - align-items: stretch; - - .o_fp_manager_head_actions { - width: 100%; - > .btn { flex: 1; } - } + grid-template-columns: 1fr; + gap: 10px; + padding: 14px; } } .o_fp_manager_title { - font-size: 1.5rem; - font-weight: 600; - display: inline-flex; - align-items: center; - @media (max-width: 600px) { font-size: 1.15rem; } + font-size: $fp-font-hero; + font-weight: 700; + letter-spacing: -0.01em; + display: inline-flex; align-items: center; gap: 10px; } - // Small breathing dot that pulses while a poll is in flight. - // At rest: soft green. While fetching: brighter, animating. + // Small breathing dot — green at rest, brighter pulse while polling .o_fp_live_dot { display: inline-block; - width: 10px; - height: 10px; - border-radius: 50%; + width: 11px; height: 11px; border-radius: 50%; background-color: color-mix(in srgb, var(--bs-success) 75%, transparent); - transition: background-color 160ms ease; - box-shadow: 0 0 0 0 transparent; + transition: background-color $fp-dur $fp-ease; &[data-active="y"] { background-color: var(--bs-success); - animation: o_fp_live_pulse 1.0s ease-in-out infinite; + animation: o_fp_live_pulse 1.1s ease-in-out infinite; } } @keyframes o_fp_live_pulse { - 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--bs-success) 55%, transparent); } - 70% { box-shadow: 0 0 0 8px color-mix(in srgb, var(--bs-success) 0%, transparent); } + 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--bs-success) 60%, transparent); } + 70% { box-shadow: 0 0 0 10px color-mix(in srgb, var(--bs-success) 0%, transparent); } 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--bs-success) 0%, transparent); } } .o_fp_manager_subtitle { - font-size: 0.95rem; + font-size: $fp-font-sm; color: var(--bs-secondary-color); + margin-top: 4px; + } + .o_fp_manager_head_actions { + display: flex; gap: 8px; align-items: center; + + .btn { + min-height: $fp-touch-min; + padding: 8px 16px; + border-radius: $fp-radius-md; + font-weight: 600; + } + @media (max-width: 600px) { + width: 100%; flex-wrap: wrap; + > .btn { flex: 1; } + } } - // 3-column grid: Unassigned | In Progress | Team + + // ========================================================================= + // KPI strip — same token system as the tablet + // ========================================================================= + .o_fp_kpi_strip { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); + gap: 12px; + @media (max-width: 600px) { + grid-template-columns: repeat(2, 1fr); + gap: 8px; + } + } + .o_fp_kpi { + position: relative; overflow: hidden; + padding: 18px 18px 16px; + border-radius: $fp-radius-lg; + background-color: $fp-surface-raised; + border: 1px solid $fp-border; + box-shadow: $fp-shadow-sm; + transition: transform $fp-dur $fp-ease, box-shadow $fp-dur $fp-ease; + display: flex; flex-direction: column; gap: 4px; + min-height: 104px; + + &::before { + content: ""; + position: absolute; + top: 0; left: 0; right: 0; height: 3px; + background-image: linear-gradient(90deg, transparent, currentColor, transparent); + opacity: 0.7; + } + &::after { + content: ""; + position: absolute; inset: 0; opacity: 0.35; pointer-events: none; + } + > * { position: relative; z-index: 1; } + + @media (hover: hover) { + &:hover { transform: translateY(-2px); box-shadow: $fp-shadow-md; } + } + + > .fa { + position: absolute; + right: 14px; top: 14px; + font-size: 1.5rem; + opacity: 0.28; + } + .o_fp_kpi_value { + font-size: $fp-font-kpi; + font-weight: 800; + line-height: 1; + letter-spacing: -0.02em; + font-variant-numeric: tabular-nums; + color: var(--bs-body-color); + } + .o_fp_kpi_label { + font-size: $fp-font-xs; + color: var(--bs-secondary-color); + text-transform: uppercase; + letter-spacing: 0.06em; + font-weight: 600; + } + + &.o_fp_kpi_info { color: var(--bs-info); &::after { @include fp-grad(--bs-info); } } + &.o_fp_kpi_success { color: var(--bs-success); &::after { @include fp-grad(--bs-success); } } + &.o_fp_kpi_warning { color: var(--bs-warning); &::after { @include fp-grad(--bs-warning); } } + &.o_fp_kpi_danger { + color: var(--bs-danger); + border-color: color-mix(in srgb, var(--bs-danger) 45%, $fp-border); + &::after { @include fp-grad(--bs-danger, 22%, 8%); } + .o_fp_kpi_value { color: var(--bs-danger); } + } + &.o_fp_kpi_muted { color: var(--bs-secondary-color); &::after { opacity: 0; } } + + @media (max-width: 600px) { + min-height: 84px; padding: 12px 12px 10px; + .o_fp_kpi_value { font-size: 1.6rem; } + .o_fp_kpi_label { font-size: 0.68rem; } + > .fa { font-size: 1rem; top: 10px; right: 10px; } + } + } + + + // ========================================================================= + // Flash message (shares tablet styling) + // ========================================================================= + .o_fp_tablet_message { + padding: 12px 16px; + border-radius: $fp-radius-md; + font-size: $fp-font-base; + font-weight: 500; + display: flex; align-items: center; gap: 10px; + box-shadow: $fp-shadow-xs; + + &.o_fp_msg_info { @include fp-tone(--bs-info); } + &.o_fp_msg_success { @include fp-tone(--bs-success); } + &.o_fp_msg_warning { @include fp-tone(--bs-warning); } + &.o_fp_msg_danger { @include fp-tone(--bs-danger); } + } + + + // ========================================================================= + // 3-column workload grid + // ========================================================================= .o_fp_manager_grid { display: grid; - grid-template-columns: minmax(0, 1.2fr) minmax(0, 1.2fr) minmax(0, 0.8fr); - gap: 14px; - } - // iPad landscape - @media (max-width: 1280px) { - .o_fp_manager_grid { + grid-template-columns: minmax(0, 1.15fr) minmax(0, 1.15fr) minmax(0, 0.85fr); + gap: 16px; + + @media (max-width: 1280px) { grid-template-columns: 1fr 1fr; + .o_fp_panel_team { grid-column: span 2; } } - .o_fp_panel_team { grid-column: span 2; } - } - // iPad portrait / phone landscape - @media (max-width: 900px) { - .o_fp_manager_grid { - grid-template-columns: 1fr; - gap: 10px; + @media (max-width: 900px) { + grid-template-columns: 1fr; gap: 10px; + .o_fp_panel_team { grid-column: auto; } } - .o_fp_panel_team { grid-column: auto; } } - .o_fp_panel_unassigned { - border-left: 4px solid var(--bs-warning); + + // ========================================================================= + // Panels with coloured top accent + // ========================================================================= + .o_fp_panel { + position: relative; + overflow: hidden; + background-color: $fp-surface-raised; + border: 1px solid $fp-border; + border-radius: $fp-radius-lg; + padding: 18px 20px; + box-shadow: $fp-shadow-sm; + @media (max-width: 600px) { padding: 12px 14px; border-radius: $fp-radius-md; } + + &::before { + content: ""; + position: absolute; top: 0; left: 0; right: 0; height: 4px; + background-color: $fp-border-strong; + } } - .o_fp_panel_active { - border-left: 4px solid var(--bs-success); + .o_fp_panel_unassigned::before { + background-image: linear-gradient(90deg, + var(--bs-warning), color-mix(in srgb, var(--bs-warning) 50%, transparent)); } - .o_fp_panel_team { - border-left: 4px solid var(--bs-info); + .o_fp_panel_active::before { + background-image: linear-gradient(90deg, + var(--bs-success), color-mix(in srgb, var(--bs-success) 50%, transparent)); + } + .o_fp_panel_team::before { + background-image: linear-gradient(90deg, + var(--bs-info), color-mix(in srgb, var(--bs-info) 50%, transparent)); } + .o_fp_panel_head { + display: flex; justify-content: space-between; align-items: center; + margin-bottom: 14px; padding-bottom: 12px; + border-bottom: 1px solid $fp-border; + + h3 { + font-size: $fp-font-lg; + font-weight: 700; + margin: 0; + display: inline-flex; align-items: center; gap: 10px; + letter-spacing: -0.01em; + } + } + .o_fp_panel_count { + min-width: 34px; + padding: 3px 12px; + border-radius: $fp-radius-pill; + font-weight: 700; + font-size: $fp-font-sm; + font-variant-numeric: tabular-nums; + background-color: color-mix(in srgb, var(--bs-body-color) 8%, transparent); + color: var(--bs-body-color); + border: 1px solid $fp-border; + } + + .o_fp_empty { + padding: 32px 16px; + text-align: center; + color: var(--bs-secondary-color); + i.fa { display: block; font-size: 2.25rem; opacity: 0.55; margin-bottom: 8px; } + } + + + // ========================================================================= + // MO card list (Unassigned + In Progress columns) + // ========================================================================= .o_fp_mgr_card_list { - display: flex; - flex-direction: column; - gap: 10px; + display: flex; flex-direction: column; gap: 10px; } .o_fp_mgr_card { - border: 1px solid var(--bs-border-color); - border-radius: 10px; - transition: border-color 120ms ease, box-shadow 120ms ease; + position: relative; + overflow: hidden; + border: 1px solid $fp-border; + border-radius: $fp-radius-md; + background-color: $fp-surface; + transition: border-color $fp-dur $fp-ease, + box-shadow $fp-dur $fp-ease, + transform $fp-dur-fast $fp-ease; + + @media (hover: hover) { + &:hover { + border-color: $fp-border-accent; + box-shadow: $fp-shadow-sm; + transform: translateY(-1px); + } + } &[data-priority="2"] { - border-left: 4px solid var(--bs-danger); - background-color: color-mix(in srgb, var(--bs-danger) 4%, transparent); + border-color: color-mix(in srgb, var(--bs-danger) 50%, $fp-border); + &::before { + content: ""; + position: absolute; top: 0; left: 0; bottom: 0; width: 4px; + background: var(--bs-danger); + } } &[data-priority="1"] { - border-left: 4px solid var(--bs-warning); - } - - &:hover { - border-color: color-mix(in srgb, var(--o-action) 40%, var(--bs-border-color)); + &::before { + content: ""; + position: absolute; top: 0; left: 0; bottom: 0; width: 4px; + background: var(--bs-warning); + } } } .o_fp_mgr_card_head { - display: flex; - justify-content: space-between; - align-items: center; - padding: 10px 14px; - cursor: pointer; - gap: 10px; - min-height: 56px; // comfortable touch zone - - @media (max-width: 600px) { - flex-wrap: wrap; - padding: 10px 12px; - } + display: flex; justify-content: space-between; align-items: center; + padding: 12px 14px; cursor: pointer; gap: 10px; + min-height: 60px; + @media (max-width: 600px) { flex-wrap: wrap; padding: 12px; } } .o_fp_mgr_card_title { - font-weight: 600; - font-size: 1rem; + font-weight: 700; font-size: $fp-font-base; + letter-spacing: -0.01em; } .o_fp_mgr_card_sub { - color: var(--bs-secondary-color); - font-size: 0.88rem; - margin-top: 2px; + color: var(--bs-secondary-color); font-size: $fp-font-sm; + margin-top: 3px; } .o_fp_mgr_card_chips { - display: flex; - gap: 6px; - flex-wrap: wrap; + display: flex; gap: 6px; flex-wrap: wrap; } .o_fp_mgr_card_body { - border-top: 1px dashed var(--bs-border-color); - padding: 10px 14px; - display: flex; - flex-direction: column; - gap: 8px; + border-top: 1px dashed $fp-border; + padding: 12px 14px; + display: flex; flex-direction: column; gap: 10px; background-color: color-mix(in srgb, var(--bs-body-color) 2%, transparent); - border-bottom-left-radius: 10px; - border-bottom-right-radius: 10px; } + + // Per-WO row inside the expanded card .o_fp_mgr_wo_row { display: grid; grid-template-columns: 1fr auto auto auto auto; gap: 8px; align-items: center; - padding: 6px 0; - font-size: 0.92rem; - border-bottom: 1px dashed color-mix(in srgb, var(--bs-body-color) 6%, transparent); + padding: 8px 10px; + background-color: $fp-surface; + border: 1px solid $fp-border; + border-radius: $fp-radius-sm; + font-size: $fp-font-sm; - &:last-child { border-bottom: 0; } - } - @media (max-width: 1400px) { - .o_fp_mgr_wo_row { + @media (max-width: 1400px) { grid-template-columns: 1fr auto auto; - // Worker picker takes its own row .o_fp_mgr_picker:nth-of-type(1) { grid-column: 1 / -1; } - // Tank picker also takes its own row .o_fp_mgr_picker:nth-of-type(2) { grid-column: 1 / -1; } } - } - // Phone — everything stacks full-width - @media (max-width: 600px) { - .o_fp_mgr_wo_row { + @media (max-width: 600px) { grid-template-columns: 1fr; - padding: 10px 0; gap: 6px; - .o_fp_mgr_picker { max-width: 100% !important; width: 100%; } - .btn { min-height: 44px; } + .btn { min-height: $fp-touch-min; } } } .o_fp_mgr_wo_info { min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + font-weight: 600; } .o_fp_mgr_picker { - min-width: 120px; - max-width: 180px; - min-height: 38px; // readable tap target + min-width: 130px; max-width: 200px; + min-height: 38px; + padding: 4px 10px; + border-radius: $fp-radius-sm; + background-color: $fp-surface; + border: 1px solid $fp-border; + color: var(--bs-body-color); + font-size: $fp-font-sm; + &:focus { @include fp-focus-ring; border-color: var(--o-action); } } - // Team grid + + // ========================================================================= + // Status chips (shared styles in tablet scss, but redefined in case + // manager is loaded standalone) + // ========================================================================= + .o_fp_chip { + display: inline-flex; + align-items: center; + padding: 3px 10px; + border-radius: $fp-radius-pill; + font-size: 0.72rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.04em; + + &.o_fp_chip_info { @include fp-tone(--bs-info); } + &.o_fp_chip_success { @include fp-tone(--bs-success); } + &.o_fp_chip_warning { @include fp-tone(--bs-warning); } + &.o_fp_chip_danger { @include fp-tone(--bs-danger); } + &.o_fp_chip_muted { + background-color: color-mix(in srgb, var(--bs-body-color) 6%, transparent); + color: var(--bs-secondary-color); + border: 1px solid $fp-border; + } + } + + + // ========================================================================= + // Team column — avatar grid + // ========================================================================= .o_fp_team_grid { - display: grid; - grid-template-columns: 1fr; - gap: 8px; + display: flex; flex-direction: column; gap: 10px; } .o_fp_team_card { - display: flex; + position: relative; + display: grid; + grid-template-columns: 56px 1fr; align-items: center; - gap: 12px; - padding: 10px 12px; - border: 1px solid var(--bs-border-color); - border-radius: 10px; + gap: 14px; + padding: 12px 14px; + border: 1px solid $fp-border; + border-radius: $fp-radius-md; + background-color: $fp-surface; cursor: pointer; - transition: border-color 120ms ease, background-color 120ms ease; + min-height: 72px; + transition: border-color $fp-dur $fp-ease, + box-shadow $fp-dur $fp-ease, + transform $fp-dur-fast $fp-ease; - &:hover { - border-color: color-mix(in srgb, var(--o-action) 40%, var(--bs-border-color)); - background-color: color-mix(in srgb, var(--o-action) 6%, transparent); + @media (hover: hover) { + &:hover { + border-color: $fp-border-accent; + box-shadow: $fp-shadow-sm; + transform: translateY(-1px); + } } } .o_fp_team_avatar { - width: 44px; - height: 44px; + width: 48px; height: 48px; border-radius: 50%; object-fit: cover; - border: 2px solid var(--bs-border-color); - } - .o_fp_team_info { - flex: 1; - min-width: 0; + border: 2px solid $fp-border; + background-color: color-mix(in srgb, var(--o-action) 15%, $fp-surface); } + .o_fp_team_info { flex: 1; min-width: 0; } .o_fp_team_name { - font-weight: 600; - font-size: 0.95rem; + font-weight: 700; + font-size: $fp-font-base; + letter-spacing: -0.01em; } .o_fp_team_load { - display: flex; - gap: 6px; - margin-top: 4px; + display: flex; gap: 6px; margin-top: 6px; } } diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/scss/plant_overview.scss b/fusion_plating/fusion_plating_shopfloor/static/src/scss/plant_overview.scss index 33597635..8747291b 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/scss/plant_overview.scss +++ b/fusion_plating/fusion_plating_shopfloor/static/src/scss/plant_overview.scss @@ -3,61 +3,62 @@ // Copyright 2026 Nexa Systems Inc. // License OPL-1 (Odoo Proprietary License v1.0) // -// THEME AWARENESS -// --------------- -// All colours come from CSS custom properties (Bootstrap / Odoo tokens) so -// the dashboard renders correctly in BOTH light and dark mode. -// -// background: var(--bs-body-bg) -// surface: var(--o-view-background-color) -// foreground: var(--bs-body-color) -// muted text: var(--bs-secondary-color) -// border: var(--bs-border-color) -// primary: var(--o-action) +// Modernised 2026-04: gradient column headers, card depth, theme-safe +// using shared design tokens. // ============================================================================= +@import "./fp_shopfloor_tokens"; + + .o_fp_plant_overview { display: flex; flex-direction: column; height: 100%; min-height: 0; - background: var(--o-view-background-color, var(--bs-body-bg)); + background: $fp-surface-sunken; padding: 0; } // ---- Header ----------------------------------------------------------------- .o_fp_po_header { + position: relative; + overflow: hidden; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; - padding: 16px 20px; - background: var(--bs-body-bg); - border-bottom: 1px solid var(--bs-border-color); - box-shadow: 0 1px 3px color-mix(in srgb, var(--bs-body-color) 6%, transparent); + padding: 20px 24px; + background-color: $fp-surface-raised; + border-bottom: 1px solid $fp-border; + box-shadow: $fp-shadow-xs; - .o_fp_po_header_left { - display: flex; - align-items: center; + &::before { + content: ""; + position: absolute; inset: 0; + background-image: + radial-gradient(circle at 0% 50%, + color-mix(in srgb, var(--o-action) 18%, transparent) 0%, + transparent 55%); + pointer-events: none; } + > * { position: relative; z-index: 1; } + + .o_fp_po_header_left { display: flex; align-items: center; } .o_fp_po_title { margin: 0; - font-size: 1.3rem; + font-size: $fp-font-xl; font-weight: 700; + letter-spacing: -0.01em; color: var(--bs-body-color); } - .o_fp_po_refresh_ts { - font-size: 0.8rem; - } + .o_fp_po_refresh_ts { font-size: $fp-font-xs; } .o_fp_po_header_right { - display: flex; - align-items: center; - gap: 10px; + display: flex; align-items: center; gap: 10px; } } @@ -137,35 +138,49 @@ max-width: 320px; display: flex; flex-direction: column; - background: var(--bs-body-bg); - border-radius: 10px; - box-shadow: 0 1px 4px color-mix(in srgb, var(--bs-body-color) 8%, transparent); - max-height: calc(100vh - 140px); + background-color: $fp-surface-raised; + border: 1px solid $fp-border; + border-radius: $fp-radius-lg; + box-shadow: $fp-shadow-sm; + max-height: calc(100vh - 160px); + overflow: hidden; } .o_fp_po_col_header { + position: relative; + overflow: hidden; display: flex; align-items: center; justify-content: space-between; - padding: 12px 14px; - border-bottom: 2px solid var(--bs-border-color); - background: var(--bs-tertiary-bg); - border-radius: 10px 10px 0 0; + padding: 14px 16px; + border-bottom: 1px solid $fp-border; + background-color: $fp-surface-sunken; + + // Subtle gradient stripe at the top — turns the header into a "tab" + &::before { + content: ""; + position: absolute; top: 0; left: 0; right: 0; height: 3px; + background-image: linear-gradient(90deg, + var(--o-action), + color-mix(in srgb, var(--o-action) 30%, transparent)); + } + > * { position: relative; z-index: 1; } .o_fp_po_col_name { font-weight: 700; - font-size: 0.9rem; + font-size: $fp-font-sm; color: var(--bs-body-color); text-transform: uppercase; - letter-spacing: 0.3px; + letter-spacing: 0.05em; } .o_fp_po_col_count { - background: var(--bs-secondary-color); - color: #fff; - font-size: 0.75rem; - min-width: 24px; - text-align: center; + @include fp-tone(--o-action, 14%); + font-weight: 700; + font-size: 0.72rem; + min-width: 26px; + padding: 2px 10px; + border-radius: $fp-radius-pill; } } @@ -187,20 +202,22 @@ // ---- Card ------------------------------------------------------------------- .o_fp_po_card { - background: var(--bs-body-bg); - border-width: 1px; - border-style: solid; - border-color: $border-color; - border-radius: 8px; - padding: 10px 12px; - margin-bottom: 8px; + background-color: $fp-surface; + border: 1px solid $fp-border; + border-radius: $fp-radius-md; + padding: 12px 14px; + margin-bottom: 10px; cursor: grab; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08); - transition: box-shadow 0.15s, transform 0.1s, opacity 0.15s; + box-shadow: $fp-shadow-xs; + transition: box-shadow $fp-dur $fp-ease, + transform $fp-dur-fast $fp-ease, + opacity $fp-dur $fp-ease, + border-color $fp-dur $fp-ease; &:hover { - box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15); - transform: translateY(-1px); + box-shadow: $fp-shadow-md; + transform: translateY(-2px); + border-color: $fp-border-accent; border-color: darken($border-color, 10%); } diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/xml/manager_dashboard.xml b/fusion_plating/fusion_plating_shopfloor/static/src/xml/manager_dashboard.xml index c9402e33..993a8177 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/xml/manager_dashboard.xml +++ b/fusion_plating/fusion_plating_shopfloor/static/src/xml/manager_dashboard.xml @@ -88,8 +88,8 @@
- - Every active WO has a worker assigned. + +
Every active WO has a worker assigned.
@@ -176,7 +176,8 @@
- Nothing running right now. + +
Nothing running right now.
@@ -242,7 +243,8 @@
- No operators configured. + +
No operators configured yet.
diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/xml/shopfloor_tablet.xml b/fusion_plating/fusion_plating_shopfloor/static/src/xml/shopfloor_tablet.xml index 87005dab..4cf97e61 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/xml/shopfloor_tablet.xml +++ b/fusion_plating/fusion_plating_shopfloor/static/src/xml/shopfloor_tablet.xml @@ -122,8 +122,8 @@
- - All caught up. + +
All caught up — nothing waiting on you.
    @@ -167,7 +167,8 @@
    - No baths configured. + +
    No baths configured.
    @@ -202,7 +203,8 @@
    - No bakes pending. + +
    No bakes pending.
      @@ -251,7 +253,8 @@
      - No pending first-piece inspections. + +
      No pending first-piece inspections.