feat: Implement persistence for plant placements in layout editor
Some checks are pending
Test / backend-test (push) Waiting to run
Test / frontend-test (push) Waiting to run

This commit is contained in:
fullsizemalt 2026-01-01 16:29:45 -08:00
parent ec9e98e696
commit 7ec8b1fc57
4 changed files with 34 additions and 16 deletions

View file

@ -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

View file

@ -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'

View file

@ -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);

View file

@ -246,8 +246,8 @@ export const layoutApi = {
return response.data;
},
async placeBatchInPosition(positionId: string, batchId: string): Promise<void> {
await api.post(`/layout/positions/${positionId}/occupy`, { batchId });
async occupyPosition(positionId: string, data: { batchId?: string; plantTypeId?: string }): Promise<void> {
await api.post(`/layout/positions/${positionId}/occupy`, data);
},
async fillSection(sectionId: string, batchId: string, maxCount?: number): Promise<{ plantsCreated: number; message: string }> {