import React, { useState } from 'react'; import { Control, Controller, FieldError, UseFormReturn } from 'react-hook-form'; import { Calendar, Clock, Link2, X } from 'lucide-react'; import { Button, Label, Input, BooleanCellEditor, DateCellEditor, JsonCellEditor, type DatabaseRecord, type ConvertedValue, type UserInputValue, TypeBadge, } from '@/components'; import { ColumnSchema, ColumnType } from '@insforge/shared-schemas'; import { convertValueForColumn, cn, formatValueForDisplay } from '@/lib/utils/utils'; import { LinkRecordModal } from '@/features/database/components/LinkRecordModal'; import { isValid, parseISO } from 'date-fns'; // Helper function to get appropriate placeholder text function getPlaceholderText(field: ColumnSchema): string { // Check if default value is a function if (field.defaultValue && field.defaultValue.endsWith('()')) { return 'Auto-generated on submit'; } // Static default value or no default value return field.isNullable ? 'Optional' : 'Required'; } // Common interface for all form editors interface BaseFormEditorProps { nullable: boolean; onChange: (value: ConvertedValue) => void; hasForeignKey?: boolean; } // Form adapters for edit cell components interface FormBooleanEditorProps extends BaseFormEditorProps { value: boolean | null; } function FormBooleanEditor({ value, nullable, onChange, hasForeignKey }: FormBooleanEditorProps) { const [showEditor, setShowEditor] = useState(false); const handleValueChange = (newValue: string) => { if (newValue === 'null') { onChange(null); } else { onChange(newValue === 'true'); } setShowEditor(false); }; const handleCancel = () => { setShowEditor(false); }; if (showEditor) { return ( ); } return ( ); } interface FormDateEditorProps extends BaseFormEditorProps { value: string | null; type?: ColumnType.DATETIME | ColumnType.DATE; field: ColumnSchema; } function FormDateEditor({ value, type = ColumnType.DATETIME, onChange, field, }: FormDateEditorProps) { const [showEditor, setShowEditor] = useState(false); const handleValueChange = (newValue: string | null) => { if (newValue === 'null' || newValue === null) { onChange(null); } else { onChange(newValue); } setShowEditor(false); }; const handleCancel = () => { setShowEditor(false); }; const formatDisplayValue = () => { if (!value || value === 'null') { return getPlaceholderText(field); } return formatValueForDisplay(value, type); }; const formatValue = () => { if (!value || value === 'null') { return null; } const date = parseISO(value); return isValid(date) ? value : null; }; if (showEditor) { return ( ); } return ( ); } interface FormNumberEditorProps extends BaseFormEditorProps { value: number | null; type: ColumnType.INTEGER | ColumnType.FLOAT; tableName: string; field: ColumnSchema; } function FormNumberEditor({ value, type, onChange, tableName, field }: FormNumberEditorProps) { return ( { const inputValue = e.target.value; if (inputValue === '') { // Handle empty value - let form validation handle required fields onChange(null); } else { const numValue = type === ColumnType.INTEGER ? parseInt(inputValue, 10) : parseFloat(inputValue); onChange(isNaN(numValue) ? null : numValue); } }} placeholder={getPlaceholderText(field)} className={`dark:text-white dark:placeholder:text-neutral-400 dark:bg-neutral-900 dark:border-neutral-700 ${field.foreignKey ? 'pr-18' : ''}`} /> ); } interface FormJsonEditorProps extends BaseFormEditorProps { value: string | null; hasForeignKey: boolean; } function FormJsonEditor({ value, nullable, onChange, hasForeignKey }: FormJsonEditorProps) { const [showEditor, setShowEditor] = useState(false); const handleValueChange = (newValue: string) => { onChange(newValue); setShowEditor(false); }; const handleCancel = () => { setShowEditor(false); }; if (showEditor) { return ( ); } const formatDisplayValue = () => { if (!value || value === 'null') { return 'Empty JSON'; } return formatValueForDisplay(value, ColumnType.JSON); }; return ( ); } interface RecordFormFieldProps { field: ColumnSchema; form: UseFormReturn; tableName: string; } // Helper component to render field label with type badge function FieldLabel({ field, tableName, children, }: { field: ColumnSchema; tableName: string; children?: React.ReactNode; }) { return ( ); } // Generic component for any field type with foreign key linking interface FieldWithLinkProps { field: ColumnSchema; control: Control; children: React.ReactNode; } function FieldWithLink({ field, control, children }: FieldWithLinkProps) { if (!field.foreignKey) { // Regular field without foreign key return <>{children}; } // Store foreignKey in a const to help TypeScript narrow the type const foreignKey = field.foreignKey; // Field with foreign key linking capability - integrated design return ( <> { const hasLinkedValue = formField.value && formField.value !== ''; const childElement = children as React.ReactElement<{ className?: string; value?: UserInputValue; }>; const existingClassName = childElement.props.className || ''; const paddingClass = hasLinkedValue ? 'pr-16' : 'pr-11'; const modifiedChildren = React.cloneElement(childElement, { value: formField.value as UserInputValue, className: `${existingClassName} ${paddingClass}`.trim(), }); return ( <>
{modifiedChildren}
{(hasLinkedValue || hasLinkedValue === 0) && ( )} { const referenceValue = record[foreignKey.referenceColumn]; const result = convertValueForColumn( field.type, String(referenceValue || '') ); if (result.success) { formField.onChange(result.value); } else { // Fallback to string if conversion fails formField.onChange(String(referenceValue || '')); } }} > {(openModal) => ( )}
{/* Foreign Key Relationship Info */}
Has a Foreign Key relation to
); }} /> ); } export function RecordFormField({ field, form, tableName }: RecordFormFieldProps) { const { control, register, formState: { errors }, } = form; // TODO: This is a mess, we need to clean it up. To fix in future. const renderField = () => { // Infer frontend type from field name and SQLite type switch (field.type) { case ColumnType.BOOLEAN: return (
( )} />
); case ColumnType.INTEGER: case ColumnType.FLOAT: return (
( )} />
); case ColumnType.DATE: case ColumnType.DATETIME: return (
( )} />
); case ColumnType.JSON: return (
{ return ( { const result = convertValueForColumn(ColumnType.JSON, newValue as string); if (result.success) { formField.onChange(result.value as JSON); } else { // If parsing fails, keep the string value formField.onChange(newValue); } }} /> ); }} />
); case ColumnType.UUID: return (
); case ColumnType.STRING: default: return (
); } }; return (
{renderField()} {errors[field.columnName] && (

{(errors[field.columnName] as FieldError)?.message || `${field.columnName} is required`}

)}
); }