feat(rendicontazione): editor schema con form strutturato + dashboard + integrazione microservizio

- Aggiunta voce 'Rendicontazione' in AppSidebar (id 21, icon pi-receipt)
- Nuova pagina RendicontazioneHome: dashboard con tabella bandi + stato schema
  (Non creato / Bozza / Pubblicato) + azioni Crea/Modifica per ciascuno
- Nuova pagina BandoRendicontazioneSchemaEdit: form strutturato 6 sezioni
  (importi/periodo, IVA, categorie, ULA, documenti, regole gate) con
  salva bozza + pubblica, read-only dopo pubblicazione
- Nuovo service modules/rendicontazione/service/rendicontazioneService.js
  (client fetch verso rendicontazione-api, JWT dallo store Zustand)
- 2 nuove route /rendicontazione e /bandi/:id/rendicontazione-schema
  (gate su ROLE_SUPER_ADMIN)
- Bottone 'Schema rendicontazione' aggiunto in BandoEdit come shortcut
- Patch NotificationsSidebar per disabilitare WSS se REACT_APP_ENABLE_WEBSOCKET=0
  (evita errori CORS in sandbox senza RabbitMQ)

UI coerente col codebase: appPage/appPageSection/appForm/appForm__cols/
fieldsRepeater, p-fluid per width input, h1+p in header con border-left
This commit is contained in:
BFLOWS Sandbox
2026-04-18 09:37:08 +02:00
parent 563b6190ab
commit 8888e0326d
7 changed files with 870 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
/**
* Client HTTP per rendicontazione-api (microservizio BFLOWS).
* Usa fetch nativa come NetworkService. Il microservizio valida lo stesso JWT di GEPAFIN-BE.
*
* Env var: REACT_APP_RENDICONTAZIONE_API_URL (es. http://78.46.41.91:18090)
*/
import { storeGet } from '../../../store';
const BASE_URL = process.env.REACT_APP_RENDICONTAZIONE_API_URL || '';
const buildHeaders = () => {
const token = storeGet('getToken');
const h = { 'Content-Type': 'application/json' };
if (token) {
h['Authorization'] = `Bearer ${token}`;
}
return h;
};
const handleResponse = async (response, onSuccess, onError) => {
let body = null;
try {
body = await response.json();
} catch (e) {
body = { detail: response.statusText };
}
if (response.status >= 200 && response.status < 300) {
if (onSuccess) onSuccess(body);
} else {
if (onError) onError({ status: response.status, ...body });
}
};
const handleError = (err, onError) => {
if (onError) onError({ status: 0, detail: err.message });
};
const RendicontazioneService = {
getSchemaByCallId(callId, onSuccess, onError) {
fetch(`${BASE_URL}/api/rendicontazione-schemas/${callId}`, {
method: 'GET', mode: 'cors', headers: buildHeaders()
})
.then(r => handleResponse(r, onSuccess, onError))
.catch(e => handleError(e, onError));
},
initializeRestartTemplate(callId, onSuccess, onError) {
fetch(`${BASE_URL}/api/rendicontazione-schemas/${callId}/initialize-restart`, {
method: 'POST', mode: 'cors', headers: buildHeaders()
})
.then(r => handleResponse(r, onSuccess, onError))
.catch(e => handleError(e, onError));
},
updateSchema(callId, schemaJson, onSuccess, onError) {
fetch(`${BASE_URL}/api/rendicontazione-schemas/${callId}`, {
method: 'PUT', mode: 'cors', headers: buildHeaders(),
body: JSON.stringify({ schema_json: schemaJson })
})
.then(r => handleResponse(r, onSuccess, onError))
.catch(e => handleError(e, onError));
},
publishSchema(callId, onSuccess, onError) {
fetch(`${BASE_URL}/api/rendicontazione-schemas/${callId}/publish`, {
method: 'POST', mode: 'cors', headers: buildHeaders()
})
.then(r => handleResponse(r, onSuccess, onError))
.catch(e => handleError(e, onError));
},
deleteSchema(callId, onSuccess, onError) {
fetch(`${BASE_URL}/api/rendicontazione-schemas/${callId}`, {
method: 'DELETE', mode: 'cors', headers: buildHeaders()
})
.then(r => handleResponse(r, onSuccess, onError))
.catch(e => handleError(e, onError));
},
getRestartTemplatePreview(onSuccess, onError) {
fetch(`${BASE_URL}/api/rendicontazione-schemas/templates/restart`, {
method: 'GET', mode: 'cors', headers: buildHeaders()
})
.then(r => handleResponse(r, onSuccess, onError))
.catch(e => handleError(e, onError));
}
};
export default RendicontazioneService;