feat(fusion_plating_shopfloor): KanbanCard shared OWL service
Plan task P1.7. Final shared service — standard WO card used on Landing
kanban, Manager Plant Board, and Workflow Funnel. Embeds WorkflowChip,
shows progress bar, priority dot, blocker badge from step.blocker_kind.
Density prop ('compact' vs 'normal') swaps padding for funnel use.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -77,6 +77,9 @@ Copyright (c) 2026 Nexa Systems Inc. All rights reserved.
|
||||
'fusion_plating_shopfloor/static/src/scss/components/_hold_composer.scss',
|
||||
'fusion_plating_shopfloor/static/src/xml/components/hold_composer.xml',
|
||||
'fusion_plating_shopfloor/static/src/js/components/hold_composer.js',
|
||||
'fusion_plating_shopfloor/static/src/scss/components/_kanban_card.scss',
|
||||
'fusion_plating_shopfloor/static/src/xml/components/kanban_card.xml',
|
||||
'fusion_plating_shopfloor/static/src/js/components/kanban_card.js',
|
||||
'fusion_plating_shopfloor/static/src/scss/qr_scanner.scss',
|
||||
'fusion_plating_shopfloor/static/src/scss/fusion_plating_shopfloor.scss',
|
||||
'fusion_plating_shopfloor/static/src/scss/plant_overview.scss',
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/** @odoo-module **/
|
||||
// =============================================================================
|
||||
// Fusion Plating — KanbanCard (shared OWL service)
|
||||
//
|
||||
// Standard WO/step card used on:
|
||||
// • Shop Floor Landing kanban (station + all-plant modes)
|
||||
// • Manager Plant Board (Needs Worker + In Progress columns)
|
||||
// • Manager Workflow Funnel (compact density per stage)
|
||||
//
|
||||
// Embeds WorkflowChip + a blocker badge from the backend's step.blocker_kind.
|
||||
//
|
||||
// Props:
|
||||
// data : { job_id, display_wo_name, customer, part, qty,
|
||||
// qty_done, qty_scrapped, date_deadline, priority,
|
||||
// workflow_state, blocker_kind, blocker_reason,
|
||||
// current_step_id, work_center }
|
||||
// density : 'compact' | 'normal' (default 'normal')
|
||||
// showWorkflowChip : Boolean
|
||||
// showWorkcenter : Boolean
|
||||
// showAssignedTo : Boolean
|
||||
// onTap : Function(data) — called on card click
|
||||
// =============================================================================
|
||||
|
||||
import { Component } from "@odoo/owl";
|
||||
import { WorkflowChip } from "./workflow_chip";
|
||||
|
||||
export class FpKanbanCard extends Component {
|
||||
static template = "fusion_plating_shopfloor.KanbanCard";
|
||||
static components = { WorkflowChip };
|
||||
static props = {
|
||||
data: { type: Object, optional: false },
|
||||
density: { type: String, optional: true },
|
||||
showWorkflowChip: { type: Boolean, optional: true },
|
||||
showWorkcenter: { type: Boolean, optional: true },
|
||||
showAssignedTo: { type: Boolean, optional: true },
|
||||
onTap: { type: Function, optional: true },
|
||||
};
|
||||
|
||||
get isCompact() {
|
||||
return this.props.density === "compact";
|
||||
}
|
||||
|
||||
get progressPct() {
|
||||
const d = this.props.data;
|
||||
if (!d.qty || d.qty <= 0) return 0;
|
||||
return Math.min(100, Math.round((d.qty_done || 0) * 100 / d.qty));
|
||||
}
|
||||
|
||||
get priorityDot() {
|
||||
const p = this.props.data.priority;
|
||||
if (p === "rush") return "danger";
|
||||
if (p === "high") return "warning";
|
||||
return "muted";
|
||||
}
|
||||
|
||||
onClick() {
|
||||
if (this.props.onTap) this.props.onTap(this.props.data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// =============================================================================
|
||||
// KanbanCard — standard WO/step card for Landing + Manager surfaces
|
||||
// Dark-mode aware via $o-webclient-color-scheme branch.
|
||||
// =============================================================================
|
||||
|
||||
$o-webclient-color-scheme: bright !default;
|
||||
|
||||
$_kc-bg-hex: #ffffff;
|
||||
$_kc-border-hex: #d8dadd;
|
||||
$_kc-hover-hex: #f5f5f7;
|
||||
|
||||
@if $o-webclient-color-scheme == dark {
|
||||
$_kc-bg-hex: #22262d !global;
|
||||
$_kc-border-hex: #424245 !global;
|
||||
$_kc-hover-hex: #2d3138 !global;
|
||||
}
|
||||
|
||||
.o_fp_kcard {
|
||||
background: $_kc-bg-hex;
|
||||
border: 1px solid $_kc-border-hex;
|
||||
border-radius: 6px;
|
||||
padding: 0.55rem 0.7rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.1s ease;
|
||||
|
||||
&:hover { background: $_kc-hover-hex; }
|
||||
}
|
||||
|
||||
.o_fp_kcard_compact { padding: 0.4rem 0.55rem; }
|
||||
|
||||
.o_fp_kcard_h1 {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
font-weight: 700; font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.o_fp_kcard_h2 {
|
||||
color: var(--text-secondary, #666);
|
||||
font-size: 0.75rem;
|
||||
margin-top: 0.15rem;
|
||||
}
|
||||
|
||||
.o_fp_kcard_qty {
|
||||
display: flex; justify-content: space-between;
|
||||
font-size: 0.7rem; color: var(--text-secondary, #777);
|
||||
margin-top: 0.3rem;
|
||||
}
|
||||
|
||||
.o_fp_kcard_due { color: var(--text-secondary, #999); }
|
||||
|
||||
.o_fp_kcard_bar {
|
||||
height: 4px; background: rgba(0,0,0,0.08);
|
||||
border-radius: 2px; overflow: hidden;
|
||||
margin-top: 0.3rem;
|
||||
|
||||
> div { height: 100%; background: #34c759; }
|
||||
}
|
||||
|
||||
.o_fp_kcard_chips {
|
||||
display: flex; gap: 0.35rem; flex-wrap: wrap;
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
|
||||
.o_fp_kcard_pri { width: 8px; height: 8px; border-radius: 50%; }
|
||||
.o_fp_kcard_pri_danger { background: #ff3b30; }
|
||||
.o_fp_kcard_pri_warning { background: #ff9f0a; }
|
||||
.o_fp_kcard_pri_muted { background: transparent; }
|
||||
|
||||
.o_fp_kcard_blocked {
|
||||
background: rgba(255,159,10,0.15);
|
||||
color: #b06600;
|
||||
padding: 0.15rem 0.4rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.o_fp_kcard_wc {
|
||||
color: var(--text-secondary, #999);
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
@if $o-webclient-color-scheme == dark {
|
||||
.o_fp_kcard_blocked { color: #ffb84d; }
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="fusion_plating_shopfloor.KanbanCard">
|
||||
<div t-att-class="'o_fp_kcard ' + (isCompact ? 'o_fp_kcard_compact' : '')"
|
||||
t-on-click="onClick">
|
||||
<div class="o_fp_kcard_h1">
|
||||
<span class="o_fp_kcard_wo"><t t-esc="props.data.display_wo_name"/></span>
|
||||
<span t-att-class="'o_fp_kcard_pri o_fp_kcard_pri_' + priorityDot"/>
|
||||
</div>
|
||||
<div class="o_fp_kcard_h2">
|
||||
<span t-esc="props.data.customer or ''"/>
|
||||
<t t-if="props.data.part"> · <t t-esc="props.data.part"/></t>
|
||||
</div>
|
||||
<div class="o_fp_kcard_qty" t-if="props.data.qty">
|
||||
<span><t t-esc="props.data.qty_done or 0"/> / <t t-esc="props.data.qty"/> done</span>
|
||||
<span t-if="props.data.date_deadline" class="o_fp_kcard_due">
|
||||
Due <t t-esc="props.data.date_deadline"/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="o_fp_kcard_bar" t-if="props.data.qty">
|
||||
<div t-att-style="'width: ' + progressPct + '%'"/>
|
||||
</div>
|
||||
<div class="o_fp_kcard_chips">
|
||||
<WorkflowChip t-if="props.showWorkflowChip and props.data.workflow_state"
|
||||
state="props.data.workflow_state"/>
|
||||
<span t-if="props.data.blocker_kind and props.data.blocker_kind !== 'none'"
|
||||
class="o_fp_kcard_blocked"
|
||||
t-att-title="props.data.blocker_reason">
|
||||
<i class="fa fa-lock"/> Blocked
|
||||
</span>
|
||||
<span t-if="props.showWorkcenter and props.data.work_center"
|
||||
class="o_fp_kcard_wc">
|
||||
@ <t t-esc="props.data.work_center"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
Reference in New Issue
Block a user