Files
stalendeuren/components/offerte/step-dimensions.tsx
Ubuntu 87be70e78b feat: Production-ready configurator with Dutch standards, pricing & visual UI
- Update door-models.ts: 7mm VSG 33.1 safety glass, 15mm offset, Taats pivot 60mm
- Add pricing engine (lib/pricing.ts): steel €45/m + glass €140/m² + €650 base
- Wire reactive pricing into Zustand store on every config change
- Fix 3D materials: glass thickness 0.007m, corrected roughness/metalness
- Upgrade scene: apartment environment, wider contact shadows
- Add Dutch height presets: Renovatie 2015mm, Nieuwbouw 2315mm, Plafondhoog 2500mm
- Replace text buttons with visual SVG tiles for door type & grid selection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 01:11:55 +00:00

244 lines
8.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useConfiguratorStore } from "@/lib/store";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Slider } from "@/components/ui/slider";
import { Check } from "lucide-react";
export function StepDimensions() {
const {
doorConfig,
sidePanel,
width,
height,
minWidth,
maxWidth,
holeWidth,
doorLeafWidth,
sidePanelWidth,
setDoorConfig,
setSidePanel,
setWidth,
setHeight,
} = useConfiguratorStore();
return (
<div className="space-y-8">
{/* Door Configuration */}
<div>
<h2 className="mb-2 text-lg font-bold text-[#1A2E2E]">Deur Configuratie</h2>
<p className="mb-4 text-sm text-gray-600">
Kies tussen enkele of dubbele deur.
</p>
<div className="grid gap-3 sm:grid-cols-2">
{(['enkele', 'dubbele'] as const).map((config) => {
const selected = doorConfig === config;
return (
<button
key={config}
type="button"
onClick={() => setDoorConfig(config)}
className={`rounded-xl border-2 p-4 text-left transition-all ${
selected
? "border-[#C4D668] bg-[#1A2E2E] text-white"
: "border-gray-200 bg-white text-gray-900 hover:border-[#1A2E2E]/30"
}`}
>
<div className="flex items-center justify-between">
<div className="flex-1">
<h3 className="font-bold capitalize">{config} deur</h3>
<p
className={`mt-1 text-sm ${
selected ? "text-white/80" : "text-gray-500"
}`}
>
{config === 'enkele' ? '1 deurblad' : '2 deurbladen'}
</p>
</div>
{selected && (
<div className="flex size-6 items-center justify-center rounded-full bg-[#C4D668]">
<Check className="size-4 text-[#1A2E2E]" />
</div>
)}
</div>
</button>
);
})}
</div>
</div>
{/* Side Panel Configuration */}
<div>
<h2 className="mb-2 text-lg font-bold text-[#1A2E2E]">Zijpanelen</h2>
<p className="mb-4 text-sm text-gray-600">
Voeg vaste panelen toe naast de deur.
</p>
<div className="grid gap-3 sm:grid-cols-2">
{(['geen', 'links', 'rechts', 'beide'] as const).map((panel) => {
const selected = sidePanel === panel;
return (
<button
key={panel}
type="button"
onClick={() => setSidePanel(panel)}
className={`rounded-xl border-2 p-4 text-left transition-all ${
selected
? "border-[#C4D668] bg-[#1A2E2E] text-white"
: "border-gray-200 bg-white text-gray-900 hover:border-[#1A2E2E]/30"
}`}
>
<div className="flex items-center justify-between">
<div className="flex-1">
<h3 className="font-bold capitalize">{panel}</h3>
{panel !== 'geen' && sidePanelWidth > 0 && (
<p
className={`mt-1 text-sm ${
selected ? "text-white/80" : "text-gray-500"
}`}
>
{Math.round(sidePanelWidth)}mm breed
</p>
)}
</div>
{selected && (
<div className="flex size-6 items-center justify-center rounded-full bg-[#C4D668]">
<Check className="size-4 text-[#1A2E2E]" />
</div>
)}
</div>
</button>
);
})}
</div>
</div>
{/* Width Control */}
<div>
<div className="mb-4 flex items-end justify-between">
<div>
<Label className="text-base font-bold text-[#1A2E2E]">
Breedte
</Label>
<p className="text-sm text-gray-600">
{minWidth}mm - {maxWidth}mm
</p>
</div>
<div className="text-right">
<Input
type="number"
value={width}
onChange={(e) => setWidth(Number(e.target.value))}
min={minWidth}
max={maxWidth}
className="h-10 w-32 text-right font-mono text-lg font-bold"
/>
<p className="mt-1 text-xs text-gray-500">Deurblad: {Math.round(doorLeafWidth)}mm</p>
</div>
</div>
<Slider
value={[width]}
onValueChange={(values) => setWidth(values[0])}
min={minWidth}
max={maxWidth}
step={10}
className="w-full"
/>
</div>
{/* Height Presets - Dutch Market Standards */}
<div>
<Label className="text-base font-bold text-[#1A2E2E]">
Standaard Hoogtes
</Label>
<p className="mb-3 text-sm text-gray-600">
Kies een standaard hoogte of stel handmatig in.
</p>
<div className="grid grid-cols-3 gap-2">
{[
{ label: 'Renovatie', value: 2015, desc: '201.5 cm' },
{ label: 'Nieuwbouw', value: 2315, desc: '231.5 cm' },
{ label: 'Plafondhoog', value: 2500, desc: '250+ cm' },
].map((preset) => {
const isActive = height === preset.value;
return (
<button
key={preset.value}
type="button"
onClick={() => setHeight(preset.value)}
className={`rounded-lg border-2 px-3 py-2.5 text-center transition-all ${
isActive
? 'border-[#C4D668] bg-[#1A2E2E] text-white shadow-md'
: 'border-gray-200 bg-white text-gray-700 hover:border-[#1A2E2E]/30 hover:shadow-sm'
}`}
>
<span className="block text-xs font-bold uppercase tracking-wide">
{preset.label}
</span>
<span className={`block font-mono text-sm ${isActive ? 'text-[#C4D668]' : 'text-gray-500'}`}>
{preset.desc}
</span>
</button>
);
})}
</div>
</div>
{/* Height Control */}
<div>
<div className="mb-4 flex items-end justify-between">
<div>
<Label className="text-base font-bold text-[#1A2E2E]">
Hoogte
</Label>
<p className="text-sm text-gray-600">1800mm - 3000mm</p>
</div>
<Input
type="number"
value={height}
onChange={(e) => setHeight(Number(e.target.value))}
min={1800}
max={3000}
className="h-10 w-32 text-right font-mono text-lg font-bold"
/>
</div>
<Slider
value={[height]}
onValueChange={(values) => setHeight(values[0])}
min={1800}
max={3000}
step={10}
className="w-full"
/>
</div>
{/* Summary */}
<div className="rounded-xl bg-slate-50 p-4">
<h3 className="mb-2 text-sm font-bold text-[#1A2E2E]">
Samenvatting afmetingen
</h3>
<div className="space-y-1 text-sm text-gray-600">
<p>
<span className="font-medium">Deurblad breedte:</span>{" "}
{Math.round(doorLeafWidth)}mm
</p>
<p>
<span className="font-medium">Totale wandopening:</span>{" "}
{Math.round(holeWidth)}mm × {height}mm
</p>
{sidePanelWidth > 0 && (
<p>
<span className="font-medium">Zijpaneel breedte:</span>{" "}
{Math.round(sidePanelWidth)}mm
</p>
)}
</div>
</div>
</div>
);
}