'use client'; import React, { useEffect, useRef, useState } from 'react'; import useContractManagement from '../hooks/useContractManagement'; type Props = { editingTemplateId?: string | null; onCancelEdit?: () => void; onSaved?: (info?: { action: 'created' | 'revised'; templateId: string }) => void; }; export default function ContractEditor({ onSaved, editingTemplateId, onCancelEdit }: Props) { const [name, setName] = useState(''); const [htmlCode, setHtmlCode] = useState(''); const [isPreview, setIsPreview] = useState(false); const [saving, setSaving] = useState(false); const [statusMsg, setStatusMsg] = useState(null); const [lang, setLang] = useState<'en' | 'de'>('en'); const [type, setType] = useState<'contract' | 'bill' | 'other'>('contract'); const [contractType, setContractType] = useState<'contract' | 'gdpr'>('contract'); const [userType, setUserType] = useState<'personal' | 'company' | 'both'>('personal'); const [description, setDescription] = useState(''); const [editingMeta, setEditingMeta] = useState<{ id: string; version: number; state: string } | null>(null); const iframeRef = useRef(null); const { uploadTemplate, updateTemplateState, getTemplate, reviseTemplate } = useContractManagement(); const resetEditorFields = () => { setName(''); setHtmlCode(''); setDescription(''); setIsPreview(false); setEditingMeta(null); }; // Load template into editor when editing useEffect(() => { const load = async () => { if (!editingTemplateId) { setEditingMeta(null); return; } setSaving(true); setStatusMsg(null); try { const tpl = await getTemplate(editingTemplateId); setName(tpl.name || ''); setHtmlCode(tpl.html || ''); setDescription((tpl.description as any) || ''); setLang((tpl.lang as any) || 'en'); setType((tpl.type as any) || 'contract'); setContractType(((tpl.contract_type as any) || 'contract') as 'contract' | 'gdpr'); setUserType(((tpl.user_type as any) || 'both') as 'personal' | 'company' | 'both'); setEditingMeta({ id: editingTemplateId, version: Number(tpl.version || 1), state: String(tpl.state || 'inactive') }); setStatusMsg(`Loaded template for editing (v${Number(tpl.version || 1)}).`); } catch (e: any) { setStatusMsg(e?.message || 'Failed to load template.'); } finally { setSaving(false); } }; load(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [editingTemplateId]); // Build a full HTML doc if user pasted only a snippet const wrapIfNeeded = (src: string) => { const hasDoc = /]/i.test(src); if (hasDoc) return src; // Minimal A4 skeleton so snippets render and print correctly return ` Preview
${src}
`; }; // Write/refresh iframe preview useEffect(() => { if (!isPreview) return; const iframe = iframeRef.current; if (!iframe) return; const doc = iframe.contentDocument || iframe.contentWindow?.document; if (!doc) return; const html = wrapIfNeeded(htmlCode); doc.open(); doc.write(html); doc.close(); const resize = () => { // Allow time for layout/styles requestAnimationFrame(() => { const h = doc.body ? Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight) : 1200; iframe.style.height = Math.min(Math.max(h, 1123), 2000) + 'px'; // clamp for UX }); }; // Initial resize and after load resize(); const onLoad = () => resize(); iframe.addEventListener('load', onLoad); // Also observe mutations to adjust height if content changes const mo = new MutationObserver(resize); mo.observe(doc.documentElement, { childList: true, subtree: true, attributes: true, characterData: true }); return () => { iframe.removeEventListener('load', onLoad); mo.disconnect(); }; }, [isPreview, htmlCode]); const printPreview = () => { const w = iframeRef.current?.contentWindow; w?.focus(); w?.print(); }; const slug = (s: string) => s.toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'template'; // NEW: all-fields-required guard const canSave = Boolean( name.trim() && htmlCode.trim() && type && (type === 'contract' ? contractType : true) && userType && lang ) const save = async (publish: boolean) => { const html = htmlCode.trim(); // NEW: validate all fields if (!canSave) { setStatusMsg('Please fill all required fields (name, HTML, type, user type, language).'); return; } if (publish && type === 'contract') { const kind = contractType === 'gdpr' ? 'GDPR' : 'Contract'; const ok = window.confirm( `Activate this ${kind} template now?\n\nThis will deactivate other active ${kind} templates that apply to the same user type and language.` ); if (!ok) return; } setSaving(true); setStatusMsg(null); try { // Build a file from HTML code const file = new File([html], `${slug(name)}.html`, { type: 'text/html' }); // If editing: revise (new object + version bump) and deactivate previous if (editingTemplateId) { const revised = await reviseTemplate(editingTemplateId, { file, name, type, contract_type: type === 'contract' ? contractType : undefined, lang, description: description || undefined, user_type: userType, state: publish ? 'active' : 'inactive', }); setStatusMsg(publish ? 'New version created and activated (previous deactivated).' : 'New version created (previous deactivated).'); if (onSaved && revised?.id) onSaved({ action: 'revised', templateId: String(revised.id) }); resetEditorFields(); return; } // Otherwise: create new const created = await uploadTemplate({ file, name, type, contract_type: type === 'contract' ? contractType : undefined, lang, description: description || undefined, user_type: userType, }); if (publish && created?.id) { await updateTemplateState(created.id, 'active'); } setStatusMsg(publish ? 'Template created and activated.' : 'Template created.'); if (onSaved && created?.id) onSaved({ action: 'created', templateId: String(created.id) }); // Reset so another template can be created immediately resetEditorFields(); } catch (e: any) { setStatusMsg(e?.message || 'Save failed.'); } finally { setSaving(false); } }; return (
{editingMeta && (
Editing: {name || 'Untitled'} (v{editingMeta.version}) • state: {editingMeta.state}
{onCancelEdit && ( )}
)}
setName(e.target.value)} required className="w-full sm:w-1/2 rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm text-gray-900 outline-none focus:ring-2 focus:ring-indigo-500/50 shadow" />
{isPreview && ( )}
{/* New metadata inputs */}
{type === 'contract' && ( )} setDescription(e.target.value)} className="w-full rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm text-gray-900 outline-none focus:ring-2 focus:ring-indigo-500/50 shadow" />
{!isPreview && (