switch to bootstrap italia through AI

This commit is contained in:
2026-07-17 09:53:46 +02:00
parent 99a64f1157
commit 4cd22396d9
16 changed files with 345 additions and 672 deletions
+2 -2
View File
@@ -12,7 +12,7 @@ description: >
## Panoramica ## Panoramica
La cartella `react/common/` contiene la libreria condivisa di componenti React/TypeScript usata da tutte le pagine custom. È basata su **Fluent UI React v8** e implementa un sistema di form con campi dinamici. La cartella `react/common/` contiene la libreria condivisa di componenti React/TypeScript usata da tutte le pagine custom. È basata su **Bootstrap Italia + HTML nativo** e implementa un sistema di form con campi dinamici.
## Architettura dei Componenti ## Architettura dei Componenti
@@ -103,7 +103,7 @@ var myElixFormsReact = new ElixForms.ElixFormsReact({
### Tipi di Campo Supportati ### Tipi di Campo Supportati
| Metodo Factory | Componente FluentUI | HTML Output | | Metodo Factory | Componente Bootstrap Italia | HTML Output |
|---|---|---| |---|---|---|
| `createTextInput` | `TextField` | Text input | | `createTextInput` | `TextField` | Text input |
| `createTextAreaInput` | `TextField` (multiline) | Textarea | | `createTextAreaInput` | `TextField` (multiline) | Textarea |
+1 -1
View File
@@ -50,7 +50,7 @@ npm install -D sass-embedded
} }
``` ```
> **Nota**: React, ReactDOM e FluentUI NON vanno nel package.json della pagina. > **Nota**: React e ReactDOM NON vanno nel package.json della pagina.
> Sono nel `react/package.json` root e vengono risolti via hoisting di npm. > Sono nel `react/package.json` root e vengono risolti via hoisting di npm.
## Step 3: Configurare vite.config.js ## Step 3: Configurare vite.config.js
+2 -9
View File
@@ -130,7 +130,7 @@ Le pagine seguono un layout standard elixForms con classi CSS specifiche:
| `iuTypeString` | Tipo di input stringa | | `iuTypeString` | Tipo di input stringa |
| `iuClearContainer` | Clearfix interno | | `iuClearContainer` | Clearfix interno |
### Classi Input FluentUI Custom ### Classi Input Bootstrap Italia Custom
| Classe | Componente | | Classe | Componente |
|---|---| |---|---|
| `isiportalPartialAdminFormFieldSingleLineText` | TextField singola riga | | `isiportalPartialAdminFormFieldSingleLineText` | TextField singola riga |
@@ -143,20 +143,13 @@ Le pagine seguono un layout standard elixForms con classi CSS specifiche:
`ElixFormsComponent.module.scss`: `ElixFormsComponent.module.scss`:
```scss ```scss
@import '~@fluentui/react/dist/sass/References.scss';
.elixFormsComponent { .elixFormsComponent {
overflow: hidden; overflow: hidden;
padding: 1em; padding: 1em;
color: "[theme:bodyText, default: #323130]";
color: var(--bodyText);
&.teams {
font-family: $ms-font-family-fallbacks;
}
} }
``` ```
> **Nota**: Gli stili usano il **theming di FluentUI** con variabili CSS e placeholder di tema SharePoint. > **Nota**: Gli stili usano classi Bootstrap Italia e CSS del design system UniPR, senza dipendenze da FluentUI.
## Logo e Risorse Esterne ## Logo e Risorse Esterne
+2 -2
View File
@@ -118,13 +118,13 @@ I valori verranno letti da `QueryParamHelper` e pre-popoleranno i campi del form
``` ```
react/ ← npm install qui per dipendenze condivise react/ ← npm install qui per dipendenze condivise
├── node_modules/ ← react, react-dom, @fluentui/react ├── node_modules/ ← react, react-dom
├── package.json ├── package.json
└── scelta-carriera/ ← npm install qui per devDependencies └── scelta-carriera/ ← npm install qui per devDependencies
├── node_modules/ ← vite, eslint, plugin, sass-embedded ├── node_modules/ ← vite, eslint, plugin, sass-embedded
└── package.json └── package.json
``` ```
> **Nota**: Le dipendenze runtime (React, FluentUI) sono nel `package.json` root. > **Nota**: Le dipendenze runtime (React, ReactDOM) sono nel `package.json` root.
> Le dipendenze di sviluppo (Vite, ESLint) sono nel `package.json` della pagina. > Le dipendenze di sviluppo (Vite, ESLint) sono nel `package.json` della pagina.
> npm risolve le dipendenze risalendo la gerarchia delle cartelle (hoisting). > npm risolve le dipendenze risalendo la gerarchia delle cartelle (hoisting).
+4 -5
View File
@@ -12,7 +12,7 @@ description: >
## Panoramica ## Panoramica
Questo progetto è un **monorepo** per la creazione di pagine web React custom destinate alla piattaforma **elixForms** dell'Università di Parma (UniPR). Ogni pagina è un micro-progetto indipendente che produce un **singolo file JS bundle** contenente React, Fluent UI, CSS e il codice custom, da pubblicare su un server remoto. Questo progetto è un **monorepo** per la creazione di pagine web React custom destinate alla piattaforma **elixForms** dell'Università di Parma (UniPR). Ogni pagina è un micro-progetto indipendente che produce un **singolo file JS bundle** contenente React, Bootstrap Italia e il codice custom, da pubblicare su un server remoto.
## Struttura del Workspace ## Struttura del Workspace
@@ -21,7 +21,7 @@ elixforms-custom-pages/ ← Root del workspace (VS Code)
├── .vscode/ ← Configurazioni VS Code (launch, tasks) ├── .vscode/ ← Configurazioni VS Code (launch, tasks)
├── docs/ ← Documentazione (PDF manuali) ├── docs/ ← Documentazione (PDF manuali)
└── react/ ← Root del monorepo React └── react/ ← Root del monorepo React
├── package.json ← Dipendenze condivise (React, ReactDOM, FluentUI) ├── package.json ← Dipendenze condivise (React, ReactDOM)
├── tsconfig.base.json ← Configurazione TypeScript base condivisa ├── tsconfig.base.json ← Configurazione TypeScript base condivisa
├── .gitignore ├── .gitignore
├── common/ ← Libreria condivisa di componenti ElixForms ├── common/ ← Libreria condivisa di componenti ElixForms
@@ -58,7 +58,7 @@ Ogni pagina (es. `scelta-carriera/`) è un progetto Vite completamente autonomo
1. **Importa dalla libreria `common/`** tramite alias `@common` configurato in `vite.config.js` 1. **Importa dalla libreria `common/`** tramite alias `@common` configurato in `vite.config.js`
2. **Ha le proprie dipendenze** nel suo `package.json` (solo devDependencies e plugin Vite) 2. **Ha le proprie dipendenze** nel suo `package.json` (solo devDependencies e plugin Vite)
3. **Condivide le dipendenze runtime** (React, ReactDOM, FluentUI) dal `package.json` root di `react/` 3. **Condivide le dipendenze runtime** (React, ReactDOM) dal `package.json` root di `react/`
4. **Produce un singolo file JS** tramite la configurazione Rollup in `vite.config.js` 4. **Produce un singolo file JS** tramite la configurazione Rollup in `vite.config.js`
5. **Estende il tsconfig base** per avere configurazione TypeScript coerente 5. **Estende il tsconfig base** per avere configurazione TypeScript coerente
@@ -67,7 +67,6 @@ Ogni pagina (es. `scelta-carriera/`) è un progetto Vite completamente autonomo
Le dipendenze runtime sono hoisted al livello `react/`: Le dipendenze runtime sono hoisted al livello `react/`:
- `react` ^19.2.7 - `react` ^19.2.7
- `react-dom` ^19.2.5 - `react-dom` ^19.2.5
- `@fluentui/react` ^8.125.6
## Configurazione TypeScript ## Configurazione TypeScript
@@ -121,7 +120,7 @@ server.fs.allow: [".."] // Permette import da common/
Le pagine caricano CSS dal server remoto elixForms tramite `@import url(...)` in `App.css`: Le pagine caricano CSS dal server remoto elixForms tramite `@import url(...)` in `App.css`:
- CSS da `console-unipr.elixforms.it` per il design system dell'università - CSS da `console-unipr.elixforms.it` per il design system dell'università
- I componenti FluentUI usano stili inline/theme nativi - I componenti Bootstrap Italia usano classi HTML e CSS del design system UniPR
- SCSS modules sono usati in `common/` per stili dei componenti condivisi - SCSS modules sono usati in `common/` per stili dei componenti condivisi
- `sass-embedded` è installato come devDependency per supporto SCSS - `sass-embedded` è installato come devDependency per supporto SCSS
@@ -106,9 +106,6 @@ import { IElixFormsComponentProperties } from './IElixFormsComponentProperties';
### Import di Moduli ### Import di Moduli
```tsx ```tsx
// Fluent UI - import tutto il namespace
import * as FluentUI from '@fluentui/react';
// React - import specifici // React - import specifici
import { Component, type FormEvent, type JSX, useState } from 'react'; import { Component, type FormEvent, type JSX, useState } from 'react';
import React from 'react'; import React from 'react';
@@ -167,8 +164,8 @@ declare module "*.png";
- Usare `this.setState` con spread operator per update immutabili - Usare `this.setState` con spread operator per update immutabili
- I checkbox mantengono un `Map<string, number[]>` separato - I checkbox mantengono un `Map<string, number[]>` separato
5. **Fluent UI**: 5. **Bootstrap Italia**:
- Usare Fluent UI React v8 (`@fluentui/react`) - Usare markup HTML nativo con classi Bootstrap Italia e del design system UniPR
- Componenti: `TextField`, `Checkbox`, `ChoiceGroup`, `Dropdown`, `Label`, `Stack` - Per i campi testuali usare `input`, `textarea`, `select` con classi `form-control`/`form-select`
- Le opzioni radio usano `IChoiceGroupOption[]` - Per i checkbox/radio usare `input type="checkbox"`/`input type="radio"` con classi `form-check-input`
- Le opzioni dropdown usano `IDropdownOption[]` - Il layout dei campi deve rimanere compatibile con `ElixFormsElement`
+4 -4
View File
@@ -105,7 +105,7 @@ npm run lint
``` ```
dist/ dist/
<nome-pagina>.js ← Bundle unico (React + FluentUI + CSS + codice) <nome-pagina>.js ← Bundle unico (React + CSS + codice)
<nome-pagina>.js.map ← Source map <nome-pagina>.js.map ← Source map
*.png, *.svg ← Asset referenziati nel codice *.png, *.svg ← Asset referenziati nel codice
``` ```
@@ -121,10 +121,10 @@ dist/
- **Fix**: Verificare `resolve.alias` e che la cartella `common/` esista - **Fix**: Verificare `resolve.alias` e che la cartella `common/` esista
### Bundle troppo grande ### Bundle troppo grande
- **Causa**: FluentUI include molti componenti - **Causa**: l'uso di componenti UI pesanti o CSS non ottimizzati
- **Fix possibili**: - **Fix possibili**:
- Tree shaking (import specifici: `import { TextField } from '@fluentui/react'`) - Mantenere i CSS locali limitati e affidarsi ai CSS remoti del design system
- Considerare `@fluentui/react-components` (v9) per bundle più piccoli - Evitare dipendenze UI pesanti per le pagine custom
- Esternalizzare React se la pagina host lo fornisce già - Esternalizzare React se la pagina host lo fornisce già
### CSS non applicato in produzione ### CSS non applicato in produzione
+21 -1
View File
@@ -7,7 +7,27 @@
"": { "": {
"name": "@elixforms/common", "name": "@elixforms/common",
"version": "0.0.1", "version": "0.0.1",
"license": "ISC" "license": "ISC",
"devDependencies": {
"@types/react": "^19.2.17"
}
},
"node_modules/@types/react": {
"version": "19.2.17",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
"integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
"dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT"
} }
} }
} }
+4 -1
View File
@@ -5,5 +5,8 @@
"license": "ISC", "license": "ISC",
"author": "", "author": "",
"type": "commonjs", "type": "commonjs",
"main": "index.js" "main": "index.js",
"devDependencies": {
"@types/react": "^19.2.17"
}
} }
+17 -23
View File
@@ -1,34 +1,28 @@
@import '~@fluentui/react/dist/sass/References.scss';
.elixFormsComponent { .elixFormsComponent {
overflow: hidden; overflow: hidden;
padding: 1em; padding: 1em;
color: "[theme:bodyText, default: #323130]";
color: var(--bodyText);
&.teams {
font-family: $ms-font-family-fallbacks;
}
} }
.welcome { .autocompleteDropdown {
text-align: center; margin-top: 0.5rem;
border: 1px solid #c9d1d9;
border-radius: 0.5rem;
background: #fff;
box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.08);
} }
.welcomeImage { .autocompleteItem {
width: 100%; padding: 0.75rem 1rem;
max-width: 420px; cursor: pointer;
} }
.links { .autocompleteItem:hover,
a { .autocompleteItem.isHighlighted {
text-decoration: none; background-color: #f5f7fa;
color: "[theme:link, default:#03787c]"; }
color: var(--link); // note: CSS Custom Properties support is limited to modern browsers only
&:hover { .autocompleteStatus {
text-decoration: underline; margin-top: 0.5rem;
color: "[theme:linkHovered, default: #014446]"; font-size: 0.95rem;
color: var(--linkHovered); // note: CSS Custom Properties support is limited to modern browsers only color: #5c6f82;
}
}
} }
+179 -207
View File
@@ -1,10 +1,9 @@
import './App.css' import './App.css';
import * as FluentUI from '@fluentui/react';
import type { IElixFormsComponentFormState } from './IElixFormsComponentFormState'; import type { IElixFormsComponentFormState } from './IElixFormsComponentFormState';
import type { IElixFormsComponentProperties } from './IElixFormsComponentProperties'; import type { IElixFormsComponentProperties } from './IElixFormsComponentProperties';
import { ElixFormsElement } from './ElixFormsElement'; import { ElixFormsElement } from './ElixFormsElement';
import { QueryParamHelper } from './QueryParamHelper'; import { QueryParamHelper } from './QueryParamHelper';
import { Component, type FormEvent, type JSX, useState } from 'react'; import { Component, type JSX } from 'react';
import React from 'react'; import React from 'react';
import type { IElixFormsComponentCustomFormFieldFactory } from './IElixFormsComponentCustomFormFieldFactory'; import type { IElixFormsComponentCustomFormFieldFactory } from './IElixFormsComponentCustomFormFieldFactory';
import type { ElixFormsCheckboxOption, ElixFormsDropdownOption, ElixFormsRadioOption } from './ElixFormsTypes'; import type { ElixFormsCheckboxOption, ElixFormsDropdownOption, ElixFormsRadioOption } from './ElixFormsTypes';
@@ -16,57 +15,26 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
this.state = { this.state = {
formData: {} formData: {}
}; };
// const [formData, setFormData] = useState({});
// const handleAnyInputChange = (name: string) => (event: any, valueOrOption: any) => { //ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement> | SyntheticEvent<HTMLElement, Event>
// let value: any = null;
// // Fluent UI Dropdown passes (event, option)
// if (valueOrOption && valueOrOption.key !== undefined) {
// value = valueOrOption.key;
// }
// // Fluent UI TextField passes (event, newValue)
// else if (typeof valueOrOption === "string") {
// value = valueOrOption;
// }
// // Fluent UI Checkbox passes (event, checked)
// else if (typeof valueOrOption === "boolean") {
// value = valueOrOption;
// }
// // Native HTML inputs
// else if (event?.target) {
// value = event.target.type === "checkbox"
// ? event.target.checked
// : event.target.value;
// }
// setFormData(prev => ({
// ...prev,
// [name]: value
// }));
// console.log(`Input ${name} changed to ${value}. Current form data:`, JSON.stringify(formData));
// };
} }
private checkboxValues = new Map<string, number[]>(); private checkboxValues = new Map<string, number[]>();
private mandatoryFormFieldNames: string[] = [ private mandatoryFormFieldNames: string[] = [
"RWE2_MODULE_ID", 'RWE2_MODULE_ID',
"RWE2_REQUEST_ID", 'RWE2_REQUEST_ID',
"custom-workflow-back-url", 'custom-workflow-back-url',
"custom-workflow-generic-id", 'custom-workflow-generic-id',
"custom-workflow-current-tabrel-genid", 'custom-workflow-current-tabrel-genid',
"custom-workflow-source-field", 'custom-workflow-source-field',
"crc", 'crc',
"MODULE_TESTMODE_KEY", 'MODULE_TESTMODE_KEY',
"ELANG" 'ELANG'
]; ];
private createMandatoryFormFields(queryParams: URLSearchParams): JSX.Element { private createMandatoryFormFields(queryParams: URLSearchParams): JSX.Element {
const hiddenInputs: JSX.Element[] = []; const hiddenInputs: JSX.Element[] = [];
// Create a hidden input for each mandatory Elix parameter
this.mandatoryFormFieldNames.forEach(paramName => { this.mandatoryFormFieldNames.forEach(paramName => {
const value = queryParams.get(paramName) ?? "";
// Create the input element
hiddenInputs.push(this.createHiddenInput(paramName)); hiddenInputs.push(this.createHiddenInput(paramName));
}); });
@@ -74,7 +42,7 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
} }
private createAdditionalFormFields(): React.ReactElement { private createAdditionalFormFields(): React.ReactElement {
const schema = JSON.parse(this.props.additionalFieldsJson || "[]"); const schema = JSON.parse(this.props.additionalFieldsJson || '[]');
return ( return (
<> <>
@@ -125,10 +93,7 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
public override render(): React.ReactElement<IElixFormsComponentProperties> { public override render(): React.ReactElement<IElixFormsComponentProperties> {
const { const {
description, description,
isDarkTheme,
environmentMessage, environmentMessage,
hasTeamsContext,
userDisplayName,
additionalFieldsJson, additionalFieldsJson,
headerTitle, headerTitle,
pageTitle, pageTitle,
@@ -151,7 +116,10 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
<div className="console_header"> <div className="console_header">
<div className="leftDiv"> <div className="leftDiv">
<h1>elixForms</h1> <h1>elixForms</h1>
<div className="logo"><a href="https://www.unipr.it/" title="Torna alla homepage dell'Università: di Parma"><img src="https://console-unipr.elixforms.it/elixFormsCustom/images/logo.png" alt="Logo Università: di Parma" /></a> <div className="logo">
<a href="https://www.unipr.it/" title="Torna alla homepage dell'Università di Parma">
<img src="https://console-unipr.elixforms.it/elixFormsCustom/images/logo.png" alt="Logo Università di Parma" />
</a>
</div> </div>
</div> </div>
<div className="rightDiv"> <div className="rightDiv">
@@ -176,29 +144,28 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
</div> </div>
)} )}
<div className="mainview"> <div className="mainview">
<div className="container"> <div className="container py-4">
{pageTitle && <h1>{pageTitle}</h1>} {pageTitle && <h1 className="mb-3">{pageTitle}</h1>}
{heroImageSrc && <img src={heroImageSrc} alt="Hero Image" className="hero-image" />} {heroImageSrc && <img src={heroImageSrc} alt="Hero Image" className="hero-image img-fluid mb-3" />}
{pageDescription && <p>{pageDescription}</p>} {pageDescription && <p className="lead">{pageDescription}</p>}
{environmentMessage && <div>{environmentMessage}</div>} {environmentMessage && <div className="alert alert-info">{environmentMessage}</div>}
{description && <div>{description}</div>} {description && <div className="mb-3">{description}</div>}
{hasAuthenticationError ? ( {hasAuthenticationError ? (
<FluentUI.MessageBar messageBarType={FluentUI.MessageBarType.error} isMultiline={false}> <div className="alert alert-danger" role="alert">
Accesso non autorizzato. Parametri obbligatori mancanti ({missingMandatoryParams.join(', ')}). Accesso non autorizzato. Parametri obbligatori mancanti ({missingMandatoryParams.join(', ')}).
</FluentUI.MessageBar> </div>
) : ( ) : (
<> <>
{this.renderExtraContentPre()} {this.renderExtraContentPre()}
<form action="https://procedure.unipr.it/rwe2/ComeBackToElixAndSave" method="post" acceptCharset="ISO-8859-1"> <form action="https://procedure.unipr.it/rwe2/ComeBackToElixAndSave" method="post" acceptCharset="ISO-8859-1" className="mt-3">
{this.createMandatoryFormFields(queryParams)} {this.createMandatoryFormFields(queryParams)}
{this.createAdditionalFormFields()} {this.createAdditionalFormFields()}
{this.renderCustomFormFields()} {this.renderCustomFormFields()}
<br /> <div className="mt-4 d-flex justify-content-start">
<div className='submit'> <button type="submit" className="btn btn-primary">{submitDescription ?? 'Invia'}</button>
<input type="submit" value={submitDescription ?? 'Submit'} />
</div> </div>
</form> </form>
@@ -240,158 +207,180 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
this.checkboxValues.set(paramName, checkedValues); this.checkboxValues.set(paramName, checkedValues);
} }
const checkboxes: React.ReactElement[] = [];
const stackTokens = { childrenGap: 10 };
values.forEach((entry, entryIndex) => {
const id = `${paramName}_${entryIndex}`;
checkboxes.push(
<FluentUI.Checkbox title={label} key={id} id={id} label={entry.label} required={entry.required ?? false} defaultChecked={checkedValues.indexOf(entryIndex) !== -1} className='isiportalPartialAdminCheckboxFieldItem'
onChange={(event, checked) => {
const currentCheckboxValues = this.checkboxValues.get(paramName)!;
const checkboxIndex = currentCheckboxValues.indexOf(entryIndex);
if (checked && checkboxIndex === -1) {
currentCheckboxValues.push(entryIndex);
} else if (!checked && checkboxIndex !== -1) {
currentCheckboxValues.splice(checkboxIndex, 1);
}
const newValue = currentCheckboxValues.join(',').trim();
this.setState({ formData: { ...this.state.formData, [paramName]: newValue } }, () => {
console.log(`Checkbox ${id} changed to ${checked}. Current values for ${paramName}: ${this.state.formData[paramName]}`);
});
}}
/>
);
});
const currentValue = this.state.formData[paramName] !== undefined const currentValue = this.state.formData[paramName] !== undefined
? this.state.formData[paramName] ? this.state.formData[paramName]
: (this.checkboxValues.get(paramName)?.join(',').trim() ?? ''); : (this.checkboxValues.get(paramName)?.join(',').trim() ?? '');
const selectedValues = (currentValue || '').split(',').filter(Boolean).map((value: string) => Number(value));
return new ElixFormsElement( return new ElixFormsElement(
<><FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label></>, <label className="form-label fw-semibold" htmlFor={paramName}>{label}</label>,
<><FluentUI.Stack tokens={stackTokens}> <>
{checkboxes} <div className="d-flex flex-column gap-2" role="group" aria-label={label}>
</FluentUI.Stack> {values.map((entry, entryIndex) => {
<input type='hidden' id={paramName} name={paramName} value={currentValue} /> const id = `${paramName}_${entryIndex}`;
const isChecked = selectedValues.includes(entryIndex);
return (
<div className="form-check" key={id}>
<input
id={id}
className="form-check-input"
type="checkbox"
checked={isChecked}
onChange={(event) => {
const currentCheckboxValues = this.checkboxValues.get(paramName) ?? [];
const checkboxIndex = currentCheckboxValues.indexOf(entryIndex);
const nextValues = [...currentCheckboxValues];
if (event.target.checked && checkboxIndex === -1) {
nextValues.push(entryIndex);
} else if (!event.target.checked && checkboxIndex !== -1) {
nextValues.splice(checkboxIndex, 1);
}
this.checkboxValues.set(paramName, nextValues);
const newValue = nextValues.join(',').trim();
this.setState({ formData: { ...this.state.formData, [paramName]: newValue } });
}}
/>
<label className="form-check-label" htmlFor={id}>{entry.label}</label>
</div>
);
})}
</div>
<input type="hidden" id={paramName} name={paramName} value={currentValue} />
</> </>
).render(); ).render();
} }
private createRadioInput(paramName: string, label: string, values: Array<ElixFormsRadioOption>, required: boolean = false): JSX.Element { private createRadioInput(paramName: string, label: string, values: Array<ElixFormsRadioOption>, required: boolean = false): JSX.Element {
const radioValue = QueryParamHelper.getOptionFromQuery(paramName); const currentValue = this.state.formData[paramName] !== undefined
const radios: FluentUI.IChoiceGroupOption[] = []; ? String(this.state.formData[paramName])
: String(QueryParamHelper.getOptionFromQuery(paramName) ?? '');
values.forEach((entry, _) => {
const id = `${paramName}_${entry.value}`;
radios.push(
{
key: id,
text: entry.label,
id: id,
name: paramName,
value: entry.value,
className: 'isiportalPartialAdminFormFieldRadio'
}
);
});
return new ElixFormsElement( return new ElixFormsElement(
<></>, <span className="form-label fw-semibold">{label}</span>,
<FluentUI.ChoiceGroup <div className="d-flex flex-column gap-2" role="radiogroup" aria-label={label}>
{values.map((entry) => {
const id = `${paramName}_${entry.value}`;
return (
<div className="form-check" key={id}>
<input
id={id}
className="form-check-input"
type="radio"
name={paramName} name={paramName}
label={label} value={entry.value}
options={radios} checked={currentValue === String(entry.value)}
required={required} onChange={() => {
defaultSelectedKey={`${paramName}_${radioValue}`} this.setState({ formData: { ...this.state.formData, [paramName]: String(entry.value) } });
onChange={(event, option) => {
const value = option?.value ?? '';
this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () =>
console.log(`Radio ${option?.id} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`)
);
}} }}
required={required}
/> />
<label className="form-check-label" htmlFor={id}>{entry.label}</label>
</div>
);
})}
</div>
).render(); ).render();
} }
private createBooleanInput(paramName: string, label: string, required: boolean = false): JSX.Element { private createBooleanInput(paramName: string, label: string, required: boolean = false): JSX.Element {
const boolValue = QueryParamHelper.getOptionFromQuery(paramName); const currentValue = this.state.formData[paramName] !== undefined
const radios: FluentUI.IChoiceGroupOption[] = []; ? String(this.state.formData[paramName])
: String(QueryParamHelper.getOptionFromQuery(paramName) ?? '');
[{ "value": "true", "label": "Sì" }, { "value": "false", "label": "No" }].forEach((val) => {
const id = `${paramName}_${val.value}`;
radios.push(
{
key: id,
text: val.label,
id: id,
name: paramName,
value: val.value,
className: 'isiportalPartialAdminFormFieldRadio'
}
);
});
return new ElixFormsElement( return new ElixFormsElement(
<></>, <span className="form-label fw-semibold">{label}</span>,
<FluentUI.ChoiceGroup <div className="d-flex flex-column gap-2" role="radiogroup" aria-label={label}>
{[{ value: 'true', label: 'Sì' }, { value: 'false', label: 'No' }].map((val) => {
const id = `${paramName}_${val.value}`;
return (
<div className="form-check" key={id}>
<input
id={id}
className="form-check-input"
type="radio"
name={paramName} name={paramName}
label={label} value={val.value}
options={radios} checked={currentValue === val.value}
required={required} onChange={() => {
defaultSelectedKey={`${paramName}_${boolValue}`} this.setState({ formData: { ...this.state.formData, [paramName]: val.value } });
onChange={(event, option) => {
const value = option?.value ?? '';
this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () =>
console.log(`Boolean ${option?.id} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`)
);
}} }}
required={required}
/> />
<label className="form-check-label" htmlFor={id}>{val.label}</label>
</div>
);
})}
</div>
).render(); ).render();
} }
private createTextAreaInput(paramName: string, label: string, required: boolean = false): JSX.Element { private createTextAreaInput(paramName: string, label: string, required: boolean = false): JSX.Element {
const textareaValue = QueryParamHelper.getDecodedTextFromQuery(paramName) ?? ""; const currentValue = this.state.formData[paramName] !== undefined
? String(this.state.formData[paramName])
: (QueryParamHelper.getDecodedTextFromQuery(paramName) ?? '');
return new ElixFormsElement( return new ElixFormsElement(
<FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label>, <label className="form-label fw-semibold" htmlFor={paramName}>{label}</label>,
<FluentUI.TextField id={paramName} name={paramName} defaultValue={textareaValue} multiline rows={4} required={required} className='isiportalPartialAdminFormFieldMultiLineText' <textarea
onChange={(event, value) => { id={paramName}
this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () => name={paramName}
console.log(`TextArea ${paramName} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`) className="form-control"
); rows={4}
required={required}
value={currentValue}
onChange={(event) => {
this.setState({ formData: { ...this.state.formData, [paramName]: event.target.value } });
}} }}
/> />
).render(); ).render();
} }
private createTextInput(paramName: string, label: string, required: boolean = false): JSX.Element { private createTextInput(paramName: string, label: string, required: boolean = false): JSX.Element {
const textValue = QueryParamHelper.getDecodedTextFromQuery(paramName) ?? ""; const currentValue = this.state.formData[paramName] !== undefined
? String(this.state.formData[paramName])
: (QueryParamHelper.getDecodedTextFromQuery(paramName) ?? '');
return new ElixFormsElement( return new ElixFormsElement(
<FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label>, <label className="form-label fw-semibold" htmlFor={paramName}>{label}</label>,
<FluentUI.TextField id={paramName} name={paramName} defaultValue={textValue} required={required} className='isiportalPartialAdminFormFieldSingleLineText' <input
onChange={(event, value) => { id={paramName}
this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () => name={paramName}
console.log(`TextInput ${paramName} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`) className="form-control"
); type="text"
required={required}
value={currentValue}
onChange={(event) => {
this.setState({ formData: { ...this.state.formData, [paramName]: event.target.value } });
}} }}
/> />
).render(); ).render();
} }
private createHiddenInput(paramName: string): JSX.Element { private createHiddenInput(paramName: string): JSX.Element {
const textValue = QueryParamHelper.getDecodedTextFromQuery(paramName) ?? ""; const textValue = QueryParamHelper.getDecodedTextFromQuery(paramName) ?? '';
return <input type="hidden" id={`${paramName}_hidden`} name={paramName} defaultValue={textValue} />; return <input type="hidden" id={`${paramName}_hidden`} name={paramName} value={textValue} />;
} }
private createNumberInput(paramName: string, label: string, required: boolean = false): JSX.Element { private createNumberInput(paramName: string, label: string, required: boolean = false): JSX.Element {
const textValue = QueryParamHelper.getDecodedTextFromQuery(paramName) ?? ""; const currentValue = this.state.formData[paramName] !== undefined
? String(this.state.formData[paramName])
: (QueryParamHelper.getDecodedTextFromQuery(paramName) ?? '');
return new ElixFormsElement( return new ElixFormsElement(
<FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label>, <label className="form-label fw-semibold" htmlFor={paramName}>{label}</label>,
<FluentUI.TextField id={paramName} name={paramName} defaultValue={textValue} type="number" required={required} className='isiportalPartialAdminFormFieldSingleLineText' <input
onChange={(event, value) => { id={paramName}
this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () => name={paramName}
console.log(`NumberInput ${paramName} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`) className="form-control"
); type="number"
required={required}
value={currentValue}
onChange={(event) => {
this.setState({ formData: { ...this.state.formData, [paramName]: event.target.value } });
}} }}
/> />
).render(); ).render();
@@ -400,72 +389,55 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
private createDropdownInput(paramName: string, label: string, values: Array<ElixFormsDropdownOption>, required: boolean = false): JSX.Element { private createDropdownInput(paramName: string, label: string, values: Array<ElixFormsDropdownOption>, required: boolean = false): JSX.Element {
const queryValue = QueryParamHelper.getOptionFromQuery(paramName); const queryValue = QueryParamHelper.getOptionFromQuery(paramName);
const currentValue = this.state.formData[paramName] !== undefined const currentValue = this.state.formData[paramName] !== undefined
? this.state.formData[paramName] ? String(this.state.formData[paramName])
: (queryValue?.toString() ?? ''); : (queryValue?.toString() ?? '');
const options: FluentUI.IDropdownOption[] = [];
values.forEach(entry => {
options.push(
{
key: entry.value.toString(),
text: entry.label
}
);
});
return new ElixFormsElement( return new ElixFormsElement(
<FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label>, <label className="form-label fw-semibold" htmlFor={paramName}>{label}</label>,
<> <select
<FluentUI.Dropdown
id={paramName} id={paramName}
options={options} name={paramName}
selectedKey={currentValue} className="form-select"
placeholder='---'
required={required} required={required}
className='isiportalPartialAdminFormFieldSelect' value={currentValue}
onChange={(event, option) => { onChange={(event) => {
if (option) { this.setState({ formData: { ...this.state.formData, [paramName]: event.target.value } });
const value = option.key.toString();
this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () =>
console.log(`Dropdown ${paramName} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`)
);
}
}} }}
/> >
<input type="hidden" name={paramName} id={paramName + '_hidden'} value={currentValue} /> <option value="">Seleziona...</option>
</> {values.map(entry => (
<option key={entry.value} value={entry.value}>{entry.label}</option>
))}
</select>
).render(); ).render();
} }
private renderField(field: any): JSX.Element { private renderField(field: any): JSX.Element {
//const value = this.state.formData[field.key] || "";
switch (field.type) { switch (field.type) {
case "text": case 'text':
return this.createTextInput(field.key, field.label, field.required ?? false); return this.createTextInput(field.key, field.label, field.required ?? false);
case "textarea": case 'textarea':
return this.createTextAreaInput(field.key, field.label, field.required ?? false); return this.createTextAreaInput(field.key, field.label, field.required ?? false);
case "radio": case 'radio':
return this.createRadioInput(field.key, field.label, return this.createRadioInput(field.key, field.label,
new Array<ElixFormsRadioOption>().concat(...field.options.map((o: any) => [[o.value, o.label]])), field.options.map((o: any) => ({ value: o.value, label: o.label })),
field.required ?? false); field.required ?? false);
case "boolean": case 'boolean':
return this.createBooleanInput(field.key, field.label, field.required ?? false); return this.createBooleanInput(field.key, field.label, field.required ?? false);
case "checkbox": case 'checkbox':
return this.createCheckboxInput(field.key, field.label, return this.createCheckboxInput(field.key, field.label,
new Array<ElixFormsCheckboxOption>().concat(...field.options.map((o: any) => [[o.value, o.label, o.required ?? false]]))); field.options.map((o: any) => ({ value: o.value, label: o.label, required: o.required ?? false })));
case "number": case 'number':
return this.createNumberInput(field.key, field.label, field.required ?? false); return this.createNumberInput(field.key, field.label, field.required ?? false);
case "dropdown": case 'dropdown':
return this.createDropdownInput(field.key, field.label, return this.createDropdownInput(field.key, field.label,
new Array<ElixFormsDropdownOption>().concat(...field.options.map((o: any) => [[o.value, o.label]])), field.options.map((o: any) => ({ value: o.value, label: o.label })),
field.required ?? false); field.required ?? false);
default: default:
+27 -13
View File
@@ -1,14 +1,13 @@
import React, { type JSX } from "react"; import React, { type JSX } from 'react';
// Class to encapsulate the label and input elements
export class ElixFormsElement { export class ElixFormsElement {
label: JSX.Element; label: JSX.Element;
input: JSX.Element; input: JSX.Element;
separator: string; separator: string;
constructor(labelElement: React.ReactElement, inputElement: React.ReactElement, separator: string = "")
{ constructor(labelElement: React.ReactElement, inputElement: React.ReactElement, separator: string = '') {
if (!labelElement || !inputElement) { if (!labelElement || !inputElement) {
throw new Error("labelElement and inputElement are required."); throw new Error('labelElement and inputElement are required.');
} }
this.label = labelElement; this.label = labelElement;
@@ -16,15 +15,30 @@ export class ElixFormsElement {
this.separator = separator; this.separator = separator;
} }
// Optional: render both elements together render(): JSX.Element {
render() : JSX.Element { return (
return (<><div className="iuFieldContainer"> <div className="iuFieldContainer form-group row mb-3 align-items-start">
<div className="attrDisplay_left attrDisplay_label"><div className="iuLabelContainer"><div className="attrDisplay_center"><div className="attrDisplay_middle"> <div className="attrDisplay_left attrDisplay_label col-sm-4">
<div className="iuLabelContainer">
<div className="attrDisplay_center">
<div className="attrDisplay_middle">
{this.label}{this.separator} {this.label}{this.separator}
</div></div><div className="iuClearContainer">&nbsp;</div></div></div> </div>
<div className="attrDisplay_right attrDisplay_input"><div className="iuInputContainer iuTypeString"><div className="attrDisplay_center"><div className="attrDisplay_middle"> </div>
<div className="iuClearContainer">&nbsp;</div>
</div>
</div>
<div className="attrDisplay_right attrDisplay_input col-sm-8">
<div className="iuInputContainer iuTypeString">
<div className="attrDisplay_center">
<div className="attrDisplay_middle">
{this.input} {this.input}
</div></div><div className="iuClearContainer">&nbsp;</div></div></div> </div>
</div></>); </div>
<div className="iuClearContainer">&nbsp;</div>
</div>
</div>
</div>
);
} }
} }
-262
View File
@@ -5,266 +5,10 @@
"packages": { "packages": {
"": { "": {
"dependencies": { "dependencies": {
"@fluentui/react": "^8.125.6",
"react": "^19.2.7", "react": "^19.2.7",
"react-dom": "^19.2.5" "react-dom": "^19.2.5"
} }
}, },
"node_modules/@fluentui/date-time-utilities": {
"version": "8.6.11",
"resolved": "https://registry.npmjs.org/@fluentui/date-time-utilities/-/date-time-utilities-8.6.11.tgz",
"integrity": "sha512-zq49tveFzmzwgaJ73rVvxu9+rqhPBIAJSbevciIQnmvv6dlh2GzZcL14Zevk9QV+q6CWaF6yzvhT11E2TpAv8Q==",
"license": "MIT",
"dependencies": {
"@fluentui/set-version": "^8.2.24",
"tslib": "^2.1.0"
}
},
"node_modules/@fluentui/dom-utilities": {
"version": "2.3.10",
"resolved": "https://registry.npmjs.org/@fluentui/dom-utilities/-/dom-utilities-2.3.10.tgz",
"integrity": "sha512-6WDImiLqTOpkEtfUKSStcTDpzmJfL6ZammomcjawN9xH/8u8G3Hx72CIt2MNck9giw/oUlNLJFdWRAjeP3rmPQ==",
"license": "MIT",
"dependencies": {
"@fluentui/set-version": "^8.2.24",
"tslib": "^2.1.0"
}
},
"node_modules/@fluentui/font-icons-mdl2": {
"version": "8.5.74",
"resolved": "https://registry.npmjs.org/@fluentui/font-icons-mdl2/-/font-icons-mdl2-8.5.74.tgz",
"integrity": "sha512-q3QiQ2CuFdMjtiP9U5+Qi8LMhxKqWEi7C8R+O6SuaGbGS+3YOO/1iuenhKtt961OHn5ev/0M4j4okUZ1112GSw==",
"license": "MIT",
"dependencies": {
"@fluentui/set-version": "^8.2.24",
"@fluentui/style-utilities": "^8.15.2",
"@fluentui/utilities": "^8.17.2",
"tslib": "^2.1.0"
}
},
"node_modules/@fluentui/foundation-legacy": {
"version": "8.6.7",
"resolved": "https://registry.npmjs.org/@fluentui/foundation-legacy/-/foundation-legacy-8.6.7.tgz",
"integrity": "sha512-mu1wHlkom+XT+mlaGe7GW4eIZeIT9OjgdXg2Az/P670N7JA1OKcjHd8F/9ktMb5FGrXJ287AjQDNCDfWnjnCYA==",
"license": "MIT",
"dependencies": {
"@fluentui/merge-styles": "^8.6.14",
"@fluentui/set-version": "^8.2.24",
"@fluentui/style-utilities": "^8.15.2",
"@fluentui/utilities": "^8.17.2",
"tslib": "^2.1.0"
},
"peerDependencies": {
"@types/react": ">=16.8.0 <20.0.0",
"react": ">=16.8.0 <20.0.0"
}
},
"node_modules/@fluentui/keyboard-key": {
"version": "0.4.23",
"resolved": "https://registry.npmjs.org/@fluentui/keyboard-key/-/keyboard-key-0.4.23.tgz",
"integrity": "sha512-9GXeyUqNJUdg5JiQUZeGPiKnRzMRi9YEUn1l9zq6X/imYdMhxHrxpVZS12129cBfgvPyxt9ceJpywSfmLWqlKA==",
"license": "MIT",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/@fluentui/merge-styles": {
"version": "8.6.14",
"resolved": "https://registry.npmjs.org/@fluentui/merge-styles/-/merge-styles-8.6.14.tgz",
"integrity": "sha512-vghuHFAfQgS9WLIIs4kgDOCh/DHd5vGIddP4/bzposhlAVLZR6wUBqldm9AuCdY88r5LyCRMavVJLV+Up3xdvA==",
"license": "MIT",
"dependencies": {
"@fluentui/set-version": "^8.2.24",
"tslib": "^2.1.0"
}
},
"node_modules/@fluentui/react": {
"version": "8.125.7",
"resolved": "https://registry.npmjs.org/@fluentui/react/-/react-8.125.7.tgz",
"integrity": "sha512-MsputVjgAZEcSzfr6nXBghfZnqu+GKT4NCBwLeSL8yyQEugUxyZJNp4TVZblGdMognyRDbobNn+/zIhEK4sxNQ==",
"license": "MIT",
"dependencies": {
"@fluentui/date-time-utilities": "^8.6.11",
"@fluentui/font-icons-mdl2": "^8.5.74",
"@fluentui/foundation-legacy": "^8.6.7",
"@fluentui/merge-styles": "^8.6.14",
"@fluentui/react-focus": "^8.10.7",
"@fluentui/react-hooks": "^8.10.2",
"@fluentui/react-portal-compat-context": "^9.0.15",
"@fluentui/react-window-provider": "^2.3.2",
"@fluentui/set-version": "^8.2.24",
"@fluentui/style-utilities": "^8.15.2",
"@fluentui/theme": "^2.7.2",
"@fluentui/utilities": "^8.17.2",
"@microsoft/load-themed-styles": "^1.10.26",
"tslib": "^2.1.0"
},
"peerDependencies": {
"@types/react": ">=16.8.0 <20.0.0",
"@types/react-dom": ">=16.8.0 <20.0.0",
"react": ">=16.8.0 <20.0.0",
"react-dom": ">=16.8.0 <20.0.0"
}
},
"node_modules/@fluentui/react-focus": {
"version": "8.10.7",
"resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-8.10.7.tgz",
"integrity": "sha512-Uf0wliddeVPEYylNvbBqYu2V2ofCQ/YhC5JHIFT4RwBkZil1Ta3X+9EFhJMO1LejyLXTKnMMimPxdPS1XzLTGQ==",
"license": "MIT",
"dependencies": {
"@fluentui/keyboard-key": "^0.4.23",
"@fluentui/merge-styles": "^8.6.14",
"@fluentui/set-version": "^8.2.24",
"@fluentui/style-utilities": "^8.15.2",
"@fluentui/utilities": "^8.17.2",
"tslib": "^2.1.0"
},
"peerDependencies": {
"@types/react": ">=16.8.0 <20.0.0",
"react": ">=16.8.0 <20.0.0"
}
},
"node_modules/@fluentui/react-hooks": {
"version": "8.10.2",
"resolved": "https://registry.npmjs.org/@fluentui/react-hooks/-/react-hooks-8.10.2.tgz",
"integrity": "sha512-HAd5cX50yKW/LljWlwt+FpSpdS/pNJutk9kMb7FyzxfoGBulL7sj6vX2HvxhSKyJMRKuTstXTdfJmsh22+3W3w==",
"license": "MIT",
"dependencies": {
"@fluentui/react-window-provider": "^2.3.2",
"@fluentui/set-version": "^8.2.24",
"@fluentui/utilities": "^8.17.2",
"tslib": "^2.1.0"
},
"peerDependencies": {
"@types/react": ">=16.8.0 <20.0.0",
"react": ">=16.8.0 <20.0.0"
}
},
"node_modules/@fluentui/react-portal-compat-context": {
"version": "9.0.15",
"resolved": "https://registry.npmjs.org/@fluentui/react-portal-compat-context/-/react-portal-compat-context-9.0.15.tgz",
"integrity": "sha512-DpV+qtFvM3dmH1j8ZD+YcM5vaTvmQPHUAx6tQnnmIoYJWs2R0wU/L5p2EajXy7zSg74jrDbDRxzaziamoOaJdg==",
"license": "MIT",
"dependencies": {
"@swc/helpers": "^0.5.1"
},
"peerDependencies": {
"@types/react": ">=16.14.0 <20.0.0",
"react": ">=16.14.0 <20.0.0"
}
},
"node_modules/@fluentui/react-window-provider": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/@fluentui/react-window-provider/-/react-window-provider-2.3.2.tgz",
"integrity": "sha512-T15zFPIWr9De8hNkapne7YyvcxclyTK2bMXXHZwbWLkVeH/lGHRG0CIy/calNGKa86wuzMJhq8iqFW2W6+EwVQ==",
"license": "MIT",
"dependencies": {
"@fluentui/set-version": "^8.2.24",
"tslib": "^2.1.0"
},
"peerDependencies": {
"@types/react": ">=16.8.0 <20.0.0",
"react": ">=16.8.0 <20.0.0"
}
},
"node_modules/@fluentui/set-version": {
"version": "8.2.24",
"resolved": "https://registry.npmjs.org/@fluentui/set-version/-/set-version-8.2.24.tgz",
"integrity": "sha512-8uNi2ThvNgF+6d3q2luFVVdk/wZV0AbRfJ85kkvf2+oSRY+f6QVK0w13vMorNhA5puumKcZniZoAfUF02w7NSg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/@fluentui/style-utilities": {
"version": "8.15.2",
"resolved": "https://registry.npmjs.org/@fluentui/style-utilities/-/style-utilities-8.15.2.tgz",
"integrity": "sha512-rss7pkgiyNQuo1OpS7R2X6IJXIwDzQ0fxQxIeBB7Xp7lM+AwcKn5V7fzgIgCyE7BI6TWSy2TeQJ9Jtr/4fYEaw==",
"license": "MIT",
"dependencies": {
"@fluentui/merge-styles": "^8.6.14",
"@fluentui/set-version": "^8.2.24",
"@fluentui/theme": "^2.7.2",
"@fluentui/utilities": "^8.17.2",
"@microsoft/load-themed-styles": "^1.10.26",
"tslib": "^2.1.0"
}
},
"node_modules/@fluentui/theme": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/@fluentui/theme/-/theme-2.7.2.tgz",
"integrity": "sha512-UXGNfGa/1bLmYrOpmHXdvyc7CzlNSKUQAADweTncbNoMF1DvscWEjPj5kxFgCmOU8wVtvvn4GraNNUSWtNxeeA==",
"license": "MIT",
"dependencies": {
"@fluentui/merge-styles": "^8.6.14",
"@fluentui/set-version": "^8.2.24",
"@fluentui/utilities": "^8.17.2",
"tslib": "^2.1.0"
},
"peerDependencies": {
"@types/react": ">=16.8.0 <20.0.0",
"react": ">=16.8.0 <20.0.0"
}
},
"node_modules/@fluentui/utilities": {
"version": "8.17.2",
"resolved": "https://registry.npmjs.org/@fluentui/utilities/-/utilities-8.17.2.tgz",
"integrity": "sha512-TmeWVtGN+Lk0mch7tuRcbkeMdrBwltI68fvQbPwcNLo4igFtTInMmjEnVJGa7pBQN5lQAmHYqB9IJI6RZU/t6w==",
"license": "MIT",
"dependencies": {
"@fluentui/dom-utilities": "^2.3.10",
"@fluentui/merge-styles": "^8.6.14",
"@fluentui/react-window-provider": "^2.3.2",
"@fluentui/set-version": "^8.2.24",
"tslib": "^2.1.0"
},
"peerDependencies": {
"@types/react": ">=16.8.0 <20.0.0",
"react": ">=16.8.0 <20.0.0"
}
},
"node_modules/@microsoft/load-themed-styles": {
"version": "1.10.295",
"resolved": "https://registry.npmjs.org/@microsoft/load-themed-styles/-/load-themed-styles-1.10.295.tgz",
"integrity": "sha512-W+IzEBw8a6LOOfRJM02dTT7BDZijxm+Z7lhtOAz1+y9vQm1Kdz9jlAO+qCEKsfxtUOmKilW8DIRqFw2aUgKeGg==",
"license": "MIT"
},
"node_modules/@swc/helpers": {
"version": "0.5.23",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz",
"integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@types/react": {
"version": "19.2.17",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
"integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
},
"node_modules/@types/react-dom": {
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT",
"peer": true
},
"node_modules/react": { "node_modules/react": {
"version": "19.2.7", "version": "19.2.7",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
@@ -291,12 +35,6 @@
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT" "license": "MIT"
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
} }
} }
} }
-1
View File
@@ -1,6 +1,5 @@
{ {
"dependencies": { "dependencies": {
"@fluentui/react": "^8.125.6",
"react": "^19.2.7", "react": "^19.2.7",
"react-dom": "^19.2.5" "react-dom": "^19.2.5"
} }
@@ -1,12 +1,10 @@
import React, { type JSX } from 'react'; import React, { type JSX } from 'react';
import * as FluentUI from '@fluentui/react';
import ElixFormsComponentAbstract from '@common/src/ElixFormsComponentAbstract'; import ElixFormsComponentAbstract from '@common/src/ElixFormsComponentAbstract';
import type { IElixFormsComponentCustomFormFieldFactory } from '@common/src/IElixFormsComponentCustomFormFieldFactory'; import type { IElixFormsComponentCustomFormFieldFactory } from '@common/src/IElixFormsComponentCustomFormFieldFactory';
import { ElixFormsElement } from '@common/src/ElixFormsElement'; import { ElixFormsElement } from '@common/src/ElixFormsElement';
import { QueryParamHelper } from '@common/src/QueryParamHelper'; import { QueryParamHelper } from '@common/src/QueryParamHelper';
import config from './config.json'; import config from './config.json';
/** Singolo risultato restituito dall'API esterna */
interface ContrattoResult { interface ContrattoResult {
idContratto: string; idContratto: string;
titoloContratto: string; titoloContratto: string;
@@ -14,7 +12,6 @@ interface ContrattoResult {
idRicevuta: string; idRicevuta: string;
} }
/** State aggiuntivo per gestire l'autocomplete */
interface RecuperoPropostaState { interface RecuperoPropostaState {
searchText: string; searchText: string;
searchResults: ContrattoResult[]; searchResults: ContrattoResult[];
@@ -26,7 +23,6 @@ interface RecuperoPropostaState {
} }
export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsComponentAbstract { export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsComponentAbstract {
/** Stato locale per l'autocomplete (separato dal formData gestito dall'abstract) */
private autocompleteState: RecuperoPropostaState = { private autocompleteState: RecuperoPropostaState = {
searchText: '', searchText: '',
searchResults: [], searchResults: [],
@@ -37,10 +33,8 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
errorMessage: '', errorMessage: '',
}; };
/** Timer per il debounce della ricerca */
private debounceTimer: ReturnType<typeof setTimeout> | null = null; private debounceTimer: ReturnType<typeof setTimeout> | null = null;
/** Ref al container per gestire il click-outside */
private autocompleteRef: React.RefObject<HTMLDivElement | null> = React.createRef(); private autocompleteRef: React.RefObject<HTMLDivElement | null> = React.createRef();
private static readonly formFieldKeys = { private static readonly formFieldKeys = {
@@ -50,7 +44,6 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
idRicevuta: 'COL0040', idRicevuta: 'COL0040',
} as const; } as const;
/** Codice fiscale letto dalla query string (COL0009), usato solo per la chiamata API */
private codiceFiscale: string; private codiceFiscale: string;
constructor(props: any) { constructor(props: any) {
@@ -59,7 +52,6 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
} }
override componentDidMount(): void { override componentDidMount(): void {
// Listener per chiudere il dropdown al click fuori dall'autocomplete
document.addEventListener('mousedown', this.handleClickOutside); document.addEventListener('mousedown', this.handleClickOutside);
} }
@@ -71,19 +63,12 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
} }
private handleClickOutside = (event: MouseEvent): void => { private handleClickOutside = (event: MouseEvent): void => {
if ( if (this.autocompleteRef.current && !this.autocompleteRef.current.contains(event.target as Node)) {
this.autocompleteRef.current &&
!this.autocompleteRef.current.contains(event.target as Node)
) {
this.autocompleteState.isDropdownOpen = false; this.autocompleteState.isDropdownOpen = false;
this.forceUpdate(); 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> { private async fetchContratti(searchTerm: string): Promise<void> {
this.autocompleteState.isLoading = true; this.autocompleteState.isLoading = true;
this.autocompleteState.errorMessage = ''; this.autocompleteState.errorMessage = '';
@@ -99,9 +84,9 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
const response = await fetch(url.toString(), { const response = await fetch(url.toString(), {
method: 'GET', method: 'GET',
headers: { headers: {
'Authorization': `Basic ${credentials}`, Authorization: `Basic ${credentials}`,
'X-Api-Key': config.apiKey, '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.searchResults = [];
this.autocompleteState.isDropdownOpen = false; this.autocompleteState.isDropdownOpen = false;
this.autocompleteState.isLoading = false; this.autocompleteState.isLoading = false;
this.autocompleteState.errorMessage = this.autocompleteState.errorMessage = error instanceof Error ? error.message : 'Errore durante la ricerca';
error instanceof Error ? error.message : 'Errore durante la ricerca';
this.forceUpdate(); this.forceUpdate();
} }
} }
/** private handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
* Gestisce il cambio testo nell'input di ricerca. const text = event.target.value;
* 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; this.autocompleteState.searchText = text;
// Se l'utente cancella il testo, resetta anche la selezione
if (text.length === 0) { if (text.length === 0) {
this.autocompleteState.searchResults = []; this.autocompleteState.searchResults = [];
this.autocompleteState.isDropdownOpen = false; this.autocompleteState.isDropdownOpen = false;
@@ -146,7 +125,6 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
return; return;
} }
// Debounce della ricerca
if (this.debounceTimer) { if (this.debounceTimer) {
clearTimeout(this.debounceTimer); clearTimeout(this.debounceTimer);
} }
@@ -163,10 +141,7 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
} }
}; };
/** private handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>): void => {
* Gestisce la navigazione da tastiera nel dropdown (frecce, Invio, Escape).
*/
private handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
const { searchResults, isDropdownOpen, highlightedIndex } = this.autocompleteState; const { searchResults, isDropdownOpen, highlightedIndex } = this.autocompleteState;
if (!isDropdownOpen || searchResults.length === 0) return; if (!isDropdownOpen || searchResults.length === 0) return;
@@ -174,10 +149,7 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
switch (event.key) { switch (event.key) {
case 'ArrowDown': case 'ArrowDown':
event.preventDefault(); event.preventDefault();
this.autocompleteState.highlightedIndex = Math.min( this.autocompleteState.highlightedIndex = Math.min(highlightedIndex + 1, searchResults.length - 1);
highlightedIndex + 1,
searchResults.length - 1
);
this.forceUpdate(); this.forceUpdate();
break; 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 { private selectContract(contract: ContrattoResult): void {
this.autocompleteState.selectedContract = contract; this.autocompleteState.selectedContract = contract;
this.autocompleteState.searchText = `[${contract.idContratto}] ${contract.titoloContratto}`; this.autocompleteState.searchText = `[${contract.idContratto}] ${contract.titoloContratto}`;
@@ -212,7 +181,6 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
this.autocompleteState.searchResults = []; this.autocompleteState.searchResults = [];
this.autocompleteState.highlightedIndex = -1; this.autocompleteState.highlightedIndex = -1;
// Aggiorna il formData con i valori selezionati
this.setState({ this.setState({
formData: { formData: {
...this.state.formData, ...this.state.formData,
@@ -221,15 +189,9 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idDomanda]: contract.idDomanda, [RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idDomanda]: contract.idDomanda,
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idRicevuta]: contract.idRicevuta, [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 { private clearSelection(): void {
this.autocompleteState.selectedContract = null; 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 { private renderAutocomplete(): JSX.Element {
const { searchResults, isLoading, isDropdownOpen, highlightedIndex, errorMessage, searchText } = this.autocompleteState; const { searchResults, isLoading, isDropdownOpen, highlightedIndex, errorMessage, searchText } = this.autocompleteState;
const autocompleteInput = ( const autocompleteInput = (
<> <>
<div className="autocomplete-wrapper" ref={this.autocompleteRef}> <div className="position-relative" ref={this.autocompleteRef}>
<FluentUI.TextField <input
id="autocomplete-search" id="autocomplete-search"
name="autocomplete-search" name="autocomplete-search"
className="form-control"
type="text"
placeholder="Digita almeno 3 caratteri per cercare..." placeholder="Digita almeno 3 caratteri per cercare..."
value={searchText} value={searchText}
onChange={this.handleSearchChange} onChange={this.handleSearchChange}
onKeyDown={this.handleKeyDown} onKeyDown={this.handleKeyDown}
className="isiportalPartialAdminFormFieldSingleLineText"
autoComplete="off" autoComplete="off"
/> />
{isLoading && ( {isLoading && (
<div className="autocomplete-dropdown"> <div className="autocompleteDropdown mt-2 p-3 text-muted">
<div className="autocomplete-loading"> <span className="me-2">
<FluentUI.Spinner size={FluentUI.SpinnerSize.small} label="Ricerca in corso..." /> <span className="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
</div> </span>
Ricerca in corso...
</div> </div>
)} )}
{!isLoading && isDropdownOpen && searchResults.length > 0 && ( {!isLoading && isDropdownOpen && searchResults.length > 0 && (
<div className="autocomplete-dropdown"> <div className="autocompleteDropdown mt-2">
{searchResults.map((contract, index) => ( {searchResults.map((contract, index) => (
<div <div
key={`${contract.idContratto}_${index}`} key={`${contract.idContratto}_${index}`}
className={`autocomplete-dropdown-item${index === highlightedIndex ? ' highlighted' : ''}`} className={`autocompleteItem${index === highlightedIndex ? ' isHighlighted' : ''}`}
onMouseDown={(e) => { onMouseDown={(event) => {
e.preventDefault(); // Previene blur del TextField event.preventDefault();
this.selectContract(contract); this.selectContract(contract);
}} }}
onMouseEnter={() => { onMouseEnter={() => {
@@ -287,41 +248,31 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
this.forceUpdate(); this.forceUpdate();
}} }}
> >
<span className="contract-id">[{contract.idContratto}]</span>{' '} <span className="fw-semibold">[{contract.idContratto}]</span>{' '}
<span className="contract-title">{contract.titoloContratto}</span> <span>{contract.titoloContratto}</span>
</div> </div>
))} ))}
</div> </div>
)} )}
{!isLoading && isDropdownOpen && searchResults.length === 0 && ( {!isLoading && isDropdownOpen && searchResults.length === 0 && (
<div className="autocomplete-dropdown"> <div className="autocompleteDropdown mt-2 p-3 text-muted">Nessun risultato trovato</div>
<div className="autocomplete-no-results">Nessun risultato trovato</div>
</div>
)} )}
{errorMessage && ( {errorMessage && (
<div className="autocomplete-error">{errorMessage}</div> <div className="alert alert-danger mt-2">{errorMessage}</div>
)} )}
</div> </div>
{this.autocompleteState.selectedContract && ( {this.autocompleteState.selectedContract && (
<div className="selected-contract-summary"> <div className="card mt-3">
<div className="summary-row"> <div className="card-body">
<span className="summary-label">ID Contratto:</span> <div className="row g-2">
<span className="summary-value">{this.autocompleteState.selectedContract.idContratto}</span> <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 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> </div>
)} )}
@@ -329,16 +280,11 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
); );
return new ElixFormsElement( 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 autocompleteInput
).render(); ).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 { protected override createCustomFormFields(formFieldFactory: IElixFormsComponentCustomFormFieldFactory): JSX.Element {
const fieldKeys = RecuperoPropostaCctDaContrattiComponent.formFieldKeys; const fieldKeys = RecuperoPropostaCctDaContrattiComponent.formFieldKeys;
const idContratto = this.state.formData[fieldKeys.idContratto] ?? ''; const idContratto = this.state.formData[fieldKeys.idContratto] ?? '';
@@ -349,8 +295,6 @@ export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsCo
return ( return (
<> <>
{this.renderAutocomplete()} {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="idContratto_hidden" name={fieldKeys.idContratto} value={idContratto} />
<input type="hidden" id="titoloContratto_hidden" name={fieldKeys.titoloContratto} value={titoloContratto} /> <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="idDomanda_hidden" name={fieldKeys.idDomanda} value={idDomanda} />
@@ -6,44 +6,44 @@ export default class SceltaCarrieraComponent extends ElixFormsComponentAbstract
protected override createCustomFormFields(formFieldFactory: IElixFormsComponentCustomFormFieldFactory): JSX.Element { protected override createCustomFormFields(formFieldFactory: IElixFormsComponentCustomFormFieldFactory): JSX.Element {
return ( return (
<> <>
{formFieldFactory.createDropdownInput("COL0015", "COL0015 Goals", [ {formFieldFactory.createDropdownInput('COL0015', 'COL0015 Goals', [
{ "value": 0, "label": "Not applicable" }, { value: 0, label: 'Not applicable' },
{ "value": 1, "label": "Goal 1: No poverty" }, { value: 1, label: 'Goal 1: No poverty' },
{ "value": 2, "label": "Goal 2: Zero hunger" }, { value: 2, label: 'Goal 2: Zero hunger' },
{ "value": 3, "label": "Goal 3: Good health and well-being" }, { value: 3, label: 'Goal 3: Good health and well-being' },
{ "value": 4, "label": "Goal 4: Quality education" }, { value: 4, label: 'Goal 4: Quality education' },
{ "value": 5, "label": "Goal 5: Gender equality" }, { value: 5, label: 'Goal 5: Gender equality' },
{ "value": 6, "label": "Goal 6: Clean water and sanitation" }, { value: 6, label: 'Goal 6: Clean water and sanitation' },
{ "value": 7, "label": "Goal 7: Affordable and clean energy" }, { value: 7, label: 'Goal 7: Affordable and clean energy' },
{ "value": 8, "label": "Goal 8: Decent work and economic growth" }, { value: 8, label: 'Goal 8: Decent work and economic growth' },
{ "value": 9, "label": "Goal 9: Industry, Innovation, and Infrastructure" }, { value: 9, label: 'Goal 9: Industry, Innovation, and Infrastructure' },
{ "value": 10, "label": "Goal 10: Reduced inequalities" }, { value: 10, label: 'Goal 10: Reduced inequalities' },
{ "value": 11, "label": "Goal 11: Sustainable cities and communities" }, { value: 11, label: 'Goal 11: Sustainable cities and communities' },
{ "value": 12, "label": "Goal 12: Responsible consumption and production" }, { value: 12, label: 'Goal 12: Responsible consumption and production' },
{ "value": 13, "label": "Goal 13: Climate action" }, { value: 13, label: 'Goal 13: Climate action' },
{ "value": 14, "label": "Goal 14: Life below water" }, { value: 14, label: 'Goal 14: Life below water' },
{ "value": 15, "label": "Goal 15: Life on land" }, { value: 15, label: 'Goal 15: Life on land' },
{ "value": 16, "label": "Goal 16: Peace, justice and strong institutions" }, { value: 16, label: 'Goal 16: Peace, justice and strong institutions' },
{ "value": 17, "label": "Goal 17: Partnerships for the goals" } { value: 17, label: 'Goal 17: Partnerships for the goals' }
], true)} ], true)}
{formFieldFactory.createTextInput("COL0002", "Campo STRING", true)} {formFieldFactory.createTextInput('COL0002', 'Campo STRING', true)}
{formFieldFactory.createTextAreaInput("COL0003", "Campo TEXTAREA", true)} {formFieldFactory.createTextAreaInput('COL0003', 'Campo TEXTAREA', true)}
{formFieldFactory.createBooleanInput("COL0004", "Campo BOOLEAN", true)} {formFieldFactory.createBooleanInput('COL0004', 'Campo BOOLEAN', true)}
{formFieldFactory.createRadioInput("COL0005", "Campo RADIO", [ {formFieldFactory.createRadioInput('COL0005', 'Campo RADIO', [
{ "value": 1, "label": "Opzione 1" }, { value: 1, label: 'Opzione 1' },
{ "value": 2, "label": "Opzione 2" }, { value: 2, label: 'Opzione 2' },
{ "value": 3, "label": "Altra opzione" } { value: 3, label: 'Altra opzione' }
], true)} ], true)}
{formFieldFactory.createCheckboxInput("COL0006", "Campo CHECKBOX", [ {formFieldFactory.createCheckboxInput('COL0006', 'Campo CHECKBOX', [
{ "value": 4, "label": "Check 1" }, { value: 4, label: 'Check 1' },
{ "value": 5, "label": "Check 2" }, { value: 5, label: 'Check 2' },
{ "value": 6, "label": "Altro check" } { value: 6, label: 'Altro check' }
])} ])}
</> </>
); );
} }
protected override renderExtraContentPost(): JSX.Element { protected override renderExtraContentPost(): JSX.Element {
return <p>Altre cose</p>; return <div className="alert alert-info mt-4">Questa pagina è stata ristrutturata con i componenti Bootstrap Italia.</div>;
} }
} }