feat: Add Building creation functionality to Layout Designer
This commit is contained in:
parent
a4a7626637
commit
e34df722bb
3 changed files with 143 additions and 0 deletions
|
|
@ -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)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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 */}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue