feat(ar1-admin): variabili cliccabili nel dialog modifica testo email

I 7 Tag delle variabili disponibili (company_name, company_piva, ar1_form_url,
expires_at, days_to_expiry, variant, signer_name) sono ora CLICCABILI dentro
il dialog di modifica testo. Click inserisce {{variabile}} nel campo attivo
al punto del cursore (o in coda se focus perso).

Cambiamenti:
  - Stato nuovo: activeEmailField ('subject' | 'body_html' | 'body_text'),
    inizialmente 'body_html'
  - 3 ref: subjectInputRef, bodyHtmlInputRef, bodyTextInputRef
  - Funzione insertVariable(varName):
      1. Legge editEmailData[activeEmailField]
      2. Calcola posizione cursore via ref.getInput().selectionStart (fallback: coda)
      3. Inserisce '{{' + varName + '}}' al cursore
      4. Riposiziona cursor dopo il token inserito (setTimeout 0 per React re-render)
  - 3 campi Input wrappati con ref + onFocus={() => setActiveEmailField(...)}
  - Box variabili spostato DENTRO il dialog (prima era nel TabPanel, poco scopribile).
    Mostra live quale campo e attivo: 'inserite in: Oggetto / Corpo HTML / Corpo testo'
  - Tag stile severity=info + cursor:pointer + userSelect:none per feedback visuale
  - Box variabili nel tab ora e un semplice Message informativo (la funzione e nel dialog)

UX: l'utente clicca dentro Oggetto, poi clicca {{company_name}}, vede il token
comparire al cursore. Se clicca Corpo HTML, poi un'altra variabile, va in quel
campo. Nessun drag&drop, nessuna complicazione.
This commit is contained in:
BFLOWS
2026-04-23 15:15:18 +02:00
parent c481871fa0
commit 5bbf39488f

View File

