feat(layout): Add Ceiling Height and Tiers to Floor creation
Some checks failed
Deploy to Production / deploy (push) Failing after 0s
Test / backend-test (push) Failing after 0s
Test / frontend-test (push) Failing after 0s

- Added ceilingHeight and defaultTiers to FacilityFloor model
- Updated API routes and frontend client
- Updated AddFloorModal with new fields and clearer labels
This commit is contained in:
fullsizemalt 2025-12-11 12:23:49 -08:00
parent 0c970cadb7
commit da93320ccf
5 changed files with 94 additions and 8 deletions

View file

@ -0,0 +1,40 @@
# Deployment & Verification Summary
**Date:** 2025-12-11
**Environment:** `nexus-vector` (Demo)
**URL:** [https://777wolfpack.runfoo.run](https://777wolfpack.runfoo.run)
## Key Achievements
1. **Layout Designer Enhancements**
* **Property Creation:** Implemented "Add Property" workflow directly within the Layout Designer.
* **Building Creation:** Added `AddBuildingModal` and "Add New Building" button in the floor selector.
* **Floor Creation:** Integrated `AddFloorModal` for creating new floors on the fly.
* **API Updates:** Fixed `layout.routes.ts` to support complex nested queries and proper address generation.
2. **Backend & Database Fixes**
* **Schema Update:** Added bi-directional relation between `Batch` and `FacilityPlant` in `schema.prisma`.
* **Seed Script Fixed:** Corrected `seed.js` to use valid `RoleEnum` values (`GROWER`, `STAFF`) and `SupplyCategory` (`OTHER`), and removed invalid `createdById` field in Task creation.
* **Type Safety:** Resolved TypeScript errors in `audit.routes.ts` (null vs undefined), `insights.routes.ts` (Batch relations), and `messaging.routes.ts` (duplicate keys).
3. **Deployment Success**
* Successfully deployed the latest code to `nexus-vector`.
* Database migrations applied and seed data populated successfully.
* Verified frontend application loads and Layout Designer components are active.
## Verification Steps Performed
* **Build:** Backend and Frontend Docker builds completed successfully.
* **Database:** `prisma db push` and `node prisma/seed.js` executed without errors.
* **Frontend:** Verified access to Layout Designer and presence of "Setup Property" / creation controls.
## Usage Instructions
To test the new features:
1. Login as `admin@runfoo.run` / `password123`.
2. Navigate to **Layout Designer**.
3. Open the top-left dropdown (Floor Selector).
4. Use **"Add New Property"** to set up a facility.
5. Use **"Add New Building"** to add structures.
6. Use **"Add Floor"** to define levels.

View file

@ -545,6 +545,8 @@ model FacilityFloor {
number Int // 1, 2, 3... number Int // 1, 2, 3...
width Int // feet width Int // feet
height Int // feet height Int // feet
ceilingHeight Float? // feet
defaultTiers Int @default(1)
rooms FacilityRoom[] rooms FacilityRoom[]
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt

View file

@ -155,9 +155,17 @@ export async function layoutRoutes(fastify: FastifyInstance, options: FastifyPlu
fastify.post('/floors', { fastify.post('/floors', {
handler: async (request, reply) => { handler: async (request, reply) => {
try { try {
const { buildingId, name, number, width, height } = request.body as any; const { buildingId, name, number, width, height, ceilingHeight, defaultTiers } = request.body as any;
const floor = await prisma.facilityFloor.create({ const floor = await prisma.facilityFloor.create({
data: { buildingId, name, number, width: width || 200, height: height || 150 }, data: {
buildingId,
name,
number,
width: width || 200,
height: height || 150,
ceilingHeight: ceilingHeight || null,
defaultTiers: defaultTiers || 1
},
include: { rooms: true } include: { rooms: true }
}); });
return reply.status(201).send(floor); return reply.status(201).send(floor);

View file

@ -15,7 +15,9 @@ export function AddFloorModal({ isOpen, onClose, buildingId, onCreated }: AddFlo
name: '', name: '',
number: 1, number: 1,
width: 100, width: 100,
height: 100 height: 100,
ceilingHeight: undefined,
defaultTiers: 1
}); });
if (!isOpen || !buildingId) return null; if (!isOpen || !buildingId) return null;
@ -30,12 +32,14 @@ export function AddFloorModal({ isOpen, onClose, buildingId, onCreated }: AddFlo
name: formData.name.trim(), name: formData.name.trim(),
number: formData.number, number: formData.number,
width: formData.width, width: formData.width,
height: formData.height height: formData.height,
ceilingHeight: formData.ceilingHeight,
defaultTiers: formData.defaultTiers
}); });
onCreated(floor.id); onCreated(floor.id);
onClose(); onClose();
// Reset form // Reset form
setFormData({ name: '', number: formData.number + 1, width: 100, height: 100 }); setFormData({ name: '', number: formData.number + 1, width: 100, height: 100, ceilingHeight: undefined, defaultTiers: 1 });
} catch (error) { } catch (error) {
console.error('Failed to create floor:', error); console.error('Failed to create floor:', error);
} finally { } finally {
@ -75,7 +79,7 @@ export function AddFloorModal({ isOpen, onClose, buildingId, onCreated }: AddFlo
/> />
</div> </div>
<div className="grid grid-cols-3 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="block text-sm text-slate-400 mb-2">Number</label> <label className="block text-sm text-slate-400 mb-2">Number</label>
<input <input
@ -85,6 +89,18 @@ export function AddFloorModal({ isOpen, onClose, buildingId, onCreated }: AddFlo
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-4 py-3 text-white focus:border-emerald-500 outline-none" className="w-full bg-slate-800 border border-slate-700 rounded-lg px-4 py-3 text-white focus:border-emerald-500 outline-none"
/> />
</div> </div>
<div>
<label className="block text-sm text-slate-400 mb-2">Default Tiers</label>
<input
type="number"
value={formData.defaultTiers}
onChange={(e) => setFormData({ ...formData, defaultTiers: parseInt(e.target.value) || 1 })}
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-4 py-3 text-white focus:border-emerald-500 outline-none"
/>
</div>
</div>
<div className="grid grid-cols-3 gap-4">
<div> <div>
<label className="block text-sm text-slate-400 mb-2">Width (ft)</label> <label className="block text-sm text-slate-400 mb-2">Width (ft)</label>
<input <input
@ -95,7 +111,7 @@ export function AddFloorModal({ isOpen, onClose, buildingId, onCreated }: AddFlo
/> />
</div> </div>
<div> <div>
<label className="block text-sm text-slate-400 mb-2">Height (ft)</label> <label className="block text-sm text-slate-400 mb-2">Length (ft)</label>
<input <input
type="number" type="number"
value={formData.height} value={formData.height}
@ -103,6 +119,16 @@ export function AddFloorModal({ isOpen, onClose, buildingId, onCreated }: AddFlo
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-4 py-3 text-white focus:border-emerald-500 outline-none" className="w-full bg-slate-800 border border-slate-700 rounded-lg px-4 py-3 text-white focus:border-emerald-500 outline-none"
/> />
</div> </div>
<div>
<label className="block text-sm text-slate-400 mb-2">Ceiling (ft)</label>
<input
type="number"
value={formData.ceilingHeight ?? ''}
onChange={(e) => setFormData({ ...formData, ceilingHeight: e.target.value ? parseInt(e.target.value) : undefined })}
placeholder="Optional"
className="w-full bg-slate-800 border border-slate-700 rounded-lg px-4 py-3 text-white focus:border-emerald-500 outline-none"
/>
</div>
</div> </div>
</div> </div>

View file

@ -25,6 +25,8 @@ export interface LayoutFloor {
number: number; number: number;
width: number; width: number;
height: number; height: number;
ceilingHeight?: number;
defaultTiers?: number;
rooms: LayoutRoom[]; rooms: LayoutRoom[];
} }
@ -98,7 +100,15 @@ export const layoutApi = {
return response.data; return response.data;
}, },
async createFloor(data: { buildingId: string; name: string; number: number; width?: number; height?: number }): Promise<LayoutFloor> { async createFloor(data: {
buildingId: string;
name: string;
number: number;
width?: number;
height?: number;
ceilingHeight?: number;
defaultTiers?: number;
}): Promise<LayoutFloor> {
const response = await api.post('/api/layout/floors', data); const response = await api.post('/api/layout/floors', data);
return response.data; return response.data;
}, },