import { typeboxResolver } from "@hookform/resolvers/typebox"; import { Tabs, TextInput, Textarea, Tooltip } from "@mantine/core"; import { useDisclosure } from "@mantine/hooks"; import { Type } from "@sinclair/typebox"; import { type Static, StringIdentifier, objectCleanEmpty, ucFirstAllSnakeToPascalWithSpaces } from "core/utils"; import { Entity } from "data"; import { type TAppDataEntityFields, fieldsSchemaObject as originalFieldsSchemaObject } from "data/data-schema"; import { omit } from "lodash-es"; import { forwardRef, memo, useEffect, useImperativeHandle } from "react"; import { type FieldArrayWithId, type UseFormReturn, useFieldArray, useForm } from "react-hook-form"; import { TbGripVertical, TbSettings, TbTrash } from "react-icons/tb"; import { twMerge } from "tailwind-merge"; import { Button } from "ui"; import { useBknd } from "ui/client"; import { useBkndData } from "ui/client/schema/data/use-bknd-data"; import { IconButton } from "ui/components/buttons/IconButton"; import { JsonViewer } from "ui/components/code/JsonViewer"; import { MantineSwitch } from "ui/components/form/hook-form-mantine/MantineSwitch"; import { JsonSchemaForm } from "ui/components/form/json-schema/JsonSchemaForm"; import { type SortableItemProps, SortableList } from "ui/components/list/SortableList"; import { Popover } from "ui/components/overlay/Popover"; import { fieldSpecs } from "ui/modules/data/components/fields-specs"; import { dataFieldsUiSchema } from "../../settings/routes/data.settings"; const fieldsSchemaObject = originalFieldsSchemaObject; const fieldsSchema = Type.Union(Object.values(fieldsSchemaObject)); const fieldSchema = Type.Object({ name: StringIdentifier, new: Type.Optional(Type.Boolean({ const: true })), field: fieldsSchema }); type TFieldSchema = Static; const schema = Type.Object({ fields: Type.Array(fieldSchema) }); type TFieldsFormSchema = Static; const fieldTypes = Object.keys(fieldsSchemaObject); const defaultType = fieldTypes[0]; const blank_field = { name: "", field: { type: defaultType, config: {} } } as TFieldSchema; const commonProps = ["label", "description", "required", "fillable", "hidden", "virtual"]; function specificFieldSchema(type: keyof typeof fieldsSchemaObject) { //console.log("specificFieldSchema", type); return Type.Omit(fieldsSchemaObject[type]?.properties.config, commonProps); } export type EntityFieldsFormRef = { getValues: () => TFieldsFormSchema; getData: () => TAppDataEntityFields; isValid: () => boolean; reset: () => void; }; export const EntityFieldsForm = forwardRef< EntityFieldsFormRef, { fields: TAppDataEntityFields; onChange?: (formData: TAppDataEntityFields) => void; sortable?: boolean; } >(function EntityFieldsForm({ fields: _fields, sortable, ...props }, ref) { const entityFields = Object.entries(_fields).map(([name, field]) => ({ name, field })); /*const entityFields = entity.fields.map((field) => ({ name: field.name, field: field.toJSON() }));*/ const { control, formState: { isValid, errors }, getValues, handleSubmit, watch, register, setValue, setError, reset, clearErrors } = useForm({ mode: "all", resolver: typeboxResolver(schema), defaultValues: { fields: entityFields } as TFieldsFormSchema }); const { fields, append, remove, move } = useFieldArray({ control, name: "fields" }); function toCleanValues(formData: TFieldsFormSchema): TAppDataEntityFields { return Object.fromEntries( formData.fields.map((field) => [field.name, objectCleanEmpty(field.field)]) ); } useEffect(() => { if (props?.onChange) { console.log("----set"); watch((data: any) => { console.log("---calling"); props?.onChange?.(toCleanValues(data)); }); } //props?.onChange?.() }, []); useImperativeHandle(ref, () => ({ reset, getValues: () => getValues(), getData: () => { return toCleanValues(getValues()); /*return Object.fromEntries( getValues().fields.map((field) => [field.name, objectCleanEmpty(field.field)]) );*/ }, isValid: () => isValid })); console.log("errors", errors.fields); /*useEffect(() => { console.log("change", values); onSubmit(values); }, [values]);*/ function onSubmit(data: TFieldsFormSchema) { console.log("submit", isValid, data, errors); } function onSubmitInvalid(a, b) { console.log("submit invalid", a, b); } function handleAppend(_type: keyof typeof fieldsSchemaObject) { const newField = { name: "", new: true, field: { type: _type, config: {} } }; console.log("handleAppend", _type, newField); append(newField); } const formProps = { watch, register, setValue, getValues, control, setError }; return ( <>
{sortable ? ( item.id} disableIndices={[0]} renderItem={({ dnd, ...props }, index) => ( )} /> ) : (
{fields.map((field, index) => ( ))}
)} ( { handleAppend(type as any); toggle(); }} /> )} >
))} ); }; const Debug = ({ watch, errors }) => { return (
{Object.entries(errors).map(([key, value]) => (

{/* @ts-ignore */} {key}: {value.message}

))}
{JSON.stringify(watch(), null, 2)}
); }; const EntityFieldMemo = memo(EntityField, (prev, next) => { return prev.field.id !== next.field.id; }); function EntityField({ field, index, form: { watch, register, setValue, getValues, control, setError }, remove, errors, dnd }: { field: FieldArrayWithId; index: number; form: Pick< UseFormReturn, "watch" | "register" | "setValue" | "getValues" | "control" | "setError" >; remove: (index: number) => void; errors: any; dnd?: SortableItemProps; }) { const [opened, handlers] = useDisclosure(false); const prefix = `fields.${index}.field` as const; const type = field.field.type; const name = watch(`fields.${index}.name`); const fieldSpec = fieldSpecs.find((s) => s.type === type)!; const specificData = omit(field.field.config, commonProps); const disabled = fieldSpec.disabled || []; const hidden = fieldSpec.hidden || []; const dragDisabled = index === 0; const hasErrors = !!errors?.fields?.[index]; function handleDelete(index: number) { return () => { if (name.length === 0) { remove(index); return; } window.confirm(`Sure to delete "${name}"?`) && remove(index); }; } //console.log("register", register(`${prefix}.config.required`)); const dndProps = dnd ? { ...dnd.provided.draggableProps, ref: dnd.provided.innerRef } : {}; return (
{dnd ? (
) : null}
{field.new ? ( ) : (
{name} {field.field.config?.label && ( {field.field.config?.label} )}
)}
Required
{opened && (
{/*
{JSON.stringify(field, null, 2)}
*/} General {ucFirstAllSnakeToPascalWithSpaces(type)} Visiblity
Code