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