2026-10-21 10:37:59 +00:00
|
|
|
import { Button } from "@repo/shadcn-ui/components/button";
|
|
|
|
|
import { Input } from "@repo/shadcn-ui/components/input";
|
2026-10-24 23:19:17 +00:00
|
|
|
import type { ComponentProps, ReactNode } from "react";
|
|
|
|
|
import { useId, useRef, useState } from "react";
|
2026-10-21 10:37:59 +00:00
|
|
|
|
|
|
|
|
import { FormActions, FormBody, FormDialog } from "./form-dialog";
|
|
|
|
|
import type { RemotePickerOption } from "./remote-option-list";
|
|
|
|
|
import { RemoteOptionList } from "./remote-option-list";
|
|
|
|
|
|
|
|
|
|
export type { RemotePickerOption } from "./remote-option-list";
|
|
|
|
|
|
|
|
|
|
/** Presenta exclusivamente la página recibida; el consumidor controla consulta, filtros y paginación remota. */
|
|
|
|
|
export function RemoteOptionPicker({
|
|
|
|
|
title,
|
|
|
|
|
items,
|
2026-10-24 23:19:17 +00:00
|
|
|
initialSelection,
|
2026-10-21 10:37:59 +00:00
|
|
|
pending,
|
|
|
|
|
error,
|
|
|
|
|
search,
|
|
|
|
|
onSearch,
|
|
|
|
|
onRetry,
|
|
|
|
|
onSelect,
|
|
|
|
|
onClose,
|
|
|
|
|
pagination,
|
|
|
|
|
labels,
|
2026-10-24 23:19:17 +00:00
|
|
|
finalFocus,
|
2026-10-21 10:37:59 +00:00
|
|
|
}: {
|
2026-10-24 23:19:17 +00:00
|
|
|
initialSelection?: RemotePickerOption | undefined;
|
2026-10-21 10:37:59 +00:00
|
|
|
title: string;
|
|
|
|
|
items: readonly RemotePickerOption[];
|
|
|
|
|
pending: boolean;
|
|
|
|
|
error: string | undefined;
|
|
|
|
|
search: string;
|
|
|
|
|
onSearch: (search: string) => void;
|
|
|
|
|
onRetry: () => Promise<unknown>;
|
|
|
|
|
onSelect: (option: RemotePickerOption) => void;
|
|
|
|
|
onClose: () => void;
|
|
|
|
|
pagination: ReactNode;
|
|
|
|
|
labels: {
|
|
|
|
|
search: string;
|
|
|
|
|
selection: string;
|
|
|
|
|
name: string;
|
|
|
|
|
description: string;
|
|
|
|
|
empty: string;
|
|
|
|
|
loading: string;
|
|
|
|
|
confirm: string;
|
|
|
|
|
cancel: string;
|
|
|
|
|
retry: string;
|
|
|
|
|
};
|
2026-10-24 23:19:17 +00:00
|
|
|
finalFocus?: ComponentProps<typeof FormDialog>["finalFocus"];
|
2026-10-21 10:37:59 +00:00
|
|
|
}) {
|
|
|
|
|
const id = useId();
|
2026-10-24 23:19:17 +00:00
|
|
|
const searchRef = useRef<HTMLInputElement>(null);
|
|
|
|
|
const [selected, setSelected] = useState<RemotePickerOption | undefined>(initialSelection);
|
|
|
|
|
const current = items.find((item) => item.id === selected?.id) ?? selected;
|
|
|
|
|
const canConfirm = !!current && !current.disabled && !pending && !error;
|
2026-10-21 10:37:59 +00:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<FormDialog
|
|
|
|
|
cancelLabel={labels.cancel}
|
2026-10-24 23:19:17 +00:00
|
|
|
finalFocus={finalFocus}
|
2026-10-21 10:37:59 +00:00
|
|
|
formActions
|
|
|
|
|
layout="picker"
|
|
|
|
|
onOpenChange={(open) => {
|
|
|
|
|
if (!open) {
|
|
|
|
|
onClose();
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
open
|
|
|
|
|
size="medium"
|
|
|
|
|
title={title}
|
|
|
|
|
>
|
|
|
|
|
<FormBody contentLayout="fill">
|
|
|
|
|
<label className="sr-only" htmlFor={id}>
|
|
|
|
|
{labels.search}
|
|
|
|
|
</label>
|
|
|
|
|
<Input
|
|
|
|
|
data-quick-search=""
|
|
|
|
|
id={id}
|
|
|
|
|
maxLength={100}
|
|
|
|
|
onChange={(event) => onSearch(event.target.value)}
|
|
|
|
|
placeholder={labels.search}
|
2026-10-24 23:19:17 +00:00
|
|
|
ref={searchRef}
|
2026-10-21 10:37:59 +00:00
|
|
|
value={search}
|
|
|
|
|
/>
|
|
|
|
|
{pending && <p role="status">{labels.loading}</p>}
|
|
|
|
|
{error && (
|
|
|
|
|
<div role="alert">
|
|
|
|
|
<p>{error}</p>
|
|
|
|
|
<Button
|
|
|
|
|
onClick={async () => {
|
2026-10-24 23:19:17 +00:00
|
|
|
searchRef.current?.focus({ preventScroll: true });
|
2026-10-21 10:37:59 +00:00
|
|
|
await onRetry();
|
|
|
|
|
}}
|
|
|
|
|
variant="outline"
|
|
|
|
|
>
|
|
|
|
|
{labels.retry}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<RemoteOptionList
|
|
|
|
|
error={error}
|
|
|
|
|
items={items}
|
|
|
|
|
labels={labels}
|
|
|
|
|
onSelect={setSelected}
|
|
|
|
|
pending={pending}
|
|
|
|
|
title={title}
|
|
|
|
|
value={selected?.id ?? ""}
|
|
|
|
|
/>
|
|
|
|
|
{pagination}
|
|
|
|
|
</FormBody>
|
|
|
|
|
<FormActions>
|
|
|
|
|
<Button
|
2026-10-24 23:19:17 +00:00
|
|
|
disabled={!canConfirm}
|
2026-10-21 10:37:59 +00:00
|
|
|
onClick={() => {
|
2026-10-24 23:19:17 +00:00
|
|
|
if (canConfirm && current) {
|
|
|
|
|
onSelect(current);
|
2026-10-21 10:37:59 +00:00
|
|
|
onClose();
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{labels.confirm}
|
|
|
|
|
</Button>
|
|
|
|
|
</FormActions>
|
|
|
|
|
</FormDialog>
|
|
|
|
|
);
|
|
|
|
|
}
|