diff --git a/src/app/admin/contract-management/components/contractEditor.tsx b/src/app/admin/contract-management/components/contractEditor.tsx index 2b9a68f..777f22d 100644 --- a/src/app/admin/contract-management/components/contractEditor.tsx +++ b/src/app/admin/contract-management/components/contractEditor.tsx @@ -4,10 +4,12 @@ import React, { useEffect, useRef, useState } from 'react'; import useContractManagement from '../hooks/useContractManagement'; type Props = { - onSaved?: () => void; + editingTemplateId?: string | null; + onCancelEdit?: () => void; + onSaved?: (info?: { action: 'created' | 'revised'; templateId: string }) => void; }; -export default function ContractEditor({ onSaved }: Props) { +export default function ContractEditor({ onSaved, editingTemplateId, onCancelEdit }: Props) { const [name, setName] = useState(''); const [htmlCode, setHtmlCode] = useState(''); const [isPreview, setIsPreview] = useState(false); @@ -20,17 +22,54 @@ export default function ContractEditor({ onSaved }: Props) { 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 } = useContractManagement(); + 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); @@ -127,6 +166,25 @@ export default function ContractEditor({ onSaved }: Props) { 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, @@ -142,7 +200,7 @@ export default function ContractEditor({ onSaved }: Props) { } setStatusMsg(publish ? 'Template created and activated.' : 'Template created.'); - if (onSaved) onSaved(); + if (onSaved && created?.id) onSaved({ action: 'created', templateId: String(created.id) }); // Reset so another template can be created immediately resetEditorFields(); } catch (e: any) { @@ -154,6 +212,25 @@ export default function ContractEditor({ onSaved }: Props) { return (
+ {editingMeta && ( +
+
+ Editing: {name || 'Untitled'} (v{editingMeta.version}) • state: {editingMeta.state} +
+ {onCancelEdit && ( + + )} +
+ )}
void; }; type ContractTemplate = { @@ -27,7 +28,7 @@ function StatusBadge({ status }: { status: string }) { return {status}; } -export default function ContractTemplateList({ refreshKey = 0 }: Props) { +export default function ContractTemplateList({ refreshKey = 0, onEdit }: Props) { const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); const [q, setQ] = useState(''); @@ -132,6 +133,14 @@ export default function ContractTemplateList({ refreshKey = 0 }: Props) {

Version {c.version}{c.updatedAt ? ` • Updated ${new Date(c.updatedAt).toLocaleString()}` : ''}

+ {onEdit && ( + + )} diff --git a/src/app/admin/contract-management/hooks/useContractManagement.ts b/src/app/admin/contract-management/hooks/useContractManagement.ts index ddb9959..431729f 100644 --- a/src/app/admin/contract-management/hooks/useContractManagement.ts +++ b/src/app/admin/contract-management/hooks/useContractManagement.ts @@ -200,6 +200,33 @@ export default function useContractManagement() { return authorizedFetch(`/api/document-templates/${id}`, { method: 'PUT', body: fd }); }, [authorizedFetch]); + // NEW: revise template (create a new template record + bump version + deactivate previous) + const reviseTemplate = useCallback(async (id: string, payload: { + file: File | Blob; + name?: string; + type?: string; + contract_type?: 'contract' | 'gdpr'; + lang?: 'en' | 'de' | string; + description?: string; + user_type?: 'personal' | 'company' | 'both'; + state?: 'active' | 'inactive'; + }): Promise => { + const fd = new FormData(); + const file = payload.file instanceof File + ? payload.file + : new File([payload.file], `${payload.name || 'template'}.html`, { type: 'text/html' }); + fd.append('file', file); + if (payload.name !== undefined) fd.append('name', payload.name); + if (payload.type !== undefined) fd.append('type', payload.type); + if (payload.contract_type !== undefined) fd.append('contract_type', payload.contract_type); + if (payload.lang !== undefined) fd.append('lang', payload.lang); + if (payload.description !== undefined) fd.append('description', payload.description); + if (payload.user_type !== undefined) fd.append('user_type', payload.user_type); + if (payload.state !== undefined) fd.append('state', payload.state); + + return authorizedFetch(`/api/document-templates/${id}/revise`, { method: 'POST', body: fd }); + }, [authorizedFetch]); + const updateTemplateState = useCallback(async (id: string, state: 'active' | 'inactive'): Promise => { return authorizedFetch(`/api/document-templates/${id}/state`, { method: 'PATCH', @@ -426,6 +453,7 @@ export default function useContractManagement() { generatePdf, downloadPdf, uploadTemplate, + reviseTemplate, updateTemplate, updateTemplateState, generatePdfWithSignature, diff --git a/src/app/admin/contract-management/page.tsx b/src/app/admin/contract-management/page.tsx index 14290ac..e949cad 100644 --- a/src/app/admin/contract-management/page.tsx +++ b/src/app/admin/contract-management/page.tsx @@ -20,6 +20,7 @@ export default function ContractManagementPage() { const [mounted, setMounted] = useState(false); const router = useRouter(); const [section, setSection] = useState('templates'); + const [editingTemplateId, setEditingTemplateId] = useState(null); useEffect(() => { setMounted(true); }, []); @@ -90,7 +91,13 @@ export default function ContractManagementPage() { Templates - + { + setEditingTemplateId(id); + setSection('editor'); + }} + /> )} {section === 'editor' && ( @@ -99,7 +106,20 @@ export default function ContractManagementPage() { Create Template - + { + setEditingTemplateId(null); + setSection('templates'); + }} + onSaved={(info) => { + bumpRefresh(); + if (info?.action === 'revised') { + setEditingTemplateId(null); + setSection('templates'); + } + }} + /> )}