Refactor PVC models and update tests

- Updated the PVC models in the database and adjusted the tests accordingly to reflect the new model counts and codes.
- Removed deprecated models from the JSON data for PVC systems.
- Added new GLB models for "scorrevole_sf_3_ante" and "scorrevole_sf_4_ante".
- Enhanced the development scripts to streamline the build process for both frontends.
- Updated documentation to reflect changes in the development environment and deployment process.
- Improved the styling of texture radio options for better visibility.
- Fixed URL handling in the configurator to ensure proper routing and API calls.
This commit is contained in:
2026-10-20 18:40:59 +02:00
parent b6a32edfe6
commit 0dfda3a338
33 changed files with 339 additions and 366 deletions
+3 -2
View File
@@ -1,2 +1,3 @@
# Ambiente di sviluppo locale (pnpm dev). Usato solo dal browser (nessun segreto).
VITE_PREVENTIVI_API_BASE=http://localhost:8000/api
# Il configuratore è servito dal backend Laravel (/configuratore/) e usa l'API sulla stessa origine:
# non serve configurare nulla. Solo per puntare a un'API su un altro dominio:
# VITE_PREVENTIVI_API_BASE=https://altro-dominio.example/api
+3 -4
View File
@@ -1,4 +1,3 @@
# Copia questo file in .env.production e imposta l'URL reale dell'API Laravel prima del deploy
# (pnpm build usa .env.production quando presente). .env.production non è tracciato in git:
# va creato una volta per ambiente e non deve mai contenere un dominio "indovinato".
VITE_PREVENTIVI_API_BASE=https://preventivi.kreiosinfissi.it/api
# Opzionale: il configuratore usa di default l'API sulla stessa origine da cui è servito
# (/configuratore/ sul backend Laravel). Impostare solo per un'API su un dominio diverso.
# VITE_PREVENTIVI_API_BASE=https://preventivi.kreiosinfissi.it/api
+4 -2
View File
@@ -1,9 +1,11 @@
/.venv/
/kreios/
/dist/
/backoffice/cliente/dist/
/backoffice/public/configuratore/
/backoffice/public/preventivo/
/node_modules/
kreios.zip
.env.production
Thumbs.db
/reference/icone_profili_extra
/reference/icone_profili_extra
/reference/_*
+7 -3
View File
@@ -16,11 +16,15 @@ 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
./dev.sh # single server: Laravel on :8000 + both frontends in `vite build --watch`
./build.sh # build configuratore + app cliente into backoffice/public
pnpm build # configuratore only -> backoffice/public/configuratore (root is src/)
```
Everything is served by Laravel on one origin: configuratore at `/configuratore/?sistema=…&type=…`, quote
app (`backoffice/cliente`, Vue) at `/preventivo/`, API at `/api`, admin at `/admin`. The `index.html` of each
frontend is served by routes in `backoffice/routes/web.php`; there is no HMR, reload after the rebuild.
There is no test suite (`pnpm test` is a stub that exits with an error) and no linter configured.
### Regenerating PVC price lists
+2
View File
@@ -15,6 +15,8 @@
/auth.json
node_modules
/public/build
/public/configuratore
/public/preventivo
/public/fonts-manifest.dev.json
/public/hot
/public/storage
+12 -24
View File
@@ -18,7 +18,7 @@ riassuntivo e invia le email a cliente e amministratore.
2. `starter.js` invia `POST /api/carrello/aggiungi` (con `sistema`, `type`, dimensioni, prezzo, payload,
screenshot PNG). Il backend crea/aggiorna un `preventivo` (stato `bozza`) identificato da un `token`
salvato in `localStorage`.
3. Il pulsante "Vai al preventivo" apre l'app cliente passando il token: `…/?token=XXX`.
3. Il pulsante "Vai al preventivo" apre l'app cliente passando il token: `/preventivo/?token=XXX`.
4. L'app cliente mostra il carrello, l'utente compila i dati e conferma → `POST /api/carrello/{token}/conferma`.
5. Il backend crea il `cliente`, imposta lo stato a `inviato`, genera il PDF e invia le email
(cliente con PDF allegato + notifica admin). Il preventivo è visibile nel backoffice.
@@ -33,20 +33,14 @@ php artisan migrate --seed # crea le tabelle + utente admin
php artisan storage:link
php artisan serve --port=8000 # API + admin su http://localhost:8000
# 2. App cliente (cartella cliente/)
cd cliente
pnpm install
pnpm dev # http://localhost:5199
# 3. Configuratore (root del repo)
# 2. Frontend: configuratore + app cliente, compilati in public/ (dalla root del repo)
cd ..
pnpm dev # http://localhost:5173
./build.sh # -> public/configuratore e public/preventivo
```
Le tre porte (8000 / 5199 / 5173) sono fisse (`strictPort: true` nei rispettivi `vite.config.js`): se una è
già occupata l'avvio fallisce con un errore esplicito invece di spostarsi silenziosamente su un'altra porta
— evita che il configuratore e l'app cliente finiscano per caso sulla stessa porta (successo in passato,
dato che entrambi avevano `5174` come default).
Configuratore e app cliente sono serviti da questo backend: <http://localhost:8000/configuratore/?sistema=pvc&type=finestra_2_ante>
e <http://localhost:8000/preventivo/>. In sviluppo `./dev.sh` (root) avvia Laravel e ricompila i frontend
a ogni modifica. Dettagli in `../docs/PREVENTIVI_BACKOFFICE.md`.
### Credenziali admin (seed di default)
- URL: <http://localhost:8000/admin>
@@ -62,19 +56,13 @@ In produzione configurare SMTP nel `.env`.
|---|---|
| `DB_*` | MySQL: `127.0.0.1`, `root`, no password, `kreios_preventivi` |
| `ADMIN_NOTIFY_EMAIL` | Email che riceve la notifica di nuovo preventivo |
| `APP_FRONTEND_CLIENTE_URL` | URL dell'app cliente (per i link) |
| `CORS_ALLOWED_ORIGINS` | Origini autorizzate all'API (default `*` in dev) |
| `APP_FRONTEND_CLIENTE_URL` | URL dell'app cliente, solo se non servita da qui (default `APP_URL/preventivo`) |
| `CORS_ALLOWED_ORIGINS` | Origini esterne autorizzate all'API (default `*`; i frontend serviti da qui non ne hanno bisogno) |
### Configuratore (`../src/js/starter.js`)
`PREVENTIVI_API_BASE` è l'unico indirizzo che il configuratore deve conoscere a priori (default
`http://localhost:8000/api`, sovrascrivibile con `window.PREVENTIVI_API_BASE` prima del bundle). L'URL
dell'app cliente **non è hardcoded**: all'avvio il configuratore chiama `GET /api/config` e usa il
`frontend_cliente_url` restituito da Laravel (letto da `APP_FRONTEND_CLIENTE_URL` nel `.env`) — così non c'è
da tenere sincronizzati due URL se la porta Vite dell'app cliente cambia (es. per conflitto con altre porte
occupate). `window.APP_CLIENTE_URL` resta come fallback locale se il backend non è raggiungibile.
### App cliente (`cliente/.env`)
`VITE_API_BASE` — URL base dell'API Laravel (default `http://localhost:8000/api`).
### Frontend
Configuratore e app cliente chiamano l'API sulla stessa origine (`${location.origin}/api`): nessuna
variabile da impostare. `VITE_PREVENTIVI_API_BASE` (root) / `VITE_API_BASE` (`cliente/`) servono solo per
un'API su un altro dominio. Il link "Vai al preventivo" usa `frontend_cliente_url` da `GET /api/config`.
## Modello dati
-14
View File
@@ -1,14 +0,0 @@
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/jpeg" href="/kreios-logo.jpg" />
<title>Il tuo preventivo · Krèios</title>
<script type="module" crossorigin src="/assets/index-CmMOSSq4.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C_ir1rDB.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
+5 -2
View File
@@ -2,6 +2,9 @@
import { ref, reactive, computed, onMounted } from 'vue'
import { api, resolveToken, resolveReturnUrl, resolveClienteToken, clearToken, formatEuro } from './api.js'
// Servita sotto /preventivo/: i file di public/ vanno risolti rispetto alla base della build.
const logoUrl = `${import.meta.env.BASE_URL}kreios-logo.jpg`
const step = ref('cart') // cart | checkout | done
const loading = ref(true)
const submitting = ref(false)
@@ -158,7 +161,7 @@ onMounted(loadCarrello)
<template>
<header class="app-header">
<img src="/kreios-logo.jpg" alt="Krèios" />
<img :src="logoUrl" alt="Krèios" />
<h1>Il tuo preventivo</h1>
</header>
@@ -340,7 +343,7 @@ onMounted(loadCarrello)
<!-- STEP CONFERMA -->
<template v-else-if="step === 'done'">
<div class="card center">
<img class="done-logo" src="/kreios-logo.jpg" alt="Krèios" />
<img class="done-logo" :src="logoUrl" alt="Krèios" />
<div class="success-icon">✅</div>
<h2>Grazie!</h2>
<p>La tua richiesta di preventivo <b>n. {{ numeroConfermato }}</b> è stata inviata.</p>
+2 -1
View File
@@ -1,4 +1,5 @@
const BASE = import.meta.env.VITE_API_BASE || 'http://localhost:8000/api'
// L'app cliente è servita dal backend stesso (/preventivo/): l'API è sulla stessa origine.
const BASE = import.meta.env.VITE_API_BASE || `${window.location.origin}/api`
async function request(path, options = {}) {
const res = await fetch(`${BASE}${path}`, {
+6 -4
View File
@@ -3,9 +3,11 @@ import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
host: true,
port: 5199,
strictPort: true,
// L'app cliente è servita da Laravel sotto /preventivo/ (stessa origine dell'API):
// la build finisce direttamente in backoffice/public/preventivo.
base: '/preventivo/',
build: {
outDir: '../public/preventivo',
emptyOutDir: true,
},
})
+6 -4
View File
@@ -4,11 +4,13 @@ return [
// Email a cui inviare la notifica di nuovo preventivo.
'admin_email' => env('ADMIN_NOTIFY_EMAIL'),
// URL base dell'app cliente (per link nelle email admin).
'frontend_cliente_url' => env('APP_FRONTEND_CLIENTE_URL', 'http://localhost:5199'),
// URL base dell'app cliente (per link nelle email admin). Di default è servita da questo
// backend sotto /preventivo (build in public/preventivo, vedi routes/web.php).
'frontend_cliente_url' => env('APP_FRONTEND_CLIENTE_URL', rtrim((string) env('APP_URL', 'http://localhost'), '/').'/preventivo'),
// URL del configuratore 3D (linkato dal menu del backoffice).
'configuratore_url' => env('APP_CONFIGURATORE_URL', 'http://localhost:5173'),
// URL del configuratore 3D (linkato dal menu del backoffice). Di default è servito da questo
// backend sotto /configuratore/ (build in public/configuratore).
'configuratore_url' => env('APP_CONFIGURATORE_URL', rtrim((string) env('APP_URL', 'http://localhost'), '/').'/configuratore/'),
// Regola base di scontistica (in %), valida per tutti i clienti salvo personalizzazione:
// imponibile = totale parziale x (1 - 1° sconto) x (1 - 2° sconto extra).
@@ -0,0 +1,86 @@
<?php
use App\Models\Modello;
use App\Models\Sistema;
use Illuminate\Database\Migrations\Migration;
/**
* Scorrevoli SF PVC: i modelli 3 e 4 ante hanno ora il file 3D e vanno nel
* configuratore (listino, regole vetro e opzioni ci sono già); 1 anta SF e parallelo
* 1 anta escono dalla gamma (non hanno listino né opzioni proprie).
*/
return new class extends Migration
{
private const NUOVI = [
'scorrevole_sf_3_ante' => 'SCORREVOLE 3 ANTE SF PVC - SALAMANDER',
'scorrevole_sf_4_ante' => 'SCORREVOLE 4 ANTE SF PVC - SALAMANDER',
];
private const ELIMINATI = ['scorrevole_sf_1_anta', 'scorrevole_parallelo_1_anta'];
/** Moduli che il 2 ante ha in più rispetto ai modelli senza configurazione 3D. */
private const MODULI_3D = ['listino', 'maniglia', 'modello', 'vetro'];
public function up(): void
{
$pvc = Sistema::where('codice', 'pvc')->first();
if (! $pvc) {
return;
}
foreach (self::NUOVI as $codice => $titolo) {
$modello = $pvc->modelli()->where('codice', $codice)->first();
$modello?->update([
'attivo' => true,
'cartella' => $codice,
'file_modello' => "{$codice}.glb",
'cartella_texture' => 'finestra_2_ante',
'titolo' => $modello->titolo ?: $titolo,
'moduli' => array_values(array_unique([...self::MODULI_3D, ...($modello->moduli ?? [])])),
]);
}
// Una alla volta: l'evento deleted rimuove anche l'icona dal disco.
$pvc->modelli()->whereIn('codice', self::ELIMINATI)->get()->each->delete();
$this->dopoIl2Ante($pvc);
}
/** Attivandosi finirebbero in fondo agli attivi: li mette subito dopo lo scorrevole SF 2 ante. */
private function dopoIl2Ante(Sistema $pvc): void
{
$codici = $pvc->modelli()->orderBy('ordine')->orderBy('id')->pluck('codice')
->diff(array_keys(self::NUOVI))->values()->all();
$posizione = array_search('scorrevole_sf_2_ante', $codici, true);
if ($posizione !== false) {
array_splice($codici, $posizione + 1, 0, array_keys(self::NUOVI));
}
foreach ($codici as $i => $codice) {
$pvc->modelli()->where('codice', $codice)->update(['ordine' => $i + 1]);
}
Modello::normalizzaOrdine($pvc->id);
}
public function down(): void
{
// I modelli eliminati non vengono ricreati: si recuperano con il CatalogoSeeder
// da una versione precedente di database/seeders/data/modelli_pvc.php.
$pvc = Sistema::where('codice', 'pvc')->first();
if (! $pvc) {
return;
}
foreach (array_keys(self::NUOVI) as $codice) {
$modello = $pvc->modelli()->where('codice', $codice)->first();
$modello?->update([
'attivo' => false,
'cartella' => null,
'file_modello' => null,
'cartella_texture' => null,
'moduli' => array_values(array_diff($modello->moduli ?? [], self::MODULI_3D)),
]);
}
}
};
@@ -107,8 +107,6 @@ return [
'scorrevole_hst_2_ante' => ['ALZ2', ['finitura_esterna', 'apertura']],
'scorrevole_hst_4_ante' => ['ALZ4', ['finitura_esterna', 'apertura']],
// Non presenti nella specifica vetri: nessuna variazione di prezzo per il vetro.
'scorrevole_sf_1_anta' => [null, ['finitura_esterna', 'apertura']],
'scorrevole_parallelo_1_anta' => [null, ['finitura_esterna', 'apertura']],
'scorrevole_sf_2_ante' => ['SC2', ['finitura_esterna', 'apertura']],
'scorrevole_sf_3_ante' => ['SC3', ['finitura_esterna', 'apertura']],
'scorrevole_sf_4_ante' => ['SC4', ['finitura_esterna', 'apertura']],
@@ -636,9 +636,9 @@ return [
'portafinestra_3_ante' => 'PORTAFINESTRA 3 ANTE PVC - SALAMANDER BE 82',
'scorrevole_hst_1_anta' => 'SCORREVOLE 1 ANTA HTS PVC - SALAMANDER',
'scorrevole_hst_2_ante' => 'SCORREVOLE 2 ANTE HTS PVC - SALAMANDER',
'scorrevole_parallelo_1_anta' => 'SCORREVOLE PARALLELO 1 ANTA PVC - SALAMANDER',
'scorrevole_sf_1_anta' => 'SCORREVOLE 1 ANTA SF PVC - SALAMANDER',
'scorrevole_sf_2_ante' => 'SCORREVOLE 2 ANTE SF PVC - SALAMANDER',
'scorrevole_sf_3_ante' => 'SCORREVOLE 3 ANTE SF PVC - SALAMANDER',
'scorrevole_sf_4_ante' => 'SCORREVOLE 4 ANTE SF PVC - SALAMANDER',
'scuretto_base' => 'SCURETTO BASE PVC - SALAMANDER',
'scuretto_pannello_alessandrite' => 'SCURETTO ALESSANDRITE PVC - SALAMANDER',
'scuretto_pannello_malachite' => 'SCURETTO MALACHITE PVC - SALAMANDER',
@@ -171,17 +171,6 @@ return [
'cartella_texture' => null,
'moduli' => null,
],
[
'codice' => 'scorrevole_sf_1_anta',
'nome' => 'Scorrevole SF 1 Anta',
'descrizione' => null,
'attivo' => true,
'cartella' => 'scorrevole_sf_1_anta',
'file_modello' => 'scorrevole_sf_1_anta.glb',
'categoria' => 'scorrevoli',
'cartella_texture' => 'finestra_2_ante',
'moduli' => ['cerniere', 'listino', 'maniglia', 'modello', 'vetro'],
],
[
'codice' => 'scorrevole_sf_2_ante',
'nome' => 'Scorrevole SF 2 Ante',
@@ -197,23 +186,23 @@ return [
'codice' => 'scorrevole_sf_3_ante',
'nome' => 'Scorrevole SF 3 Ante',
'descrizione' => 'SCORREVOLE IN LINEA A 3 ANTE',
'attivo' => false,
'cartella' => null,
'file_modello' => null,
'attivo' => true,
'cartella' => 'scorrevole_sf_3_ante',
'file_modello' => 'scorrevole_sf_3_ante.glb',
'categoria' => 'scorrevoli',
'cartella_texture' => null,
'moduli' => null,
'cartella_texture' => 'finestra_2_ante',
'moduli' => ['cerniere', 'listino', 'maniglia', 'modello', 'vetro'],
],
[
'codice' => 'scorrevole_sf_4_ante',
'nome' => 'Scorrevole SF 4 Ante',
'descrizione' => 'SCORREVOLE IN LINEA A 4 ANTE',
'attivo' => false,
'cartella' => null,
'file_modello' => null,
'attivo' => true,
'cartella' => 'scorrevole_sf_4_ante',
'file_modello' => 'scorrevole_sf_4_ante.glb',
'categoria' => 'scorrevoli',
'cartella_texture' => null,
'moduli' => null,
'cartella_texture' => 'finestra_2_ante',
'moduli' => ['cerniere', 'listino', 'maniglia', 'modello', 'vetro'],
],
[
'codice' => 'traslante_scorrevole_1_anta',
@@ -237,17 +226,6 @@ return [
'cartella_texture' => null,
'moduli' => null,
],
[
'codice' => 'scorrevole_parallelo_1_anta',
'nome' => 'Scorrevole Parallelo 1 Anta',
'descrizione' => null,
'attivo' => true,
'cartella' => 'scorrevole_parallelo_1_anta',
'file_modello' => 'scorrevole_parallelo_1_anta.glb',
'categoria' => 'scorrevoli',
'cartella_texture' => 'finestra_2_ante',
'moduli' => ['cerniere', 'listino', 'maniglia', 'modello', 'vetro'],
],
[
'codice' => 'bilico',
'nome' => 'Bilico Orizzontale',
+19
View File
@@ -2,6 +2,7 @@
use App\Models\Preventivo;
use App\Support\PreventivoPdf;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
@@ -20,3 +21,21 @@ Route::get('/admin/preventivi/{preventivo}/pdf', function (Preventivo $preventiv
'preventivo-'.($preventivo->numero ?? $preventivo->id).'.pdf'
);
})->middleware(['auth'])->name('preventivi.pdf.show');
// Configuratore 3D e app cliente (preventivo) sono build Vite statiche dentro public/ (vedi build.sh):
// i file (js, css, assets) li serve direttamente il web server, qui gestiamo solo l'index.
// Lo slash finale è obbligatorio perché i path runtime sono relativi (./assets/...).
foreach (['configuratore', 'preventivo'] as $frontend) {
Route::get($frontend, function (Request $request) use ($frontend) {
if (! str_ends_with($request->getPathInfo(), '/')) {
$query = $request->getQueryString();
return redirect('/'.$frontend.'/'.($query ? '?'.$query : ''), 301);
}
$index = public_path($frontend.'/index.html');
abort_unless(is_file($index), 404, "Build di {$frontend} assente: eseguire build.sh.");
return response()->file($index, ['Cache-Control' => 'no-cache']);
})->name($frontend);
}
@@ -62,12 +62,9 @@ class CatalogoAdminTest extends TestCase
{
$codici = fn () => Modello::orderBy('ordine')->pluck('codice')->all();
// Ordine di OrdineSerramenti per gli attivi, poi i 4 disattivi nello stesso ordine.
// Ordine di OrdineSerramenti per gli attivi, poi i 2 disattivi nello stesso ordine.
$this->assertSame(['finestra_1_anta', 'finestra_2_ante', 'finestra_3_ante'], array_slice($codici(), 0, 3));
$this->assertSame(
['scorrevole_hst_4_ante', 'scorrevole_sf_3_ante', 'scorrevole_sf_4_ante', 'traslante_scorrevole_1_anta_f'],
array_slice($codici(), -4)
);
$this->assertSame(['scorrevole_hst_4_ante', 'traslante_scorrevole_1_anta_f'], array_slice($codici(), -2));
// Disattivando un modello passa nel gruppo in fondo (dopo i 25 attivi rimasti),
// mantenendo l'ordine relativo con gli altri disattivi; riattivandolo resta
@@ -81,7 +78,7 @@ class CatalogoAdminTest extends TestCase
$this->assertSame(['celino', 'bilico', 'scorrevole_hst_4_ante'], array_slice($codici(), 24, 3));
// In tabella i disattivi stanno in fondo anche se l'ordine salvato li metterebbe in mezzo.
Modello::where('codice', 'scorrevole_sf_3_ante')->update(['ordine' => 2]);
Modello::where('codice', 'scorrevole_hst_4_ante')->update(['ordine' => 2]);
Livewire::test(ListModelli::class)
->set('tableRecordsPerPage', 50)
->assertCanSeeTableRecords(
+4 -4
View File
@@ -26,14 +26,14 @@ class CatalogoApiTest extends TestCase
$this->seed(CatalogoSeeder::class);
$pvc = Sistema::where('codice', 'pvc')->firstOrFail();
$this->assertSame(30, $pvc->modelli()->count());
$this->assertSame(28, $pvc->modelli()->count());
$finestra = $pvc->modelli()->where('codice', 'finestra_1_anta')->firstOrFail();
$this->assertSame('FINESTRA 1 ANTA, A/R', $finestra->descrizione);
Storage::disk('public')->assertExists($finestra->icona);
// Modelli presenti solo nell'Excel: senza asset 3D, quindi disattivi.
$this->assertFalse($pvc->modelli()->where('codice', 'scorrevole_sf_3_ante')->value('attivo'));
$this->assertFalse($pvc->modelli()->where('codice', 'scorrevole_hst_4_ante')->value('attivo'));
}
public function test_il_seeder_non_sovrascrive_le_modifiche_admin(): void
@@ -44,7 +44,7 @@ class CatalogoApiTest extends TestCase
$this->seed(CatalogoSeeder::class);
$this->assertSame('Bilico modificato', Modello::where('codice', 'bilico')->value('nome'));
$this->assertSame(30, Modello::count());
$this->assertSame(28, Modello::count());
}
public function test_api_sistemi_espone_solo_modelli_attivi_e_con_listino_in_ordine(): void
@@ -63,7 +63,7 @@ class CatalogoApiTest extends TestCase
$this->assertSame(25, $modelli->count());
$this->assertNotContains('bilico', $modelli->pluck('id'));
$this->assertSame('finestra_1_anta', $modelli->first()['id']);
$this->assertNotContains('scorrevole_sf_3_ante', $modelli->pluck('id'));
$this->assertNotContains('scorrevole_hst_4_ante', $modelli->pluck('id'));
$this->assertStringEndsWith('/storage/modelli/icone/pvc/finestra_1_anta.jpg', $modelli->first()['icona']);
$this->assertSame('finestra_1_anta.glb', $modelli->first()['modello']);
}
@@ -230,6 +230,6 @@ class ImportazioneListiniTest extends TestCase
]);
// Modello disattivo (senza asset 3D): non esposto.
$this->getJson('/api/sistemi/pvc/modelli/scorrevole_sf_3_ante/listino')->assertNotFound();
$this->getJson('/api/sistemi/pvc/modelli/scorrevole_hst_4_ante/listino')->assertNotFound();
}
}
+9 -7
View File
@@ -1,11 +1,13 @@
#!/bin/bash
# Compila i due frontend dentro backoffice/public, da cui li serve Laravel:
# configuratore 3D -> backoffice/public/configuratore (URL /configuratore/)
# app cliente -> backoffice/public/preventivo (URL /preventivo/)
set -euo pipefail
if [ ! -f .env.production ]; then
echo "Errore: .env.production non trovato." >&2
echo "Copia .env.production.example in .env.production e imposta l'URL reale dell'API prima del deploy." >&2
exit 1
fi
ROOT="$(cd "$(dirname "$0")" && pwd)"
rm -rf kreios kreios.zip
npm run build && mv dist kreios && zip -r kreios.zip kreios
[ -d "$ROOT/node_modules" ] || (cd "$ROOT" && pnpm install)
[ -d "$ROOT/backoffice/cliente/node_modules" ] || (cd "$ROOT/backoffice/cliente" && pnpm install)
(cd "$ROOT" && pnpm build)
(cd "$ROOT/backoffice/cliente" && pnpm build)
+11 -10
View File
@@ -1,8 +1,9 @@
#!/bin/bash
# Avvia l'ambiente di sviluppo completo (vedi docs/AVVIO_LOCALE.md):
# - backoffice Laravel (API + admin) -> http://localhost:8000
# - app cliente preventivi (Vue) -> http://localhost:5199
# - configuratore 3D (Vite) -> http://localhost:5173
# Avvia l'ambiente di sviluppo completo (vedi docs/AVVIO_LOCALE.md). Un solo server, Laravel su
# http://localhost:8000, che serve API, admin e i due frontend compilati in backoffice/public:
# - configuratore 3D -> http://localhost:8000/configuratore/
# - app cliente (preventivo) -> http://localhost:8000/preventivo/
# I frontend sono ricompilati a ogni modifica (vite build --watch): ricaricare la pagina per vederle.
# Ctrl+C ferma tutti i servizi.
set -euo pipefail
@@ -22,7 +23,7 @@ if ! port_in_use 3306; then
exit 1
fi
for port in 8000 5199 5173; do
for port in 8000; do
if port_in_use "$port"; then
echo "Errore: la porta $port è già occupata:" >&2
lsof -nP -iTCP:"$port" -sTCP:LISTEN >&2
@@ -62,14 +63,14 @@ run() {
}
run "laravel" "$BACKOFFICE" php artisan serve --port=8000
run "cliente" "$CLIENTE" pnpm dev
run "config " "$ROOT" pnpm dev
run "cliente" "$CLIENTE" pnpm build --watch
run "config " "$ROOT" pnpm build --watch
cat <<EOF
Servizi in avvio:
Configuratore http://localhost:5173/?sistema=pvc&type=finestra_2_ante
App cliente http://localhost:5199
Servizi in avvio (attendere la prima build dei frontend):
Configuratore http://localhost:8000/configuratore/?sistema=pvc&type=finestra_2_ante
App cliente http://localhost:8000/preventivo/
API http://localhost:8000/api
Admin http://localhost:8000/admin (admin@kreiosinfissi.it / password)
+35 -60
View File
@@ -1,28 +1,26 @@
# Avvio locale — Krèios
Guida per avviare l'ambiente completo di sviluppo: **configuratore 3D**, **backoffice Laravel** (API + pannello admin) e **app cliente** per i preventivi.
Guida per avviare l'ambiente completo di sviluppo: **backoffice Laravel** (API + pannello admin) che serve anche il **configuratore 3D** e l'**app cliente** per i preventivi.
## Panoramica
| Componente | Cartella | Stack | Porta | URL dev |
|---|---|---|---|---|
| Configuratore 3D | `.` (root del repo) | Vite 6 + Three.js + jQuery | **5173** | <http://localhost:5173> |
| Backoffice (API + admin) | `backoffice/` | Laravel 13 + Filament 3 + MySQL | **8000** | <http://localhost:8000/api>, admin su <http://localhost:8000/admin> |
| App cliente preventivi | `backoffice/cliente/` | Vue 3 + Vite | **5199** | <http://localhost:5199> |
Un solo server: **Laravel su <http://localhost:8000>** serve API, pannello admin e i due frontend, che sono
progetti Vite compilati dentro `backoffice/public`.
Le tre porte sono **fisse** (`strictPort: true` nei vari `vite.config.js` / `serve --port`): se una porta è già occupata l'avvio fallisce con un errore esplicito invece di spostarsi silenziosamente su un'altra.
| Componente | Sorgenti | Stack | URL dev |
|---|---|---|---|
| Configuratore 3D | `.` (root del repo) | Vite 6 + Three.js + jQuery | <http://localhost:8000/configuratore/?sistema=pvc&type=finestra_2_ante> |
| App cliente (preventivo) | `backoffice/cliente/` | Vue 3 + Vite | <http://localhost:8000/preventivo/> |
| Backoffice (API + admin) | `backoffice/` | Laravel 13 + Filament 3 + MySQL | <http://localhost:8000/api>, admin su <http://localhost:8000/admin> |
**Come si collegano:**
- Il configuratore chiama l'API Laravel per il carrello (`POST /api/carrello/aggiungi`, ecc.).
- L'URL dell'app cliente **non è hardcoded** nel configuratore: all'avvio chiama `GET /api/config` e usa il `frontend_cliente_url` restituito da Laravel (letto da `APP_FRONTEND_CLIENTE_URL` nel `.env` del backoffice).
- Il flusso completo è descritto in [PREVENTIVI_BACKOFFICE.md](PREVENTIVI_BACKOFFICE.md).
**Come si collegano:** tutto è sulla stessa origine, quindi configuratore e app cliente chiamano `/api/...`
senza configurazione né CORS. Architettura e variabili in [PREVENTIVI_BACKOFFICE.md](PREVENTIVI_BACKOFFICE.md).
## Requisiti
| Strumento | Versione minima | Note |
|---|---|---|
| Node.js | 20+ | per i tre front (Vite) |
| Node.js | 20+ | per compilare i frontend (Vite) |
| pnpm | 8+ | package manager della root e dell'app cliente (`pnpm-lock.yaml` è il lockfile di riferimento) — `npm i -g pnpm` o `corepack enable` |
| PHP | 8.3+ | richiesta da `composer.json` (`"php": "^8.3"`) |
| Composer | 2+ | dipendenze Laravel |
@@ -64,15 +62,13 @@ DB_USERNAME=root
DB_PASSWORD=
APP_URL=http://localhost:8000
APP_FRONTEND_CLIENTE_URL=http://localhost:5199
ADMIN_NOTIFY_EMAIL=tua-email@example.com
MAIL_MAILER=log
```
| Variabile | Descrizione |
|---|---|
| `APP_URL` | Base URL del backoffice |
| `APP_FRONTEND_CLIENTE_URL` | URL dell'app cliente — restituito al configuratore da `GET /api/config` |
| `APP_URL` | Base URL del backoffice (e quindi di `/configuratore/` e `/preventivo/`) |
| `ADMIN_NOTIFY_EMAIL` | Email che riceve la notifica di nuovo preventivo |
| `CONFIGURATOR_SESSION_DAYS` | Durata (giorni) della sessione di login del configuratore |
| `MAIL_MAILER` | `log` in dev: le email finiscono in `storage/logs/laravel.log` (incluso il PDF allegato) |
@@ -98,71 +94,50 @@ php artisan serve --port=8000
> Alternativa: `composer dev` avvia in parallelo server, queue, log (`pail`) e Vite.
## 2) App cliente (Vue) — porta 5199
## 2) Frontend (configuratore + app cliente)
```bash
cd backoffice/cliente
pnpm install
pnpm dev
pnpm install # root: configuratore
(cd backoffice/cliente && pnpm install) # app cliente
./build.sh # compila entrambi in backoffice/public/{configuratore,preventivo}
```
`.env` già presente in repo con il default corretto per lo sviluppo: `VITE_API_BASE=http://localhost:8000/api`.
## 3) Configuratore 3D — porta 5173
```bash
pnpm install
pnpm dev
```
- Il server parte su `0.0.0.0:5173` (vedi `vite.config.js`).
- L'entry point **richiede due parametri obbligatori** nella query string:
```
http://localhost:5173/?sistema=pvc&type=finestra_2_ante
```
Dopo la build, con il solo `php artisan serve` attivo:
- configuratore: <http://localhost:8000/configuratore/?sistema=pvc&type=finestra_2_ante>
- `sistema`: `pvc` | `alluminio` | `legno` | `acciaio`
- `type`: id modello valido per quel sistema (elenco completo in [URL_PARAMS.md](URL_PARAMS.md))
- `type`: id modello valido per quel sistema (elenco in [URL_PARAMS.md](URL_PARAMS.md))
- app cliente: <http://localhost:8000/preventivo/>
Senza i due parametri l'app lancia un errore in fase di avvio.
- In produzione il configuratore gira dentro un `<iframe>` su `kreiosinfissi.it`; in dev si apre direttamente in una tab.
In produzione il configuratore gira dentro un `<iframe>` su `kreiosinfissi.it`; in dev si apre direttamente in una tab.
## Sequenza completa (tre terminali)
## Sviluppo quotidiano: `./dev.sh`
```bash
# Terminale 1 — backoffice Laravel (API + admin)
cd backoffice && php artisan serve --port=8000
# Terminale 2 — app cliente
cd backoffice/cliente && pnpm dev
# Terminale 3 — configuratore (dalla root del repo)
pnpm dev
./dev.sh
```
Avvia Laravel sulla porta 8000 e i due frontend in `vite build --watch`: a ogni modifica dei sorgenti la build
in `backoffice/public` viene rigenerata, basta ricaricare la pagina (non c'è hot reload). Ctrl+C ferma tutto.
## Verifica end-to-end
1. Apri <http://localhost:5173/?sistema=pvc&type=finestra_2_ante> — il modello 3D si carica e il menu è attivo.
1. Apri <http://localhost:8000/configuratore/?sistema=pvc&type=finestra_2_ante> — il modello 3D si carica e il menu è attivo.
2. Configura dimensioni/finiture e clicca **"Aggiungi al preventivo"** → `POST /api/carrello/aggiungi`; il token del carrello viene salvato in `localStorage`.
3. Clicca **"Vai al preventivo"** → si apre <http://localhost:5199/?token=XXX> con il riepilogo del carrello.
3. Clicca **"Vai al preventivo"** → si apre <http://localhost:8000/preventivo/?token=XXX> con il riepilogo del carrello.
4. Compila i dati del cliente e **conferma**.
5. Controlla il risultato:
- il preventivo compare in <http://localhost:8000/admin> con stato `inviato`;
- le email (cliente con PDF allegato + notifica admin) sono in `backoffice/storage/logs/laravel.log`.
## Build di produzione (configuratore)
## Build di produzione
```bash
# 1. Creare .env.production (non tracciato in git) con l'URL API reale
cp .env.production.example .env.production
# -> VITE_PREVENTIVI_API_BASE=https://preventivi.kreiosinfissi.it/api
# 2. Build + zip (build.sh fallisce se .env.production manca)
./build.sh # pnpm build -> dist/ -> rinominata in kreios/ + kreios.zip
./build.sh # configuratore -> backoffice/public/configuratore, app cliente -> backoffice/public/preventivo
```
`pnpm preview` serve localmente la build di produzione per un'anteprima.
Le due cartelle sono ignorate da git e vanno distribuite insieme al backend. Nessun `.env` dei frontend è
necessario: l'API è sulla stessa origine. Checklist completa in [PREVENTIVI_BACKOFFICE.md](PREVENTIVI_BACKOFFICE.md).
## Rigenerare i listini PVC (opzionale)
@@ -183,9 +158,9 @@ Nota: ogni nuovo titolo presente nel foglio ma non ancora mappato va aggiunto al
| Sintomo | Causa probabile / soluzione |
|---|---|
| `Port 5173/5199 is already in use` o errore di porta su 8000 | Le porte sono fisse: individuare e fermare l'altro processo (`lsof -i :5173`) |
| "Aggiungi al preventivo" dà errore / richieste CORS rifiutate | Laravel non è in ascolto su `8000`, oppure `VITE_PREVENTIVI_API_BASE` nel `.env` della root non punta a `http://localhost:8000/api` |
| "Vai al preventivo" apre la URL sbagliata | Controllare `APP_FRONTEND_CLIENTE_URL` nel `.env` del backoffice: il configuratore legge l'URL dell'app cliente da `GET /api/config` |
| `/configuratore/` o `/preventivo/` rispondono 404 "Build … assente" | Eseguire `./build.sh` (o avviare `./dev.sh` e attendere la prima build) |
| Modifiche ai frontend non visibili | Con `./dev.sh` attendere la fine della ricompilazione e ricaricare; senza, rieseguire `./build.sh` |
| "Vai al preventivo" apre la URL sbagliata | Controllare `APP_URL` (ed eventuale `APP_FRONTEND_CLIENTE_URL`) nel `.env` del backoffice: il link viene da `GET /api/config` |
| `SQLSTATE[HY000] [2002] Connection refused` | MySQL non è in esecuzione, oppure credenziali/host errati nel `.env` del backoffice |
| `SQLSTATE[HY000] [1049] Unknown database 'kreios_preventivi'` | Creare il database (vedi sezione 1) |
| Login admin rifiutato | Eseguire `php artisan migrate --seed` e usare `admin@kreiosinfissi.it` / `password` |
-2
View File
@@ -47,8 +47,6 @@ listino reale per prodotto.
| `scuretto_pannello_alessandrite` | scuretti |
| `scorrevole_hst_1_anta` | scorrevoli |
| `scorrevole_hst_2_ante` | scorrevoli |
| `scorrevole_parallelo_1_anta` | scorrevoli |
| `scorrevole_sf_1_anta` | scorrevoli |
| `scorrevole_sf_2_ante` | scorrevoli |
| `traslante_scorrevole_1_anta` | scorrevoli |
+71 -132
View File
@@ -1,162 +1,101 @@
# Backoffice Preventivi — Architettura, ambienti e deploy
Questo documento descrive il sistema di preventivi collegato al configuratore: come sono collegati i tre
componenti, come gestiscono le porte/URL in sviluppo, e cosa serve per portarlo in produzione.
Questo documento descrive il sistema di preventivi collegato al configuratore: come sono collegati i
componenti, come vengono serviti e cosa serve per portarlo in produzione.
Per il modello dati, l'API e il flusso applicativo dettagliato vedi [`backoffice/README.md`](../backoffice/README.md).
Per il contratto dei parametri URL del configuratore vedi [`URL_PARAMS.md`](./URL_PARAMS.md).
## Architettura: tre componenti separati
## Architettura: un solo server
```
┌─────────────────────┐ POST /api/carrello/aggiungi ┌──────────────────────┐
│ Configuratore 3D │ ───────────────────────────────────────▶ │ Backend Laravel │
│ (root del repo, │ │ (backoffice/) │
│ Three.js + Vite) │ ◀─────────────────────────────────────── │ API + Filament │
└──────────┬───────────┘ GET /api/config (frontend url) └──────────┬────────────┘
│ │
│ link "Vai al preventivo" │ GET/DELETE/POST
│ https://<app-cliente>/?token=XXX │ /api/carrello/...
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ App cliente Vue │ ───────────────────────────────────────▶ │ (stesso backend) │
│ (backoffice/cliente)│ carrello + checkout │ │
└──────────────────────┘ └──────────────────────┘
Backend Laravel (backoffice/) — unica origine
┌──────────────────────────────────────────────────────────────────────────────────┐
│ /configuratore/ build statica del configuratore 3D (root repo, Vite+Three.js) │
│ /preventivo/ build statica dell'app cliente (backoffice/cliente, Vue 3) │
│ /api/... API catalogo, prezzi, carrello, auth │
│ /admin pannello Filament │
└──────────────────────────────────────────────────────────────────────────────────┘
```
Sono **tre processi indipendenti**, ognuno con il proprio server di sviluppo/deploy:
| Componente | Cartella | Tecnologia | Ruolo |
| Componente | Sorgenti | Build in | URL |
|---|---|---|---|
| Configuratore 3D | `/` (root repo) | Vite + Three.js | UI di configurazione prodotto, genera lo screenshot e invia il primo prodotto al carrello |
| Backend | `backoffice/` | Laravel 13 + Filament 3 | API carrello/preventivi, PDF, email, pannello admin |
| App cliente | `backoffice/cliente/` | Vue 3 + Vite | Riepilogo carrello, form contatti, conferma preventivo |
| Configuratore 3D | `/` (root repo, `src/`) | `backoffice/public/configuratore/` | `/configuratore/?sistema=…&type=…` |
| App cliente (preventivo) | `backoffice/cliente/` | `backoffice/public/preventivo/` | `/preventivo/?token=…` |
| Backend (API + admin) | `backoffice/` | — | `/api`, `/admin` |
Il configuratore e l'app cliente **non si parlano mai direttamente**: comunicano solo passando dal backend
Laravel (il configuratore scrive nel carrello via API, l'app cliente lo legge via API). Il collegamento tra
i due frontend è un singolo `token` passato in query string nel link "Vai al preventivo".
I due frontend restano progetti Vite separati, ma vengono compilati dentro `backoffice/public` e serviti da
Laravel: un solo dominio, un solo processo, nessun CORS tra frontend e API. Il configuratore e l'app cliente
non si parlano direttamente: il configuratore scrive nel carrello via API, l'app cliente lo legge via API;
il collegamento tra i due è il `token` passato in query string nel link "Vai al preventivo".
## Il problema che questo documento previene
### Come vengono serviti
I tre servizi girano su porte/domini diversi. Storicamente questo ha causato due classi di bug:
- I file statici (`/configuratore/assets/…`, `/preventivo/assets/…`) li serve direttamente il web server
(o `php artisan serve`), come qualsiasi file in `public/`.
- L'`index.html` di ciascun frontend è servito dalle route `configuratore` e `preventivo` in
[`backoffice/routes/web.php`](../backoffice/routes/web.php) (`Cache-Control: no-cache`, così una nuova
build è visibile subito; gli asset hanno nomi con hash). Se la build manca rispondono 404.
- Il configuratore usa path runtime relativi (`./assets/…`), quindi l'URL deve finire con `/`: Apache/nginx
reindirizzano `/configuratore` → `/configuratore/`, e uno script inline in `src/index.html` aggiunge lo
slash anche quando il server non lo fa (es. `php artisan serve`).
1. **Porte di sviluppo che collidono silenziosamente** — Vite, se una porta è occupata, ne sceglie
un'altra automaticamente. Se due progetti hanno lo stesso default (era il caso: sia il configuratore
che l'app cliente avevano `port: 5174`), il secondo processo avviato scivola su un'altra porta senza
errori — e i link generati puntano alla porta sbagliata.
2. **URL di produzione indovinati o hardcoded** — se l'indirizzo di un servizio è scritto a mano nel
codice dell'altro, ogni cambio di dominio/porta richiede una modifica e un rebuild manuale, facile da
dimenticare.
### URL dell'API e dell'app cliente
Soluzione adottata: **porte fisse in sviluppo** (`strictPort: true`, fallisce rumorosamente invece di
scivolare) + **scoperta dinamica degli URL** dove possibile, invece di hardcoding.
- **Configuratore → API**: di default `${location.origin}/api` ([`src/js/api_base.js`](../src/js/api_base.js)).
Override possibili: `VITE_PREVENTIVI_API_BASE` a build time (`.env*` nella root del repo) o
`window.PREVENTIVI_API_BASE` a runtime — servono solo se l'API sta su un altro dominio.
- **App cliente → API**: di default `${location.origin}/api` ([`backoffice/cliente/src/api.js`](../backoffice/cliente/src/api.js)),
override con `VITE_API_BASE`.
- **Configuratore → app cliente**: il link "Vai al preventivo" usa `frontend_cliente_url` restituito da
`GET /api/config` ([`backoffice/config/preventivi.php`](../backoffice/config/preventivi.php)), che di default
vale `APP_URL/preventivo`. Fallback se l'API non risponde: `${location.origin}/preventivo`.
## Porte di sviluppo (fisse)
## Sviluppo
| Servizio | Porta | Comando | Config |
|---|---|---|---|
| Configuratore | `5173` | `pnpm dev` (root) | [`vite.config.js`](../vite.config.js) — `server.port` + `strictPort: true` |
| App cliente | `5199` | `pnpm dev` (in `backoffice/cliente/`) | [`backoffice/cliente/vite.config.js`](../backoffice/cliente/vite.config.js) |
| Backend Laravel | `8000` | `php artisan serve --port=8000` (in `backoffice/`) | nessuna config fissa, va specificata al comando |
`./dev.sh` dalla root avvia `php artisan serve --port=8000` e i due `vite build --watch`: ogni modifica ai
sorgenti ricompila il frontend in `backoffice/public/…`; basta ricaricare la pagina (non c'è HMR).
Dettagli in [AVVIO_LOCALE.md](AVVIO_LOCALE.md).
Con `strictPort: true`, se una porta è già occupata **l'avvio fallisce con un errore esplicito** invece di
spostarsi su un'altra porta a caso. Se capita, il fix è liberare la porta (o capire quale altro processo
la sta usando) — non serve più inseguire link rotti.
## Come i due frontend si trovano l'un l'altro
### App cliente → Backend
L'app cliente conosce l'indirizzo dell'API tramite `VITE_API_BASE` in
[`backoffice/cliente/.env`](../backoffice/cliente/.env) (default `http://localhost:8000/api`). È un valore
di build-time (Vite), va impostato per ambiente.
### Configuratore → Backend
Il configuratore conosce l'indirizzo dell'API tramite `VITE_PREVENTIVI_API_BASE`, letto a build-time da
`.env` / `.env.production` nella **root del repo** (vedi sezione successiva). È l'unico URL che deve essere
"noto a priori": da lì in poi il configuratore può fare domande al backend.
### Configuratore → App cliente (il pezzo dinamico)
Il configuratore **non conosce a priori** dove gira l'app cliente. All'avvio chiama:
```
GET {VITE_PREVENTIVI_API_BASE}/api/config
→ { "frontend_cliente_url": "http://localhost:5199" }
```
Il valore restituito viene letto da Laravel dalla variabile `APP_FRONTEND_CLIENTE_URL` nel suo `.env`
([`backoffice/config/preventivi.php`](../backoffice/config/preventivi.php)). Il link "Vai al preventivo"
viene costruito con questo valore, non con un default fisso nel bundle del configuratore.
**Implicazione pratica**: se l'app cliente cambia dominio/porta, basta aggiornare **una sola variabile**
lato Laravel (`APP_FRONTEND_CLIENTE_URL`) — il configuratore già in produzione la recepisce alla prossima
richiesta, senza rebuild.
Logica in [`src/js/starter.js`](../src/js/starter.js) (`configReady`, `mostraLinkPreventivo`,
`ripristinaLinkPreventivo`). Se il backend non è raggiungibile, resta un fallback locale
(`window.APP_CLIENTE_URL` se impostato, altrimenti `http://localhost:5199`) — solo per non rompere lo
sviluppo offline, non pensato per la produzione.
## Variabili d'ambiente per componente
### Configuratore (root del repo)
Attenzione: `vite.config.js` ha `root: 'src'` ma `envDir` è impostato esplicitamente sulla root del repo
(dove stanno anche `build.sh`/`deploy.sh`) — i file `.env*` vanno quindi creati **nella root del repo**, non
dentro `src/`.
| File | Tracciato in git? | Contenuto |
|---|---|---|
| `.env` | Sì | `VITE_PREVENTIVI_API_BASE=http://localhost:8000/api` — default di sviluppo |
| `.env.production` | **No** (in `.gitignore`) | `VITE_PREVENTIVI_API_BASE=<url reale dell'API in produzione>` — va creato a mano prima del deploy |
| `.env.production.example` | Sì | Traccia/promemoria del formato atteso, da copiare in `.env.production` |
`pnpm build` (production mode di Vite) usa `.env.production` se presente. **`build.sh` blocca la build se
`.env.production` manca**, per evitare di deployare per sbaglio con `localhost` incorporato nel bundle.
Override runtime senza rebuild (usato raramente, es. hotfix): impostare `window.PREVENTIVI_API_BASE` /
`window.APP_CLIENTE_URL` in uno script inline prima del bundle in `index.html` — ha priorità sul valore di
build.
## Variabili d'ambiente
### Backend Laravel (`backoffice/.env`)
| Variabile | Descrizione | Dev | Produzione |
|---|---|---|---|
| `APP_URL` | URL pubblico del backend stesso (usato per link nelle email e asset Filament) | `http://localhost:8000` | URL reale del backend |
| `APP_FRONTEND_CLIENTE_URL` | URL dell'app cliente — letto da `/api/config` | `http://localhost:5199` | URL reale dell'app cliente |
| `ADMIN_NOTIFY_EMAIL` | Email che riceve la notifica di nuovo preventivo | — | email reale dell'admin |
| `CORS_ALLOWED_ORIGINS` | Origini autorizzate a chiamare l'API | `*` (di default, comodo in dev) | **da restringere** alle origini reali di configuratore + app cliente, separate da virgola |
| `DB_*` | MySQL: `127.0.0.1` / `root` / nessuna password / `kreios_preventivi` in locale | — | credenziali del DB di produzione |
| `MAIL_*` | `MAIL_MAILER=log` in dev (le email finiscono in `storage/logs/laravel.log`) | — | SMTP reale |
| Variabile | Descrizione | Default |
|---|---|---|
| `APP_URL` | URL pubblico del backend: base per link nelle email, `/preventivo` e `/configuratore/` | — |
| `APP_FRONTEND_CLIENTE_URL` | Solo se l'app cliente **non** è servita dal backend | `APP_URL/preventivo` |
| `APP_CONFIGURATORE_URL` | Link "Configuratore" nel menu admin, solo se servito altrove | `APP_URL/configuratore/` |
| `ADMIN_NOTIFY_EMAIL` | Email che riceve la notifica di nuovo preventivo | — |
| `CORS_ALLOWED_ORIGINS` | Origini autorizzate all'API da domini **esterni** (i frontend serviti dal backend non ne hanno bisogno) | `*` |
| `DB_*`, `MAIL_*` | Database e posta | — |
### App cliente (`backoffice/cliente/.env`)
### Frontend
| Variabile | Descrizione | Dev | Produzione |
|---|---|---|---|
| `VITE_API_BASE` | URL base dell'API Laravel | `http://localhost:8000/api` | URL reale dell'API |
Nessuna variabile necessaria. `VITE_PREVENTIVI_API_BASE` (root) e `VITE_API_BASE` (`backoffice/cliente`)
restano disponibili solo per puntare a un'API su un dominio diverso.
## Checklist di deploy in produzione
1. **Backend Laravel** (`backoffice/`)
- `.env` di produzione: `APP_URL`, `APP_FRONTEND_CLIENTE_URL`, `DB_*`, `MAIL_*`, `ADMIN_NOTIFY_EMAIL`.
- Restringere `CORS_ALLOWED_ORIGINS` alle origini reali (non lasciare `*`).
- `php artisan migrate --force`, `php artisan storage:link`, `php artisan config:cache`.
2. **App cliente** (`backoffice/cliente/`)
- `cliente/.env` (o `.env.production`) con `VITE_API_BASE` reale.
- `pnpm build`, deploy dei file statici generati.
3. **Configuratore** (root del repo)
- `cp .env.production.example .env.production` e impostare `VITE_PREVENTIVI_API_BASE` reale.
- `./build.sh` (fallisce se `.env.production` manca) → `./deploy.sh`.
- **Non serve** impostare l'URL dell'app cliente qui: viene letto da `/api/config` a runtime.
4. Verifica end-to-end: configurare un prodotto → "Aggiungi al preventivo" → il link "Vai al preventivo"
deve puntare al dominio reale dell'app cliente → completare il checkout → verificare che il preventivo
compaia nel backoffice Filament e che le email (cliente + admin) siano state inviate.
1. `./build.sh` dalla root: compila configuratore e app cliente in `backoffice/public/configuratore` e
`backoffice/public/preventivo` (cartelle ignorate da git: vanno generate in fase di deploy o caricate
insieme al backend).
2. Backend: `.env` di produzione (`APP_URL`, `DB_*`, `MAIL_*`, `ADMIN_NOTIFY_EMAIL`), poi
`php artisan migrate --force`, `php artisan storage:link`, `php artisan config:cache`.
3. Nel sito `kreiosinfissi.it` l'iframe punta a `https://<backend>/configuratore/?sistema=…&type=…`.
4. Restringere `CORS_ALLOWED_ORIGINS` (o lasciarlo vuoto) se nessun dominio esterno usa l'API.
5. Verifica end-to-end: configurare un prodotto → "Aggiungi al preventivo" → "Vai al preventivo" apre
`/preventivo/` sullo stesso dominio → completare il checkout → il preventivo compare in Filament e le
email (cliente + admin) sono inviate.
`deploy.sh` (upload di `kreios.zip` su un host statico separato) appartiene al vecchio schema a server
separati e non serve più.
## Riferimenti file
- [`src/js/starter.js`](../src/js/starter.js) — integrazione configuratore↔backend, scoperta dinamica dell'URL app cliente.
- [`vite.config.js`](../vite.config.js) — porta fissa, `envDir`.
- [`backoffice/routes/api.php`](../backoffice/routes/api.php) — endpoint `/api/config` e `/api/carrello/*`.
- [`backoffice/config/preventivi.php`](../backoffice/config/preventivi.php) — sorgente di `frontend_cliente_url`.
- [`backoffice/config/cors.php`](../backoffice/config/cors.php) — origini autorizzate.
- [`build.sh`](../build.sh) — guardia su `.env.production`.
- [`backoffice/README.md`](../backoffice/README.md) — setup locale, modello dati, API, branding.
- [`backoffice/routes/web.php`](../backoffice/routes/web.php) — route `/configuratore/` e `/preventivo/`.
- [`vite.config.js`](../vite.config.js), [`backoffice/cliente/vite.config.js`](../backoffice/cliente/vite.config.js) — `base` e `outDir` dentro `backoffice/public`.
- [`src/js/api_base.js`](../src/js/api_base.js), [`backoffice/cliente/src/api.js`](../backoffice/cliente/src/api.js) — base URL dell'API.
- [`src/js/starter.js`](../src/js/starter.js) — integrazione configuratore↔backend, link all'app cliente.
- [`backoffice/config/preventivi.php`](../backoffice/config/preventivi.php) — `frontend_cliente_url`, `configuratore_url`.
- [`build.sh`](../build.sh), [`dev.sh`](../dev.sh) — build e ambiente di sviluppo.
-32
View File
@@ -236,22 +236,6 @@
"vetro"
]
},
{
"id": "scorrevole_sf_1_anta",
"nome": "Scorrevole SF 1 Anta",
"cartella": "scorrevole_sf_1_anta",
"modello": "scorrevole_sf_1_anta.glb",
"categoria": "scorrevoli",
"enabled": true,
"cartella_texture": "finestra_2_ante",
"moduli": [
"cerniere",
"listino",
"maniglia",
"modello",
"vetro"
]
},
{
"id": "scorrevole_sf_2_ante",
"nome": "Scorrevole SF 2 Ante",
@@ -284,22 +268,6 @@
"vetro"
]
},
{
"id": "scorrevole_parallelo_1_anta",
"nome": "Scorrevole Parallelo 1 Anta",
"cartella": "scorrevole_parallelo_1_anta",
"modello": "scorrevole_parallelo_1_anta.glb",
"categoria": "scorrevoli",
"enabled": true,
"cartella_texture": "finestra_2_ante",
"moduli": [
"cerniere",
"listino",
"maniglia",
"modello",
"vetro"
]
},
{
"id": "bilico",
"nome": "Bilico Orizzontale",
Binary file not shown.
Binary file not shown.
+6
View File
@@ -383,6 +383,12 @@ body {
width: 21%;
}
/* Contorno leggero: altrimenti le finiture chiare (es. bianco) non si distinguono dallo sfondo */
.texture-radio .option-content img {
box-sizing: border-box;
border: 1px solid #d0d0d0;
}
.option-label {
margin-top: 0px;
font-size: 11px;
+5
View File
@@ -2,6 +2,11 @@
<html lang="en">
<head>
<script>
// I path runtime sono relativi (./assets/...): servono l'URL con lo slash finale. Apache/nginx
// reindirizzano /configuratore -> /configuratore/, il server PHP di sviluppo no.
if (!location.pathname.endsWith('/')) history.replaceState(null, '', location.pathname + '/' + location.search + location.hash);
</script>
<title></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+4 -4
View File
@@ -1,7 +1,7 @@
// Base URL dell'API Laravel: impostata in build (import.meta.env.VITE_PREVENTIVI_API_BASE, da
// .env/.env.production — vedi root del repo), sovrascrivibile a runtime con window.PREVENTIVI_API_BASE
// senza dover rebuildare (es. per un hotfix). Nessuna delle due impostata → fallback dev locale.
// Base URL dell'API Laravel. Il configuratore è servito dallo stesso backend (/configuratore/),
// quindi di default l'API è sulla stessa origine. Sovrascrivibile a build time con
// import.meta.env.VITE_PREVENTIVI_API_BASE o a runtime con window.PREVENTIVI_API_BASE.
export const API_BASE =
(typeof window.PREVENTIVI_API_BASE === 'string' && window.PREVENTIVI_API_BASE) ||
import.meta.env.VITE_PREVENTIVI_API_BASE ||
'http://localhost:8000/api';
`${window.location.origin}/api`;
+15
View File
@@ -257,6 +257,21 @@ const buildListaModelli = (modelli, modello, categoria, sistema) => {
});
$('.lista-modelli').html(html);
// Porta in vista il modello selezionato: le icone si caricano in modo asincrono e ne cambiano la
// larghezza, quindi si ricalcola anche a ogni load (solo scroll orizzontale, niente scrollIntoView
// che scorrerebbe anche la pagina/iframe).
const scrollToSelected = () => {
document.querySelectorAll('.lista-modelli').forEach(lista => {
const sel = lista.querySelector('img.selected');
if (!sel) return;
const item = sel.closest('.btn-categoria');
const delta = item.getBoundingClientRect().left - lista.getBoundingClientRect().left;
lista.scrollLeft += delta - (lista.clientWidth - item.offsetWidth) / 2;
});
};
scrollToSelected();
$('.lista-modelli img').one('load', scrollToSelected);
}
const buildSelect = (options, parent) => {
+4 -5
View File
@@ -25,12 +25,11 @@ window.GLTFLoader = GLTFLoader; // Used by APP Scripts.
// --- Integrazione backend preventivi ---
const CART_TOKEN_KEY = 'kreios_cart_token';
// URL dell'app cliente (checkout): richiesto all'API invece di essere indovinato, perché la
// porta Vite dell'app cliente può cambiare (es. se 5199 è già occupata). Fallback locale se
// il backend non è raggiungibile.
// URL dell'app cliente (checkout): richiesto all'API (APP_FRONTEND_CLIENTE_URL). Fallback: la pagina
// /preventivo servita dallo stesso backend del configuratore.
let appClienteUrl =
(typeof window.APP_CLIENTE_URL === 'string' && window.APP_CLIENTE_URL) ||
'http://localhost:5199';
`${window.location.origin}/preventivo`;
const configReady = (async () => {
try {
@@ -66,7 +65,7 @@ window.mostraLinkPreventivo = (token, count) => {
// diverse in dev/prod): lo passiamo qui come parametro, così il link "torna al configuratore"
// nella pagina di conferma può essere generato dinamicamente invece di essere hardcoded.
const ritorno = encodeURIComponent(window.location.href);
let href = `${appClienteUrl}/?token=${encodeURIComponent(token)}&ritorno=${ritorno}`;
let href = `${appClienteUrl.replace(/\/+$/, '')}/?token=${encodeURIComponent(token)}&ritorno=${ritorno}`;
// Se il cliente è loggato nel configuratore, passiamo il suo token anche al checkout
// così può precompilare l'anagrafica già salvata (vedi AuthGate.getToken()).
const clienteToken = AuthGate.getToken();
+4 -7
View File
@@ -4,17 +4,14 @@ import { viteStaticCopy } from 'vite-plugin-static-copy';
import { ViteMinifyPlugin } from 'vite-plugin-minify'
export default defineConfig({
server: {
host: '0.0.0.0',
port: 5173,
strictPort: true,
},
base: '',
// Il configuratore è servito da Laravel sotto /configuratore/ (stessa origine dell'API):
// la build finisce direttamente in backoffice/public/configuratore.
base: '/configuratore/',
root: 'src',
// root è 'src', ma i file .env/.env.production vivono nella root del repo (accanto a build.sh).
envDir: path.resolve(__dirname),
build: {
outDir: '../dist',
outDir: path.resolve(__dirname, 'backoffice/public/configuratore'),
minify: true,
cssMinify: true,