fix(shopfloor): Manager Desk WO row layout — proper info stack + action group
Screenshot showed the new WO row was broken:
• Kind chip text clipped ("Mas" instead of "Mask", "Rac" instead of
"Racking")
• WO name truncated to first 4 chars
• The wet WO had no info column at all — kind chip + name pushed
off-screen by the tank picker
• "Needs:" chip showed as just an exclamation icon with "N" cut off
• Take Over and Open WO buttons unevenly sized
Root cause: `.o_fp_mgr_wo_info` carried `nowrap + ellipsis` from the
old single-line design, but the new template stacks kind chip + name +
meta + needs across multiple lines. Plus the rigid grid
(1fr auto auto auto auto) gave the info column whatever the dropdowns
left over — usually nothing.
**Layout rewrite** — flex with wrap instead of grid:
• `.o_fp_mgr_wo_row` — flex row, info on left, actions on right,
wraps to two rows on narrow viewports.
• `.o_fp_mgr_wo_info` — `flex: 1 1 280px` so it grows but never
narrower than 280px. Contains a vertical stack: title row
(badge + name) → meta row (workcenter / role / equipment chips)
→ needs row (yellow chip if anything missing).
• `.o_fp_mgr_wo_actions` — `flex: 0 0 auto` with its own gap, so
pickers + buttons align cleanly to the right.
• Kind chip can wrap to its full label; meta row uses `flex-wrap`
so equipment hints don't get clipped.
• Take Over collapses to icon-only with title tooltip — the row
was getting too wide on the wet kind (which adds the tank picker).
**Other tweaks**
• Added `tank_id` to the controller payload so the tank picker
pre-selects the current tank (was missing on the previous
"current tank" highlight).
@720px the action group stacks below the info — pickers go full-width,
buttons get `min-height: $fp-touch-min` for thumb tap.
Asset cache cleared as part of the deploy so the SCSS recompiles.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -116,6 +116,7 @@ class FpManagerDashboardController(http.Controller):
|
||||
'duration_expected': w.duration_expected or 0,
|
||||
'bath': w.x_fc_bath_id.name or '',
|
||||
'tank': w.x_fc_tank_id.name or '',
|
||||
'tank_id': w.x_fc_tank_id.id if w.x_fc_tank_id else False,
|
||||
'priority': w.x_fc_priority or '0',
|
||||
'assigned_user_id': (
|
||||
w.x_fc_assigned_user_id.id
|
||||
|
||||
@@ -437,36 +437,66 @@
|
||||
// -------------------------------------------------------------------------
|
||||
// WO row inside expanded card
|
||||
// -------------------------------------------------------------------------
|
||||
// WO row = info column (vertical stack) + actions column (pickers + buttons)
|
||||
// Flex with wrap so narrow viewports drop actions below the info naturally
|
||||
// instead of squishing everything into a single broken grid line.
|
||||
.o_fp_mgr_wo_row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto auto auto auto;
|
||||
gap: $fp-space-2;
|
||||
align-items: center;
|
||||
padding: $fp-space-2 $fp-space-3;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: $fp-space-3;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
padding: $fp-space-3;
|
||||
background-color: $fp-card;
|
||||
border: 1px solid #{$fp-border};
|
||||
border-radius: $fp-radius-sm;
|
||||
font-size: $fp-text-sm;
|
||||
}
|
||||
|
||||
@media (max-width: 1400px) {
|
||||
grid-template-columns: 1fr auto auto;
|
||||
.o_fp_mgr_picker:nth-of-type(1) { grid-column: 1 / -1; }
|
||||
.o_fp_mgr_picker:nth-of-type(2) { grid-column: 1 / -1; }
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
.o_fp_mgr_picker { max-width: 100% !important; width: 100%; }
|
||||
.btn { min-height: $fp-touch-min; }
|
||||
}
|
||||
}
|
||||
.o_fp_mgr_wo_info {
|
||||
min-width: 0;
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
font-weight: $fp-weight-semibold;
|
||||
flex: 1 1 280px; // grows but never narrower than 280px
|
||||
min-width: 0; // allows children to shrink properly
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $fp-space-1;
|
||||
color: $fp-ink;
|
||||
|
||||
// Title row — kind badge + WO name + step number
|
||||
.o_fp_mgr_wo_title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $fp-space-2;
|
||||
flex-wrap: wrap;
|
||||
font-weight: $fp-weight-semibold;
|
||||
font-size: $fp-text-base;
|
||||
line-height: 1.25;
|
||||
}
|
||||
// Meta row — workcenter / role / set equipment
|
||||
.o_fp_mgr_wo_meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $fp-space-2;
|
||||
flex-wrap: wrap;
|
||||
font-size: $fp-text-xs;
|
||||
color: $fp-ink-mute;
|
||||
i { margin-right: 2px; }
|
||||
}
|
||||
// Chip row — what's still missing for the manager to set
|
||||
.o_fp_mgr_wo_needs {
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_fp_mgr_wo_actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: $fp-space-2;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.o_fp_mgr_picker {
|
||||
min-width: 140px; max-width: 220px;
|
||||
min-width: 180px;
|
||||
min-height: 40px;
|
||||
padding: 0 $fp-space-3;
|
||||
border: 1px solid #{$fp-border};
|
||||
@@ -485,10 +515,18 @@
|
||||
font-weight: $fp-weight-semibold;
|
||||
background-color: $fp-card-soft;
|
||||
color: $fp-ink;
|
||||
white-space: nowrap;
|
||||
transition: filter $fp-dur-fast $fp-ease;
|
||||
&:hover { filter: brightness(0.95); }
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
// Stack info + actions vertically on narrow viewports
|
||||
.o_fp_mgr_wo_actions { width: 100%; }
|
||||
.o_fp_mgr_picker { flex: 1 1 100%; max-width: 100%; }
|
||||
.o_fp_mgr_wo_row .btn { min-height: $fp-touch-min; }
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Status chips (reused)
|
||||
|
||||
@@ -134,57 +134,67 @@
|
||||
t-if="state.expandedMoId === card.mo_id or state.mode === 'detailed'">
|
||||
<t t-foreach="card.wos" t-as="wo" t-key="wo.id">
|
||||
<div class="o_fp_mgr_wo_row">
|
||||
<!-- LEFT: information stack (badge, name, meta, needs) -->
|
||||
<div class="o_fp_mgr_wo_info">
|
||||
<div>
|
||||
<div class="o_fp_mgr_wo_title">
|
||||
<span t-attf-class="o_fp_chip o_fp_chip_kind o_fp_chip_kind_{{ wo.wo_kind }}"
|
||||
t-esc="wo.wo_kind_label || wo.wo_kind"/>
|
||||
<strong class="ms-1" t-esc="wo.name"/>
|
||||
<span t-esc="wo.name"/>
|
||||
</div>
|
||||
<div class="text-muted small mt-1">
|
||||
<t t-esc="wo.workcenter"/>
|
||||
<t t-if="wo.role_name"> · <i class="fa fa-id-badge"/> <t t-esc="wo.role_name"/></t>
|
||||
<t t-if="wo.bath"> · <i class="fa fa-flask"/> <t t-esc="wo.bath"/></t>
|
||||
<t t-if="wo.oven"> · <i class="fa fa-fire"/> <t t-esc="wo.oven"/></t>
|
||||
<t t-if="wo.rack"> · <i class="fa fa-th"/> <t t-esc="wo.rack"/></t>
|
||||
<t t-if="wo.masking_material"> · <i class="fa fa-tag"/> <t t-esc="wo.masking_material"/></t>
|
||||
<div class="o_fp_mgr_wo_meta">
|
||||
<span><i class="fa fa-cog"/><t t-esc="wo.workcenter"/></span>
|
||||
<span t-if="wo.role_name">· <i class="fa fa-id-badge"/><t t-esc="wo.role_name"/></span>
|
||||
<span t-if="wo.bath">· <i class="fa fa-flask"/><t t-esc="wo.bath"/></span>
|
||||
<span t-if="wo.oven">· <i class="fa fa-fire"/><t t-esc="wo.oven"/></span>
|
||||
<span t-if="wo.rack">· <i class="fa fa-th"/><t t-esc="wo.rack"/></span>
|
||||
<span t-if="wo.masking_material">· <i class="fa fa-tag"/><t t-esc="wo.masking_material"/></span>
|
||||
</div>
|
||||
<div t-if="wo.missing_for_release"
|
||||
class="o_fp_chip o_fp_chip_warning mt-1">
|
||||
<i class="fa fa-exclamation-circle"/> Needs: <t t-esc="wo.missing_for_release"/>
|
||||
class="o_fp_mgr_wo_needs">
|
||||
<span class="o_fp_chip o_fp_chip_warning">
|
||||
<i class="fa fa-exclamation-circle me-1"/>
|
||||
Needs: <t t-esc="wo.missing_for_release"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<select class="o_fp_mgr_picker"
|
||||
t-on-change="(ev) => this.onAssignWorker(wo, ev.target.value)">
|
||||
<option value="">— Assign worker —</option>
|
||||
<t t-foreach="operatorsForWO(wo)" t-as="op" t-key="op.id">
|
||||
<option t-att-value="op.id"
|
||||
t-att-selected="wo.assigned_user_id === op.id"
|
||||
t-att-data-bucket="op.bucket">
|
||||
<t t-if="op.is_clocked_in">●</t>
|
||||
<t t-else="">○</t>
|
||||
<t t-esc="' ' + op.name + operatorBadge(op)"/>
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
<select t-if="wo.wo_kind === 'wet'"
|
||||
class="o_fp_mgr_picker"
|
||||
t-on-change="(ev) => this.onAssignTank(wo, ev.target.value)">
|
||||
<option value="">— Tank —</option>
|
||||
<t t-foreach="state.overview.tanks" t-as="tnk" t-key="tnk.id">
|
||||
<option t-att-value="tnk.id">
|
||||
<t t-esc="tnk.name"/>
|
||||
<t t-if="tnk.current_bath"> · <t t-esc="tnk.current_bath"/></t>
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
<button class="btn"
|
||||
t-on-click="() => this.onTakeOver(wo)">
|
||||
<i class="fa fa-user"/> Take Over
|
||||
</button>
|
||||
<button class="btn"
|
||||
t-on-click="() => this.openRecord('mrp.workorder', wo.id)">
|
||||
Open WO
|
||||
</button>
|
||||
|
||||
<!-- RIGHT: action group (pickers + buttons) -->
|
||||
<div class="o_fp_mgr_wo_actions">
|
||||
<select class="o_fp_mgr_picker"
|
||||
t-on-change="(ev) => this.onAssignWorker(wo, ev.target.value)">
|
||||
<option value="">— Assign worker —</option>
|
||||
<t t-foreach="operatorsForWO(wo)" t-as="op" t-key="op.id">
|
||||
<option t-att-value="op.id"
|
||||
t-att-selected="wo.assigned_user_id === op.id"
|
||||
t-att-data-bucket="op.bucket">
|
||||
<t t-if="op.is_clocked_in">●</t>
|
||||
<t t-else="">○</t>
|
||||
<t t-esc="' ' + op.name + operatorBadge(op)"/>
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
<select t-if="wo.wo_kind === 'wet'"
|
||||
class="o_fp_mgr_picker"
|
||||
t-on-change="(ev) => this.onAssignTank(wo, ev.target.value)">
|
||||
<option value="">— Tank —</option>
|
||||
<t t-foreach="state.overview.tanks" t-as="tnk" t-key="tnk.id">
|
||||
<option t-att-value="tnk.id"
|
||||
t-att-selected="wo.tank_id === tnk.id">
|
||||
<t t-esc="tnk.name"/>
|
||||
<t t-if="tnk.current_bath"> · <t t-esc="tnk.current_bath"/></t>
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
<button class="btn"
|
||||
t-on-click="() => this.onTakeOver(wo)"
|
||||
title="Assign yourself">
|
||||
<i class="fa fa-user"/>
|
||||
</button>
|
||||
<button class="btn"
|
||||
t-on-click="() => this.openRecord('mrp.workorder', wo.id)">
|
||||
Open WO
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user