From 151bc7077593004910569b6d99c3cda55e4857ae Mon Sep 17 00:00:00 2001 From: Pier-Paolo Mammi Date: Mon, 20 Jul 2026 14:27:17 +0200 Subject: [PATCH] refactor solution to PHP through AI --- .agents/skills/php-common-library/SKILL.md | 190 ++++++ .agents/skills/php-create-new-page/SKILL.md | 258 ++++++++ .../skills/php-project-architecture/SKILL.md | 124 ++++ .artifacts/php_implementation_plan.md | 171 +++++ .../php_implementation_plan_recupero.md | 57 ++ .artifacts/php_walkthrough.md | 44 ++ .docker/php/Dockerfile | 8 + .docker/php/xdebug.ini | 9 + .vscode/launch.json | 79 ++- php/AGENTS.md | 41 ++ php/common/ElixFormsComponent.php | 610 ++++++++++++++++++ php/common/ElixFormsElement.php | 59 ++ php/common/HttpClient.php | 153 +++++ php/common/QueryParamHelper.php | 91 +++ php/docker-compose.yml | 19 + .../config.json.template | 32 + .../index.php | 439 +++++++++++++ .../search-contratti.php | 76 +++ php/scelta-carriera/index.php | 91 +++ react/AGENTS.md | 36 ++ .../src/elixforms-www.code-workspace | 17 + 21 files changed, 2563 insertions(+), 41 deletions(-) create mode 100644 .agents/skills/php-common-library/SKILL.md create mode 100644 .agents/skills/php-create-new-page/SKILL.md create mode 100644 .agents/skills/php-project-architecture/SKILL.md create mode 100644 .artifacts/php_implementation_plan.md create mode 100644 .artifacts/php_implementation_plan_recupero.md create mode 100644 .artifacts/php_walkthrough.md create mode 100644 .docker/php/Dockerfile create mode 100644 .docker/php/xdebug.ini create mode 100644 php/AGENTS.md create mode 100644 php/common/ElixFormsComponent.php create mode 100644 php/common/ElixFormsElement.php create mode 100644 php/common/HttpClient.php create mode 100644 php/common/QueryParamHelper.php create mode 100644 php/docker-compose.yml create mode 100644 php/recupero-proposta-cct-da-contratti/config.json.template create mode 100644 php/recupero-proposta-cct-da-contratti/index.php create mode 100644 php/recupero-proposta-cct-da-contratti/search-contratti.php create mode 100644 php/scelta-carriera/index.php create mode 100644 react/AGENTS.md create mode 100644 react/recupero-proposta-cct-da-contratti/src/elixforms-www.code-workspace diff --git a/.agents/skills/php-common-library/SKILL.md b/.agents/skills/php-common-library/SKILL.md new file mode 100644 index 0000000..e386153 --- /dev/null +++ b/.agents/skills/php-common-library/SKILL.md @@ -0,0 +1,190 @@ +--- +name: elixforms-php-common-library +description: > + Documentazione della libreria condivisa PHP common/ del progetto elixForms. + Descrive la classe base ElixFormsComponent, i metodi factory per i campi form, + ElixFormsElement per il layout, QueryParamHelper per i query parameter, + e il pattern di ereditarietà per creare pagine custom. + Attiva questa skill quando lavori sui componenti PHP condivisi, crei nuovi tipi di campo, + modifichi la classe base, o integri con la piattaforma elixForms in PHP. +--- + +# Libreria Common PHP elixForms + +## Panoramica + +La cartella `php/common/` contiene la libreria condivisa PHP usata da tutte le pagine custom PHP. È basata su **Bootstrap Italia + HTML nativo** e implementa un sistema di form con campi dinamici, analogo alla libreria React in `react/common/`. + +## Architettura delle Classi + +``` +ElixFormsComponent (classe astratta, PHP 8.0+) + └── SceltaCarrieraPage (classe concreta, in scelta-carriera/index.php) + └── [AltrePageCustom] (classi concrete nelle sotto-cartelle) +``` + +### Namespace + +Tutte le classi usano il namespace PSR-4: +```php +namespace ElixForms\Common; +``` + +Le pagine importano con: +```php +require_once __DIR__ . '/../common/ElixFormsComponent.php'; +use ElixForms\Common\ElixFormsComponent; +``` + +## File della Libreria + +### `ElixFormsComponent.php` (Classe Base Astratta) + +Equivalente di `ElixFormsComponentAbstract.tsx` in React. + +#### Proprietà + +```php +protected array $properties; // Equivalente di IElixFormsComponentProperties +``` + +Chiavi supportate in `$properties`: +| Chiave | Tipo | Obbligatorio | Descrizione | +|---|---|---|---| +| `moduleName` | string | Sì | Nome del modulo nel breadcrumb | +| `cardTitle` | string | Sì | Titolo della card principale | +| `userDisplayName` | string | No | Nome utente nell'header | +| `headerTitle` | string | No | Titolo sotto il breadcrumb | +| `headerHeroImageSrc` | string | No | URL immagine hero | +| `cardDescription` | string | No | Descrizione nella card | +| `alertInfoTitle` | string | No | Titolo alert informativo | +| `alertInfoMessage` | string | No | Messaggio alert informativo | +| `instructionsTitle` | string | No | Titolo istruzioni | +| `instructionsMessage` | string | No | Messaggio istruzioni | +| `submitText` | string | No | Testo bottone submit (default: 'INVIA') | +| `additionalFieldsJson` | string | No | JSON schema per campi dinamici | +| `isDarkTheme` | bool | No | Tema scuro (riservato per uso futuro) | + +#### Metodi Factory (Campi Form) + +| Metodo | Tipo Campo | HTML Output | +|---|---|---| +| `createTextInput($name, $label, $required)` | Testo | `` | +| `createTextAreaInput($name, $label, $required)` | Textarea | `'; + + return (new ElixFormsElement($labelHtml, $inputHtml))->render(); + } + + /** + * Crea un campo numerico (input type="number"). + * + * @param string $paramName Nome/ID del campo + * @param string $label Etichetta del campo + * @param bool $required Se il campo è obbligatorio + * @return string HTML del campo + */ + protected function createNumberInput(string $paramName, string $label, bool $required = false): string + { + $currentValue = htmlspecialchars(QueryParamHelper::getDecodedTextFromQuery($paramName) ?? '', ENT_QUOTES, 'UTF-8'); + $requiredAttr = $required ? ' required' : ''; + + $labelHtml = ''; + $inputHtml = ''; + + return (new ElixFormsElement($labelHtml, $inputHtml))->render(); + } + + /** + * Crea un campo booleano (radio Sì/No). + * + * @param string $paramName Nome/ID del campo + * @param string $label Etichetta del campo + * @param bool $required Se il campo è obbligatorio + * @return string HTML del campo + */ + protected function createBooleanInput(string $paramName, string $label, bool $required = false): string + { + $currentValue = QueryParamHelper::getDecodedTextFromQuery($paramName) ?? ''; + $requiredAttr = $required ? ' required' : ''; + $escapedName = htmlspecialchars($paramName); + $escapedLabel = htmlspecialchars($label); + + $labelHtml = '' . $escapedLabel . ''; + + $options = [ + ['value' => 'true', 'label' => 'Sì'], + ['value' => 'false', 'label' => 'No'], + ]; + + $inputHtml = '
'; + foreach ($options as $option) { + $id = $escapedName . '_' . $option['value']; + $checked = $currentValue === $option['value'] ? ' checked' : ''; + $inputHtml .= << + + +
+ HTML; + } + $inputHtml .= ''; + + return (new ElixFormsElement($labelHtml, $inputHtml))->render(); + } + + /** + * Crea un campo radio con opzioni personalizzate. + * + * @param string $paramName Nome/ID del campo + * @param string $label Etichetta del campo + * @param array $options Opzioni radio + * @param bool $required Se il campo è obbligatorio + * @return string HTML del campo + */ + protected function createRadioInput(string $paramName, string $label, array $options, bool $required = false): string + { + $currentValue = (string)(QueryParamHelper::getOptionFromQuery($paramName) ?? ''); + $requiredAttr = $required ? ' required' : ''; + $escapedName = htmlspecialchars($paramName); + $escapedLabel = htmlspecialchars($label); + + $labelHtml = '' . $escapedLabel . ''; + + $inputHtml = '
'; + foreach ($options as $option) { + $id = $escapedName . '_' . $option['value']; + $checked = $currentValue === (string)$option['value'] ? ' checked' : ''; + $optionLabel = htmlspecialchars($option['label']); + $inputHtml .= << + + +
+ HTML; + } + $inputHtml .= ''; + + return (new ElixFormsElement($labelHtml, $inputHtml))->render(); + } + + /** + * Crea un campo checkbox con opzioni multiple. + * I valori selezionati vengono inviati come stringa separata da virgola in un input hidden. + * + * @param string $paramName Nome/ID del campo (usato per l'hidden input) + * @param string $label Etichetta del campo + * @param array $options Opzioni checkbox + * @return string HTML del campo + */ + protected function createCheckboxInput(string $paramName, string $label, array $options): string + { + $checkedValues = QueryParamHelper::getCheckedFromQuery($paramName); + $currentValue = implode(',', $checkedValues); + $escapedName = htmlspecialchars($paramName); + $escapedLabel = htmlspecialchars($label); + + $labelHtml = ''; + + $inputHtml = '
'; + foreach ($options as $entryIndex => $entry) { + $id = $escapedName . '_' . $entryIndex; + $isChecked = in_array($entryIndex, $checkedValues) ? ' checked' : ''; + $optionLabel = htmlspecialchars($entry['label']); + $inputHtml .= << + + +
+ HTML; + } + $inputHtml .= ''; + // Hidden input che contiene il valore aggregato (aggiornato via JS client-side) + $inputHtml .= ''; + + return (new ElixFormsElement($labelHtml, $inputHtml))->render(); + } + + /** + * Crea un campo dropdown (select). + * + * @param string $paramName Nome/ID del campo + * @param string $label Etichetta del campo + * @param array $options Opzioni dropdown + * @param bool $required Se il campo è obbligatorio + * @return string HTML del campo + */ + protected function createDropdownInput(string $paramName, string $label, array $options, bool $required = false): string + { + $queryValue = QueryParamHelper::getOptionFromQuery($paramName); + $currentValue = $queryValue !== null ? (string)$queryValue : ''; + $requiredAttr = $required ? ' required' : ''; + $escapedName = htmlspecialchars($paramName); + $escapedLabel = htmlspecialchars($label); + + $labelHtml = ''; + + $inputHtml = ''; + + return (new ElixFormsElement($labelHtml, $inputHtml))->render(); + } + + /** + * Crea un campo nascosto (hidden input). + * L'ID dell'hidden ha il suffisso _hidden per evitare conflitti. + * + * @param string $paramName Nome del campo + * @return string HTML dell'input hidden + */ + protected function createHiddenInput(string $paramName): string + { + $value = htmlspecialchars(QueryParamHelper::getDecodedTextFromQuery($paramName) ?? '', ENT_QUOTES, 'UTF-8'); + $escapedName = htmlspecialchars($paramName); + + return ''; + } + + // ========================================================================= + // Metodi interni di rendering + // ========================================================================= + + /** + * Genera gli hidden input per tutti i parametri obbligatori elixForms. + * + * @return string HTML degli hidden input + */ + private function createMandatoryFormFields(): string + { + $html = ''; + foreach ($this->mandatoryFormFieldNames as $paramName) { + $html .= $this->createHiddenInput($paramName); + } + return $html; + } + + /** + * Genera i campi form da uno schema JSON (additionalFieldsJson). + * + * @return string HTML dei campi aggiuntivi + */ + private function createAdditionalFormFields(): string + { + $json = $this->properties['additionalFieldsJson'] ?? '[]'; + $schema = json_decode($json, true); + + if (!is_array($schema)) { + return ''; + } + + $html = ''; + foreach ($schema as $field) { + $html .= $this->renderField($field); + } + return $html; + } + + /** + * Renderizza un singolo campo form in base al tipo specificato nello schema. + * + * @param array $field Definizione del campo + * @return string HTML del campo + */ + private function renderField(array $field): string + { + $key = $field['key'] ?? ''; + $fieldLabel = $field['label'] ?? ''; + $required = $field['required'] ?? false; + $fieldOptions = $field['options'] ?? []; + + return match ($field['type'] ?? '') { + 'text' => $this->createTextInput($key, $fieldLabel, $required), + 'textarea' => $this->createTextAreaInput($key, $fieldLabel, $required), + 'number' => $this->createNumberInput($key, $fieldLabel, $required), + 'boolean' => $this->createBooleanInput($key, $fieldLabel, $required), + 'radio' => $this->createRadioInput($key, $fieldLabel, $fieldOptions, $required), + 'checkbox' => $this->createCheckboxInput($key, $fieldLabel, $fieldOptions), + 'dropdown' => $this->createDropdownInput($key, $fieldLabel, $fieldOptions, $required), + default => '', + }; + } + + // ========================================================================= + // Render principale + // ========================================================================= + + /** + * Genera l'HTML completo della pagina. + * Include: head con CSS, header Bootstrap Italia, breadcrumb, card con form, footer. + * + * @return string HTML completo della pagina + */ + public function render(): string + { + $userDisplayName = $this->properties['userDisplayName'] ?? ''; + $moduleName = htmlspecialchars($this->properties['moduleName'] ?? ''); + $headerTitle = $this->properties['headerTitle'] ?? ''; + $headerHeroImageSrc = $this->properties['headerHeroImageSrc'] ?? ''; + $cardTitle = $this->properties['cardTitle'] ?? ''; + $cardDescription = $this->properties['cardDescription'] ?? ''; + $alertInfoTitle = $this->properties['alertInfoTitle'] ?? ''; + $alertInfoMessage = $this->properties['alertInfoMessage'] ?? ''; + $instructionsTitle = $this->properties['instructionsTitle'] ?? ''; + $instructionsMessage = $this->properties['instructionsMessage'] ?? ''; + $submitText = $this->properties['submitText'] ?? 'INVIA'; + + // Verifica parametri obbligatori + $missingParams = array_filter($this->mandatoryFormFieldNames, fn($p) => !isset($_GET[$p])); + $hasAuthenticationError = count($missingParams) > 0; + + // Contenuti del form + $mandatoryFields = $this->createMandatoryFormFields(); + $additionalFields = $this->createAdditionalFormFields(); + $customFields = $this->createCustomFormFields(); + $extraContentPre = $this->renderExtraContentPre(); + $extraContentPost = $this->renderExtraContentPost(); + + // Costruisci l'HTML della pagina + ob_start(); + ?> + + + + + + <?= htmlspecialchars($cardTitle) ?> — elixForms + + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+ + + +
+ +
+ + + + Hero Image + + + +