@@ -59,7 +59,7 @@ const PEC_KIND_OPTIONS = [
];
/**
* Ar1AdminConfig — configurazione AR1 per superadmin. (build 1776949690)
* Ar1AdminConfig — configurazione AR1 per superadmin. (build 1776950093)
* URL: /ar1-admin
*
* 5 sezioni (TabView):
@@ -112,6 +112,51 @@ const Ar1AdminConfig = () => {
const [editEmailData, setEditEmailData] = useState(null);
const [previewHtml, setPreviewHtml] = useState(null);
const [previewSubject, setPreviewSubject] = useState(null);
// Tracking campo attivo per inserimento variabili con click
const [activeEmailField, setActiveEmailField] = useState('body_html'); // 'subject' | 'body_html' | 'body_text'
const subjectInputRef = useRef(null);
const bodyHtmlInputRef = useRef(null);
const bodyTextInputRef = useRef(null);
// Inserisce {{variabile}} nel campo attivo, al cursore se possibile
const insertVariable = (varName) => {
if (!editEmailData) return;
const token = '{{' + varName + '}}';
const fieldName = activeEmailField;
const refMap = { subject: subjectInputRef, body_html: bodyHtmlInputRef, body_text: bodyTextInputRef };
const ref = refMap[fieldName];
const currentValue = editEmailData[fieldName] || '';
// InputText/InputTextarea PrimeReact espone l'elemento nativo via .getElement() o .current.element in v10+
// fallback sicuro: appendo in coda
let insertAt = currentValue.length;
let el = null;
try {
el = ref?.current?.getInput ? ref.current.getInput() : (ref?.current?.element || ref?.current);
if (el && typeof el.selectionStart === 'number') {
insertAt = el.selectionStart;
}
} catch (e) {
// ignore, insertAt rimane a length
}
const before = currentValue.slice(0, insertAt);
const after = currentValue.slice(insertAt);
const newValue = before + token + after;
setEditEmailData({ ...editEmailData, [fieldName]: newValue });
// riposiziono focus e cursor dopo il token appena inserito
setTimeout(() => {
try {
if (el && typeof el.setSelectionRange === 'function') {
el.focus();
const pos = insertAt + token.length;
el.setSelectionRange(pos, pos);
}
} catch (e) { /* ignore */ }
}, 0);
};
// ---- load all ----
const loadTemplates = () => {
@@ -655,12 +700,7 @@ const Ar1AdminConfig = () => {
<Card>
<Message severity="info" style={{ marginBottom: 10 }} text="Qui modifichi oggetto e corpo delle PEC inviate dal sistema. Ogni modifica incrementa la versione: il backend Gepafin sincronizza automaticamente i testi nei suoi template per-hub (PEC Massiva / Mailgun)." />
{availableVariables.length > 0 && (
<div style={{ marginBottom: 14, padding: 10, background: '#f5f5f5', borderRadius: 4 }}>
<strong>Variabili disponibili</strong> (usa nel testo come <code>{'{{nome_variabile}}'}</code>):<br />
{availableVariables.map(v => <Tag key={v} value={v} style={{ marginRight: 4, marginTop: 4 }} />)}
</div>
)}
<Message severity="info" style={{ marginBottom: 10 }} text="Clicca su 'Modifica' per editare un testo. Dentro il dialog potrai inserire le variabili con un click." />
<DataTable value={emailTemplates} loading={loadingEmail} emptyMessage="Nessun testo configurato">
<Column field="kind" header="Tipo comunicazione" body={(r) => (
@@ -859,21 +899,61 @@ const Ar1AdminConfig = () => {
>
{editEmailData && (
<div>
<Message severity="info" style={{ marginBottom: 10 }} text="Usa le variabili nei testi con la sintassi {{nome}} (es. {{company_name}}). Il backend Gepafin le sostituira al momento dell'invio." />
<Message severity="info" style={{ marginBottom: 10 }} text="Clicca un campo per attivarlo, poi clicca una variabile qui sotto per inserirla al cursore." />
{availableVariables.length > 0 && (
<div style={{ marginBottom: 14, padding: 10, background: '#eef6ff', border: '1px solid #cfe0f7', borderRadius: 4 }}>
<strong style={{ fontSize: 13 }}>Variabili disponibili </strong>
<small style={{ color: '#555' }}>
(inserite in: <strong>{activeEmailField === 'subject' ? 'Oggetto' : activeEmailField === 'body_html' ? 'Corpo HTML' : 'Corpo testo'}</strong>)
</small>
<div style={{ marginTop: 8, display: 'flex', flexWrap: 'wrap', gap: 6 }}>
{availableVariables.map(v => (
<Tag
key={v}
value={'{{' + v + '}}'}
severity="info"
style={{ cursor: 'pointer', userSelect: 'none' }}
onClick={() => insertVariable(v)}
/>
))}
</div>
</div>
)}
<div style={{ marginBottom: 10 }}>
<label style={{ display: 'block', marginBottom: 4, fontWeight: 500 }}>Oggetto</label>
<InputText value={editEmailData.subject} onChange={(e) => setEditEmailData({ ...editEmailData, subject: e.target.value })} style={{ width: '100%' }} />
<InputText
ref={subjectInputRef}
value={editEmailData.subject}
onChange={(e) => setEditEmailData({ ...editEmailData, subject: e.target.value })}
onFocus={() => setActiveEmailField('subject')}
style={{ width: '100%' }}
/>
</div>
<div style={{ marginBottom: 10 }}>
<label style={{ display: 'block', marginBottom: 4, fontWeight: 500 }}>Corpo HTML</label>
<InputTextarea value={editEmailData.body_html} onChange={(e) => setEditEmailData({ ...editEmailData, body_html: e.target.value })} rows={10} style={{ width: '100%', fontFamily: 'monospace', fontSize: 12 }} />
<InputTextarea
ref={bodyHtmlInputRef}
value={editEmailData.body_html}
onChange={(e) => setEditEmailData({ ...editEmailData, body_html: e.target.value })}
onFocus={() => setActiveEmailField('body_html')}
rows={10}
style={{ width: '100%', fontFamily: 'monospace', fontSize: 12 }}
/>
</div>
<div style={{ marginBottom: 10 }}>
<label style={{ display: 'block', marginBottom: 4, fontWeight: 500 }}>Corpo testo semplice (fallback)</label>
<InputTextarea value={editEmailData.body_text} onChange={(e) => setEditEmailData({ ...editEmailData, body_text: e.target.value })} rows={5} style={{ width: '100%', fontSize: 13 }} />
<InputTextarea
ref={bodyTextInputRef}
value={editEmailData.body_text}
onChange={(e) => setEditEmailData({ ...editEmailData, body_text: e.target.value })}
onFocus={() => setActiveEmailField('body_text')}
rows={5}
style={{ width: '100%', fontSize: 13 }}
/>
<small style={{ color: '#888' }}>Usato dai client email che non supportano HTML.</small>
</div>