diff --git a/frontend/src/components/facility3d/HierarchyBreadcrumb.tsx b/frontend/src/components/facility3d/HierarchyBreadcrumb.tsx new file mode 100644 index 0000000..1fe3e16 --- /dev/null +++ b/frontend/src/components/facility3d/HierarchyBreadcrumb.tsx @@ -0,0 +1,56 @@ +import { ChevronRight, Building2, Layers, LayoutGrid, Box, Grid3X3 } from 'lucide-react'; + +export interface BreadcrumbData { + facility?: string; + building?: string; + floor?: string; + room?: string; + section?: string; + tier?: number; + position?: string; +} + +interface HierarchyBreadcrumbProps { + data: BreadcrumbData; + onNavigate?: (level: keyof BreadcrumbData) => void; +} + +const levelIcons: Record = { + facility: , + building: , + floor: , + room: , + section: , + tier: , +}; + +export function HierarchyBreadcrumb({ data, onNavigate }: HierarchyBreadcrumbProps) { + const levels: { key: keyof BreadcrumbData; label: string }[] = [ + { key: 'facility', label: data.facility || '' }, + { key: 'building', label: data.building || '' }, + { key: 'floor', label: data.floor || '' }, + { key: 'room', label: data.room || '' }, + { key: 'section', label: data.section || '' }, + { key: 'tier', label: data.tier ? `Tier ${data.tier}` : '' }, + ].filter(l => l.label); + + if (levels.length === 0) return null; + + return ( +
+ {levels.map((level, i) => ( +
+ {i > 0 && } + +
+ ))} +
+ ); +} diff --git a/frontend/src/components/facility3d/RoomObject.tsx b/frontend/src/components/facility3d/RoomObject.tsx index b3347cf..fa74da3 100644 --- a/frontend/src/components/facility3d/RoomObject.tsx +++ b/frontend/src/components/facility3d/RoomObject.tsx @@ -106,6 +106,7 @@ export function RoomObject({ room, visMode, onPlantClick, highlightedTags, dimMo onPlantClick={onPlantClick} highlightedTags={highlightedTags} dimMode={dimMode} + roomName={room.name} /> ))} diff --git a/frontend/src/components/facility3d/SmartRack.tsx b/frontend/src/components/facility3d/SmartRack.tsx index 084391f..2e1cce5 100644 --- a/frontend/src/components/facility3d/SmartRack.tsx +++ b/frontend/src/components/facility3d/SmartRack.tsx @@ -14,10 +14,12 @@ interface SmartRackProps { onPlantClick: (plant: PlantPosition) => void; highlightedTags?: string[]; dimMode?: boolean; + roomName?: string; // For breadcrumb } -export function SmartRack({ section, visMode, onPlantClick, highlightedTags, dimMode }: SmartRackProps) { +export function SmartRack({ section, visMode, onPlantClick, highlightedTags, dimMode, roomName }: SmartRackProps) { // Scale section dimensions to world units + // Section posX/posY are RELATIVE to room, so we scale them for placement within room group const scaledSection = { posX: section.posX * SCALE, posY: section.posY * SCALE, @@ -25,16 +27,31 @@ export function SmartRack({ section, visMode, onPlantClick, highlightedTags, dim height: section.height * SCALE, }; + // Calculate plant positions RELATIVE to section position (within room group) const positions: PlantPosition[] = useMemo(() => { const plantSpacing = 0.5; // Spacing between plants in world units + // Calculate how many columns/rows fit based on section dimensions + const maxCols = Math.max(...section.positions.map(p => p.column)) || 1; + const maxRows = Math.max(...section.positions.map(p => p.row)) || 1; + + // Calculate actual spacing based on section dimensions + const colSpacing = scaledSection.width / (maxCols + 1); + const rowSpacing = scaledSection.height / (maxRows + 1); + const actualSpacing = Math.min(colSpacing, rowSpacing, plantSpacing); + return section.positions.map((pos: Position3D) => ({ ...pos, // Position plants within the scaled section bounds - x: scaledSection.posX + (pos.column * plantSpacing), - z: scaledSection.posY + (pos.row * plantSpacing), + x: scaledSection.posX + (pos.column * actualSpacing), + z: scaledSection.posY + (pos.row * actualSpacing), y: 0.4 + (pos.tier * 0.6), + // Add breadcrumb data + breadcrumb: { + section: section.code || section.name, + room: roomName, + }, })); - }, [section, scaledSection]); + }, [section, scaledSection, roomName]); const distinctTiers = [...new Set(positions.map(p => p.tier))].sort((a, b) => a - b); const distinctRows = [...new Set(positions.map(p => p.row))].sort((a, b) => a - b); @@ -44,6 +61,7 @@ export function SmartRack({ section, visMode, onPlantClick, highlightedTags, dim return ( + {/* Section/Table Label */} {visMode === 'STANDARD' && ( - {section.code} + {section.code || section.name} )} + {/* Shelf/Tier surfaces */} {distinctTiers.map(tier => ( ))} + {/* Support posts */} {[0, 1].map(xOffset => [0, 1].map(zOffset => ( ( ))} + {/* Column labels */} {visMode === 'STANDARD' && distinctCols.map(col => ( +
+ {/* Breadcrumb */} + {selectedPlant.breadcrumb && ( +
+ +
+ )} +
@@ -399,7 +416,7 @@ export default function Facility3DViewerPage() {
Stage - + {selectedPlant.plant.stage || 'N/A'}