diff --git a/frontend/src/components/facility3d/HierarchyNav.tsx b/frontend/src/components/facility3d/HierarchyNav.tsx new file mode 100644 index 0000000..c337ddb --- /dev/null +++ b/frontend/src/components/facility3d/HierarchyNav.tsx @@ -0,0 +1,159 @@ +import { useState } from 'react'; +import { ChevronRight, ChevronLeft, Building2, Layers, LayoutGrid, Home, Maximize } from 'lucide-react'; +import type { Floor3DData, Room3D } from '../../lib/layoutApi'; + +interface HierarchyNavProps { + floorData: Floor3DData; + onRoomSelect: (room: Room3D) => void; + onResetView: () => void; +} + +type NavLevel = 'facility' | 'building' | 'floor' | 'rooms'; + +export function HierarchyNav({ floorData, onRoomSelect, onResetView }: HierarchyNavProps) { + const [currentLevel, setCurrentLevel] = useState('rooms'); + const [selectedRoom, setSelectedRoom] = useState(null); + + const facility = floorData.floor.property || 'Facility'; + const building = floorData.floor.building || 'Building'; + const floor = floorData.floor.name || 'Floor'; + + const handleBack = () => { + switch (currentLevel) { + case 'rooms': + setCurrentLevel('floor'); + break; + case 'floor': + setCurrentLevel('building'); + break; + case 'building': + setCurrentLevel('facility'); + break; + } + }; + + const handleForward = (level: NavLevel) => { + setCurrentLevel(level); + }; + + const handleRoomClick = (room: Room3D) => { + setSelectedRoom(room); + onRoomSelect(room); + }; + + // Breadcrumb path based on current level + const getBreadcrumb = () => { + const parts: { label: string; level: NavLevel }[] = []; + parts.push({ label: facility, level: 'facility' }); + if (currentLevel !== 'facility') { + parts.push({ label: building, level: 'building' }); + } + if (currentLevel === 'floor' || currentLevel === 'rooms') { + parts.push({ label: floor, level: 'floor' }); + } + if (currentLevel === 'rooms') { + parts.push({ label: 'Rooms', level: 'rooms' }); + } + return parts; + }; + + return ( +
+ {/* Header with breadcrumb */} +
+ {currentLevel !== 'facility' && ( + + )} +
+ {getBreadcrumb().map((part, i) => ( + + {i > 0 && } + + + ))} +
+ +
+ + {/* Content based on level */} +
+ {currentLevel === 'facility' && ( +
+ +
+ )} + + {currentLevel === 'building' && ( +
+ +
+ )} + + {currentLevel === 'floor' && ( +
+ +
+ )} + + {currentLevel === 'rooms' && ( +
+ {floorData.rooms.map((room) => ( + + ))} +
+ )} +
+
+ ); +} diff --git a/frontend/src/pages/Facility3DViewerPage.tsx b/frontend/src/pages/Facility3DViewerPage.tsx index 1ec2434..4d8066d 100644 --- a/frontend/src/pages/Facility3DViewerPage.tsx +++ b/frontend/src/pages/Facility3DViewerPage.tsx @@ -7,6 +7,7 @@ import { Link, useSearchParams } from 'react-router-dom'; import { FacilityScene } from '../components/facility3d/FacilityScene'; import { PlantSearch } from '../components/facility3d/PlantSearch'; import { PlantLibrary } from '../components/facility3d/PlantLibrary'; +import { HierarchyNav } from '../components/facility3d/HierarchyNav'; import { TimelineSlider } from '../components/facility3d/TimelineSlider'; import { PlantPosition, VisMode, COLORS } from '../components/facility3d/types'; import { CameraPreset, CameraPresetSelector } from '../components/facility3d/CameraPresets'; @@ -318,27 +319,13 @@ export default function Facility3DViewerPage() { {/* Room Navigation Sidebar */} {floorData && ( -
-
-
- Rooms - -
-
- {floorData.rooms.map((room: Room3D) => ( - - ))} -
-
+
+ {/* Hierarchical Navigation */} + {/* Focus Mode Toggle */} {highlightedTags.length > 0 && (