mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-06 04:12:09 +00:00
SH-331 requires Street Address suggestions after 3 typed characters, with a pick filling Street, City and State and unpicked typing kept as free text. SH-272, which SH-331 consolidates, specifies those suggestions as mocked, and the shared AddressAutocompleteField that Vendors already uses (SH-271) was built for Sites to reuse. No Places key is involved. The Add Site modal and the detail panel's edit mode now render that field. A pick writes all three values through their own controllers, so the form marks them dirty and Save changes enables in edit mode. The field gains an opt-in requiredMarker prop so Sites keeps its label asterisk while Vendors stays without one.
111 lines
4.1 KiB
TypeScript
111 lines
4.1 KiB
TypeScript
import { Controller, useController, useWatch, type UseFormReturn } from "react-hook-form";
|
|
import { Stack, TextField } from "@mui/material";
|
|
import { AddressAutocompleteField } from "@/components/common/address-autocomplete-field";
|
|
import { normalizeStateCode } from "@/data/areas";
|
|
import type { AccountListItem } from "@/domain/accounts/types/account";
|
|
import type { LocationFormValues } from "@/domain/locations/schemas/location-schema";
|
|
import { ClientSelect } from "./client-select";
|
|
import { LocationContactsFieldArray } from "./location-contacts-field-array";
|
|
import { SiteMapPreview } from "./site-map-preview";
|
|
import { StateSelect } from "./state-select";
|
|
|
|
interface SiteFormFieldsProps {
|
|
form: UseFormReturn<LocationFormValues>;
|
|
/** Edit mode: Site Code is immutable after creation. */
|
|
siteCodeReadOnly?: boolean;
|
|
initialClient?: Pick<AccountListItem, "id" | "name">;
|
|
}
|
|
|
|
/** Shared Add Site / edit-mode fields, in the prototype order. */
|
|
export function SiteFormFields({
|
|
form,
|
|
siteCodeReadOnly = false,
|
|
initialClient,
|
|
}: SiteFormFieldsProps) {
|
|
const {
|
|
control,
|
|
register,
|
|
formState: { errors },
|
|
} = form;
|
|
const [address, city, state] = useWatch({ control, name: ["address", "city", "state"] });
|
|
const { field: addressField } = useController({ control, name: "address" });
|
|
const { field: cityField } = useController({ control, name: "city" });
|
|
const { field: stateField } = useController({ control, name: "state" });
|
|
|
|
return (
|
|
<Stack spacing={2}>
|
|
<Controller
|
|
name="name"
|
|
control={control}
|
|
render={({ field }) => (
|
|
<TextField
|
|
{...field}
|
|
onChange={(event) => field.onChange(event.target.value.toUpperCase())}
|
|
label="Site Code"
|
|
placeholder="e.g. AB12"
|
|
required={!siteCodeReadOnly}
|
|
disabled={siteCodeReadOnly}
|
|
error={Boolean(errors.name)}
|
|
helperText={
|
|
errors.name?.message ?? (siteCodeReadOnly ? "Site Code can't be changed" : undefined)
|
|
}
|
|
slotProps={{ htmlInput: { maxLength: 50, readOnly: siteCodeReadOnly } }}
|
|
fullWidth
|
|
/>
|
|
)}
|
|
/>
|
|
<ClientSelect control={control} error={errors.accountId} initialClient={initialClient} />
|
|
<AddressAutocompleteField
|
|
value={addressField.value}
|
|
onInputChange={addressField.onChange}
|
|
onSelect={(parts) => {
|
|
// One pick fills all three, each through its own controller so the
|
|
// form marks them dirty and revalidates (edit mode's Save depends on it).
|
|
addressField.onChange(parts.street);
|
|
cityField.onChange(parts.city);
|
|
stateField.onChange(normalizeStateCode(parts.state) ?? "");
|
|
}}
|
|
label="Street Address"
|
|
required
|
|
requiredMarker
|
|
error={Boolean(errors.address)}
|
|
helperText={
|
|
errors.address?.message ?? "Pick a suggestion to auto-fill City/State, or type your own."
|
|
}
|
|
/>
|
|
<Stack direction={{ xs: "column", sm: "row" }} spacing={2}>
|
|
<TextField
|
|
{...cityField}
|
|
label="City"
|
|
required
|
|
error={Boolean(errors.city)}
|
|
helperText={errors.city?.message}
|
|
fullWidth
|
|
/>
|
|
<StateSelect control={control} error={errors.state} />
|
|
</Stack>
|
|
<SiteMapPreview address={address} city={city} state={state} />
|
|
<Stack direction={{ xs: "column", sm: "row" }} spacing={2}>
|
|
<TextField
|
|
{...register("sitePhone")}
|
|
label="Site Phone (optional)"
|
|
placeholder="(000) 000-0000"
|
|
error={Boolean(errors.sitePhone)}
|
|
helperText={errors.sitePhone?.message}
|
|
fullWidth
|
|
slotProps={{ htmlInput: { maxLength: 20 } }}
|
|
/>
|
|
<TextField
|
|
{...register("contactEmail")}
|
|
label="Site Email (optional)"
|
|
placeholder="site@company.com"
|
|
type="email"
|
|
error={Boolean(errors.contactEmail)}
|
|
helperText={errors.contactEmail?.message}
|
|
fullWidth
|
|
/>
|
|
</Stack>
|
|
<LocationContactsFieldArray control={control} errors={errors} register={register} />
|
|
</Stack>
|
|
);
|
|
}
|