'use client' import { type AnyNode, type SlabNode, useScene } from '@pascal-app/core' import { useViewer } from '@pascal-app/viewer' import { Edit, Plus, Trash2, X } from 'lucide-react' import Image from 'next/image' import { useCallback, useEffect } from 'react' import useEditor from '@/store/use-editor' import { NumberInput } from '@/components/ui/primitives/number-input' export function SlabPanel() { const selectedIds = useViewer((s) => s.selection.selectedIds) const setSelection = useViewer((s) => s.setSelection) const nodes = useScene((s) => s.nodes) const updateNode = useScene((s) => s.updateNode) const editingHole = useEditor((s) => s.editingHole) const setEditingHole = useEditor((s) => s.setEditingHole) // Get the first selected node if it's a slab const selectedId = selectedIds[0] const node = selectedId ? (nodes[selectedId as AnyNode['id']] as SlabNode | undefined) : undefined const handleUpdate = useCallback( (updates: Partial) => { if (!selectedId) return updateNode(selectedId as AnyNode['id'], updates) }, [selectedId, updateNode], ) const handleClose = useCallback(() => { setSelection({ selectedIds: [] }) setEditingHole(null) }, [setSelection, setEditingHole]) // Clear hole editing state when slab is deselected useEffect(() => { if (!node) { setEditingHole(null) } }, [node, setEditingHole]) // Clear hole editing state on unmount useEffect(() => { return () => { setEditingHole(null) } }, [setEditingHole]) const handleAddHole = useCallback(() => { if (!node || !selectedId) return // Calculate centroid of the slab polygon const polygon = node.polygon let cx = 0 let cz = 0 for (const [x, z] of polygon) { cx += x cz += z } cx /= polygon.length cz /= polygon.length // Create a default small rectangular hole centered at the slab's centroid const holeSize = 0.5 const newHole: Array<[number, number]> = [ [cx - holeSize, cz - holeSize], [cx + holeSize, cz - holeSize], [cx + holeSize, cz + holeSize], [cx - holeSize, cz + holeSize], ] const currentHoles = node?.holes || [] handleUpdate({ holes: [...currentHoles, newHole] }) // Enter edit mode for the new hole setEditingHole({ nodeId: selectedId, holeIndex: currentHoles.length }) }, [node, selectedId, handleUpdate, setEditingHole]) const handleEditHole = useCallback( (index: number) => { if (!selectedId) return setEditingHole({ nodeId: selectedId, holeIndex: index }) }, [selectedId, setEditingHole], ) const handleDeleteHole = useCallback( (index: number) => { if (!selectedId) return const currentHoles = node?.holes || [] const newHoles = currentHoles.filter((_, i) => i !== index) handleUpdate({ holes: newHoles }) if (editingHole?.nodeId === selectedId && editingHole?.holeIndex === index) { setEditingHole(null) } }, [selectedId, node?.holes, handleUpdate, editingHole, setEditingHole], ) // Only show if exactly one slab is selected if (!node || node.type !== 'slab' || selectedIds.length !== 1) return null // Calculate approximate area from polygon const calculateArea = (polygon: Array<[number, number]>): number => { if (polygon.length < 3) return 0 let area = 0 const n = polygon.length for (let i = 0; i < n; i++) { const j = (i + 1) % n area += polygon[i]![0] * polygon[j]![1] area -= polygon[j]![0] * polygon[i]![1] } return Math.abs(area) / 2 } const area = calculateArea(node.polygon) return (
{/* Header */}

{node.name || "Slab"}

{/* Content */}
{/* Elevation */}
{ handleUpdate({ elevation: value }) }} precision={3} className="flex-1" /> m

Height offset from the level base (positive = raised, negative = sunken)

{/* Quick preset buttons */}
{/* Area info */}
{area.toFixed(2)} m²
{/* Holes */}
{editingHole?.nodeId === selectedId ? ( ) : ( )}
{node.holes && node.holes.length > 0 ? (
{node.holes.map((hole, index) => { const holeArea = calculateArea(hole) const isEditing = editingHole?.nodeId === selectedId && editingHole?.holeIndex === index return (

Hole {index + 1} {isEditing && '(Editing)'}

{holeArea.toFixed(2)} m² · {hole.length} vertices

{!isEditing && ( <> )}
) })}
) : (

No holes. Click "Add Hole" to create one.

)}
) }