diff --git a/src/modules/rendicontazione/components/SchemaTemplatePicker.js b/src/modules/rendicontazione/components/SchemaTemplatePicker.js
new file mode 100644
index 0000000..189a4fb
--- /dev/null
+++ b/src/modules/rendicontazione/components/SchemaTemplatePicker.js
@@ -0,0 +1,198 @@
+import React, { useEffect, useState } from 'react';
+import { __ } from '@wordpress/i18n';
+
+import { Button } from 'primereact/button';
+import { Card } from 'primereact/card';
+import { Dropdown } from 'primereact/dropdown';
+import { Message } from 'primereact/message';
+import { Skeleton } from 'primereact/skeleton';
+
+import { schemaPickerService } from '../service/rendicontazioneService';
+
+/**
+ * SchemaTemplatePicker
+ * Mostrato quando un bando non ha ancora uno schema di rendicontazione.
+ * Offre 3 modalita: schema nuovo vuoto, da template predefinito, clone da altro bando.
+ *
+ * Props:
+ * callId number — bando target
+ * onInitialized fn(schemaData) — chiamato dopo initialize con successo
+ * onError fn(err)
+ */
+const SchemaTemplatePicker = ({ callId, onInitialized, onError }) => {
+ const [templates, setTemplates] = useState([]);
+ const [clonableCalls, setClonableCalls] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [saving, setSaving] = useState(false);
+
+ // Selezione utente
+ const [mode, setMode] = useState(null); // null | 'blank' | 'template' | 'clone'
+ const [pickedTemplateId, setPickedTemplateId] = useState(null);
+ const [pickedSourceCallId, setPickedSourceCallId] = useState(null);
+
+ useEffect(() => {
+ let active = true;
+ setLoading(true);
+ let tpls = null, clones = null;
+ const finish = () => {
+ if (tpls !== null && clones !== null && active) setLoading(false);
+ };
+
+ schemaPickerService.listTemplates(
+ (resp) => { if (active) { tpls = resp?.data?.templates || []; setTemplates(tpls); } finish(); },
+ (err) => { tpls = []; finish(); if (onError) onError(err); }
+ );
+ schemaPickerService.listClonableCalls(
+ (resp) => { if (active) { clones = resp?.data?.calls || []; setClonableCalls(clones); } finish(); },
+ (err) => { clones = []; finish(); }
+ );
+ return () => { active = false; };
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
+
+ const canSubmit = () => {
+ if (!mode) return false;
+ if (mode === 'blank') return true;
+ if (mode === 'template') return !!pickedTemplateId;
+ if (mode === 'clone') return !!pickedSourceCallId;
+ return false;
+ };
+
+ const handleSubmit = () => {
+ const payload = { source: mode };
+ if (mode === 'template') payload.template_id = pickedTemplateId;
+ if (mode === 'clone') payload.source_call_id = pickedSourceCallId;
+
+ setSaving(true);
+ schemaPickerService.initializeSchema(callId, payload,
+ (resp) => { setSaving(false); if (onInitialized) onInitialized(resp?.data); },
+ (err) => { setSaving(false); if (onError) onError(err); }
+ );
+ };
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ const templateOptions = templates
+ .filter(t => t.template_id !== 'blank') // blank ha card dedicata
+ .map(t => ({ label: t.label, value: t.template_id, description: t.description }));
+
+ const cloneOptions = clonableCalls.map(c => ({
+ label: `${c.name} · ${c.schema_status === 'PUBLISHED' ? __('Pubblicato','gepafin') : __('Bozza','gepafin')}`,
+ value: c.call_id,
+ }));
+
+ const cardStyle = (selected) => ({
+ cursor: 'pointer',
+ height: '100%',
+ border: selected ? '2px solid var(--primary-color)' : '2px solid transparent',
+ transition: 'border-color 0.15s'
+ });
+
+ return (
+
+
+
{__('Scegli come iniziare lo schema di rendicontazione','gepafin')}
+
+ {__("Puoi partire da un modello vuoto, usare un template predefinito oppure clonare lo schema di un altro bando. Lo schema creato sarà in bozza e modificabile liberamente.", 'gepafin')}
+
+
+
+
+ {/* Card 1: NUOVO SCHEMA VUOTO */}
+
+
{__('Nuovo schema','gepafin')}>}
+ style={cardStyle(mode === 'blank')}
+ onClick={() => setMode('blank')}
+ >
+
+ {__('Parti da zero con sezioni vuote. Configurerai categorie di spesa, documenti richiesti e controlli aggiuntivi secondo le esigenze del bando.','gepafin')}
+
+
+ {__('Consigliato se il bando è nuovo e non somiglia a bandi precedenti.','gepafin')}
+
+
+
+
+ {/* Card 2: DA TEMPLATE */}
+
+
{__('Da template','gepafin')}>}
+ style={cardStyle(mode === 'template')}
+ onClick={() => setMode('template')}
+ >
+
+ {__('Parti da un template predefinito che replica schemi di bandi noti (es. RE-START). Potrai comunque modificare tutto.','gepafin')}
+
+ {mode === 'template' && (
+ e.stopPropagation()}>
+ setPickedTemplateId(e.value)}
+ options={templateOptions}
+ placeholder={__('Scegli un template...','gepafin')}
+ style={{ width: '100%' }}
+ />
+ {pickedTemplateId && (
+
+ {templateOptions.find(t => t.value === pickedTemplateId)?.description}
+
+ )}
+
+ )}
+
+
+
+ {/* Card 3: CLONE */}
+
+
{__('Clona da bando','gepafin')}>}
+ style={cardStyle(mode === 'clone')}
+ onClick={() => setMode('clone')}
+ >
+
+ {__('Copia lo schema di un altro bando (in bozza o pubblicato). Utile se il nuovo bando è molto simile a uno esistente.','gepafin')}
+
+ {mode === 'clone' && (
+ e.stopPropagation()}>
+ {cloneOptions.length === 0 ? (
+
+ ) : (
+ setPickedSourceCallId(e.value)}
+ options={cloneOptions}
+ placeholder={__('Scegli un bando sorgente...','gepafin')}
+ style={{ width: '100%' }}
+ />
+ )}
+
+ )}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default SchemaTemplatePicker;
diff --git a/src/modules/rendicontazione/pages/BandoRendicontazioneSchemaEdit.js b/src/modules/rendicontazione/pages/BandoRendicontazioneSchemaEdit.js
index 98e1589..1b9ed43 100644
--- a/src/modules/rendicontazione/pages/BandoRendicontazioneSchemaEdit.js
+++ b/src/modules/rendicontazione/pages/BandoRendicontazioneSchemaEdit.js
@@ -22,6 +22,7 @@ import BlockingOverlay from '../../../components/BlockingOverlay';
// api
import RendicontazioneService from '../service/rendicontazioneService';
+import SchemaTemplatePicker from '../components/SchemaTemplatePicker';
import BandoService from '../../../service/bando-service';
// ---------- costanti ----------
@@ -343,15 +344,25 @@ const BandoRendicontazioneSchemaEdit = () => {
)}
{!schemaLoading && !hasSchema && (
-
-
-
{__('Nessuno schema di rendicontazione per questo bando','gepafin')}
-
- {__('Puoi inizializzarlo con un template predefinito. Per ora è disponibile il template RE-START (fondo prestiti con remissione del debito).','gepafin')}
-
-
+
+ {
+ setSchemaRecord(data);
+ setForm(schemaJsonToForm(data.schema_json));
+ setDirty(false);
+ toast.current?.show({
+ severity: 'success',
+ summary: __('Schema inizializzato', 'gepafin'),
+ detail: __('Puoi ora configurare le sezioni e salvare come bozza.', 'gepafin')
+ });
+ }}
+ onError={(err) => toast.current?.show({
+ severity: 'error',
+ summary: __('Inizializzazione fallita', 'gepafin'),
+ detail: err?.detail || err?.message
+ })}
+ />
)}
diff --git a/src/modules/rendicontazione/service/rendicontazioneService.js b/src/modules/rendicontazione/service/rendicontazioneService.js
index 782b415..115e426 100644
--- a/src/modules/rendicontazione/service/rendicontazioneService.js
+++ b/src/modules/rendicontazione/service/rendicontazioneService.js
@@ -88,6 +88,39 @@ const RendicontazioneService = {
export default RendicontazioneService;
+// =========================================================================
+// v2.1 — Picker schema (blank / template / clone)
+// =========================================================================
+
+export const schemaPickerService = {
+ listTemplates(onSuccess, onError) {
+ fetch(`${BASE_URL}/api/rendicontazione-schemas/templates`, {
+ method: 'GET', mode: 'cors', headers: buildHeaders()
+ })
+ .then(r => handleResponse(r, onSuccess, onError))
+ .catch(e => handleError(e, onError));
+ },
+
+ listClonableCalls(onSuccess, onError) {
+ fetch(`${BASE_URL}/api/rendicontazione-schemas/clonable-calls`, {
+ method: 'GET', mode: 'cors', headers: buildHeaders()
+ })
+ .then(r => handleResponse(r, onSuccess, onError))
+ .catch(e => handleError(e, onError));
+ },
+
+ initializeSchema(callId, payload, onSuccess, onError) {
+ // payload = { source: "blank"|"template"|"clone", template_id?, source_call_id? }
+ fetch(`${BASE_URL}/api/rendicontazione-schemas/${callId}/initialize`, {
+ method: 'POST', mode: 'cors', headers: buildHeaders(),
+ body: JSON.stringify(payload)
+ })
+ .then(r => handleResponse(r, onSuccess, onError))
+ .catch(e => handleError(e, onError));
+ }
+};
+
+
// ====================== PRATICHE BENEFICIARIO ======================
const extendPractice = {