feat: Add Building creation functionality to Layout Designer
Some checks failed
Deploy to Production / deploy (push) Failing after 1s
Test / backend-test (push) Failing after 1s
Test / frontend-test (push) Failing after 0s

This commit is contained in:
fullsizemalt 2025-12-11 11:36:31 -08:00
parent a4a7626637
commit e34df722bb
3 changed files with 143 additions and 0 deletions

View file

@ -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<string | null>(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}
/>
<AddBuildingModal
isOpen={showAddBuildingModal}
onClose={() => setShowAddBuildingModal(false)}
propertyId={property?.id || null}
onCreated={loadProperties}
/>
<AddFloorModal
isOpen={showAddFloorModal}
onClose={() => setShowAddFloorModal(false)}

View file

@ -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 (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm">
<div className="bg-slate-900 border border-white/10 rounded-2xl w-full max-w-md mx-4 overflow-hidden shadow-2xl">
{/* Header */}
<div className="px-6 py-4 border-b border-white/10 flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-emerald-600/20 flex items-center justify-center">
<Building2 className="text-emerald-400" size={20} />
</div>
<div>
<h2 className="text-lg font-bold text-white">Add New Building</h2>
<p className="text-xs text-slate-400">Expand your facility</p>
</div>
</div>
<button onClick={onClose} className="p-2 hover:bg-white/5 rounded-lg transition-colors">
<X size={20} className="text-slate-400" />
</button>
</div>
{/* Content */}
<div className="p-6 space-y-4">
<div>
<label className="block text-sm text-slate-400 mb-2">Building Name</label>
<input
type="text"
value={formData.name}
onChange={(e) => 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"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm text-slate-400 mb-2">Code</label>
<input
type="text"
value={formData.code}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm text-slate-400 mb-2">Type</label>
<select
value={formData.type}
onChange={(e) => setFormData({ ...formData, type: e.target.value })}
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-4 py-3 text-white focus:border-emerald-500 outline-none"
>
<option value="INDOOR">Indoor</option>
<option value="GREENHOUSE">Greenhouse</option>
<option value="OUTDOOR">Outdoor</option>
</select>
</div>
</div>
</div>
{/* Footer */}
<div className="px-6 py-4 border-t border-white/10 flex justify-end gap-3">
<button onClick={onClose} className="px-4 py-2 text-sm text-slate-400 hover:text-white transition-colors">
Cancel
</button>
<button
onClick={handleCreate}
disabled={!formData.name.trim() || loading}
className="flex items-center gap-2 px-5 py-2 bg-emerald-600 hover:bg-emerald-500 disabled:bg-slate-700 disabled:text-slate-500 rounded-lg text-sm font-medium transition-colors"
>
{loading ? <Loader2 size={16} className="animate-spin" /> : <ArrowRight size={16} />}
Create Building
</button>
</div>
</div>
</div>
);
}

View file

@ -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 */}
<div className="max-h-96 overflow-auto p-4">
{onAddBuilding && property && (
<button
onClick={() => {
onAddBuilding();
onClose();
}}
className="w-full flex items-center justify-center gap-2 px-3 py-2 mb-3 bg-slate-800 hover:bg-slate-700 border border-slate-700 border-dashed rounded-lg text-slate-400 hover:text-emerald-400 transition-colors"
>
<Plus size={16} />
<span className="text-sm font-medium">Add New Building</span>
</button>
)}
{property?.buildings.map(building => (
<div key={building.id} className="mb-2">
{/* Building Header */}