import type { FieldApi, FormApi } from "@tanstack/react-form"; import { type Entity, type EntityData, EnumField, type Field, JsonField, JsonSchemaField, RelationField } from "data"; import { MediaField } from "media/MediaField"; import { type ComponentProps, Suspense } from "react"; import { useClient } from "ui/client"; import { JsonEditor } from "ui/components/code/JsonEditor"; import * as Formy from "ui/components/form/Formy"; import { FieldLabel } from "ui/components/form/Formy"; import { useEvent } from "ui/hooks/use-event"; import { Dropzone, type FileState } from "../../media/components/dropzone/Dropzone"; import { mediaItemsToFileStates } from "../../media/helper"; import { EntityJsonSchemaFormField } from "./fields/EntityJsonSchemaFormField"; import { EntityRelationalFormField } from "./fields/EntityRelationalFormField"; type EntityFormProps = { entity: Entity; entityId?: number; data?: EntityData; handleSubmit: (e: React.FormEvent) => void; fieldsDisabled: boolean; Form: FormApi; className?: string; action: "create" | "update"; }; export function EntityForm({ entity, entityId, handleSubmit, fieldsDisabled, Form, data, className, action }: EntityFormProps) { const fields = entity.getFillableFields(action, true); console.log("data", { data, fields }); return (
{ //console.log("state", state); return [state.canSubmit, state.isValid, state.errors]; }} children={([canSubmit, isValid, errors]) => { //console.log("form:state", { canSubmit, isValid, errors }); return ( !isValid && (

Form is invalid.

{Array.isArray(errors) && (
    {errors.map((error, key) => (
  • {error}
  • ))}
)}
) ); }} />
{fields.map((field, key) => { // @todo: tanstack form re-uses the state, causes issues navigating between entities w/ same fields // media field needs to render outside of the form // as its value is not stored in the form state if (field instanceof MediaField) { return ( ); } if (!field.isFillable(action)) { return; } const _key = `${entity.name}-${field.name}-${key}`; return ( ( )} /> ); })}
); } type EntityFormFieldProps< T extends keyof JSX.IntrinsicElements = "input", F extends Field = Field > = ComponentProps & { fieldApi: FieldApi; field: F; action: "create" | "update"; data?: EntityData; }; type FormInputElement = HTMLInputElement | HTMLTextAreaElement; function EntityFormField({ fieldApi, field, action, data, ...props }: EntityFormFieldProps) { const handleUpdate = useEvent((e: React.ChangeEvent | any) => { if (typeof e === "object" && "target" in e) { console.log("handleUpdate", e.target.value); fieldApi.handleChange(e.target.value); } else { console.log("handleUpdate-", e); fieldApi.handleChange(e); } }); //const required = field.isRequired(); //const customFieldProps = { ...props, action, required }; if (field instanceof RelationField) { return ( ); } if (field instanceof JsonField) { return ; } if (field instanceof JsonSchemaField) { return ( ); } if (field instanceof EnumField) { return ; } const fieldElement = field.getHtmlConfig().element; const fieldProps = field.getHtmlConfig().props as any; const Element = Formy.formElementFactory(fieldElement ?? "input", fieldProps); return ( ); } function EntityMediaFormField({ formApi, field, entity, entityId, disabled }: { formApi: FormApi; field: MediaField; entity: Entity; entityId?: number; disabled?: boolean; }) { if (!entityId) return; const client = useClient(); const value = formApi.useStore((state) => { const val = state.values[field.name]; if (!val || typeof val === "undefined") return []; if (Array.isArray(val)) return val; return [val]; }); const initialItems: FileState[] = value.length === 0 ? [] : mediaItemsToFileStates(value, { baseUrl: client.baseUrl, overrides: { state: "uploaded" } }); const getUploadInfo = useEvent(() => { const api = client.media().api(); return { url: api.getEntityUploadUrl(entity.name, entityId, field.name), headers: api.getUploadHeaders(), method: "POST" }; }); const handleDelete = useEvent(async (file) => { client.__invalidate(entity.name, entityId); return await client.media().deleteFile(file); }); return ( ); } function EntityJsonFormField({ fieldApi, field, ...props }: { fieldApi: FieldApi; field: JsonField }) { const handleUpdate = useEvent((value: any) => { fieldApi.handleChange(value); }); return ( {field.getLabel()} {/**/} ); } function EntityEnumFormField({ fieldApi, field, ...props }: { fieldApi: FieldApi; field: EnumField }) { const handleUpdate = useEvent((e: React.ChangeEvent) => { fieldApi.handleChange(e.target.value); }); return ( {field.getLabel()} {!field.isRequired() && } {field.getOptions().map((option) => ( ))} ); }