'use client' import { type AnyNode, type CeilingNode, useScene } from '@pascal-app/core' import { useViewer } from '@pascal-app/viewer' import { Edit, Plus, Trash2 } from 'lucide-react' import { useCallback, useEffect } from 'react' import useEditor from '@/store/use-editor' import { PanelWrapper } from './panel-wrapper' import { PanelSection } from '../controls/panel-section' import { SliderControl } from '../controls/slider-control' import { ActionButton } from '../controls/action-button' export function CeilingPanel() { 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) const selectedId = selectedIds[0] const node = selectedId ? (nodes[selectedId as AnyNode['id']] as CeilingNode | 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]) useEffect(() => { if (!node) { setEditingHole(null) } }, [node, setEditingHole]) useEffect(() => { return () => { setEditingHole(null) } }, [setEditingHole]) const handleAddHole = useCallback(() => { if (!node || !selectedId) return 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 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] }) 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], ) if (!node || node.type !== 'ceiling' || selectedIds.length !== 1) return null 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 ( handleUpdate({ height: v })} min={0} max={6} precision={3} step={0.01} unit="m" />
handleUpdate({ height: 2.4 })} /> handleUpdate({ height: 2.5 })} /> handleUpdate({ height: 3.0 })} />
Area {area.toFixed(2)} m²
{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} pts

{isEditing ? ( setEditingHole(null)} className="h-7 bg-primary text-primary-foreground hover:bg-primary/90" /> ) : ( <> )}
) })}
) : (
No holes
)}
} label="Add Hole" onClick={handleAddHole} className="w-full" disabled={editingHole?.nodeId === selectedId} />
) }