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:
gsinghpal
2026-04-19 13:05:27 -04:00
parent 8fc864623b
commit e01a2a0e35
3 changed files with 112 additions and 63 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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>