MetadataFormRenderer.tsx 2.49 KB
// Metadata-driven form renderer.
//
// Given a form definition slug, fetches the JSON Schema + UI Schema
// from the metadata API and renders a fully functional @rjsf form
// using the VibeErp theme and custom ERP widgets.

import { useEffect, useState } from 'react'
import Form from '@rjsf/core'
import type { IChangeEvent } from '@rjsf/core'
import type { RJSFSchema, UiSchema } from '@rjsf/utils'
import validator from '@rjsf/validator-ajv8'
import { apiFetch } from '@/api/client'
import type { FormDefinition } from '@/types/api'
import { vibeWidgets } from '@/components/form-widgets'
import { vibeTemplates } from '@/components/form-widgets/vibeErpTheme'
import { Loading } from '@/components/Loading'
import { ErrorBox } from '@/components/ErrorBox'

interface MetadataFormRendererProps {
  slug: string
  initialValues?: Record<string, unknown>
  onSubmit?: (values: Record<string, unknown>) => void
  readOnly?: boolean
}

export function MetadataFormRenderer({
  slug,
  initialValues,
  onSubmit,
  readOnly = false,
}: MetadataFormRendererProps) {
  const [def, setDef] = useState<FormDefinition | null>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState<Error | null>(null)
  const [formData, setFormData] = useState<Record<string, unknown>>(
    initialValues ?? {},
  )

  useEffect(() => {
    setLoading(true)
    setError(null)
    apiFetch<FormDefinition>(`/api/v1/_meta/metadata/forms/${slug}`)
      .then((data) => {
        setDef(data)
        if (initialValues) setFormData(initialValues)
      })
      .catch((err: unknown) => {
        if (err instanceof Error) {
          setError(err)
        } else {
          setError(new Error(String(err)))
        }
      })
      .finally(() => setLoading(false))
  }, [slug, initialValues])

  if (loading) return <Loading />
  if (error) return <ErrorBox error={error} />
  if (!def) return <ErrorBox error={new Error(`Form "${slug}" not found.`)} />

  const handleChange = (e: IChangeEvent) => {
    setFormData((e.formData as Record<string, unknown>) ?? {})
  }

  const handleSubmit = (e: IChangeEvent) => {
    onSubmit?.((e.formData as Record<string, unknown>) ?? {})
  }

  return (
    <Form
      schema={def.jsonSchema as RJSFSchema}
      uiSchema={def.uiSchema as UiSchema}
      formData={formData}
      validator={validator}
      widgets={vibeWidgets}
      templates={vibeTemplates}
      readonly={readOnly}
      onChange={handleChange}
      onSubmit={handleSubmit}
      formContext={{ formData }}
    />
  )
}