feat: deliver auditable Smart Pakke quote flow and free site geometry (#31)
CI - Test & Build / Lint & Type Check (push) Canceled after 0s
CI - Test & Build / Backend Unit Tests (push) Canceled after 0s
CI - Test & Build / Frontend Build (push) Canceled after 0s
CI - Test & Build / Security Scan (push) Canceled after 0s
CI - Test & Build / E2E Tests (Playwright) (push) Canceled after 0s
CI - Test & Build / CI Summary (push) Canceled after 0s
CI - Test & Build / Lint & Type Check (push) Canceled after 0s
CI - Test & Build / Backend Unit Tests (push) Canceled after 0s
CI - Test & Build / Frontend Build (push) Canceled after 0s
CI - Test & Build / Security Scan (push) Canceled after 0s
CI - Test & Build / E2E Tests (Playwright) (push) Canceled after 0s
CI - Test & Build / CI Summary (push) Canceled after 0s
* feat: move login credentials to a DB-backed users table with an admin management page Replaces the hardcoded AUTH_USERNAME/AUTH_PASSWORD login check with a new auth_accounts table (bcrypt-hashed passwords, admin/user roles). Adds admin-only /api/users CRUD routes and a "Brugere" admin page in the frontend for managing logins without redeploying. Removes the unused, unmounted duplicate login route in src/routes/auth.js. * docs: add architecture codemaps with diagrams for the whole system Adds codemaps/architecture.md, backend.md, frontend.md, and data.md — Mermaid-diagrammed design documentation verified against the live codebase and database rather than assumed from CLAUDE.md. Covers the unified-server.js request flow (mounted routers + ~183 inline routes), 68 backend services grouped by domain, the frontend's state-driven view-switch (no React Router in practice despite BrowserRouter being present), and the full 122-table DB schema with the auth_accounts vs unrelated users table naming trap flagged explicitly. Links added from the root README. Co-Authored-By: Claude Sonnet 5 <[email protected]> * feat: ship canonical roof quote workflow * fix: keep migration dry-run idempotent * [verified] feat: complete Smart Pakker management * [verified] fix: ignore blank task dependencies * [verified] fix: align package duplication with schema * [verified] fix: enforce Discord status limits * [verified] fix: link Smart Pakke materials safely * [verified] fix: harden material link review * [verified] feat: improve material matching * fix: scope pitch validation to roof packages * fix: support canonical snapshots on production schema * [verified] fix: hide internal package metadata from PDF * [verified] feat: deliver sales-ready customer PDF * [verified] feat: ship sales-ready PDF with AI overview * [verified] fix: authenticate project list requests * [verified] fix: refresh project-list authentication * [verified] fix: open existing project details * [verified] fix: keep roof components searchable in builder * [verified] fix: expose all Smart Package categories * [verified] fix: authenticate project creation * [verified] feat: make Smart Pakker the universal project flow * [verified] feat: preview Smart Package contents * [verified] test: keep generic release isolated from downpipe work * feat: add first-class Smart Pakke rentals * [verified] feat: add gutter and downpipe smart packages * [verified] fix: prepare six-house gutter quote flow * [verified] fix: open generic quotes without roof geometry * [verified] fix: review generic quotes with authenticated APIs * [verified] fix: calculate generic Smart Package quotes * [verified] fix: return generic calculation breakdown * feat: checkpoint generic signed snapshot validation with red-green tests * feat: complete fail-closed generic quote approval and customer PDF flow * feat: use generic signed snapshot in final review * feat: redesign generic quote final review * fix: harden generic review summaries * feat: add auditable six-house package basis * [verified] feat: finish auditable Smart Pakke UI * [verified] fix: bind auditable quantity and price bases * [verified] fix: keep six-house basis across package versions * [verified] fix: complete smart package discovery management * [verified] fix: simplify composition and generic scope * [verified] test: keep explicit roof contracts fail closed * [verified] fix: harden generic quote snapshots * fix: make generic quote delivery customer safe * [verified] fix: secure package catalog reads * [verified] fix: close workspace provenance blockers * fix: harden customer document language boundary * [verified] fix: secure smart package internal reads * fix: version package child mutations atomically * feat: add generic customer quote text flow * [verified] fix: allow manual customer numbers * [verified] fix: expose optional roof geometry * [verified] fix: rebase hydrated packages after geometry edits * [verified] feat: add free editable site area map * [verified] fix: harden map recovery and geocoding gate * fix: bind map quantities to authoritative geometry * fix: release geocoder lock before dispatch * fix: separate roof and site geometry provenance * fix: revoke stale admin authorization * fix: migrate task geometry basis * fix: make backend CI dependency-complete * ci: seed isolated e2e login account * fix: allow clean database bootstrap * fix: skip indexes for optional tables * test: use canonical mansard geometry in e2e * [verified] fix(auth): enforce live operator boundary * fix: fail close Ordrestyring offer transport * fix(frontend): authenticate customer project requests * fix: align canonical roof type contract * [verified] fix: reconcile legacy package labor safely * [verified] fix: audit site geometry deletion * docs: add PR 31 reviewer guide * docs: synchronize Obsidian vault * docs: sync integrated reviewer guide to Obsidian * ci: seed isolated auth account explicitly * fix: close offer bootstrap and service readiness gaps * fix: authenticate protected package callers * fix: provision initial admin and disable generic send * [verified] fix: close final quote release blockers * [verified] fix: seed gutter packages before deployment --------- Co-authored-by: alexpolo1 <[email protected]> Co-authored-by: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
alexpolo1
parent
8b16d35287
commit
f37adae2cb
@@ -0,0 +1,51 @@
|
||||
<!-- generated: 2026-09-04 -->
|
||||
# Tilbudgivern — System Architecture
|
||||
|
||||
Tilbudgivern is an AI-assisted quote calculator for the Danish carpentry trade (tømrerfaget). One Node/Express process serves the API and the built React frontend; there is no separate frontend host and no microservices.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Browser["Browser (React SPA)"]
|
||||
|
||||
subgraph Host["This machine — also the production server"]
|
||||
PM2["PM2 process: tilbudgivern-unified"]
|
||||
Server["backend/unified-server.js\nExpress on :4032"]
|
||||
Static["frontend/build/\n(static React bundle)"]
|
||||
Socket["Socket.IO\n(WebSocket, real-time updates)"]
|
||||
DB[("MariaDB\ndatabase: tilbudgivern")]
|
||||
|
||||
PM2 --> Server
|
||||
Server -- serves --> Static
|
||||
Server --- Socket
|
||||
Server --> DB
|
||||
end
|
||||
|
||||
OpenAI["OpenAI GPT-4 API\n(quote generation, budget-capped)"]
|
||||
Ordrestyring["Ordrestyring GraphQL API\ncustomers, cases, offers, calendar, hours"]
|
||||
Stark["Stark CSV catalog import"]
|
||||
Bygma["Bygma price book / install manuals"]
|
||||
|
||||
Browser <-- "HTTPS + WebSocket" --> Server
|
||||
Server -- REST/HTTPS --> OpenAI
|
||||
Server -- GraphQL --> Ordrestyring
|
||||
Server -- CSV upload --> Stark
|
||||
Server -- scrape/import --> Bygma
|
||||
```
|
||||
|
||||
## Why one process
|
||||
|
||||
`backend/unified-server.js` is a single ~400K-line Express app: it mounts modular routers from `backend/src/routes/*.js` and `backend/routes/*.js`, but also defines roughly 180 routes directly inline in the file itself (see [backend.md](./backend.md)). This is a real characteristic of the codebase, not a diagram simplification — treat the file as the de facto entry point and router table combined.
|
||||
|
||||
## Deployment model
|
||||
|
||||
- **This host is production.** There is no separate deploy target and no CI/CD pipeline that ships on merge.
|
||||
- Process manager: **PM2**, process name `tilbudgivern-unified`.
|
||||
- Deploying = pulling `main`, rebuilding the frontend into `frontend/build/`, `npm ci` in `backend/`, then `pm2 restart tilbudgivern-unified`. See the root `CLAUDE.md` "Deployment" section for the exact command sequence and health-check curls.
|
||||
- Daily DB backups via cron (02:00, 7-day retention).
|
||||
- A GitHub Actions SSH/rsync deploy workflow was removed — it targeted secrets that were never configured and every run of it failed historically. If a real second deploy target appears later, that workflow's structure (versioned release dirs, atomic symlink swap, pre-deploy backup, smoke tests) is a reasonable starting point, per `CLAUDE.md`.
|
||||
|
||||
## Companion documents
|
||||
|
||||
- [backend.md](./backend.md) — routes, services, request flow, auth
|
||||
- [frontend.md](./frontend.md) — component structure, view-switch pattern, auth context
|
||||
- [data.md](./data.md) — database schema, table inventory, migration model
|
||||
@@ -0,0 +1,82 @@
|
||||
<!-- generated: 2026-09-04 -->
|
||||
# Backend Structure
|
||||
|
||||
Entry point: `backend/unified-server.js`. It mounts modular routers **and** defines ~180 routes inline in the same file (verified by grepping `app.get/post/put/patch/delete('/api/...')` occurrences). Both patterns are live in production — inline routes are not legacy cruft to be migrated, they're simply how a large share of this API is built.
|
||||
|
||||
## Mounted routers
|
||||
|
||||
| Mount path | File | Purpose |
|
||||
|---|---|---|
|
||||
| `/api/enhanced` | `src/routes/enhancedFeatures.js` | Roof-type package search, add package to project (loaded in a `try/catch` at startup, not top-level) |
|
||||
| `/api/smart-packages` | `src/routes/smartPackagesRoutes.js` | Smart Package CRUD, management view, recalculation (also try/catch-loaded) |
|
||||
| `/api/stark` | `src/routes/starkImport.js` | Stark CSV catalog upload + import history/status (also try/catch-loaded) |
|
||||
| `/api/customer-projects` | `src/routes/customerProjects.js` | Customer + project CRUD, the core project lifecycle |
|
||||
| `/api/ai` | `src/routes/ai.js` | AI feature flags, subscription usage, support-draft generation |
|
||||
| `/api/users` | `src/routes/users.js` | **New (2026-09-04).** Admin-only user management CRUD |
|
||||
| `/api/mobile` | `src/routes/mobileOrders.js` | Field/mobile intake against Ordrestyring order history |
|
||||
| `/api/analytics` | `src/routes/analytics.js` | KPIs, employees, customers pulled from the Ordrestyring GraphQL layer |
|
||||
| `/api/product-telemetry` | `src/routes/productTelemetry.js` | Frontend product-usage event tracking + scorecards |
|
||||
| `/api/ordrestyring` | `routes/ordrestyring.js` | Quote submission, order status/list against Ordrestyring |
|
||||
| `/api/ordrestyring/offers` | `routes/offers.js` | Offer creation in Ordrestyring |
|
||||
| `/api/calendar` | `routes/calendar.js` | Calendar read + sync |
|
||||
| `/api/ordrestyring/case` | `routes/cases.js` | Case detail / work breakdown |
|
||||
| `/api/visual-reports` | `routes/visualTestReports.js` | Visual test report browsing (admin-API-key gated) |
|
||||
| `/api/admin/logs` | `routes/adminLogs.js` | Structured log browsing (admin-API-key gated) |
|
||||
| `/api/health` | `src/routes/healthDashboard.js` | DB/server health metrics |
|
||||
| `/api/client-errors` | `routes/clientErrors.js` | Frontend error/session reporting intake |
|
||||
| `/api/support` | `src/routes/supportTickets.js` | osTicket support worklist/ticket creation |
|
||||
|
||||
Note there are **two** routes directories: `backend/src/routes/` (newer) and `backend/routes/` (older, still actively mounted — `ordrestyring.js`, `offers.js`, `calendar.js`, `cases.js`, `adminLogs.js`, `clientErrors.js`, `visualTestReports.js`). Both are live; the split is historical, not a deprecation boundary.
|
||||
|
||||
## Dead route files (unmounted — do not assume these are live)
|
||||
|
||||
These files exist in `backend/src/routes/` but are **not required anywhere** in the codebase (verified by grepping every `require('./src/routes/<name>')` call site): `bygmaPrisbog.js`, `categories.js`, `cleanup.js`, `pricing.js`, `quotes.js`, `uploads.js`, `webPrices.js`. Same situation applied to `auth.js`, which was deleted 2026-09-04 for exactly this reason — its logic had silently diverged from the real, inline `/api/auth/login` handler in `unified-server.js`. Before editing any of these seven files, confirm first whether they're actually reachable; as of this writing, none are.
|
||||
|
||||
## Inline routes in unified-server.js (representative, not exhaustive)
|
||||
|
||||
`/api/auth/login`, `/api/auth` (legacy no-op), `/api/pdf/generate`, `/api/web-prices/search`, plus ~180 more (183 `app.<verb>('/api/...')` calls total, verified 2026-09-04) covering pricing, geometry, quote generation, PDF, and Ordrestyring glue. Grep `unified-server.js` for `app\.(get|post|put|patch|delete)\('/api/` to enumerate the current full list — it changes often enough that a static list here would go stale immediately.
|
||||
|
||||
## Services (`backend/src/services/`, 68 files)
|
||||
|
||||
Grouped by domain:
|
||||
|
||||
**AI / quote generation** — `openaiService.js`, `projectAiService.js`, `aiSuggestionService.js`, `aiValidationJobService.js`, `aiFeatureFlagService.js`, `codexGenerationService.js`, `codexCliValidationService.js`, `quoteRealismService.js`, `quoteTemplateService.js`, `quoteBenchmarkService.js`, `quoteEconomicsService.js`
|
||||
|
||||
**Smart packages** — `smartPackageService.js`, `smartPackageManagementService.js`, `smartPackageDefaultsService.js`, `smartPackageIntegrityService.js`, `smartPackageWorkspaceService.js`, `smartPackageMaterialMatchService.js`, `smartPackageExcelImportService.js`, `smartPackageExcelValidationService.js`, `historicalSmartPackageSearchService.js`, `packageService.js`, `materialPackageService.js` (an older, parallel package system — see [data.md](./data.md))
|
||||
|
||||
**Material pricing / import** — `starkImportService.js`, `bygmaImportService.js`, `bygmaPrisbogImportService.js`, `bygmaScraperService.js`, `haandvaerkPriserImportService.js`, `priceImportService.js`, `pricingService.js`, `materialPriceStatusService.js`, `installationManualService.js`, `webPriceService.js`
|
||||
|
||||
**Project lifecycle** — `customerProjectService.js`, `projectCalculationService.js`, `projectLaborService.js`, `projectMaterialService.js`, `projectExperienceService.js`, `projectFlowValidationService.js`, `projectQuoteGenerationService.js`, `roofGeometryService.js`, `advancedGeometryService.js`, `timeCalculatorService.js`, `orderSuggestionService.js`, `taskCategorizationService.js`, `planningService.js`
|
||||
|
||||
**Ordrestyring sync/integration** — `ordrestyringService.js`, `ordrestyringSyncService.js`, `ordrestyringQuoteService.js`, `ordrestyringMoneyService.js`, `orderStatusService.js`, `graphqlClient.js`, `enhancedOrderDataService.js`, `mobileOrderService.js`
|
||||
|
||||
**PDF / documents** — `pdfGenerationService.js`, `pdfResponseService.js`, `documentParserService.js`, `ocrService.js`, `excelMappingService.js`
|
||||
|
||||
**Auth / users** — `userService.js` (new, 2026-09-04, bcrypt + `auth_accounts`)
|
||||
|
||||
**Platform / infra** — `databaseService.js` (MariaDB pool + query wrapper; also runs ~37 inline `CREATE TABLE IF NOT EXISTS` statements at startup — see [data.md](./data.md)), `databaseCleanupService.js`, `bootstrapServiceContainer.js`, `dynamicImportService.js`, `productTelemetryService.js`, `googleSearchService.js`, `repositoryStatusDiscordService.js`, `supportDraftService.js`
|
||||
|
||||
## Request flow and auth
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Req["Incoming request"] --> R{"Router or\ninline handler?"}
|
||||
R -- "mounted router" --> MW["verifyToken\n(backend/src/middleware/auth.js)"]
|
||||
R -- "inline in\nunified-server.js" --> MW
|
||||
MW -- "valid JWT" --> Admin{"needs admin?"}
|
||||
MW -- "missing/invalid" --> Reject["401/403"]
|
||||
Admin -- "yes: requireAdmin" --> Svc["Service layer\n(backend/src/services/*)"]
|
||||
Admin -- "no" --> Svc
|
||||
Svc --> DBS["databaseService.query()"]
|
||||
DBS --> DB[("MariaDB")]
|
||||
```
|
||||
|
||||
Not every route requires auth — `verifyToken` is applied per-router/per-endpoint, not globally. Some admin surfaces use a separate `optionalAdminApiKeyGuard` (API-key based) rather than JWT — see the `/api/visual-reports` and `/api/admin/logs` mounts above.
|
||||
|
||||
## Auth accounts — the `auth_accounts` / `users` naming trap
|
||||
|
||||
As of 2026-09-04, login credentials moved out of hardcoded env vars into a real table: **`auth_accounts`** (`id`, `username`, `password_hash` [bcrypt], `role` enum `admin`/`user`, `last_login_at`), managed by `userService.js` and `src/routes/users.js`.
|
||||
|
||||
**This is deliberately not called `users`.** A pre-existing, unrelated table named `users` already exists — it holds employee records synced from Ordrestyring (`first_name`, `last_name`, `init`, `email`; written by `ordrestyringSyncService.js`). It has nothing to do with login. If you're adding auth-related columns or queries, make sure you're touching `auth_accounts`, not `users`.
|
||||
|
||||
The live login handler is the inline `POST /api/auth/login` in `unified-server.js` (not a separate router file) — it looks up `auth_accounts` via `userService.findByUsername`, verifies with bcrypt, and issues a JWT carrying `{ username, id, role }`. `requireAdmin` middleware gates `/api/users/*` on `role === 'admin'`.
|
||||
@@ -0,0 +1,71 @@
|
||||
<!-- generated: 2026-09-04 -->
|
||||
# Data Models and Schema
|
||||
|
||||
MariaDB, database `tilbudgivern`. Verified live against `SHOW TABLES` / `DESCRIBE` on 2026-09-04 — **122 tables/views**, far more than the tracked migration files account for (see "How schema actually gets created" below). This document groups them by domain rather than listing all 122 in full DDL.
|
||||
|
||||
## How schema actually gets created (read this before adding a migration)
|
||||
|
||||
There is **no unified migration runner**. Schema comes from three uncoordinated sources:
|
||||
|
||||
1. **`backend/src/services/databaseService.js`** — contains ~37 inline `CREATE TABLE IF NOT EXISTS` statements that run automatically every time the server starts (`Database tables created/verified successfully` in the PM2 logs). This is the primary source for a large share of the schema.
|
||||
2. **`database/migrations/`** — 11 standalone scripts (mix of `.sql` run manually via a MySQL client and `.js` scripts run with `node <file>.js`), timestamp-prefixed, each independent. No tracking table of "which migrations ran."
|
||||
3. **`backend/migrations/`** — a *second*, separate migrations folder with its own independent set of `.sql`/`.js` files. Nothing unifies this with `database/migrations/`.
|
||||
|
||||
Plus `backend/sql/customer_project_system.sql`, a standalone schema file. When adding a table, check whether `databaseService.js` should own it (if the app must always have it present) or whether a one-off migration script is more appropriate — don't assume there's a single place new schema belongs.
|
||||
|
||||
## Table inventory by domain
|
||||
|
||||
**Smart packages** (the app's core reusable-package system) — `smart_packages`, `package_tasks`, `package_materials`, `smart_package_categories`, `smart_package_components`, `smart_package_materials`, `smart_package_steps`, `smart_package_tasks`, `smart_package_types`, `smart_package_usage`, `custom_packages`, `custom_installation_tasks`.
|
||||
|
||||
**A second, older/parallel package system** also exists: `material_packages` (with its own Excel-import provenance columns, `is_template`, `validation_status`, etc.) plus `project_packages`. It's not clearly deprecated in code — treat both systems as live until proven otherwise, and check which one a given feature actually reads before assuming "the" packages table.
|
||||
|
||||
**Customer / project lifecycle** — `customer_projects` (the actual "projects" table — **note: there is no table literally named `projects`**, despite that name appearing informally in some docs/comments), `customer_project_packages` (links `customer_projects` ↔ `smart_packages`), `project_smart_package_workspaces` (JSON workspace snapshots, versioned), `project_labor`, `project_materials`, `project_calculations`, `project_documents`, `project_history`, `project_quotes`, `project_rentals`, `project_tasks`, `project_time_calculations`, `project_types`, `project_analytics`.
|
||||
|
||||
**Roof / geometry** — `roof_geometry` (one row per project; roof type, dimensions, spær calculations, kvist/dormer fields), `roof_types`, `advanced_geometry_data`.
|
||||
|
||||
**Materials / pricing / suppliers** — `materials`, `material_categories`, `material_category_mapping`, `material_formulas`, `material_prices`, `material_analytics`, `material_performance_view`, `dynamic_materials`, `carpenter_materials`, `pricing_rules`, `price_database`, `prices`, `labor_prices`, `labor_tasks`, `suppliers`, `vendors`, `vendor_products_history`, `vendor_price_history`, `vendor_current_prices`, `vendor_import_log`.
|
||||
|
||||
**Supplier imports** — Bygma: `bygma_products`, `bygma_categories`, `bygma_product_groups`, `bygma_materials_cache`, `bygma_materials_mapping`, `bygma_price_history`, `bygma_import_log`, `v_bygma_import_stats`. Stark: `stark_materials_cache`. Håndværkpriser: `haandvaerkpriser_imports`. Generic: `import_logs`, `imported_quotes`, `installation_manuals`.
|
||||
|
||||
**Quotes / generation** — `quotes`, `generated_quotes`, `submitted_quotes`, `enhanced_quote_details`, `quote_feedback`, `v_enhanced_quotes`.
|
||||
|
||||
**Auth** — `auth_accounts` (**new, 2026-09-04**): `id`, `username`, `password_hash` (bcrypt), `role` enum(`admin`,`user`), `created_at`, `updated_at`, `last_login_at`. Owned by `backend/src/services/userService.js`.
|
||||
|
||||
**⚠️ `users` (pre-existing, unrelated to auth)** — `id`, `first_name`, `last_name`, `init`, `email`, `created_at`. This is an **employee roster synced from Ordrestyring** (written by `ordrestyringSyncService.js`), not a login table. The name collision with "who's allowed to log in" is a trap — the auth migration (`database/migrations/20260904_users_table.js`) deliberately created `auth_accounts` instead of reusing/renaming this table. Do not point login logic at `users`.
|
||||
|
||||
**Ordrestyring sync/cache** — `ordrestyring_calendar`, `ordrestyring_case_features`, `ordrestyring_case_latest`, `ordrestyring_case_material_snapshots`, `ordrestyring_data_quality_issues`, `ordrestyring_employee_types`, `ordrestyring_hours_normalized`, `ordrestyring_materials_normalized`, `ordrestyring_offer_line_snapshots`, `ordrestyring_offer_snapshots`, `ordrestyring_reference_cases`, `ordrestyring_reference_labor_entries`, `ordrestyring_reference_materials`, `ordrestyring_sync_metadata`, `ordrestyring_users`.
|
||||
|
||||
**Mobile / field intake** — `mobile_order_intakes`, `mobile_order_attachments`, `mobile_order_checklist_answers`, `mobile_order_quote_drafts`.
|
||||
|
||||
**OCR / documents** — `ocr_documents`, `ocr_materials`, `document_uploads`.
|
||||
|
||||
**Analytics / business metrics** — `analytics_calculation_log`, `business_benchmarks`, `business_health_dashboard`, `customer_analytics`, `daily_business_metrics`, `employee_performance_analytics`, `high_value_customers_view`, `top_employees_view`, `v_category_statistics`, `product_flow_events`.
|
||||
|
||||
**Platform / ops** — `system_logs`, `system_settings`, `request_logs`, `openai_usage_stats`, `openai_usage_tracking`, `ai_jobs`, `ai_suggestions`, `support_work_items`, `category_keywords`, `task_categories`, `task_categorization` support tables (`carpenter_task_categories`, `carpenter_task_steps`, `carpenter_tasks`, `carpenter_tools`), `quality_standards`, `safety_procedures`, `product_categories`.
|
||||
|
||||
**Housekeeping artifact** — `materials_deleted_backup_nov14`: a manual backup table from a prior cleanup, still present. Evidence that cleanups here are done by renaming/backing up rather than hard-deleting — consistent with how this project prefers reversible operations.
|
||||
|
||||
## Core relationships
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
customer_projects ||--o| roof_geometry : "has (project_id)"
|
||||
customer_projects ||--o{ customer_project_packages : "selects"
|
||||
customer_project_packages }o--|| smart_packages : "references"
|
||||
smart_packages ||--o{ package_tasks : "has"
|
||||
smart_packages ||--o{ package_materials : "has"
|
||||
package_materials }o--o| materials : "matched to"
|
||||
customer_projects ||--o| project_smart_package_workspaces : "versioned workspace"
|
||||
customer_projects ||--o{ project_labor : "has"
|
||||
customer_projects ||--o{ project_materials : "has"
|
||||
customer_projects ||--o{ project_quotes : "generates"
|
||||
|
||||
auth_accounts {
|
||||
int id PK
|
||||
varchar username
|
||||
varchar password_hash
|
||||
enum role
|
||||
}
|
||||
```
|
||||
|
||||
`auth_accounts` is intentionally standalone in this diagram — it has no foreign-key relationship to `customer_projects` or anything else; it's purely login/authorization state. The unrelated `users` (employee roster) table is omitted here since it's outside the quoting domain.
|
||||
@@ -0,0 +1,52 @@
|
||||
<!-- generated: 2026-09-04 -->
|
||||
# Frontend Structure
|
||||
|
||||
React 18 + Material-UI, built statically and served by the backend (see [architecture.md](./architecture.md)) — there's no separate frontend deployment.
|
||||
|
||||
## Navigation is a state switch, not a router
|
||||
|
||||
`react-router-dom` is a dependency and `frontend/src/index.js` wraps the tree in `<BrowserRouter>` — but that's vestigial. Nothing in the codebase uses `<Route>`, `useNavigate`, or `useParams` (verified by grep). All real navigation is plain React state in `App.js`: `NAV_VIEWS` (an array of view IDs) + `VIEW_CONFIG` (icon/label/description per ID) drive the nav bar, and `currentView` (persisted to `localStorage`) picks which lazy-loaded component renders via a long `currentView === '...' ? <X /> : ...` chain. There are no URLs per view and no deep links — "adding a page" means adding an entry to `VIEW_CONFIG`/`NAV_VIEWS` and a branch in that chain, not adding a `<Route>`.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Login["LoginForm\n(unauthenticated)"] -- "AuthContext.login()" --> Gate{"isAuthenticated?"}
|
||||
Gate -- no --> Login
|
||||
Gate -- yes --> AppContent["AppContent\ncurrentView state"]
|
||||
AppContent --> Nav["Nav bar\n(navViews = NAV_VIEWS\n+ 'users' if role==admin)"]
|
||||
Nav --> Switch{"currentView"}
|
||||
Switch -->|projects| ProjectFlow
|
||||
Switch -->|planning| PlanningDashboard
|
||||
Switch -->|mobile| MobileOrders
|
||||
Switch -->|materials| MaterialsList
|
||||
Switch -->|smart-packages| SmartPackagesRoutes
|
||||
Switch -->|openai-usage| AIUsageView["inline AI usage view"]
|
||||
Switch -->|noegletal| NoeglatalDashboard
|
||||
Switch -->|dashboard| AdvancedDashboard
|
||||
Switch -->|users, admin only| UsersManagement
|
||||
```
|
||||
|
||||
## Top-level components (`frontend/src/components/`, lazy-loaded ones especially)
|
||||
|
||||
| Component | Role |
|
||||
|---|---|
|
||||
| `ProjectFlow.js` | The core quote-building wizard: project data → geometry → smart packages → final review |
|
||||
| `EnhancedGeometry.js` | Roof geometry input with SVG visualizations for all 7 supported roof types |
|
||||
| `InlineSmartPackage.js` | Smart Package step-by-step wizard embedded in the project flow |
|
||||
| `FinalReview.js` | Quote review + Ordrestyring offer creation |
|
||||
| `MaterialsList.js` | Material search/selection with cached API queries |
|
||||
| `PlanningDashboard.js` | Order/calendar planning view |
|
||||
| `MobileOrders.js` | Field/besigtigelse intake against Ordrestyring order history |
|
||||
| `NoeglatalDashboard.js`, `AdvancedDashboard.js` | Metrics/KPI dashboards |
|
||||
| `UsersManagement.js` | **New (2026-09-04).** Admin-only user CRUD page ("Brugere" nav item), only rendered when `AuthContext.user.role === 'admin'` |
|
||||
| `LoginForm.js` | Credential form, calls `AuthContext.login` |
|
||||
|
||||
## Smart Packages subsystem (`components/smartPackages/`, 19 files)
|
||||
|
||||
`SmartPackages.js` is the management shell; supporting pieces: `SmartPackageWizard.js` / `SmartPackageForm.js` (creation flow), `PackageDetailsDialog.js`, `TaskManagement.js`, `StatisticsView.js`, `SmartPackageReviewQueue.js`, `HaandvaerkPriserImportPanel.js` (haandvaerkpriser.dk import), `BackupImport.js`, `ComponentsLibrary.js`, `smartPackageCategories.js` / `smartPackageManagementQuery.js` (data/query helpers). Routed into the main app via `frontend/src/routes/SmartPackagesRoutes.js` (lazy-loaded as the `smart-packages` view).
|
||||
|
||||
## Auth (`frontend/src/contexts/AuthContext.js`)
|
||||
|
||||
- Login posts to `/api/auth/login`; on success stores `accessToken` in `localStorage` and sets it as the axios default `Authorization` header for every subsequent request.
|
||||
- `user` is now `{ username, role }` — **`role` was added 2026-09-04** to gate the `users` nav view/component; it comes straight from the login response (`response.data.user.role`), matching the backend's `auth_accounts.role` column (see [data.md](./data.md), [backend.md](./backend.md)).
|
||||
- A refresh-token flow (`/api/auth/refresh`, HttpOnly cookie) is scaffolded in the axios response interceptor but the backend endpoint doesn't exist yet — this is explicitly flagged in the file's own `TODO`/`WARNING` comments as incomplete, not a bug to silently "fix" without checking backend readiness first.
|
||||
- `logout()` clears `localStorage` and the axios header; there's no server-side session invalidation (JWTs just expire, 24h access / 7d refresh).
|
||||
Reference in New Issue
Block a user