mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 16:19:01 +00:00
fix(work-orders): extract WO number editor under max-lines gate
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
3cd8298c62
commit
392ac86757
1 changed files with 78 additions and 89 deletions
|
|
@ -1,45 +1,30 @@
|
|||
import { useEffect, useState } from "react";
|
||||
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
import { workOrdersApi } from "@/domain/work-orders/api/work-orders-api";
|
||||
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
||||
import { findDuplicateWoNumberRow } from "@/domain/work-orders/utils/find-duplicate-wo-number-row";
|
||||
|
||||
import { HighlightText } from "./highlight-text";
|
||||
|
||||
type EditableWONumberCellProps = {
|
||||
value: string;
|
||||
|
||||
rescheduleCount: number;
|
||||
|
||||
carriedOver?: number;
|
||||
|
||||
q?: string;
|
||||
|
||||
currentId: string | number;
|
||||
|
||||
allOrders: WorkOrderTableRow[];
|
||||
|
||||
onSave: (v: string) => void;
|
||||
|
||||
onDuplicateFound?: (existing: WorkOrderTableRow) => void;
|
||||
};
|
||||
|
||||
function CounterPill({ label, count }: { label: string; count: number }) {
|
||||
if (count < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (count < 2) return null;
|
||||
return (
|
||||
<span
|
||||
title={label}
|
||||
className="inline-flex h-[18px] items-center rounded px-1.5 text-[10px] font-semibold"
|
||||
style={{
|
||||
background: "var(--color-bg-muted)",
|
||||
|
||||
color: "var(--color-text-secondary)",
|
||||
|
||||
border: "1px solid var(--color-border)",
|
||||
}}
|
||||
>
|
||||
|
|
@ -48,54 +33,104 @@ function CounterPill({ label, count }: { label: string; count: number }) {
|
|||
);
|
||||
}
|
||||
|
||||
type WoNumberEditorProps = {
|
||||
draft: string;
|
||||
invalid: boolean;
|
||||
isChecking: boolean;
|
||||
onDraftChange: (value: string) => void;
|
||||
onCommit: () => void;
|
||||
onCancel: () => void;
|
||||
onBlur: () => void;
|
||||
};
|
||||
|
||||
function WoNumberEditor({
|
||||
draft,
|
||||
invalid,
|
||||
isChecking,
|
||||
onDraftChange,
|
||||
onCommit,
|
||||
onCancel,
|
||||
onBlur,
|
||||
}: WoNumberEditorProps) {
|
||||
return (
|
||||
<input
|
||||
autoFocus
|
||||
disabled={isChecking}
|
||||
inputMode="numeric"
|
||||
value={draft}
|
||||
onChange={(e) => onDraftChange(e.target.value.replace(/\D/g, "").slice(0, 11))}
|
||||
onFocus={(e) => e.target.select()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
onCommit();
|
||||
} else if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
onCancel();
|
||||
}
|
||||
}}
|
||||
onBlur={onBlur}
|
||||
placeholder="12345678901"
|
||||
aria-label="Work order number"
|
||||
aria-invalid={invalid}
|
||||
className="font-mono focus:outline-none"
|
||||
style={{
|
||||
fontSize: 13,
|
||||
fontWeight: 700,
|
||||
color: "var(--foreground)",
|
||||
background: "var(--color-bg-surface)",
|
||||
border: `1px solid ${invalid ? "var(--color-destructive)" : "var(--color-ring)"}`,
|
||||
boxShadow: `0 0 0 1px ${invalid ? "var(--color-destructive)" : "var(--color-accent)"}`,
|
||||
borderRadius: 4,
|
||||
padding: "2px 4px",
|
||||
width: 120,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
async function resolveDuplicateWoNumber(
|
||||
draft: string,
|
||||
allOrders: WorkOrderTableRow[],
|
||||
currentId: string | number,
|
||||
) {
|
||||
const local = findDuplicateWoNumberRow(draft, allOrders, currentId);
|
||||
const remote = local ?? (await workOrdersApi.findByWoNumber(draft));
|
||||
return remote && String(remote.id) !== String(currentId) ? remote : undefined;
|
||||
}
|
||||
|
||||
export function EditableWONumberCell({
|
||||
value,
|
||||
|
||||
rescheduleCount,
|
||||
|
||||
carriedOver = 0,
|
||||
|
||||
q,
|
||||
|
||||
currentId,
|
||||
|
||||
allOrders,
|
||||
|
||||
onSave,
|
||||
|
||||
onDuplicateFound,
|
||||
}: EditableWONumberCellProps) {
|
||||
const [editing, setEditing] = useState(false);
|
||||
|
||||
const [draft, setDraft] = useState(value);
|
||||
|
||||
const [isChecking, setIsChecking] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editing) {
|
||||
setDraft(value);
|
||||
}
|
||||
if (!editing) setDraft(value);
|
||||
}, [value, editing]);
|
||||
|
||||
const commit = () => {
|
||||
if (draft === value || isChecking) {
|
||||
setEditing(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (draft.length !== 11) {
|
||||
toast("Work order number must be exactly 11 digits");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setIsChecking(true);
|
||||
void (async () => {
|
||||
try {
|
||||
const local = findDuplicateWoNumberRow(draft, allOrders, currentId);
|
||||
const remote = local ?? (await workOrdersApi.findByWoNumber(draft));
|
||||
const duplicate = remote && String(remote.id) !== String(currentId) ? remote : undefined;
|
||||
const duplicate = await resolveDuplicateWoNumber(draft, allOrders, currentId);
|
||||
if (duplicate) {
|
||||
onDuplicateFound?.(duplicate);
|
||||
setDraft(value);
|
||||
|
|
@ -113,19 +148,15 @@ export function EditableWONumberCell({
|
|||
|
||||
const cancel = () => {
|
||||
setDraft(value);
|
||||
|
||||
setEditing(false);
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
if (isChecking) {
|
||||
return;
|
||||
}
|
||||
if (isChecking) return;
|
||||
if (draft !== value && draft.length === 11) {
|
||||
commit();
|
||||
return;
|
||||
}
|
||||
|
||||
setEditing(false);
|
||||
};
|
||||
|
||||
|
|
@ -134,76 +165,34 @@ export function EditableWONumberCell({
|
|||
return (
|
||||
<div className="flex items-center gap-1.5">
|
||||
{editing ? (
|
||||
<input
|
||||
autoFocus
|
||||
disabled={isChecking}
|
||||
inputMode="numeric"
|
||||
value={draft}
|
||||
onChange={(e) => setDraft(e.target.value.replace(/\D/g, "").slice(0, 11))}
|
||||
onFocus={(e) => e.target.select()}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
|
||||
commit();
|
||||
} else if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
|
||||
cancel();
|
||||
}
|
||||
}}
|
||||
<WoNumberEditor
|
||||
draft={draft}
|
||||
invalid={invalid}
|
||||
isChecking={isChecking}
|
||||
onDraftChange={setDraft}
|
||||
onCommit={commit}
|
||||
onCancel={cancel}
|
||||
onBlur={handleBlur}
|
||||
placeholder="12345678901"
|
||||
aria-label="Work order number"
|
||||
aria-invalid={invalid}
|
||||
className="font-mono focus:outline-none"
|
||||
style={{
|
||||
fontSize: 13,
|
||||
|
||||
fontWeight: 700,
|
||||
|
||||
color: "var(--foreground)",
|
||||
|
||||
background: "var(--color-bg-surface)",
|
||||
|
||||
border: `1px solid ${invalid ? "var(--color-destructive)" : "var(--color-ring)"}`,
|
||||
|
||||
boxShadow: `0 0 0 1px ${invalid ? "var(--color-destructive)" : "var(--color-accent)"}`,
|
||||
|
||||
borderRadius: 4,
|
||||
|
||||
padding: "2px 4px",
|
||||
|
||||
width: 120,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
setEditing(true);
|
||||
}}
|
||||
className="inline-block cursor-text font-mono transition-colors hover:bg-[var(--color-bg-muted)]"
|
||||
style={{
|
||||
fontSize: 13,
|
||||
|
||||
fontWeight: 700,
|
||||
|
||||
color: value ? "var(--foreground)" : "var(--color-text-muted)",
|
||||
|
||||
borderRadius: 4,
|
||||
|
||||
padding: "2px 4px",
|
||||
}}
|
||||
>
|
||||
{value ? <HighlightText text={value} q={q} /> : "-"}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<CounterPill label="Reschedules" count={rescheduleCount} />
|
||||
|
||||
<CounterPill label="Carried over" count={carriedOver} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue