This commit is contained in:
2026-04-07 06:32:40 +03:00
parent 24a44481a0
commit 11db1e6941
101 changed files with 54585 additions and 39160 deletions
@@ -54,6 +54,12 @@ export function AsyncSelectField<TOption = AsyncOption>({
value={value}
onValueChange={(val) => onChange(val)}
disabled={disabled}
isItemEqualToValue={(item, val) => {
if (item !== null && typeof item === "object" && "value" in (item as object)) {
return (item as any).value === (val as any)?.value
}
return item === val
}}
onInputValueChange={(val, { reason }) => {
if (reason === "input-change") {
onInputValueChange?.(val)
@@ -0,0 +1,100 @@
"use client"
import { useState } from "react"
import { useFormContext, type FieldValues, type FieldPath } from "react-hook-form"
import { useQuery } from "@tanstack/react-query"
import { RefreshCw } from "lucide-react"
import { useAuthApi } from "@/shared/useApi"
import { AUTO_GENERATE_ROUTES } from "@garage/api"
import { FieldShell } from "../field-shell"
import { Input } from "@/shared/components/ui/input"
import { Button } from "@/shared/components/ui/button"
type RhfAutoGenerateFieldProps<
TValues extends FieldValues,
TName extends FieldPath<TValues>,
> = {
name: TName
label?: string
description?: string
required?: boolean
disabled?: boolean
placeholder?: string
table: string
/** When true, fetches the next code immediately on mount */
autoFetch?: boolean
}
export function RhfAutoGenerateField<
TValues extends FieldValues,
TName extends FieldPath<TValues>,
>({
name,
label,
description,
required,
disabled,
placeholder,
table,
autoFetch = false,
}: RhfAutoGenerateFieldProps<TValues, TName>) {
const api = useAuthApi()
const { setValue, watch, formState: { errors } } = useFormContext<TValues>()
const value = watch(name)
const error = errors[name]
const [enabled, setEnabled] = useState(autoFetch)
const { isFetching } = useQuery({
queryKey: [AUTO_GENERATE_ROUTES.BY_TABLE, table],
queryFn: async () => {
const res = await api.autoGenerate.generate(table)
const generated = (res as any)?.data
if (generated) {
setValue(name, generated as any, { shouldValidate: true })
}
return res
},
enabled,
refetchOnWindowFocus: false,
staleTime: 0,
gcTime: 0,
})
const handleGenerate = () => {
setEnabled(false)
// Reset and re-enable to trigger a fresh fetch
setTimeout(() => setEnabled(true), 0)
}
return (
<FieldShell
label={label}
error={error?.message as string | undefined}
description={description}
required={required}
>
<div className="flex gap-2">
<Input
value={value ?? ""}
onChange={(e) => setValue(name, e.target.value as any, { shouldValidate: true })}
name={name}
disabled={disabled}
aria-invalid={!!error || undefined}
placeholder={placeholder}
/>
<Button
type="button"
variant="outline"
size="icon"
disabled={disabled || isFetching}
onClick={handleGenerate}
title="Auto-generate"
>
<RefreshCw className={isFetching ? "animate-spin" : ""} />
</Button>
</div>
</FieldShell>
)
}
@@ -39,3 +39,4 @@ export { RhfAsyncSelectField, RhfAsyncMultiSelectField, type InlineCreateFormPro
export { SimpleTitleForm, type SimpleTitleFormProps } from "./fields/simple-title-form"
export { RhfDateField } from "./fields/rhf-date-field"
export { RhfTimeField } from "./fields/rhf-time-field"
export { RhfAutoGenerateField } from "./fields/rhf-auto-generate-field"