fix(work-orders): allow read-only open for canceled board rows

This commit is contained in:
Arthur Bassi 2026-07-22 10:18:27 -03:00 • committed by Alexandre Brandizzi
parent 32609659b7
commit fef7918f6b
2 changed files with 83 additions and 43 deletions

View file

@ -3,7 +3,7 @@ import { Eye, Pencil } from "lucide-react";
type WoTableRowActionsProps = { type WoTableRowActionsProps = {
onView: () => void; onView: () => void;
onEdit: () => void; onEdit?: () => void;
}; };
export function WoTableRowActions({ onView, onEdit }: WoTableRowActionsProps) { export function WoTableRowActions({ onView, onEdit }: WoTableRowActionsProps) {
@ -61,7 +61,7 @@ export function WoTableRowActions({ onView, onEdit }: WoTableRowActionsProps) {
<div className="flex items-center justify-end gap-1 opacity-0 transition-opacity group-hover/row:opacity-100"> <div className="flex items-center justify-end gap-1 opacity-0 transition-opacity group-hover/row:opacity-100">
{mk(Eye, "View details", onView, "#e6f4fb", "var(--primary)")} {mk(Eye, "View details", onView, "#e6f4fb", "var(--primary)")}
{mk(Pencil, "Edit", onEdit, "#e6f4fb", "var(--primary)")} {onEdit ? mk(Pencil, "Edit", onEdit, "#e6f4fb", "var(--primary)") : null}
</div> </div>
); );
} }

View file

