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 { import {
Alert, Alert,
Box, Box,
Button,
CircularProgress, CircularProgress,
Dialog, Dialog,
DialogActions, DialogActions,
DialogContent, DialogContent,
DialogTitle, DialogTitle,
Stack, Stack,
TextField,
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import type { VendorEditorState } from "./work-order-vendor-editor-types"; 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 = { type WorkOrderVendorEditorDialogProps = {
editor: VendorEditorState | null; editor: VendorEditorState | null;
@ -51,75 +50,23 @@ export function WorkOrderVendorEditorDialog({
<CircularProgress size={28} /> <CircularProgress size={28} />
</Box> </Box>
) : ( ) : (
<> <WorkOrderVendorEditorFields
<TextField editor={editor}
label="Technician name" disabled={viewOnly}
value={editor?.contactName ?? ""} onEditorChange={onEditorChange}
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 } }}
/>
</>
)} )}
</Stack> </Stack>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
{viewOnly ? ( <WorkOrderVendorEditorActions
<Button onClick={onClose}>Close</Button> viewOnly={viewOnly}
) : ( editor={editor}
<> loading={loading}
<Button onClick={onClose} disabled={isSaving}> isSaving={isSaving}
Cancel onClose={onClose}
</Button> onSave={onSave}
<Button />
variant="contained"
onClick={onSave}
disabled={!editor || loading || isSaving || !isVendorEditorValid(editor)}
>
{isSaving ? "Saving..." : "Save"}
</Button>
</>
)}
</DialogActions> </DialogActions>
</Dialog> </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) { function WizardApptTimeFields({ draft, onUpdate, onPatch }: WizardApptTimeFieldsProps) {
const duration = calcDuration(draft.apptStart, draft.apptEnd); const duration = calcDuration(draft.apptStart, draft.apptEnd);