import React, { useState, useEffect } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { useEntities, EntityFieldType } from '../../contexts/EntityContext' import { FaSave, FaArrowLeft, FaPlus, FaTrashAlt, FaDatabase, FaCog, FaTable, FaColumns, } from 'react-icons/fa' import { CustomEntityField } from '@/proxy/developerKit/models' import { ROUTES_ENUM } from '@/routes/route.constant' import { useLocalization } from '@/utils/hooks/useLocalization' import { Formik, Form, Field, FieldProps, FieldArray } from 'formik' import * as Yup from 'yup' import { FormItem, Input, Select, Checkbox, FormContainer, Button } from '@/components/ui' import { SelectBoxOption } from '@/shared/types' // Validation schema const validationSchema = Yup.object({ name: Yup.string().required('Entity name is required'), displayName: Yup.string().required('Display name is required'), tableName: Yup.string().required('Table name is required'), description: Yup.string(), fields: Yup.array() .of( Yup.object({ name: Yup.string().required('Field name is required'), type: Yup.string().required('Field type is required'), isRequired: Yup.boolean(), maxLength: Yup.number().nullable(), isUnique: Yup.boolean(), defaultValue: Yup.string().notRequired(), description: Yup.string().notRequired(), displayOrder: Yup.number().required(), }), ) .min(1, 'At least one field is required'), isActive: Yup.boolean(), isFullAuditedEntity: Yup.boolean(), isMultiTenant: Yup.boolean(), }) const EntityEditor: React.FC = () => { const { id } = useParams() const navigate = useNavigate() const { translate } = useLocalization() const { getEntity, addEntity, updateEntity } = useEntities() const isEditing = !!id // Initial values for Formik const [initialValues, setInitialValues] = useState({ name: '', displayName: '', tableName: '', description: '', fields: [ { id: crypto.randomUUID(), entityId: id || '', name: 'Name', type: 'string' as EntityFieldType, isRequired: true, maxLength: 100, description: 'Entity name', displayOrder: 1, }, ] as CustomEntityField[], isActive: true, isFullAuditedEntity: true, isMultiTenant: true, }) useEffect(() => { if (isEditing && id) { const entity = getEntity(id) if (entity) { // Ensure fields are sorted by displayOrder and normalized to sequential values const sortedFields = (entity.fields || []) .slice() .sort((a, b) => (a.displayOrder ?? 0) - (b.displayOrder ?? 0)) .map((f, idx) => ({ ...f, displayOrder: f.displayOrder ?? idx + 1 })) setInitialValues({ name: entity.name, displayName: entity.displayName, tableName: entity.tableName, description: entity.description || '', fields: sortedFields, isActive: entity.isActive, isFullAuditedEntity: entity.isFullAuditedEntity, isMultiTenant: entity.isMultiTenant, }) } } }, [id, isEditing, getEntity]) const handleSubmit = async (values: typeof initialValues, { setSubmitting }: any) => { try { const sanitizedFields = values.fields.map((f) => { // send both `displayOrder` (frontend proxy) and `order` (backend DTO) to be safe const sanitized: any = { ...(f.id && isEditing ? { id: f.id } : {}), name: f.name.trim(), type: f.type, isRequired: f.isRequired, maxLength: f.maxLength, isUnique: f.isUnique || false, defaultValue: f.defaultValue, description: f.description, displayOrder: f.displayOrder, order: f.displayOrder, } return sanitized }) const entityData = { name: values.name.trim(), displayName: values.displayName.trim(), tableName: values.tableName.trim(), description: values.description.trim(), fields: sanitizedFields, isActive: values.isActive, isFullAuditedEntity: values.isFullAuditedEntity, isMultiTenant: values.isMultiTenant, } if (isEditing && id) { await updateEntity(id, entityData) } else { await addEntity(entityData) } navigate(ROUTES_ENUM.protected.saas.developerKit.entities, { replace: true }) } catch (error) { console.error('Error saving entity:', error) alert('Failed to save entity. Please try again.') } finally { setSubmitting(false) } } const fieldTypes = [ { value: 'string', label: 'String' }, { value: 'number', label: 'Number' }, { value: 'decimal', label: 'Decimal' }, { value: 'boolean', label: 'Boolean' }, { value: 'date', label: 'Date' }, { value: 'guid', label: 'Guid' }, ] return ( {({ values, touched, errors, isSubmitting, setFieldValue, submitForm, isValid }) => ( <> {/* Enhanced Header */}

{isEditing ? `${translate('::App.DeveloperKit.EntityEditor.Title.Edit')} - ${values.name || initialValues.name || 'Entity'}` : translate('::App.DeveloperKit.Entity.CreateEntity')}

{isEditing ? 'Modify your entity' : 'Create a new entity'}

{/* Save Button in Header */}
{/* Migration Status Info Banner */} {isEditing && id && getEntity(id)?.migrationStatus === 'applied' && (

Migration Applied - Changes Will Require New Migration

This entity has been migrated to the database. Any structural changes you make will reset the migration status to "pending", and you'll need to generate and apply a new migration to update the database schema.

)} {/* Basic Entity Information */}

Entity Settings

{({ field }: FieldProps) => ( { field.onBlur(e) if (!values.tableName) { setFieldValue('tableName', values.name + 's') } if (!values.displayName) { setFieldValue('displayName', values.name) } }} placeholder="e.g., Product, User, Order" className="px-2 py-1.5 bg-slate-50 focus:bg-white transition-all duration-200 text-sm h-7" /> )}

Includes CreationTime, CreatorId, LastModificationTime, LastModifierId, IsDeleted, DeleterId, DeletionTime

Adds TenantId column for multi-tenancy support

{/* Fields Section */}
{({ push, remove }) => ( <>

{translate('::App.DeveloperKit.EntityEditor.Fields')}

Order *
{translate('::App.DeveloperKit.EntityEditor.FieldName')} *
{translate('::App.DeveloperKit.EntityEditor.Type')} *
{translate('::App.DeveloperKit.EntityEditor.DefaultValue')}
{translate('::App.DeveloperKit.EntityEditor.MaxLength')}
{translate('::App.DeveloperKit.EntityEditor.Description')}
{translate('::App.DeveloperKit.EntityEditor.Required')}
{translate('::App.DeveloperKit.EntityEditor.Unique')}
{values.fields.map((field, index) => (
{({ field, form }: FieldProps) => (