Files
kreios/CLAUDE.md
T
carmine.savino 06617a985a 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.
2026-10-19 19:49:44 +02:00

96 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project overview
Web-based 3D configurator for infissi (windows/doors) — "kreiosinfissi-vite". It renders a Three.js scene
(GLB models) driven by JSON product data and a jQuery-based menu UI, and is meant to run **inside an
`<iframe>`** embedded on `https://www.kreiosinfissi.it` (or `https://kreiosinfissi.test` in dev), with the
final "add to cart" action posted back to the parent window via `postMessage`.
## Commands
Package manager is **pnpm** (`pnpm-lock.yaml` is the lockfile of record even though `package-lock.json` is
also present).
```bash
pnpm install # install deps
pnpm dev # vite dev server on 0.0.0.0:5173
pnpm build # production build -> dist/ (root is src/, see vite.config.js)
pnpm preview # preview the production build
```
There is no test suite (`pnpm test` is a stub that exits with an error) and no linter configured.
### Regenerating PVC price lists
`scripts/extract_listini_pvc.py` parses `PVC_LISTINO.xls` and regenerates
`src/assets/data/modelli/pvc/<modello>/listino.json` files. Requires a Python venv with `xlrd`
(`pip install -r scripts/requirements.txt`). Run with `python3 scripts/extract_listini_pvc.py` from repo root.
Any new title in the spreadsheet needs an entry in that script's `KNOWN_FOLDERS` map or it won't be matched
to an existing model folder.
## Architecture
Entry point is `src/js/starter.js`, loaded from `src/index.html`. Flow on load (`start()`):
1. **`ParamsParser.getQueryParams()`** (`src/js/params_parser.js`) reads the URL query string. Two params
drive everything: `sistema` (`pvc` | `alluminio` | `legno` | `acciaio`) and `type` (model id). See
`docs/URL_PARAMS.md` for the full param contract and the list of valid `type` values per `sistema`.
*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. 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 (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
`playerOff` (offscreen, used only to render the PNG snapshot sent to the cart). Key methods: `swapModel`/
`hideModel` (toggle between pre-loaded variants in the same scene), `changeTexture`/`changeColor` (applied
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. **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`, ...).
- 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
(inline `onclick`/`onchange` in the `_menu*.html` templates) and the JS modules — expect to grep for a
`window.<name> =` assignment when tracing menu behavior, not for exported module functions.