From f00dda2abdfaff4e39c235d654a479d06848c597 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Fri, 22 May 2026 22:21:53 -0400 Subject: [PATCH] feat(fusion_plating_shopfloor): Manager Desk 4-tab refactor (P4.5-P4.10) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plan tasks P4.5 through P4.10 batched. Existing 3-column Plant Board becomes one tab of four; adds Workflow Funnel (default), Approval Inbox, and At-Risk siblings. Adds 2 new KPI tiles for Pending Cert + At-Risk. WORKFLOW FUNNEL (default tab) Calls /fp/manager/funnel. Renders one row per fp.job.workflow.state with stage chip + count + top 5 WO cards. Tap a card → JobWorkspace. Bar chart bar behind each row scales with stage count. APPROVAL INBOX Calls /fp/manager/approval_inbox. Three strips: Holds to Release, Certs to Issue, Scrap to Review. Per-row open + Open Workspace buttons. Tab badge shows total pending count. PLANT BOARD (existing — relocated as one tab) The 3-column Needs Worker / In Progress / Team layout that already exists, wrapped in t-if="activeTab === 'plant_board'". No behaviour change — still uses /fp/manager/overview with 8s refresh. AT-RISK Calls /fp/manager/at_risk. 3 sub-panels: Trending Late (sorted by late_risk_ratio desc), Hold Reasons (read_group), Bottleneck heatmap (bottleneck_score from P4.1 with red/yellow/green bars). KPI STRIP (new conditional tiles) Pending Cert — count from inbox.certs_to_issue, click to open Inbox tab. At-Risk — count from at_risk.trending_late, click to open At-Risk. Auto-refresh: 8s for /fp/manager/overview (existing); the active tab's data also refreshes every 8s via refreshActiveTab(). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../static/src/js/manager_dashboard.js | 93 ++++++- .../static/src/scss/manager_dashboard.scss | 227 ++++++++++++++++++ .../static/src/xml/manager_dashboard.xml | 212 +++++++++++++++- 3 files changed, 529 insertions(+), 3 deletions(-) diff --git a/fusion_plating/fusion_plating_shopfloor/static/src/js/manager_dashboard.js b/fusion_plating/fusion_plating_shopfloor/static/src/js/manager_dashboard.js index 83dcbd7a..b00363f6 100644 --- a/fusion_plating/fusion_plating_shopfloor/static/src/js/manager_dashboard.js +++ b/fusion_plating/fusion_plating_shopfloor/static/src/js/manager_dashboard.js @@ -43,15 +43,27 @@ export class ManagerDashboard extends Component { // Defaults to false because lead-hand coverage often needs // off-roster names. hideOffShift: false, + // Phase 4 tablet redesign — 4 sibling tabs. + // funnel | inbox | plant_board | at_risk + activeTab: "funnel", + funnel: null, // /fp/manager/funnel payload + inbox: null, // /fp/manager/approval_inbox payload + atRisk: null, // /fp/manager/at_risk payload }); this._lastHash = null; // sent to server to skip unchanged polls onMounted(async () => { await this.refresh(); + // Load the default tab's data (Workflow Funnel) on first paint + await this.loadFunnel(); // 8s cadence: fast enough for production pace, light on the // network since unchanged payloads short-circuit server-side. - this._interval = setInterval(() => this.refresh(), 8000); + // The active tab's data also refreshes on each tick. + this._interval = setInterval(() => { + this.refresh(); + this.refreshActiveTab(); + }, 8000); }); onWillUnmount(() => { @@ -283,6 +295,85 @@ export class ManagerDashboard extends Component { target: "current", }); } + + // ================================================================== + // Phase 4 tablet redesign — 4 sibling tabs + // ================================================================== + + async setActiveTab(tab) { + if (this.state.activeTab === tab) return; + this.state.activeTab = tab; + // Load the tab's data on first switch — subsequent ticks refresh + // via the auto-poll. + await this.refreshActiveTab(); + } + + async refreshActiveTab() { + if (this.state.activeTab === "funnel") return this.loadFunnel(); + if (this.state.activeTab === "inbox") return this.loadInbox(); + if (this.state.activeTab === "at_risk") return this.loadAtRisk(); + // plant_board uses /fp/manager/overview via refresh() + } + + async loadFunnel() { + try { + const res = await rpc("/fp/manager/funnel", {}); + if (res && res.ok) this.state.funnel = res; + } catch (err) { + this.setMessage(`Funnel: ${err.message}`, "danger"); + } + } + + async loadInbox() { + try { + const res = await rpc("/fp/manager/approval_inbox", {}); + if (res && res.ok) this.state.inbox = res; + } catch (err) { + this.setMessage(`Inbox: ${err.message}`, "danger"); + } + } + + async loadAtRisk() { + try { + const res = await rpc("/fp/manager/at_risk", {}); + if (res && res.ok) this.state.atRisk = res; + } catch (err) { + this.setMessage(`At-Risk: ${err.message}`, "danger"); + } + } + + // Tap a WO card on any tab → open the JobWorkspace (Phase 1) + openJobWorkspace(jobId) { + this.action.doAction({ + type: "ir.actions.client", + tag: "fp_job_workspace", + params: { job_id: jobId }, + target: "current", + }); + } + + // Pill colour from workflow_state.color (mirrors WorkflowChip toneClass) + funnelStageTone(color) { + const map = { + grey: "muted", blue: "info", cyan: "info", + yellow: "warning", orange: "warning", + green: "success", success: "success", + danger: "danger", purple: "info", + }; + return map[color] || "muted"; + } + + // Bottleneck severity tone for the heatmap bar colour + bottleneckTone(score) { + if (score >= 200) return "danger"; + if (score >= 60) return "warning"; + return "success"; + } + + bottleneckPct(score) { + // Normalize to 0-100 for the bar width; cap at 100 + return Math.min(100, Math.round(score / 5)); + } } registry.category("actions").add("fp_manager_dashboard", ManagerDashboard); 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 34c396bd..ae6ab02d 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 @@ -646,3 +646,230 @@ display: flex; gap: $fp-space-1; margin-top: 4px; } } + +// ============================================================================= +// Phase 4 tablet redesign — Manager dashboard sibling tabs +// ============================================================================= + +.o_fp_mgr_tabs { + display: flex; + gap: 4px; + padding: 8px 16px 0; + border-bottom: 1px solid $fp-border; + background: $fp-card; + + .o_fp_mgr_tab { + background: transparent; + border: none; + border-bottom: 2px solid transparent; + padding: 8px 14px; + font-size: 0.9rem; + color: $fp-ink-soft; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 6px; + transition: color 0.15s ease, border-color 0.15s ease; + + &:hover { color: $fp-ink; } + &.active { + color: $fp-accent; + border-bottom-color: $fp-accent; + font-weight: 600; + } + + .o_fp_mgr_tab_badge { + background: $fp-accent; + color: white; + border-radius: 999px; + padding: 1px 8px; + font-size: 0.65rem; + font-weight: 700; + margin-left: 4px; + } + } +} + +// ---- Workflow Funnel tab ------------------------------------------------- +.o_fp_mgr_funnel { + padding: 16px; + display: flex; + flex-direction: column; + gap: 4px; + + .o_fp_funnel_row { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 0; + border-bottom: 1px solid $fp-border; + } + + .o_fp_funnel_stage { + display: flex; + align-items: center; + gap: 8px; + min-width: 200px; + } + + .o_fp_funnel_count { + font-weight: 700; + font-size: 1.1rem; + min-width: 28px; + text-align: right; + } + + .o_fp_funnel_cards { + display: flex; + gap: 6px; + flex: 1; + overflow-x: auto; + align-items: center; + } + + .o_fp_funnel_card { + background: $fp-card; + border: 1px solid $fp-border; + border-radius: 6px; + padding: 6px 10px; + font-size: 0.78rem; + min-width: 130px; + cursor: pointer; + transition: background 0.1s ease, border-color 0.1s ease; + + &:hover { + background: color-mix(in srgb, #{$fp-accent} 5%, #{$fp-card}); + border-color: color-mix(in srgb, #{$fp-accent} 30%, #{$fp-border}); + } + + .o_fp_funnel_card_wo { font-weight: 600; } + .o_fp_funnel_card_meta { color: $fp-ink-soft; font-size: 0.7rem; } + } + + .o_fp_funnel_more, .o_fp_funnel_empty { + color: $fp-ink-soft; + font-size: 0.78rem; + padding: 0 6px; + } +} + +// ---- Approval Inbox tab -------------------------------------------------- +.o_fp_mgr_inbox { + padding: 16px; + display: flex; + flex-direction: column; + gap: 12px; + + .o_fp_inbox_strip { + background: $fp-card; + border: 1px solid $fp-border; + border-radius: 8px; + padding: 12px 16px; + + h4 { + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 0.04em; + color: $fp-ink-soft; + margin-bottom: 8px; + display: flex; + align-items: center; + gap: 8px; + } + } + + .o_fp_inbox_row { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 0; + font-size: 0.85rem; + border-bottom: 1px dashed $fp-border; + + &:last-child { border-bottom: none; } + .ms-auto { margin-left: auto; } + } + + .o_fp_empty_small { + color: $fp-ink-soft; + font-size: 0.8rem; + font-style: italic; + padding: 4px 0; + } +} + +// ---- At-Risk tab --------------------------------------------------------- +.o_fp_mgr_atrisk { + padding: 16px; + + .o_fp_atrisk_grid { + display: grid; + grid-template-columns: 1.4fr 1fr 1.2fr; + gap: 12px; + + @media (max-width: 1100px) { + grid-template-columns: 1fr; + } + } + + .o_fp_atrisk_card { + background: $fp-card; + border: 1px solid $fp-border; + border-radius: 8px; + padding: 12px 16px; + + h4 { + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 0.04em; + color: $fp-ink-soft; + margin-bottom: 10px; + display: flex; + align-items: center; + gap: 6px; + } + } + + .o_fp_atrisk_row { + display: flex; + gap: 6px; + padding: 6px 0; + font-size: 0.82rem; + border-bottom: 1px dashed $fp-border; + align-items: center; + cursor: default; + + &[t-on-click], &:hover { cursor: pointer; } + &:last-child { border-bottom: none; } + .ms-auto { margin-left: auto; } + } + + .o_fp_atrisk_bar { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 0; + font-size: 0.78rem; + + .o_fp_atrisk_bar_name { min-width: 100px; } + .o_fp_atrisk_bar_track { + flex: 1; + height: 10px; + background: color-mix(in srgb, #{$fp-ink-soft} 15%, transparent); + border-radius: 5px; + overflow: hidden; + } + .o_fp_atrisk_bar_fill { height: 100%; display: block; } + .o_fp_atrisk_bar_danger { background: #ff3b30; } + .o_fp_atrisk_bar_warning { background: #ff9f0a; } + .o_fp_atrisk_bar_success { background: #34c759; } + .o_fp_atrisk_bar_score { font-weight: 600; min-width: 32px; text-align: right; } + } + + .o_fp_empty_small { + color: $fp-ink-soft; + font-size: 0.8rem; + font-style: italic; + padding: 4px 0; + } +} 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 29d9b5e3..d3626983 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 @@ -153,10 +153,53 @@ + +
+ +
+ +
+
Pending Cert
+
+
+ +
+ +
+
At-Risk
+
- -
+ +
+ + + + +
+ + +
@@ -369,6 +412,171 @@
+ +
+
+ +
Loading workflow funnel…
+
+ +
+
+ + + + + +
+
+ +
+
+
+ · d +
+
+ + + + more + + +
+
+ +
+ + +
+
+ +
Loading approval inbox…
+
+ + +
+

+ + Holds to Release () +

+
+ No open holds. +
+ +
+ · + · · qty + · + +
+
+
+ +
+

+ + Certs to Issue () +

+
+ No certs waiting to be issued. +
+ +
+ · + · needs + all steps done + +
+
+
+ +
+

+ + Scrap to Review () +

+
+ No recent scrap to acknowledge. +
+ +
+ · scrapped + · "" + · + +
+
+
+
+
+ + +
+
+ +
Loading at-risk view…
+
+ +
+
+

Trending Late ()

+
+ No late-risk jobs right now. +
+ +
+ · + · stuck at + × +
+
+
+
+

Hold Reasons

+
+ No open holds. +
+ +
+ + +
+
+
+
+

Bottleneck

+
+ No bottlenecks detected. +
+ +
+ + + + + +
+
+
+
+
+
+