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]>
58 lines
3.1 KiB
JavaScript
58 lines
3.1 KiB
JavaScript
import React, { useEffect, useRef } from 'react';
|
|
import L from 'leaflet';
|
|
import 'leaflet/dist/leaflet.css';
|
|
|
|
export default function EditableAreaMap({ points, center, drawing, disabled, onChange }) {
|
|
const host = useRef(null);
|
|
const map = useRef(null);
|
|
const layers = useRef(null);
|
|
const current = useRef({ points, drawing, disabled, onChange });
|
|
current.current = { points, drawing, disabled, onChange };
|
|
useEffect(() => {
|
|
const instance = L.map(host.current,{scrollWheelZoom:false,doubleClickZoom:false}).setView([56,10],18);
|
|
map.current = instance;
|
|
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png',{
|
|
maxZoom:19,attribution:'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors · <a href="https://opendatacommons.org/licenses/odbl/1-0/">ODbL</a>'
|
|
}).addTo(instance);
|
|
layers.current = L.layerGroup().addTo(instance);
|
|
instance.on('click',event => {
|
|
const state=current.current;
|
|
if (state.drawing && !state.disabled && state.points.length < 500) state.onChange([...state.points,[event.latlng.lng,event.latlng.lat]]);
|
|
});
|
|
const resize = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(() => instance.invalidateSize()) : null;
|
|
resize?.observe(host.current);
|
|
return () => {resize?.disconnect();instance.remove();};
|
|
},[]);
|
|
useEffect(() => { map.current.setView(center,18); },[center]);
|
|
useEffect(() => {
|
|
const group=layers.current;
|
|
group.clearLayers();
|
|
const latLngs=points.map(([lng,lat]) => [lat,lng]);
|
|
if (points.length>=3) L.polygon(latLngs,{color:'#176a59',fillOpacity:.18,interactive:false}).addTo(group);
|
|
else if(points.length>1) L.polyline(latLngs,{color:'#176a59',interactive:false}).addTo(group);
|
|
points.forEach((point,index) => {
|
|
const marker=L.marker([point[1],point[0]],{
|
|
draggable:!disabled,keyboard:false,
|
|
icon:L.divIcon({className:'site-area-handle',html:`<span>${index+1}</span>`,iconSize:[44,44],iconAnchor:[22,22]})
|
|
}).addTo(group);
|
|
marker.on('dragstart',() => map.current.dragging.disable());
|
|
marker.on('dragend',() => {
|
|
map.current.dragging.enable();
|
|
const position=marker.getLatLng();
|
|
current.current.onChange(current.current.points.map((p,i) => i===index ? [position.lng,position.lat] : p));
|
|
});
|
|
// A midpoint is a large touch target for inserting an extra editable vertex.
|
|
if (points.length>=2 && !drawing && !disabled) {
|
|
const next=points[(index+1)%points.length];
|
|
const middle=[(point[0]+next[0])/2,(point[1]+next[1])/2];
|
|
L.marker([middle[1],middle[0]],{keyboard:false,icon:L.divIcon({className:'site-area-handle',html:'<span>+</span>',iconSize:[44,44],iconAnchor:[22,22]})})
|
|
.addTo(group).on('click',event => {
|
|
L.DomEvent.stopPropagation(event);
|
|
onChange([...points.slice(0,index+1),middle,...points.slice(index+1)]);
|
|
});
|
|
}
|
|
});
|
|
},[points,drawing,disabled,onChange]);
|
|
return <div ref={host} className="site-area-map" role="region" aria-label="Kort til opmåling" aria-describedby="site-area-instructions" />;
|
|
}
|