- company files for evaluation functionality;

This commit is contained in:
Vitalii Kiiko
2025-11-19 13:31:27 +01:00
parent 68cc6716e9
commit 9121a4fd9f
7 changed files with 365 additions and 187 deletions

View File

@@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { classNames } from 'primereact/utils'; import { classNames } from 'primereact/utils';
import { __ } from '@wordpress/i18n'; import { __ } from '@wordpress/i18n';
import { isEmpty, pathOr, pluck } from 'ramda'; import { isEmpty, isNil, pathOr, pluck } from 'ramda';
// service // service
import FileUploadService from '../../../../service/file-upload-service'; import FileUploadService from '../../../../service/file-upload-service';
@@ -27,7 +27,8 @@ const FileSelect = ({
sourceId = 0, sourceId = 0,
source = 'DOCUMENT', source = 'DOCUMENT',
documentCategories = [], documentCategories = [],
saveFormCallback saveFormCallback,
attachFilesCallback,
}) => { }) => {
//const [stateFieldData, setStateFieldData] = useState([]); //const [stateFieldData, setStateFieldData] = useState([]);
const stateFieldData = useRef([]); const stateFieldData = useRef([]);
@@ -52,6 +53,16 @@ const FileSelect = ({
setAddNewMode(false); setAddNewMode(false);
}, [selectedUnconfirmed]); }, [selectedUnconfirmed]);
const attachSelectedFilesCallback = useCallback(() => {
const existingIds = pluck('id', stateFieldData.current);
const selectedToBeAdded = selectedUnconfirmed.filter(o => !existingIds.includes(o.id));
setSelectedUnconfirmed([]);
// eslint-disable-next-line array-callback-return
selectedToBeAdded.map(o => attachFilesCallback(o));
setAddNewMode(false);
}, [selectedUnconfirmed]);
const doGoBackToListOfFiles = () => { const doGoBackToListOfFiles = () => {
setSelectedUnconfirmed([]); setSelectedUnconfirmed([]);
setAddNewMode(false); setAddNewMode(false);
@@ -100,7 +111,8 @@ const FileSelect = ({
defaultFocus: 'reject', defaultFocus: 'reject',
acceptClassName: 'p-button-danger', acceptClassName: 'p-button-danger',
accept: () => removeAttached(id), accept: () => removeAttached(id),
reject: () => {} reject: () => {
}
}); });
}; };
@@ -116,19 +128,19 @@ const FileSelect = ({
? o ? o
: documentCategories.includes(o.category.id)) : documentCategories.includes(o.category.id))
.reduce((acc, cur) => { .reduce((acc, cur) => {
const catName = pathOr('', ['category', 'categoryName'], cur); const catName = pathOr('', ['category', 'categoryName'], cur);
const catLabel = pathOr('', ['category', 'description'], cur); const catLabel = pathOr('', ['category', 'description'], cur);
if (!acc[catName]) { if (!acc[catName]) {
acc[catName] = { acc[catName] = {
code: catName, code: catName,
label: catLabel, label: catLabel,
items: [] items: []
}; };
} }
acc[catName].items.push(cur) acc[catName].items.push(cur)
return acc; return acc;
}, {}); }, {});
setOptionsTransformed(Object.values(optionsDefault)); setOptionsTransformed(Object.values(optionsDefault));
} }
@@ -137,13 +149,14 @@ const FileSelect = ({
useEffect(() => { useEffect(() => {
stateFieldData.current = defaultValue; stateFieldData.current = defaultValue;
}, [defaultValue]); }, [defaultValue]);
//console.log([...stateFieldData.current])
return ( return (
<> <>
<label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}> {!isNil(label)
{label}{config.required || config.isRequired ? ? <label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
<span className="appForm__field--required">*</span> : null} {label}{config.required || config.isRequired ?
</label> <span className="appForm__field--required">*</span> : null}
</label> : null}
<div className="fileselectInner"> <div className="fileselectInner">
{addNewMode {addNewMode
? <div className="fileselectInner__selectionBox"> ? <div className="fileselectInner__selectionBox">
@@ -160,7 +173,7 @@ const FileSelect = ({
? <Button ? <Button
severity="success" severity="success"
disabled={loading} disabled={loading}
onClick={attachSelectedFiles} onClick={!isNil(attachFilesCallback) ? attachSelectedFilesCallback : attachSelectedFiles}
label={__('Conferma i file scelti', 'gepafin')} label={__('Conferma i file scelti', 'gepafin')}
icon="pi pi-arrow-right" size="small" iconPos="right"/> icon="pi pi-arrow-right" size="small" iconPos="right"/>
: <Button : <Button
@@ -172,19 +185,21 @@ const FileSelect = ({
</div> : null} </div> : null}
{!isEmpty(optionsTransformed) && !addNewMode {!isEmpty(optionsTransformed) && !addNewMode
? <div className="fileselectInner__selectedFiles"> ? <div className="fileselectInner__selectedFiles">
<p>{__('I file selezionati')}</p> <p><strong>{__('I file selezionati:')}</strong></p>
<ul> <ul>
{[...stateFieldData.current].map(o => <li key={o.id}> {!isEmpty(stateFieldData.current)
{o.name} ? [...stateFieldData.current].map(o => <li key={o.id}>
<div className="appPageSection__iconActions"> {o.name}
<Button icon="pi pi-times" rounded {isNil(attachFilesCallback)
type="button" ? <div className="appPageSection__iconActions">
size="small" <Button icon="pi pi-times" rounded
onClick={(e) => confirmDelete(e, o.id)} type="button"
outlined severity="danger" size="small"
aria-label={__('Cancella', 'gepafin')}/> onClick={(e) => confirmDelete(e, o.id)}
</div> outlined severity="danger"
</li>)} aria-label={__('Cancella', 'gepafin')}/>
</div> : null}
</li>) : <li>{__('Nessun file selezionato', 'gepafin')}</li>}
</ul> </ul>
<Button <Button
severity="success" severity="success"
@@ -205,7 +220,7 @@ const FileSelect = ({
</div> </div>
: null} : null}
{infoText ? <small>{infoText}</small> : null} {infoText ? <small>{infoText}</small> : null}
<ConfirmPopup /> <ConfirmPopup/>
</>) </>)
} }

View File

@@ -50,7 +50,7 @@ import { InputTextarea } from 'primereact/inputtextarea';
import { InputText } from 'primereact/inputtext'; import { InputText } from 'primereact/inputtext';
import { Dropdown } from 'primereact/dropdown'; import { Dropdown } from 'primereact/dropdown';
import ListOfFiles from '../DomandaEditPreInstructor/components/ListOfFiles'; import ListOfFiles from '../DomandaEditPreInstructor/components/ListOfFiles';
import RepeaterFields from '../DomandaEditPreInstructor/components/RepeaterFields'; import EvaluationExtraFiles from '../DomandaEditPreInstructor/components/EvaluationExtraFiles';
import ApplicationInfo from '../DomandaEditPreInstructor/components/ApplicationInfo'; import ApplicationInfo from '../DomandaEditPreInstructor/components/ApplicationInfo';
import ApplicationDownloadFiles from '../DomandaEditPreInstructor/components/ApplicationDownloadFiles'; import ApplicationDownloadFiles from '../DomandaEditPreInstructor/components/ApplicationDownloadFiles';
import FormField from '../../components/FormField'; import FormField from '../../components/FormField';
@@ -1288,7 +1288,7 @@ const DomandaEditInstructorManager = () => {
<div className="appPageSection"> <div className="appPageSection">
<h2>{__('Documenti aggiuntivi', 'gepafin')}</h2> <h2>{__('Documenti aggiuntivi', 'gepafin')}</h2>
<RepeaterFields <EvaluationExtraFiles
defaultValue={data.evaluationDocument ?? []} defaultValue={data.evaluationDocument ?? []}
updateFn={(data) => updateEvaluationValue( updateFn={(data) => updateEvaluationValue(
data, data,
@@ -1305,6 +1305,8 @@ const DomandaEditInstructorManager = () => {
})} })}
shouldDisable={['APPROVED', 'REJECTED'].includes(data.applicationStatus) || evaluationBlockedForUser(data)} shouldDisable={['APPROVED', 'REJECTED'].includes(data.applicationStatus) || evaluationBlockedForUser(data)}
sourceId={data.id} sourceId={data.id}
applicationId={data.applicationId}
companyId={data.companyId}
sourceName="evaluation"/> sourceName="evaluation"/>
</div> </div>

View File

@@ -0,0 +1,300 @@
import React, { useMemo, useState, useCallback, useEffect, useRef } from 'react';
import { useForm, useFieldArray } from 'react-hook-form';
import { isEmpty, head, pluck } from 'ramda';
import { __ } from '@wordpress/i18n';
import { klona } from 'klona';
// tools
import uniqid from '../../../../helpers/uniqid';
// components
import FormField from '../../../../components/FormField';
import { Button } from 'primereact/button';
import { Dialog } from 'primereact/dialog';
import { Toast } from 'primereact/toast';
import CompanyService from '../../../../service/company-service';
import FileSelect from '../../../../components/FormField/components/FileSelect';
import ApplicationService from '../../../../service/application-service';
import set404FromErrorResponse from '../../../../helpers/set404FromErrorResponse';
import { storeSet } from '../../../../store';
const EvaluationExtraFiles = ({
sourceId,
applicationId,
companyId,
sourceName,
updateFn = () => {
},
updateCallbackFn = () => {
},
defaultValue = [],
shouldDisable = false
}) => {
const [chosen, setChosen] = useState('');
const [isVisibleCompanyDocsDialog, setIsVisibleCompanyDocsDialog] = useState(false);
const [companyDocs, setCompanyDocs] = useState([]);
const [companyDocsSelected, setCompanyDocsSelected] = useState([]);
const toast = useRef(null);
const {
control,
handleSubmit,
formState: { errors },
setValue,
register,
trigger,
getValues,
watch
} = useForm({
defaultValues: useMemo(() => {
return {
items: defaultValue || []
};
}, [defaultValue]), mode: 'onChange'
});
const { fields, append, remove } = useFieldArray({
control,
name: 'items'
});
const watchFields = watch('items');
const onSubmit = () => {
}
const doUpdateAfterFileUploaded = () => {
const formData = getValues();
updateFn(formData.items);
updateCallbackFn(formData.items);
}
const addNew = () => {
const uid = uniqid('f');
const newItem = {
fieldId: uid,
nameValue: '',
fileValue: []
}
append(newItem);
setChosen(newItem.fieldId);
trigger();
};
const setNewChosen = useCallback((id) => {
const chosenObj = head(fields.filter(o => id === o.fieldId));
if (chosenObj) {
setChosen(chosen === id ? '' : id);
}
}, [fields, chosen]);
const removeItem = useCallback((index) => {
const chosenObj = klona(fields[index]);
remove(index);
if (chosen === chosenObj.fieldId) {
setChosen('');
}
const formData = getValues();
updateFn(formData.items);
updateCallbackFn(formData.items);
}, [fields, chosen]);
const openCompanyArchive = () => {
setIsVisibleCompanyDocsDialog(true);
}
const headerCompanyDocsDialog = () => {
return <span>{__('Documenti aziendale', 'gepafin')}</span>;
}
const hideCompanyDocsDialog = () => {
setIsVisibleCompanyDocsDialog(false);
setCompanyDocsSelected([]);
}
const footerPreTecEvalDialog = useCallback(() => {
return <div>
<Button type="button" label={__('Annulla', 'gepafin')} onClick={hideCompanyDocsDialog} outlined/>
<Button
type="button"
disabled={isEmpty(companyDocsSelected)}
label={__('Aggiungi', 'gepafin')} onClick={doImportCompanyDocs}/>
</div>
}, [companyDocsSelected]);
const doImportCompanyDocs = useCallback(() => {
if (!isEmpty(companyDocsSelected)) {
const ids = pluck('id', companyDocsSelected);
ApplicationService.setApplicationDocuments(applicationId, setDocsCallback, errSetDocsCallback, [
['companyDocumentIds', ids]
])
}
}, [companyDocsSelected]);
const setDocsCallback = (resp) => {
if (resp.status === 'SUCCESS') {
if (toast.current && resp.message) {
toast.current.show({
severity: 'success',
summary: '',
detail: resp.message
});
}
}
setIsVisibleCompanyDocsDialog(false);
setCompanyDocsSelected([]);
storeSet('unsetAsyncRequest');
setTimeout(() => {
window.location.reload();
}, 2500);
}
const errSetDocsCallback = (resp) => {
if (toast.current && resp.message) {
toast.current.show({
severity: resp.status === 'SUCCESS' ? 'info' : 'error',
summary: '',
detail: resp.message
});
}
setIsVisibleCompanyDocsDialog(false);
setCompanyDocsSelected([]);
set404FromErrorResponse(resp);
storeSet('unsetAsyncRequest');
}
const getDocsCallback = (resp) => {
if (resp.status === 'SUCCESS') {
setCompanyDocs(resp.data);
}
}
const errGetDocsCallback = (resp) => {
if (toast.current && resp.message) {
toast.current.show({
severity: resp.status === 'SUCCESS' ? 'info' : 'error',
summary: '',
detail: resp.message
});
}
set404FromErrorResponse(resp);
}
useEffect(() => {
CompanyService.getCompanyDocuments(companyId, getDocsCallback, errGetDocsCallback, [
['documentType', 'COMPANY_DOCUMENT']
])
}, [companyId]);
return (
<>
<div className="fieldsRepeater">
<form className="appForm" onSubmit={handleSubmit(onSubmit)}>
{watchFields
? watchFields.map((o, index) => <div key={o.fieldId}
className="fieldsRepeater__panel p-panel p-component">
<div className="fieldsRepeater__heading p-panel p-panel-header">
<span onClick={() => setNewChosen(o.fieldId)}>{o.nameValue}</span>
<Button icon="pi pi-times"
disabled={shouldDisable}
outlined
severity="danger"
className="actionBtn"
type="button"
aria-label={__('Cancella', 'gepafin')}
onClick={() => removeItem(index)}/>
</div>
<div className="fieldsRepeater__fields p-panel-content" data-hide={chosen !== o.fieldId}>
<FormField
type="textinput"
disabled={shouldDisable}
fieldName={`items.${index}.nameValue`}
label={__('Titolo del file', 'gepafin')}
control={control}
errors={errors}
defaultValue={o.nameValue}
config={{ required: __('È obbligatorio', 'gepafin') }}
/>
<FormField
type="fileupload"
disabled={isEmpty(o.nameValue) || shouldDisable}
setDataFn={setValue}
saveFormCallback={doUpdateAfterFileUploaded}
fieldName={`items.${index}.fileValue`}
label={__('File', 'gepafin')}
control={control}
register={register}
errors={errors}
defaultValue={o.fileValue ? o.fileValue : []}
accept={[]}
source={sourceName}
sourceId={sourceId}
multiple={false}
deleteOnBackend={true}
config={{ required: __('È obbligatorio', 'gepafin') }}
/>
</div>
</div>
) : null}
</form>
<div style={{ display: 'flex', gap: '10px' }}>
<Button
className="fieldsRepeater__addNew"
outlined
type="button"
disabled={(watchFields && watchFields.filter(o => isEmpty(o.nameValue) || isEmpty(o.fileValue)).length > 0) || shouldDisable}
onClick={addNew}
label={__('Aggiungi nuovo file', 'gepafin')}
/>
<Button
className="fieldsRepeater__addNew"
outlined
type="button"
disabled={(watchFields && watchFields.filter(o => isEmpty(o.nameValue) || isEmpty(o.fileValue)).length > 0) || shouldDisable}
onClick={openCompanyArchive}
label={__('Documenti aziendale', 'gepafin')}
/>
</div>
</div>
<Toast ref={toast}/>
<Dialog
visible={isVisibleCompanyDocsDialog}
modal
header={headerCompanyDocsDialog}
footer={footerPreTecEvalDialog}
style={{ maxWidth: '600px', width: '100%' }}
onHide={hideCompanyDocsDialog}>
<div className="appForm__field">
{/*<label
className={classNames({
'p-error': isEmpty(companyDocsSelected)
})}>
{__('Documenti', 'gepafin')}
</label>*/}
<FileSelect
fieldName="companyDocsSelected"
label={null}
errors={{}}
register={() => {}}
key={companyDocsSelected}
defaultValue={companyDocsSelected}
options={companyDocs}
sourceId="0"
source="DOCUMENT"
documentCategories={[]}
attachFilesCallback={(o) => {
setCompanyDocsSelected(prev => {
const newSelected = [...prev];
if (!newSelected.find(s => s.id === o.id)) {
newSelected.push(o);
}
return newSelected;
})
}}
/>
</div>
</Dialog>
</>
)
}
export default EvaluationExtraFiles;

View File

@@ -1,149 +0,0 @@
import React, { useMemo, useState, useCallback } from 'react';
import { useForm, useFieldArray } from 'react-hook-form';
import { isEmpty, head } from 'ramda';
import { __ } from '@wordpress/i18n';
import { klona } from 'klona';
// tools
import uniqid from '../../../../helpers/uniqid';
// components
import FormField from '../../../../components/FormField';
import { Button } from 'primereact/button';
const RepeaterFields = ({
sourceId,
sourceName,
updateFn = () => {
},
updateCallbackFn = () => {
},
defaultValue = [],
shouldDisable = false
}) => {
const [chosen, setChosen] = useState('');
const {
control,
handleSubmit,
formState: { errors },
setValue,
register,
trigger,
getValues,
watch
} = useForm({
defaultValues: useMemo(() => {
return {
items: defaultValue || []
};
}, [defaultValue]), mode: 'onChange'
});
const { fields, append, remove } = useFieldArray({
control,
name: 'items'
});
const watchFields = watch('items');
const onSubmit = () => {
}
const doUpdateAfterFileUploaded = () => {
const formData = getValues();
updateFn(formData.items);
updateCallbackFn(formData.items);
}
const addNew = () => {
const uid = uniqid('f');
const newItem = {
fieldId: uid,
nameValue: '',
fileValue: []
}
append(newItem);
setChosen(newItem.fieldId);
trigger();
};
const setNewChosen = useCallback((id) => {
const chosenObj = head(fields.filter(o => id === o.fieldId));
if (chosenObj) {
setChosen(chosen === id ? '' : id);
}
}, [fields, chosen]);
const removeItem = useCallback((index) => {
const chosenObj = klona(fields[index]);
remove(index);
if (chosen === chosenObj.fieldId) {
setChosen('');
}
const formData = getValues();
updateFn(formData.items);
updateCallbackFn(formData.items);
}, [fields, chosen]);
return (
<div className="fieldsRepeater">
<form className="appForm" onSubmit={handleSubmit(onSubmit)}>
{watchFields
? watchFields.map((o, index) => <div key={o.fieldId}
className="fieldsRepeater__panel p-panel p-component">
<div className="fieldsRepeater__heading p-panel p-panel-header">
<span onClick={() => setNewChosen(o.fieldId)}>{o.nameValue}</span>
<Button icon="pi pi-times"
disabled={shouldDisable}
outlined
severity="danger"
className="actionBtn"
type="button"
aria-label={__('Cancella', 'gepafin')}
onClick={() => removeItem(index)}/>
</div>
<div className="fieldsRepeater__fields p-panel-content" data-hide={chosen !== o.fieldId}>
<FormField
type="textinput"
disabled={shouldDisable}
fieldName={`items.${index}.nameValue`}
label={__('Titolo del file', 'gepafin')}
control={control}
errors={errors}
defaultValue={o.nameValue}
config={{ required: __('È obbligatorio', 'gepafin') }}
/>
<FormField
type="fileupload"
disabled={isEmpty(o.nameValue) || shouldDisable}
setDataFn={setValue}
saveFormCallback={doUpdateAfterFileUploaded}
fieldName={`items.${index}.fileValue`}
label={__('File', 'gepafin')}
control={control}
register={register}
errors={errors}
defaultValue={o.fileValue ? o.fileValue : []}
accept={[]}
source={sourceName}
sourceId={sourceId}
multiple={false}
deleteOnBackend={true}
config={{ required: __('È obbligatorio', 'gepafin') }}
/>
</div>
</div>
) : null}
</form>
<Button
className="fieldsRepeater__addNew"
outlined
type="button"
disabled={(watchFields && watchFields.filter(o => isEmpty(o.nameValue) || isEmpty(o.fileValue)).length > 0) || shouldDisable}
onClick={addNew}
label={__('Aggiungi nuovo file', 'gepafin')}
/>
</div>
)
}
export default RepeaterFields;

View File

@@ -50,7 +50,7 @@ import { InputTextarea } from 'primereact/inputtextarea';
import { InputText } from 'primereact/inputtext'; import { InputText } from 'primereact/inputtext';
import { Dropdown } from 'primereact/dropdown'; import { Dropdown } from 'primereact/dropdown';
import ListOfFiles from './components/ListOfFiles'; import ListOfFiles from './components/ListOfFiles';
import RepeaterFields from './components/RepeaterFields'; import EvaluationExtraFiles from './components/EvaluationExtraFiles';
import ApplicationInfo from './components/ApplicationInfo'; import ApplicationInfo from './components/ApplicationInfo';
import ApplicationDownloadFiles from './components/ApplicationDownloadFiles'; import ApplicationDownloadFiles from './components/ApplicationDownloadFiles';
import FormField from '../../components/FormField'; import FormField from '../../components/FormField';
@@ -1288,7 +1288,7 @@ const DomandaEditPreInstructor = () => {
<div className="appPageSection"> <div className="appPageSection">
<h2>{__('Documenti aggiuntivi', 'gepafin')}</h2> <h2>{__('Documenti aggiuntivi', 'gepafin')}</h2>
<RepeaterFields <EvaluationExtraFiles
defaultValue={data.evaluationDocument ?? []} defaultValue={data.evaluationDocument ?? []}
updateFn={(data) => updateEvaluationValue( updateFn={(data) => updateEvaluationValue(
data, data,
@@ -1305,6 +1305,8 @@ const DomandaEditPreInstructor = () => {
})} })}
shouldDisable={['APPROVED', 'REJECTED'].includes(data.applicationStatus) || evaluationBlockedForUser(data)} shouldDisable={['APPROVED', 'REJECTED'].includes(data.applicationStatus) || evaluationBlockedForUser(data)}
sourceId={data.id} sourceId={data.id}
applicationId={data.applicationId}
companyId={data.companyId}
sourceName="evaluation"/> sourceName="evaluation"/>
</div> </div>

View File

@@ -71,4 +71,8 @@ export default class ApplicationService {
static downloadRanking = (callId, callback, errCallback)=> { static downloadRanking = (callId, callback, errCallback)=> {
NetworkService.getBlob(`${API_BASE_URL}/application/call/${callId}/ranking-csv`, callback, errCallback); NetworkService.getBlob(`${API_BASE_URL}/application/call/${callId}/ranking-csv`, callback, errCallback);
} }
static setApplicationDocuments = (id, callback, errCallback, queryParams) => {
NetworkService.get(`${API_BASE_URL}/application/${id}/companyDocuments`, callback, errCallback, queryParams);
};
} }

View File

@@ -39,4 +39,8 @@ export default class CompanyService {
static deleteCompany = (id, callback, errCallback) => { static deleteCompany = (id, callback, errCallback) => {
NetworkService.delete(`${API_BASE_URL}/company/user/${id}`, {}, callback, errCallback); NetworkService.delete(`${API_BASE_URL}/company/user/${id}`, {}, callback, errCallback);
}; };
static getCompanyDocuments = (id, callback, errCallback, queryParams) => {
NetworkService.get(`${API_BASE_URL}/companyDocument/company/${id}`, callback, errCallback, queryParams);
};
} }