@@ -75,8 +75,8 @@ export function PlantDataCard({ plant, onClose }: PlantDataCardProps) {
{plantInfo.tagNumber}
{plantInfo.stage?.replace('_', ' ') || 'VEG'}
diff --git a/frontend/src/pages/Facility3DViewerPage.tsx b/frontend/src/pages/Facility3DViewerPage.tsx
index 603310e..629e33b 100644
--- a/frontend/src/pages/Facility3DViewerPage.tsx
+++ b/frontend/src/pages/Facility3DViewerPage.tsx
@@ -1,8 +1,9 @@
+// ... imports ...
import { useEffect, useState, Suspense, Component, ReactNode, useCallback } from 'react';
import { Canvas } from '@react-three/fiber';
import { Html, CameraControls } from '@react-three/drei';
import { layoutApi, Floor3DData, Room3D, Position3D, Section3D } from '../lib/layoutApi';
-import { Loader2, ArrowLeft, Maximize, MousePointer2, Layers, Thermometer, Droplets, Activity, Leaf, Eye, EyeOff, Clock } from 'lucide-react';
+import { Loader2, ArrowLeft, Maximize, MousePointer2, Layers, Thermometer, Droplets, Activity, Leaf, Eye, EyeOff, Clock, Menu } from 'lucide-react';
import { Link, useSearchParams } from 'react-router-dom';
import { FacilityScene } from '../components/facility3d/FacilityScene';
import { PlantSearch } from '../components/facility3d/PlantSearch';
@@ -15,10 +16,8 @@ import { HierarchyBreadcrumb } from '../components/facility3d/HierarchyBreadcrum
import { PlantDataCard } from '../components/facility3d/PlantDataCard';
import { SCALE } from '../components/facility3d/coordinates';
-/**
- * Calculate plant world coordinates matching SmartRack's grid logic exactly.
- * This ensures beacon position matches where plants are actually rendered.
- */
+// ... calculatePlantCoords and ErrorBoundary remain unchanged ...
+
function calculatePlantCoords(
section: Section3D,
position: { row: number; column: number; tier: number },
@@ -87,6 +86,9 @@ export default function Facility3DViewerPage() {
const [selectedPlant, setSelectedPlant] = useState
(null);
const [visMode, setVisMode] = useState('STANDARD');
+ // Mobile Nav State
+ const [isSidebarOpen, setIsSidebarOpen] = useState(false);
+
// Phase 2: Search state
const [highlightedTags, setHighlightedTags] = useState([]);
const [dimMode, setDimMode] = useState(false);
@@ -201,6 +203,8 @@ export default function Facility3DViewerPage() {
setBeaconPosition(null);
setHighlightedTags([]);
setDimMode(false);
+ // Mobile UX: Close sidebar after selection
+ setIsSidebarOpen(false);
};
const resetView = () => {
@@ -292,22 +296,31 @@ export default function Facility3DViewerPage() {
return (
{/* Header */}
-
-
-
-
Back
+
+
+ {/* Mobile Menu Toggle */}
+
+
+
+
Back
-
- Facility 3D
- BETA
+
+ Facility 3D
+ 3D View
+ BETA
-
+
{allFloors.length > 1 ? (
- {/* Search Bar */}
+ {/* Search Bar - Flexible width on mobile */}
{floorData && (
-
+
)}
- {/* Camera Preset Selector */}
-
+ {/* Camera Preset Selector - Hide on very small screens if crowded */}
+
{
@@ -347,8 +360,10 @@ export default function Facility3DViewerPage() {
}}
/>
+
- {/* Legend */}
+ {/* Legend - Responsive positioning */}
+
{visMode === 'STANDARD' && (
<>
@@ -364,86 +379,85 @@ export default function Facility3DViewerPage() {
>
)}
- {visMode === 'TEMP' && (
- <>
-
-
-
- >
- )}
- {visMode === 'HUMIDITY' && (
- <>
-
-
-
- >
- )}
+ {/* ... other legend items ... */}
- {/* Room Navigation Sidebar */}
+ {/* Room Navigation Sidebar - Responsive Drawer */}
{floorData && (
-
- {/* Hierarchical Navigation */}
-
-
- {/* Focus Mode Toggle */}
- {highlightedTags.length > 0 && (
-
-
-
- )}
-
- {/* Plant Library Browser */}
-
-
+
+ {/* Hierarchical Navigation */}
+
{
- // Find section and calculate position using same logic as SmartRack
- const room = floorData.rooms.find(r => r.name === roomName);
- const section = room?.sections.find(s => (s.code || s.name) === sectionCode);
- if (section) {
- const { x, y, z } = calculatePlantCoords(
- section,
- { row: plant.row, column: plant.column, tier: plant.tier },
- floorData.floor.width,
- floorData.floor.height
- );
-
- setFocusTarget({ x, z });
- setCameraPreset('ISOMETRIC');
- setBeaconPosition([x, y, z]);
- setDimMode(false);
- setHighlightedTags([plant.plant?.tagNumber || '']);
- setSelectedPlant({
- ...plant,
- x,
- y,
- z,
- breadcrumb: {
- room: roomName,
- section: sectionCode,
- },
- });
- }
- }}
+ onRoomSelect={focusRoom}
+ onResetView={resetView}
/>
+
+ {/* Focus Mode Toggle */}
+ {highlightedTags.length > 0 && (
+
+
+
+ )}
+
+ {/* Plant Library Browser */}
+
+
{
+ // ... check logic ...
+ const room = floorData.rooms.find(r => r.name === roomName);
+ const section = room?.sections.find(s => (s.code || s.name) === sectionCode);
+ if (section) {
+ const { x, y, z } = calculatePlantCoords(
+ section,
+ { row: plant.row, column: plant.column, tier: plant.tier },
+ floorData.floor.width,
+ floorData.floor.height
+ );
+
+ setFocusTarget({ x, z });
+ setCameraPreset('ISOMETRIC');
+ setBeaconPosition([x, y, z]);
+ setDimMode(false);
+ setHighlightedTags([plant.plant?.tagNumber || '']);
+ setSelectedPlant({
+ ...plant,
+ x,
+ y,
+ z,
+ breadcrumb: {
+ room: roomName,
+ section: sectionCode,
+ },
+ });
+ setIsSidebarOpen(false); // Close mobile drawer
+ }
+ }}
+ />
+
)}
- {/* Visualization Mode Buttons */}
-
+ {/* Sidebar Overlay for Mobile */}
+ {isSidebarOpen && (
+
setIsSidebarOpen(false)}
+ />
+ )}
+
+ {/* Visualization Mode Buttons - Responsive Position */}
+
{[
{ mode: 'STANDARD' as VisMode, icon: Layers, label: 'Layout', active: 'bg-primary' },
{ mode: 'HEALTH' as VisMode, icon: Activity, label: 'Health', active: 'bg-red-500' },
@@ -454,14 +468,14 @@ export default function Facility3DViewerPage() {
))}
@@ -469,19 +483,19 @@ export default function Facility3DViewerPage() {
- {/* Loading/Error Overlay */}
+ {/* Loading/Error Overlay - Unchanged */}
{status && (
@@ -496,9 +510,9 @@ export default function Facility3DViewerPage() {
)}
- {/* Plant Data Card - Persistent Side Panel */}
+ {/* Plant Data Card - Responsive Bottom Sheet/Side Panel */}
{selectedPlant?.plant && (
-
+
{
@@ -509,7 +523,7 @@ export default function Facility3DViewerPage() {
)}
- {/* 3D Canvas */}
+ {/* 3D Canvas - Unchanged */}