diff --git a/src/layouts/DefaultLayout/components/AppSidebar/index.js b/src/layouts/DefaultLayout/components/AppSidebar/index.js
index efdd7c7..89b5903 100644
--- a/src/layouts/DefaultLayout/components/AppSidebar/index.js
+++ b/src/layouts/DefaultLayout/components/AppSidebar/index.js
@@ -158,7 +158,8 @@ const AppSidebar = () => {
'ROOT_MANAGE_APPL_VIEW_DELETED',
'ROOT_MANAGE_APPL_DELETE_CONFIRM',
'ROOT_MANAGE_APPL_DELETE',
- 'ROOT_MANAGE_PEC_SEND'
+ 'ROOT_MANAGE_PEC_SEND',
+ 'ROOT_MANAGE_VIEW_LOG'
]).length
},
{
diff --git a/src/pages/Admin/components/ManageSendPecSection/index.js b/src/pages/Admin/components/ManageSendPecSection/index.js
new file mode 100644
index 0000000..1135369
--- /dev/null
+++ b/src/pages/Admin/components/ManageSendPecSection/index.js
@@ -0,0 +1,121 @@
+import React, { useState, useRef, useCallback } from 'react';
+import { __ } from '@wordpress/i18n';
+
+// api
+import AdminService from '../../../../service/admin-service';
+
+// components
+import { InputText } from 'primereact/inputtext';
+import { InputTextarea } from 'primereact/inputtextarea';
+import { Button } from 'primereact/button';
+import { Toast } from 'primereact/toast';
+
+const ManageSendPecSection = () => {
+ const [subject, setSubject] = useState('');
+ const [body, setBody] = useState('');
+ const [csvFile, setCsvFile] = useState(null);
+ const [attachment, setAttachment] = useState(null);
+ const [localAsyncRequest, setLocalAsyncRequest] = useState(false);
+ const toast = useRef(null);
+ const csvInputRef = useRef(null);
+ const attachmentInputRef = useRef(null);
+
+ const isSubmitDisabled = localAsyncRequest || !subject.trim() || !body.trim() || !csvFile;
+
+ const handleSubmit = useCallback(() => {
+ const formData = new FormData();
+ formData.append('subject', subject);
+ formData.append('body', body);
+ formData.append('csv_file', csvFile);
+ if (attachment) {
+ formData.append('attachment', attachment);
+ }
+
+ setLocalAsyncRequest(true);
+ AdminService.doSendPec(
+ formData,
+ (resp) => {
+ setLocalAsyncRequest(false);
+ if (resp && resp.status === 'ok') {
+ const detail = resp.data
+ ? __(`Invio completato: ${resp.data.total_sent} inviati, ${resp.data.total_errors} errori`, 'gepafin')
+ : __('Invio completato con successo', 'gepafin');
+ if (toast.current) {
+ toast.current.show({ severity: 'success', summary: '', detail });
+ }
+ setSubject('');
+ setBody('');
+ setCsvFile(null);
+ setAttachment(null);
+ if (csvInputRef.current) csvInputRef.current.value = '';
+ if (attachmentInputRef.current) attachmentInputRef.current.value = '';
+ } else {
+ if (toast.current) {
+ toast.current.show({ severity: 'error', summary: '', detail: resp && resp.detail ? resp.detail : __('Errore durante l\'invio', 'gepafin') });
+ }
+ }
+ },
+ (resp) => {
+ setLocalAsyncRequest(false);
+ if (toast.current) {
+ toast.current.show({ severity: 'error', summary: '', detail: resp && resp.detail ? resp.detail : __('Errore durante l\'invio', 'gepafin') });
+ }
+ }
+ );
+ }, [subject, body, csvFile, attachment]);
+
+ return (
+
+
{__('Invio PEC Massivo', 'gepafin')}
+
+
+
+
+ setSubject(e.target.value)}
+ style={{ width: '100%' }}/>
+
+
+
+ setBody(e.target.value)}
+ rows={8}
+ style={{ width: '100%' }}/>
+
+
+
+ setCsvFile(e.target.files[0] || null)}/>
+ {__('Il file CSV deve contenere gli indirizzi PEC nella prima colonna', 'gepafin')}
+
+
+
+ setAttachment(e.target.files[0] || null)}/>
+
+
+
+
+
+ );
+};
+
+export default ManageSendPecSection;
diff --git a/src/pages/Admin/index.js b/src/pages/Admin/index.js
index 9aac328..ff872e8 100644
--- a/src/pages/Admin/index.js
+++ b/src/pages/Admin/index.js
@@ -1,46 +1,88 @@
import React from 'react';
import { __ } from '@wordpress/i18n';
import { intersection } from 'ramda';
+import { useNavigate } from 'react-router-dom';
// store
import { useStoreValue } from '../../store';
// components
-import ManageNdgSection from './components/ManageNdgSection';
-import ManageApplStatusSection from './components/ManageApplStatusSection';
-import ManageAmendmentReopenSection from './components/ManageAmendmentReopenSection';
-import ManageAmendmentExtendSection from './components/ManageAmendmentExtendSection';
-import ManageApplDeleteSection from './components/ManageApplDeleteSection';
+import { Card } from 'primereact/card';
+
+const navItems = [
+ {
+ permission: ['ROOT_MANAGE_NDG'],
+ route: '/admin/set-ndg',
+ label: __('Gestione NDG', 'gepafin'),
+ subtitle: __('Imposta il codice NDG per le domande', 'gepafin'),
+ icon: 'pi pi-id-card'
+ },
+ {
+ permission: ['ROOT_MANAGE_APPL_STATUS'],
+ route: '/admin/set-status',
+ label: __('Gestione Stato Domanda', 'gepafin'),
+ subtitle: __('Modifica lo stato delle domande', 'gepafin'),
+ icon: 'pi pi-list'
+ },
+ {
+ permission: ['ROOT_MANAGE_AMENDMENT_REOPEN'],
+ route: '/admin/amendment-reopen',
+ label: __('Riapri Integrazione', 'gepafin'),
+ subtitle: __('Riapri un\'integrazione chiusa', 'gepafin'),
+ icon: 'pi pi-refresh'
+ },
+ {
+ permission: ['ROOT_MANAGE_AMENDMENT_EXTEND'],
+ route: '/admin/amendment-extend',
+ label: __('Estendi Integrazione', 'gepafin'),
+ subtitle: __('Estendi la scadenza di un\'integrazione', 'gepafin'),
+ icon: 'pi pi-calendar-plus'
+ },
+ {
+ permission: ['ROOT_MANAGE_APPL_VIEW_DELETED', 'ROOT_MANAGE_APPL_DELETE_CONFIRM', 'ROOT_MANAGE_APPL_DELETE'],
+ route: '/admin/elimina-domande',
+ label: __('Elimina Domande', 'gepafin'),
+ subtitle: __('Gestione eliminazione domande', 'gepafin'),
+ icon: 'pi pi-trash'
+ },
+ {
+ permission: ['ROOT_MANAGE_VIEW_LOG'],
+ route: '/admin/log',
+ label: __('Log Operazioni', 'gepafin'),
+ subtitle: __('Storico delle operazioni amministrative', 'gepafin'),
+ icon: 'pi pi-history'
+ },
+ {
+ permission: ['ROOT_MANAGE_PEC_SEND'],
+ route: '/admin/send-pec',
+ label: __('Invio PEC Massivo', 'gepafin'),
+ subtitle: __('Invia PEC a più destinatari tramite file CSV', 'gepafin'),
+ icon: 'pi pi-envelope'
+ }
+];
const Admin = () => {
const permissions = useStoreValue('getPermissions');
+ const navigate = useNavigate();
- const hasNdg = intersection(permissions, ['ROOT_MANAGE_NDG']).length > 0;
- const hasApplStatus = intersection(permissions, ['ROOT_MANAGE_APPL_STATUS']).length > 0;
- const hasAmendmentReopen = intersection(permissions, ['ROOT_MANAGE_AMENDMENT_REOPEN']).length > 0;
- const hasAmendmentExtend = intersection(permissions, ['ROOT_MANAGE_AMENDMENT_EXTEND']).length > 0;
- const hasApplDelete = intersection(permissions, [
- 'ROOT_MANAGE_APPL_VIEW_DELETED',
- 'ROOT_MANAGE_APPL_DELETE_CONFIRM',
- 'ROOT_MANAGE_APPL_DELETE'
- ]).length > 0;
-
- const canViewDeleted = intersection(permissions, ['ROOT_MANAGE_APPL_VIEW_DELETED']).length > 0;
- const canDeleteConfirm = intersection(permissions, ['ROOT_MANAGE_APPL_DELETE_CONFIRM']).length > 0;
- const canDelete = intersection(permissions, ['ROOT_MANAGE_APPL_DELETE']).length > 0;
+ const visibleItems = navItems.filter(
+ (item) => intersection(permissions, item.permission).length > 0
+ );
return
{__('Admin', 'gepafin')}
- {hasNdg && <>
>}
- {hasApplStatus && <>
>}
- {hasAmendmentReopen && <>
>}
- {hasAmendmentExtend && <>
>}
- {hasApplDelete && <>
>}
+
+ {visibleItems.map((item) => (
+ {item.label}}
+ subTitle={item.subtitle}
+ style={{ width: '280px', cursor: 'pointer', padding: '0.5rem' }}
+ onClick={() => navigate(item.route)}
+ />
+ ))}
+
}
diff --git a/src/pages/AdminAmendmentExtend/index.js b/src/pages/AdminAmendmentExtend/index.js
new file mode 100644
index 0000000..ac82607
--- /dev/null
+++ b/src/pages/AdminAmendmentExtend/index.js
@@ -0,0 +1,35 @@
+import React, { useEffect } from 'react';
+import { __ } from '@wordpress/i18n';
+import { intersection } from 'ramda';
+import { useNavigate } from 'react-router-dom';
+
+// store
+import { useStoreValue } from '../../store';
+
+// components
+import { Button } from 'primereact/button';
+import ManageAmendmentExtendSection from '../Admin/components/ManageAmendmentExtendSection';
+
+const AdminAmendmentExtend = () => {
+ const permissions = useStoreValue('getPermissions');
+ const navigate = useNavigate();
+ const hasPermission = intersection(permissions, ['ROOT_MANAGE_AMENDMENT_EXTEND']).length > 0;
+
+ useEffect(() => {
+ if (!hasPermission) navigate('/admin');
+ }, [hasPermission]);
+
+ return
+
+
{__('Estendi Integrazione', 'gepafin')}
+
+
+
+
+
+ {hasPermission &&
}
+
+}
+
+export default AdminAmendmentExtend;
diff --git a/src/pages/AdminAmendmentReopen/index.js b/src/pages/AdminAmendmentReopen/index.js
new file mode 100644
index 0000000..e2a3206
--- /dev/null
+++ b/src/pages/AdminAmendmentReopen/index.js
@@ -0,0 +1,35 @@
+import React, { useEffect } from 'react';
+import { __ } from '@wordpress/i18n';
+import { intersection } from 'ramda';
+import { useNavigate } from 'react-router-dom';
+
+// store
+import { useStoreValue } from '../../store';
+
+// components
+import { Button } from 'primereact/button';
+import ManageAmendmentReopenSection from '../Admin/components/ManageAmendmentReopenSection';
+
+const AdminAmendmentReopen = () => {
+ const permissions = useStoreValue('getPermissions');
+ const navigate = useNavigate();
+ const hasPermission = intersection(permissions, ['ROOT_MANAGE_AMENDMENT_REOPEN']).length > 0;
+
+ useEffect(() => {
+ if (!hasPermission) navigate('/admin');
+ }, [hasPermission]);
+
+ return
+
+
{__('Riapri Integrazione', 'gepafin')}
+
+
+
+
+
+ {hasPermission &&
}
+
+}
+
+export default AdminAmendmentReopen;
diff --git a/src/pages/AdminEliminaDomande/index.js b/src/pages/AdminEliminaDomande/index.js
new file mode 100644
index 0000000..7aee2ae
--- /dev/null
+++ b/src/pages/AdminEliminaDomande/index.js
@@ -0,0 +1,48 @@
+import React, { useEffect } from 'react';
+import { __ } from '@wordpress/i18n';
+import { intersection } from 'ramda';
+import { useNavigate } from 'react-router-dom';
+
+// store
+import { useStoreValue } from '../../store';
+
+// components
+import { Button } from 'primereact/button';
+import ManageApplDeleteSection from '../Admin/components/ManageApplDeleteSection';
+
+const DELETE_PERMISSIONS = [
+ 'ROOT_MANAGE_APPL_VIEW_DELETED',
+ 'ROOT_MANAGE_APPL_DELETE_CONFIRM',
+ 'ROOT_MANAGE_APPL_DELETE'
+];
+
+const AdminEliminaDomande = () => {
+ const permissions = useStoreValue('getPermissions');
+ const navigate = useNavigate();
+ const hasPermission = intersection(permissions, DELETE_PERMISSIONS).length > 0;
+ const canViewDeleted = intersection(permissions, ['ROOT_MANAGE_APPL_VIEW_DELETED']).length > 0;
+ const canDeleteConfirm = intersection(permissions, ['ROOT_MANAGE_APPL_DELETE_CONFIRM']).length > 0;
+ const canDelete = intersection(permissions, ['ROOT_MANAGE_APPL_DELETE']).length > 0;
+
+ useEffect(() => {
+ if (!hasPermission) navigate('/admin');
+ }, [hasPermission]);
+
+ return
+
+
{__('Elimina Domande', 'gepafin')}
+
+
+
+
+
+ {hasPermission &&
}
+
+}
+
+export default AdminEliminaDomande;
diff --git a/src/pages/AdminLog/index.js b/src/pages/AdminLog/index.js
new file mode 100644
index 0000000..69bae14
--- /dev/null
+++ b/src/pages/AdminLog/index.js
@@ -0,0 +1,244 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { __ } from '@wordpress/i18n';
+import { intersection } from 'ramda';
+import { useNavigate } from 'react-router-dom';
+
+// store
+import { useStoreValue } from '../../store';
+
+// api
+import AdminService from '../../service/admin-service';
+
+// components
+import { Button } from 'primereact/button';
+import { InputText } from 'primereact/inputtext';
+import { Calendar } from 'primereact/calendar';
+import { DataTable } from 'primereact/datatable';
+import { Column } from 'primereact/column';
+import { Dialog } from 'primereact/dialog';
+import { Toast } from 'primereact/toast';
+
+const buildDiffRows = (oldData, newData) => {
+ const keys = Array.from(new Set([
+ ...Object.keys(oldData || {}),
+ ...Object.keys(newData || {})
+ ]));
+ return keys.map((key) => ({
+ key,
+ old: oldData ? String(oldData[key] ?? '') : '',
+ new: newData ? String(newData[key] ?? '') : ''
+ }));
+};
+
+const AdminLog = () => {
+ const permissions = useStoreValue('getPermissions');
+ const navigate = useNavigate();
+ const toast = useRef(null);
+ const hasPermission = intersection(permissions, ['ROOT_MANAGE_VIEW_LOG']).length > 0;
+
+ const [callId, setCallId] = useState('');
+ const [userId, setUserId] = useState('');
+ const [dateFrom, setDateFrom] = useState(null);
+ const [dateTo, setDateTo] = useState(null);
+ const [items, setItems] = useState(null);
+ const [totalRecordsNum, setTotalRecordsNum] = useState(0);
+ const [loading, setLoading] = useState(false);
+ const [lazyState, setLazyState] = useState({ first: 0, rows: 10, page: 0 });
+
+ const [detailRow, setDetailRow] = useState(null);
+
+ useEffect(() => {
+ if (!hasPermission) navigate('/admin');
+ }, [hasPermission]);
+
+ const formatDate = (date) => {
+ if (!date) return undefined;
+ const d = new Date(date);
+ const year = d.getFullYear();
+ const month = String(d.getMonth() + 1).padStart(2, '0');
+ const day = String(d.getDate()).padStart(2, '0');
+ return `${year}-${month}-${day}`;
+ };
+
+ const validate = () => {
+ if ((dateFrom && !dateTo) || (!dateFrom && dateTo)) {
+ toast.current.show({
+ severity: 'warn',
+ summary: __('Attenzione', 'gepafin'),
+ detail: __('Inserire sia la data di inizio che la data di fine.', 'gepafin')
+ });
+ return false;
+ }
+ if (dateFrom && dateTo && dateFrom > dateTo) {
+ toast.current.show({
+ severity: 'warn',
+ summary: __('Attenzione', 'gepafin'),
+ detail: __('La data di inizio non può essere superiore alla data di fine.', 'gepafin')
+ });
+ return false;
+ }
+ return true;
+ };
+
+ const fetchData = (page = 0, rows = lazyState.rows) => {
+ if (!validate()) return;
+ setLoading(true);
+
+ const queryParams = [
+ ['page', page + 1],
+ ['limit', rows]
+ ];
+ if (callId) queryParams.push(['call_id', callId]);
+ if (userId) queryParams.push(['user_id', userId]);
+ if (dateFrom) queryParams.push(['date_from', formatDate(dateFrom)]);
+ if (dateTo) queryParams.push(['date_to', formatDate(dateTo)]);
+
+ AdminService.getAdminLog(
+ (resp) => {
+ if (resp && resp.status === 'ok') {
+ setItems(resp.records || []);
+ setTotalRecordsNum(resp.totalRecords || 0);
+ }
+ setLoading(false);
+ },
+ () => { setLoading(false); },
+ queryParams
+ );
+ };
+
+ const onPage = (event) => {
+ setLazyState(event);
+ fetchData(event.page, event.rows);
+ };
+
+ const azioniTemplate = (row) => (
+