Author SHA1 Message Date
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
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
43 changed files with 6557 additions and 945 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="mainview">
<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="mainview">
<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 |
| `mainview` | 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`
View File
+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",
Binary file not shown.
+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"
}
}
+2 -2
View File
@@ -1,11 +1,11 @@
{ {
"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"
} }
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"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",
+55
View File
@@ -0,0 +1,55 @@
@import url('https://fonts.googleapis.com/css2?family=Titillium+Web:wght@300;400;600;700&display=swap');
@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");
/* Submit button styling (elixForms main submit) */
.mainview .container .submit {
width: auto;
text-align: right;
}
.mainview .container form input[type="submit"] {
background-color: #0066cc;
color: #ffffff;
border: none;
border-radius: 6px;
padding: 10px 20px;
font-weight: 700;
font-size: 15px;
cursor: pointer;
box-shadow: none;
transition:
transform 120ms ease,
opacity 120ms ease;
display: inline-block;
}
.mainview .container form input[type="submit"]:hover {
background-color: #0053a6;
}
.mainview .container form input[type="submit"]:active {
opacity: 0.5;
}
@media (max-width: 480px) {
.mainview .container form input[type="submit"] {
width: 100%;
padding-left: 18px;
padding-right: 18px;
}
}
-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 };
+129 -20
View File
@@ -1,3 +1,4 @@
import './App.css'
import * as FluentUI from '@fluentui/react'; import * as FluentUI from '@fluentui/react';
import type { IElixFormsComponentFormState } from './IElixFormsComponentFormState'; import type { IElixFormsComponentFormState } from './IElixFormsComponentFormState';
import type { IElixFormsComponentProperties } from './IElixFormsComponentProperties'; import type { IElixFormsComponentProperties } from './IElixFormsComponentProperties';
@@ -86,7 +87,11 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
); );
} }
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 +111,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,23 +130,106 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
hasTeamsContext, hasTeamsContext,
userDisplayName, userDisplayName,
additionalFieldsJson, additionalFieldsJson,
customFormFieldsConfiguration headerTitle,
pageTitle,
pageDescription,
submitDescription,
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>
<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">
{headerTitle && (
<div>
<div className="fe-module-header-container">
<h2>{headerTitle}</h2>
</div>
</div>
)}
<div className="mainview">
<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"> <form action="https://procedure.unipr.it/rwe2/ComeBackToElixAndSave" method="post" acceptCharset="ISO-8859-1">
{this.createMandatoryFormFields(queryParams)} {this.createMandatoryFormFields(queryParams)}
{this.createAdditionalFormFields()} {this.createAdditionalFormFields()}
{this.createCustomFormFields(customFormFieldsConfiguration)} {this.renderCustomFormFields()}
<input type="submit" value="Submit"/> <br />
<div className='submit'>
<input type="submit" value={submitDescription ?? 'Submit'} />
</div>
</form> </form>
</section>
{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>
); );
} }
@@ -159,21 +255,25 @@ 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();
} }
@@ -280,7 +380,7 @@ export default abstract class ElixFormsComponentAbstract extends Component<IElix
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 {
@@ -298,14 +398,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 +416,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 <FluentUI.Dropdown
id={paramName} id={paramName}
options={options} options={options}
selectedKey={selectedValue?.toString()} selectedKey={currentValue}
placeholder='---' placeholder='---'
required={required} required={required}
className='isiportalPartialAdminFormFieldSelect' className='isiportalPartialAdminFormFieldSelect'
onChange={(event, value) => { onChange={(event, option) => {
if (option) {
const value = option.key.toString();
this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () => this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () =>
console.log(`Dropdown ${paramName} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`) 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();
} }
@@ -1,6 +1,3 @@
import type { JSX } from "react";
import type { IElixFormsComponentCustomFormFieldFactory } from "./IElixFormsComponentCustomFormFieldFactory";
export interface IElixFormsComponentProperties { export interface IElixFormsComponentProperties {
description: string; description: string;
isDarkTheme: boolean; isDarkTheme: boolean;
@@ -8,7 +5,13 @@ 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;
submitDescription?: string;
heroImageSrc?: 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": [
"**/*" "**/*"
+22 -22
View File
@@ -31,26 +31,26 @@
} }
}, },
"node_modules/@fluentui/font-icons-mdl2": { "node_modules/@fluentui/font-icons-mdl2": {
"version": "8.5.73", "version": "8.5.74",
"resolved": "https://registry.npmjs.org/@fluentui/font-icons-mdl2/-/font-icons-mdl2-8.5.73.tgz", "resolved": "https://registry.npmjs.org/@fluentui/font-icons-mdl2/-/font-icons-mdl2-8.5.74.tgz",
"integrity": "sha512-pyR9OE8LJMEVDAUTaTu/rGXaMCsyRqpp5124DhBoFNf6q5kI660IgbzuQadCpyMBEQadYI5/GLkLN7b1Lgou9Q==", "integrity": "sha512-q3QiQ2CuFdMjtiP9U5+Qi8LMhxKqWEi7C8R+O6SuaGbGS+3YOO/1iuenhKtt961OHn5ev/0M4j4okUZ1112GSw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fluentui/set-version": "^8.2.24", "@fluentui/set-version": "^8.2.24",
"@fluentui/style-utilities": "^8.15.1", "@fluentui/style-utilities": "^8.15.2",
"@fluentui/utilities": "^8.17.2", "@fluentui/utilities": "^8.17.2",
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"node_modules/@fluentui/foundation-legacy": { "node_modules/@fluentui/foundation-legacy": {
"version": "8.6.6", "version": "8.6.7",
"resolved": "https://registry.npmjs.org/@fluentui/foundation-legacy/-/foundation-legacy-8.6.6.tgz", "resolved": "https://registry.npmjs.org/@fluentui/foundation-legacy/-/foundation-legacy-8.6.7.tgz",
"integrity": "sha512-PIcMLOymLvIunp9DrBHUT+dZqwyslKsIOPi+5g/fLU/ySzCg3fs6wQyWHxN3esI5FsSfWi+yCpp+6u5+N00kJQ==", "integrity": "sha512-mu1wHlkom+XT+mlaGe7GW4eIZeIT9OjgdXg2Az/P670N7JA1OKcjHd8F/9ktMb5FGrXJ287AjQDNCDfWnjnCYA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fluentui/merge-styles": "^8.6.14", "@fluentui/merge-styles": "^8.6.14",
"@fluentui/set-version": "^8.2.24", "@fluentui/set-version": "^8.2.24",
"@fluentui/style-utilities": "^8.15.1", "@fluentui/style-utilities": "^8.15.2",
"@fluentui/utilities": "^8.17.2", "@fluentui/utilities": "^8.17.2",
"tslib": "^2.1.0" "tslib": "^2.1.0"
}, },
@@ -79,21 +79,21 @@
} }
}, },
"node_modules/@fluentui/react": { "node_modules/@fluentui/react": {
"version": "8.125.6", "version": "8.125.7",
"resolved": "https://registry.npmjs.org/@fluentui/react/-/react-8.125.6.tgz", "resolved": "https://registry.npmjs.org/@fluentui/react/-/react-8.125.7.tgz",
"integrity": "sha512-uvq0PdAL+Tznikek54zbS31JefTqcPaCkwjHjJz0t8NAC2ZFHozKnkApaQo2+sf0390K3k1ErGWr/+3Tvc6+JQ==", "integrity": "sha512-MsputVjgAZEcSzfr6nXBghfZnqu+GKT4NCBwLeSL8yyQEugUxyZJNp4TVZblGdMognyRDbobNn+/zIhEK4sxNQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fluentui/date-time-utilities": "^8.6.11", "@fluentui/date-time-utilities": "^8.6.11",
"@fluentui/font-icons-mdl2": "^8.5.73", "@fluentui/font-icons-mdl2": "^8.5.74",
"@fluentui/foundation-legacy": "^8.6.6", "@fluentui/foundation-legacy": "^8.6.7",
"@fluentui/merge-styles": "^8.6.14", "@fluentui/merge-styles": "^8.6.14",
"@fluentui/react-focus": "^8.10.6", "@fluentui/react-focus": "^8.10.7",
"@fluentui/react-hooks": "^8.10.2", "@fluentui/react-hooks": "^8.10.2",
"@fluentui/react-portal-compat-context": "^9.0.15", "@fluentui/react-portal-compat-context": "^9.0.15",
"@fluentui/react-window-provider": "^2.3.2", "@fluentui/react-window-provider": "^2.3.2",
"@fluentui/set-version": "^8.2.24", "@fluentui/set-version": "^8.2.24",
"@fluentui/style-utilities": "^8.15.1", "@fluentui/style-utilities": "^8.15.2",
"@fluentui/theme": "^2.7.2", "@fluentui/theme": "^2.7.2",
"@fluentui/utilities": "^8.17.2", "@fluentui/utilities": "^8.17.2",
"@microsoft/load-themed-styles": "^1.10.26", "@microsoft/load-themed-styles": "^1.10.26",
@@ -107,15 +107,15 @@
} }
}, },
"node_modules/@fluentui/react-focus": { "node_modules/@fluentui/react-focus": {
"version": "8.10.6", "version": "8.10.7",
"resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-8.10.6.tgz", "resolved": "https://registry.npmjs.org/@fluentui/react-focus/-/react-focus-8.10.7.tgz",
"integrity": "sha512-hMQw7AETttfex3XEAB/XQalJSL8/RgzOdJrL1RPKcrxdkBal0C1MRQ43Bbmw/4WtI0vK0wI4+jHvCIlM0lkodA==", "integrity": "sha512-Uf0wliddeVPEYylNvbBqYu2V2ofCQ/YhC5JHIFT4RwBkZil1Ta3X+9EFhJMO1LejyLXTKnMMimPxdPS1XzLTGQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fluentui/keyboard-key": "^0.4.23", "@fluentui/keyboard-key": "^0.4.23",
"@fluentui/merge-styles": "^8.6.14", "@fluentui/merge-styles": "^8.6.14",
"@fluentui/set-version": "^8.2.24", "@fluentui/set-version": "^8.2.24",
"@fluentui/style-utilities": "^8.15.1", "@fluentui/style-utilities": "^8.15.2",
"@fluentui/utilities": "^8.17.2", "@fluentui/utilities": "^8.17.2",
"tslib": "^2.1.0" "tslib": "^2.1.0"
}, },
@@ -177,9 +177,9 @@
} }
}, },
"node_modules/@fluentui/style-utilities": { "node_modules/@fluentui/style-utilities": {
"version": "8.15.1", "version": "8.15.2",
"resolved": "https://registry.npmjs.org/@fluentui/style-utilities/-/style-utilities-8.15.1.tgz", "resolved": "https://registry.npmjs.org/@fluentui/style-utilities/-/style-utilities-8.15.2.tgz",
"integrity": "sha512-EwjJE7P7XWViNIN+Klm4rFCaADujsfwHB4nzkYeD0zjMokrznsiAvqERxaGZMCRH7tO+DLKITt7kaoQstNLCVQ==", "integrity": "sha512-rss7pkgiyNQuo1OpS7R2X6IJXIwDzQ0fxQxIeBB7Xp7lM+AwcKn5V7fzgIgCyE7BI6TWSy2TeQJ9Jtr/4fYEaw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fluentui/merge-styles": "^8.6.14", "@fluentui/merge-styles": "^8.6.14",
@@ -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,102 @@
/* ===== 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,20 @@
import './App.css'
import RecuperoPropostaCctDaContrattiComponent from './RecuperoPropostaCctDaContrattiComponent';
function App() {
return (
<RecuperoPropostaCctDaContrattiComponent
description=""
isDarkTheme={false}
environmentMessage=""
hasTeamsContext={false}
userDisplayName=""
headerTitle="Modulo - Placeholder Titolo Modulo"
pageTitle="Recupero Proposta CCT da Contratti"
pageDescription=""
submitDescription='CONFERMA E PROSEGUI'
/>
);
}
export default App
@@ -0,0 +1,361 @@
import React, { type JSX } from 'react';
import * as FluentUI from '@fluentui/react';
import ElixFormsComponentAbstract from '@common/src/ElixFormsComponentAbstract';
import type { IElixFormsComponentCustomFormFieldFactory } from '@common/src/IElixFormsComponentCustomFormFieldFactory';
import { ElixFormsElement } from '@common/src/ElixFormsElement';
import { QueryParamHelper } from '@common/src/QueryParamHelper';
import config from './config.json';
/** Singolo risultato restituito dall'API esterna */
interface ContrattoResult {
idContratto: string;
titoloContratto: string;
idDomanda: string;
idRicevuta: string;
}
/** State aggiuntivo per gestire l'autocomplete */
interface RecuperoPropostaState {
searchText: string;
searchResults: ContrattoResult[];
isLoading: boolean;
isDropdownOpen: boolean;
selectedContract: ContrattoResult | null;
highlightedIndex: number;
errorMessage: string;
}
export default class RecuperoPropostaCctDaContrattiComponent extends ElixFormsComponentAbstract {
/** Stato locale per l'autocomplete (separato dal formData gestito dall'abstract) */
private autocompleteState: RecuperoPropostaState = {
searchText: '',
searchResults: [],
isLoading: false,
isDropdownOpen: false,
selectedContract: null,
highlightedIndex: -1,
errorMessage: '',
};
/** Timer per il debounce della ricerca */
private debounceTimer: ReturnType<typeof setTimeout> | null = null;
/** Ref al container per gestire il click-outside */
private autocompleteRef: React.RefObject<HTMLDivElement | null> = React.createRef();
private static readonly formFieldKeys = {
idContratto: 'COL0010',
titoloContratto: 'COL0020',
idDomanda: 'COL0030',
idRicevuta: 'COL0040',
} as const;
/** Codice fiscale letto dalla query string (COL0009), usato solo per la chiamata API */
private codiceFiscale: string;
constructor(props: any) {
super(props);
this.codiceFiscale = QueryParamHelper.getDecodedTextFromQuery('COL0009') ?? '';
}
override componentDidMount(): void {
// Listener per chiudere il dropdown al click fuori dall'autocomplete
document.addEventListener('mousedown', this.handleClickOutside);
}
override componentWillUnmount(): void {
document.removeEventListener('mousedown', this.handleClickOutside);
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
}
private handleClickOutside = (event: MouseEvent): void => {
if (
this.autocompleteRef.current &&
!this.autocompleteRef.current.contains(event.target as Node)
) {
this.autocompleteState.isDropdownOpen = false;
this.forceUpdate();
}
};
/**
* Chiama l'API esterna per ottenere i contratti corrispondenti al testo di ricerca.
* Autenticazione: Basic Auth (username:password) + header X-Api-Key.
*/
private async fetchContratti(searchTerm: string): Promise<void> {
this.autocompleteState.isLoading = true;
this.autocompleteState.errorMessage = '';
this.forceUpdate();
try {
const url = new URL(config.apiUrl);
url.searchParams.set('cod_fis', this.codiceFiscale);
url.searchParams.set('term', searchTerm);
const credentials = btoa(`${config.apiUsername}:${config.apiPassword}`);
const response = await fetch(url.toString(), {
method: 'GET',
headers: {
'Authorization': `Basic ${credentials}`,
'X-Api-Key': config.apiKey,
'Accept': 'application/json',
},
});
if (!response.ok) {
throw new Error(`Errore API: ${response.status} ${response.statusText}`);
}
const data: ContrattoResult[] = await response.json();
this.autocompleteState.searchResults = data;
this.autocompleteState.isDropdownOpen = data.length > 0;
this.autocompleteState.highlightedIndex = -1;
this.autocompleteState.isLoading = false;
this.forceUpdate();
} catch (error) {
console.error('Errore durante la ricerca contratti:', error);
this.autocompleteState.searchResults = [];
this.autocompleteState.isDropdownOpen = false;
this.autocompleteState.isLoading = false;
this.autocompleteState.errorMessage =
error instanceof Error ? error.message : 'Errore durante la ricerca';
this.forceUpdate();
}
}
/**
* Gestisce il cambio testo nell'input di ricerca.
* Applica debounce e chiama l'API al raggiungimento del minimo di caratteri.
*/
private handleSearchChange = (_event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string): void => {
const text = newValue ?? '';
this.autocompleteState.searchText = text;
// Se l'utente cancella il testo, resetta anche la selezione
if (text.length === 0) {
this.autocompleteState.searchResults = [];
this.autocompleteState.isDropdownOpen = false;
this.autocompleteState.highlightedIndex = -1;
this.autocompleteState.errorMessage = '';
this.clearSelection();
this.forceUpdate();
return;
}
// Debounce della ricerca
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
if (text.length >= config.minCharsForSearch) {
this.debounceTimer = setTimeout(() => {
this.fetchContratti(text);
}, 300);
} else {
this.autocompleteState.searchResults = [];
this.autocompleteState.isDropdownOpen = false;
this.autocompleteState.highlightedIndex = -1;
this.forceUpdate();
}
};
/**
* Gestisce la navigazione da tastiera nel dropdown (frecce, Invio, Escape).
*/
private handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
const { searchResults, isDropdownOpen, highlightedIndex } = this.autocompleteState;
if (!isDropdownOpen || searchResults.length === 0) return;
switch (event.key) {
case 'ArrowDown':
event.preventDefault();
this.autocompleteState.highlightedIndex = Math.min(
highlightedIndex + 1,
searchResults.length - 1
);
this.forceUpdate();
break;
case 'ArrowUp':
event.preventDefault();
this.autocompleteState.highlightedIndex = Math.max(highlightedIndex - 1, 0);
this.forceUpdate();
break;
case 'Enter':
event.preventDefault();
if (highlightedIndex >= 0 && highlightedIndex < searchResults.length) {
this.selectContract(searchResults[highlightedIndex]);
}
break;
case 'Escape':
event.preventDefault();
this.autocompleteState.isDropdownOpen = false;
this.forceUpdate();
break;
}
};
/**
* Seleziona un contratto dal dropdown e popola i campi hidden del form.
*/
private selectContract(contract: ContrattoResult): void {
this.autocompleteState.selectedContract = contract;
this.autocompleteState.searchText = `[${contract.idContratto}] ${contract.titoloContratto}`;
this.autocompleteState.isDropdownOpen = false;
this.autocompleteState.searchResults = [];
this.autocompleteState.highlightedIndex = -1;
// Aggiorna il formData con i valori selezionati
this.setState({
formData: {
...this.state.formData,
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idContratto]: contract.idContratto,
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.titoloContratto]: contract.titoloContratto,
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idDomanda]: contract.idDomanda,
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idRicevuta]: contract.idRicevuta,
},
}, () => {
console.log('Contratto selezionato:', contract);
console.log('Form data aggiornato:', this.state.formData);
});
}
/**
* Resetta la selezione e svuota i campi hidden.
*/
private clearSelection(): void {
this.autocompleteState.selectedContract = null;
this.setState({
formData: {
...this.state.formData,
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idContratto]: '',
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.titoloContratto]: '',
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idDomanda]: '',
[RecuperoPropostaCctDaContrattiComponent.formFieldKeys.idRicevuta]: '',
},
});
}
/**
* Renderizza il componente autocomplete completo (label + input + dropdown).
*/
private renderAutocomplete(): JSX.Element {
const { searchResults, isLoading, isDropdownOpen, highlightedIndex, errorMessage, searchText } = this.autocompleteState;
const autocompleteInput = (
<>
<div className="autocomplete-wrapper" ref={this.autocompleteRef}>
<FluentUI.TextField
id="autocomplete-search"
name="autocomplete-search"
placeholder="Digita almeno 3 caratteri per cercare..."
value={searchText}
onChange={this.handleSearchChange}
onKeyDown={this.handleKeyDown}
className="isiportalPartialAdminFormFieldSingleLineText"
autoComplete="off"
/>
{isLoading && (
<div className="autocomplete-dropdown">
<div className="autocomplete-loading">
<FluentUI.Spinner size={FluentUI.SpinnerSize.small} label="Ricerca in corso..." />
</div>
</div>
)}
{!isLoading && isDropdownOpen && searchResults.length > 0 && (
<div className="autocomplete-dropdown">
{searchResults.map((contract, index) => (
<div
key={`${contract.idContratto}_${index}`}
className={`autocomplete-dropdown-item${index === highlightedIndex ? ' highlighted' : ''}`}
onMouseDown={(e) => {
e.preventDefault(); // Previene blur del TextField
this.selectContract(contract);
}}
onMouseEnter={() => {
this.autocompleteState.highlightedIndex = index;
this.forceUpdate();
}}
>
<span className="contract-id">[{contract.idContratto}]</span>{' '}
<span className="contract-title">{contract.titoloContratto}</span>
</div>
))}
</div>
)}
{!isLoading && isDropdownOpen && searchResults.length === 0 && (
<div className="autocomplete-dropdown">
<div className="autocomplete-no-results">Nessun risultato trovato</div>
</div>
)}
{errorMessage && (
<div className="autocomplete-error">{errorMessage}</div>
)}
</div>
{this.autocompleteState.selectedContract && (
<div className="selected-contract-summary">
<div className="summary-row">
<span className="summary-label">ID Contratto:</span>
<span className="summary-value">{this.autocompleteState.selectedContract.idContratto}</span>
</div>
<div className="summary-row">
<span className="summary-label">Titolo:</span>
<span className="summary-value">{this.autocompleteState.selectedContract.titoloContratto}</span>
</div>
<div className="summary-row">
<span className="summary-label">ID Domanda:</span>
<span className="summary-value">{this.autocompleteState.selectedContract.idDomanda}</span>
</div>
<div className="summary-row">
<span className="summary-label">ID Ricevuta:</span>
<span className="summary-value">{this.autocompleteState.selectedContract.idRicevuta}</span>
</div>
</div>
)}
</>
);
return new ElixFormsElement(
<FluentUI.Label htmlFor="autocomplete-search">Cerca Contratto</FluentUI.Label>,
autocompleteInput
).render();
}
/**
* Override del metodo factory per definire i campi custom del form.
* - Autocomplete per la ricerca contratti
* - 4 campi hidden (COL0010, COL0020, COL0030, COL0040) per i dati da rimandare a elixForms
*/
protected override createCustomFormFields(formFieldFactory: IElixFormsComponentCustomFormFieldFactory): JSX.Element {
const fieldKeys = RecuperoPropostaCctDaContrattiComponent.formFieldKeys;
const idContratto = this.state.formData[fieldKeys.idContratto] ?? '';
const titoloContratto = this.state.formData[fieldKeys.titoloContratto] ?? '';
const idDomanda = this.state.formData[fieldKeys.idDomanda] ?? '';
const idRicevuta = this.state.formData[fieldKeys.idRicevuta] ?? '';
return (
<>
{this.renderAutocomplete()}
{/* Campi hidden per i dati del contratto selezionato — verranno inviati nel POST a elixForms */}
<input type="hidden" id="idContratto_hidden" name={fieldKeys.idContratto} value={idContratto} />
<input type="hidden" id="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,7 @@
{
"apiUrl": "http://localhost:8000/contratti/cerca",
"apiUsername": "your-username",
"apiPassword": "your-password",
"apiKey": "your-api-key",
"minCharsForSearch": 3
}
@@ -0,0 +1,3 @@
declare module "*.css";
declare module "*.svg";
declare module "*.png";
@@ -0,0 +1 @@
/* Global styles - currently empty */
@@ -0,0 +1,13 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
const rootElement = document.getElementById('root')
if (!rootElement) throw new Error('Root element not found')
createRoot(rootElement).render(
<StrictMode>
<App />
</StrictMode>,
)
@@ -0,0 +1,18 @@
{
"extends": "../tsconfig.base.json",
"references": [
{
"path": "../common"
}
],
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"allowJs": true,
"checkJs": true,
//"resolveJsonModule": true
},
"include": [
"src/**/*"
]
}
@@ -0,0 +1,38 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js';
import path from "path";
// https://vite.dev/config/
export default defineConfig({
plugins: [
react(),
cssInjectedByJsPlugin() // forza l'iniezione del CSS nel JS
],
build: {
outDir: 'dist',
cssCodeSplit: false, // evita file CSS separati
rollupOptions: {
input: 'src/main.jsx',
output: {
codeSplitting: false, // forza tutto in un singolo bundle
manualChunks: undefined, // disabilita la creazione di chunk multipli
entryFileNames: 'recupero-proposta-cct-da-contratti.js',
assetFileNames: '[name].[ext]'
},
},
sourcemap: true,
},
resolve: {
alias: {
"@common": path.resolve(__dirname, "../common"),
}
},
server: {
fs: {
allow: [
".." // permette import fuori dalla cartella del progetto
]
}
}
})
+352 -353
View File
File diff suppressed because it is too large Load Diff
+13 -196
View File
@@ -1,204 +1,21 @@
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 * as ElixForms from '@common/src/ElixFormsComponent'; import SceltaCarrieraComponent from './SceltaCarrieraComponent';
function App() { function App() {
const [count, setCount] = useState(0) return (
<SceltaCarrieraComponent
/** @type {import('@common/src/ElixFormsComponent').ElixFormsReact} */ description=""
var myElixFormsReact = new ElixForms.ElixFormsReact({ isDarkTheme={false}
description: "This is a custom form created with ElixFormsReact component.", environmentMessage=""
isDarkTheme: false, hasTeamsContext={false}
environmentMessage: "TESTING!", userDisplayName="John Doe"
hasTeamsContext: false, headerTitle="Modulo A/13 - Richiesta di Certificato"
userDisplayName: "John Doe", pageTitle="Scelta Carriera"
/* pageDescription="Benvenuto nella piattaforma di scelta carriera! Esplora le tue opzioni e trova la strada giusta per te."
additionalFieldsJson: JSON.stringify([ heroImageSrc={heroImg}
{ />
"key": "COL0002",
"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>;
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"files": [], //"files": [],
"references": [ "references": [
{ {
"path": "./common" "path": "./common"