- saving changes;

This commit is contained in:
Vitalii Kiiko
2025-02-27 17:31:50 +01:00
parent ba7946cd3e
commit 186291b52b
9 changed files with 517 additions and 14 deletions

View File

@@ -4,7 +4,8 @@ import Routes from './routes';
import { createI18n, setLocaleData } from '@wordpress/i18n';
import { I18nProvider } from '@wordpress/react-i18n';
import './assets/scss/theme.scss';
import { isEmpty, head } from 'ramda'
import { isEmpty, head } from 'ramda';
import { addLocale, PrimeReactProvider } from 'primereact/api';
// store
import { useStore, storeSet, storeGet } from './store';
@@ -18,6 +19,9 @@ function App() {
const role = useStore().main.getRole();
const chosenCompanyId = useStore().main.chosenCompanyId();
const isRedirectedOnceNoCompany = useStore().main.isRedirectedOnceNoCompany();
const value = {
locale: 'it',
};
const callback = (data) => {
if (data.status === 'SUCCESS') {
@@ -59,6 +63,51 @@ function App() {
storeSet.main.setAsyncRequest();
AuthenticationService.me(callback, errCallback);
addLocale('it', {
startsWith: 'Inizia con',
contains: 'Contiene',
notContains: 'Non contiene',
endsWith: 'Finisce con',
equals: 'Uguale a',
notEquals: 'Diverso da',
noFilter: 'Nessun filtro',
lt: 'Minore di',
lte: 'Minore o uguale a',
gt: 'Maggiore di',
gte: 'Maggiore o uguale a',
dateIs: 'Data uguale a',
dateIsNot: 'Data diversa da',
dateBefore: 'Data prima di',
dateAfter: 'Data dopo',
custom: 'Personalizzato',
clear: 'Cancella',
apply: 'Applica',
matchAll: 'Tutte le condizioni',
matchAny: 'Qualsiasi condizione',
addRule: 'Aggiungi regola',
removeRule: 'Rimuovi regola',
accept: 'Sì',
reject: 'No',
choose: 'Scegli',
upload: 'Carica',
cancel: 'Annulla',
dayNames: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'],
dayNamesShort: ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab'],
dayNamesMin: ['D', 'L', 'M', 'M', 'G', 'V', 'S'],
monthNames: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
monthNamesShort: ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'],
today: 'Oggi',
weekHeader: 'Sm',
firstDayOfWeek: 1,
dateFormat: 'dd/mm/yy',
weak: 'Debole',
medium: 'Medio',
strong: 'Forte',
passwordPrompt: 'Inserisci una password',
emptyMessage: 'Nessun risultato trovato',
emptyFilterMessage: 'Nessun risultato trovato'
});
fetch('/languages/en_US.json')
.then((res) => res.json())
.then(res => {
@@ -69,7 +118,9 @@ function App() {
return (
<I18nProvider i18n={i18n}>
<BrowserRouter>
<Routes role={role} chosenCompanyId={chosenCompanyId}/>
<PrimeReactProvider value={value}>
<Routes role={role} chosenCompanyId={chosenCompanyId}/>
</PrimeReactProvider>
</BrowserRouter>
</I18nProvider>
);

View File

@@ -113,6 +113,10 @@
font-weight: 600;
line-height: normal;
margin: 0 0 24px;
display: flex;
gap: 1rem;
flex-wrap: wrap;
align-items: center;
}
h3 {

View File

@@ -14,6 +14,9 @@ const getBandoLabel = (status) => {
case 'APPROVED':
return __('Approvato', 'gepafin');
case 'VALID':
return __('Valido', 'gepafin');
case 'READY_TO_PUBLISH':
return __('Pronto', 'gepafin');
@@ -29,6 +32,9 @@ const getBandoLabel = (status) => {
case 'ADMISSIBLE':
return __('Ammisibile', 'gepafin');
case 'DUE':
return __('In scadenza', 'gepafin');
case 'RESPONSE_RECEIVED':
return __('Riposta ricevuta', 'gepafin');

View File

@@ -12,6 +12,9 @@ const getBandoSeverity = (status) => {
case 'APPROVED':
return 'success';
case 'VALID':
return 'success';
case 'READY_TO_PUBLISH':
return 'info';
@@ -27,6 +30,9 @@ const getBandoSeverity = (status) => {
case 'ADMISSIBLE':
return 'info';
case 'DUE':
return 'warning';
case 'RESPONSE_RECEIVED':
return 'warning';

View File

@@ -0,0 +1,221 @@
import React, { useState, useEffect } from 'react';
import { __ } from '@wordpress/i18n';
import { is, uniq } from 'ramda';
import copy from 'copy-to-clipboard';
import { Link } from 'react-router-dom';
// store
import { useStore } from '../../../../store';
// tools
import getBandoSeverity from '../../../../helpers/getBandoSeverity';
import getBandoLabel from '../../../../helpers/getBandoLabel';
import getDateFromISOstring from '../../../../helpers/getDateFromISOstring';
// api
import CompanyDocumentsService from '../../../../service/company-documents-service';
// components
import { FilterMatchMode, FilterOperator } from 'primereact/api';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { Dropdown } from 'primereact/dropdown';
import { Button } from 'primereact/button';
import { Calendar } from 'primereact/calendar';
import { Tag } from 'primereact/tag';
import ProperBandoLabel from '../../../../components/ProperBandoLabel';
import translationStrings from '../../../../translationStringsForComponents';
import { confirmPopup, ConfirmPopup } from 'primereact/confirmpopup';
const DocumentsTable = ({ type, reload = 0 }) => {
const chosenCompanyId = useStore().main.chosenCompanyId();
const [docs, setDocs] = useState([]);
const [filters, setFilters] = useState(null);
const [loading, setLoading] = useState(false);
const [statuses, setStatuses] = useState([]);
useEffect(() => {
if (!loading && chosenCompanyId && chosenCompanyId !== 0 && reload !== 0) {
setLoading(true);
CompanyDocumentsService.getCompanyDocuments(chosenCompanyId, getCallback, errGetCallbacks, [
['documentType', type]
]);
}
}, [chosenCompanyId, reload]);
useEffect(() => {
setLoading(true);
CompanyDocumentsService.getCompanyDocuments(chosenCompanyId, getCallback, errGetCallbacks, [
['documentType', type]
]);
}, []);
const getCallback = (resp) => {
if (resp.status === 'SUCCESS') {
setDocs(getFormattedData(resp.data));
setStatuses(uniq(resp.data.map(o => o.status)))
initFilters();
}
setLoading(false);
}
const errGetCallbacks = () => {
setLoading(false);
}
const getFormattedData = (data) => {
return data.map((d) => {
d.callEndDate = is(String, d.callEndDate) ? new Date(d.callEndDate) : (d.callEndDate ? d.callEndDate : '');
return d;
});
};
const clearFilter = () => {
initFilters();
};
const initFilters = () => {
setFilters({
name: {
operator: FilterOperator.AND,
constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }]
},
createdDate: {
operator: FilterOperator.AND,
constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }]
},
expirationDate: {
operator: FilterOperator.AND,
constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }]
}
});
};
const renderHeader = () => {
return (
<div className="appTableHeader">
<Button type="button" icon="pi pi-filter-slash" label={__('Pulisci', 'gepafin')} outlined
onClick={clearFilter}/>
</div>
);
};
const dateCreatedBodyTemplate = (rowData) => {
return getDateFromISOstring(rowData.createdDate);
};
const dateExpirationBodyTemplate = (rowData) => {
return getDateFromISOstring(rowData.expirationDate);
};
const dateFilterTemplate = (options) => {
return <Calendar value={options.value} onChange={(e) => options.filterCallback(e.value, options.index)}
dateFormat="mm/dd/yy" placeholder="mm/dd/yyyy" mask="99/99/9999"/>;
};
const catBodyTemplate = (rowData) => {
return rowData.category.categoryName;
};
const statusBodyTemplate = (rowData) => {
return <ProperBandoLabel status={rowData.status}/>;
};
const statusFilterTemplate = (options) => {
return <Dropdown value={options.value} options={statuses}
onChange={(e) => options.filterCallback(e.value, options.index)}
itemTemplate={statusItemTemplate} placeholder={__('Scegli uno', 'gepafin')}
className="p-column-filter"
showClear/>;
};
const statusItemTemplate = (option) => {
return <Tag value={getBandoLabel(option)} severity={getBandoSeverity(option)}/>;
};
const handleDeleteFile = (id) => {
setLoading(true);
CompanyDocumentsService.deleteCompanyDocument(id,(resp) => deleteCallback(resp, id), errDeleteCallback)
}
const deleteCallback = (resp, deletedId) => {
if (resp.status === 'SUCCESS') {
setDocs(docs.filter(o => o.id !== deletedId));
}
setLoading(false);
}
const errDeleteCallback = () => {
setLoading(false);
}
const actionsBodyTemplate = (rowData) => {
return <div className="appPageSection__iconActions">
<Button icon="pi pi-eye" rounded
onClick={() => {
window.open(rowData.filePath, '_blank').focus()
}}
outlined severity="info"
aria-label={__('Mostra', 'gepafin')}/>
{/*<Button icon="pi pi-sync" rounded
onClick={() => {}}
outlined severity="success"
aria-label={__('Aggiorna', 'gepafin')}/>*/}
<ConfirmPopup/>
<Button icon="pi pi-trash" rounded
onClick={(event) => confirmDelete(event, rowData.id)}
outlined severity="danger"
aria-label={__('Cancella', 'gepafin')}/>
</div>
}
const confirmDelete = (event, id) => {
confirmPopup({
target: event.currentTarget,
message: __('Sei sicuro di voler rimuovere il file?', 'gepafin'),
acceptLabel: __('Si', 'gepafin'),
icon: 'pi pi-info-circle',
defaultFocus: 'reject',
acceptClassName: 'p-button-danger',
accept: () => {
handleDeleteFile(id);
},
reject: () => {
}
});
};
const header = renderHeader();
return (
<div className="appPageSection__table">
<DataTable value={docs} paginator showGridlines rows={5} loading={loading} dataKey="id"
filters={filters} stripedRows removableSort
header={header}
onFilter={(e) => setFilters(e.filters)}>
<Column field="name"
header={__('Nome documento', 'gepafin')}
filter filterField="name"
filterPlaceholder={__('Cerca per nome', 'gepafin')}
style={{ minWidth: '12rem' }}/>
<Column body={catBodyTemplate} header={__('Categoria', 'gepafin')}
style={{ minWidth: '8rem' }}/>
<Column header={__('Data caricamento', 'gepafin')}
filterField="createdDate" dataType="date"
style={{ minWidth: '7rem' }}
body={dateCreatedBodyTemplate} filter filterElement={dateFilterTemplate}/>
<Column header={__('Data scadenza', 'gepafin')}
filterField="expirationDate" dataType="date"
style={{ minWidth: '7rem' }}
body={dateExpirationBodyTemplate} filter filterElement={dateFilterTemplate}/>
<Column field="status" header={__('Stato', 'gepafin')}
filterMenuStyle={{ width: '14rem' }}
style={{ width: '120px' }} body={statusBodyTemplate}
filterElement={statusFilterTemplate}/>
<Column header={__('Azioni', 'gepafin')}
body={actionsBodyTemplate}/>
</DataTable>
</div>
)
}
export default DocumentsTable;

View File

@@ -1,23 +1,138 @@
import React, { useEffect, useState, useCallback } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { __ } from '@wordpress/i18n';
import { pathOr } from 'ramda';
import { classNames } from 'primereact/utils';
import { wrap } from 'object-path-immutable';
import { isEmpty, isNil } from 'ramda';
// store
import { useStore } from '../../store';
// api
import DocumentCategoryService from '../../service/document-category-service';
// components
import DocumentsTable from './components/DocumentsTable';
import { Dialog } from 'primereact/dialog';
import { Button } from 'primereact/button';
import { Dropdown } from 'primereact/dropdown';
import { InputText } from 'primereact/inputtext';
import { useStore } from '../../store';
import { Calendar } from 'primereact/calendar';
import { FileUpload } from 'primereact/fileupload';
import formatDateString from '../../helpers/formatDateString';
import CompanyDocumentsService from '../../service/company-documents-service';
const DocumentsBeneficiary = () => {
const [loading, setLoading] = useState(false);
const chosenCompanyId = useStore().main.chosenCompanyId();
const [isVisibleAddNewDialog, setIsVisibleAddNewDialog] = useState(false);
const [categories, setCategories] = useState(false);
const [newFileData, setNewFileData] = useState({});
const [fileAttached, setFileAttached] = useState([]);
const [reloadHash, setReloadHash] = useState(0);
const today = new Date();
const tomorrow = new Date(today);
tomorrow.setDate(today.getDate() + 1);
const getStats = (resp) => {}
const onCreateNew = useCallback((type) => {
const newData = wrap({})
.set(['documentType'], type)
.set(['documentCategoryId'], 0)
.set(['expirationDate'], '')
.set(['name'], '')
.value();
const errGetStats = () => {}
setNewFileData(newData);
setFileAttached([]);
setIsVisibleAddNewDialog(true);
}, [newFileData, chosenCompanyId]);
const hideAddNewDialog = () => {
setIsVisibleAddNewDialog(false);
const newData = wrap({})
.set(['documentType'], '')
.set(['documentCategoryId'], 0)
.set(['expirationDate'], '')
.set(['name'], '')
.value();
setNewFileData(newData);
setFileAttached([]);
}
const headerAddNewDialog = () => {
return <span>{__('Aggiungi file', 'gepafin')}</span>
}
const footerAddNewDialog = () => {
return <div>
<Button type="button" label={__('Anulla', 'gepafin')} onClick={hideAddNewDialog} outlined/>
<Button
type="button"
disabled={loading || !isValidForm()}
label={__('Salva', 'gepafin')} onClick={doAddNew}/>
</div>
}
const isValidForm = useCallback(() => {
return !isEmpty(fileAttached) && !Object.keys(newFileData).filter(k => isInvalidField(newFileData, k)
|| newFileData[k] === 0).length
}, [fileAttached, newFileData]);
const doAddNew = useCallback(() => {
const submitData = {
...newFileData,
expirationDate: formatDateString(newFileData.expirationDate)
}
const queryParams = Object.keys(submitData).map(k => [
k, submitData[k]
]);
if (!isEmpty(fileAttached)) {
const formData = new FormData()
for (const file of fileAttached) {
formData.append('file', file)
}
setLoading(true);
CompanyDocumentsService.uploadCompanyDocument(chosenCompanyId, formData, uploadDoc, errUploadDoc, queryParams);
}
}, [fileAttached, newFileData, chosenCompanyId]);
const uploadDoc = (resp) => {
if (resp.status === 'SUCCESS') {
hideAddNewDialog();
setReloadHash(new Date().getTime())
}
setLoading(false);
}
const errUploadDoc = () => {
setLoading(false);
}
const isInvalidField = (data, key) => isEmpty(data[key]) || isNil(data[key]);
const onUpdateFieldValue = useCallback((value, name) => {
const newData = wrap(newFileData).set([name], value).value();
setNewFileData(newData);
}, [newFileData]);
const onFileSelect = (file) => {
setFileAttached(file.files);
}
const getCategories = (resp) => {
if (resp.status === 'SUCCESS') {
setCategories(resp.data.map(o => ({value: o.id, label: o.description})));
}
setLoading(false);
}
const errGetCategories = () => {
setLoading(false);
}
useEffect(() => {
}, [chosenCompanyId]);
setLoading(true);
DocumentCategoryService.getCategories(getCategories, errGetCategories)
}, []);
return(
<div className="appPage">
@@ -28,17 +143,86 @@ const DocumentsBeneficiary = () => {
<div className="appPage__spacer"></div>
<div className="appPageSection statsBigBadges">
<h2>{__('Documenti del rappresentante legale', 'gepafin')}</h2>
<h2>
{__('Documenti del rappresentante legale', 'gepafin')}
<Button
onClick={() => onCreateNew('PERSONAL_DOCUMENT')}
size="small"
label={__('Aggiungi nuovo')} icon="pi pi-plus" iconPos="right"/>
</h2>
<DocumentsTable type="PERSONAL_DOCUMENT" reload={reloadHash}/>
</div>
<div className="appPage__spacer"></div>
<div className="appPageSection statsBigBadges">
<h2>{__('Documenti dell\'azienda', 'gepafin')}</h2>
<h2>
{__('Documenti dell\'azienda', 'gepafin')}
<Button
onClick={() => onCreateNew('COMPANY_DOCUMENT')}
size="small"
label={__('Aggiungi nuovo')} icon="pi pi-plus" iconPos="right"/>
</h2>
<DocumentsTable type="COMPANY_DOCUMENT" reload={reloadHash}/>
</div>
<Dialog
visible={isVisibleAddNewDialog}
modal
header={headerAddNewDialog}
footer={footerAddNewDialog}
style={{ maxWidth: '600px', width: '100%' }}
onHide={hideAddNewDialog}>
<div className="appForm__cols">
<div className="appForm__field">
<label
className={classNames({ 'p-error': isInvalidField(newFileData, 'name') })}>
{__('Nome', 'gepafin')}*
</label>
<InputText value={newFileData.name}
invalid={isInvalidField(newFileData, 'name')}
onChange={(e) => onUpdateFieldValue(e.target.value, 'name')}/>
</div>
<div className="appForm__field">
<label
className={classNames({ 'p-error': isInvalidField(newFileData, 'documentCategoryId') || newFileData.documentCategoryId === 0 })}>
{__('Categoria', 'gepafin')}*
</label>
<Dropdown
value={newFileData.documentCategoryId}
invalid={isEmpty(newFileData.documentCategoryId) || isNil(newFileData.documentCategoryId) || newFileData.documentCategoryId === 0}
onChange={(e) => onUpdateFieldValue(e.value, 'documentCategoryId')}
options={categories}
optionLabel="label"
optionValue="value"/>
</div>
</div>
<div className="appForm__cols">
<div className="appForm__field">
<label
className={classNames({ 'p-error': isEmpty(newFileData.expirationDate) || isNil(newFileData.expirationDate) })}>
{__('Scadenza', 'gepafin')}*
</label>
<Calendar
value={newFileData.expirationDate}
minDate={tomorrow}
invalid={isEmpty(newFileData.expirationDate) || isNil(newFileData.expirationDate) || newFileData.expirationDate === 0}
onChange={(e) => onUpdateFieldValue(e.value, 'expirationDate')}/>
</div>
</div>
<div className="appForm__cols">
<div className="appForm__field">
<label
className={classNames({ 'p-error': isEmpty(newFileData.file) || isNil(newFileData.expirationDate) })}>
{__('File', 'gepafin')}*
</label>
<FileUpload
mode="basic"
name="file"
onSelect={onFileSelect}/>
</div>
</div>
</Dialog>
</div>
)
}

View File

@@ -0,0 +1,20 @@
import { NetworkService } from './network-service';
const API_BASE_URL = process.env.REACT_APP_API_EXECUTION_ADDRESS;
export default class CompanyDocumentsService {
static getCompanyDocuments = (companyId, callback, errCallback, queryParams) => {
NetworkService.get(`${API_BASE_URL}/companyDocument/company/${companyId}`, callback, errCallback, queryParams);
};
static uploadCompanyDocument = (id, body, callback, errCallback, queryParams) => {
NetworkService.postMultiPart(`${API_BASE_URL}/companyDocument/company/${id}/upload`, body, callback, errCallback, queryParams);
};
static deleteCompanyDocument = (id, callback, errCallback) => {
NetworkService.delete(`${API_BASE_URL}/companyDocument`, {}, callback, errCallback, [
['id', id]
]);
};
}

View File

@@ -0,0 +1,10 @@
import { NetworkService } from './network-service';
const API_BASE_URL = process.env.REACT_APP_API_EXECUTION_ADDRESS;
export default class DocumentCategoryService {
static getCategories = (callback, errCallback) => {
NetworkService.get(`${API_BASE_URL}/documentCategory`, callback, errCallback);
};
}

View File

@@ -1,5 +1,6 @@
/* data table related */
import { __ } from '@wordpress/i18n';
import { FilterMatchMode } from 'primereact/api';
const currentPageReportTemplate = '';