Author SHA1 Message Date
pierpaolo.mammi a77f6f19cf fix vite config for correct build paths 2026-07-17 16:36:55 +02:00
pierpaolo.mammi 746c43c7b4 exclude config.json and add template 2026-07-17 16:12:37 +02:00
pierpaolo.mammi 85b51dfbc4 adjust recupero contratti component configuration 2026-07-17 16:08:03 +02:00
pierpaolo.mammi 1aacb21b1b make userDisplayName optional 2026-07-17 16:08:03 +02:00
pierpaolo.mammi 1c02e3d1b2 apply custom elixforms styling 2026-07-17 16:08:03 +02:00
pierpaolo.mammi bad919a98a apply custom elixforms styling 2026-07-17 15:19:55 +02:00
pierpaolo.mammi fefb45505c move magic values to configuration file 2026-07-17 13:46:12 +02:00
pierpaolo.mammi c3d4c2a660 switch to bootstrap-italia styling 2026-07-17 13:34:40 +02:00
pierpaolo.mammi 1943252422 first update to bootstrap italia styles 2026-07-17 10:40:42 +02:00
pierpaolo.mammi 4cd22396d9 switch to bootstrap italia through AI 2026-07-17 09:53:46 +02:00
pierpaolo.mammi 99a64f1157 move submit button to the right 2026-07-17 09:40:44 +02:00
pierpaolo.mammi beee00bee3 add custom styling to common component 2026-07-16 16:09:33 +02:00
pierpaolo.mammi e404bf0954 fix autocomplete results box positioning 2026-07-16 15:36:14 +02:00
pierpaolo.mammi 50681d07c5 cleanup call to contratti API 2026-07-16 15:32:48 +02:00
pierpaolo.mammi 6b5aa556fe move field names in a single place 2026-07-14 16:20:08 +02:00
pierpaolo.mammi 2c8a24032f add missing values to mock server response 2026-07-14 16:12:40 +02:00
pierpaolo.mammi dcf28d46b5 temporary implementation to call mock server 2026-07-14 16:12:34 +02:00
pierpaolo.mammi 296c481516 fix wrong component parameter name 2026-07-14 16:12:02 +02:00
pierpaolo.mammi faedc6203a update npm packages and hopefully fix debugging 2026-07-14 16:11:22 +02:00
pierpaolo.mammi 99e868bfb4 try to restore debug functionality 2026-07-14 15:05:09 +02:00
pierpaolo.mammi 5b756cb9cd move gitignore to root 2026-07-14 15:04:43 +02:00
pierpaolo.mammi 550978a852 add basic mock server for api testing 2026-07-14 15:04:22 +02:00
administrator 0de10445f6 add new custom page: recupero-proposta-cct-da-contratti 2026-07-13 18:48:54 +02:00
42 changed files with 5640 additions and 1113 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 |
+2 -2
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
@@ -169,7 +169,7 @@ function App() {
{/* Header, contenuto e footer della pagina */} {/* Header, contenuto e footer della pagina */}
<div className="grid_12 console_main"> <div className="grid_12 console_main">
<div className="inside"> <div className="inside">
<div className="receipt"> <div className="mainview">
<div className="container"> <div className="container">
<h1>Titolo Pagina</h1> <h1>Titolo Pagina</h1>
{elixFormsReactRender} {elixFormsReactRender}
+4 -11
View File
@@ -76,7 +76,7 @@ Le pagine seguono un layout standard elixForms con classi CSS specifiche:
<div class="fe-module-header-container"> <div class="fe-module-header-container">
<h2>Titolo Modulo</h2> <h2>Titolo Modulo</h2>
</div> </div>
<div class="receipt"> <div class="mainview">
<div class="container"> <div class="container">
<!-- Contenuto custom della pagina --> <!-- Contenuto custom della pagina -->
</div> </div>
@@ -115,7 +115,7 @@ Le pagine seguono un layout standard elixForms con classi CSS specifiche:
| `console_header` | Header della console | | `console_header` | Header della console |
| `console_main` | Area contenuto principale | | `console_main` | Area contenuto principale |
| `fe-module-header-container` | Header del modulo/form | | `fe-module-header-container` | Header del modulo/form |
| `receipt` | Container del contenuto del form | | `mainview` | Container del contenuto del form |
### Layout Campi Form (ElixFormsElement) ### Layout Campi Form (ElixFormsElement)
| Classe | Scopo | | Classe | Scopo |
@@ -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
+3
View File
@@ -52,3 +52,6 @@ obj
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
# Configuration files
**/config.json
+43 -2
View File
@@ -4,12 +4,36 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{
"name": "Recupero Proposta CCT da contratti (BROWSER)",
"cwd": "${workspaceFolder}/react/recupero-proposta-cct-da-contratti",
"env": {
"NO_COLOR": "true"
},
"outputCapture": "console",
"type": "node",
"request": "launch",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev"],
"skipFiles": [
"<node_internals>/**",
"**/node_modules/**",
"**/@vite/client/**"
],
"serverReadyAction": {
"action": "debugWithChrome",
"pattern": "Local:\\s+http://localhost:([0-9]+)/",
"uriFormat": "http://localhost:%s",
"webRoot": "${workspaceFolder}/react/recupero-proposta-cct-da-contratti/src"
},
},
{ {
"name": "Scelta Carriere (BROWSER)", "name": "Scelta Carriere (BROWSER)",
"cwd": "${workspaceFolder}/react/scelta-carriera", "cwd": "${workspaceFolder}/react/scelta-carriera",
"env": { "env": {
"NO_COLOR": "1" "NO_COLOR": "true"
}, },
"outputCapture": "console", "outputCapture": "console",
"type": "node", "type": "node",
@@ -32,7 +56,24 @@
"name": "Scelta Carriere (DEBUG ONLY)", "name": "Scelta Carriere (DEBUG ONLY)",
"cwd": "${workspaceFolder}/react/scelta-carriera", "cwd": "${workspaceFolder}/react/scelta-carriera",
"env": { "env": {
"NO_COLOR": "1" "NO_COLOR": "true"
},
"outputCapture": "console",
"type": "node",
"request": "launch",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev"],
"skipFiles": [
"<node_internals>/**",
"**/node_modules/**",
"**/@vite/client/**"
]
},
{
"name": "Recupero Proposta CCT da contratti (DEBUG ONLY)",
"cwd": "${workspaceFolder}/react/recupero-proposta-cct-da-contratti",
"env": {
"NO_COLOR": "true"
}, },
"outputCapture": "console", "outputCapture": "console",
"type": "node", "type": "node",
+11
View File
@@ -0,0 +1,11 @@
# Mock server
## JSON-Server
See: <https://github.com/typicode/json-server>
To start server:
```pwsh
npx json-server db.json
```
+52
View File
@@ -0,0 +1,52 @@
{
"$schema": [
"./node_modules/json-server/schema.json"
],
"posts": [
{
"id": "1",
"title": "a title",
"views": 100
},
{
"id": "2",
"title": "another title",
"views": 200
}
],
"comments": [
{
"id": "1",
"text": "a comment about post 1",
"postId": "1"
},
{
"id": "2",
"text": "another comment about post 1",
"postId": "1"
}
],
"contratti": [
{
"idContratto": "ID_CONTRATTO_1",
"titoloContratto": "Titolo del contratto #1",
"idDomanda": "12345",
"idRicevuta": "ABCDE"
},
{
"idContratto": "ID_CONTRATTO_2",
"titoloContratto": "Titolo del secondo contratto",
"idDomanda": "67890",
"idRicevuta": "FGHIJ"
},
{
"idContratto": "ID_CONTRATTO_3",
"titoloContratto": "Terzo titolo",
"idDomanda": "13579",
"idRicevuta": "KLMNO"
}
],
"profile": {
"name": "typicode"
}
}
+532
View File
@@ -0,0 +1,532 @@
{
"name": "mock-server",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"json-server": "^1.0.0-beta.15"
}
},
"node_modules/@polka/url": {
"version": "1.0.0-next.29",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
"integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
"license": "MIT"
},
"node_modules/@tinyhttp/accepts": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@tinyhttp/accepts/-/accepts-2.3.0.tgz",
"integrity": "sha512-hdKkMGAUqnagpWO1R8rVBYqbu4sWQ2Fo682gkJmO0nl54DPvnzxx81b2WZtV3VwB7EdLfUoasj2BAkyTcyZ5aw==",
"license": "MIT",
"dependencies": {
"mime": "4.1.0"
},
"engines": {
"node": ">=14.13.1"
},
"funding": {
"type": "individual",
"url": "https://github.com/tinyhttp/tinyhttp?sponsor=1"
}
},
"node_modules/@tinyhttp/app": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@tinyhttp/app/-/app-3.0.7.tgz",
"integrity": "sha512-btit/gSWisksJ19crNLct1mwZvX+/AYwh/H1x8SB/VGDmNFkSsdRDZaa54wW1Eq3bM7mCIr7+l7Oh3tAi/C+Bw==",
"license": "MIT",
"dependencies": {
"@tinyhttp/accepts": "^2.3.0",
"@tinyhttp/cookie": "2.1.1",
"@tinyhttp/proxy-addr": "3.0.1",
"@tinyhttp/req": "2.2.8",
"@tinyhttp/res": "2.2.11",
"@tinyhttp/router": "2.2.5",
"regexparam": "^2.0.2"
},
"engines": {
"node": ">=16.10.0"
},
"funding": {
"type": "individual",
"url": "https://github.com/tinyhttp/tinyhttp?sponsor=1"
}
},
"node_modules/@tinyhttp/content-disposition": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/@tinyhttp/content-disposition/-/content-disposition-2.2.4.tgz",
"integrity": "sha512-5Kc5CM2Ysn3vTTArBs2vESUt0AQiWZA86yc1TI3B+lxXmtEq133C1nxXNOgnzhrivdPZIh3zLj5gDnZjoLL5GA==",
"license": "MIT",
"engines": {
"node": ">=12.17.0"
},
"funding": {
"type": "individual",
"url": "https://github.com/tinyhttp/tinyhttp?sponsor=1"
}
},
"node_modules/@tinyhttp/content-type": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/@tinyhttp/content-type/-/content-type-0.1.4.tgz",
"integrity": "sha512-dl6f3SHIJPYbhsW1oXdrqOmLSQF/Ctlv3JnNfXAE22kIP7FosqJHxkz/qj2gv465prG8ODKH5KEyhBkvwrueKQ==",
"license": "MIT",
"engines": {
"node": ">=12.4"
}
},
"node_modules/@tinyhttp/cookie": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@tinyhttp/cookie/-/cookie-2.1.1.tgz",
"integrity": "sha512-h/kL9jY0e0Dvad+/QU3efKZww0aTvZJslaHj3JTPmIPC9Oan9+kYqmh3M6L5JUQRuTJYFK2nzgL2iJtH2S+6dA==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"funding": {
"type": "individual",
"url": "https://github.com/tinyhttp/tinyhttp?sponsor=1"
}
},
"node_modules/@tinyhttp/cookie-signature": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@tinyhttp/cookie-signature/-/cookie-signature-2.1.1.tgz",
"integrity": "sha512-VDsSMY5OJfQJIAtUgeQYhqMPSZptehFSfvEEtxr+4nldPA8IImlp3QVcOVuK985g4AFR4Hl1sCbWCXoqBnVWnw==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/@tinyhttp/cors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@tinyhttp/cors/-/cors-2.0.1.tgz",
"integrity": "sha512-qrmo6WJuaiCzKWagv2yA/kw6hIISfF/hOqPWwmI6w0o8apeTMmRN3DoCFvQ/wNVuWVdU5J4KU7OX8aaSOEq51A==",
"license": "MIT",
"dependencies": {
"@tinyhttp/vary": "^0.1.3"
},
"engines": {
"node": ">=12.20 || 14.x || >=16"
}
},
"node_modules/@tinyhttp/encode-url": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@tinyhttp/encode-url/-/encode-url-2.1.1.tgz",
"integrity": "sha512-AhY+JqdZ56qV77tzrBm0qThXORbsVjs/IOPgGCS7x/wWnsa/Bx30zDUU/jPAUcSzNOzt860x9fhdGpzdqbUeUw==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/@tinyhttp/etag": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@tinyhttp/etag/-/etag-2.1.2.tgz",
"integrity": "sha512-j80fPKimGqdmMh6962y+BtQsnYPVCzZfJw0HXjyH70VaJBHLKGF+iYhcKqzI3yef6QBNa8DKIPsbEYpuwApXTw==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/@tinyhttp/forwarded": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@tinyhttp/forwarded/-/forwarded-2.1.2.tgz",
"integrity": "sha512-9H/eulJ68ElY/+zYpTpNhZ7vxGV+cnwaR6+oQSm7bVgZMyuQfgROW/qvZuhmgDTIxnGMXst+Ba4ij6w6Krcs3w==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/@tinyhttp/logger": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@tinyhttp/logger/-/logger-2.1.0.tgz",
"integrity": "sha512-Ma1fJ9CwUbn9r61/4HW6+nflsVoslpOnCrfQ6UeZq7GGIgwLzofms3HoSVG7M+AyRMJpxlfcDdbH5oFVroDMKA==",
"license": "MIT",
"dependencies": {
"colorette": "^2.0.20",
"dayjs": "^1.11.13",
"http-status-emojis": "^2.2.0"
},
"engines": {
"node": ">=14.18 || >=16.20"
}
},
"node_modules/@tinyhttp/proxy-addr": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@tinyhttp/proxy-addr/-/proxy-addr-3.0.1.tgz",
"integrity": "sha512-vP0JVsy9ZMIldsaP/QHdMF+sb3B6wn7e2QXRdqpX/Cqz1ie35Am29DK88DeVmiwdTQle3FtYaVNtU3RgTGYZ+w==",
"license": "MIT",
"dependencies": {
"@tinyhttp/forwarded": "2.1.2"
},
"engines": {
"node": ">=16.10.0"
}
},
"node_modules/@tinyhttp/req": {
"version": "2.2.8",
"resolved": "https://registry.npmjs.org/@tinyhttp/req/-/req-2.2.8.tgz",
"integrity": "sha512-HCsceFNgMpssUsnRao16iJyyfdWRwKlhL7OMTPUEjZsZGREnBzpjlrPHA31G5xNNzR7XOWVXDXGyrGgSpcwGSA==",
"license": "MIT",
"dependencies": {
"@tinyhttp/accepts": "2.3.0",
"@tinyhttp/type-is": "2.2.5",
"@tinyhttp/url": "2.1.1",
"header-range-parser": "^1.1.3"
},
"engines": {
"node": ">=14.13.1"
}
},
"node_modules/@tinyhttp/res": {
"version": "2.2.11",
"resolved": "https://registry.npmjs.org/@tinyhttp/res/-/res-2.2.11.tgz",
"integrity": "sha512-t7GJzjqpG2svJ11RYvqaYU+xTV9MsEr0usbTIAWa5B8d7IIzpMC6AlT2K6wCIu1XGUYRpZY7qMM5cMgluvsfeg==",
"license": "MIT",
"dependencies": {
"@tinyhttp/content-disposition": "2.2.4",
"@tinyhttp/cookie": "2.1.1",
"@tinyhttp/cookie-signature": "2.1.1",
"@tinyhttp/encode-url": "2.1.1",
"@tinyhttp/req": "2.2.8",
"@tinyhttp/send": "2.2.5",
"@tinyhttp/vary": "^0.1.3",
"mime": "4.1.0"
},
"engines": {
"node": ">=14.13.1"
}
},
"node_modules/@tinyhttp/router": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/@tinyhttp/router/-/router-2.2.5.tgz",
"integrity": "sha512-HI9Mpo9+IVpCzx/36okjJvtvifBSh3Ufhl9n1vylAbNLEykceJiMBkr06+W0qqRlo8TiZeUtg2XinEJu+GFcRA==",
"license": "MIT",
"dependencies": {
"regexparam": "^2.0.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@tinyhttp/send": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/@tinyhttp/send/-/send-2.2.5.tgz",
"integrity": "sha512-XhBwziPOCydOJzb9rVw0xuKX6HmMA0gXKHHqec7V97jU6JtSCCIvwW2FBMh/XrG9S5W8DRETroPohASrIwf7Uw==",
"license": "MIT",
"dependencies": {
"@tinyhttp/content-type": "^0.1.4",
"@tinyhttp/etag": "2.1.2",
"mime": "4.1.0"
},
"engines": {
"node": ">=14.13.1"
}
},
"node_modules/@tinyhttp/type-is": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/@tinyhttp/type-is/-/type-is-2.2.5.tgz",
"integrity": "sha512-BCPEB+NV8v/9lzEE9GbfRPAKVsyayp84m6SSWn70j8yFkPBXeuVeq004pwVrjW1CRdmAZz9ZSH147pqqzAdr5g==",
"license": "MIT",
"dependencies": {
"@tinyhttp/content-type": "^0.1.4",
"mime": "4.1.0"
},
"engines": {
"node": ">=14.13.1"
}
},
"node_modules/@tinyhttp/url": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@tinyhttp/url/-/url-2.1.1.tgz",
"integrity": "sha512-POJeq2GQ5jI7Zrdmj22JqOijB5/GeX+LEX7DUdml1hUnGbJOTWDx7zf2b5cCERj7RoXL67zTgyzVblBJC+NJWg==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/@tinyhttp/vary": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@tinyhttp/vary/-/vary-0.1.3.tgz",
"integrity": "sha512-SoL83sQXAGiHN1jm2VwLUWQSQeDAAl1ywOm6T0b0Cg1CZhVsjoiZadmjhxF6FHCCY7OHHVaLnTgSMxTPIDLxMg==",
"license": "MIT",
"engines": {
"node": ">=12.20"
}
},
"node_modules/chalk": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chokidar": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
"license": "MIT",
"dependencies": {
"readdirp": "^5.0.0"
},
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/colorette": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"license": "MIT"
},
"node_modules/dayjs": {
"version": "1.11.21",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz",
"integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==",
"license": "MIT"
},
"node_modules/dot-prop": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-10.1.0.tgz",
"integrity": "sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q==",
"license": "MIT",
"dependencies": {
"type-fest": "^5.0.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eta": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/eta/-/eta-4.6.0.tgz",
"integrity": "sha512-lW6is4T1NFOYnmqGZIfvixqj7A7sSvScF+DN8EK6K58xI5MZ5UvYe0GjopxOXQtZvUn4eDdVuZ8XSoYWTMEKwA==",
"license": "MIT",
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/bgub/eta?sponsor=1"
}
},
"node_modules/header-range-parser": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/header-range-parser/-/header-range-parser-1.1.5.tgz",
"integrity": "sha512-n5JOx67HBL0MGqtu6NFoEYWb+xDYAOgBI5dBkyMDff1xHbhGnjCMglj1aiMNPHps6HwXO+2i5jbPU/zJSk7etQ==",
"license": "MIT",
"engines": {
"node": ">=12.22.0"
}
},
"node_modules/http-status-emojis": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/http-status-emojis/-/http-status-emojis-2.2.0.tgz",
"integrity": "sha512-ompKtgwpx8ff0hsbpIB7oE4ax1LXoHmftsHHStMELX56ivG3GhofTX8ZHWlUaFKfGjcGjw6G3rPk7dJRXMmbbg==",
"license": "MIT"
},
"node_modules/inflection": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/inflection/-/inflection-3.0.2.tgz",
"integrity": "sha512-+Bg3+kg+J6JUWn8J6bzFmOWkTQ6L/NHfDRSYU+EVvuKHDxUDHAXgqixHfVlzuBQaPOTac8hn43aPhMNk6rMe3g==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/json-server": {
"version": "1.0.0-beta.15",
"resolved": "https://registry.npmjs.org/json-server/-/json-server-1.0.0-beta.15.tgz",
"integrity": "sha512-I5UB/OWHLGoQW9IVld2yzZFYYiGYNBn2OYRlGbkfj5xDidT26yKYkV7Sr093zJtPh9zbofaLgyT89Ov5U0RRBQ==",
"license": "MIT",
"dependencies": {
"@tinyhttp/app": "^3.0.1",
"@tinyhttp/cors": "^2.0.1",
"@tinyhttp/logger": "^2.1.0",
"chalk": "^5.6.2",
"chokidar": "^5.0.0",
"dot-prop": "^10.1.0",
"eta": "^4.5.0",
"inflection": "^3.0.2",
"json5": "^2.2.3",
"lowdb": "^7.0.1",
"milliparsec": "^5.1.0",
"sirv": "^3.0.2",
"sort-on": "^7.0.0"
},
"bin": {
"json-server": "lib/bin.js"
},
"engines": {
"node": ">=22.12.0"
}
},
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"license": "MIT",
"bin": {
"json5": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/lowdb": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/lowdb/-/lowdb-7.0.1.tgz",
"integrity": "sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==",
"license": "MIT",
"dependencies": {
"steno": "^4.0.2"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/typicode"
}
},
"node_modules/milliparsec": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/milliparsec/-/milliparsec-5.1.1.tgz",
"integrity": "sha512-jkEDaSWZp4/Q3vprqdqukBqUEyNNqC1pwTjZ5cp9YkaR1wv5fvTCd8VFsecbw7i8DNBGjzhJ83MDoPZlcTaPQg==",
"license": "MIT",
"engines": {
"node": ">=18.13 || >=19.20 || >=20"
}
},
"node_modules/mime": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz",
"integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==",
"funding": [
"https://github.com/sponsors/broofa"
],
"license": "MIT",
"bin": {
"mime": "bin/cli.js"
},
"engines": {
"node": ">=16"
}
},
"node_modules/mrmime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
"integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/readdirp": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
"integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
"license": "MIT",
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/regexparam": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.2.tgz",
"integrity": "sha512-A1PeDEYMrkLrfyOwv2jwihXbo9qxdGD3atBYQA9JJgreAx8/7rC6IUkWOw2NQlOxLp2wL0ifQbh1HuidDfYA6w==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/sirv": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz",
"integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==",
"license": "MIT",
"dependencies": {
"@polka/url": "^1.0.0-next.24",
"mrmime": "^2.0.0",
"totalist": "^3.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/sort-on": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/sort-on/-/sort-on-7.0.0.tgz",
"integrity": "sha512-e+4RRxt7jsWdGPp4H5PKOER/ELYlemNB1plvW686Qi3j4WVaCjCpro2zaTD7Cn0VtBImq/hg3x1JfovMNXXfJQ==",
"license": "MIT",
"dependencies": {
"dot-prop": "^10.1.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/steno": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/steno/-/steno-4.0.2.tgz",
"integrity": "sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/typicode"
}
},
"node_modules/tagged-tag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz",
"integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==",
"license": "MIT",
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/totalist": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
"integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/type-fest": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.8.0.tgz",
"integrity": "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==",
"license": "(MIT OR CC0-1.0)",
"dependencies": {
"tagged-tag": "^1.0.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
}
}
}
+5
View File
@@ -0,0 +1,5 @@
{
"dependencies": {
"json-server": "^1.0.0-beta.15"
}
}
+23 -3
View File
@@ -1,13 +1,33 @@
{ {
"name": "common", "name": "@elixforms/common",
"version": "0.0.1", "version": "0.0.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "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"
} }
} }
} }
+5 -2
View File
@@ -1,9 +1,12 @@
{ {
"name": "common", "name": "@elixforms/common",
"version": "0.0.1", "version": "0.0.1",
"description": "Common components to build elixForms custom pages", "description": "Common components to build elixForms custom pages",
"license": "ISC", "license": "ISC",
"author": "", "author": "",
"type": "commonjs", "type": "commonjs",
"main": "index.js" "main": "index.js",
"devDependencies": {
"@types/react": "^19.2.17"
}
} }
+24
View File
@@ -0,0 +1,24 @@
@import url("https://fonts.googleapis.com/css2?family=Titillium+Web:wght@300;400;600;700&display=swap");
@import url("https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css");
@import url("https://cdn.jsdelivr.net/npm/bootstrap-italia@2.18.2/dist/css/bootstrap-italia.min.css");
/* elixForms styles customizations */
@import url("https://console-unipr.elixforms.it/rwe2/css/design-bs/util.css");
@import url("https://console-unipr.elixforms.it/rwe2/css/themes/blu-italia.css");
/* These are here for future imports...
@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/plugins/modaldisplay.css");
@import url("https://console-unipr.elixforms.it/rwe2/isipcss/css/grid_fluid.css");
*/
+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;
}
}
} }
+331 -282
View File
@@ -1,12 +1,13 @@
import * as FluentUI from '@fluentui/react'; import './App.css';
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 { QueryParamHelper } from './QueryParamHelper';
import { Component, type FormEvent, type JSX, useState } 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';
import { ElixFormsElement } from './ElixFormsElement';
import { QueryParamHelper } from './QueryParamHelper';
import { Component, type JSX } from 'react';
import React from 'react';
export default abstract class ElixFormsComponentAbstract extends Component<IElixFormsComponentProperties, IElixFormsComponentFormState> { export default abstract class ElixFormsComponentAbstract extends Component<IElixFormsComponentProperties, IElixFormsComponentFormState> {
constructor(props: IElixFormsComponentProperties) { constructor(props: IElixFormsComponentProperties) {
@@ -15,57 +16,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));
}); });
@@ -73,7 +43,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 (
<> <>
@@ -123,16 +93,18 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
public override render(): React.ReactElement<IElixFormsComponentProperties> { public override render(): React.ReactElement<IElixFormsComponentProperties> {
const { const {
description,
isDarkTheme, isDarkTheme,
environmentMessage,
hasTeamsContext,
userDisplayName, userDisplayName,
additionalFieldsJson, moduleName,
moduleTitle, headerTitle,
pageTitle, headerHeroImageSrc: headerTitleImageSrc,
pageDescription, cardTitle,
heroImageSrc cardDescription,
alertInfoTitle,
alertInfoMessage,
instructionsTitle,
instructionsMessage,
submitText,
} = this.props; } = this.props;
const queryParams = new URLSearchParams(window.location.search); const queryParams = new URLSearchParams(window.location.search);
@@ -140,90 +112,162 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
const hasAuthenticationError = missingMandatoryParams.length > 0; const hasAuthenticationError = missingMandatoryParams.length > 0;
return ( return (
<div className="container_12" id="pageBody"> <div>
<div className="grid_12"> <header className="it-header-wrapper text-white">
<div id="userConsole" className="fe-behaviour"> {userDisplayName &&
<header> <div className="it-header-slim-wrapper">
<div className="neutral"> <div className="container">
<div className="grid_12"> <div className="row">
<div className="console_header"> <div className="col-12">
<div className="leftDiv"> <div className="it-header-slim-wrapper-content">
<h1>elixForms</h1> {/* <a className="d-none d-lg-block navbar-brand" href="#">Ente appartenenza</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 className="nav-mobile">
{/* <nav aria-label="Navigazione accessoria">
<a className="it-opener d-lg-none" data-bs-toggle="collapse" href="#menu1a" role="button"
aria-expanded="false" aria-controls="menu4">
<span>Ente appartenenza</span>
<svg className="icon" aria-hidden="true">
<use href="/bootstrap-italia/dist/svg/sprites.svg#it-expand"></use>
</svg>
</a>
<div className="link-list-wrapper collapse" id="menu1a">
<ul className="link-list">
<li><a className="dropdown-item list-item" href="#">Link 1</a></li>
<li><a className="list-item active" href="#" aria-current="page">Link 2 (Attivo)</a></li>
</ul>
</div>
</nav> */}
</div>
<div className="it-header-slim-right-zone">
{/* <div className="nav-item dropdown">
<a className="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
aria-expanded="false">
<span className="visually-hidden">Selezione lingua: lingua selezionata</span>
<span>ITA</span>
<svg className="icon d-none d-lg-block">
<use href="/bootstrap-italia/dist/svg/sprites.svg#it-expand"></use>
</svg>
</a>
<div className="dropdown-menu">
<div className="row">
<div className="col-12">
<div className="link-list-wrapper">
<ul className="link-list">
<li><a className="dropdown-item list-item" href="#"><span>ITA <span
className="visually-hidden">selezionata</span></span></a></li>
<li><a className="dropdown-item list-item" href="#"><span>ENG</span></a></li>
</ul>
</div>
</div>
</div>
</div>
</div> */}
<div className="it-access-top-wrapper">
{userDisplayName}
</div> </div>
</div> </div>
<div className="rightDiv">
<div className="rightDivInside info padding_r">
<div className="clear"></div>
</div>
</div>
<div className="clear"></div>
</div>
</div>
<div className="clear"></div>
</div>
</header>
<div className="grid_12 console_main">
<div className="inside">
{moduleTitle && (
<div>
<div className="fe-module-header-container">
<h2>{moduleTitle}</h2>
</div>
</div>
)}
<div className="receipt">
<div className="container">
{pageTitle && <h1>{pageTitle}</h1>}
{heroImageSrc && <img src={heroImageSrc} alt="Hero Image" className="hero-image" />}
{pageDescription && <p>{pageDescription}</p>}
{environmentMessage && <div>{environmentMessage}</div>}
{description && <div>{description}</div>}
{hasAuthenticationError ? (
<FluentUI.MessageBar messageBarType={FluentUI.MessageBarType.error} isMultiline={false}>
Accesso non autorizzato. Parametri obbligatori mancanti ({missingMandatoryParams.join(', ')}).
</FluentUI.MessageBar>
) : (
<>
{this.renderExtraContentPre()}
<form action="https://procedure.unipr.it/rwe2/ComeBackToElixAndSave" method="post" acceptCharset="ISO-8859-1">
{this.createMandatoryFormFields(queryParams)}
{this.createAdditionalFormFields()}
{this.renderCustomFormFields()}
<input type="submit" value="Submit" />
</form>
{this.renderExtraContentPost()}
</>
)}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>}
<div className="clear"></div> <div className="it-header-center-wrapper">
<div className="container">
<div className="it-header-center-content-wrapper">
<div className="it-brand-wrapper">
<a href="https://www.unipr.it/" title="Torna alla homepage dell'Università di Parma">
<img className='icon' src="https://procedure.unipr.it/UploadImgs/12_marchio_UNIPR_neg_b1_squareLogo.png" alt="Università di Parma" />
<div className="it-brand-text">
<p className="h2 no_toc">Università di Parma</p>
{/* <p className="h3 no_toc d-none d-md-block">Procedure Online</p> */}
</div>
</a>
</div>
<footer className="it-footer"> <div className="it-right-zone">
<div className="it-footer-main"> {/* <div className="it-search-wrapper">
<div className="container"> <span className="d-none d-md-block">Cerca</span>
<div className="row clearfix"> <button className="search-link" aria-label="Cerca">
<div className="col-sm-12"> <span className="it-search"></span>
<div className="footer-content"> </button>
<div className="subfooter-top">powered by <span className="highlight">elixForms</span></div> </div> */}
<div className="subfooter-bottom"><div>versione 1.24.0</div></div> </div>
</div>
</div>
</div>
</header>
<div className="container my-2 mx-4">
<div className='cmp-breadcrumb' role='navigation'>
<nav className="breadcrumb-container" aria-label="breadcrumb">
<ol className="breadcrumb">
<li className='breadcrumb-item'>Procedure Online<span className="separator">&gt;</span></li>
<li className="breadcrumb-item active" aria-current="page">{moduleName}</li>
</ol>
</nav>
</div>
{headerTitleImageSrc && <img src={headerTitleImageSrc} alt="Hero Image" className="hero-image img-fluid mb-3" />}
{headerTitle && (<p className='h2'>{headerTitle}</p>)}
<div className="container">
<div className="card shadow-sm mb-4">
<div className="card-body">
{cardTitle && <p className="h3 mb-3">{cardTitle}</p>}
{cardDescription && <p className="lead text-secondary">{cardDescription}</p>}
{(alertInfoTitle || alertInfoMessage) &&
<div className="custom-alert custom-alert-warning" role="status">
{alertInfoTitle && <div><b>{alertInfoTitle}</b></div>}
{alertInfoMessage && <div className='small'>{alertInfoMessage}</div>}
</div>}
{hasAuthenticationError ? (
<div className="alert alert-danger" role="alert">Accesso non autorizzato!</div>
) : (
<>
{this.renderExtraContentPre()}
{(instructionsTitle || instructionsMessage) &&
<div className="custom-alert custom-alert-info" role="status">
{instructionsTitle && <div><b>{instructionsTitle}</b></div>}
{instructionsMessage && <div className='small'>{instructionsMessage}</div>}
</div> </div>
}
<form action="https://procedure.unipr.it/rwe2/ComeBackToElixAndSave" method="post" acceptCharset="ISO-8859-1" className="mt-3">
{this.createMandatoryFormFields(queryParams)}
{this.createAdditionalFormFields()}
{this.renderCustomFormFields()}
<div className="mt-4 d-flex justify-content-end">
<button type="submit" className="btn btn-primary">{submitText ?? 'INVIA'} <span className="it-arrow-right text-white ms-2"></span></button>
</div>
</form>
{this.renderExtraContentPost()}
</>
)}
</div>
</div>
</div>
</div>
<footer className="it-footer text-white">
<div className="it-footer-small-prints clearfix">
<div className="container">
<section>
<div className="row clearfix">
<div className="col-sm-12">
<div className="footer-content text-center p-2">
<div className="subfooter-top">powered by <span className="highlight">elixForms</span></div>
{/* <div className="subfooter-bottom"><div>versione 1.24.0</div></div> */}
</div> </div>
</div> </div>
</div> </div>
</div> </section>
</footer> </div>
</div> </div>
<div className="clear"></div> </footer>
</div>
</div> </div>
); );
} }
@@ -235,158 +279,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}>
name={paramName} {values.map((entry) => {
label={label} const id = `${paramName}_${entry.value}`;
options={radios} return (
required={required} <div className="form-check" key={id}>
defaultSelectedKey={`${paramName}_${radioValue}`} <input
onChange={(event, option) => { id={id}
const value = option?.value ?? ''; className="form-check-input"
this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () => type="radio"
console.log(`Radio ${option?.id} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`) name={paramName}
value={entry.value}
checked={currentValue === String(entry.value)}
onChange={() => {
this.setState({ formData: { ...this.state.formData, [paramName]: String(entry.value) } });
}}
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}>
name={paramName} {[{ value: 'true', label: 'Sì' }, { value: 'false', label: 'No' }].map((val) => {
label={label} const id = `${paramName}_${val.value}`;
options={radios} return (
required={required} <div className="form-check" key={id}>
defaultSelectedKey={`${paramName}_${boolValue}`} <input
onChange={(event, option) => { id={id}
const value = option?.value ?? ''; className="form-check-input"
this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () => type="radio"
console.log(`Boolean ${option?.id} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`) name={paramName}
value={val.value}
checked={currentValue === val.value}
onChange={() => {
this.setState({ formData: { ...this.state.formData, [paramName]: val.value } });
}}
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();
@@ -395,72 +461,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} name={paramName}
options={options} className="form-select"
selectedKey={currentValue} required={required}
placeholder='---' value={currentValue}
required={required} onChange={(event) => {
className='isiportalPartialAdminFormFieldSelect' this.setState({ formData: { ...this.state.formData, [paramName]: event.target.value } });
onChange={(event, option) => { }}
if (option) { >
const value = option.key.toString(); <option value="">Seleziona...</option>
this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () => {values.map(entry => (
console.log(`Dropdown ${paramName} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`) <option key={entry.value} value={entry.value}>{entry.label}</option>
); ))}
} </select>
}}
/>
<input type="hidden" name={paramName} id={paramName + '_hidden'} value={currentValue} />
</>
).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:
+16 -14
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,18 @@ 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="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="col-12 col-md-4">
{this.label}{this.separator} <div className="text-md-end pe-md-3 mt-2">
</div></div><div className="iuClearContainer">&nbsp;</div></div></div> {this.label}{this.separator}
<div className="attrDisplay_right attrDisplay_input"><div className="iuInputContainer iuTypeString"><div className="attrDisplay_center"><div className="attrDisplay_middle"> </div>
</div>
<div className="col-12 col-md-8">
{this.input} {this.input}
</div></div><div className="iuClearContainer">&nbsp;</div></div></div> </div>
</div></>); </div>
);
} }
} }
@@ -1,19 +1,19 @@
import type { JSX } from "react";
import type { IElixFormsComponentCustomFormFieldFactory } from "./IElixFormsComponentCustomFormFieldFactory";
export interface IElixFormsComponentProperties { export interface IElixFormsComponentProperties {
description: string; isDarkTheme?: boolean;
isDarkTheme: boolean; userDisplayName?: string;
environmentMessage: string;
hasTeamsContext: boolean;
userDisplayName: string;
additionalFieldsJson?: string; additionalFieldsJson?: string;
// Layout Properties // Layout Properties
moduleName: string;
headerTitle?: string; headerTitle?: string;
pageTitle?: string; headerHeroImageSrc?: string;
pageDescription?: string; cardTitle: string;
heroImageSrc?: string; cardDescription?: string;
alertInfoTitle?: string;
alertInfoMessage?: string;
instructionsTitle?: string;
instructionsMessage?: string;
submitText?: string;
} }
// Additional Fields JSON Schema // Additional Fields JSON Schema
+3
View File
@@ -0,0 +1,3 @@
declare module "*.css";
declare module "*.svg";
declare module "*.png";
+1 -1
View File
@@ -9,7 +9,7 @@
"jsx": "react-jsx", "jsx": "react-jsx",
"isolatedModules": true, "isolatedModules": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"skipLibCheck": true, "skipLibCheck": true
}, },
"include": [ "include": [
"**/*" "**/*"
+380 -300
View File
@@ -1,303 +1,383 @@
{ {
"name": "react", "name": "react",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"dependencies": { "dependencies": {
"@fluentui/react": "^8.125.6", "bootstrap-italia": "^2.18.2",
"react": "^19.2.7", "react": "^19.2.7",
"react-dom": "^19.2.5" "react-dom": "^19.2.5"
} }
}, },
"node_modules/@fluentui/date-time-utilities": { "node_modules/@babel/runtime": {
"version": "8.6.11", "version": "7.29.7",
"resolved": "https://registry.npmjs.org/@fluentui/date-time-utilities/-/date-time-utilities-8.6.11.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
"integrity": "sha512-zq49tveFzmzwgaJ73rVvxu9+rqhPBIAJSbevciIQnmvv6dlh2GzZcL14Zevk9QV+q6CWaF6yzvhT11E2TpAv8Q==", "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
"license": "MIT", "license": "MIT",
"dependencies": { "engines": {
"@fluentui/set-version": "^8.2.24", "node": ">=6.9.0"
"tslib": "^2.1.0" }
} },
}, "node_modules/@popperjs/core": {
"node_modules/@fluentui/dom-utilities": { "version": "2.11.8",
"version": "2.3.10", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"resolved": "https://registry.npmjs.org/@fluentui/dom-utilities/-/dom-utilities-2.3.10.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"integrity": "sha512-6WDImiLqTOpkEtfUKSStcTDpzmJfL6ZammomcjawN9xH/8u8G3Hx72CIt2MNck9giw/oUlNLJFdWRAjeP3rmPQ==", "license": "MIT",
"license": "MIT", "funding": {
"dependencies": { "type": "opencollective",
"@fluentui/set-version": "^8.2.24", "url": "https://opencollective.com/popperjs"
"tslib": "^2.1.0" }
} },
}, "node_modules/@splidejs/splide": {
"node_modules/@fluentui/font-icons-mdl2": { "version": "4.1.4",
"version": "8.5.73", "resolved": "https://registry.npmjs.org/@splidejs/splide/-/splide-4.1.4.tgz",
"resolved": "https://registry.npmjs.org/@fluentui/font-icons-mdl2/-/font-icons-mdl2-8.5.73.tgz", "integrity": "sha512-5I30evTJcAJQXt6vJ26g2xEkG+l1nXcpEw4xpKh0/FWQ8ozmAeTbtniVtVmz2sH1Es3vgfC4SS8B2X4o5JMptA==",
"integrity": "sha512-pyR9OE8LJMEVDAUTaTu/rGXaMCsyRqpp5124DhBoFNf6q5kI660IgbzuQadCpyMBEQadYI5/GLkLN7b1Lgou9Q==", "license": "MIT"
"license": "MIT", },
"dependencies": { "node_modules/@types/bootstrap": {
"@fluentui/set-version": "^8.2.24", "version": "5.2.11",
"@fluentui/style-utilities": "^8.15.1", "resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.2.11.tgz",
"@fluentui/utilities": "^8.17.2", "integrity": "sha512-69ykbLiNzQxi6Q/y/aYJLrlzI5QkLQjhIYW2U+cxujBxAmaBtmXKtxnFes59Ttfpt936as6xJU0dYnmVpcwIwg==",
"tslib": "^2.1.0" "license": "MIT",
} "dependencies": {
}, "@popperjs/core": "^2.9.2"
"node_modules/@fluentui/foundation-legacy": { }
"version": "8.6.6", },
"resolved": "https://registry.npmjs.org/@fluentui/foundation-legacy/-/foundation-legacy-8.6.6.tgz", "node_modules/@videojs/http-streaming": {
"integrity": "sha512-PIcMLOymLvIunp9DrBHUT+dZqwyslKsIOPi+5g/fLU/ySzCg3fs6wQyWHxN3esI5FsSfWi+yCpp+6u5+N00kJQ==", "version": "3.17.5",
"license": "MIT", "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-3.17.5.tgz",
"dependencies": { "integrity": "sha512-PRRQVu0hYW5h0XvRWpIyIpEatpMZzZtf7L1avV89Hnu+69f+0c7oLsSL9jwpg2nbvzGkdX5rQxQkz5Vc7tFR9w==",
"@fluentui/merge-styles": "^8.6.14", "license": "Apache-2.0",
"@fluentui/set-version": "^8.2.24", "dependencies": {
"@fluentui/style-utilities": "^8.15.1", "@babel/runtime": "^7.12.5",
"@fluentui/utilities": "^8.17.2", "@videojs/vhs-utils": "^4.1.2",
"tslib": "^2.1.0" "aes-decrypter": "^4.0.2",
}, "global": "^4.4.0",
"peerDependencies": { "m3u8-parser": "^7.2.0",
"@types/react": ">=16.8.0 <20.0.0", "mpd-parser": "^1.4.0",
"react": ">=16.8.0 <20.0.0" "mux.js": "7.1.0",
} "video.js": "^7 || ^8"
}, },
"node_modules/@fluentui/keyboard-key": { "engines": {
"version": "0.4.23", "node": ">=8",
"resolved": "https://registry.npmjs.org/@fluentui/keyboard-key/-/keyboard-key-0.4.23.tgz", "npm": ">=5"
"integrity": "sha512-9GXeyUqNJUdg5JiQUZeGPiKnRzMRi9YEUn1l9zq6X/imYdMhxHrxpVZS12129cBfgvPyxt9ceJpywSfmLWqlKA==", },
"license": "MIT", "peerDependencies": {
"dependencies": { "video.js": "^8.19.0"
"tslib": "^2.1.0" }
} },
}, "node_modules/@videojs/vhs-utils": {
"node_modules/@fluentui/merge-styles": { "version": "4.1.2",
"version": "8.6.14", "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-4.1.2.tgz",
"resolved": "https://registry.npmjs.org/@fluentui/merge-styles/-/merge-styles-8.6.14.tgz", "integrity": "sha512-1Dsv5dtWOq0LP1G37O46FQFA2tKAZON5UMe6dFPxT7JvPhEx8QIXcFQxan8+hHI3fX6aomFRFrUBJNgRYQDSXA==",
"integrity": "sha512-vghuHFAfQgS9WLIIs4kgDOCh/DHd5vGIddP4/bzposhlAVLZR6wUBqldm9AuCdY88r5LyCRMavVJLV+Up3xdvA==", "license": "MIT",
"license": "MIT", "dependencies": {
"dependencies": { "@babel/runtime": "^7.12.5",
"@fluentui/set-version": "^8.2.24", "global": "^4.4.0"
"tslib": "^2.1.0" },
} "engines": {
}, "node": ">=8",
"node_modules/@fluentui/react": { "npm": ">=5"
"version": "8.125.6", }
"resolved": "https://registry.npmjs.org/@fluentui/react/-/react-8.125.6.tgz", },
"integrity": "sha512-uvq0PdAL+Tznikek54zbS31JefTqcPaCkwjHjJz0t8NAC2ZFHozKnkApaQo2+sf0390K3k1ErGWr/+3Tvc6+JQ==", "node_modules/@videojs/xhr": {
"license": "MIT", "version": "2.7.0",
"dependencies": { "resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.7.0.tgz",
"@fluentui/date-time-utilities": "^8.6.11", "integrity": "sha512-giab+EVRanChIupZK7gXjHy90y3nncA2phIOyG3Ne5fvpiMJzvqYwiTOnEVW2S4CoYcuKJkomat7bMXA/UoUZQ==",
"@fluentui/font-icons-mdl2": "^8.5.73", "license": "MIT",
"@fluentui/foundation-legacy": "^8.6.6", "dependencies": {
"@fluentui/merge-styles": "^8.6.14", "@babel/runtime": "^7.5.5",
"@fluentui/react-focus": "^8.10.6", "global": "~4.4.0",
"@fluentui/react-hooks": "^8.10.2", "is-function": "^1.0.1"
"@fluentui/react-portal-compat-context": "^9.0.15", }
"@fluentui/react-window-provider": "^2.3.2", },
"@fluentui/set-version": "^8.2.24", "node_modules/@xmldom/xmldom": {
"@fluentui/style-utilities": "^8.15.1", "version": "0.8.13",
"@fluentui/theme": "^2.7.2", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz",
"@fluentui/utilities": "^8.17.2", "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==",
"@microsoft/load-themed-styles": "^1.10.26", "license": "MIT",
"tslib": "^2.1.0" "engines": {
}, "node": ">=10.0.0"
"peerDependencies": { }
"@types/react": ">=16.8.0 <20.0.0", },
"@types/react-dom": ">=16.8.0 <20.0.0", "node_modules/aes-decrypter": {
"react": ">=16.8.0 <20.0.0", "version": "4.0.2",
"react-dom": ">=16.8.0 <20.0.0" "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-4.0.2.tgz",
} "integrity": "sha512-lc+/9s6iJvuaRe5qDlMTpCFjnwpkeOXp8qP3oiZ5jsj1MRg+SBVUmmICrhxHvc8OELSmc+fEyyxAuppY6hrWzw==",
}, "license": "Apache-2.0",
"node_modules/@fluentui/react-focus": { "dependencies": {
"version": "8.10.6", "@babel/runtime": "^7.12.5",
"resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-8.10.6.tgz", "@videojs/vhs-utils": "^4.1.1",
"integrity": "sha512-hMQw7AETttfex3XEAB/XQalJSL8/RgzOdJrL1RPKcrxdkBal0C1MRQ43Bbmw/4WtI0vK0wI4+jHvCIlM0lkodA==", "global": "^4.4.0",
"license": "MIT", "pkcs7": "^1.0.4"
"dependencies": { }
"@fluentui/keyboard-key": "^0.4.23", },
"@fluentui/merge-styles": "^8.6.14", "node_modules/animejs": {
"@fluentui/set-version": "^8.2.24", "version": "3.2.2",
"@fluentui/style-utilities": "^8.15.1", "resolved": "https://registry.npmjs.org/animejs/-/animejs-3.2.2.tgz",
"@fluentui/utilities": "^8.17.2", "integrity": "sha512-Ao95qWLpDPXXM+WrmwcKbl6uNlC5tjnowlaRYtuVDHHoygjtIPfDUoK9NthrlZsQSKjZXlmji2TrBUAVbiH0LQ==",
"tslib": "^2.1.0" "license": "MIT"
}, },
"peerDependencies": { "node_modules/bootstrap-italia": {
"@types/react": ">=16.8.0 <20.0.0", "version": "2.18.2",
"react": ">=16.8.0 <20.0.0" "resolved": "https://registry.npmjs.org/bootstrap-italia/-/bootstrap-italia-2.18.2.tgz",
} "integrity": "sha512-qoOrq8lDfmELVS46PJekZVacWCkVqG+PF4g41QBevbIWgw2LII6mESrE+ntADMrEKhDAVYPHt0Z9viHhabzXFw==",
}, "license": "BSD-3-Clause",
"node_modules/@fluentui/react-hooks": { "dependencies": {
"version": "8.10.2", "@popperjs/core": "^2.11.6",
"resolved": "https://registry.npmjs.org/@fluentui/react-hooks/-/react-hooks-8.10.2.tgz", "@splidejs/splide": "^4.1.4",
"integrity": "sha512-HAd5cX50yKW/LljWlwt+FpSpdS/pNJutk9kMb7FyzxfoGBulL7sj6vX2HvxhSKyJMRKuTstXTdfJmsh22+3W3w==", "@types/bootstrap": "^5.2.6",
"license": "MIT", "animejs": "^3.2.1",
"dependencies": { "design-tokens-italia": "^1.3.3",
"@fluentui/react-window-provider": "^2.3.2", "just-validate": "^4.3.0",
"@fluentui/set-version": "^8.2.24", "minimasonry": "^1.3.2",
"@fluentui/utilities": "^8.17.2", "progressbar.js": "^1.1.0",
"tslib": "^2.1.0" "uuid": "^14.0.0",
}, "video.js": "^8.23.7"
"peerDependencies": { }
"@types/react": ">=16.8.0 <20.0.0", },
"react": ">=16.8.0 <20.0.0" "node_modules/design-tokens-italia": {
} "version": "1.3.3",
}, "resolved": "https://registry.npmjs.org/design-tokens-italia/-/design-tokens-italia-1.3.3.tgz",
"node_modules/@fluentui/react-portal-compat-context": { "integrity": "sha512-9vdoT91ln2H/mppLO1LkTD9q18YPbT+BX9seJxzAC8r7gPlDRmbPJwsmt8dqXnULaH8Lqkp1KEdL8HW3Ah6K6A==",
"version": "9.0.15", "license": "BSD-3-Clause"
"resolved": "https://registry.npmjs.org/@fluentui/react-portal-compat-context/-/react-portal-compat-context-9.0.15.tgz", },
"integrity": "sha512-DpV+qtFvM3dmH1j8ZD+YcM5vaTvmQPHUAx6tQnnmIoYJWs2R0wU/L5p2EajXy7zSg74jrDbDRxzaziamoOaJdg==", "node_modules/dom-walk": {
"license": "MIT", "version": "0.1.2",
"dependencies": { "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
"@swc/helpers": "^0.5.1" "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
}, },
"peerDependencies": { "node_modules/fsevents": {
"@types/react": ">=16.14.0 <20.0.0", "version": "2.3.3",
"react": ">=16.14.0 <20.0.0" "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
} "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
}, "hasInstallScript": true,
"node_modules/@fluentui/react-window-provider": { "license": "MIT",
"version": "2.3.2", "optional": true,
"resolved": "https://registry.npmjs.org/@fluentui/react-window-provider/-/react-window-provider-2.3.2.tgz", "os": [
"integrity": "sha512-T15zFPIWr9De8hNkapne7YyvcxclyTK2bMXXHZwbWLkVeH/lGHRG0CIy/calNGKa86wuzMJhq8iqFW2W6+EwVQ==", "darwin"
"license": "MIT", ],
"dependencies": { "engines": {
"@fluentui/set-version": "^8.2.24", "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
"tslib": "^2.1.0" }
}, },
"peerDependencies": { "node_modules/global": {
"@types/react": ">=16.8.0 <20.0.0", "version": "4.4.0",
"react": ">=16.8.0 <20.0.0" "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
} "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
}, "license": "MIT",
"node_modules/@fluentui/set-version": { "dependencies": {
"version": "8.2.24", "min-document": "^2.19.0",
"resolved": "https://registry.npmjs.org/@fluentui/set-version/-/set-version-8.2.24.tgz", "process": "^0.11.10"
"integrity": "sha512-8uNi2ThvNgF+6d3q2luFVVdk/wZV0AbRfJ85kkvf2+oSRY+f6QVK0w13vMorNhA5puumKcZniZoAfUF02w7NSg==", }
"license": "MIT", },
"dependencies": { "node_modules/is-function": {
"tslib": "^2.1.0" "version": "1.0.2",
} "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz",
}, "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==",
"node_modules/@fluentui/style-utilities": { "license": "MIT"
"version": "8.15.1", },
"resolved": "https://registry.npmjs.org/@fluentui/style-utilities/-/style-utilities-8.15.1.tgz", "node_modules/just-validate": {
"integrity": "sha512-EwjJE7P7XWViNIN+Klm4rFCaADujsfwHB4nzkYeD0zjMokrznsiAvqERxaGZMCRH7tO+DLKITt7kaoQstNLCVQ==", "version": "4.3.0",
"license": "MIT", "resolved": "https://registry.npmjs.org/just-validate/-/just-validate-4.3.0.tgz",
"dependencies": { "integrity": "sha512-9yS7ZnFk95vq+O7CAa7XZEJvZN5i6jTQWNhLwOjtgkSqiLxo7GZJhK8ViylP76fXPfHLs8tYWqi81XgxPF3OGA==",
"@fluentui/merge-styles": "^8.6.14", "license": "MIT"
"@fluentui/set-version": "^8.2.24", },
"@fluentui/theme": "^2.7.2", "node_modules/lodash.merge": {
"@fluentui/utilities": "^8.17.2", "version": "4.6.2",
"@microsoft/load-themed-styles": "^1.10.26", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"tslib": "^2.1.0" "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
} "license": "MIT"
}, },
"node_modules/@fluentui/theme": { "node_modules/m3u8-parser": {
"version": "2.7.2", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@fluentui/theme/-/theme-2.7.2.tgz", "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-7.2.0.tgz",
"integrity": "sha512-UXGNfGa/1bLmYrOpmHXdvyc7CzlNSKUQAADweTncbNoMF1DvscWEjPj5kxFgCmOU8wVtvvn4GraNNUSWtNxeeA==", "integrity": "sha512-CRatFqpjVtMiMaKXxNvuI3I++vUumIXVVT/JpCpdU/FynV/ceVw1qpPyyBNindL+JlPMSesx+WX1QJaZEJSaMQ==",
"license": "MIT", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@fluentui/merge-styles": "^8.6.14", "@babel/runtime": "^7.12.5",
"@fluentui/set-version": "^8.2.24", "@videojs/vhs-utils": "^4.1.1",
"@fluentui/utilities": "^8.17.2", "global": "^4.4.0"
"tslib": "^2.1.0" }
}, },
"peerDependencies": { "node_modules/min-document": {
"@types/react": ">=16.8.0 <20.0.0", "version": "2.19.2",
"react": ">=16.8.0 <20.0.0" "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.2.tgz",
} "integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==",
}, "license": "MIT",
"node_modules/@fluentui/utilities": { "dependencies": {
"version": "8.17.2", "dom-walk": "^0.1.0"
"resolved": "https://registry.npmjs.org/@fluentui/utilities/-/utilities-8.17.2.tgz", }
"integrity": "sha512-TmeWVtGN+Lk0mch7tuRcbkeMdrBwltI68fvQbPwcNLo4igFtTInMmjEnVJGa7pBQN5lQAmHYqB9IJI6RZU/t6w==", },
"license": "MIT", "node_modules/minimasonry": {
"dependencies": { "version": "1.3.2",
"@fluentui/dom-utilities": "^2.3.10", "resolved": "https://registry.npmjs.org/minimasonry/-/minimasonry-1.3.2.tgz",
"@fluentui/merge-styles": "^8.6.14", "integrity": "sha512-ygvf5JIR4NT1LbM6o8z1oz1icTkta72gXKwidvSqIVxuzVd/P21UI0Wbq1J+MStAu50Vvr8qWYNSz1W4uycqMQ==",
"@fluentui/react-window-provider": "^2.3.2", "license": "MIT"
"@fluentui/set-version": "^8.2.24", },
"tslib": "^2.1.0" "node_modules/mpd-parser": {
}, "version": "1.4.0",
"peerDependencies": { "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.4.0.tgz",
"@types/react": ">=16.8.0 <20.0.0", "integrity": "sha512-blbA7XpAaOdC/PR0Tu8GiUxlx6CpBuRowQPYV7lhi9Yr9G9+dkPXIUjqjzc/NCn/uZZPopluaJlIYntIEI/VLw==",
"react": ">=16.8.0 <20.0.0" "license": "Apache-2.0",
} "dependencies": {
}, "@babel/runtime": "^7.12.5",
"node_modules/@microsoft/load-themed-styles": { "@videojs/vhs-utils": "^4.1.2",
"version": "1.10.295", "@xmldom/xmldom": "^0.8.3",
"resolved": "https://registry.npmjs.org/@microsoft/load-themed-styles/-/load-themed-styles-1.10.295.tgz", "global": "^4.4.0"
"integrity": "sha512-W+IzEBw8a6LOOfRJM02dTT7BDZijxm+Z7lhtOAz1+y9vQm1Kdz9jlAO+qCEKsfxtUOmKilW8DIRqFw2aUgKeGg==", },
"license": "MIT" "bin": {
}, "mpd-to-m3u8-json": "bin/parse.js"
"node_modules/@swc/helpers": { }
"version": "0.5.23", },
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", "node_modules/mux.js": {
"integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", "version": "7.1.0",
"license": "Apache-2.0", "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-7.1.0.tgz",
"dependencies": { "integrity": "sha512-NTxawK/BBELJrYsZThEulyUMDVlLizKdxyAsMuzoCD1eFj97BVaA8D/CvKsKu6FOLYkFojN5CbM9h++ZTZtknA==",
"tslib": "^2.8.0" "license": "Apache-2.0",
} "dependencies": {
}, "@babel/runtime": "^7.11.2",
"node_modules/@types/react": { "global": "^4.4.0"
"version": "19.2.17", },
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", "bin": {
"integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "muxjs-transmux": "bin/transmux.js"
"license": "MIT", },
"peer": true, "engines": {
"dependencies": { "node": ">=8",
"csstype": "^3.2.2" "npm": ">=5"
} }
}, },
"node_modules/@types/react-dom": { "node_modules/pkcs7": {
"version": "19.2.3", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz",
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==",
"license": "MIT", "license": "Apache-2.0",
"peer": true, "dependencies": {
"peerDependencies": { "@babel/runtime": "^7.5.5"
"@types/react": "^19.2.0" },
} "bin": {
}, "pkcs7": "bin/cli.js"
"node_modules/csstype": { }
"version": "3.2.3", },
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "node_modules/process": {
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "version": "0.11.10",
"license": "MIT" "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
}, "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
"node_modules/react": { "license": "MIT",
"version": "19.2.7", "engines": {
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", "node": ">= 0.6.0"
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", }
"license": "MIT", },
"peer": true, "node_modules/progressbar.js": {
"engines": { "version": "1.1.1",
"node": ">=0.10.0" "resolved": "https://registry.npmjs.org/progressbar.js/-/progressbar.js-1.1.1.tgz",
} "integrity": "sha512-FBsw3BKsUbb+hNeYfiP3xzvAAQrPi4DnGDw66bCmfuRCDLcslxyxv2GyYUdBSKFGSIBa73CUP5WMcl6F8AAXlw==",
}, "license": "MIT",
"node_modules/react-dom": { "dependencies": {
"version": "19.2.7", "lodash.merge": "^4.6.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", "shifty": "^2.8.3"
"integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", }
"license": "MIT", },
"peer": true, "node_modules/react": {
"dependencies": { "version": "19.2.7",
"scheduler": "^0.27.0" "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
}, "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
"peerDependencies": { "license": "MIT",
"react": "^19.2.7" "engines": {
} "node": ">=0.10.0"
}, }
"node_modules/scheduler": { },
"version": "0.27.0", "node_modules/react-dom": {
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "version": "19.2.7",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
"license": "MIT" "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
}, "license": "MIT",
"node_modules/tslib": { "dependencies": {
"version": "2.8.1", "scheduler": "^0.27.0"
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", },
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "peerDependencies": {
"license": "0BSD" "react": "^19.2.7"
}
},
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT"
},
"node_modules/shifty": {
"version": "2.20.4",
"resolved": "https://registry.npmjs.org/shifty/-/shifty-2.20.4.tgz",
"integrity": "sha512-4Y0qRkg8ME5XN8yGNAwmFOmsIURGFKT9UQfNL6DDJQErYtN5HsjyoBuJn41ZQfTkuu2rIbRMn9qazjKsDpO2TA==",
"license": "MIT",
"optionalDependencies": {
"fsevents": "^2.3.2"
}
},
"node_modules/uuid": {
"version": "14.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.1.tgz",
"integrity": "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist-node/bin/uuid"
}
},
"node_modules/video.js": {
"version": "8.23.9",
"resolved": "https://registry.npmjs.org/video.js/-/video.js-8.23.9.tgz",
"integrity": "sha512-3UCqyFW4wIusQu4TuhgRUxqtbzbj0WPT9hps0XIEXST43QBD11zb+bG7tBPrinrZztwVYdXCiOBo2pcGj9L05w==",
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime": "^7.28.4",
"@videojs/http-streaming": "^3.17.5",
"@videojs/vhs-utils": "^4.1.2",
"@videojs/xhr": "2.7.0",
"aes-decrypter": "^4.0.2",
"global": "4.4.0",
"m3u8-parser": "^7.2.0",
"mpd-parser": "^1.4.0",
"mux.js": "^7.0.1",
"videojs-contrib-quality-levels": "4.1.0",
"videojs-font": "4.2.0",
"videojs-vtt.js": "0.15.5"
}
},
"node_modules/videojs-contrib-quality-levels": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-4.1.0.tgz",
"integrity": "sha512-TfrXJJg1Bv4t6TOCMEVMwF/CoS8iENYsWNKip8zfhB5kTcegiFYezEA0eHAJPU64ZC8NQbxQgOwAsYU8VXbOWA==",
"license": "Apache-2.0",
"dependencies": {
"global": "^4.4.0"
},
"engines": {
"node": ">=16",
"npm": ">=8"
},
"peerDependencies": {
"video.js": "^8"
}
},
"node_modules/videojs-font": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-4.2.0.tgz",
"integrity": "sha512-YPq+wiKoGy2/M7ccjmlvwi58z2xsykkkfNMyIg4xb7EZQQNwB71hcSsB3o75CqQV7/y5lXkXhI/rsGAS7jfEmQ==",
"license": "Apache-2.0"
},
"node_modules/videojs-vtt.js": {
"version": "0.15.5",
"resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.5.tgz",
"integrity": "sha512-yZbBxvA7QMYn15Lr/ZfhhLPrNpI/RmCSCqgIff57GC2gIrV5YfyzLfLyZMj0NnZSAz8syB4N0nHXpZg9MyrMOQ==",
"license": "Apache-2.0",
"dependencies": {
"global": "^4.3.1"
}
}
} }
}
} }
+5 -5
View File
@@ -1,7 +1,7 @@
{ {
"dependencies": { "dependencies": {
"@fluentui/react": "^8.125.6", "bootstrap-italia": "^2.18.2",
"react": "^19.2.7", "react": "^19.2.7",
"react-dom": "^19.2.5" "react-dom": "^19.2.5"
} }
} }
@@ -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>
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,104 @@
@import url('https://cdn.jsdelivr.net/npm/bootstrap-italia@2.0.0/dist/css/bootstrap-italia.min.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,22 @@
import './App.css'
import RecuperoPropostaCctDaContrattiComponent from './RecuperoPropostaCctDaContrattiComponent';
import config from './config.json';
function App() {
return (
<RecuperoPropostaCctDaContrattiComponent
moduleName={config.component.moduleName}
headerTitle={config.component.headerTitle}
headerHeroImageSrc={config.component.headerHeroImageSrc}
cardTitle={config.component.cardTitle}
cardDescription={config.component.cardDescription}
alertInfoTitle={config.component.alertInfoTitle}
alertInfoMessage={config.component.alertInfoMessage}
instructionsTitle={config.component.instructionsTitle}
instructionsMessage={config.component.instructionsMessage}
submitText={config.component.submitText}
/>
);
}
export default App
@@ -0,0 +1,305 @@
import React, { type JSX } from 'react';
import ElixFormsComponentAbstract from '@common/src/ElixFormsComponentAbstract';
import type { IElixFormsComponentCustomFormFieldFactory } from '@common/src/IElixFormsComponentCustomFormFieldFactory';
import { ElixFormsElement } from '@common/src/ElixFormsElement';
import { QueryParamHelper } from '@common/src/QueryParamHelper';
import config from './config.json';
interface ContrattoResult {
idContratto: string;
titoloContratto: string;
idDomanda: string;
idRicevuta: string;
}
interface RecuperoPropostaState {
searchText: string;
searchResults: ContrattoResult[];
isLoading: boolean;
isDropdownOpen: boolean;
selectedContract: ContrattoResult | null;
highlightedIndex: number;
errorMessage: string;
}
export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsComponentAbstract {
private autocompleteState: RecuperoPropostaState = {
searchText: '',
searchResults: [],
isLoading: false,
isDropdownOpen: false,
selectedContract: null,
highlightedIndex: -1,
errorMessage: '',
};
private debounceTimer: ReturnType<typeof setTimeout> | null = null;
private autocompleteRef: React.RefObject<HTMLDivElement | null> = React.createRef();
private static readonly formFieldKeys = {
codiceContratto: config.elixForms.outputs.codiceContratto,
titoloContratto: config.elixForms.outputs.titoloContratto,
idDomanda: config.elixForms.outputs.idDomanda,
idRicevuta: config.elixForms.outputs.idRicevuta,
} as const;
private codiceFiscale: string;
constructor(props: any) {
super(props);
this.codiceFiscale = QueryParamHelper.getDecodedTextFromQuery(config.elixForms.inputs.codiceFiscale) ?? '';
}
override componentDidMount(): void {
document.addEventListener('mousedown', this.handleClickOutside);
}
override componentWillUnmount(): void {
document.removeEventListener('mousedown', this.handleClickOutside);
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
}
private handleClickOutside = (event: MouseEvent): void => {
if (this.autocompleteRef.current && !this.autocompleteRef.current.contains(event.target as Node)) {
this.autocompleteState.isDropdownOpen = false;
this.forceUpdate();
}
};
private async fetchContratti(searchTerm: string): Promise<void> {
this.autocompleteState.isLoading = true;
this.autocompleteState.errorMessage = '';
this.forceUpdate();
try {
const url = new URL(config.contrattiWS.apiUrl);
url.searchParams.set('cod_fis', this.codiceFiscale);
url.searchParams.set('term', searchTerm);
const credentials = btoa(`${config.contrattiWS.apiUsername}:${config.contrattiWS.apiPassword}`);
const response = await fetch(url.toString(), {
method: 'GET',
headers: {
Authorization: `Basic ${credentials}`,
'X-Api-Key': config.contrattiWS.apiKey,
Accept: 'application/json',
},
});
if (!response.ok) {
throw new Error(`Errore API: ${response.status} ${response.statusText}`);
}
const data: ContrattoResult[] = await response.json();
this.autocompleteState.searchResults = data;
this.autocompleteState.isDropdownOpen = data.length > 0;
this.autocompleteState.highlightedIndex = -1;
this.autocompleteState.isLoading = false;
this.forceUpdate();
} catch (error) {
console.error('Errore durante la ricerca contratti:', error);
this.autocompleteState.searchResults = [];
this.autocompleteState.isDropdownOpen = false;
this.autocompleteState.isLoading = false;
this.autocompleteState.errorMessage = error instanceof Error ? error.message : 'Errore durante la ricerca';
this.forceUpdate();
}
}
private handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
const text = event.target.value;
this.autocompleteState.searchText = text;
if (text.length === 0) {
this.autocompleteState.searchResults = [];
this.autocompleteState.isDropdownOpen = false;
this.autocompleteState.highlightedIndex = -1;
this.autocompleteState.errorMessage = '';
this.clearSelection();
this.forceUpdate();
return;
}
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
if (text.length >= config.component.minCharsForSearch) {
this.debounceTimer = setTimeout(() => {
this.fetchContratti(text);
}, 300);
} else {
this.autocompleteState.searchResults = [];
this.autocompleteState.isDropdownOpen = false;
this.autocompleteState.highlightedIndex = -1;
this.forceUpdate();
}
};
private handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>): void => {
const { searchResults, isDropdownOpen, highlightedIndex } = this.autocompleteState;
if (!isDropdownOpen || searchResults.length === 0) return;
switch (event.key) {
case 'ArrowDown':
event.preventDefault();
this.autocompleteState.highlightedIndex = Math.min(highlightedIndex + 1, searchResults.length - 1);
this.forceUpdate();
break;
case 'ArrowUp':
event.preventDefault();
this.autocompleteState.highlightedIndex = Math.max(highlightedIndex - 1, 0);
this.forceUpdate();
break;
case 'Enter':
event.preventDefault();
if (highlightedIndex >= 0 && highlightedIndex < searchResults.length) {
this.selectContract(searchResults[highlightedIndex]);
}
break;
case 'Escape':
event.preventDefault();
this.autocompleteState.isDropdownOpen = false;
this.forceUpdate();
break;
}
};
private selectContract(contract: ContrattoResult): void {
this.autocompleteState.selectedContract = contract;
this.autocompleteState.searchText = `[${contract.idContratto}] ${contract.titoloContratto}`;
this.autocompleteState.isDropdownOpen = false;
this.autocompleteState.searchResults = [];
this.autocompleteState.highlightedIndex = -1;
this.setState({
formData: {
...this.state.formData,
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.codiceContratto]: contract.idContratto,
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.titoloContratto]: contract.titoloContratto,
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idDomanda]: contract.idDomanda,
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idRicevuta]: contract.idRicevuta,
},
});
}
private clearSelection(): void {
this.autocompleteState.selectedContract = null;
this.setState({
formData: {
...this.state.formData,
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.codiceContratto]: '',
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.titoloContratto]: '',
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idDomanda]: '',
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idRicevuta]: '',
},
});
}
private renderAutocomplete(): JSX.Element {
const { searchResults, isLoading, isDropdownOpen, highlightedIndex, errorMessage, searchText } = this.autocompleteState;
const autocompleteInput = (
<>
<div className="position-relative" ref={this.autocompleteRef}>
<input
id="autocomplete-search"
name="autocomplete-search"
className="form-control"
type="text"
placeholder="Digita almeno 3 caratteri per cercare..."
value={searchText}
onChange={this.handleSearchChange}
onKeyDown={this.handleKeyDown}
autoComplete="off"
/>
{isLoading && (
<div className="autocompleteDropdown mt-2 p-3 text-muted">
<span className="me-2">
<span className="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
</span>
Ricerca in corso...
</div>
)}
{!isLoading && isDropdownOpen && searchResults.length > 0 && (
<div className="autocompleteDropdown mt-2">
{searchResults.map((contract, index) => (
<div
key={`${contract.idContratto}_${index}`}
className={`autocompleteItem${index === highlightedIndex ? ' isHighlighted' : ''}`}
onMouseDown={(event) => {
event.preventDefault();
this.selectContract(contract);
}}
onMouseEnter={() => {
this.autocompleteState.highlightedIndex = index;
this.forceUpdate();
}}
>
<span className="fw-semibold">[{contract.idContratto}]</span>{' '}
<span>{contract.titoloContratto}</span>
</div>
))}
</div>
)}
{!isLoading && isDropdownOpen && searchResults.length === 0 && (
<div className="autocompleteDropdown mt-2 p-3 text-muted">Nessun risultato trovato</div>
)}
{errorMessage && (
<div className="custom-alert custom-alert-danger mt-2">{errorMessage}</div>
)}
</div>
{this.autocompleteState.selectedContract && (
<div className="card mt-3">
<div className="card-body">
<div className="row g-2">
<div className="col-12 col-md-6"><strong>ID Contratto:</strong> {this.autocompleteState.selectedContract.idContratto}</div>
<div className="col-12 col-md-6"><strong>Titolo:</strong> {this.autocompleteState.selectedContract.titoloContratto}</div>
<div className="col-12 col-md-6"><strong>ID Domanda:</strong> {this.autocompleteState.selectedContract.idDomanda}</div>
<div className="col-12 col-md-6"><strong>ID Ricevuta:</strong> {this.autocompleteState.selectedContract.idRicevuta}</div>
</div>
</div>
</div>
)}
</>
);
return new ElixFormsElement(
<label className="form-label fw-semibold" htmlFor="autocomplete-search">Cerca Contratto</label>,
autocompleteInput
).render();
}
protected override createCustomFormFields(formFieldFactory: IElixFormsComponentCustomFormFieldFactory): JSX.Element {
const fieldKeys = RecuperoPropostaCctDaContrattiComponent.formFieldKeys;
const idContratto = this.state.formData[fieldKeys.codiceContratto] ?? '';
const titoloContratto = this.state.formData[fieldKeys.titoloContratto] ?? '';
const idDomanda = this.state.formData[fieldKeys.idDomanda] ?? '';
const idRicevuta = this.state.formData[fieldKeys.idRicevuta] ?? '';
return (
<>
{this.renderAutocomplete()}
<input type="hidden" id="idContratto_hidden" name={fieldKeys.codiceContratto} value={idContratto} />
<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="idRicevuta_hidden" name={fieldKeys.idRicevuta} value={idRicevuta} />
</>
);
}
}
@@ -0,0 +1,32 @@
{
"component": {
"minCharsForSearch": 3,
"moduleName": "Ripartizione Utili / Compensi - Presentazione Proposta",
"headerTitle": "",
"headerHeroImageSrc": "",
"cardTitle": "Recupero Proposta CCT da Contratti",
"cardDescription": "",
"alertInfoTitle": "",
"alertInfoMessage": "",
"instructionsTitle": "Istruzioni di compilazione",
"instructionsMessage": "Completa i campi richiesti per procedere con la richiesta.",
"submitText": "CONFERMA E PROSEGUI"
},
"elixForms": {
"inputs": {
"codiceFiscale": "COL000A"
},
"outputs": {
"idDomanda": "COL00B1",
"idRicevuta": "COL00B2",
"codiceContratto": "COL00B3",
"titoloContratto": "COL00B4"
}
},
"contrattiWS": {
"apiUrl": "http://localhost:8000/contratti/cerca",
"apiUsername": "username",
"apiPassword": "password",
"apiKey": "apitoken"
}
}
@@ -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,40 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js';
import path from "path";
const ASSETS_DIR = 'assets';
// https://vite.dev/config/
export default defineConfig({
plugins: [
react(),
cssInjectedByJsPlugin() // forza l'iniezione del CSS nel JS
],
base: './',
build: {
outDir: 'dist',
cssCodeSplit: false, // evita file CSS separati
rollupOptions: {
output: {
codeSplitting: false, // forza tutto in un singolo bundle
manualChunks: undefined, // disabilita la creazione di chunk multipli
entryFileNames: `${ASSETS_DIR}/recupero-proposta-cct-da-contratti.js`,
assetFileNames: `${ASSETS_DIR}/[name].[ext]`,
},
},
sourcemap: true,
},
resolve: {
alias: {
"@common": path.resolve(__dirname, "../common"),
}
},
server: {
fs: {
allow: [
".." // permette import fuori dalla cartella del progetto
]
}
}
})
+363 -382
View File
File diff suppressed because it is too large Load Diff
+12
View File
@@ -1,3 +1,14 @@
@import url('https://fonts.googleapis.com/css2?family=Titillium+Web:wght@300;400;600;700&display=swap');
@import url('https://cdn.jsdelivr.net/npm/bootstrap-italia@2.0.0/dist/css/bootstrap-italia.min.css');
:root {
--font-family-sans-serif: "Titillium Web", "Segoe UI", Arial, sans-serif;
}
body {
font-family: "Titillium Web", "Segoe UI", Arial, sans-serif;
}
/*
@import url('https://console-unipr.elixforms.it/elixFormsCustom/css/elixForms.css'); @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_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/bootstrap_overrides.css');
@@ -14,6 +25,7 @@
@import url('https://console-unipr.elixforms.it/rwe2/css/plugins/modaldisplay.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/css/themes/blu-italia.css');
@import url('https://console-unipr.elixforms.it/rwe2/isipcss/css/grid_fluid.css'); @import url('https://console-unipr.elixforms.it/rwe2/isipcss/css/grid_fluid.css');
*/
/* /*
.counter { .counter {
+7 -14
View File
@@ -1,26 +1,19 @@
import { useState } from 'react'
// import reactLogo from './assets/react.svg'
// import viteLogo from './assets/vite.svg'
import heroImg from './assets/hero.png' import heroImg from './assets/hero.png'
import './App.css' import './App.css'
import SceltaCarrieraComponent from './SceltaCarrieraComponent'; import SceltaCarrieraComponent from './SceltaCarrieraComponent';
function App() { function App() {
const [count, setCount] = useState(0)
return ( return (
<SceltaCarrieraComponent <SceltaCarrieraComponent
description="" moduleName=""
isDarkTheme={false} alertInfoMessage=""
environmentMessage=""
hasTeamsContext={false}
userDisplayName="John Doe" userDisplayName="John Doe"
moduleTitle="Modulo A/13 - Richiesta di Certificato" headerTitle="Modulo A/13 - Richiesta di Certificato"
pageTitle="Scelta Carriera" cardTitle="Scelta Carriera"
pageDescription="Benvenuto nella piattaforma di scelta carriera! Esplora le tue opzioni e trova la strada giusta per te." cardDescription="Benvenuto nella piattaforma di scelta carriera! Esplora le tue opzioni e trova la strada giusta per te."
heroImageSrc={heroImg} headerHeroImageSrc={heroImg}
/> />
); )
} }
export default App export default App
@@ -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>;
} }
} }
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"files": [], //"files": [],
"references": [ "references": [
{ {
"path": "./common" "path": "./common"