diff --git a/src/app/(protected)/workorders/_components/wizard/new-wo-wizard.tsx b/src/app/(protected)/workorders/_components/wizard/new-wo-wizard.tsx index 97e1ec64..f636c2c7 100644 --- a/src/app/(protected)/workorders/_components/wizard/new-wo-wizard.tsx +++ b/src/app/(protected)/workorders/_components/wizard/new-wo-wizard.tsx @@ -1,6 +1,7 @@ import { useEffect, useMemo, useState } from "react"; import { Dialog } from "@mui/material"; import { dialogBlurBackdropSlotProps } from "@/components/ui/dialog-blur-backdrop"; +import { Text } from "@/components/ui/text"; import { useDropdownOptionsByCategory } from "@/domain/settings/dropdown-options/use-cases/use-dropdown-options-by-category"; import { mapWizardDraftToFormValues } from "@/domain/work-orders/mappers/wizard-draft-mapper"; import { isWoWizardMockEnabled } from "@/domain/work-orders/mocks/is-wo-wizard-mock-enabled"; @@ -35,6 +36,46 @@ type NewWoWizardProps = { users: WorkOrderUser[]; }; +function WizardStepHeader({ step }: { step: number }) { + return ( +
+
+ Step {step} of {WIZARD_STEPS.length} +
+ + {WIZARD_STEPS[step - 1].title} + + + {WIZARD_STEPS[step - 1].subtitle} + +
+ ); +} + export function NewWoWizard({ open, onOpenChange, users }: NewWoWizardProps) { const mockFallback = isWoWizardMockEnabled(); const [step, setStep] = useState(1); @@ -109,9 +150,7 @@ export function NewWoWizard({ open, onOpenChange, users }: NewWoWizardProps) { } }; - const handleCreate = () => { - createMutation.mutate(mapWizardDraftToFormValues(draft)); - }; + const handleCreate = () => createMutation.mutate(mapWizardDraftToFormValues(draft)); return ( -
-
- Step {step} of {WIZARD_STEPS.length} -
-

- {WIZARD_STEPS[step - 1].title} -

-

- {WIZARD_STEPS[step - 1].subtitle} -

-
+ {step === 1 && ( - {allowWeekMode && ( + {Boolean(allowWeekMode) && (
{(["week", "date"] as const).map((m) => { const activeMode = cellMode === m; diff --git a/src/app/(protected)/workorders/_components/wizard/wizard-sidebar.tsx b/src/app/(protected)/workorders/_components/wizard/wizard-sidebar.tsx index 3db0de5b..62a13a75 100644 --- a/src/app/(protected)/workorders/_components/wizard/wizard-sidebar.tsx +++ b/src/app/(protected)/workorders/_components/wizard/wizard-sidebar.tsx @@ -21,39 +21,6 @@ type WizardSidebarProps = { }; export function WizardSidebar({ step, draft }: WizardSidebarProps) { - const summary: [string, string][] = [ - ["WO #", draft.woNumber ? `#${draft.woNumber}` : "—"], - ["Type", draft.type || "—"], - [ - "Schedule on", - draft.scheduledOn - ? draft.scheduleWeekOnly - ? fmtWeekRange(draft.scheduledOn) - : fmtDate(draft.scheduledOn) - : "—", - ], - ["Due date", fmtDate(draft.dueDate) || "—"], - ["Assigned to", draft.assigneeName || "—"], - ["Site", draft.locationName || "—"], - [ - "POC", - draft.pocName - ? draft.pocPhone - ? `${draft.pocName} · ${draft.pocPhone}` - : draft.pocName - : "—", - ], - ["Service", draft.pm || "—"], - ["Technician", draft.vendorName || "—"], - ["Company", draft.vendorName || "—"], - ["Appt time", draft.apptStart ? formatApptRange(draft.apptStart, draft.apptEnd) : "—"], - ["Status", draft.status || "Incomplete"], - ]; - - const statusLabel = draft.status || "Incomplete"; - const statusValue = statusLabel as WizardWOStatus; - const statusDotColor = WIZARD_STATUS_DOTS[statusValue] ?? WIZARD_STATUS_DOTS.Incomplete; - return (
-
+ +
+ ); +} + +function WizardSidebarSummary({ draft }: { draft: WorkOrderWizardDraft }) { + const summary: [string, string][] = [ + ["WO #", draft.woNumber ? `#${draft.woNumber}` : "—"], + ["Type", draft.type || "—"], + [ + "Schedule on", + draft.scheduledOn + ? draft.scheduleWeekOnly + ? fmtWeekRange(draft.scheduledOn) + : fmtDate(draft.scheduledOn) + : "—", + ], + ["Due date", fmtDate(draft.dueDate) || "—"], + ["Assigned to", draft.assigneeName || "—"], + ["Site", draft.locationName || "—"], + [ + "POC", + draft.pocName + ? draft.pocPhone + ? `${draft.pocName} · ${draft.pocPhone}` + : draft.pocName + : "—", + ], + ["Service", draft.pm || "—"], + ["Technician", draft.vendorName || "—"], + ["Company", draft.vendorName || "—"], + ["Appt time", draft.apptStart ? formatApptRange(draft.apptStart, draft.apptEnd) : "—"], + ["Status", draft.status || "Incomplete"], + ]; + + const statusLabel = draft.status || "Incomplete"; + const statusValue = statusLabel as WizardWOStatus; + const statusDotColor = WIZARD_STATUS_DOTS[statusValue] ?? WIZARD_STATUS_DOTS.Incomplete; + + return ( +
+
+ This work order +
+ {summary.map(([label, val], i) => (
- This work order -
- {summary.map(([label, val], i) => ( -
- - {label} - - {label === "Status" ? ( - - - - {statusLabel} - - - ) : ( + {label} + + {label === "Status" ? ( + + - {val} + {statusLabel} - )} -
- ))} -
+ + ) : ( + + {val} + + )} +
+ ))}
); } diff --git a/src/app/(protected)/workorders/_components/wizard/wizard-step-location-service.tsx b/src/app/(protected)/workorders/_components/wizard/wizard-step-location-service.tsx index 8d602150..28a19d9b 100644 --- a/src/app/(protected)/workorders/_components/wizard/wizard-step-location-service.tsx +++ b/src/app/(protected)/workorders/_components/wizard/wizard-step-location-service.tsx @@ -1,4 +1,5 @@ import { ExternalLink, Info, Mail, MapPin, Phone } from "lucide-react"; +import { Text } from "@/components/ui/text"; import type { LocationOption } from "@/domain/work-orders/types/work-order"; import type { Location } from "@/domain/locations/types/location"; import type { WorkOrderWizardDraft } from "@/domain/work-orders/types/work-order-wizard"; @@ -63,16 +64,18 @@ export function WizardStepLocationService({ }} /> {!draft.locationId && ( -

Site is required to continue. -

+ )} - {locationDetail && draft.locationId && ( + {locationDetail != null && draft.locationId !== "" && ( <>
- {locationDetail.phone && } - {locationDetail.contactEmail && ( - + {Boolean(locationDetail.phone) && ( + )} - {locationDetail.address && ( + {Boolean(locationDetail.contactEmail) && ( + + )} + {Boolean(locationDetail.address) && (
Point of contact -

+ Auto-filled from records — edit if outdated -

+
{!draft.locationId && !draft.pm && ( -

+ Both are optional — leave blank to fill in later from the dispatch board. -

+ )}
); diff --git a/src/app/(protected)/workorders/_components/wizard/wizard-step-type-schedule.tsx b/src/app/(protected)/workorders/_components/wizard/wizard-step-type-schedule.tsx index ac288ec6..e3d4de1e 100644 --- a/src/app/(protected)/workorders/_components/wizard/wizard-step-type-schedule.tsx +++ b/src/app/(protected)/workorders/_components/wizard/wizard-step-type-schedule.tsx @@ -1,4 +1,5 @@ import type { WorkOrderUser } from "@/domain/work-orders/types/work-order"; +import { Text } from "@/components/ui/text"; import type { WorkOrderWizardDraft } from "@/domain/work-orders/types/work-order-wizard"; import { WO_TYPE_META, WO_TYPES, type WOType } from "@/domain/work-orders/types/work-order-wizard"; import { WizardDateField } from "@/app/(protected)/workorders/_components/wizard/wizard-date-field"; @@ -23,6 +24,93 @@ const WO_TYPE_ICONS: Record = { Overdue: Clock, }; +function WoTypePicker({ + selected, + onSelect, +}: { + selected: WOType | ""; + onSelect: (type: WOType) => void; +}) { + return ( +
+ {WO_TYPES.map((t) => { + const isSelected = selected === t; + const meta = WO_TYPE_META[t]; + const Icon = WO_TYPE_ICONS[t]; + return ( + + ); + })} +
+ ); +} + type WizardStepTypeScheduleProps = { draft: WorkOrderWizardDraft; users: WorkOrderUser[]; @@ -40,82 +128,7 @@ export function WizardStepTypeSchedule({
Type of WO -
- {WO_TYPES.map((t) => { - const selected = draft.type === t; - const meta = WO_TYPE_META[t]; - const Icon = WO_TYPE_ICONS[t]; - return ( - - ); - })} -
+ onUpdate("type", t)} />
diff --git a/src/app/(protected)/workorders/_components/wizard/wizard-step-vendor-time.tsx b/src/app/(protected)/workorders/_components/wizard/wizard-step-vendor-time.tsx index f43462c6..d41c939a 100644 --- a/src/app/(protected)/workorders/_components/wizard/wizard-step-vendor-time.tsx +++ b/src/app/(protected)/workorders/_components/wizard/wizard-step-vendor-time.tsx @@ -1,4 +1,5 @@ import { Clock, ExternalLink, Mail, Wrench } from "lucide-react"; +import { Text } from "@/components/ui/text"; import type { VendorDropdownItem } from "@/domain/work-orders/types/work-order"; import { ALL_WIZARD_STATUSES, @@ -28,6 +29,70 @@ type WizardStepVendorTimeProps = { onPatch: (patch: Partial) => void; }; +function WizardVendorDetail({ + vendor, + techPhone, + vendorNotes, + onUpdate, +}: { + vendor: VendorDropdownItem; + techPhone: string; + vendorNotes: string; + onUpdate: (key: K, value: WorkOrderWizardDraft[K]) => void; +}) { + return ( + <> +
+ Technician phone + onUpdate("techPhone", e.target.value)} + placeholder="(000) 000-0000" + className={`${DLG_INPUT_CLS} mt-2`} + style={DLG_INPUT_STYLE} + /> +
+
+
+ Notes +