Author SHA1 Message Date
administrator 8412a0ab41 update skills and code based on official documentation 2026-07-10 09:36:01 +02:00
administrator 1daa5c7409 fix form state for dropdown controls
minor cleanups
2026-07-06 13:32:37 +02:00
administrator 158892bd3e refactor: move template rendering to own properties and functions 2026-07-06 11:41:27 +02:00
administrator 4af653e08a refactor: remove unneeded concrete common class
custom form fields creation logic as overridable function
2026-07-06 11:37:04 +02:00
administrator e50099a0c7 start of ai-based development 2026-07-06 10:44:32 +02:00
17 changed files with 1471 additions and 294 deletions
+4
View File
@@ -0,0 +1,4 @@
# Regole e Convenzioni del Progetto elixForms Custom Pages
## Gestione ID dei form element
Gli ID dei campi del form (es. nel Dropdown, Checkbox, TextField) che vengono creati con i vari metodi `create...` in `ElixFormsComponentAbstract.tsx` non devono mai essere cambiati o manipolati (es. aggiungendo suffissi come `_dropdown`), poiché vengono ricevuti e inviati da una pagina iniziale che si basa strettamente sul loro nome e ID esatto. In caso di conflitti di ID (es. dovuti a input nascosti), il suffisso va applicato agli altri elementi (come l'input hidden) e MAI all'elemento principale visibile.
+168
View File
@@ -0,0 +1,168 @@
---
name: elixforms-common-library
description: >
Documentazione della libreria condivisa common/ del progetto elixForms.
Descrive i componenti ElixForms, il pattern di form field factory,
le interfacce TypeScript, i tipi, e l'helper per i query parameter.
Attiva questa skill quando lavori sui componenti condivisi, crei nuovi tipi di campo,
modifichi la gestione dello stato del form, o integri con la piattaforma elixForms.
---
# Libreria Common elixForms
## 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.
## Architettura dei Componenti
```
ElixFormsComponentAbstract (abstract class, extends React.Component)
└── ElixFormsComponent (classe concreta, exported as ElixFormsReact)
└── Usata nelle pagine come `new ElixForms.ElixFormsReact(props)`
```
### Gerarchia delle classi
1. **`ElixFormsComponentAbstract`** (`ElixFormsComponentAbstract.tsx`)
- Classe astratta che estende `React.Component<IElixFormsComponentProperties, IElixFormsComponentFormState>`
- Contiene tutta la logica di creazione dei campi form (factory methods)
- Gestisce lo state del form (`formData: { [key: string]: any }`)
- Implementa `render()` che genera `<form>` con action POST verso elixForms
2. **`ElixFormsComponent`** (`ElixFormsComponent.tsx`)
- Classe concreta che estende `ElixFormsComponentAbstract`
- Esportata come `ElixFormsReact` per uso nelle pagine
- Attualmente vuota (la logica è nell'abstract)
## Interfacce TypeScript
### `IElixFormsComponentProperties` (Props)
```typescript
interface IElixFormsComponentProperties {
description: string;
isDarkTheme: boolean;
environmentMessage: string;
hasTeamsContext: boolean;
userDisplayName: string;
additionalFieldsJson?: string; // JSON schema per campi dinamici
customFormFieldsConfiguration?: ( // Callback con factory per campi custom
formFieldFactory: IElixFormsComponentCustomFormFieldFactory
) => JSX.Element;
}
```
### `IElixFormsComponentFormState` (State)
```typescript
interface IElixFormsComponentFormState {
formData: { [key: string]: any };
}
```
### `IElixFormsComponentCustomFormFieldFactory` (Form Field Factory)
```typescript
type IElixFormsComponentCustomFormFieldFactory = {
createBooleanInput(name: string, label: string, required?: boolean): JSX.Element;
createCheckboxInput(name: string, label: string, options: ElixFormsCheckboxOption[]): JSX.Element;
createDropdownInput(name: string, label: string, options: ElixFormsDropdownOption[], required?: boolean): JSX.Element;
createHiddenInput(name: string): JSX.Element;
createNumberInput(name: string, label: string, required?: boolean): JSX.Element;
createRadioInput(name: string, label: string, options: ElixFormsRadioOption[], required?: boolean): JSX.Element;
createTextInput(name: string, label: string, required?: boolean): JSX.Element;
createTextAreaInput(name: string, label: string, required?: boolean): JSX.Element;
};
```
### Tipi per le Opzioni (`ElixFormsTypes.tsx`)
```typescript
type ElixFormsCheckboxOption = { value: number; label: string; required?: boolean };
type ElixFormsDropdownOption = { value: number; label: string };
type ElixFormsRadioOption = { value: number; label: string };
```
## Form Field Factory Pattern
Le pagine usano il pattern **Custom Form Field Factory** per dichiarare i campi del form:
```jsx
// In App.jsx della pagina
var myElixFormsReact = new ElixForms.ElixFormsReact({
// ...props...
customFormFieldsConfiguration: (formFieldFactory) => (
<>
{formFieldFactory.createDropdownInput("COL0015", "Goals", [...options], true)}
{formFieldFactory.createTextInput("COL0002", "Campo STRING", true)}
{formFieldFactory.createTextAreaInput("COL0003", "Campo TEXTAREA", true)}
{formFieldFactory.createBooleanInput("COL0004", "Campo BOOLEAN", true)}
{formFieldFactory.createRadioInput("COL0005", "Campo RADIO", [...options], true)}
{formFieldFactory.createCheckboxInput("COL0006", "Campo CHECKBOX", [...options])}
</>
)
});
```
### Tipi di Campo Supportati
| Metodo Factory | Componente FluentUI | HTML Output |
|---|---|---|
| `createTextInput` | `TextField` | Text input |
| `createTextAreaInput` | `TextField` (multiline) | Textarea |
| `createNumberInput` | `TextField` (type=number) | Number input |
| `createBooleanInput` | `ChoiceGroup` (Sì/No) | Radio buttons |
| `createRadioInput` | `ChoiceGroup` | Radio buttons |
| `createCheckboxInput` | `Checkbox` + hidden input | Checkboxes |
| `createDropdownInput` | `Dropdown` | Select dropdown |
| `createHiddenInput` | Native `<input type="text">` | Hidden field |
## ElixFormsElement (Wrapper Layout)
`ElixFormsElement` è una classe che incapsula label + input in un layout a due colonne compatibile con il design system elixForms:
```tsx
class ElixFormsElement {
constructor(labelElement: ReactElement, inputElement: ReactElement, separator?: string);
render(): JSX.Element;
}
```
Genera markup con classi CSS specifiche di elixForms:
- `.iuFieldContainer` → container del campo
- `.attrDisplay_left`, `.attrDisplay_label` → colonna label
- `.attrDisplay_right`, `.attrDisplay_input` → colonna input
## QueryParamHelper
Utility statica per leggere parametri dalla URL corrente:
```typescript
class QueryParamHelper {
static getCheckedFromQuery(paramName: string): number[]; // Per checkbox (valori separati da virgola)
static getOptionFromQuery(paramName: string): number | undefined; // Per radio/dropdown
static getBooleanFromQuery(paramName: string): boolean | undefined;// Per boolean
static getDecodedTextFromQuery(paramName: string): string | undefined; // Per testo (URI decoded)
}
```
Usato per pre-popolare i campi form con i valori passati via query string dalla piattaforma elixForms.
## Campi Obbligatori elixForms
Il componente abstract gestisce automaticamente questi parametri come hidden inputs:
- `RWE2_MODULE_ID`
- `RWE2_REQUEST_ID`
- `custom-workflow-back-url`
- `custom-workflow-generic-id`
- `custom-workflow-current-tabrel-genid`
- `custom-workflow-source-field`
- `crc`
- `MODULE_TESTMODE_KEY`
- `ELANG`
## Note Importanti
- Lo **state management** attuale usa `React.Component` class-based con `this.state` e `this.setState`
- I checkbox gestiscono un `Map<string, number[]>` interno per tracciare i valori selezionati
- Il form fa POST a `https://procedure.unipr.it/rwe2/ComeBackToElixAndSave`
- L'encoding charset è `ISO-8859-1` per compatibilità con il backend
- La libreria usa **SCSS modules** per gli stili (`ElixFormsComponent.module.scss`)
- Il package `common` è di tipo `commonjs` (diverso dalle pagine che sono `module`)
+229
View File
@@ -0,0 +1,229 @@
---
name: elixforms-create-new-page
description: >
Guida step-by-step per creare una nuova pagina custom nel progetto elixForms.
Copre la struttura dei file, la configurazione Vite per single-bundle,
il setup TypeScript, l'importazione dei componenti common, e le convenzioni.
Attiva questa skill quando devi creare una nuova pagina, duplicare una pagina esistente,
o scaffoldare un nuovo micro-progetto nel monorepo.
---
# Creare una Nuova Pagina elixForms Custom
## Prerequisiti
- Node.js installato
- Dipendenze root installate: `cd react && npm install`
## Step 1: Scaffolding con Vite
```powershell
cd react
npm create vite@latest <nome-pagina> -- --template react
cd <nome-pagina>
npm install vite-plugin-css-injected-by-js
npm install -D sass-embedded
```
## Step 2: Configurare package.json
```json
{
"name": "@elixforms/<nome-pagina>",
"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": {
"@vitejs/plugin-react": "^6.0.1",
"sass-embedded": "^1.99.0",
"vite": "^8.0.10"
}
}
```
> **Nota**: React, ReactDOM e FluentUI NON vanno nel package.json della pagina.
> Sono nel `react/package.json` root e vengono risolti via hoisting di npm.
## Step 3: Configurare vite.config.js
```js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js';
import path from "path";
export default defineConfig({
plugins: [
react(),
cssInjectedByJsPlugin()
],
build: {
outDir: 'dist',
cssCodeSplit: false,
rollupOptions: {
input: 'src/main.jsx', // o main.tsx se TypeScript
output: {
codeSplitting: false,
manualChunks: undefined,
entryFileNames: '<nome-pagina>.js',
assetFileNames: '[name].[ext]'
},
},
sourcemap: true,
},
resolve: {
alias: {
"@common": path.resolve(__dirname, "../common"),
}
},
server: {
fs: {
allow: [".."]
}
}
})
```
## Step 4: Configurare tsconfig.json
```json
{
"extends": "../tsconfig.base.json",
"references": [
{ "path": "../common" }
],
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"allowJs": true,
"checkJs": true
},
"include": ["src/**/*"]
}
```
## Step 5: Creare il file di dichiarazioni globali
`src/global.d.ts`:
```typescript
declare module "*.css";
declare module "*.svg";
declare module "*.png";
```
## Step 6: Creare l'entry point
`src/main.jsx` (o `main.tsx`):
```jsx
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>,
)
```
## Step 7: Creare il componente App
`src/App.jsx` (o `App.tsx`):
```jsx
import './App.css'
import * as ElixForms from '@common/src/ElixFormsComponent';
function App() {
var myElixFormsReact = new ElixForms.ElixFormsReact({
description: "Descrizione pagina",
isDarkTheme: false,
environmentMessage: "",
hasTeamsContext: false,
userDisplayName: "",
customFormFieldsConfiguration: (formFieldFactory) => (
<>
{/* Aggiungi i tuoi campi qui usando formFieldFactory */}
</>
)
});
var elixFormsReactRender = myElixFormsReact.render();
return <>
<div className="container_12" id="pageBody">
<div className="grid_12">
<div id="userConsole" className="fe-behaviour">
{/* Header, contenuto e footer della pagina */}
<div className="grid_12 console_main">
<div className="inside">
<div className="receipt">
<div className="container">
<h1>Titolo Pagina</h1>
{elixFormsReactRender}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</>;
}
export default App
```
## Step 8: Configurare i CSS
`src/App.css` — Importa i CSS remoti del design system 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/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/style-bs.css');
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/style.css');
/* ... altri CSS necessari */
```
## Step 9: Configurare VS Code (opzionale)
Aggiungere configurazione di launch e task in `.vscode/launch.json` e `.vscode/tasks.json` seguendo il pattern esistente per `scelta-carriera`.
## Step 10: Build e Deploy
```powershell
npm run build
```
Output in `dist/`:
- `<nome-pagina>.js` — Singolo bundle JS pronto per il deploy
- Eventuali asset (immagini) referenziati
## Checklist Nuova Pagina
- [ ] Cartella creata in `react/<nome-pagina>/`
- [ ] `package.json` con `"name": "@elixforms/<nome-pagina>"`
- [ ] `vite.config.js` con `entryFileNames: '<nome-pagina>.js'`
- [ ] `tsconfig.json` estende `../tsconfig.base.json`
- [ ] Alias `@common` configurato in vite.config.js
- [ ] CSS remoti importati in App.css
- [ ] Entry point `main.jsx` con mount su `#root`
- [ ] `global.d.ts` con dichiarazioni per moduli CSS/SVG/PNG
- [ ] `npm install` eseguito nella cartella della pagina
- [ ] `npm run dev` funziona senza errori
- [ ] `npm run build` produce singolo bundle in `dist/`
+171
View File
@@ -0,0 +1,171 @@
---
name: elixforms-css-design-system
description: >
Strategia CSS e design system del progetto elixForms.
Documenta come vengono importati i CSS remoti della piattaforma,
il layout a griglia elixForms, le classi CSS specifiche della piattaforma,
gli stili SCSS modules nella libreria common, e la struttura visiva delle pagine.
Attiva questa skill quando lavori sugli stili, sul layout delle pagine,
o devi capire come funzionano le classi CSS del design system elixForms.
---
# CSS e Design System elixForms
## Strategia CSS
Il progetto utilizza **tre livelli di stili**:
1. **CSS remoti** — Design system dell'Università di Parma, caricati via `@import url(...)` da `console-unipr.elixforms.it`
2. **SCSS Modules** — Stili specifici dei componenti in `common/` (es. `ElixFormsComponent.module.scss`)
3. **CSS locali** — Stili specifici della pagina (es. `App.css`, `index.css`)
## CSS Remoti (Design System UniPR/elixForms)
Importati in `App.css` di ogni pagina:
```css
/* CSS principali del design system */
@import url('https://console-unipr.elixforms.it/elixFormsCustom/css/elixForms.css');
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/bootstrap_adaptation.css');
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/bootstrap_overrides.css');
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/console_user.css');
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/designitalia_adaptation.css');
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/fonts.css');
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/isipcss_overrides.css');
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/iulib.css');
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/print.css');
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/responsive.css');
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/style-bs.css');
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/style.css');
@import url('https://console-unipr.elixforms.it/rwe2/css/design-bs/util.css');
@import url('https://console-unipr.elixforms.it/rwe2/css/plugins/modaldisplay.css');
@import url('https://console-unipr.elixforms.it/rwe2/css/themes/blu-italia.css');
@import url('https://console-unipr.elixforms.it/rwe2/isipcss/css/grid_fluid.css');
```
> **IMPORTANTE**: Questi CSS sono caricati a runtime dal CDN e **non vengono bundlati** nel JS.
> La pagina deve avere connessione internet per visualizzarsi correttamente.
## Layout della Pagina (Struttura HTML)
Le pagine seguono un layout standard elixForms con classi CSS specifiche:
```html
<div class="container_12" id="pageBody">
<div class="grid_12">
<div id="userConsole" class="fe-behaviour">
<!-- HEADER -->
<header>
<div class="neutral">
<div class="grid_12">
<div class="console_header">
<div class="leftDiv">
<h1>elixForms</h1>
<div class="logo"><!-- Logo UniPR --></div>
</div>
<div class="rightDiv">...</div>
</div>
</div>
</div>
</header>
<!-- CONTENUTO PRINCIPALE -->
<div class="grid_12 console_main">
<div class="inside">
<div class="fe-module-header-container">
<h2>Titolo Modulo</h2>
</div>
<div class="receipt">
<div class="container">
<!-- Contenuto custom della pagina -->
</div>
</div>
</div>
</div>
<!-- FOOTER -->
<footer class="it-footer">
<div class="it-footer-main">
<div class="container">
<div class="subfooter-top">powered by elixForms</div>
<div class="subfooter-bottom">versione X.XX.X</div>
</div>
</div>
</footer>
</div>
</div>
</div>
```
## Classi CSS del Design System
### Layout a Griglia
| Classe | Scopo |
|---|---|
| `container_12` | Container principale a 12 colonne |
| `grid_12` | Occupa tutte le 12 colonne |
| `clear` | Clearfix |
### Struttura Pagina
| Classe | Scopo |
|---|---|
| `fe-behaviour` | Container principale della console utente |
| `console_header` | Header della console |
| `console_main` | Area contenuto principale |
| `fe-module-header-container` | Header del modulo/form |
| `receipt` | Container del contenuto del form |
### Layout Campi Form (ElixFormsElement)
| Classe | Scopo |
|---|---|
| `iuFieldContainer` | Container di un campo form (label + input) |
| `attrDisplay_left` | Colonna sinistra (label) |
| `attrDisplay_right` | Colonna destra (input) |
| `attrDisplay_label` | Marcatore per la label |
| `attrDisplay_input` | Marcatore per l'input |
| `iuLabelContainer` | Container interno della label |
| `iuInputContainer` | Container interno dell'input |
| `iuTypeString` | Tipo di input stringa |
| `iuClearContainer` | Clearfix interno |
### Classi Input FluentUI Custom
| Classe | Componente |
|---|---|
| `isiportalPartialAdminFormFieldSingleLineText` | TextField singola riga |
| `isiportalPartialAdminFormFieldMultiLineText` | TextField multiriga |
| `isiportalPartialAdminFormFieldRadio` | ChoiceGroup radio |
| `isiportalPartialAdminFormFieldSelect` | Dropdown select |
| `isiportalPartialAdminCheckboxFieldItem` | Checkbox singolo |
## SCSS Modules (common/)
`ElixFormsComponent.module.scss`:
```scss
@import '~@fluentui/react/dist/sass/References.scss';
.elixFormsComponent {
overflow: hidden;
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.
## Logo e Risorse Esterne
- **Logo UniPR**: `https://console-unipr.elixforms.it/elixFormsCustom/images/logo.png`
- Le immagini locali vanno in `src/assets/` e importate come moduli ES
- Le immagini vengono copiate nella `dist/` durante la build
## Note Importanti
1. Il CSS di `index.css` nella pagina `scelta-carriera` è **attualmente tutto commentato** — si usano solo i CSS remoti
2. Il CSS injection plugin (`vite-plugin-css-injected-by-js`) gestisce solo i CSS locali e SCSS, non gli `@import url(...)` che restano come riferimenti esterni
3. Per funzionare correttamente, la pagina deployata deve poter raggiungere `console-unipr.elixforms.it`
@@ -0,0 +1,32 @@
---
name: elixforms-custom-workflow-logic
description: Logica di funzionamento delle custom page richiamate dal Custom Workflow v2 di elixForms. Descrive il passaggio dei parametri, i campi obbligatori, e le regole di submit.
---
# Logica di Funzionamento Custom Workflow v2 in elixForms
Le **custom page** all'interno del progetto elixForms servono a gestire flussi esterni personalizzati (chiamati *Custom Workflow 2*).
Vengono lanciate tramite un pulsante presente su un modulo elixForms; la piattaforma reindirizza l'utente alla URL della custom page (sviluppata in questo progetto), passandole in query string diversi parametri di contesto. Al termine delle operazioni, l'utente viene reindirizzato nuovamente al modulo elixForms.
## Parametri e Query String
### Parametri Obbligatori
elixForms invia sempre alla custom page una serie di parametri obbligatori (es. `RWE2_MODULE_ID`, `RWE2_REQUEST_ID`, `custom-workflow-back-url`, `crc`, ecc.).
Questi parametri sono definiti nella classe `ElixFormsComponentAbstract` (metodo/variabile `mandatoryFormFieldNames`).
- **Regola:** Se alla custom page manca uno solo di questi parametri, deve presentare un **errore di autenticazione/accesso** (es. "Parametri obbligatori mancanti") in quanto il flusso risulta non valido (ad es. tentativo di accesso diretto bypassando elixForms). Non deve permettere l'utilizzo del form.
### Parametri Custom (Dati Modulo)
Oltre ai parametri obbligatori, elixForms può passare dati già compilati nel modulo (es. `email`, `COL0001`, ecc.).
- Questi dati vengono ricevuti tramite query string e possono essere utilizzati o visualizzati all'interno della custom page.
## Sottomissione del Form e Rientro in elixForms
Per tornare a elixForms, la custom page deve eseguire un submit `POST` contenente tutti i parametri necessari.
- **Form Action:** L'endpoint di destinazione (es. `https://[server]/rwe2/ComeBackToElixAndSave`).
- **Input Nascosti (Mandatory):** Il form *deve* contenere (come `input type="hidden"`) tutti i parametri obbligatori ricevuti originariamente dalla query string.
- **Input Custom / Dati Rientro:** Il form deve contenere tutti i campi aggiuntivi o le variabili calcolate che devono essere rimandate ad elixForms (es. un campo anagrafico). L'attributo `name` di questi input deve corrispondere esattamente all'identificativo atteso su elixForms (es. `name="COL0001"`).
## Gestione degli ID (Design System e Convenzioni)
Come da regole generali (vedi `AGENTS.md`):
- Gli `ID` dei componenti visibili (TextField, Checkbox, Dropdown) **non devono mai subire alterazioni** (non vanno aggiunti suffissi).
- Qualora ci sia necessità di inserire un input `hidden` per mantenere un valore che abbia lo stesso nome di un campo visibile (per rispettare la convenzione del `name`), l'attributo `ID` dell'input nascosto **deve** avere un suffisso (es. `id="MioCampo_hidden"`), mentre il componente visibile principale mantiene l'ID originale senza suffissi. Questo evita conflitti HTML a livello di ID mantenendo corretti i valori inviati (`name`).
+130
View File
@@ -0,0 +1,130 @@
---
name: elixforms-dev-workflow
description: >
Workflow di sviluppo, debug e deploy del progetto elixForms Custom Pages.
Copre i comandi per avviare il dev server, buildare, debuggare con VS Code,
e le configurazioni di launch/tasks.
Attiva questa skill quando devi eseguire comandi di sviluppo,
configurare il debug, o gestire il ciclo dev/build/deploy.
---
# Workflow di Sviluppo
## Comandi Principali
Tutti i comandi vanno eseguiti **dalla cartella della pagina** (es. `react/scelta-carriera/`):
```powershell
# Installare le dipendenze (prima volta o dopo modifiche a package.json)
cd react
npm install
cd scelta-carriera
npm install
# Avviare il dev server con HMR
npm run dev
# Build per produzione (singolo bundle JS)
npm run build
# Preview della build di produzione
npm run preview
# Lint del codice
npm run lint
```
## Configurazione VS Code
### Launch Configurations (`.vscode/launch.json`)
#### 1. "Scelta Carriere (BROWSER)" — Dev + Chrome Debug
- Avvia `npm run dev` nella cartella della pagina
- Apre automaticamente Chrome quando il server è pronto
- Pattern di rilevamento: `Local:\s+http://localhost:([0-9]+)/`
- WebRoot per sourcemaps: `${workspaceFolder}/react/scelta-carriera/src`
#### 2. "Scelta Carriere (DEBUG ONLY)" — Solo Dev Server
- Avvia `npm run dev` senza aprire il browser
- Utile per debug da terminale o browser già aperto
### Variabili d'Ambiente
- `NO_COLOR: "1"` — Disabilita colori ANSI nell'output (migliora leggibilità nel debug console)
### Skip Files nel Debug
```json
"skipFiles": [
"<node_internals>/**",
"**/node_modules/**",
"**/@vite/client/**"
]
```
### Tasks (`.vscode/tasks.json`)
#### "Start scelta-carriera (Vite App)"
- Task background che avvia `npm run dev`
- Problem matcher configurato per output Vite:
- `beginsPattern`: "VITE"
- `endsPattern`: "ready in"
## Aggiungere una Nuova Pagina al Debug
Per aggiungere una nuova configurazione di debug per una pagina:
```json
// In .vscode/launch.json, aggiungere in "configurations":
{
"name": "<Nome Pagina> (BROWSER)",
"cwd": "${workspaceFolder}/react/<nome-pagina>",
"env": { "NO_COLOR": "1" },
"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/<nome-pagina>/src"
}
}
```
## Flusso di Deploy
1. `npm run build` nella cartella della pagina
2. Output: `dist/<nome-pagina>.js` (+ sourcemap e asset)
3. Caricare il file JS sul server remoto `console-unipr.elixforms.it`
4. La pagina viene servita dalla piattaforma elixForms con i parametri query appropriati
## Testing Locale con Query Parameters
Per testare localmente con i parametri elixForms, aggiungere query params all'URL del dev server:
```
http://localhost:5173/?RWE2_MODULE_ID=123&RWE2_REQUEST_ID=456&COL0002=test&COL0015=3
```
I valori verranno letti da `QueryParamHelper` e pre-popoleranno i campi del form.
## Struttura delle Dipendenze
```
react/ ← npm install qui per dipendenze condivise
├── node_modules/ ← react, react-dom, @fluentui/react
├── package.json
└── scelta-carriera/ ← npm install qui per devDependencies
├── node_modules/ ← vite, eslint, plugin, sass-embedded
└── package.json
```
> **Nota**: Le dipendenze runtime (React, FluentUI) sono nel `package.json` root.
> Le dipendenze di sviluppo (Vite, ESLint) sono nel `package.json` della pagina.
> npm risolve le dipendenze risalendo la gerarchia delle cartelle (hoisting).
@@ -0,0 +1,150 @@
---
name: elixforms-project-architecture
description: >
Architettura e struttura del progetto elixForms Custom Pages.
Descrive il monorepo, la libreria common condivisa, il pattern delle pagine autonome
e tutte le convenzioni di naming, configurazione e build.
Attiva questa skill quando lavori su qualsiasi parte del progetto,
crei nuove pagine, modifichi l'architettura o devi capire come è organizzato il codice.
---
# Architettura del Progetto elixForms Custom Pages
## 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.
## Struttura del Workspace
```
elixforms-custom-pages/ ← Root del workspace (VS Code)
├── .vscode/ ← Configurazioni VS Code (launch, tasks)
├── docs/ ← Documentazione (PDF manuali)
└── react/ ← Root del monorepo React
├── package.json ← Dipendenze condivise (React, ReactDOM, FluentUI)
├── tsconfig.base.json ← Configurazione TypeScript base condivisa
├── .gitignore
├── common/ ← Libreria condivisa di componenti ElixForms
│ ├── package.json
│ ├── tsconfig.json ← Estende tsconfig.base, composite: true
│ └── src/
│ ├── ElixFormsComponent.tsx
│ ├── ElixFormsComponentAbstract.tsx
│ ├── ElixFormsElement.tsx
│ ├── ElixFormsTypes.tsx
│ ├── ElixFormsComponent.module.scss
│ ├── IElixFormsComponentProperties.ts
│ ├── IElixFormsComponentFormState.tsx
│ ├── IElixFormsComponentCustomFormFieldFactory.ts
│ └── QueryParamHelper.tsx
└── scelta-carriera/ ← Esempio di pagina custom (micro-progetto)
├── package.json
├── tsconfig.json ← Estende tsconfig.base
├── vite.config.js
├── eslint.config.js
├── index.html
└── src/
├── main.jsx ← Entry point React
├── App.jsx ← Componente principale della pagina
├── App.css ← CSS (importa CSS remoti di elixForms)
├── index.css ← CSS globale (attualmente commentato)
├── global.d.ts ← Dichiarazioni di tipo per moduli
└── assets/ ← Immagini e risorse statiche
```
## Pattern Architetturale: Micro-Progetto per Pagina
Ogni pagina (es. `scelta-carriera/`) è un progetto Vite completamente autonomo che:
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)
3. **Condivide le dipendenze runtime** (React, ReactDOM, FluentUI) dal `package.json` root di `react/`
4. **Produce un singolo file JS** tramite la configurazione Rollup in `vite.config.js`
5. **Estende il tsconfig base** per avere configurazione TypeScript coerente
## Dipendenze Condivise (react/package.json)
Le dipendenze runtime sono hoisted al livello `react/`:
- `react` ^19.2.7
- `react-dom` ^19.2.5
- `@fluentui/react` ^8.125.6
## Configurazione TypeScript
### Base (tsconfig.base.json)
- `module`: ESNext
- `target`: ES2022
- `moduleResolution`: bundler
- `jsx`: react-jsx
- `strict`: true
- Path alias: `@common/*``./common/*`
### Common (common/tsconfig.json)
- `composite`: true (per project references)
- `declaration`: true, `declarationMap`: true
### Pagine (scelta-carriera/tsconfig.json)
- `extends`: `../tsconfig.base.json`
- `references`: `../common`
- `allowJs`: true, `checkJs`: true (supporto misto JSX/TSX)
## Configurazione Build (Vite)
Ogni pagina usa questa configurazione per generare un **singolo bundle JS**:
```js
// Plugin chiave
react() // Supporto React JSX
cssInjectedByJsPlugin() // CSS iniettato nel JS (no file .css separati)
// Build options
cssCodeSplit: false
rollupOptions.input: 'src/main.jsx'
rollupOptions.output:
codeSplitting: false
manualChunks: undefined
entryFileNames: '<nome-pagina>.js' // Nome descrittivo (es. 'scelta-carriera.js')
assetFileNames: '[name].[ext]'
sourcemap: true
```
## Resolve Alias
```js
resolve.alias: {
"@common": path.resolve(__dirname, "../common")
}
server.fs.allow: [".."] // Permette import da common/
```
## CSS Strategy
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à
- I componenti FluentUI usano stili inline/theme nativi
- SCSS modules sono usati in `common/` per stili dei componenti condivisi
- `sass-embedded` è installato come devDependency per supporto SCSS
## Integrazione con elixForms
La pagina custom interagisce con la piattaforma elixForms tramite:
1. **Query parameters**: Parametri obbligatori passati nell'URL (es. `RWE2_MODULE_ID`, `RWE2_REQUEST_ID`, `crc`, etc.)
2. **Form POST**: Il form fa submit a `https://procedure.unipr.it/rwe2/ComeBackToElixAndSave`
3. **Hidden inputs**: I parametri query vengono inseriti come campi hidden nel form
4. **Encoding**: `acceptCharset="ISO-8859-1"` per compatibilità con il backend
## Convenzioni di Naming
- **Cartelle pagina**: kebab-case (es. `scelta-carriera`)
- **Package name**: `@elixforms/<nome-pagina>` (es. `@elixforms/scelta-carriera`)
- **Bundle output**: `<nome-pagina>.js` (es. `scelta-carriera.js`)
- **Componenti React**: PascalCase
- **File sorgente**: I file correnti sono `.jsx` ma il progetto ha pieno supporto TypeScript (`.tsx`)
- **Tipo moduli**: `"type": "module"` nelle pagine
## Ambiente di Sviluppo
- **Dev server**: `npm run dev` (Vite HMR)
- **Build**: `npm run build` → output in `dist/`
- **Debug VS Code**: Configurazioni in `.vscode/launch.json` per avviare Vite + Chrome
- **Lint**: ESLint con plugin react-hooks e react-refresh
@@ -0,0 +1,174 @@
---
name: elixforms-typescript-conventions
description: >
Convenzioni TypeScript e React per il progetto elixForms.
Copre la configurazione TypeScript, i pattern di codice, la gestione dei tipi,
l'uso di JSDoc per type annotations nei file .jsx, e le best practices.
Attiva questa skill quando scrivi codice TypeScript o React,
crei nuovi componenti, definisci interfacce, o gestisci i tipi nel progetto.
---
# Convenzioni TypeScript e React
## Configurazione TypeScript del Progetto
### Opzioni Compiler Principali
- **target**: ES2022
- **module**: ESNext
- **moduleResolution**: bundler
- **jsx**: react-jsx (trasformazione automatica JSX senza import React)
- **strict**: true
- **isolatedModules**: true
- **verbatimModuleSyntax**: true (usa `import type` per import solo di tipi)
### File Misti JSX/TSX
Il progetto supporta **sia file `.jsx` che `.tsx`** grazie a:
- `allowJs: true` e `checkJs: true` nel tsconfig delle pagine
- Type annotations via JSDoc nei file `.jsx`
#### Pattern JSDoc per Tipi nei File .jsx
```jsx
/** @type {import('@common/src/ElixFormsComponent').ElixFormsReact} */
var myElixFormsReact = new ElixForms.ElixFormsReact({...});
// Per i parametri di callback
customFormFieldsConfiguration: (
/** @type {import('../../common/src/IElixFormsComponentCustomFormFieldFactory').IElixFormsComponentCustomFormFieldFactory} */
formFieldFactory) => (...)
```
## Pattern React Correnti
### Class Components (common/)
La libreria `common/` usa **React Class Components** (non hooks):
```tsx
abstract class ElixFormsComponentAbstract
extends Component<IElixFormsComponentProperties, IElixFormsComponentFormState> {
constructor(props: IElixFormsComponentProperties) {
super(props);
this.state = { formData: {} };
}
// State gestito con this.setState()
this.setState({ formData: { ...this.state.formData, [name]: value } });
}
```
### Functional Components (pagine)
Le pagine usano **Functional Components** con hooks:
```jsx
import { useState } from 'react'
function App() {
const [count, setCount] = useState(0)
// ...
}
```
### Istanziazione del Componente ElixForms
Il componente ElixForms viene istanziato come classe (non come JSX):
```jsx
// ✅ Corretto - istanziazione diretta
var myElixFormsReact = new ElixForms.ElixFormsReact({...props...});
var elixFormsReactRender = myElixFormsReact.render();
// Poi nel JSX
return <>{elixFormsReactRender}</>;
// ❌ NON usare come componente JSX
return <ElixForms.ElixFormsReact {...props} />;
```
## Import Conventions
### Import Type-Only (verbatimModuleSyntax)
Con `verbatimModuleSyntax: true`, usare sempre `import type` per i tipi:
```tsx
// ✅ Corretto
import type { IElixFormsComponentProperties } from './IElixFormsComponentProperties';
import type { JSX } from 'react';
// ❌ Errato - causerà errore con verbatimModuleSyntax
import { IElixFormsComponentProperties } from './IElixFormsComponentProperties';
```
### Import di Moduli
```tsx
// Fluent UI - import tutto il namespace
import * as FluentUI from '@fluentui/react';
// React - import specifici
import { Component, type FormEvent, type JSX, useState } from 'react';
import React from 'react';
// Componenti common - import namespace
import * as ElixForms from '@common/src/ElixFormsComponent';
// CSS
import './App.css'
```
## Tipi Custom del Progetto
```typescript
// Opzioni per campi form
type ElixFormsCheckboxOption = { value: number; label: string; required?: boolean };
type ElixFormsDropdownOption = { value: number; label: string };
type ElixFormsRadioOption = { value: number; label: string };
// State del form
interface IElixFormsComponentFormState {
formData: { [key: string]: any };
}
```
## Dichiarazioni di Modulo
`global.d.ts` nella cartella `src/` di ogni pagina:
```typescript
declare module "*.css";
declare module "*.svg";
declare module "*.png";
```
## Best Practices per il Progetto
1. **Naming**:
- Interfacce: prefisso `I` (es. `IElixFormsComponentProperties`)
- Tipi: PascalCase (es. `ElixFormsCheckboxOption`)
- File interfacce: nome dell'interfaccia (es. `IElixFormsComponentProperties.ts`)
- File componenti: nome del componente (es. `ElixFormsComponent.tsx`)
2. **Estensioni file**:
- `.tsx` per componenti React con TypeScript (common/)
- `.ts` per moduli TypeScript puri (interfacce, tipi)
- `.jsx` per componenti React con JavaScript (pagine attuali)
- `.scss` per stili con moduli SCSS
3. **Export**:
- Named export per tipi e interfacce
- Default export per classi componente
- Re-export con alias: `export { ElixFormsComponent as ElixFormsReact }`
4. **State management**:
- Usare `this.setState` con spread operator per update immutabili
- I checkbox mantengono un `Map<string, number[]>` separato
5. **Fluent UI**:
- Usare Fluent UI React v8 (`@fluentui/react`)
- Componenti: `TextField`, `Checkbox`, `ChoiceGroup`, `Dropdown`, `Label`, `Stack`
- Le opzioni radio usano `IChoiceGroupOption[]`
- Le opzioni dropdown usano `IDropdownOption[]`
+136
View File
@@ -0,0 +1,136 @@
---
name: elixforms-vite-build-config
description: >
Configurazione Vite e Rollup per il progetto elixForms.
Copre la build single-bundle, i plugin utilizzati, gli alias,
il sourcemap, e la strategia CSS (injection nel JS).
Attiva questa skill quando devi modificare la build configuration,
risolvere problemi di bundling, aggiungere plugin Vite,
o ottimizzare l'output di produzione.
---
# Configurazione Vite e Build
## Stack di Build
- **Vite** v8.x — Dev server e bundler
- **@vitejs/plugin-react** v6.x — Supporto React/JSX
- **vite-plugin-css-injected-by-js** v5.x — Inietta CSS nel bundle JS
- **sass-embedded** v1.99.x — Compilazione SCSS (per common/)
- **Rollup** — Bundler interno di Vite (configurato via `rollupOptions`)
## Configurazione Vite Completa di Riferimento
```js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js';
import path from "path";
export default defineConfig({
plugins: [
react(),
cssInjectedByJsPlugin()
],
build: {
outDir: 'dist',
cssCodeSplit: false,
rollupOptions: {
input: 'src/main.jsx',
output: {
codeSplitting: false,
manualChunks: undefined,
entryFileNames: '<nome-pagina>.js',
assetFileNames: '[name].[ext]'
},
},
sourcemap: true,
},
resolve: {
alias: {
"@common": path.resolve(__dirname, "../common"),
}
},
server: {
fs: {
allow: [".."]
}
}
})
```
## Dettaglio Opzioni Critiche
### Single Bundle (Rollup)
| Opzione | Valore | Scopo |
|---|---|---|
| `codeSplitting` | `false` | Disabilita il code splitting |
| `manualChunks` | `undefined` | Impedisce separazione vendor/app |
| `entryFileNames` | `'<nome>.js'` | Nome deterministico senza hash |
| `assetFileNames` | `'[name].[ext]'` | Asset senza hash nei nomi |
### CSS Strategy
| Opzione | Valore | Scopo |
|---|---|---|
| `cssCodeSplit` | `false` | No file CSS separati |
| `cssInjectedByJsPlugin()` | plugin | CSS iniettato runtime nel DOM via JS |
### Alias di Import
| Alias | Path | Scopo |
|---|---|---|
| `@common` | `../common` | Accesso alla libreria condivisa |
### Dev Server
| Opzione | Valore | Scopo |
|---|---|---|
| `server.fs.allow` | `[".."]` | Permette import fuori dalla root del progetto |
## Comandi
```powershell
# Dev con HMR
npm run dev
# Build produzione
npm run build
# Preview build produzione
npm run preview
# Lint
npm run lint
```
## Output della Build
```
dist/
<nome-pagina>.js ← Bundle unico (React + FluentUI + CSS + codice)
<nome-pagina>.js.map ← Source map
*.png, *.svg ← Asset referenziati nel codice
```
## Risoluzione Problemi Comuni
### Errore: "The request url is outside of Vite serving allow list"
- **Causa**: Import di file dalla cartella `common/` o altre cartelle esterne
- **Fix**: Aggiungere `server.fs.allow: [".."]` nella configurazione Vite
### Errore: "Cannot find module '@common/...'"
- **Causa**: Alias non configurato o path errato
- **Fix**: Verificare `resolve.alias` e che la cartella `common/` esista
### Bundle troppo grande
- **Causa**: FluentUI include molti componenti
- **Fix possibili**:
- Tree shaking (import specifici: `import { TextField } from '@fluentui/react'`)
- Considerare `@fluentui/react-components` (v9) per bundle più piccoli
- Esternalizzare React se la pagina host lo fornisce già
### CSS non applicato in produzione
- **Causa**: `cssInjectedByJsPlugin` non installato o non configurato
- **Fix**: Verificare plugin in `vite.config.js` e `npm install vite-plugin-css-injected-by-js`
### SCSS non compilato
- **Causa**: `sass-embedded` mancante
- **Fix**: `npm install -D sass-embedded`
Binary file not shown.
-16
View File
@@ -1,16 +0,0 @@
//import * as React from 'react';
import ElixFormsComponentAbstract from './ElixFormsComponentAbstract';
export default class ElixFormsComponent extends ElixFormsComponentAbstract {
// public override createCustomFormFields(): React.ReactElement {
// // const textTest = this.CreateTextInput("COL0018", "TEXT INPUT");
// // const checkTest = this.CreateCheckboxInput("COL0090", "CHECKBOX INPUT", new Map<number, string>([ [1, "Option 1"], [2, "Option 2"], [3, "Option 3"] ]));
// // const radioTest = this.CreateRadioInput("COL0091", "RADIO INPUT", new Map<number, string>([ [1, "Option 1"], [2, "Option 2"], [3, "Option 3"] ]));
// // const boolTest = this.CreateBooleanInput("COL0092", "BOOLEAN INPUT");
// // const textareaTest = this.CreateTextAreaInput("COL0093", "TEXTAREA INPUT");
// return (<></>);
// }
}
export { ElixFormsComponent as ElixFormsReact };
+160 -56
View File
@@ -59,34 +59,38 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
"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 // Create a hidden input for each mandatory Elix parameter
this.mandatoryFormFieldNames.forEach(paramName => { this.mandatoryFormFieldNames.forEach(paramName => {
const value = queryParams.get(paramName) ?? ""; const value = queryParams.get(paramName) ?? "";
// Create the input element // Create the input element
hiddenInputs.push(this.createHiddenInput(paramName)); hiddenInputs.push(this.createHiddenInput(paramName));
}); });
return <>{hiddenInputs}</>; return <>{hiddenInputs}</>;
} }
private createAdditionalFormFields() : React.ReactElement { private createAdditionalFormFields(): React.ReactElement {
const schema = JSON.parse(this.props.additionalFieldsJson || "[]"); const schema = JSON.parse(this.props.additionalFieldsJson || "[]");
return ( return (
<> <>
{schema.map((field: { key: React.Key | null | undefined; }) => ( {schema.map((field: { key: React.Key | null | undefined; }) => (
<div key={field.key}> <div key={field.key}>
{this.renderField(field)} {this.renderField(field)}
</div> </div>
))} ))}
</> </>
); );
} }
public createCustomFormFields(callback: (formFieldFactory: IElixFormsComponentCustomFormFieldFactory) => JSX.Element = () => <></>): JSX.Element { protected createCustomFormFields(formFieldFactory: IElixFormsComponentCustomFormFieldFactory): JSX.Element {
return <></>;
}
private renderCustomFormFields(): JSX.Element {
const formFieldFactory = { const formFieldFactory = {
createBooleanInput: (name: string, label: string, required: boolean = false) => createBooleanInput: (name: string, label: string, required: boolean = false) =>
this.createBooleanInput(name, label, required), this.createBooleanInput(name, label, required),
@@ -106,7 +110,15 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
this.createTextAreaInput(name, label, required), this.createTextAreaInput(name, label, required),
}; };
return callback(formFieldFactory); return this.createCustomFormFields(formFieldFactory);
}
protected renderExtraContentPre(): JSX.Element | null {
return null;
}
protected renderExtraContentPost(): JSX.Element | null {
return null;
} }
public override render(): React.ReactElement<IElixFormsComponentProperties> { public override render(): React.ReactElement<IElixFormsComponentProperties> {
@@ -117,28 +129,107 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
hasTeamsContext, hasTeamsContext,
userDisplayName, userDisplayName,
additionalFieldsJson, additionalFieldsJson,
customFormFieldsConfiguration moduleTitle,
pageTitle,
pageDescription,
heroImageSrc
} = this.props; } = this.props;
const queryParams = new URLSearchParams(window.location.search); const queryParams = new URLSearchParams(window.location.search);
const missingMandatoryParams = this.mandatoryFormFieldNames.filter(paramName => !queryParams.has(paramName));
const hasAuthenticationError = missingMandatoryParams.length > 0;
return ( return (
<section> <div className="container_12" id="pageBody">
{/* className={`${styles.elixFormsReact} ${hasTeamsContext ? styles.teams : ''}`} */} <div className="grid_12">
<div>{environmentMessage}</div> <div id="userConsole" className="fe-behaviour">
<div>{description}</div> <header>
<form action="https://procedure.unipr.it/rwe2/ComeBackToElixAndSave" method="post" acceptCharset="ISO-8859-1"> <div className="neutral">
{this.createMandatoryFormFields(queryParams)} <div className="grid_12">
{this.createAdditionalFormFields()} <div className="console_header">
{this.createCustomFormFields(customFormFieldsConfiguration)} <div className="leftDiv">
<input type="submit" value="Submit"/> <h1>elixForms</h1>
</form> <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>
</section> </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 className="clear"></div>
<footer className="it-footer">
<div className="it-footer-main">
<div className="container">
<div className="row clearfix">
<div className="col-sm-12">
<div className="footer-content">
<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>
</footer>
</div>
<div className="clear"></div>
</div>
</div>
); );
} }
private createCheckboxInput(paramName: string, label: string, values: Array<ElixFormsCheckboxOption>) : JSX.Element { private createCheckboxInput(paramName: string, label: string, values: Array<ElixFormsCheckboxOption>): JSX.Element {
const checkedValues = QueryParamHelper.getCheckedFromQuery(paramName); const checkedValues = QueryParamHelper.getCheckedFromQuery(paramName);
if (!this.checkboxValues.get(paramName)) { if (!this.checkboxValues.get(paramName)) {
this.checkboxValues.set(paramName, checkedValues); this.checkboxValues.set(paramName, checkedValues);
@@ -159,26 +250,30 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
} else if (!checked && checkboxIndex !== -1) { } else if (!checked && checkboxIndex !== -1) {
currentCheckboxValues.splice(checkboxIndex, 1); currentCheckboxValues.splice(checkboxIndex, 1);
} }
this.state.formData[paramName] = currentCheckboxValues.join(',').trim() ?? ''; const newValue = currentCheckboxValues.join(',').trim();
//this.setState({ formData: { ...this.state.formData, [paramName]: 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]}`); console.log(`Checkbox ${id} changed to ${checked}. Current values for ${paramName}: ${this.state.formData[paramName]}`);
//); });
}} }}
/> />
); );
}); });
const currentValue = this.state.formData[paramName] !== undefined
? this.state.formData[paramName]
: (this.checkboxValues.get(paramName)?.join(',').trim() ?? '');
return new ElixFormsElement( return new ElixFormsElement(
<><FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label></>, <><FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label></>,
<><FluentUI.Stack tokens={stackTokens}> <><FluentUI.Stack tokens={stackTokens}>
{checkboxes} {checkboxes}
</FluentUI.Stack> </FluentUI.Stack>
<input type='hidden' id={paramName} name={paramName} value={this.state.formData[paramName] ?? ''} /> <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 radioValue = QueryParamHelper.getOptionFromQuery(paramName);
const radios: FluentUI.IChoiceGroupOption[] = []; const radios: FluentUI.IChoiceGroupOption[] = [];
@@ -214,7 +309,7 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
).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 boolValue = QueryParamHelper.getOptionFromQuery(paramName);
const radios: FluentUI.IChoiceGroupOption[] = []; const radios: FluentUI.IChoiceGroupOption[] = [];
@@ -250,9 +345,9 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
).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 textareaValue = QueryParamHelper.getDecodedTextFromQuery(paramName) ?? "";
return new ElixFormsElement( return new ElixFormsElement(
<FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label>, <FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label>,
<FluentUI.TextField id={paramName} name={paramName} defaultValue={textareaValue} multiline rows={4} required={required} className='isiportalPartialAdminFormFieldMultiLineText' <FluentUI.TextField id={paramName} name={paramName} defaultValue={textareaValue} multiline rows={4} required={required} className='isiportalPartialAdminFormFieldMultiLineText'
onChange={(event, value) => { onChange={(event, value) => {
@@ -264,7 +359,7 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
).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 textValue = QueryParamHelper.getDecodedTextFromQuery(paramName) ?? "";
return new ElixFormsElement( return new ElixFormsElement(
<FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label>, <FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label>,
@@ -278,16 +373,16 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
).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='text' id={paramName} name={paramName} defaultValue={textValue} /><br/></>; return <input type="hidden" id={`${paramName}_hidden`} name={paramName} defaultValue={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 textValue = QueryParamHelper.getDecodedTextFromQuery(paramName) ?? "";
return new ElixFormsElement( return new ElixFormsElement(
<FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label>, <FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label>,
<FluentUI.TextField id={paramName} name={paramName} defaultValue={textValue} type="number" required={required} className='isiportalPartialAdminFormFieldSingleLineText' <FluentUI.TextField id={paramName} name={paramName} defaultValue={textValue} type="number" required={required} className='isiportalPartialAdminFormFieldSingleLineText'
onChange={(event, value) => { onChange={(event, value) => {
this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () => this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () =>
console.log(`NumberInput ${paramName} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`) console.log(`NumberInput ${paramName} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`)
@@ -298,14 +393,17 @@ 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 selectedValue = QueryParamHelper.getOptionFromQuery(paramName); const queryValue = QueryParamHelper.getOptionFromQuery(paramName);
const currentValue = this.state.formData[paramName] !== undefined
? this.state.formData[paramName]
: (queryValue?.toString() ?? '');
const options: FluentUI.IDropdownOption[] = []; const options: FluentUI.IDropdownOption[] = [];
values.forEach(entry => { values.forEach(entry => {
const id = `${paramName}_${entry.value}`;
options.push( options.push(
{ {
key: id, key: entry.value.toString(),
text: entry.label text: entry.label
} }
); );
@@ -313,19 +411,25 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
return new ElixFormsElement( return new ElixFormsElement(
<FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label>, <FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label>,
<FluentUI.Dropdown <>
id={paramName} <FluentUI.Dropdown
options={options} id={paramName}
selectedKey={selectedValue?.toString()} options={options}
placeholder='---' selectedKey={currentValue}
required={required} placeholder='---'
className='isiportalPartialAdminFormFieldSelect' required={required}
onChange={(event, value) => { className='isiportalPartialAdminFormFieldSelect'
this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () => onChange={(event, option) => {
console.log(`Dropdown ${paramName} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`) if (option) {
); const value = option.key.toString();
}} this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () =>
/> console.log(`Dropdown ${paramName} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`)
);
}
}}
/>
<input type="hidden" name={paramName} id={paramName + '_hidden'} value={currentValue} />
</>
).render(); ).render();
} }
@@ -8,7 +8,12 @@ export interface IElixFormsComponentProperties {
hasTeamsContext: boolean; hasTeamsContext: boolean;
userDisplayName: string; userDisplayName: string;
additionalFieldsJson?: string; additionalFieldsJson?: string;
customFormFieldsConfiguration?: (formFieldFactory: IElixFormsComponentCustomFormFieldFactory) => JSX.Element;
// Layout Properties
headerTitle?: string;
pageTitle?: string;
pageDescription?: string;
heroImageSrc?: string;
} }
// Additional Fields JSON Schema // Additional Fields JSON Schema
+3 -2
View File
@@ -262,14 +262,14 @@
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/react": { "node_modules/react": {
"version": "19.2.7", "version": "19.2.7",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -279,6 +279,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
"integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"scheduler": "^0.27.0" "scheduler": "^0.27.0"
}, },
+42 -24
View File
@@ -54,6 +54,7 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.29.0", "@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0", "@babel/generator": "^7.29.0",
@@ -270,31 +271,10 @@
"devOptional": true, "devOptional": true,
"license": "(Apache-2.0 AND BSD-3-Clause)" "license": "(Apache-2.0 AND BSD-3-Clause)"
}, },
"node_modules/@emnapi/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.1",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": { "node_modules/@emnapi/wasi-threads": {
"version": "1.2.1", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
@@ -1078,6 +1058,37 @@
"node": "^20.19.0 || >=22.12.0" "node": "^20.19.0 || >=22.12.0"
} }
}, },
"node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.1",
"tslib": "^2.4.0"
}
},
"node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": { "node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.0.0-rc.17", "version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz",
@@ -1154,6 +1165,7 @@
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"csstype": "^3.2.2" "csstype": "^3.2.2"
} }
@@ -1200,6 +1212,7 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -1290,6 +1303,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.10.12", "baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782", "caniuse-lite": "^1.0.30001782",
@@ -1447,6 +1461,7 @@
"integrity": "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==", "integrity": "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.2", "@eslint-community/regexpp": "^4.12.2",
@@ -2487,6 +2502,7 @@
"integrity": "sha512-gF/juR1aX02lZHkvwxdF80SapkQeg2fetoDF6gIQkNbSw5YEUFspMkyGTjPjgZSgIHuZpy+Wz4PlebKnLXMjdg==", "integrity": "sha512-gF/juR1aX02lZHkvwxdF80SapkQeg2fetoDF6gIQkNbSw5YEUFspMkyGTjPjgZSgIHuZpy+Wz4PlebKnLXMjdg==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@bufbuild/protobuf": "^2.5.0", "@bufbuild/protobuf": "^2.5.0",
"colorjs.io": "^0.5.0", "colorjs.io": "^0.5.0",
@@ -2981,6 +2997,7 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz",
"integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"lightningcss": "^1.32.0", "lightningcss": "^1.32.0",
"picomatch": "^4.0.4", "picomatch": "^4.0.4",
@@ -3114,6 +3131,7 @@
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }
+14 -192
View File
@@ -3,202 +3,24 @@ import { useState } from 'react'
// import viteLogo from './assets/vite.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 * as ElixForms from '@common/src/ElixFormsComponent'; import SceltaCarrieraComponent from './SceltaCarrieraComponent';
function App() { function App() {
const [count, setCount] = useState(0) const [count, setCount] = useState(0)
/** @type {import('@common/src/ElixFormsComponent').ElixFormsReact} */ return (
var myElixFormsReact = new ElixForms.ElixFormsReact({ <SceltaCarrieraComponent
description: "This is a custom form created with ElixFormsReact component.", description=""
isDarkTheme: false, isDarkTheme={false}
environmentMessage: "TESTING!", environmentMessage=""
hasTeamsContext: false, hasTeamsContext={false}
userDisplayName: "John Doe", userDisplayName="John Doe"
/* moduleTitle="Modulo A/13 - Richiesta di Certificato"
additionalFieldsJson: JSON.stringify([ pageTitle="Scelta Carriera"
{ pageDescription="Benvenuto nella piattaforma di scelta carriera! Esplora le tue opzioni e trova la strada giusta per te."
"key": "COL0002", heroImageSrc={heroImg}
"type": "text", />
"label": "Campo STRING", );
"required": false
},
{
"key": "COL0003",
"type": "textarea",
"label": "Campo TEXTAREA",
"required": false
},
{
"key": "COL0004",
"type": "boolean",
"label": "Campo Boolean",
"required": false
},
{
"key": "COL0005",
"type": "radio",
"label": "Campo _RADIO_",
"required": false,
"options": [
{ "value": 1, "label": "Opzione 1" },
{ "value": 2, "label": "Opzione 2" },
{ "value": 3, "label": "Altra opzione" }
]
},
{
"key": "COL0006",
"type": "checkbox",
"label": "Campo CHECKBOX",
"options": [
{ "value": 4, "label": "Check 1" },
{ "value": 5, "label": "Check due" },
{ "value": 6, "label": "Altro check" }
]
},
{
"key": "COL0015",
"type": "dropdown",
"label": "Validazione lista",
"required": true,
"options": [
{ "value": 0, "label": "Not applicable" },
{ "value": 1, "label": "Goal 1: No poverty" },
{ "value": 2, "label": "Goal 2: Zero hunger" },
{ "value": 3, "label": "Goal 3: Good health and well-being" },
{ "value": 4, "label": "Goal 4: Quality education" },
{ "value": 5, "label": "Goal 5: Gender equality" },
{ "value": 6, "label": "Goal 6: Clean water and sanitation" },
{ "value": 7, "label": "Goal 7: Affordable and clean energy" },
{ "value": 8, "label": "Goal 8: Decent work and economic growth" },
{ "value": 9, "label": "Goal 9: Industry, Innovation, and Infrastructure" },
{ "value": 10, "label": "Goal 10: Reduced inequalities" },
{ "value": 11, "label": "Goal 11: Sustainable cities and communities" },
{ "value": 12, "label": "Goal 12: Responsible consumption and production" },
{ "value": 13, "label": "Goal 13: Climate action" },
{ "value": 14, "label": "Goal 14: Life below water" },
{ "value": 15, "label": "Goal 15: Life on land" },
{ "value": 16, "label": "Goal 16: Peace, justice and strong institutions" },
{ "value": 17, "label": "Goal 17: Partnerships for the goals" }
]
}
]),
*/
customFormFieldsConfiguration: (
/** @type {import('../../common/src/IElixFormsComponentCustomFormFieldFactory').IElixFormsComponentCustomFormFieldFactory} */
formFieldFactory) => (
<>
{/* {formFieldFactory.createTextInput("required", "Campo Required", true)}
{formFieldFactory.createNumberInput("number", "Number")}
{formFieldFactory.createBooleanInput("boolean", "Boolean")}
{formFieldFactory.createRadioInput("radio", "Radio", [{ "value": 1, "label": "Opzione 1" }, { "value": 2, "label": "Opzione 2" }, { "value": 3, "label": "Altra opzione" }])}
{formFieldFactory.createDropdownInput("dropdown", "Dropdown", [{ "value": 0, "label": "Not applicable" }, { "value": 1, "label": "Goal 1: No poverty" }, { "value": 2, "label": "Goal 2: Zero hunger" }, { "value": 3, "label": "Goal 3: Good health and well-being" }])}
{formFieldFactory.createCheckboxInput("checkbox", "Checkbox", [{ "value": 0, "label": "Check 1" }, { "value": 1, "label": "Check 2" }, { "value": 2, "label": "Check 3" }])} */}
{formFieldFactory.createDropdownInput("COL0015", "COL0015 Goals", [
{ "value": 0, "label": "Not applicable" },
{ "value": 1, "label": "Goal 1: No poverty" },
{ "value": 2, "label": "Goal 2: Zero hunger" },
{ "value": 3, "label": "Goal 3: Good health and well-being" },
{ "value": 4, "label": "Goal 4: Quality education" },
{ "value": 5, "label": "Goal 5: Gender equality" },
{ "value": 6, "label": "Goal 6: Clean water and sanitation" },
{ "value": 7, "label": "Goal 7: Affordable and clean energy" },
{ "value": 8, "label": "Goal 8: Decent work and economic growth" },
{ "value": 9, "label": "Goal 9: Industry, Innovation, and Infrastructure" },
{ "value": 10, "label": "Goal 10: Reduced inequalities" },
{ "value": 11, "label": "Goal 11: Sustainable cities and communities" },
{ "value": 12, "label": "Goal 12: Responsible consumption and production" },
{ "value": 13, "label": "Goal 13: Climate action" },
{ "value": 14, "label": "Goal 14: Life below water" },
{ "value": 15, "label": "Goal 15: Life on land" },
{ "value": 16, "label": "Goal 16: Peace, justice and strong institutions" },
{ "value": 17, "label": "Goal 17: Partnerships for the goals" }
], true)}
{formFieldFactory.createTextInput("COL0002", "Campo STRING", true)}
{formFieldFactory.createTextAreaInput("COL0003", "Campo TEXTAREA", true)}
{formFieldFactory.createBooleanInput("COL0004", "Campo BOOLEAN", true)}
{formFieldFactory.createRadioInput("COL0005", "Campo RADIO", [
{ "value": 1, "label": "Opzione 1" },
{ "value": 2, "label": "Opzione 2" },
{ "value": 3, "label": "Altra opzione" }
], true)}
{formFieldFactory.createCheckboxInput("COL0006", "Campo CHECKBOX", [
{ "value": 4, "label": "Check 1" },
{ "value": 5, "label": "Check 2" },
{ "value": 6, "label": "Altro check" }
])}
</>
)
});
var elixFormsReactRender = myElixFormsReact.render();
return <>
<div className="container_12" id="pageBody">
<div className="grid_12">
<div id="userConsole" className="fe-behaviour">
<header>
<div className="neutral">
<div className="grid_12">
<div className="console_header">
<div className="leftDiv">
<h1>elixForms</h1>
<div className="logo"><a href="https://www.unipr.it/" title="Torna alla homepage dell'Università: di Parma"><img src="https://console-unipr.elixforms.it/elixFormsCustom/images/logo.png" alt="Logo Università: di Parma" /></a>
</div>
</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">
<div>
<div className="fe-module-header-container">
<h2>Modulo A/13 - Richiesta di Certificato</h2>
</div>
</div>
<div className="receipt">
<div className="container">
<h1>Scelta Carriera</h1>
<img src={heroImg} alt="Hero Image" className="hero-image" />
<p>Benvenuto nella piattaforma di scelta carriera! Esplora le tue opzioni e trova la strada giusta per te.</p>
{elixFormsReactRender}
<p>Altre cose</p>
</div>
</div>
</div>
</div>
<div className="clear"></div>
<footer className="it-footer">
<div className="it-footer-main">
<div className="container">
<div className="row clearfix">
<div className="col-sm-12">
<div className="footer-content">
<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>
</footer>
</div>
<div className="clear"></div>
</div>
</div>
</>;
} }
export default App export default App
@@ -0,0 +1,49 @@
import type { JSX } from 'react';
import ElixFormsComponentAbstract from '@common/src/ElixFormsComponentAbstract';
import type { IElixFormsComponentCustomFormFieldFactory } from '@common/src/IElixFormsComponentCustomFormFieldFactory';
export default class SceltaCarrieraComponent extends ElixFormsComponentAbstract {
protected override createCustomFormFields(formFieldFactory: IElixFormsComponentCustomFormFieldFactory): JSX.Element {
return (
<>
{formFieldFactory.createDropdownInput("COL0015", "COL0015 Goals", [
{ "value": 0, "label": "Not applicable" },
{ "value": 1, "label": "Goal 1: No poverty" },
{ "value": 2, "label": "Goal 2: Zero hunger" },
{ "value": 3, "label": "Goal 3: Good health and well-being" },
{ "value": 4, "label": "Goal 4: Quality education" },
{ "value": 5, "label": "Goal 5: Gender equality" },
{ "value": 6, "label": "Goal 6: Clean water and sanitation" },
{ "value": 7, "label": "Goal 7: Affordable and clean energy" },
{ "value": 8, "label": "Goal 8: Decent work and economic growth" },
{ "value": 9, "label": "Goal 9: Industry, Innovation, and Infrastructure" },
{ "value": 10, "label": "Goal 10: Reduced inequalities" },
{ "value": 11, "label": "Goal 11: Sustainable cities and communities" },
{ "value": 12, "label": "Goal 12: Responsible consumption and production" },
{ "value": 13, "label": "Goal 13: Climate action" },
{ "value": 14, "label": "Goal 14: Life below water" },
{ "value": 15, "label": "Goal 15: Life on land" },
{ "value": 16, "label": "Goal 16: Peace, justice and strong institutions" },
{ "value": 17, "label": "Goal 17: Partnerships for the goals" }
], true)}
{formFieldFactory.createTextInput("COL0002", "Campo STRING", true)}
{formFieldFactory.createTextAreaInput("COL0003", "Campo TEXTAREA", true)}
{formFieldFactory.createBooleanInput("COL0004", "Campo BOOLEAN", true)}
{formFieldFactory.createRadioInput("COL0005", "Campo RADIO", [
{ "value": 1, "label": "Opzione 1" },
{ "value": 2, "label": "Opzione 2" },
{ "value": 3, "label": "Altra opzione" }
], true)}
{formFieldFactory.createCheckboxInput("COL0006", "Campo CHECKBOX", [
{ "value": 4, "label": "Check 1" },
{ "value": 5, "label": "Check 2" },
{ "value": 6, "label": "Altro check" }
])}
</>
);
}
protected override renderExtraContentPost(): JSX.Element {
return <p>Altre cose</p>;
}
}