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 | null = null; private autocompleteRef: React.RefObject = 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 { 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): 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): 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 = ( <>
{isLoading && (
Ricerca in corso...
)} {!isLoading && isDropdownOpen && searchResults.length > 0 && (
{searchResults.map((contract, index) => (
{ event.preventDefault(); this.selectContract(contract); }} onMouseEnter={() => { this.autocompleteState.highlightedIndex = index; this.forceUpdate(); }} > [{contract.idContratto}]{' '} {contract.titoloContratto}
))}
)} {!isLoading && isDropdownOpen && searchResults.length === 0 && (
Nessun risultato trovato
)} {errorMessage && (
{errorMessage}
)}
{this.autocompleteState.selectedContract && (
ID Contratto: {this.autocompleteState.selectedContract.idContratto}
Titolo: {this.autocompleteState.selectedContract.titoloContratto}
ID Domanda: {this.autocompleteState.selectedContract.idDomanda}
ID Ricevuta: {this.autocompleteState.selectedContract.idRicevuta}
)} ); return new ElixFormsElement( , 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()} ); } }