From 7ec8b1fc57ad54eca5c4fb5243c0239a2d948e66 Mon Sep 17 00:00:00 2001 From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com> Date: Thu, 1 Jan 2026 16:29:45 -0800 Subject: [PATCH] feat: Implement persistence for plant placements in layout editor --- backend/prisma/schema.prisma | 2 + backend/src/routes/layout.routes.ts | 3 +- .../components/layout-editor/LayoutEditor.tsx | 41 +++++++++++++------ frontend/src/lib/layoutApi.ts | 4 +- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 56f8b05..35106e9 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -644,6 +644,8 @@ model FacilityPlant { tagNumber String @unique // METRC tag batchId String? batch Batch? @relation(fields: [batchId], references: [id]) + plantTypeId String? + plantType PlantType? @relation(fields: [plantTypeId], references: [id]) position FacilityPosition @relation(fields: [positionId], references: [id]) positionId String @unique address String // Full hierarchical address diff --git a/backend/src/routes/layout.routes.ts b/backend/src/routes/layout.routes.ts index 3970f85..e9cbeae 100644 --- a/backend/src/routes/layout.routes.ts +++ b/backend/src/routes/layout.routes.ts @@ -591,7 +591,7 @@ export async function layoutRoutes(fastify: FastifyInstance, options: FastifyPlu handler: async (request, reply) => { try { const { id } = request.params as any; - const { batchId } = request.body as any; + const { batchId, plantTypeId } = request.body as any; const position = await prisma.facilityPosition.findUnique({ where: { id }, @@ -614,6 +614,7 @@ export async function layoutRoutes(fastify: FastifyInstance, options: FastifyPlu data: { tagNumber: `P-${Date.now()}-${Math.floor(Math.random() * 1000)}`, batchId, + plantTypeId, positionId: id, address, status: 'ACTIVE' diff --git a/frontend/src/components/layout-editor/LayoutEditor.tsx b/frontend/src/components/layout-editor/LayoutEditor.tsx index bc2d8ba..208acb6 100644 --- a/frontend/src/components/layout-editor/LayoutEditor.tsx +++ b/frontend/src/components/layout-editor/LayoutEditor.tsx @@ -55,15 +55,23 @@ export function LayoutEditor({ floorId, className }: LayoutEditorProps) { rows: section.rows, columns: section.columns, tiers: 1, // TODO: get from section - slots: section.positions.map(pos => ({ - id: pos.id, - row: pos.row, - column: pos.column, - tier: pos.tier, - status: pos.status as PlantSlotData['status'], - plantType: pos.plant ? plantTypes.find(pt => pt.strain === pos.plant?.strain) : undefined, - tagNumber: pos.plant?.tagNumber, - })), + slots: section.positions.map(pos => { + // Find plant type by ID (new way) or strain name (legacy way) + const mappedPlantType = pos.plant + ? plantTypes.find(pt => pt.id === pos.plant?.plantTypeId) || + plantTypes.find(pt => pt.strain === pos.plant?.strain) + : undefined; + + return { + id: pos.id, + row: pos.row, + column: pos.column, + tier: pos.tier, + status: pos.status as PlantSlotData['status'], + plantType: mappedPlantType, + tagNumber: pos.plant?.tagNumber, + }; + }), })) ) || []; @@ -72,10 +80,17 @@ export function LayoutEditor({ floorId, className }: LayoutEditorProps) { }, []); const handleSlotDrop = useCallback(async (slot: PlantSlotData, plantType: LayoutPlantType) => { - console.log('Dropped', plantType.name, 'onto slot', slot.id); - // TODO: Implement actual placement via API - // For now, just log the action - }, []); + try { + await layoutApi.occupyPosition(slot.id, { plantTypeId: plantType.id }); + + // Reload floor data to reflect changes + const floor = await layoutApi.getFloor3D(floorId); + setFloorData(floor); + } catch (e) { + console.error('Failed to place plant:', e); + setError('Failed to place plant. Please try again.'); + } + }, [floorId]); const handleDragStart = useCallback((plantType: LayoutPlantType) => { setDraggingType(plantType); diff --git a/frontend/src/lib/layoutApi.ts b/frontend/src/lib/layoutApi.ts index c6f94b9..b928e27 100644 --- a/frontend/src/lib/layoutApi.ts +++ b/frontend/src/lib/layoutApi.ts @@ -246,8 +246,8 @@ export const layoutApi = { return response.data; }, - async placeBatchInPosition(positionId: string, batchId: string): Promise { - await api.post(`/layout/positions/${positionId}/occupy`, { batchId }); + async occupyPosition(positionId: string, data: { batchId?: string; plantTypeId?: string }): Promise { + await api.post(`/layout/positions/${positionId}/occupy`, data); }, async fillSection(sectionId: string, batchId: string, maxCount?: number): Promise<{ plantsCreated: number; message: string }> {