switch to bootstrap italia through AI
This commit is contained in:
+37
-93
@@ -1,12 +1,10 @@
|
||||
import React, { type JSX } from 'react';
|
||||
import * as FluentUI from '@fluentui/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';
|
||||
|
||||
/** Singolo risultato restituito dall'API esterna */
|
||||
interface ContrattoResult {
|
||||
idContratto: string;
|
||||
titoloContratto: string;
|
||||
@@ -14,7 +12,6 @@ interface ContrattoResult {
|
||||
idRicevuta: string;
|
||||
}
|
||||
|
||||
/** State aggiuntivo per gestire l'autocomplete */
|
||||
interface RecuperoPropostaState {
|
||||
searchText: string;
|
||||
searchResults: ContrattoResult[];
|
||||
@@ -26,7 +23,6 @@ interface RecuperoPropostaState {
|
||||
}
|
||||
|
||||
export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsComponentAbstract {
|
||||
/** Stato locale per l'autocomplete (separato dal formData gestito dall'abstract) */
|
||||
private autocompleteState: RecuperoPropostaState = {
|
||||
searchText: '',
|
||||
searchResults: [],
|
||||
@@ -37,20 +33,17 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
|
||||
errorMessage: '',
|
||||
};
|
||||
|
||||
/** Timer per il debounce della ricerca */
|
||||
private debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
/** Ref al container per gestire il click-outside */
|
||||
private autocompleteRef: React.RefObject<HTMLDivElement | null> = React.createRef();
|
||||
|
||||
private static readonly formFieldKeys = {
|
||||
idContratto: 'COL0010',
|
||||
idContratto: 'COL0010',
|
||||
titoloContratto: 'COL0020',
|
||||
idDomanda: 'COL0030',
|
||||
idRicevuta: 'COL0040',
|
||||
idDomanda: 'COL0030',
|
||||
idRicevuta: 'COL0040',
|
||||
} as const;
|
||||
|
||||
/** Codice fiscale letto dalla query string (COL0009), usato solo per la chiamata API */
|
||||
private codiceFiscale: string;
|
||||
|
||||
constructor(props: any) {
|
||||
@@ -59,7 +52,6 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
|
||||
}
|
||||
|
||||
override componentDidMount(): void {
|
||||
// Listener per chiudere il dropdown al click fuori dall'autocomplete
|
||||
document.addEventListener('mousedown', this.handleClickOutside);
|
||||
}
|
||||
|
||||
@@ -71,19 +63,12 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
|
||||
}
|
||||
|
||||
private handleClickOutside = (event: MouseEvent): void => {
|
||||
if (
|
||||
this.autocompleteRef.current &&
|
||||
!this.autocompleteRef.current.contains(event.target as Node)
|
||||
) {
|
||||
if (this.autocompleteRef.current && !this.autocompleteRef.current.contains(event.target as Node)) {
|
||||
this.autocompleteState.isDropdownOpen = false;
|
||||
this.forceUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Chiama l'API esterna per ottenere i contratti corrispondenti al testo di ricerca.
|
||||
* Autenticazione: Basic Auth (username:password) + header X-Api-Key.
|
||||
*/
|
||||
private async fetchContratti(searchTerm: string): Promise<void> {
|
||||
this.autocompleteState.isLoading = true;
|
||||
this.autocompleteState.errorMessage = '';
|
||||
@@ -99,9 +84,9 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
|
||||
const response = await fetch(url.toString(), {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Basic ${credentials}`,
|
||||
Authorization: `Basic ${credentials}`,
|
||||
'X-Api-Key': config.apiKey,
|
||||
'Accept': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -121,21 +106,15 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
|
||||
this.autocompleteState.searchResults = [];
|
||||
this.autocompleteState.isDropdownOpen = false;
|
||||
this.autocompleteState.isLoading = false;
|
||||
this.autocompleteState.errorMessage =
|
||||
error instanceof Error ? error.message : 'Errore durante la ricerca';
|
||||
this.autocompleteState.errorMessage = error instanceof Error ? error.message : 'Errore durante la ricerca';
|
||||
this.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gestisce il cambio testo nell'input di ricerca.
|
||||
* Applica debounce e chiama l'API al raggiungimento del minimo di caratteri.
|
||||
*/
|
||||
private handleSearchChange = (_event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string): void => {
|
||||
const text = newValue ?? '';
|
||||
private handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
const text = event.target.value;
|
||||
this.autocompleteState.searchText = text;
|
||||
|
||||
// Se l'utente cancella il testo, resetta anche la selezione
|
||||
if (text.length === 0) {
|
||||
this.autocompleteState.searchResults = [];
|
||||
this.autocompleteState.isDropdownOpen = false;
|
||||
@@ -146,7 +125,6 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
|
||||
return;
|
||||
}
|
||||
|
||||
// Debounce della ricerca
|
||||
if (this.debounceTimer) {
|
||||
clearTimeout(this.debounceTimer);
|
||||
}
|
||||
@@ -163,10 +141,7 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gestisce la navigazione da tastiera nel dropdown (frecce, Invio, Escape).
|
||||
*/
|
||||
private handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
|
||||
private handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>): void => {
|
||||
const { searchResults, isDropdownOpen, highlightedIndex } = this.autocompleteState;
|
||||
|
||||
if (!isDropdownOpen || searchResults.length === 0) return;
|
||||
@@ -174,10 +149,7 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
|
||||
switch (event.key) {
|
||||
case 'ArrowDown':
|
||||
event.preventDefault();
|
||||
this.autocompleteState.highlightedIndex = Math.min(
|
||||
highlightedIndex + 1,
|
||||
searchResults.length - 1
|
||||
);
|
||||
this.autocompleteState.highlightedIndex = Math.min(highlightedIndex + 1, searchResults.length - 1);
|
||||
this.forceUpdate();
|
||||
break;
|
||||
|
||||
@@ -202,9 +174,6 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Seleziona un contratto dal dropdown e popola i campi hidden del form.
|
||||
*/
|
||||
private selectContract(contract: ContrattoResult): void {
|
||||
this.autocompleteState.selectedContract = contract;
|
||||
this.autocompleteState.searchText = `[${contract.idContratto}] ${contract.titoloContratto}`;
|
||||
@@ -212,7 +181,6 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
|
||||
this.autocompleteState.searchResults = [];
|
||||
this.autocompleteState.highlightedIndex = -1;
|
||||
|
||||
// Aggiorna il formData con i valori selezionati
|
||||
this.setState({
|
||||
formData: {
|
||||
...this.state.formData,
|
||||
@@ -221,15 +189,9 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
|
||||
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idDomanda]: contract.idDomanda,
|
||||
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idRicevuta]: contract.idRicevuta,
|
||||
},
|
||||
}, () => {
|
||||
console.log('Contratto selezionato:', contract);
|
||||
console.log('Form data aggiornato:', this.state.formData);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resetta la selezione e svuota i campi hidden.
|
||||
*/
|
||||
private clearSelection(): void {
|
||||
this.autocompleteState.selectedContract = null;
|
||||
|
||||
@@ -244,42 +206,41 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Renderizza il componente autocomplete completo (label + input + dropdown).
|
||||
*/
|
||||
private renderAutocomplete(): JSX.Element {
|
||||
const { searchResults, isLoading, isDropdownOpen, highlightedIndex, errorMessage, searchText } = this.autocompleteState;
|
||||
|
||||
const autocompleteInput = (
|
||||
<>
|
||||
<div className="autocomplete-wrapper" ref={this.autocompleteRef}>
|
||||
<FluentUI.TextField
|
||||
<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}
|
||||
className="isiportalPartialAdminFormFieldSingleLineText"
|
||||
autoComplete="off"
|
||||
/>
|
||||
|
||||
{isLoading && (
|
||||
<div className="autocomplete-dropdown">
|
||||
<div className="autocomplete-loading">
|
||||
<FluentUI.Spinner size={FluentUI.SpinnerSize.small} label="Ricerca in corso..." />
|
||||
</div>
|
||||
<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="autocomplete-dropdown">
|
||||
<div className="autocompleteDropdown mt-2">
|
||||
{searchResults.map((contract, index) => (
|
||||
<div
|
||||
key={`${contract.idContratto}_${index}`}
|
||||
className={`autocomplete-dropdown-item${index === highlightedIndex ? ' highlighted' : ''}`}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault(); // Previene blur del TextField
|
||||
className={`autocompleteItem${index === highlightedIndex ? ' isHighlighted' : ''}`}
|
||||
onMouseDown={(event) => {
|
||||
event.preventDefault();
|
||||
this.selectContract(contract);
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
@@ -287,41 +248,31 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
|
||||
this.forceUpdate();
|
||||
}}
|
||||
>
|
||||
<span className="contract-id">[{contract.idContratto}]</span>{' '}
|
||||
<span className="contract-title">{contract.titoloContratto}</span>
|
||||
<span className="fw-semibold">[{contract.idContratto}]</span>{' '}
|
||||
<span>{contract.titoloContratto}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading && isDropdownOpen && searchResults.length === 0 && (
|
||||
<div className="autocomplete-dropdown">
|
||||
<div className="autocomplete-no-results">Nessun risultato trovato</div>
|
||||
</div>
|
||||
<div className="autocompleteDropdown mt-2 p-3 text-muted">Nessun risultato trovato</div>
|
||||
)}
|
||||
|
||||
{errorMessage && (
|
||||
<div className="autocomplete-error">{errorMessage}</div>
|
||||
<div className="alert alert-danger mt-2">{errorMessage}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{this.autocompleteState.selectedContract && (
|
||||
<div className="selected-contract-summary">
|
||||
<div className="summary-row">
|
||||
<span className="summary-label">ID Contratto:</span>
|
||||
<span className="summary-value">{this.autocompleteState.selectedContract.idContratto}</span>
|
||||
</div>
|
||||
<div className="summary-row">
|
||||
<span className="summary-label">Titolo:</span>
|
||||
<span className="summary-value">{this.autocompleteState.selectedContract.titoloContratto}</span>
|
||||
</div>
|
||||
<div className="summary-row">
|
||||
<span className="summary-label">ID Domanda:</span>
|
||||
<span className="summary-value">{this.autocompleteState.selectedContract.idDomanda}</span>
|
||||
</div>
|
||||
<div className="summary-row">
|
||||
<span className="summary-label">ID Ricevuta:</span>
|
||||
<span className="summary-value">{this.autocompleteState.selectedContract.idRicevuta}</span>
|
||||
<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>
|
||||
)}
|
||||
@@ -329,16 +280,11 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
|
||||
);
|
||||
|
||||
return new ElixFormsElement(
|
||||
<FluentUI.Label htmlFor="autocomplete-search">Cerca Contratto</FluentUI.Label>,
|
||||
<label className="form-label fw-semibold" htmlFor="autocomplete-search">Cerca Contratto</label>,
|
||||
autocompleteInput
|
||||
).render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override del metodo factory per definire i campi custom del form.
|
||||
* - Autocomplete per la ricerca contratti
|
||||
* - 4 campi hidden (COL0010, COL0020, COL0030, COL0040) per i dati da rimandare a elixForms
|
||||
*/
|
||||
protected override createCustomFormFields(formFieldFactory: IElixFormsComponentCustomFormFieldFactory): JSX.Element {
|
||||
const fieldKeys = RecuperoPropostaCctDaContrattiComponent.formFieldKeys;
|
||||
const idContratto = this.state.formData[fieldKeys.idContratto] ?? '';
|
||||
@@ -349,8 +295,6 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
|
||||
return (
|
||||
<>
|
||||
{this.renderAutocomplete()}
|
||||
|
||||
{/* Campi hidden per i dati del contratto selezionato — verranno inviati nel POST a elixForms */}
|
||||
<input type="hidden" id="idContratto_hidden" name={fieldKeys.idContratto} value={idContratto} />
|
||||
<input type="hidden" id="titoloContratto_hidden" name={fieldKeys.titoloContratto} value={titoloContratto} />
|
||||
<input type="hidden" id="idDomanda_hidden" name={fieldKeys.idDomanda} value={idDomanda} />
|
||||
|
||||
Reference in New Issue
Block a user