@@ -59,6 +85,7 @@ export function WizardStepLocationService({
onPatch({
locationId: id,
locationName: loc?.name ?? "",
+ siteCode: loc ? resolveSiteCode(loc) : "",
});
}}
/>
@@ -72,41 +99,63 @@ export function WizardStepLocationService({
)}
- {locationDetail && draft.locationId && (
+ {draft.locationId && (
<>
-
- {locationDetail.phone &&
}
- {locationDetail.contactEmail && (
-
- )}
- {locationDetail.address && (
-
- Open in Google Maps
-
+ {locationDetail ? (
+ <>
+
+ {locationDetail.phone &&
}
+ {locationDetail.contactEmail && (
+
+ )}
+ {addressPreview && (
+
+ Open in Google Maps
+
+ )}
+ >
+ ) : (
+ <>
+
+ {locationDetailLoading && (
+
+
+
+
+ )}
+ {locationDetailError && (
+
+ Could not load full site details. City and site code above are still available.
+
+ )}
+ >
)}
+
Point of contact
@@ -146,22 +195,60 @@ export function WizardStepLocationService({
value={draft.pm}
placeholder="Select service…"
icon={
-
+
}
searchPlaceholder="Search PM type…"
- options={services.map((s) => ({
- key: s.value,
- label: s.value,
- }))}
- onPick={(p) => onUpdate("pm", p)}
+ options={services.map((s) => {
+ const Icon = getPmTypeIcon(s.value);
+ return {
+ key: s.value,
+ label: s.value,
+ left: (
+
+ ),
+ };
+ })}
+ onPick={(p) =>
+ onPatch({
+ pm: p,
+ extraServices: draft.extraServices.filter((x) => x !== p),
+ })
+ }
/>
+ {!svcOpen ? (
+
+ ) : (
+
+ onUpdate("pmNote", v)}
+ search={svcSearch}
+ setSearch={onSvcSearchChange}
+ />
+
+ )}
-
- {!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..94c9dc7d 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,6 +1,7 @@
import type { WorkOrderUser } from "@/domain/work-orders/types/work-order";
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 { getDispatcherAvatarColor } from "@/domain/work-orders/utils/dispatcher-avatar-color";
import { WizardDateField } from "@/app/(protected)/workorders/_components/wizard/wizard-date-field";
import { WizardFieldSelect } from "@/app/(protected)/workorders/_components/wizard/wizard-field-select";
import {
@@ -36,6 +37,8 @@ export function WizardStepTypeSchedule({
onUpdate,
onPatch,
}: WizardStepTypeScheduleProps) {
+ const selectedUser = users.find((u) => String(u.id) === draft.assigneeId);
+
return (
@@ -122,9 +125,10 @@ export function WizardStepTypeSchedule({
WO Number
onUpdate("woNumber", e.target.value)}
- placeholder="e.g. 24818"
+ onChange={(e) => onUpdate("woNumber", e.target.value.replace(/\D/g, "").slice(0, 20))}
+ placeholder="e.g. 12345678901"
maxLength={20}
className={DLG_INPUT_CLS}
style={DLG_INPUT_STYLE}
@@ -137,13 +141,17 @@ export function WizardStepTypeSchedule({
value={draft.assigneeId}
placeholder="Select…"
icon={
-
+ selectedUser ? (
+
+ ) : (
+
+ )
}
searchPlaceholder="Search dispatcher…"
options={users.map((u) => ({
key: String(u.id),
label: u.name,
- left: ,
+ left: ,
}))}
onPick={(id) => {
const user = users.find((u) => String(u.id) === id);
@@ -160,8 +168,15 @@ export function WizardStepTypeSchedule({
onPatch({ scheduledOn: "", scheduleWeekOnly: false })
+ draft.scheduledOn !== "" || draft.scheduleWeekOnly || draft.scheduleEnd
+ ? () =>
+ onPatch({
+ scheduledOn: "",
+ scheduleWeekOnly: false,
+ scheduleEnd: "",
+ schedMode: undefined,
+ targetWeek: "",
+ })
: undefined
}
>
@@ -169,11 +184,38 @@ export function WizardStepTypeSchedule({
onPatch({ scheduledOn: iso, scheduleWeekOnly: false })}
- onChangeWeek={(w) => onPatch({ scheduledOn: w, scheduleWeekOnly: Boolean(w) })}
+ scheduleEnd={draft.scheduleEnd}
+ schedMode={draft.schedMode}
+ onChange={(iso) =>
+ onPatch({
+ scheduledOn: iso,
+ scheduleWeekOnly: false,
+ scheduleEnd: "",
+ schedMode: "date",
+ targetWeek: "",
+ })
+ }
+ onChangeWeek={(w) =>
+ onPatch({
+ scheduledOn: w,
+ scheduleWeekOnly: Boolean(w),
+ scheduleEnd: "",
+ schedMode: "week",
+ targetWeek: w,
+ })
+ }
+ onChangeRange={(s, e) =>
+ onPatch({
+ scheduledOn: s,
+ scheduleEnd: e,
+ schedMode: "range",
+ scheduleWeekOnly: false,
+ targetWeek: "",
+ })
+ }
/>
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..9d990dc1 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,4 @@
-import { Clock, ExternalLink, Mail, Wrench } from "lucide-react";
+import { Clock, ExternalLink, Mail, User, Wrench } from "lucide-react";
import type { VendorDropdownItem } from "@/domain/work-orders/types/work-order";
import {
ALL_WIZARD_STATUSES,
@@ -8,6 +8,10 @@ import {
type WorkOrderWizardDraft,
} from "@/domain/work-orders/types/work-order-wizard";
import { calcDuration, fmt12 } from "@/domain/work-orders/utils/wizard-date-utils";
+import {
+ formatVendorDistance,
+ formatVendorTechnicianLabel,
+} from "@/domain/work-orders/utils/vendor-dropdown-label";
import { WizardFieldSelect } from "@/app/(protected)/workorders/_components/wizard/wizard-field-select";
import {
DLG_INPUT_CLS,
@@ -37,6 +41,7 @@ export function WizardStepVendorTime({
const selectedVendor = vendors.find((v) => String(v.id) === draft.vendorId);
const statusValue = (draft.status || "Incomplete") as WizardWOStatus;
const statusDotColor = WIZARD_STATUS_DOTS[statusValue] ?? WIZARD_STATUS_DOTS.Incomplete;
+ const selectedDistance = formatVendorDistance(selectedVendor?.distanceMiles);
return (
@@ -68,7 +73,9 @@ export function WizardStepVendorTime({
color: "var(--color-text-secondary)",
}}
>
- {selectedVendor.companyName.slice(0, 2).toUpperCase()}
+ {(selectedVendor.contactName || selectedVendor.companyName)
+ .slice(0, 2)
+ .toUpperCase()}
) : (
@@ -77,7 +84,7 @@ export function WizardStepVendorTime({
searchPlaceholder="Search technician…"
options={vendors.map((v) => ({
key: String(v.id),
- label: v.companyName,
+ label: formatVendorTechnicianLabel(v),
}))}
onPick={(id) => {
const vendor = vendors.find((v) => String(v.id) === id);
@@ -111,9 +118,25 @@ export function WizardStepVendorTime({
border: "1px solid var(--color-border)",
}}
>
-