mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 06:59:01 +00:00
65 lines
2.4 KiB
TypeScript
65 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}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
);
|
||
|
|
}
|