'use client'; /** * @file RelationSelectField.tsx * @description A styled select field for relation fields that supports: * - Standard single-click selection * - Double-click to open QuickCreateModal for in-place record creation * - Auto-update when a new record is injected via usePublicationStore * * Usage: * ```tsx * * ``` */ import React, { useEffect, useRef, useState, useCallback } from 'react'; import { createPortal } from 'react-dom'; import { PlusCircle, ChevronDown, MousePointerClick } from 'lucide-react'; import { RelationSelectFieldProps } from '@/types/publications'; import { usePublicationStore } from '@/store/usePublicationStore'; import { collectionMap } from '@/lib/schema'; import { APPWRITE_ALMEIRA_CONFIG } from '@/lib/appwrite'; // ───────────────────────────────────────────── // QUICK CREATE MODAL (Portal-based) // ───────────────────────────────────────────── interface QuickCreatePortalProps { collection: string; fieldName: string; onClose: () => void; } function QuickCreatePortal({ collection, fieldName, onClose }: QuickCreatePortalProps) { const { injectRelationValue } = usePublicationStore(); const [fields, setFields] = useState>({}); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setSaving(true); setError(null); try { const res = await fetch('/api/publications/quick-create', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ collection, data: fields }), }); const json = await res.json(); if (!res.ok || !json.success) { throw new Error(json.error || 'Error al crear el registro.'); } // Inject the new record into the store so the parent select updates injectRelationValue(fieldName, collection, json.data); onClose(); } catch (err: unknown) { setError(err instanceof Error ? err.message : 'Error desconocido.'); } finally { setSaving(false); } }; // Determine simple fields to show for quick creation const quickFields: { name: string; label: string }[] = (() => { const schemaMap: Record> = { publication_types: [ { name: 'codigo', label: 'Código', type: 'text' }, { name: 'descripcion', label: 'Descripción', type: 'text' }, ], clients: [ { name: 'nombre_entidad', label: 'Nombre de la Empresa', type: 'text' }, { name: 'numero_whatsapp', label: 'WhatsApp', type: 'text' }, { name: 'slug', label: 'Slug', type: 'text' }, ], categories: [ { name: 'nombre', label: 'Nombre', type: 'text' }, { name: 'slug', label: 'Slug', type: 'text' }, ], }; return schemaMap[collection] ?? [{ name: 'descripcion', label: 'Descripción', type: 'text' }]; })(); const modal = ( {/* Backdrop */} {/* Modal Card */} {/* Header */} Crear Nuevo {collection.replace(/_/g, ' ')} × {/* Form */} {quickFields.map((field) => ( {field.label} setFields((prev) => ({ ...prev, [field.name]: e.target.value }))} className="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-2.5 text-white text-sm focus:border-cyan-500 focus:outline-none transition-colors" /> ))} {error && ( {error} )} Cancelar {saving ? 'Guardando…' : 'Crear'} ); // Use a Portal to render outside the form DOM tree return createPortal(modal, document.body); } // ───────────────────────────────────────────── // RELATION SELECT FIELD // ───────────────────────────────────────────── export const RelationSelectField: React.FC = ({ name, label, options: initialOptions, labelKey, value: externalValue, onChange, relationCollection, required = false, disabled = false, }) => { const { relationCache, pendingRelationField, openQuickCreate, closeQuickCreate, } = usePublicationStore(); // Merge initial options with any newly injected ones from the store const cachedOptions = relationCache[name] || relationCache[relationCollection] || []; const mergedOptions = cachedOptions.length > 0 ? cachedOptions : initialOptions; const [selectedValue, setSelectedValue] = useState(externalValue || ''); const [isModalOpen, setIsModalOpen] = useState(false); const doubleClickTimer = useRef | null>(null); // Auto-select the newly injected option useEffect(() => { if (pendingRelationField === name && cachedOptions.length > 0) { const newest = cachedOptions[0]; if (newest) { setSelectedValue(newest.$id); onChange?.(newest.$id); } } }, [pendingRelationField, cachedOptions, name, onChange]); const handleChange = useCallback((e: React.ChangeEvent) => { const val = e.target.value; setSelectedValue(val); onChange?.(val); }, [onChange]); const handleDoubleClick = useCallback(() => { if (doubleClickTimer.current) clearTimeout(doubleClickTimer.current); openQuickCreate(name, relationCollection); setIsModalOpen(true); }, [name, relationCollection, openQuickCreate]); const handleCloseModal = useCallback(() => { setIsModalOpen(false); closeQuickCreate(); }, [closeQuickCreate]); return ( {/* Label with hint */} {label} {required && *} Doble-click para crear nuevo {/* Select Wrapper */} — Seleccionar — {mergedOptions.map((opt) => ( {String(opt[labelKey] ?? opt.$id)} ))} {/* Quick Create Portal */} {isModalOpen && ( )} ); }; export default RelationSelectField;
{collection.replace(/_/g, ' ')}
{error}