feat: add batch detail endpoint and fix drill-down navigation
Some checks are pending
Deploy to Production / deploy (push) Waiting to run
Test / backend-test (push) Waiting to run
Test / frontend-test (push) Waiting to run

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
This commit is contained in:
fullsizemalt 2025-12-12 19:04:16 -08:00
parent 817abb732d
commit e7be23cce4
4 changed files with 61 additions and 19 deletions

View file

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

View file

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

View file

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

View file

@ -181,7 +181,7 @@ export default function BatchDetailPage() {
const [batch, setBatch] = useState<Batch | null>(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() {
<div className="flex items-center gap-2 mb-1">
<h1 className="text-xl font-semibold text-primary">{batch.name}</h1>
<span className={`badge ${batch.stage === 'FLOWERING' ? 'badge-warning' :
batch.stage === 'VEGETATIVE' ? 'badge-success' :
'badge'
batch.stage === 'VEGETATIVE' ? 'badge-success' :
'badge'
}`}>{batch.stage}</span>
</div>
<div className="flex items-center gap-4 text-sm text-secondary">
@ -324,12 +318,22 @@ export default function BatchDetailPage() {
<div className="card">
<div className="p-4 border-b border-subtle flex items-center justify-between">
<h3 className="text-sm font-medium text-primary">Recent Activity</h3>
<span className="text-xs text-tertiary">{touchPoints.length} entries</span>
<span className="text-xs text-tertiary">{batch.touchPoints?.length || 0} entries</span>
</div>
<div className="p-4 divide-y divide-subtle">
{touchPoints.map((tp, i) => (
<TouchPoint key={i} {...tp} />
))}
{batch.touchPoints && batch.touchPoints.length > 0 ? (
batch.touchPoints.map((tp) => (
<TouchPoint
key={tp.id}
type={tp.type}
date={tp.createdAt}
user={tp.user?.name || 'Unknown'}
notes={tp.notes}
/>
))
) : (
<p className="text-sm text-tertiary text-center py-4">No activity yet</p>
)}
</div>
</div>
@ -344,7 +348,7 @@ export default function BatchDetailPage() {
<div className="text-xs text-tertiary">Days</div>
</div>
<div>
<div className="text-2xl font-semibold text-primary">{touchPoints.length}</div>
<div className="text-2xl font-semibold text-primary">{batch.touchPoints?.length || 0}</div>
<div className="text-xs text-tertiary">Touch Points</div>
</div>
<div>