import { useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' import { Plus, Sparkles, Trash2, X } from 'lucide-react' import { useLocale } from '@meshkee/dashboard-ui' import type { TechnicalFieldType, TechnicalFormFieldDraft } from '../types/technicalForm' import { useT } from '../i18n/useT' import type { BusinessMessageKey } from '../i18n/messages' import { createId } from '../utils/id' import { Tooltip } from './Tooltip' import styles from './VariationsModal.module.css' import fieldStyles from './TechnicalFormModal.module.css' import aiStyles from '../styles/ai.module.css' interface TechnicalFormModalProps { open: boolean categoryName: string fields: TechnicalFormFieldDraft[] isLoading?: boolean isSaving?: boolean isGenerating?: boolean escapeDisabled?: boolean error?: string onClose: () => void onChange: (fields: TechnicalFormFieldDraft[]) => void onSave: () => void onGenerate?: () => void onGenerateClick?: () => void } const ANIMATION_MS = 220 const FIELD_TYPE_KEYS: Record = { text: 'categories.technical.type.text', textarea: 'categories.technical.type.textarea', select: 'categories.technical.type.select', multi_select: 'categories.technical.type.multi_select', } function createEmptyField(): TechnicalFormFieldDraft { return { id: createId(), label: '', type: 'text', isRequired: false, options: [''], } } export function TechnicalFormModal({ open, categoryName, fields, isLoading = false, isSaving = false, isGenerating = false, escapeDisabled = false, error = '', onClose, onChange, onSave, onGenerate, onGenerateClick, }: TechnicalFormModalProps) { const t = useT() const { locale } = useLocale() const isFa = locale === 'fa' const [mounted, setMounted] = useState(open) const [closing, setClosing] = useState(false) const optionInputRefs = useRef>({}) const pendingFocus = useRef<{ fieldId: string; index: number } | null>(null) useEffect(() => { if (open) { setMounted(true) setClosing(false) } else if (mounted) { setClosing(true) const timer = setTimeout(() => { setMounted(false) setClosing(false) }, ANIMATION_MS) return () => clearTimeout(timer) } }, [open, mounted]) useEffect(() => { if (!pendingFocus.current) return const { fieldId, index } = pendingFocus.current optionInputRefs.current[fieldId]?.[index]?.focus() pendingFocus.current = null }, [fields]) useEffect(() => { if (!mounted || closing || escapeDisabled) return const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() } document.addEventListener('keydown', onKey) return () => document.removeEventListener('keydown', onKey) }, [mounted, closing, escapeDisabled, onClose]) if (!mounted) return null function updateField(fieldId: string, patch: Partial) { onChange( fields.map((field) => (field.id === fieldId ? { ...field, ...patch } : field)), ) } function removeField(fieldId: string) { onChange(fields.filter((field) => field.id !== fieldId)) } function addField() { onChange([...fields, createEmptyField()]) } function updateOption(fieldId: string, index: number, value: string) { onChange( fields.map((field) => { if (field.id !== fieldId) return field return { ...field, options: field.options.map((option, optionIndex) => optionIndex === index ? value : option, ), } }), ) } function addOption(fieldId: string) { onChange( fields.map((field) => field.id === fieldId ? { ...field, options: [...field.options, ''] } : field, ), ) } function removeOption(fieldId: string, index: number) { onChange( fields.map((field) => { if (field.id !== fieldId) return field const next = field.options.filter((_, optionIndex) => optionIndex !== index) return { ...field, options: next.length ? next : [''] } }), ) } function handleOptionKeyDown( fieldId: string, index: number, e: React.KeyboardEvent, options: string[], ) { if (e.key !== 'Enter') return e.preventDefault() const nextIndex = index + 1 if (nextIndex < options.length) { optionInputRefs.current[fieldId]?.[nextIndex]?.focus() return } pendingFocus.current = { fieldId, index: nextIndex } addOption(fieldId) } const canSave = !isLoading && !isSaving && !isGenerating && fields.every((field) => { if (!field.label.trim()) return false if (field.type === 'select' || field.type === 'multi_select') { return field.options.some((option) => option.trim()) } return true }) return createPortal(
e.stopPropagation()} role="dialog" aria-modal="true" aria-labelledby="technical-form-title" lang={isFa ? 'fa' : 'en'} dir={isFa ? 'rtl' : 'ltr'} >

{t('categories.technical.title')}

{categoryName ?

{categoryName}

: null}
{isLoading ? (

{t('categories.technical.loading')}

) : ( <>
{fields.map((field) => (
updateField(field.id, { label: e.target.value })} aria-label={t('categories.technical.fieldLabel')} />
{(field.type === 'select' || field.type === 'multi_select') && (
{t('categories.technical.options')}
{field.options.map((option, optionIndex) => (
{ if (!optionInputRefs.current[field.id]) { optionInputRefs.current[field.id] = [] } optionInputRefs.current[field.id][optionIndex] = el }} type="text" placeholder={t('categories.technical.optionN', { n: optionIndex + 1, })} value={option} onChange={(e) => updateOption(field.id, optionIndex, e.target.value) } onKeyDown={(e) => handleOptionKeyDown(field.id, optionIndex, e, field.options) } /> {field.options.length > 1 && (
)} {field.options.length <= 1 &&
}
))}
)}
))} {!fields.length && (

{t('categories.technical.empty')}

)}
)} {error ?

{error}

: null}
{(onGenerateClick ?? onGenerate) ? ( ) : null}
, document.body, ) }