@ -85,23 +85,37 @@ export function WoTableRow({
} }
const patchAndMaybeAuto = (patch: Partial<WorkOrderTableRow>) => { const patchAndMaybeAuto = (patch: Partial<WorkOrderTableRow>) => {
if (isCanceled) return;
const merged = { ...row, ...patch }; const merged = { ...row, ...patch };
const auto = maybeAutoSchedulePatch(merged); const auto = maybeAutoSchedulePatch(merged);
handlers.onPatch(row.id, { ...patch, ...auto }); handlers.onPatch(row.id, { ...patch, ...auto });
}; };
const patchRow = (patch: Partial<WorkOrderTableRow>) => {
if (isCanceled) return;
handlers.onPatch(row.id, patch);
};
/** Block mutation controls on canceled rows while keeping row/View clickable. */
const mutationCellStyle = isCanceled ? ({ pointerEvents: "none" } as const) : undefined;
return ( return (
<tr <tr
id={`wo-row-${row.id}`} id={`wo-row-${row.id}`}
draggable={!isCanceled} draggable={!isCanceled}
title={isCanceled ? "This work order has been cancelled" : undefined} title={isCanceled ? "This work order has been cancelled" : undefined}
onClick={() => handlers.onRowClick(row)} onClick={() => handlers.onRowClick(row)}
onDragStart={() => handlers.onDragStart(row.id)} onDragStart={() => {
if (isCanceled) return;
handlers.onDragStart(row.id);
}}
onDragOver={(e) => { onDragOver={(e) => {
if (isCanceled) return;
e.preventDefault(); e.preventDefault();
handlers.onDragOver(row.id); handlers.onDragOver(row.id);
}} }}
onDrop={(e) => { onDrop={(e) => {
if (isCanceled) return;
e.preventDefault(); e.preventDefault();
handlers.onDrop(); handlers.onDrop();
}} }}
@ -112,7 +126,6 @@ export function WoTableRow({
borderBottom: "1px solid var(--border)", borderBottom: "1px solid var(--border)",
borderTop: isDropTarget ? "2px solid var(--color-primary)" : undefined, borderTop: isDropTarget ? "2px solid var(--color-primary)" : undefined,
opacity: isCanceled ? 0.45 : isDragging ? 0.4 : 1, opacity: isCanceled ? 0.45 : isDragging ? 0.4 : 1,
pointerEvents: isCanceled ? "none" : undefined,
background: isFlashing || isSelected ? "var(--color-primary-tint)" : "var(--card)", background: isFlashing || isSelected ? "var(--color-primary-tint)" : "var(--card)",
outline: isFlashing ? "2px solid var(--color-primary)" : "none", outline: isFlashing ? "2px solid var(--color-primary)" : "none",
outlineOffset: isFlashing ? "-2px" : undefined, outlineOffset: isFlashing ? "-2px" : undefined,
@ -131,23 +144,34 @@ export function WoTableRow({
}} }}
> >
<td <td
style={{ padding: "0 0 0 6px", boxShadow: firstCellShadow }} style={{ padding: "0 0 0 6px", boxShadow: firstCellShadow, ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<span <span
className="flex items-center justify-center" className="flex items-center justify-center"
style={{ style={{
cursor: isDragging ? "grabbing" : "grab", cursor: isCanceled ? "default" : isDragging ? "grabbing" : "grab",
color: "var(--color-text-muted)", color: "var(--color-text-muted)",
}} }}
> >
<GripVertical size={14} /> <GripVertical size={14} />
</span> </span>
</td> </td>
<td style={{ padding: "0 2px 0 8px" }} onClick={(e) => e.stopPropagation()}> <td
<FlagCell color={flagColor} onChange={(c) => handlers.setFlag(row.id, c)} /> style={{ padding: "0 2px 0 8px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<FlagCell
color={flagColor}
disabled={isCanceled}
onChange={(c) => handlers.setFlag(row.id, c)}
/>
</td> </td>
<td className="group/cell" style={{ padding: "0 14px" }} onClick={(e) => e.stopPropagation()}> <td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<SiteCell <SiteCell
value={row.site} value={row.site}
location={row.location} location={row.location}
@ -158,7 +182,7 @@ export function WoTableRow({
sites={sites} sites={sites}
q={search} q={search}
onSave={(p) => onSave={(p) =>
handlers.onPatch(row.id, { patchRow({
site: p.site, site: p.site,
location: p.location, location: p.location,
locationId: p.locationId, locationId: p.locationId,
@ -169,7 +193,7 @@ export function WoTableRow({
} }
/> />
</td> </td>
<td style={{ padding: "0 14px", whiteSpace: "nowrap" }}> <td style={{ padding: "0 14px", whiteSpace: "nowrap", ...mutationCellStyle }}>
<EditableWONumberCell <EditableWONumberCell
value={row.woNumber} value={row.woNumber}
rescheduleCount={row.rescheduleCount} rescheduleCount={row.rescheduleCount}
@ -177,18 +201,22 @@ export function WoTableRow({
q={search} q={search}
currentId={row.id} currentId={row.id}
allOrders={allRows} allOrders={allRows}
onSave={(v) => handlers.onPatch(row.id, { woNumber: v })} onSave={(v) => patchRow({ woNumber: v })}
onDuplicateFound={handlers.onDuplicateFound} onDuplicateFound={handlers.onDuplicateFound}
/> />
</td> </td>
<td className="group/cell" style={{ padding: "0 14px" }} onClick={(e) => e.stopPropagation()}> <td
<TypeCell className="group/cell"
value={row.type} style={{ padding: "0 14px", ...mutationCellStyle }}
q={search} onClick={(e) => e.stopPropagation()}
onSave={(t) => handlers.onPatch(row.id, { type: t })} >
/> <TypeCell value={row.type} q={search} onSave={(t) => patchRow({ type: t })} />
</td> </td>
<td className="group/cell" style={{ padding: "0 14px" }} onClick={(e) => e.stopPropagation()}> <td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<AssignedToCell <AssignedToCell
dispatcherId={row.dispatcherId} dispatcherId={row.dispatcherId}
users={users} users={users}
@ -196,7 +224,11 @@ export function WoTableRow({
onSave={(id, name) => patchAndMaybeAuto({ dispatcherId: id, dispatcherName: name })} onSave={(id, name) => patchAndMaybeAuto({ dispatcherId: id, dispatcherName: name })}
/> />
</td> </td>
<td className="group/cell" style={{ padding: "0 14px" }} onClick={(e) => e.stopPropagation()}> <td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<DateCell <DateCell
value={row.scheduledOn} value={row.scheduledOn}
q={search} q={search}
@ -215,22 +247,25 @@ export function WoTableRow({
} }
/> />
</td> </td>
<td className="group/cell" style={{ padding: "0 14px" }} onClick={(e) => e.stopPropagation()}> <td
<DateCell className="group/cell"
value={row.dueDate} style={{ padding: "0 14px", ...mutationCellStyle }}
q={search} onClick={(e) => e.stopPropagation()}
onSave={(date) => handlers.onPatch(row.id, { dueDate: date })} >
/> <DateCell value={row.dueDate} q={search} onSave={(date) => patchRow({ dueDate: date })} />
</td> </td>
<td className="group/cell" style={{ padding: "0 14px" }} onClick={(e) => e.stopPropagation()}> <td
<PMTypeCell className="group/cell"
value={row.pm} style={{ padding: "0 14px", ...mutationCellStyle }}
pmTypes={pmTypes} onClick={(e) => e.stopPropagation()}
q={search} >
onSave={(pm) => handlers.onPatch(row.id, { pm })} <PMTypeCell value={row.pm} pmTypes={pmTypes} q={search} onSave={(pm) => patchRow({ pm })} />
/>
</td> </td>
<td className="group/cell" style={{ padding: "0 14px" }} onClick={(e) => e.stopPropagation()}> <td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<VendorCell <VendorCell
company={row.company} company={row.company}
tech={row.tech} tech={row.tech}
@ -238,7 +273,7 @@ export function WoTableRow({
vendors={vendors} vendors={vendors}
q={search} q={search}
onSave={(p) => onSave={(p) =>
handlers.onPatch(row.id, { patchRow({
company: p.company, company: p.company,
tech: p.tech, tech: p.tech,
techPhone: p.techPhone, techPhone: p.techPhone,
@ -246,16 +281,21 @@ export function WoTableRow({
} }
/> />
</td> </td>
<td className="group/cell" style={{ padding: "0 14px" }} onClick={(e) => e.stopPropagation()}> <td
<ApptTimeCell className="group/cell"
apptTime={row.apptTime} style={{ padding: "0 14px", ...mutationCellStyle }}
onSave={(v) => handlers.onPatch(row.id, { apptTime: v })} onClick={(e) => e.stopPropagation()}
/> >
<ApptTimeCell apptTime={row.apptTime} onSave={(v) => patchRow({ apptTime: v })} />
</td> </td>
<td className="group/cell" style={{ padding: "0 14px" }} onClick={(e) => e.stopPropagation()}> <td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<StatusCell <StatusCell
wo={row} wo={row}
onChangeStatus={(status) => handlers.onPatch(row.id, { status })} onChangeStatus={(status) => patchRow({ status })}
onOpenSlideOver={() => handlers.onOpenSlideOver(row)} onOpenSlideOver={() => handlers.onOpenSlideOver(row)}
onPastDueAttempt={() => handlers.onPastDueAttempt?.(row.id)} onPastDueAttempt={() => handlers.onPastDueAttempt?.(row.id)}
/> />
@ -272,7 +312,7 @@ export function WoTableRow({
<td style={{ padding: "0 8px" }} onClick={(e) => e.stopPropagation()}> <td style={{ padding: "0 8px" }} onClick={(e) => e.stopPropagation()}>
<WoTableRowActions <WoTableRowActions
onView={() => handlers.onView(row)} onView={() => handlers.onView(row)}
onEdit={() => handlers.onEdit(row)} onEdit={isCanceled ? undefined : () => handlers.onEdit(row)}
/> />
</td> </td>
</tr> </tr>