feat: add previous configuration handling for user selections in quotes

- Implemented session storage management for the last configuration choices made by the user.
- Added functionality to prompt users when changing models to retain their previous selections.
- Introduced methods to apply previous choices to the newly loaded model, including finishes, handles, glass types, and options.
- Enhanced user experience by ensuring selections are automatically applied where applicable.
This commit is contained in:
2026-10-19 19:49:44 +02:00
parent 863e6ca920
commit 06617a985a
197 changed files with 4892 additions and 400 deletions
+29 -16
View File
@@ -41,16 +41,21 @@ Entry point is `src/js/starter.js`, loaded from `src/index.html`. Flow on load (
*Note: that doc describes a more modular refactor (`Application.js`, `communication/UrlParamsParser.js`,
`communication/IframeBridge.js`) that does not exist yet in `src/js` — the current implementation is the
flatter `starter.js`/`params_parser.js` described here.*
2. Product/catalog data is fetched as JSON from `src/assets/data/sistemi/<sistema>/modelli.json` and
`src/assets/data/modelli/<sistema>/<cartella>/{form,modello,maniglia,avvolgibile,vetro,listino}.json`
(which JSON files are fetched depends on the `moduli` array in the matched `modelli.json` entry).
2. Catalog data comes from the Laravel backoffice (`backoffice/`, API base in `src/js/api_base.js`):
`GET /api/sistemi` (systems + active models that have a price list; models without one are hidden and flagged in the admin list) and
`GET /api/sistemi/<sistema>/modelli/<modello>/configurazione`, which returns the menu wiring (`form`: radios,
selects, finish menus — the old `form.json` shape), finishes, handles, glass types and the price-grid sizes,
with image URLs served by `GET /api/media/...` (CORS-enabled, needed for WebGL textures). Only
`avvolgibile.json` and `sistemi/<sistema>/altezza_maniglia_<categoria>.json` are still read from `src/assets/data`.
3. **`MenuBuilder.build()`** (`src/js/menu_builder.js`) injects an HTML template (`assets/_menu.html`, or
`assets/_menu-cassonetto.html` when `type=cassonetto`) into `#left-container`, builds the model-picker
list, handles the mobile-portrait/landscape layout reflow, and initializes `PriceCalculator`. It returns
the price calculator instance used by the rest of the app.
4. **`Configurator.init()` / `.enable()`** (`src/js/configurator.js`) wires a single delegated `input` handler
on the size fields, selects and radios. On every change it recomputes price via `PriceCalculator`, builds
`window.configurazione` (the object that gets posted to the parent on "add to cart"), and updates the DOM.
on the size fields, selects and radios. On every change (debounced) it sends the selected codes to
`POST /api/sistemi/<sistema>/modelli/<modello>/prezzo`, stores the result in `window.configurazione` and
updates the DOM. "Add to cart" sends the same codes (`scelte`): the backend recomputes the price and ignores
any price coming from the client.
5. **`APP.Player`** (`src/js/app.js`) wraps a Three.js renderer/scene/camera (scene graph loaded from
`src/assets/app.json`, a three.js-editor-style export) plus the actual product GLB
(`src/assets/models/<sistema>/<modello.modello>`). Two players are created: `player` (visible canvas) and
@@ -59,22 +64,30 @@ Entry point is `src/js/starter.js`, loaded from `src/index.html`. Flow on load (
per `userData.categoria` mesh tag — categories like `interno`, `esterno`, `maniglia`, `vetro`, `cerniere`),
`flipView` (180° GSAP-animated turn between front/back view), `addMarker` (in-scene hotspot with a video
popup).
6. **`PriceCalculator`** (`src/js/price_calculator.js`) is a stateless-ish module (single module-level
`listini` cache set via `init`) that looks up a base price from a width/height grid in **mm** (served by
the backoffice API, using the first grid size ≥ the requested one) and layers on percentage/flat supplements for finish, glass, hinges, handle
height, and actuator type, then adds 22% VAT.
6. **Pricing is server-side only**: `backoffice/app/Support/Configuratore/CalcolatorePrezzo.php`.
`prezzo = base × (1 + supplementi % / 100) + supplementi fissi + variazione vetro` (VAT excluded). Base is
the first grid size ≥ the requested one (mm). Supplements come from `opzioni` rows (finish type
bianco/uniforme/bicolore, hinges, profili extra, ...): percentage, fixed, or per linear metre
(ml from L/H multipliers + extra mm). Groups with `ui = multipla` (profili extra) accept several codes,
with same-`alternativa` options mutually exclusive. Sistema-level rows can be overridden per model by group.
Option groups themselves live in `gruppi_opzioni` (title, control radio/select/multipla, menu section, and
the model `moduli` key that enables them): a group without fixed markup in `_menu.html` is rendered by
`MenuBuilder` into `.opzioni-sistema` / `.opzioni-accessori`. Select options can call a `window[func]`
(e.g. `nascondiCategoria` with `categoria: maniglia` for the motor) via their `parametri`. The glass
variation is Σ max(pane area, minimo_mq) × maggiorazione_mq, with panes from the model's `regole_vetro`
(spec: "Specifica tecnica vetri"). All of it is edited in Filament under "Configuratore".
`src/js/price_calculator.js` only wraps the API call and the grid sizes.
### Data conventions
- Every 3D-visible part is tagged with `userData.categoria` in the GLB/scene so JS can show/hide/retexture it
without knowing mesh names (`interno`, `esterno`, `maniglia`, `cerniere`, `vetro`, ...).
- Each model folder under `src/assets/data/modelli/<sistema>/<cartella>/` follows the same file set:
`form.json` (UI radios/selects/finiture wiring — declares which named `<select>`/radio groups exist and
which categories/texture folders they map to), `modello.json` (finish options), `maniglia.json` (handle
options), `avvolgibile.json` (shutter, cassonetto-only), `vetro.json` (glass options), `listino.json`
(price grid).
- Texture images live under `src/assets/data/immagini/<sistema>/<folder>/<texture_folder>/<id>/...` and are
loaded lazily per selection (`APP.Player.changeTexture`); a monotonically-checked `isStale()` guard discards
- Which menu sections a model shows (and which items enter the price) is driven by its `moduli` in the
backoffice (`Modello::MODULI`, e.g. `finitura_esterna`, `apertura`, `telaio`, `vetro`). The per-model JSON
files under `src/assets/data/modelli/` are legacy and no longer read (except `avvolgibile.json`).
- Texture and icon images live in the backoffice (`storage/app/public/configuratore/...`, seeded once by
`ConfiguratoreSeeder` from `backoffice/database/seeders/data/configuratore/`) and are loaded lazily per
selection (`APP.Player.changeTexture`) from absolute URLs; a monotonically-checked `isStale()` guard discards
a texture load if the user changed the selection again before it finished.
- `window.*` globals (`window.configurazione`, `window.cambiaTexture`, `window.caricaModello`,
`window.rotate`, `window.sistema`, etc.) are the integration surface between the jQuery-driven menu markup