+ + +
+
+
+ +

+ + + +

+ + + +
+
+
+
+ + + + + + + + + +
+
+
+
+ + +
+ + + +
+ +
+
+ + + + +
+
+
+
+ +
+ +
+
+ + + renderCheckboxScript() ?> + + + con il codice JS + */ + private function renderCheckboxScript(): string + { + return <<<'SCRIPT' + +SCRIPT; + } +} diff --git a/php/common/ElixFormsElement.php b/php/common/ElixFormsElement.php new file mode 100644 index 0000000..4fa8052 --- /dev/null +++ b/php/common/ElixFormsElement.php @@ -0,0 +1,59 @@ +labelHtml = $labelHtml; + $this->inputHtml = $inputHtml; + $this->separator = $separator; + } + + /** + * Genera l'HTML del campo form con layout a due colonne. + * + * @return string HTML renderizzato + */ + public function render(): string + { + $sep = $this->separator !== '' ? htmlspecialchars($this->separator) : ''; + + return << +
+
+ {$this->labelHtml}{$sep} +
+
+
+ {$this->inputHtml} +
+ + HTML; + } +} diff --git a/php/common/HttpClient.php b/php/common/HttpClient.php new file mode 100644 index 0000000..cf8b022 --- /dev/null +++ b/php/common/HttpClient.php @@ -0,0 +1,153 @@ +defaultHeaders = $defaultHeaders; + } + + /** + * Imposta le credenziali per l'autenticazione Basic. + */ + public function setBasicAuth(string $username, string $password): self + { + $this->username = $username; + $this->password = $password; + return $this; + } + + /** + * Imposta il timeout massimo per la connessione e l'esecuzione. + */ + public function setTimeout(int $seconds): self + { + $this->timeout = $seconds; + return $this; + } + + /** + * Esegue una richiesta HTTP GET. + * + * @param string $url URL della richiesta + * @param array $queryParams Parametri query aggiuntivi + * @param array $headers Header specifici per questa richiesta + * @return string Risposta in formato testuale + * @throws \RuntimeException In caso di errore curl o codice di stato non 2xx + */ + public function get(string $url, array $queryParams = [], array $headers = []): string + { + if (!empty($queryParams)) { + $separator = (strpos($url, '?') === false) ? '?' : '&'; + $url .= $separator . http_build_query($queryParams); + } + + return $this->request($url, 'GET', null, $headers); + } + + /** + * Esegue una richiesta HTTP POST. + * + * @param string $url URL della richiesta + * @param mixed $data Dati da inviare nel body (array, stringa o JSON) + * @param array $headers Header specifici per questa richiesta + * @return string Risposta in formato testuale + * @throws \RuntimeException In caso di errore curl o codice di stato non 2xx + */ + public function post(string $url, $data, array $headers = []): string + { + return $this->request($url, 'POST', $data, $headers); + } + + /** + * Metodo interno per eseguire la richiesta tramite cURL. + */ + private function request(string $url, string $method, $data = null, array $headers = []): string + { + $ch = curl_init(); + if ($ch === false) { + throw new \RuntimeException('Impossibile inizializzare cURL.'); + } + + // Configura le opzioni cURL di base + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_MAXREDIRS, 3); + + // Disabilita la verifica SSL in ambiente di sviluppo locale se necessario, + // ma di default è attiva per sicurezza. + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + + // Gestione metodo HTTP + $method = strtoupper($method); + if ($method === 'POST') { + curl_setopt($ch, CURLOPT_POST, true); + if ($data !== null) { + if (is_array($data)) { + $postData = http_build_query($data); + } else { + $postData = $data; + } + curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); + } + } elseif ($method !== 'GET') { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + } + + // Costruisci gli header + $mergedHeaders = array_merge($this->defaultHeaders, $headers); + $formattedHeaders = []; + foreach ($mergedHeaders as $name => $value) { + $formattedHeaders[] = "{$name}: {$value}"; + } + + if (!empty($formattedHeaders)) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $formattedHeaders); + } + + // Autenticazione Basic + if ($this->username !== null && $this->password !== null) { + curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); + curl_setopt($ch, CURLOPT_USERPWD, "{$this->username}:{$this->password}"); + } + + // Esegui la richiesta + $response = curl_exec($ch); + $error = curl_error($ch); + $errno = curl_errno($ch); + $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + // curl_close non è più necessario in PHP 8.0+ ed è deprecato in PHP 8.5+ + + if ($errno !== 0) { + throw new \RuntimeException("Errore cURL durante la chiamata a {$url}: [{$errno}] {$error}"); + } + + if ($statusCode < 200 || $statusCode >= 300) { + throw new \RuntimeException("Richiesta fallita con codice di stato HTTP {$statusCode}."); + } + + return (string)$response; + } +} diff --git a/php/common/QueryParamHelper.php b/php/common/QueryParamHelper.php new file mode 100644 index 0000000..49770c1 --- /dev/null +++ b/php/common/QueryParamHelper.php @@ -0,0 +1,91 @@ +fieldKeys = $fieldKeys; + $this->minCharsForSearch = $minCharsForSearch; + + // Recupera il codice fiscale dalla query string + $this->codiceFiscale = QueryParamHelper::getDecodedTextFromQuery($fieldKeys['codiceFiscale']) ?? ''; + } + + /** + * Genera l'HTML e CSS del componente Autocomplete e i relativi campi nascosti. + */ + protected function createCustomFormFields(): string + { + $escapedCodFis = htmlspecialchars($this->codiceFiscale, ENT_QUOTES, 'UTF-8'); + $minChars = $this->minCharsForSearch; + + // Campi di output definiti nella configurazione + $outCodiceContratto = htmlspecialchars($this->fieldKeys['codiceContratto']); + $outTitoloContratto = htmlspecialchars($this->fieldKeys['titoloContratto']); + $outIdDomanda = htmlspecialchars($this->fieldKeys['idDomanda']); + $outIdRicevuta = htmlspecialchars($this->fieldKeys['idRicevuta']); + + // Recupero di eventuali valori correnti dal GET per precompilazione iniziale + $currCodice = htmlspecialchars(QueryParamHelper::getDecodedTextFromQuery($this->fieldKeys['codiceContratto']) ?? '', ENT_QUOTES, 'UTF-8'); + $currTitolo = htmlspecialchars(QueryParamHelper::getDecodedTextFromQuery($this->fieldKeys['titoloContratto']) ?? '', ENT_QUOTES, 'UTF-8'); + $currDomanda = htmlspecialchars(QueryParamHelper::getDecodedTextFromQuery($this->fieldKeys['idDomanda']) ?? '', ENT_QUOTES, 'UTF-8'); + $currRicevuta = htmlspecialchars(QueryParamHelper::getDecodedTextFromQuery($this->fieldKeys['idRicevuta']) ?? '', ENT_QUOTES, 'UTF-8'); + + $initialSearchText = ''; + if ($currCodice !== '' && $currTitolo !== '') { + $initialSearchText = "[{$currCodice}] {$currTitolo}"; + } + + // Layout del campo di ricerca + $autocompleteInputHtml = << + + +
+ +
+
+
+
ID Contratto:
+
Titolo:
+
ID Domanda:
+
ID Ricevuta:
+
+
+
+ + + + + + + + HTML; + + $labelHtml = ''; + + return (new \ElixForms\Common\ElixFormsElement($labelHtml, $autocompleteInputHtml))->render(); + } + + /** + * Inietta i CSS specifici per l'autocomplete e la card. + */ + protected function renderExtraContentPre(): string + { + ob_start(); + ?> + + codiceFiscale, ENT_QUOTES, 'UTF-8'); + $minChars = $this->minCharsForSearch; + + ob_start(); + ?> + + $config['elixForms']['inputs']['codiceFiscale'], + 'idDomanda' => $config['elixForms']['outputs']['idDomanda'], + 'idRicevuta' => $config['elixForms']['outputs']['idRicevuta'], + 'codiceContratto'=> $config['elixForms']['outputs']['codiceContratto'], + 'titoloContratto'=> $config['elixForms']['outputs']['titoloContratto'], + ], + $config['component']['minCharsForSearch'] ?? 3 +); + +echo $page->render(); diff --git a/php/recupero-proposta-cct-da-contratti/search-contratti.php b/php/recupero-proposta-cct-da-contratti/search-contratti.php new file mode 100644 index 0000000..c292ec3 --- /dev/null +++ b/php/recupero-proposta-cct-da-contratti/search-contratti.php @@ -0,0 +1,76 @@ + 'Il parametro di ricerca "term" deve contenere almeno 3 caratteri.'], JSON_UNESCAPED_UNICODE); + exit; + } + + if (empty($codFis)) { + http_response_code(400); + echo json_encode(['error' => 'Il parametro "cod_fis" (Codice Fiscale) è obbligatorio.'], JSON_UNESCAPED_UNICODE); + exit; + } + + // 2. Caricamento configurazione + $configFile = __DIR__ . '/config.json'; + if (!file_exists($configFile)) { + throw new \RuntimeException('File di configurazione config.json non trovato.'); + } + + $configJson = file_get_contents($configFile); + $config = json_decode($configJson, true); + if (json_last_error() !== JSON_ERROR_NONE || !isset($config['contrattiWS'])) { + throw new \RuntimeException('Configurazione del Web Service "contrattiWS" mancante o non valida.'); + } + + $wsConfig = $config['contrattiWS']; + $apiUrl = $wsConfig['apiUrl'] ?? ''; + $apiUsername = $wsConfig['apiUsername'] ?? ''; + $apiPassword = $wsConfig['apiPassword'] ?? ''; + $apiKey = $wsConfig['apiKey'] ?? ''; + + // 3. Inizializzazione ed esecuzione della chiamata HTTP con HttpClient + $client = new HttpClient([ + 'X-Api-Key' => $apiKey, + 'Accept' => 'application/json' + ]); + + // Configura autenticazione basic + $client->setBasicAuth($apiUsername, $apiPassword); + + // Esegui la richiesta GET + $responseBody = $client->get($apiUrl, [ + 'cod_fis' => $codFis, + 'term' => $term + ]); + + // Valida che la risposta sia in formato JSON corretto + json_decode($responseBody); + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \RuntimeException('Il servizio contratti ha restituito una risposta in un formato non valido (non JSON).'); + } + + // Ritorna la risposta del WS direttamente + echo $responseBody; + +} catch (\Throwable $e) { + http_response_code(500); + echo json_encode([ + 'error' => 'Errore durante la ricerca dei contratti.', + 'message' => 'Servizio temporaneamente non disponibile o risposta non valida.' + ], JSON_UNESCAPED_UNICODE); +} diff --git a/php/scelta-carriera/index.php b/php/scelta-carriera/index.php new file mode 100644 index 0000000..80bccca --- /dev/null +++ b/php/scelta-carriera/index.php @@ -0,0 +1,91 @@ +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); + + // Text input: COL0002 + $html .= $this->createTextInput('COL0002', 'Campo STRING', true); + + // Textarea: COL0003 + $html .= $this->createTextAreaInput('COL0003', 'Campo TEXTAREA', true); + + // Boolean: COL0004 + $html .= $this->createBooleanInput('COL0004', 'Campo BOOLEAN', true); + + // Radio: COL0005 + $html .= $this->createRadioInput('COL0005', 'Campo RADIO', [ + ['value' => 1, 'label' => 'Opzione 1'], + ['value' => 2, 'label' => 'Opzione 2'], + ['value' => 3, 'label' => 'Altra opzione'], + ], true); + + // Checkbox: COL0006 + $html .= $this->createCheckboxInput('COL0006', 'Campo CHECKBOX', [ + ['value' => 4, 'label' => 'Check 1'], + ['value' => 5, 'label' => 'Check 2'], + ['value' => 6, 'label' => 'Altro check'], + ]); + + return $html; + } + + protected function renderExtraContentPost(): string + { + return '
Questa pagina è la versione PHP della custom page Scelta Carriera.
'; + } +} + +// ============================================================================= +// Entry point — Istanziazione e rendering della pagina +// ============================================================================= + +$page = new SceltaCarrieraPage([ + 'moduleName' => '', + 'alertInfoMessage' => '', + 'userDisplayName' => 'John Doe', + 'headerTitle' => 'Modulo A/13 - Richiesta di Certificato', + 'cardTitle' => 'Scelta Carriera', + 'cardDescription' => 'Benvenuto nella piattaforma di scelta carriera! Esplora le tue opzioni e trova la strada giusta per te.', + 'headerHeroImageSrc' => '', +]); + +echo $page->render(); diff --git a/react/AGENTS.md b/react/AGENTS.md new file mode 100644 index 0000000..5574c74 --- /dev/null +++ b/react/AGENTS.md @@ -0,0 +1,36 @@ +# Agente React — Regole e Convenzioni + +Questo file definisce le istruzioni specifiche, le regole e le convenzioni per lo sviluppo e la manutenzione delle pagine custom e dei componenti scritti in **React e TypeScript** nel monorepo `react/`. + +## Gestione ID dei Form Element +Gli ID dei campi del form (es. nel Dropdown, Checkbox, TextField) creati tramite i metodi `create...` in [ElixFormsComponentAbstract.tsx](file:///c:/__Git/_UniPR/elixforms-custom-pages/react/common/src/ElixFormsComponentAbstract.tsx) **non devono mai essere cambiati o manipolati** (ad es. aggiungendo suffissi come `_dropdown`), in quanto la piattaforma elixForms si basa sul loro nome ed ID esatto. +In caso di conflitti di ID (ad esempio con input `hidden`), applicare il suffisso `_hidden` all'input hidden e mantenere l'ID originale senza suffissi sull'elemento principale visibile. + +## Convenzioni di Codice e Framework +1. **Libreria Common (`react/common/`)**: + - Scritta in **TypeScript** (`.tsx` o `.ts`). + - Usa **React Class Components** (non hooks/functional components) ereditando da `React.Component`. + - Lo stato è gestito in modo immutabile tramite `this.setState`. + - I checkbox mantengono uno stato locale tramite un `Map` interno. + +2. **Pagine Custom (`react//`)**: + - Possono essere in `.jsx` o `.tsx` (TypeScript con `checkJs: true`). + - Usano **Functional Components** ed eventualmente Hooks se necessario. + - Istanziano la pagina tramite la classe: `new ElixForms.ElixFormsReact(props).render()`. + +3. **Import di Tipi**: + - Avendo `verbatimModuleSyntax: true` abilitato in TypeScript, è obbligatorio usare `import type` per i tipi (es. `import type { JSX } from 'react';`). + +## Configurazione di Build e Sviluppo +- Ogni pagina è un'applicazione Vite autonoma configurata in `vite.config.js`. +- La build deve produrre un **singolo bundle JS** autolimitato (`codeSplitting: false`, `manualChunks: undefined`). +- I CSS locali e SCSS moduli devono essere iniettati nel bundle finale tramite il plugin `vite-plugin-css-injected-by-js`. +- I CSS globali del design system di UniPR non vanno inclusi nella build ma caricati a runtime via `@import url(...)` in `App.css`. + +## Skill React Correlate +Fai riferimento alle seguenti skill per maggiori dettagli operativi: +- [elixforms-common-library](file:///c:/__Git/_UniPR/elixforms-custom-pages/.agents/skills/common-library/SKILL.md): Documentazione componenti e factory. +- [elixforms-create-new-page](file:///c:/__Git/_UniPR/elixforms-custom-pages/.agents/skills/create-new-page/SKILL.md): Scaffolding di una nuova pagina React. +- [elixforms-typescript-conventions](file:///c:/__Git/_UniPR/elixforms-custom-pages/.agents/skills/typescript-react-conventions/SKILL.md): Linee guida di tipizzazione. +- [elixforms-vite-build-config](file:///c:/__Git/_UniPR/elixforms-custom-pages/.agents/skills/vite-build-config/SKILL.md): Bundling e build singola. +- [elixforms-dev-workflow](file:///c:/__Git/_UniPR/elixforms-custom-pages/.agents/skills/dev-workflow/SKILL.md): Comandi dev, build e debug in VS Code. diff --git a/react/recupero-proposta-cct-da-contratti/src/elixforms-www.code-workspace b/react/recupero-proposta-cct-da-contratti/src/elixforms-www.code-workspace new file mode 100644 index 0000000..223d0e9 --- /dev/null +++ b/react/recupero-proposta-cct-da-contratti/src/elixforms-www.code-workspace @@ -0,0 +1,17 @@ +{ + "folders": [ + { + "name": "elixforms-custom-pages", + "path": "../../.." + }, + { + "name": "elixforms-webservices", + "path": "../../../../elixforms-webservices" + }, + { + "name": "SSH FS - Dev Server", + "uri": "ssh://dev.unipr.it/home/dev68/dev" + } + ], + "settings": {} +} \ No newline at end of file