306 lines
11 KiB
TypeScript
306 lines
11 KiB
TypeScript
import React, { type JSX } from 'react';
|
|
import ElixFormsComponentAbstract from '@common/src/ElixFormsComponentAbstract';
|
|
import type { IElixFormsComponentCustomFormFieldFactory } from '@common/src/IElixFormsComponentCustomFormFieldFactory';
|
|
import { ElixFormsElement } from '@common/src/ElixFormsElement';
|
|
import { QueryParamHelper } from '@common/src/QueryParamHelper';
|
|
import config from './config.json';
|
|
|
|
interface ContrattoResult {
|
|
idContratto: string;
|
|
titoloContratto: string;
|
|
idDomanda: string;
|
|
idRicevuta: string;
|
|
}
|
|
|
|
interface RecuperoPropostaState {
|
|
searchText: string;
|
|
searchResults: ContrattoResult[];
|
|
isLoading: boolean;
|
|
isDropdownOpen: boolean;
|
|
selectedContract: ContrattoResult | null;
|
|
highlightedIndex: number;
|
|
errorMessage: string;
|
|
}
|
|
|
|
export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsComponentAbstract {
|
|
private autocompleteState: RecuperoPropostaState = {
|
|
searchText: '',
|
|
searchResults: [],
|
|
isLoading: false,
|
|
isDropdownOpen: false,
|
|
selectedContract: null,
|
|
highlightedIndex: -1,
|
|
errorMessage: '',
|
|
};
|
|
|
|
private debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
|
|
private autocompleteRef: React.RefObject<HTMLDivElement | null> = React.createRef();
|
|
|
|
private static readonly formFieldKeys = {
|
|
codiceContratto: config.elixForms.outputs.codiceContratto,
|
|
titoloContratto: config.elixForms.outputs.titoloContratto,
|
|
idDomanda: config.elixForms.outputs.idDomanda,
|
|
idRicevuta: config.elixForms.outputs.idRicevuta,
|
|
} as const;
|
|
|
|
private codiceFiscale: string;
|
|
|
|
constructor(props: any) {
|
|
super(props);
|
|
this.codiceFiscale = QueryParamHelper.getDecodedTextFromQuery(config.elixForms.inputs.codiceFiscale) ?? '';
|
|
}
|
|
|
|
override componentDidMount(): void {
|
|
document.addEventListener('mousedown', this.handleClickOutside);
|
|
}
|
|
|
|
override componentWillUnmount(): void {
|
|
document.removeEventListener('mousedown', this.handleClickOutside);
|
|
if (this.debounceTimer) {
|
|
clearTimeout(this.debounceTimer);
|
|
}
|
|
}
|
|
|
|
private handleClickOutside = (event: MouseEvent): void => {
|
|
if (this.autocompleteRef.current && !this.autocompleteRef.current.contains(event.target as Node)) {
|
|
this.autocompleteState.isDropdownOpen = false;
|
|
this.forceUpdate();
|
|
}
|
|
};
|
|
|
|
private async fetchContratti(searchTerm: string): Promise<void> {
|
|
this.autocompleteState.isLoading = true;
|
|
this.autocompleteState.errorMessage = '';
|
|
this.forceUpdate();
|
|
|
|
try {
|
|
const url = new URL(config.contrattiWS.apiUrl);
|
|
url.searchParams.set('cod_fis', this.codiceFiscale);
|
|
url.searchParams.set('term', searchTerm);
|
|
|
|
const credentials = btoa(`${config.contrattiWS.apiUsername}:${config.contrattiWS.apiPassword}`);
|
|
|
|
const response = await fetch(url.toString(), {
|
|
method: 'GET',
|
|
headers: {
|
|
Authorization: `Basic ${credentials}`,
|
|
'X-Api-Key': config.contrattiWS.apiKey,
|
|
Accept: 'application/json',
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Errore API: ${response.status} ${response.statusText}`);
|
|
}
|
|
|
|
const data: ContrattoResult[] = await response.json();
|
|
|
|
this.autocompleteState.searchResults = data;
|
|
this.autocompleteState.isDropdownOpen = data.length > 0;
|
|
this.autocompleteState.highlightedIndex = -1;
|
|
this.autocompleteState.isLoading = false;
|
|
this.forceUpdate();
|
|
} catch (error) {
|
|
console.error('Errore durante la ricerca contratti:', error);
|
|
this.autocompleteState.searchResults = [];
|
|
this.autocompleteState.isDropdownOpen = false;
|
|
this.autocompleteState.isLoading = false;
|
|
this.autocompleteState.errorMessage = error instanceof Error ? error.message : 'Errore durante la ricerca';
|
|
this.forceUpdate();
|
|
}
|
|
}
|
|
|
|
private handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
|
const text = event.target.value;
|
|
this.autocompleteState.searchText = text;
|
|
|
|
if (text.length === 0) {
|
|
this.autocompleteState.searchResults = [];
|
|
this.autocompleteState.isDropdownOpen = false;
|
|
this.autocompleteState.highlightedIndex = -1;
|
|
this.autocompleteState.errorMessage = '';
|
|
this.clearSelection();
|
|
this.forceUpdate();
|
|
return;
|
|
}
|
|
|
|
if (this.debounceTimer) {
|
|
clearTimeout(this.debounceTimer);
|
|
}
|
|
|
|
if (text.length >= config.component.minCharsForSearch) {
|
|
this.debounceTimer = setTimeout(() => {
|
|
this.fetchContratti(text);
|
|
}, 300);
|
|
} else {
|
|
this.autocompleteState.searchResults = [];
|
|
this.autocompleteState.isDropdownOpen = false;
|
|
this.autocompleteState.highlightedIndex = -1;
|
|
this.forceUpdate();
|
|
}
|
|
};
|
|
|
|
private handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>): void => {
|
|
const { searchResults, isDropdownOpen, highlightedIndex } = this.autocompleteState;
|
|
|
|
if (!isDropdownOpen || searchResults.length === 0) return;
|
|
|
|
switch (event.key) {
|
|
case 'ArrowDown':
|
|
event.preventDefault();
|
|
this.autocompleteState.highlightedIndex = Math.min(highlightedIndex + 1, searchResults.length - 1);
|
|
this.forceUpdate();
|
|
break;
|
|
|
|
case 'ArrowUp':
|
|
event.preventDefault();
|
|
this.autocompleteState.highlightedIndex = Math.max(highlightedIndex - 1, 0);
|
|
this.forceUpdate();
|
|
break;
|
|
|
|
case 'Enter':
|
|
event.preventDefault();
|
|
if (highlightedIndex >= 0 && highlightedIndex < searchResults.length) {
|
|
this.selectContract(searchResults[highlightedIndex]);
|
|
}
|
|
break;
|
|
|
|
case 'Escape':
|
|
event.preventDefault();
|
|
this.autocompleteState.isDropdownOpen = false;
|
|
this.forceUpdate();
|
|
break;
|
|
}
|
|
};
|
|
|
|
private selectContract(contract: ContrattoResult): void {
|
|
this.autocompleteState.selectedContract = contract;
|
|
this.autocompleteState.searchText = `[${contract.idContratto}] ${contract.titoloContratto}`;
|
|
this.autocompleteState.isDropdownOpen = false;
|
|
this.autocompleteState.searchResults = [];
|
|
this.autocompleteState.highlightedIndex = -1;
|
|
|
|
this.setState({
|
|
formData: {
|
|
...this.state.formData,
|
|
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.codiceContratto]: contract.idContratto,
|
|
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.titoloContratto]: contract.titoloContratto,
|
|
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idDomanda]: contract.idDomanda,
|
|
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idRicevuta]: contract.idRicevuta,
|
|
},
|
|
});
|
|
}
|
|
|
|
private clearSelection(): void {
|
|
this.autocompleteState.selectedContract = null;
|
|
|
|
this.setState({
|
|
formData: {
|
|
...this.state.formData,
|
|
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.codiceContratto]: '',
|
|
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.titoloContratto]: '',
|
|
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idDomanda]: '',
|
|
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idRicevuta]: '',
|
|
},
|
|
});
|
|
}
|
|
|
|
private renderAutocomplete(): JSX.Element {
|
|
const { searchResults, isLoading, isDropdownOpen, highlightedIndex, errorMessage, searchText } = this.autocompleteState;
|
|
|
|
const autocompleteInput = (
|
|
<>
|
|
<div className="position-relative" ref={this.autocompleteRef}>
|
|
<input
|
|
id="autocomplete-search"
|
|
name="autocomplete-search"
|
|
className="form-control"
|
|
type="text"
|
|
placeholder="Digita almeno 3 caratteri per cercare..."
|
|
value={searchText}
|
|
onChange={this.handleSearchChange}
|
|
onKeyDown={this.handleKeyDown}
|
|
autoComplete="off"
|
|
/>
|
|
|
|
{isLoading && (
|
|
<div className="autocompleteDropdown mt-2 p-3 text-muted">
|
|
<span className="me-2">
|
|
<span className="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
|
</span>
|
|
Ricerca in corso...
|
|
</div>
|
|
)}
|
|
|
|
{!isLoading && isDropdownOpen && searchResults.length > 0 && (
|
|
<div className="autocompleteDropdown mt-2">
|
|
{searchResults.map((contract, index) => (
|
|
<div
|
|
key={`${contract.idContratto}_${index}`}
|
|
className={`autocompleteItem${index === highlightedIndex ? ' isHighlighted' : ''}`}
|
|
onMouseDown={(event) => {
|
|
event.preventDefault();
|
|
this.selectContract(contract);
|
|
}}
|
|
onMouseEnter={() => {
|
|
this.autocompleteState.highlightedIndex = index;
|
|
this.forceUpdate();
|
|
}}
|
|
>
|
|
<span className="fw-semibold">[{contract.idContratto}]</span>{' '}
|
|
<span>{contract.titoloContratto}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{!isLoading && isDropdownOpen && searchResults.length === 0 && (
|
|
<div className="autocompleteDropdown mt-2 p-3 text-muted">Nessun risultato trovato</div>
|
|
)}
|
|
|
|
{errorMessage && (
|
|
<div className="alert alert-danger mt-2">{errorMessage}</div>
|
|
)}
|
|
</div>
|
|
|
|
{this.autocompleteState.selectedContract && (
|
|
<div className="card mt-3">
|
|
<div className="card-body">
|
|
<div className="row g-2">
|
|
<div className="col-12 col-md-6"><strong>ID Contratto:</strong> {this.autocompleteState.selectedContract.idContratto}</div>
|
|
<div className="col-12 col-md-6"><strong>Titolo:</strong> {this.autocompleteState.selectedContract.titoloContratto}</div>
|
|
<div className="col-12 col-md-6"><strong>ID Domanda:</strong> {this.autocompleteState.selectedContract.idDomanda}</div>
|
|
<div className="col-12 col-md-6"><strong>ID Ricevuta:</strong> {this.autocompleteState.selectedContract.idRicevuta}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
|
|
return new ElixFormsElement(
|
|
<label className="form-label fw-semibold" htmlFor="autocomplete-search">Cerca Contratto</label>,
|
|
autocompleteInput
|
|
).render();
|
|
}
|
|
|
|
protected override createCustomFormFields(formFieldFactory: IElixFormsComponentCustomFormFieldFactory): JSX.Element {
|
|
const fieldKeys = RecuperoPropostaCctDaContrattiComponent.formFieldKeys;
|
|
const idContratto = this.state.formData[fieldKeys.codiceContratto] ?? '';
|
|
const titoloContratto = this.state.formData[fieldKeys.titoloContratto] ?? '';
|
|
const idDomanda = this.state.formData[fieldKeys.idDomanda] ?? '';
|
|
const idRicevuta = this.state.formData[fieldKeys.idRicevuta] ?? '';
|
|
|
|
return (
|
|
<>
|
|
{this.renderAutocomplete()}
|
|
<input type="hidden" id="idContratto_hidden" name={fieldKeys.codiceContratto} value={idContratto} />
|
|
<input type="hidden" id="titoloContratto_hidden" name={fieldKeys.titoloContratto} value={titoloContratto} />
|
|
<input type="hidden" id="idDomanda_hidden" name={fieldKeys.idDomanda} value={idDomanda} />
|
|
<input type="hidden" id="idRicevuta_hidden" name={fieldKeys.idRicevuta} value={idRicevuta} />
|
|
</>
|
|
);
|
|
}
|
|
}
|