add new custom page: recupero-proposta-cct-da-contratti
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
import js from '@eslint/js'
|
||||||
|
import globals from 'globals'
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
|
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
globalIgnores(['dist']),
|
||||||
|
{
|
||||||
|
files: ['**/*.{js,jsx}'],
|
||||||
|
extends: [
|
||||||
|
js.configs.recommended,
|
||||||
|
reactHooks.configs.flat.recommended,
|
||||||
|
reactRefresh.configs.vite,
|
||||||
|
],
|
||||||
|
languageOptions: {
|
||||||
|
globals: globals.browser,
|
||||||
|
parserOptions: { ecmaFeatures: { jsx: true } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>recupero-proposta-cct-da-contratti</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+3121
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "@elixforms/recupero-proposta-cct-da-contratti",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vite-plugin-css-injected-by-js": "^5.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^10.0.1",
|
||||||
|
"@types/react": "^19.2.14",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
|
"eslint": "^10.2.1",
|
||||||
|
"eslint-plugin-react-hooks": "^7.1.1",
|
||||||
|
"eslint-plugin-react-refresh": "^0.5.2",
|
||||||
|
"globals": "^17.5.0",
|
||||||
|
"sass-embedded": "^1.99.0",
|
||||||
|
"vite": "^8.0.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
@import url('https://console-unipr.elixforms.it/elixFormsCustom/css/elixForms.css');
|
||||||
|
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/bootstrap_adaptation.css');
|
||||||
|
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/bootstrap_overrides.css');
|
||||||
|
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/console_user.css');
|
||||||
|
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/designitalia_adaptation.css');
|
||||||
|
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/fonts.css');
|
||||||
|
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/isipcss_overrides.css');
|
||||||
|
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/iulib.css');
|
||||||
|
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/print.css');
|
||||||
|
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/responsive.css');
|
||||||
|
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/style-bs.css');
|
||||||
|
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/style.css');
|
||||||
|
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/util.css');
|
||||||
|
@import url('https://console-unipr.elixforms.it/rwe2/css/plugins/modaldisplay.css');
|
||||||
|
@import url('https://console-unipr.elixforms.it/rwe2/css/themes/blu-italia.css');
|
||||||
|
@import url('https://console-unipr.elixforms.it/rwe2/isipcss/css/grid_fluid.css');
|
||||||
|
|
||||||
|
/* ===== Autocomplete Component Styles ===== */
|
||||||
|
|
||||||
|
.autocomplete-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #a19f9d;
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 2px 2px;
|
||||||
|
max-height: 250px;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-dropdown-item {
|
||||||
|
padding: 8px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.4;
|
||||||
|
border-bottom: 1px solid #f3f2f1;
|
||||||
|
transition: background-color 0.1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-dropdown-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-dropdown-item:hover,
|
||||||
|
.autocomplete-dropdown-item.highlighted {
|
||||||
|
background-color: #edebe9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-dropdown-item .contract-id {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #323130;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-dropdown-item .contract-title {
|
||||||
|
color: #605e5c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-loading {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
|
color: #605e5c;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-no-results {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
|
color: #a19f9d;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-error {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
|
color: #a4262c;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Selected Contract Summary ===== */
|
||||||
|
|
||||||
|
.selected-contract-summary {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background-color: #f3f2f1;
|
||||||
|
border: 1px solid #e1dfdd;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #323130;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-contract-summary .summary-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-contract-summary .summary-row:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-contract-summary .summary-label {
|
||||||
|
font-weight: 600;
|
||||||
|
min-width: 120px;
|
||||||
|
color: #605e5c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-contract-summary .summary-value {
|
||||||
|
color: #323130;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import './App.css'
|
||||||
|
import RecuperoPropostaCctDaContrattiComponent from './RecuperoPropostaCctDaContrattiComponent';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<RecuperoPropostaCctDaContrattiComponent
|
||||||
|
description=""
|
||||||
|
isDarkTheme={false}
|
||||||
|
environmentMessage=""
|
||||||
|
hasTeamsContext={false}
|
||||||
|
userDisplayName=""
|
||||||
|
moduleTitle="Modulo - Placeholder Titolo Modulo"
|
||||||
|
pageTitle="Recupero Proposta CCT da Contratti"
|
||||||
|
pageDescription=""
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
||||||
+351
@@ -0,0 +1,351 @@
|
|||||||
|
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;
|
||||||
|
idDomanda: string;
|
||||||
|
idRicevuta: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** State aggiuntivo per gestire l'autocomplete */
|
||||||
|
interface RecuperoPropostaState {
|
||||||
|
searchText: string;
|
||||||
|
searchResults: ContrattoResult[];
|
||||||
|
isLoading: boolean;
|
||||||
|
isDropdownOpen: boolean;
|
||||||
|
selectedContract: ContrattoResult | null;
|
||||||
|
highlightedIndex: number;
|
||||||
|
errorMessage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsComponentAbstract {
|
||||||
|
/** Stato locale per l'autocomplete (separato dal formData gestito dall'abstract) */
|
||||||
|
private autocompleteState: RecuperoPropostaState = {
|
||||||
|
searchText: '',
|
||||||
|
searchResults: [],
|
||||||
|
isLoading: false,
|
||||||
|
isDropdownOpen: false,
|
||||||
|
selectedContract: null,
|
||||||
|
highlightedIndex: -1,
|
||||||
|
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();
|
||||||
|
|
||||||
|
/** Codice fiscale letto dalla query string (COL0009), usato solo per la chiamata API */
|
||||||
|
private codiceFiscale: string;
|
||||||
|
|
||||||
|
constructor(props: any) {
|
||||||
|
super(props);
|
||||||
|
this.codiceFiscale = QueryParamHelper.getDecodedTextFromQuery('COL0009') ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
override componentDidMount(): void {
|
||||||
|
// Listener per chiudere il dropdown al click fuori dall'autocomplete
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = '';
|
||||||
|
this.forceUpdate();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = new URL(config.apiUrl);
|
||||||
|
url.searchParams.set('cod_fis', this.codiceFiscale);
|
||||||
|
url.searchParams.set('term', searchTerm);
|
||||||
|
|
||||||
|
const credentials = btoa(`${config.apiUsername}:${config.apiPassword}`);
|
||||||
|
|
||||||
|
const response = await fetch(url.toString(), {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Basic ${credentials}`,
|
||||||
|
'X-Api-Key': config.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 ?? '';
|
||||||
|
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;
|
||||||
|
this.autocompleteState.highlightedIndex = -1;
|
||||||
|
this.autocompleteState.errorMessage = '';
|
||||||
|
this.clearSelection();
|
||||||
|
this.forceUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debounce della ricerca
|
||||||
|
if (this.debounceTimer) {
|
||||||
|
clearTimeout(this.debounceTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.length >= config.minCharsForSearch) {
|
||||||
|
this.debounceTimer = setTimeout(() => {
|
||||||
|
this.fetchContratti(text);
|
||||||
|
}, 300);
|
||||||
|
} else {
|
||||||
|
this.autocompleteState.searchResults = [];
|
||||||
|
this.autocompleteState.isDropdownOpen = false;
|
||||||
|
this.autocompleteState.highlightedIndex = -1;
|
||||||
|
this.forceUpdate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gestisce la navigazione da tastiera nel dropdown (frecce, Invio, Escape).
|
||||||
|
*/
|
||||||
|
private handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>): 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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}`;
|
||||||
|
this.autocompleteState.isDropdownOpen = false;
|
||||||
|
this.autocompleteState.searchResults = [];
|
||||||
|
this.autocompleteState.highlightedIndex = -1;
|
||||||
|
|
||||||
|
// Aggiorna il formData con i valori selezionati
|
||||||
|
this.setState({
|
||||||
|
formData: {
|
||||||
|
...this.state.formData,
|
||||||
|
COL0010: contract.idContratto,
|
||||||
|
COL0020: contract.titoloContratto,
|
||||||
|
COL0030: contract.idDomanda,
|
||||||
|
COL0040: 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;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
formData: {
|
||||||
|
...this.state.formData,
|
||||||
|
COL0010: '',
|
||||||
|
COL0020: '',
|
||||||
|
COL0030: '',
|
||||||
|
COL0040: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
id="autocomplete-search"
|
||||||
|
name="autocomplete-search"
|
||||||
|
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>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isLoading && isDropdownOpen && searchResults.length > 0 && (
|
||||||
|
<div className="autocomplete-dropdown">
|
||||||
|
{searchResults.map((contract, index) => (
|
||||||
|
<div
|
||||||
|
key={`${contract.idContratto}_${index}`}
|
||||||
|
className={`autocomplete-dropdown-item${index === highlightedIndex ? ' highlighted' : ''}`}
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
e.preventDefault(); // Previene blur del TextField
|
||||||
|
this.selectContract(contract);
|
||||||
|
}}
|
||||||
|
onMouseEnter={() => {
|
||||||
|
this.autocompleteState.highlightedIndex = index;
|
||||||
|
this.forceUpdate();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="contract-id">[{contract.idContratto}]</span>{' '}
|
||||||
|
<span className="contract-title">{contract.titoloContratto}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isLoading && isDropdownOpen && searchResults.length === 0 && (
|
||||||
|
<div className="autocomplete-dropdown">
|
||||||
|
<div className="autocomplete-no-results">Nessun risultato trovato</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{errorMessage && (
|
||||||
|
<div className="autocomplete-error">{errorMessage}</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>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return new ElixFormsElement(
|
||||||
|
<FluentUI.Label htmlFor="autocomplete-search">Cerca Contratto</FluentUI.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 col0010Value = this.state.formData['COL0010'] ?? '';
|
||||||
|
const col0020Value = this.state.formData['COL0020'] ?? '';
|
||||||
|
const col0030Value = this.state.formData['COL0030'] ?? '';
|
||||||
|
const col0040Value = this.state.formData['COL0040'] ?? '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{this.renderAutocomplete()}
|
||||||
|
|
||||||
|
{/* Campi hidden per i dati del contratto selezionato — verranno inviati nel POST a elixForms */}
|
||||||
|
<input type="hidden" id="COL0010_hidden" name="COL0010" value={col0010Value} />
|
||||||
|
<input type="hidden" id="COL0020_hidden" name="COL0020" value={col0020Value} />
|
||||||
|
<input type="hidden" id="COL0030_hidden" name="COL0030" value={col0030Value} />
|
||||||
|
<input type="hidden" id="COL0040_hidden" name="COL0040" value={col0040Value} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"apiUrl": "https://api.example.com/contratti",
|
||||||
|
"apiUsername": "your-username",
|
||||||
|
"apiPassword": "your-password",
|
||||||
|
"apiKey": "your-api-key",
|
||||||
|
"minCharsForSearch": 3
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
declare module "*.css";
|
||||||
|
declare module "*.svg";
|
||||||
|
declare module "*.png";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/* Global styles - currently empty */
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { StrictMode } from 'react'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import './index.css'
|
||||||
|
import App from './App.jsx'
|
||||||
|
|
||||||
|
const rootElement = document.getElementById('root')
|
||||||
|
if (!rootElement) throw new Error('Root element not found')
|
||||||
|
|
||||||
|
createRoot(rootElement).render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>,
|
||||||
|
)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.base.json",
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../common"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist",
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js';
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
react(),
|
||||||
|
cssInjectedByJsPlugin() // forza l'iniezione del CSS nel JS
|
||||||
|
],
|
||||||
|
build: {
|
||||||
|
outDir: 'dist',
|
||||||
|
cssCodeSplit: false, // evita file CSS separati
|
||||||
|
rollupOptions: {
|
||||||
|
input: 'src/main.jsx',
|
||||||
|
output: {
|
||||||
|
codeSplitting: false, // forza tutto in un singolo bundle
|
||||||
|
manualChunks: undefined, // disabilita la creazione di chunk multipli
|
||||||
|
entryFileNames: 'recupero-proposta-cct-da-contratti.js',
|
||||||
|
assetFileNames: '[name].[ext]'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@common": path.resolve(__dirname, "../common"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
fs: {
|
||||||
|
allow: [
|
||||||
|
".." // permette import fuori dalla cartella del progetto
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user