From e34df722bb13a17137c50d37e40c31a9b6453518 Mon Sep 17 00:00:00 2001 From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com> Date: Thu, 11 Dec 2025 11:36:31 -0800 Subject: [PATCH] feat: Add Building creation functionality to Layout Designer --- .../layout-designer/LayoutDesignerPage.tsx | 9 ++ .../components/AddBuildingModal.tsx | 119 ++++++++++++++++++ .../components/FloorSelector.tsx | 15 +++ 3 files changed, 143 insertions(+) create mode 100644 frontend/src/features/layout-designer/components/AddBuildingModal.tsx diff --git a/frontend/src/features/layout-designer/LayoutDesignerPage.tsx b/frontend/src/features/layout-designer/LayoutDesignerPage.tsx index 88a3d4b..fb45705 100644 --- a/frontend/src/features/layout-designer/LayoutDesignerPage.tsx +++ b/frontend/src/features/layout-designer/LayoutDesignerPage.tsx @@ -12,6 +12,7 @@ import { ElementsPanel } from './components/ElementsPanel'; import { InspectorPanel } from './components/InspectorPanel'; import { FloorSelector } from './components/FloorSelector'; import { PropertySetup } from './components/PropertySetup'; +import { AddBuildingModal } from './components/AddBuildingModal'; import { AddFloorModal } from './components/AddFloorModal'; import { PlantInventory } from './components/PlantInventory'; import { layoutApi } from '../../lib/layoutApi'; @@ -42,6 +43,7 @@ export default function LayoutDesignerPage() { const [showFloorSelector, setShowFloorSelector] = useState(false); const [showPropertySetup, setShowPropertySetup] = useState(false); const [showAddFloorModal, setShowAddFloorModal] = useState(false); + const [showAddBuildingModal, setShowAddBuildingModal] = useState(false); const [buildingForFloor, setBuildingForFloor] = useState(null); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const [sidebarMode, setSidebarMode] = useState<'design' | 'plants'>('design'); @@ -495,6 +497,7 @@ export default function LayoutDesignerPage() { onClose={() => setShowFloorSelector(false)} onSelectFloor={handleSelectFloor} onAddProperty={() => setShowPropertySetup(true)} + onAddBuilding={() => setShowAddBuildingModal(true)} onAddFloor={(bId) => { setBuildingForFloor(bId); setShowAddFloorModal(true); @@ -505,6 +508,12 @@ export default function LayoutDesignerPage() { onClose={() => setShowPropertySetup(false)} onCreated={loadProperties} /> + setShowAddBuildingModal(false)} + propertyId={property?.id || null} + onCreated={loadProperties} + /> setShowAddFloorModal(false)} diff --git a/frontend/src/features/layout-designer/components/AddBuildingModal.tsx b/frontend/src/features/layout-designer/components/AddBuildingModal.tsx new file mode 100644 index 0000000..b81f2a0 --- /dev/null +++ b/frontend/src/features/layout-designer/components/AddBuildingModal.tsx @@ -0,0 +1,119 @@ +import { useState } from 'react'; +import { X, Building2, ArrowRight, Loader2 } from 'lucide-react'; +import { layoutApi } from '../../../lib/layoutApi'; + +interface AddBuildingModalProps { + isOpen: boolean; + onClose: () => void; + propertyId: string | null; + onCreated: (buildingId: string) => void; +} + +export function AddBuildingModal({ isOpen, onClose, propertyId, onCreated }: AddBuildingModalProps) { + const [loading, setLoading] = useState(false); + const [formData, setFormData] = useState({ + name: '', + code: '', + type: 'INDOOR' // INDOOR, GREENHOUSE, OUTDOOR + }); + + if (!isOpen || !propertyId) return null; + + const handleCreate = async () => { + if (!formData.name.trim()) return; + + setLoading(true); + try { + const building = await layoutApi.createBuilding({ + propertyId, + name: formData.name.trim(), + code: formData.code.trim() || formData.name.substring(0, 3).toUpperCase(), + type: formData.type + }); + onCreated(building.id); + onClose(); + // Reset form + setFormData({ name: '', code: '', type: 'INDOOR' }); + } catch (error) { + console.error('Failed to create building:', error); + } finally { + setLoading(false); + } + }; + + return ( +
+
+ {/* Header */} +
+
+
+ +
+
+

Add New Building

+

Expand your facility

+
+
+ +
+ + {/* Content */} +
+
+ + setFormData({ ...formData, name: e.target.value })} + placeholder="Greenhouse B" + className="w-full bg-slate-800 border border-slate-700 rounded-lg px-4 py-3 text-white focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 outline-none" + /> +
+ +
+
+ + setFormData({ ...formData, code: e.target.value })} + placeholder="GH-B" + className="w-full bg-slate-800 border border-slate-700 rounded-lg px-4 py-3 text-white focus:border-emerald-500 outline-none uppercase" + /> +
+
+ + +
+
+
+ + {/* Footer */} +
+ + +
+
+
+ ); +} diff --git a/frontend/src/features/layout-designer/components/FloorSelector.tsx b/frontend/src/features/layout-designer/components/FloorSelector.tsx index e967aff..9f058d2 100644 --- a/frontend/src/features/layout-designer/components/FloorSelector.tsx +++ b/frontend/src/features/layout-designer/components/FloorSelector.tsx @@ -7,6 +7,7 @@ interface FloorSelectorProps { onClose: () => void; onSelectFloor: (floorId: string, buildingId: string) => void; onAddProperty?: () => void; + onAddBuilding?: () => void; onAddFloor?: (buildingId: string) => void; } @@ -15,6 +16,7 @@ export function FloorSelector({ onClose, onSelectFloor, onAddProperty, + onAddBuilding, onAddFloor }: FloorSelectorProps) { const { property, buildingId, floorId } = useLayoutStore(); @@ -82,6 +84,19 @@ export function FloorSelector({ {/* Buildings & Floors List */}
+ {onAddBuilding && property && ( + + )} + {property?.buildings.map(building => (
{/* Building Header */}