import React, { useState, useEffect, useCallback } from 'react';
import { EmailTemplate } from '../types';
import { saveTemplate, generateSQL, generateSelectSQL, getTemplates, generateTemplateKey, generateN8nCode } from '../services/storage';
import { generateEmailContent } from '../services/geminiService';
import RichTextEditor from './RichTextEditor';
import {
Save, ArrowLeft, Eye, Database, Wand2, Copy, Check, Code
} from 'lucide-react';
interface Props {
templateId?: string;
initialTemplate?: EmailTemplate;
onBack: () => void;
onSave: () => void;
}
// Fallback for crypto.randomUUID in non-secure (HTTP) contexts
const generateUUID = () => {
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
return crypto.randomUUID();
}
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
const DEFAULT_HEADER = `
La Mia Azienda
`;
const DEFAULT_BODY = `
Gentile {{first_name}},
Questo è un messaggio di default.
`;
const DEFAULT_FOOTER = `
© 2024 La Mia Azienda. Tutti i diritti riservati.
`;
const Editor: React.FC = ({ initialTemplate, onBack, onSave }) => {
const [name, setName] = useState(initialTemplate?.name || 'Nuovo Template');
const [description, setDescription] = useState(initialTemplate?.description || '');
const [subject, setSubject] = useState(initialTemplate?.subject || 'Benvenuti nel nostro servizio');
const [header, setHeader] = useState(initialTemplate?.header || DEFAULT_HEADER);
const [body, setBody] = useState(initialTemplate?.body || DEFAULT_BODY);
const [footer, setFooter] = useState(initialTemplate?.footer || DEFAULT_FOOTER);
const [activeTab, setActiveTab] = useState<'header' | 'body' | 'footer'>('body');
const [showSqlModal, setShowSqlModal] = useState(false);
const [detectedVars, setDetectedVars] = useState([]);
const [isGenerating, setIsGenerating] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [aiPrompt, setAiPrompt] = useState('');
const [showAiModal, setShowAiModal] = useState(false);
const [nameError, setNameError] = useState('');
const detectVariables = useCallback(() => {
const regex = /\{\{([\w\d_-]+)\}\}/g;
const allText = `${subject} ${header} ${body} ${footer}`;
const matches = [...allText.matchAll(regex)];
const uniqueVars = Array.from(new Set(matches.map(m => m[1])));
setDetectedVars(uniqueVars);
}, [header, body, footer, subject]);
useEffect(() => {
detectVariables();
}, [detectVariables]);
useEffect(() => {
if (nameError) setNameError('');
}, [name]);
const handleSave = async () => {
const newKey = generateTemplateKey(name);
if (!newKey) {
setNameError('Il nome del template non può essere vuoto o contenere solo simboli.');
return;
}
setIsSaving(true);
try {
console.log("Inizio processo di salvataggio per:", name);
// Check for duplicates
let allTemplates: EmailTemplate[] = [];
try {
allTemplates = await getTemplates();
} catch (err) {
console.error("Errore durante il recupero dei template esistenti:", err);
// Non blocchiamo necessariamente, ma avvisiamo
}
const isDuplicate = allTemplates.some(t => {
if (initialTemplate && t.id === initialTemplate.id) return false;
return generateTemplateKey(t.name) === newKey;
});
if (isDuplicate) {
setNameError('Esiste già un template con questo nome.');
setIsSaving(false);
return;
}
const newTemplate: EmailTemplate = {
id: initialTemplate?.id || generateUUID(),
name,
description,
subject,
header,
body,
footer,
variables: detectedVars,
updatedAt: new Date().toISOString()
};
console.log("Invio dati al server...", newTemplate);
await saveTemplate(newTemplate);
console.log("Salvataggio completato con successo");
onSave();
} catch (e: any) {
console.error("ERRORE SALVATAGGIO:", e);
alert(`Errore: ${e.message || 'Impossibile salvare il template'}`);
} finally {
setIsSaving(false);
}
};
const handleAiGenerate = async () => {
if (!aiPrompt.trim()) return;
setIsGenerating(true);
try {
const generated = await generateEmailContent(aiPrompt, activeTab);
if (activeTab === 'header') setHeader(generated);
if (activeTab === 'body') setBody(generated);
if (activeTab === 'footer') setFooter(generated);
setShowAiModal(false);
setAiPrompt('');
} catch (e) {
alert("Errore nella generazione del contenuto. Controlla la configurazione della API Key.");
} finally {
setIsGenerating(false);
}
};
const tempTemplateObj = {
id: initialTemplate?.id || 'ANTEPRIMA',
name,
description,
subject,
header,
body,
footer,
variables: detectedVars,
updatedAt: ''
};
const currentInsertSql = showSqlModal ? generateSQL(tempTemplateObj) : '';
const currentSelectSql = showSqlModal ? generateSelectSQL(tempTemplateObj) : '';
const currentN8nCode = showSqlModal ? generateN8nCode(tempTemplateObj) : '';
const getActiveContent = () => {
switch (activeTab) {
case 'header': return header;
case 'body': return body;
case 'footer': return footer;
}
};
const setActiveContent = (val: string) => {
switch (activeTab) {
case 'header': setHeader(val); break;
case 'body': setBody(val); break;
case 'footer': setFooter(val); break;
}
};
const tabNames: Record = {
header: 'Testata',
body: 'Corpo',
footer: 'Piè di pagina'
};
return (
{initialTemplate ? 'Modifica Template' : 'Crea Template'}
setShowSqlModal(true)}
className="flex items-center gap-2 px-4 py-2 text-slate-600 bg-white border border-slate-300 rounded-lg hover:bg-slate-50 font-medium transition-colors"
>
Integrazione
{isSaving ? 'Salvataggio...' : 'Salva Template'}
Nome Template
setName(e.target.value)}
className={`w-full px-3 py-2 border rounded-md focus:ring-2 focus:ring-brand-500 outline-none bg-white ${nameError ? 'border-red-500 focus:border-red-500' : 'border-slate-300 focus:border-brand-500'}`}
placeholder="es. Email di Benvenuto"
/>
{nameError &&
{nameError}
}
Deve essere univoco. Usato per generare la chiave DB: {generateTemplateKey(name) || '...'}
Descrizione
Oggetto Email
setSubject(e.target.value)}
className="w-full px-3 py-2 border border-slate-300 rounded-md focus:ring-2 focus:ring-brand-500 focus:border-brand-500 outline-none bg-white"
placeholder="Oggetto... (supporta {{placeholder}})"
/>
Variabili Attive
Rilevate automaticamente dal testo
{detectedVars.length === 0 && Nessuna variabile rilevata. Scrivi {'{{nome}}'} per aggiungerne una. }
{detectedVars.map(v => (
{`{{${v}}}`}
))}
{(['header', 'body', 'footer'] as const).map((tab) => (
setActiveTab(tab)}
className={`px-4 py-2 font-medium text-sm capitalize ${
activeTab === tab
? 'text-brand-600 border-b-2 border-brand-600'
: 'text-slate-500 hover:text-slate-700'
}`}
>
{tabNames[tab]}
))}
Editor Contenuti
setShowAiModal(true)}
className="text-xs flex items-center gap-1 text-purple-600 hover:text-purple-700 font-medium bg-purple-50 px-2 py-1 rounded"
>
Genera con IA
Usa il pulsante "Variabile" nella toolbar per inserire placeholder come {'{{nome}}'}.
Anteprima Live
Renderizzato come HTML standard
Oggetto:
{subject.replace(/\{\{([\w\d_-]+)\}\}/g, (match, p1) => `${match} `)}
{showSqlModal && (
Dettagli Integrazione
setShowSqlModal(false)} className="text-slate-400 hover:text-slate-600">
×
1. Setup (Esegui una volta nel DB)
{currentInsertSql}
navigator.clipboard.writeText(currentInsertSql)}
className="absolute top-2 right-2 p-2 bg-slate-700 text-white rounded hover:bg-slate-600"
title="Copia SQL INSERT"
>
2. Recupero (Nodo SQL n8n)
{currentSelectSql}
navigator.clipboard.writeText(currentSelectSql)}
className="absolute top-2 right-2 p-2 bg-slate-600 text-white rounded hover:bg-slate-500"
title="Copia SQL SELECT"
>
3. Popolamento (Nodo Codice n8n)
{currentN8nCode}
navigator.clipboard.writeText(currentN8nCode)}
className="absolute top-2 right-2 p-2 bg-white border border-slate-200 text-slate-600 rounded hover:bg-slate-50 shadow-sm"
title="Copia Codice JS"
>
Incolla questo codice in un Nodo Code collegato dopo il tuo nodo SQL. Sostituisci REPLACE_WITH_VALUE con le variabili reali (es. $('NodeName').item.json.name).
)}
{showAiModal && (
Generatore Contenuti IA
Descrivi cosa vuoi per la sezione {tabNames[activeTab]} .
L'IA genererà il codice HTML con i placeholder necessari.
)}
);
};
export default Editor;