- Create HierarchyBreadcrumb component for navigation - Fix SmartRack plant positioning within section bounds - Add breadcrumb data to PlantPosition type - Display breadcrumb path in plant selection panel - Pass roomName through component hierarchy
155 lines
6 KiB
TypeScript
155 lines
6 KiB
TypeScript
import { useMemo } from 'react';
|
|
import { Text } from '@react-three/drei';
|
|
import * as THREE from 'three';
|
|
import type { Section3D, Position3D } from '../../lib/layoutApi';
|
|
import { PlantPosition, VisMode } from './types';
|
|
import { PlantSystem } from './PlantSystem';
|
|
|
|
// Convert pixel coordinates to world units (DB stores pixels, 3D uses meters/units)
|
|
const SCALE = 0.1; // 1 pixel = 0.1 world units (so 700px = 70 units)
|
|
|
|
interface SmartRackProps {
|
|
section: Section3D;
|
|
visMode: VisMode;
|
|
onPlantClick: (plant: PlantPosition) => void;
|
|
highlightedTags?: string[];
|
|
dimMode?: boolean;
|
|
roomName?: string; // For breadcrumb
|
|
}
|
|
|
|
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,
|
|
width: section.width * SCALE,
|
|
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 * 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, 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);
|
|
const distinctCols = [...new Set(positions.map(p => p.column))].sort((a, b) => a - b);
|
|
|
|
const plantSpacing = 0.5;
|
|
|
|
return (
|
|
<group>
|
|
{/* Section/Table Label */}
|
|
{visMode === 'STANDARD' && (
|
|
<Text
|
|
position={[scaledSection.posX + scaledSection.width / 2, 3, scaledSection.posY + scaledSection.height / 2]}
|
|
fontSize={0.8}
|
|
color="#94a3b8"
|
|
anchorX="center"
|
|
anchorY="bottom"
|
|
outlineColor="#000"
|
|
outlineWidth={0.03}
|
|
>
|
|
{section.code || section.name}
|
|
</Text>
|
|
)}
|
|
|
|
{/* Shelf/Tier surfaces */}
|
|
{distinctTiers.map(tier => (
|
|
<mesh
|
|
key={`shelf-${tier}`}
|
|
position={[
|
|
scaledSection.posX + scaledSection.width / 2,
|
|
0.35 + (tier * 0.6),
|
|
scaledSection.posY + scaledSection.height / 2
|
|
]}
|
|
rotation={[-Math.PI / 2, 0, 0]}
|
|
receiveShadow
|
|
>
|
|
<planeGeometry args={[scaledSection.width * 0.95, scaledSection.height * 0.95]} />
|
|
<meshStandardMaterial
|
|
color="#64748b"
|
|
roughness={0.3}
|
|
metalness={0.7}
|
|
side={THREE.DoubleSide}
|
|
/>
|
|
</mesh>
|
|
))}
|
|
|
|
{/* Support posts */}
|
|
{[0, 1].map(xOffset =>
|
|
[0, 1].map(zOffset => (
|
|
<mesh
|
|
key={`support-${xOffset}-${zOffset}`}
|
|
position={[
|
|
scaledSection.posX + (xOffset * scaledSection.width),
|
|
(distinctTiers.length * 0.6) / 2 + 0.2,
|
|
scaledSection.posY + (zOffset * scaledSection.height)
|
|
]}
|
|
>
|
|
<boxGeometry args={[0.08, distinctTiers.length * 0.6 + 0.3, 0.08]} />
|
|
<meshStandardMaterial color="#374151" roughness={0.4} metalness={0.8} />
|
|
</mesh>
|
|
))
|
|
)}
|
|
|
|
{/* Row labels */}
|
|
{visMode === 'STANDARD' && distinctRows.map(row => (
|
|
<Text
|
|
key={`row-${row}`}
|
|
position={[scaledSection.posX - 0.3, 0.8, scaledSection.posY + row * plantSpacing]}
|
|
rotation={[-Math.PI / 2, 0, 0]}
|
|
fontSize={0.18}
|
|
color="#64748b"
|
|
anchorX="right"
|
|
>
|
|
R{row}
|
|
</Text>
|
|
))}
|
|
|
|
{/* Column labels */}
|
|
{visMode === 'STANDARD' && distinctCols.map(col => (
|
|
<Text
|
|
key={`col-${col}`}
|
|
position={[scaledSection.posX + col * plantSpacing, 0.15, scaledSection.posY + scaledSection.height + 0.2]}
|
|
rotation={[-Math.PI / 2, 0, 0]}
|
|
fontSize={0.18}
|
|
color="#64748b"
|
|
anchorX="center"
|
|
>
|
|
C{col}
|
|
</Text>
|
|
))}
|
|
|
|
<PlantSystem
|
|
positions={positions}
|
|
visMode={visMode}
|
|
onPlantClick={onPlantClick}
|
|
highlightedTags={highlightedTags}
|
|
dimMode={dimMode}
|
|
/>
|
|
</group>
|
|
);
|
|
}
|