From 186291b52b1865380c0d4a5908bec7c8c264a272 Mon Sep 17 00:00:00 2001 From: Vitalii Kiiko Date: Thu, 27 Feb 2025 17:31:50 +0100 Subject: [PATCH] - saving changes; --- src/App.js | 55 ++++- src/assets/scss/components/appPage.scss | 4 + src/helpers/getBandoLabel.js | 6 + src/helpers/getBandoSeverity.js | 6 + .../components/DocumentsTable/index.js | 221 ++++++++++++++++++ src/pages/DocumentsBeneficiary/index.js | 208 ++++++++++++++++- src/service/company-documents-service.js | 20 ++ src/service/document-category-service.js | 10 + src/translationStringsForComponents.js | 1 + 9 files changed, 517 insertions(+), 14 deletions(-) create mode 100644 src/pages/DocumentsBeneficiary/components/DocumentsTable/index.js create mode 100644 src/service/company-documents-service.js create mode 100644 src/service/document-category-service.js diff --git a/src/App.js b/src/App.js index 7d3a901..2c0deea 100644 --- a/src/App.js +++ b/src/App.js @@ -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 ( - + + + ); diff --git a/src/assets/scss/components/appPage.scss b/src/assets/scss/components/appPage.scss index a07df79..a608ea4 100644 --- a/src/assets/scss/components/appPage.scss +++ b/src/assets/scss/components/appPage.scss @@ -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 { diff --git a/src/helpers/getBandoLabel.js b/src/helpers/getBandoLabel.js index 7cbf3bf..9a13e36 100644 --- a/src/helpers/getBandoLabel.js +++ b/src/helpers/getBandoLabel.js @@ -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'); diff --git a/src/helpers/getBandoSeverity.js b/src/helpers/getBandoSeverity.js index 03b8704..cd74015 100644 --- a/src/helpers/getBandoSeverity.js +++ b/src/helpers/getBandoSeverity.js @@ -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'; diff --git a/src/pages/DocumentsBeneficiary/components/DocumentsTable/index.js b/src/pages/DocumentsBeneficiary/components/DocumentsTable/index.js new file mode 100644 index 0000000..6089454 --- /dev/null +++ b/src/pages/DocumentsBeneficiary/components/DocumentsTable/index.js @@ -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 ( +
+
+ ); + }; + + const dateCreatedBodyTemplate = (rowData) => { + return getDateFromISOstring(rowData.createdDate); + }; + const dateExpirationBodyTemplate = (rowData) => { + return getDateFromISOstring(rowData.expirationDate); + }; + + const dateFilterTemplate = (options) => { + return 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 ; + }; + + const statusFilterTemplate = (options) => { + return options.filterCallback(e.value, options.index)} + itemTemplate={statusItemTemplate} placeholder={__('Scegli uno', 'gepafin')} + className="p-column-filter" + showClear/>; + }; + + const statusItemTemplate = (option) => { + return ; + }; + + 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
+
+ } + + 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 ( +
+ setFilters(e.filters)}> + + + + + + + +
+ ) +} + +export default DocumentsTable; diff --git a/src/pages/DocumentsBeneficiary/index.js b/src/pages/DocumentsBeneficiary/index.js index a101868..7f99c92 100644 --- a/src/pages/DocumentsBeneficiary/index.js +++ b/src/pages/DocumentsBeneficiary/index.js @@ -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 {__('Aggiungi file', 'gepafin')} + } + + const footerAddNewDialog = () => { + return
+
+ } + + 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(
@@ -28,17 +143,86 @@ const DocumentsBeneficiary = () => {
-

{__('Documenti del rappresentante legale', 'gepafin')}

- +

+ {__('Documenti del rappresentante legale', 'gepafin')} +

+
-

{__('Documenti dell\'azienda', 'gepafin')}

- +

+ {__('Documenti dell\'azienda', 'gepafin')} +

+
+ +
+
+ + onUpdateFieldValue(e.target.value, 'name')}/> +
+
+ + onUpdateFieldValue(e.value, 'documentCategoryId')} + options={categories} + optionLabel="label" + optionValue="value"/> +
+
+
+
+ + onUpdateFieldValue(e.value, 'expirationDate')}/> +
+
+
+
+ + +
+
+
) } diff --git a/src/service/company-documents-service.js b/src/service/company-documents-service.js new file mode 100644 index 0000000..9e6676b --- /dev/null +++ b/src/service/company-documents-service.js @@ -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] + ]); + }; +} diff --git a/src/service/document-category-service.js b/src/service/document-category-service.js new file mode 100644 index 0000000..1cdb30b --- /dev/null +++ b/src/service/document-category-service.js @@ -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); + }; +} diff --git a/src/translationStringsForComponents.js b/src/translationStringsForComponents.js index e33247e..2591a26 100644 --- a/src/translationStringsForComponents.js +++ b/src/translationStringsForComponents.js @@ -1,5 +1,6 @@ /* data table related */ import { __ } from '@wordpress/i18n'; +import { FilterMatchMode } from 'primereact/api'; const currentPageReportTemplate = '';