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
This commit is contained in:
parent
817abb732d
commit
e7be23cce4
4 changed files with 61 additions and 19 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue