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 { InspectorPanel } from './components/InspectorPanel';
|
||||||
import { FloorSelector } from './components/FloorSelector';
|
import { FloorSelector } from './components/FloorSelector';
|
||||||
import { PropertySetup } from './components/PropertySetup';
|
import { PropertySetup } from './components/PropertySetup';
|
||||||
|
import { AddBuildingModal } from './components/AddBuildingModal';
|
||||||
import { AddFloorModal } from './components/AddFloorModal';
|
import { AddFloorModal } from './components/AddFloorModal';
|
||||||
import { PlantInventory } from './components/PlantInventory';
|
import { PlantInventory } from './components/PlantInventory';
|
||||||
import { layoutApi } from '../../lib/layoutApi';
|
import { layoutApi } from '../../lib/layoutApi';
|
||||||
|
|
@ -42,6 +43,7 @@ export default function LayoutDesignerPage() {
|
||||||
const [showFloorSelector, setShowFloorSelector] = useState(false);
|
const [showFloorSelector, setShowFloorSelector] = useState(false);
|
||||||
const [showPropertySetup, setShowPropertySetup] = useState(false);
|
const [showPropertySetup, setShowPropertySetup] = useState(false);
|
||||||
const [showAddFloorModal, setShowAddFloorModal] = useState(false);
|
const [showAddFloorModal, setShowAddFloorModal] = useState(false);
|
||||||
|
const [showAddBuildingModal, setShowAddBuildingModal] = useState(false);
|
||||||
const [buildingForFloor, setBuildingForFloor] = useState<string | null>(null);
|
const [buildingForFloor, setBuildingForFloor] = useState<string | null>(null);
|
||||||
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
||||||
const [sidebarMode, setSidebarMode] = useState<'design' | 'plants'>('design');
|
const [sidebarMode, setSidebarMode] = useState<'design' | 'plants'>('design');
|
||||||
|
|
@ -495,6 +497,7 @@ export default function LayoutDesignerPage() {
|
||||||
onClose={() => setShowFloorSelector(false)}
|
onClose={() => setShowFloorSelector(false)}
|
||||||
onSelectFloor={handleSelectFloor}
|
onSelectFloor={handleSelectFloor}
|
||||||
onAddProperty={() => setShowPropertySetup(true)}
|
onAddProperty={() => setShowPropertySetup(true)}
|
||||||
|
onAddBuilding={() => setShowAddBuildingModal(true)}
|
||||||
onAddFloor={(bId) => {
|
onAddFloor={(bId) => {
|
||||||
setBuildingForFloor(bId);
|
setBuildingForFloor(bId);
|
||||||
setShowAddFloorModal(true);
|
setShowAddFloorModal(true);
|
||||||
|
|
@ -505,6 +508,12 @@ export default function LayoutDesignerPage() {
|
||||||
onClose={() => setShowPropertySetup(false)}
|
onClose={() => setShowPropertySetup(false)}
|
||||||
onCreated={loadProperties}
|
onCreated={loadProperties}
|
||||||
/>
|
/>
|
||||||
|
<AddBuildingModal
|
||||||
|
isOpen={showAddBuildingModal}
|
||||||
|
onClose={() => setShowAddBuildingModal(false)}
|
||||||
|
propertyId={property?.id || null}
|
||||||
|
onCreated={loadProperties}
|
||||||
|
/>
|
||||||
<AddFloorModal
|
<AddFloorModal
|
||||||
isOpen={showAddFloorModal}
|
isOpen={showAddFloorModal}
|
||||||
onClose={() => setShowAddFloorModal(false)}
|
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;
|
onClose: () => void;
|
||||||
onSelectFloor: (floorId: string, buildingId: string) => void;
|
onSelectFloor: (floorId: string, buildingId: string) => void;
|
||||||
onAddProperty?: () => void;
|
onAddProperty?: () => void;
|
||||||
|
onAddBuilding?: () => void;
|
||||||
onAddFloor?: (buildingId: string) => void;
|
onAddFloor?: (buildingId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -15,6 +16,7 @@ export function FloorSelector({
|
||||||
onClose,
|
onClose,
|
||||||
onSelectFloor,
|
onSelectFloor,
|
||||||
onAddProperty,
|
onAddProperty,
|
||||||
|
onAddBuilding,
|
||||||
onAddFloor
|
onAddFloor
|
||||||
}: FloorSelectorProps) {
|
}: FloorSelectorProps) {
|
||||||
const { property, buildingId, floorId } = useLayoutStore();
|
const { property, buildingId, floorId } = useLayoutStore();
|
||||||
|
|
@ -82,6 +84,19 @@ export function FloorSelector({
|
||||||
|
|
||||||
{/* Buildings & Floors List */}
|
{/* Buildings & Floors List */}
|
||||||
<div className="max-h-96 overflow-auto p-4">
|
<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 => (
|
{property?.buildings.map(building => (
|
||||||
<div key={building.id} className="mb-2">
|
<div key={building.id} className="mb-2">
|
||||||
{/* Building Header */}
|
{/* Building Header */}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue