From e7be23cce4ec40b45d198576e308d145fc138e08 Mon Sep 17 00:00:00 2001 From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com> Date: Fri, 12 Dec 2025 19:04:16 -0800 Subject: [PATCH] feat: add batch detail endpoint and fix drill-down navigation Backend: - Add getBatchById controller with touch points and IPM schedule - Add GET /batches/:id route Frontend: - Update Batch interface to include touchPoints - BatchDetailPage now uses real touch points from API - Better error handling on batch load failure --- backend/src/controllers/batches.controller.ts | 27 +++++++++++++ backend/src/routes/batches.routes.ts | 3 +- frontend/src/lib/batchesApi.ts | 10 +++++ frontend/src/pages/BatchDetailPage.tsx | 40 ++++++++++--------- 4 files changed, 61 insertions(+), 19 deletions(-) diff --git a/backend/src/controllers/batches.controller.ts b/backend/src/controllers/batches.controller.ts index 6dd8360..d87da73 100644 --- a/backend/src/controllers/batches.controller.ts +++ b/backend/src/controllers/batches.controller.ts @@ -17,6 +17,33 @@ export const getBatches = async (request: FastifyRequest, reply: FastifyReply) = return batches; }; +export const getBatchById = async (request: FastifyRequest, reply: FastifyReply) => { + const { id } = request.params as { id: string }; + + const batch = await request.server.prisma.batch.findUnique({ + where: { id }, + include: { + room: true, + ipmSchedule: true, + touchPoints: { + orderBy: { createdAt: 'desc' }, + take: 10, + include: { + user: { + select: { id: true, name: true } + } + } + } + } + }); + + if (!batch) { + return reply.status(404).send({ message: 'Batch not found' }); + } + + return batch; +}; + export const createBatch = async (request: FastifyRequest, reply: FastifyReply) => { const { diff --git a/backend/src/routes/batches.routes.ts b/backend/src/routes/batches.routes.ts index 4a54df0..685d0fb 100644 --- a/backend/src/routes/batches.routes.ts +++ b/backend/src/routes/batches.routes.ts @@ -1,5 +1,5 @@ import { FastifyInstance } from 'fastify'; -import { getBatches, createBatch, updateBatch } from '../controllers/batches.controller'; +import { getBatches, getBatchById, createBatch, updateBatch } from '../controllers/batches.controller'; export async function batchRoutes(server: FastifyInstance) { server.addHook('onRequest', async (request) => { @@ -11,6 +11,7 @@ export async function batchRoutes(server: FastifyInstance) { }); server.get('/', getBatches); + server.get('/:id', getBatchById); server.post('/', createBatch); server.put('/:id', updateBatch); } diff --git a/frontend/src/lib/batchesApi.ts b/frontend/src/lib/batchesApi.ts index ef71ca5..9544c40 100644 --- a/frontend/src/lib/batchesApi.ts +++ b/frontend/src/lib/batchesApi.ts @@ -21,6 +21,16 @@ export interface Batch { intervalDays: number; product?: string; }; + touchPoints?: { + id: string; + type: string; + notes?: string; + createdAt: string; + user?: { + id: string; + name: string; + }; + }[]; } export const batchesApi = { diff --git a/frontend/src/pages/BatchDetailPage.tsx b/frontend/src/pages/BatchDetailPage.tsx index 46b0921..17d6cf8 100644 --- a/frontend/src/pages/BatchDetailPage.tsx +++ b/frontend/src/pages/BatchDetailPage.tsx @@ -181,7 +181,7 @@ export default function BatchDetailPage() { const [batch, setBatch] = useState(null); const [loading, setLoading] = useState(true); - // Mock sensor data + // Mock sensor data (will be replaced with real sensor API) const [sensorData] = useState(() => ({ temperature: generateMockData(14, 72, 78), humidity: generateMockData(14, 50, 65), @@ -190,20 +190,14 @@ export default function BatchDetailPage() { lightPPFD: generateMockData(14, 600, 900), })); - // Mock touch points - const [touchPoints] = useState(() => [ - { type: 'INSPECT', date: new Date().toISOString(), user: 'Alex', notes: 'Looking healthy' }, - { type: 'WATER', date: new Date(Date.now() - 86400000).toISOString(), user: 'Jordan' }, - { type: 'FEED', date: new Date(Date.now() - 172800000).toISOString(), user: 'Alex', notes: 'Week 3 flower nutrients' }, - { type: 'PHOTO', date: new Date(Date.now() - 259200000).toISOString(), user: 'Sam', notes: 'Progress photo' }, - { type: 'INSPECT', date: new Date(Date.now() - 345600000).toISOString(), user: 'Jordan', notes: 'No issues' }, - ]); - useEffect(() => { if (id) { batchesApi.getById(id) .then(setBatch) - .catch(() => navigate('/batches')) + .catch((err) => { + console.error('Failed to load batch:', err); + navigate('/batches'); + }) .finally(() => setLoading(false)); } }, [id, navigate]); @@ -243,8 +237,8 @@ export default function BatchDetailPage() {

{batch.name}

{batch.stage}
@@ -324,12 +318,22 @@ export default function BatchDetailPage() {

Recent Activity

- {touchPoints.length} entries + {batch.touchPoints?.length || 0} entries
- {touchPoints.map((tp, i) => ( - - ))} + {batch.touchPoints && batch.touchPoints.length > 0 ? ( + batch.touchPoints.map((tp) => ( + + )) + ) : ( +

No activity yet

+ )}
@@ -344,7 +348,7 @@ export default function BatchDetailPage() {
Days
-
{touchPoints.length}
+
{batch.touchPoints?.length || 0}
Touch Points