fix(work-orders): extract completed vendor dialog to pass maintainability

This commit is contained in:
Arthur Bassi 2026-08-24 12:14:21 -03:00
parent eeb415951c
commit 17ad0e5098
4 changed files with 127 additions and 69 deletions

View file

@ -0,0 +1,40 @@
import { Button } from "@mui/material";
import type { VendorEditorState } from "./work-order-vendor-editor-types";
import { isVendorEditorValid } from "./work-order-vendor-editor-utils";
type WorkOrderVendorEditorActionsProps = {
viewOnly: boolean;
editor: VendorEditorState | null;
loading: boolean;
isSaving: boolean;
onClose: () => void;
onSave: () => void;
};
export function WorkOrderVendorEditorActions({
viewOnly,
editor,
loading,
isSaving,
onClose,
onSave,
}: WorkOrderVendorEditorActionsProps) {
if (viewOnly) {
return <Button onClick={onClose}>Close</Button>;
}
return (
<>
<Button onClick={onClose} disabled={isSaving}>
Cancel
</Button>
<Button
variant="contained"
onClick={onSave}
disabled={!editor || loading || isSaving || !isVendorEditorValid(editor)}
>
{isSaving ? "Saving..." : "Save"}
</Button>
</>
);
}

View file

@ -1,18 +1,17 @@
import {
Alert,
Box,
Button,
CircularProgress,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Stack,
TextField,
Typography,
} from "@mui/material";
import type { VendorEditorState } from "./work-order-vendor-editor-types";
import { formatPhoneMask, isVendorEditorValid } from "./work-order-vendor-editor-utils";
import { WorkOrderVendorEditorActions } from "./work-order-vendor-editor-actions";
import { WorkOrderVendorEditorFields } from "./work-order-vendor-editor-fields";
type WorkOrderVendorEditorDialogProps = {
editor: VendorEditorState | null;
@ -51,75 +50,23 @@ export function WorkOrderVendorEditorDialog({
<CircularProgress size={28} />
</Box>
) : (
<>
<TextField
label="Technician name"
value={editor?.contactName ?? ""}
disabled={viewOnly}
onChange={(event) =>
onEditorChange((current) =>
current ? { ...current, contactName: event.target.value } : current,
)
}
slotProps={{ htmlInput: { maxLength: 200 } }}
/>
<TextField
label="Phone"
value={editor?.phone ?? ""}
disabled={viewOnly}
onChange={(event) =>
onEditorChange((current) =>
current ? { ...current, phone: formatPhoneMask(event.target.value) } : current,
)
}
error={Boolean(editor?.phone && !/^\(\d{3}\) \d{3}-\d{4}$/.test(editor.phone))}
helperText="Use (XXX) XXX-XXXX"
/>
<TextField
label="Email"
type="email"
value={editor?.email ?? ""}
disabled={viewOnly}
onChange={(event) =>
onEditorChange((current) =>
current ? { ...current, email: event.target.value } : current,
)
}
/>
<TextField
label="Notes"
multiline
minRows={3}
value={editor?.notes ?? ""}
disabled={viewOnly}
onChange={(event) =>
onEditorChange((current) =>
current ? { ...current, notes: event.target.value } : current,
)
}
slotProps={{ htmlInput: { maxLength: 2000 } }}
/>
</>
<WorkOrderVendorEditorFields
editor={editor}
disabled={viewOnly}
onEditorChange={onEditorChange}
/>
)}
</Stack>
</DialogContent>
<DialogActions>
{viewOnly ? (
<Button onClick={onClose}>Close</Button>
) : (
<>
<Button onClick={onClose} disabled={isSaving}>
Cancel
</Button>
<Button
variant="contained"
onClick={onSave}
disabled={!editor || loading || isSaving || !isVendorEditorValid(editor)}
>
{isSaving ? "Saving..." : "Save"}
</Button>
</>
)}
<WorkOrderVendorEditorActions
viewOnly={viewOnly}
editor={editor}
loading={loading}
isSaving={isSaving}
onClose={onClose}
onSave={onSave}
/>
</DialogActions>
</Dialog>
);

View file

@ -0,0 +1,71 @@
import { TextField } from "@mui/material";
import type { VendorEditorState } from "./work-order-vendor-editor-types";
import { formatPhoneMask } from "./work-order-vendor-editor-utils";
type VendorEditorChange = (
updater: (current: VendorEditorState | null) => VendorEditorState | null,
) => void;
type WorkOrderVendorEditorFieldsProps = {
editor: VendorEditorState | null;
disabled: boolean;
onEditorChange: VendorEditorChange;
};
export function WorkOrderVendorEditorFields({
editor,
disabled,
onEditorChange,
}: WorkOrderVendorEditorFieldsProps) {
return (
<>
<TextField
label="Technician name"
value={editor?.contactName ?? ""}
disabled={disabled}
onChange={(event) =>
onEditorChange((current) =>
current ? { ...current, contactName: event.target.value } : current,
)
}
slotProps={{ htmlInput: { maxLength: 200 } }}
/>
<TextField
label="Phone"
value={editor?.phone ?? ""}
disabled={disabled}
onChange={(event) =>
onEditorChange((current) =>
current ? { ...current, phone: formatPhoneMask(event.target.value) } : current,
)
}
error={Boolean(editor?.phone && !/^\(\d{3}\) \d{3}-\d{4}$/.test(editor.phone))}
helperText="Use (XXX) XXX-XXXX"
/>
<TextField
label="Email"
type="email"
value={editor?.email ?? ""}
disabled={disabled}
onChange={(event) =>
onEditorChange((current) =>
current ? { ...current, email: event.target.value } : current,
)
}
/>
<TextField
label="Notes"
multiline
minRows={3}
value={editor?.notes ?? ""}
disabled={disabled}
onChange={(event) =>
onEditorChange((current) =>
current ? { ...current, notes: event.target.value } : current,
)
}
slotProps={{ htmlInput: { maxLength: 2000 } }}
/>
</>
);
}

View file

@ -117,7 +117,7 @@ function WizardVendorDetail({
);
}
type WizardApptTimeFieldsProps = Omit<WizardStepVendorTimeProps, "vendors">;
type WizardApptTimeFieldsProps = Pick<WizardStepVendorTimeProps, "draft" | "onUpdate" | "onPatch">;
function WizardApptTimeFields({ draft, onUpdate, onPatch }: WizardApptTimeFieldsProps) {
const duration = calcDuration(draft.apptStart, draft.apptEnd);