Files
stalendeuren/components/configurator/door-3d.tsx
Ubuntu df8a45a2b2 Integrate comprehensive dimension calculation logic
Created complete business logic system for door dimensions:

**New: lib/calculations.ts**
- calculateHoleWidth: Total wall opening needed
- calculateHoleMinWidth/MaxWidth: Dynamic limits based on config
- calculateSidePanelWidth: Side panel sizing logic
- calculateDoorLeafWidth: Individual door panel width
- validateDimensions: Real-time validation
- Constants: Frame profiles (80mm), min/max widths

**Enhanced: lib/store.ts**
- Added doorConfig: 'enkele' | 'dubbele' (single/double door)
- Added sidePanel: 'geen' | 'links' | 'rechts' | 'beide'
- Calculated fields: holeWidth, doorLeafWidth, sidePanelWidth, min/maxWidth
- Auto-recalculation on config changes
- setWidth/setHeight with automatic clamping to valid ranges
- Internal recalculate() method updates all derived values

**Redesigned: step-dimensions.tsx**
- Door configuration selector (enkele/dubbele)
- Side panel selector (geen/links/rechts/beide)
- Width slider with dynamic min/max based on configuration
- Height slider (1800-3000mm)
- Real-time input fields synced with sliders
- Summary panel showing:
  - Door leaf width
  - Total wall opening (hole width × height)
  - Side panel width (when applicable)
- Premium tile-based UI (Dark Green/Pistachio theme)

**Added: components/ui/slider.tsx**
- Shadcn Slider component for smooth value selection

**Connected: door-3d.tsx**
- Door now uses doorLeafWidth from store (in mm, converted to meters)
- Door height from store (in mm, converted to meters)
- 3D door resizes in real-time as user drags sliders
- Maintains realistic proportions

**User Flow:**
1. User selects enkele/dubbele configuration
2. User selects side panels (if any)
3. Store calculates valid min/max widths
4. User drags width slider (clamped to valid range)
5. Store calculates door leaf width and side panel widths
6. 3D door updates instantly to show new dimensions
7. Summary shows all calculated dimensions

**Result:** Professional dimension configurator with real business logic,
          automatic validation, and real-time 3D preview

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-10 16:43:16 +00:00

123 lines
4.4 KiB
TypeScript

"use client";
import { useRef } from "react";
import { useConfiguratorStore } from "@/lib/store";
import * as THREE from "three";
export function Door3D() {
const { doorType, gridType, finish, handle, doorLeafWidth, height } =
useConfiguratorStore();
const doorRef = useRef<THREE.Group>(null);
// Frame color based on finish
const frameColor = {
zwart: "#1a1a1a",
brons: "#8B6F47",
grijs: "#525252",
}[finish];
// Convert mm to meters for 3D scene
const doorWidth = doorLeafWidth / 1000; // Convert mm to m
const doorHeight = height / 1000; // Convert mm to m
const frameThickness = 0.03; // Slim steel profile (3cm)
const frameDepth = 0.05; // Depth of frame
const glassThickness = 0.015; // Realistic glass thickness
// Calculate grid divider positions
const getDividerPositions = () => {
if (gridType === "3-vlak") {
return [-doorHeight / 3, doorHeight / 3];
} else if (gridType === "4-vlak") {
return [-doorHeight / 2, 0, doorHeight / 2];
}
return [];
};
const dividerPositions = getDividerPositions();
return (
<group ref={doorRef} position={[0, doorHeight / 2, 0]}>
{/* Outer Frame - Top */}
<mesh position={[0, doorHeight / 2, 0]} castShadow>
<boxGeometry args={[doorWidth + frameThickness * 2, frameThickness, frameDepth]} />
<meshStandardMaterial color={frameColor} metalness={0.9} roughness={0.2} />
</mesh>
{/* Outer Frame - Bottom */}
<mesh position={[0, -doorHeight / 2, 0]} castShadow>
<boxGeometry args={[doorWidth + frameThickness * 2, frameThickness, frameDepth]} />
<meshStandardMaterial color={frameColor} metalness={0.9} roughness={0.2} />
</mesh>
{/* Outer Frame - Left */}
<mesh position={[-doorWidth / 2 - frameThickness / 2, 0, 0]} castShadow>
<boxGeometry args={[frameThickness, doorHeight + frameThickness * 2, frameDepth]} />
<meshStandardMaterial color={frameColor} metalness={0.9} roughness={0.2} />
</mesh>
{/* Outer Frame - Right */}
<mesh position={[doorWidth / 2 + frameThickness / 2, 0, 0]} castShadow>
<boxGeometry args={[frameThickness, doorHeight + frameThickness * 2, frameDepth]} />
<meshStandardMaterial color={frameColor} metalness={0.9} roughness={0.2} />
</mesh>
{/* Glass Panel - Premium Physical Material */}
<mesh position={[0, 0, 0]} castShadow receiveShadow>
<boxGeometry args={[doorWidth, doorHeight, glassThickness]} />
<meshPhysicalMaterial
color="#eef6fc"
transparent
transmission={1}
roughness={0}
thickness={2}
envMapIntensity={1}
clearcoat={1}
clearcoatRoughness={0}
/>
</mesh>
{/* Horizontal Dividers (Grid) - Slim profiles */}
{dividerPositions.map((yPos, index) => (
<mesh key={`divider-${index}`} position={[0, yPos, 0.01]} castShadow>
<boxGeometry args={[doorWidth, frameThickness, frameDepth]} />
<meshStandardMaterial color={frameColor} metalness={0.9} roughness={0.2} />
</mesh>
))}
{/* Vertical Divider for Paneel */}
{doorType === "paneel" && (
<mesh position={[0, 0, 0.01]} castShadow>
<boxGeometry args={[frameThickness, doorHeight, frameDepth]} />
<meshStandardMaterial color={frameColor} metalness={0.9} roughness={0.2} />
</mesh>
)}
{/* Handle - U-Greep for Taats */}
{doorType === "taats" && handle === "u-greep" && (
<mesh position={[0, 0, frameDepth + 0.01]} castShadow>
<boxGeometry args={[0.025, 0.6, 0.025]} />
<meshStandardMaterial color={frameColor} metalness={0.9} roughness={0.15} />
</mesh>
)}
{/* Handle - Klink for Scharnier */}
{doorType === "scharnier" && handle === "klink" && (
<group position={[doorWidth / 2 - 0.12, 0, frameDepth + 0.01]}>
<mesh castShadow>
<boxGeometry args={[0.1, 0.025, 0.025]} />
<meshStandardMaterial color={frameColor} metalness={0.9} roughness={0.15} />
</mesh>
<mesh position={[0.05, 0, 0]} castShadow>
<sphereGeometry args={[0.02, 32, 32]} />
<meshStandardMaterial
color={finish === "brons" ? "#6B5434" : frameColor}
metalness={0.95}
roughness={0.05}
/>
</mesh>
</group>
)}
</group>
);
}