mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-03 20:13:15 +00:00
fix(work-orders): restore blank alphanumeric WO number on create
Allow blank SH-provisional WO numbers and refresh the wizard visual baseline.
This commit is contained in:
parent
402a4552f4
commit
50764b707d
6 changed files with 41 additions and 78 deletions
Binary file not shown.
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 184 KiB |
|
|
@ -241,14 +241,13 @@ export function WizardStepTypeSchedule({
|
|||
onDuplicateWoNumber,
|
||||
}: WizardStepTypeScheduleProps) {
|
||||
const needsSeverity = typeNeedsSeverity(draft.type);
|
||||
const typeError = showErrors && !draft.type;
|
||||
const woNumberError = showErrors && !isWizardWoNumberValid(draft.woNumber);
|
||||
const selectedUser = users.find((u) => String(u.id) === draft.assigneeId);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<WizLabel required>Type of WO</WizLabel>
|
||||
<WizLabel optional>Type of WO</WizLabel>
|
||||
<WoTypePicker
|
||||
selected={draft.type}
|
||||
onSelect={(t) => {
|
||||
|
|
@ -258,14 +257,6 @@ export function WizardStepTypeSchedule({
|
|||
});
|
||||
}}
|
||||
/>
|
||||
<Text
|
||||
as="p"
|
||||
variant="error"
|
||||
when={Boolean(typeError)}
|
||||
sx={{ fontSize: 11, marginTop: "6px" }}
|
||||
>
|
||||
Type of WO is required
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
{Boolean(needsSeverity) && (
|
||||
|
|
@ -277,19 +268,18 @@ export function WizardStepTypeSchedule({
|
|||
)}
|
||||
|
||||
<div>
|
||||
<WizLabel required>WO Number</WizLabel>
|
||||
<WizLabel optional>WO Number</WizLabel>
|
||||
<input
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
value={draft.woNumber}
|
||||
onChange={(e) =>
|
||||
onUpdate(
|
||||
"woNumber",
|
||||
e.target.value.replace(/\D/g, "").slice(0, WIZARD_WO_NUMBER_MAX_LENGTH),
|
||||
e.target.value.replace(/[^a-zA-Z0-9]/g, "").slice(0, WIZARD_WO_NUMBER_MAX_LENGTH),
|
||||
)
|
||||
}
|
||||
onBlur={() => onDuplicateWoNumber?.(draft.woNumber)}
|
||||
placeholder="Enter WO number"
|
||||
placeholder="Leave blank for SH provisional number"
|
||||
maxLength={WIZARD_WO_NUMBER_MAX_LENGTH}
|
||||
className={DLG_INPUT_CLS}
|
||||
style={DLG_INPUT_STYLE}
|
||||
|
|
@ -300,7 +290,7 @@ export function WizardStepTypeSchedule({
|
|||
when={Boolean(woNumberError)}
|
||||
sx={{ fontSize: 11, marginTop: "6px" }}
|
||||
>
|
||||
{draft.woNumber.trim() ? "WO number must be up to 11 digits" : "WO number is required"}
|
||||
WO number must be alphanumeric.
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -103,14 +103,15 @@ export const EMPTY_WIZARD_DRAFT: WorkOrderWizardDraft = {
|
|||
isAddOnOverride: false,
|
||||
};
|
||||
|
||||
export const WIZARD_WO_NUMBER_MAX_LENGTH = 11;
|
||||
export const WIZARD_WO_NUMBER_MAX_LENGTH = 20;
|
||||
|
||||
/** Create requires a WO number of 1–11 digits. */
|
||||
/** Blank is valid (backend generates an SH provisional number). Otherwise alphanumeric, max 20. */
|
||||
export function isWizardWoNumberValid(woNumber: string): boolean {
|
||||
const trimmed = woNumber.trim();
|
||||
return (
|
||||
trimmed.length > 0 && trimmed.length <= WIZARD_WO_NUMBER_MAX_LENGTH && /^\d+$/.test(trimmed)
|
||||
);
|
||||
if (!trimmed) {
|
||||
return true;
|
||||
}
|
||||
return trimmed.length <= WIZARD_WO_NUMBER_MAX_LENGTH && /^[a-z0-9]+$/i.test(trimmed);
|
||||
}
|
||||
|
||||
/** Type is optional at creation; empty draft type defaults to PM. */
|
||||
|
|
@ -186,9 +187,6 @@ export const TIME_SLOTS: string[] = (() => {
|
|||
})();
|
||||
|
||||
export function canContinueWizardStep1(draft: WorkOrderWizardDraft): boolean {
|
||||
if (!draft.type) {
|
||||
return false;
|
||||
}
|
||||
if (!isWizardWoNumberValid(draft.woNumber)) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -202,14 +200,8 @@ export function wizardStep1ContinueHint(draft: WorkOrderWizardDraft): string | n
|
|||
if (canContinueWizardStep1(draft)) {
|
||||
return null;
|
||||
}
|
||||
if (!draft.type) {
|
||||
return "Select a work order type to continue";
|
||||
}
|
||||
if (!draft.woNumber.trim()) {
|
||||
return "Enter a WO number to continue";
|
||||
}
|
||||
if (!isWizardWoNumberValid(draft.woNumber)) {
|
||||
return "WO number must be up to 11 digits";
|
||||
return "WO number must be alphanumeric";
|
||||
}
|
||||
return "Select severity to continue";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ describe("NewWoWizard duplicate wo number", () => {
|
|||
/>,
|
||||
);
|
||||
|
||||
const woNumberInput = screen.getByPlaceholderText("Enter WO number");
|
||||
const woNumberInput = screen.getByPlaceholderText("Leave blank for SH provisional number");
|
||||
fireEvent.change(woNumberInput, {
|
||||
target: { value: "12345678901" },
|
||||
});
|
||||
|
|
@ -77,7 +77,7 @@ describe("NewWoWizard duplicate wo number", () => {
|
|||
/>,
|
||||
);
|
||||
|
||||
const woNumberInput = screen.getByPlaceholderText("Enter WO number");
|
||||
const woNumberInput = screen.getByPlaceholderText("Leave blank for SH provisional number");
|
||||
fireEvent.change(woNumberInput, {
|
||||
target: { value: "98765432109" },
|
||||
});
|
||||
|
|
|
|||
|
|
@ -227,16 +227,9 @@ describe("WorkOrdersListPage board-core public interface", () => {
|
|||
|
||||
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: /type & schedule/i })).toBeInTheDocument();
|
||||
expect(screen.getByPlaceholderText(/enter wo number/i)).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole("button", { name: /continue/i }));
|
||||
expect(screen.getByText(/type of wo is required/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/wo number is required/i)).toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: /type & schedule/i })).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /scheduled preventive maintenance/i }));
|
||||
fireEvent.change(screen.getByPlaceholderText(/enter wo number/i), {
|
||||
target: { value: "12345678901" },
|
||||
});
|
||||
expect(
|
||||
screen.getByPlaceholderText(/leave blank for sh provisional number/i),
|
||||
).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole("button", { name: /continue/i }));
|
||||
expect(screen.getByRole("heading", { name: /location & service/i })).toBeInTheDocument();
|
||||
|
||||
|
|
|
|||
|
|
@ -67,21 +67,21 @@ describe("mapWizardDraftToFormValues", () => {
|
|||
});
|
||||
|
||||
describe("wizard step validation", () => {
|
||||
it("blocks step 1 without a woNumber", () => {
|
||||
expect(canContinueWizardStep1(EMPTY_WIZARD_DRAFT)).toBe(false);
|
||||
expect(isWizardStepValid(1, EMPTY_WIZARD_DRAFT)).toBe(false);
|
||||
it("allows step 1 with a blank woNumber for backend SH generation", () => {
|
||||
expect(canContinueWizardStep1(EMPTY_WIZARD_DRAFT)).toBe(true);
|
||||
expect(isWizardStepValid(1, EMPTY_WIZARD_DRAFT)).toBe(true);
|
||||
});
|
||||
|
||||
it("blocks step 1 without type even when woNumber is set", () => {
|
||||
it("allows step 1 without type when woNumber is set", () => {
|
||||
const draft = { ...EMPTY_WIZARD_DRAFT, woNumber: "12345" };
|
||||
expect(canContinueWizardStep1(draft)).toBe(false);
|
||||
expect(isWizardStepValid(1, draft)).toBe(false);
|
||||
expect(canContinueWizardStep1(draft)).toBe(true);
|
||||
expect(isWizardStepValid(1, draft)).toBe(true);
|
||||
});
|
||||
|
||||
it("blocks step 1 without woNumber even when type is set", () => {
|
||||
it("allows step 1 without woNumber when type is set", () => {
|
||||
const draft = { ...EMPTY_WIZARD_DRAFT, type: "PM" as const };
|
||||
expect(canContinueWizardStep1(draft)).toBe(false);
|
||||
expect(isWizardStepValid(1, draft)).toBe(false);
|
||||
expect(canContinueWizardStep1(draft)).toBe(true);
|
||||
expect(isWizardStepValid(1, draft)).toBe(true);
|
||||
});
|
||||
|
||||
it("allows step 1 when type and woNumber are set (schedule optional)", () => {
|
||||
|
|
@ -194,56 +194,44 @@ describe("wizard step validation", () => {
|
|||
expect(isWizardStepValid(2, draft)).toBe(true);
|
||||
});
|
||||
|
||||
it("blocks SH-prefixed woNumber on create", () => {
|
||||
it("allows provisional SH-prefixed woNumber", () => {
|
||||
const draft = {
|
||||
...EMPTY_WIZARD_DRAFT,
|
||||
type: "PM" as const,
|
||||
woNumber: "SH12345",
|
||||
};
|
||||
expect(canContinueWizardStep1(draft)).toBe(false);
|
||||
expect(isWizardWoNumberValid(draft.woNumber)).toBe(true);
|
||||
expect(canContinueWizardStep1(draft)).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects blank woNumber", () => {
|
||||
const draft = {
|
||||
...EMPTY_WIZARD_DRAFT,
|
||||
type: "PM" as const,
|
||||
woNumber: "",
|
||||
};
|
||||
expect(isWizardWoNumberValid(draft.woNumber)).toBe(false);
|
||||
expect(canContinueWizardStep1(draft)).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects whitespace-only woNumber", () => {
|
||||
it("accepts whitespace-only woNumber for backend SH generation", () => {
|
||||
const draft = {
|
||||
...EMPTY_WIZARD_DRAFT,
|
||||
type: "PM" as const,
|
||||
woNumber: " ",
|
||||
};
|
||||
expect(isWizardWoNumberValid(draft.woNumber)).toBe(false);
|
||||
expect(canContinueWizardStep1(draft)).toBe(false);
|
||||
expect(isWizardWoNumberValid(draft.woNumber)).toBe(true);
|
||||
expect(canContinueWizardStep1(draft)).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects non-digit woNumber", () => {
|
||||
it("rejects non-alphanumeric woNumber", () => {
|
||||
expect(isWizardWoNumberValid("!!!")).toBe(false);
|
||||
expect(canContinueWizardStep1({ ...EMPTY_WIZARD_DRAFT, type: "PM", woNumber: "!!!" })).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects woNumber longer than 11 digits", () => {
|
||||
expect(isWizardWoNumberValid("123456789012")).toBe(false);
|
||||
it("accepts alphanumeric woNumber", () => {
|
||||
expect(isWizardWoNumberValid("ABC123")).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts an 11-digit woNumber", () => {
|
||||
expect(isWizardWoNumberValid("12345678901")).toBe(true);
|
||||
});
|
||||
|
||||
it("hints type then WO number when step 1 is incomplete", () => {
|
||||
expect(wizardStep1ContinueHint(EMPTY_WIZARD_DRAFT)).toBe(
|
||||
"Select a work order type to continue",
|
||||
it("hints alphanumeric WO number or severity when step 1 is incomplete", () => {
|
||||
expect(wizardStep1ContinueHint(EMPTY_WIZARD_DRAFT)).toBeNull();
|
||||
expect(wizardStep1ContinueHint({ ...EMPTY_WIZARD_DRAFT, type: "PM", woNumber: "!!!" })).toBe(
|
||||
"WO number must be alphanumeric",
|
||||
);
|
||||
expect(wizardStep1ContinueHint({ ...EMPTY_WIZARD_DRAFT, type: "PM" })).toBe(
|
||||
"Enter a WO number to continue",
|
||||
expect(wizardStep1ContinueHint({ ...EMPTY_WIZARD_DRAFT, type: "Emergency" })).toBe(
|
||||
"Select severity to continue",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue