mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-06 20:32:05 +00:00
- Sites page: Add Site modal with Site Code, Client, address, State/Area, optional Site Phone and Email, points of contact - Duplicate Site Code shows inline under the field and keeps the modal open - Row opens a non-modal detail panel; Street Address links to Google Maps; key-less map preview - Edit switches the panel in place with Site Code read-only; Save needs a valid change, Cancel makes no call - Site Phone is sent independently of the contacts; zip and title are carried through edits - Retire the separate new/edit location pages
64 lines
2.4 KiB
TypeScript
64 lines
2.4 KiB
TypeScript
import { useMemo, useState } from "react";
|
|
import { Controller, type Control, type FieldError } from "react-hook-form";
|
|
import { Autocomplete, TextField } from "@mui/material";
|
|
import type { AccountListItem } from "@/domain/accounts/types/account";
|
|
import { useAccountsList } from "@/domain/accounts/use-cases/use-accounts-list";
|
|
import type { LocationFormValues } from "@/domain/locations/schemas/location-schema";
|
|
import { useDebounce } from "@/hooks/use-debounce";
|
|
|
|
type ClientOption = Pick<AccountListItem, "id" | "name">;
|
|
|
|
interface ClientSelectProps {
|
|
control: Control<LocationFormValues>;
|
|
error?: FieldError;
|
|
/** Stored client, shown even when it is not in the first page of search results. */
|
|
initialClient?: ClientOption;
|
|
}
|
|
|
|
/** Searchable client (account) picker bound to `accountId`. */
|
|
export function ClientSelect({ control, error, initialClient }: ClientSelectProps) {
|
|
const [search, setSearch] = useState("");
|
|
const debouncedSearch = useDebounce(search, 300);
|
|
const { data, isLoading } = useAccountsList({ search: debouncedSearch, page: 1, pageSize: 50 });
|
|
|
|
const options = useMemo<ClientOption[]>(() => {
|
|
const items: ClientOption[] = data?.items ?? [];
|
|
const hasInitial =
|
|
initialClient !== undefined &&
|
|
items.some((item) => String(item.id) === String(initialClient.id));
|
|
return initialClient === undefined || hasInitial ? items : [initialClient, ...items];
|
|
}, [data?.items, initialClient]);
|
|
|
|
return (
|
|
<Controller
|
|
name="accountId"
|
|
control={control}
|
|
render={({ field }) => (
|
|
<Autocomplete<ClientOption>
|
|
options={options}
|
|
loading={isLoading}
|
|
getOptionLabel={(option) => option.name || String(option.id)}
|
|
isOptionEqualToValue={(option, value) => String(option.id) === String(value.id)}
|
|
value={options.find((option) => String(option.id) === field.value) ?? null}
|
|
onChange={(_event, option) => field.onChange(option ? String(option.id) : "")}
|
|
onBlur={field.onBlur}
|
|
onInputChange={(_event, value, reason) => {
|
|
if (reason === "input") {
|
|
setSearch(value);
|
|
}
|
|
}}
|
|
renderInput={(params) => (
|
|
<TextField
|
|
{...params}
|
|
label="Client"
|
|
required
|
|
placeholder="Search clients..."
|
|
error={Boolean(error)}
|
|
helperText={error?.message}
|
|
/>
|
|
)}
|
|
/>
|
|
)}
|
|
/>
|
|
);
|
|
}
|