101 lines
3.0 KiB
TypeScript
101 lines
3.0 KiB
TypeScript
"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>
|
|
)
|
|
}
|