diff --git a/backend/src/controllers/walkthrough.controller.ts b/backend/src/controllers/walkthrough.controller.ts new file mode 100644 index 0000000..0fa8abe --- /dev/null +++ b/backend/src/controllers/walkthrough.controller.ts @@ -0,0 +1,267 @@ +import { FastifyRequest, FastifyReply } from 'fastify'; + +interface CreateWalkthroughBody { + date?: string; +} + +interface AddReservoirCheckBody { + tankName: string; + tankType: 'VEG' | 'FLOWER'; + levelPercent: number; + status: 'OK' | 'LOW' | 'CRITICAL'; + photoUrl?: string; + notes?: string; +} + +interface AddIrrigationCheckBody { + zoneName: string; + drippersTotal: number; + drippersWorking: number; + drippersFailed?: string[]; + waterFlow: boolean; + nutrientsMixed: boolean; + scheduleActive: boolean; + photoUrl?: string; + issues?: string; +} + +interface AddPlantHealthCheckBody { + zoneName: string; + healthStatus: 'GOOD' | 'FAIR' | 'NEEDS_ATTENTION'; + pestsObserved: boolean; + pestType?: string; + waterAccess: 'OK' | 'ISSUES'; + foodAccess: 'OK' | 'ISSUES'; + flaggedForAttention?: boolean; + issuePhotoUrl?: string; + referencePhotoUrl?: string; + notes?: string; +} + +/** + * Start a new daily walkthrough + */ +export const createWalkthrough = async (request: FastifyRequest, reply: FastifyReply) => { + const { date } = request.body as CreateWalkthroughBody; + const userId = (request.user as any)?.userId; + + if (!userId) { + return reply.code(401).send({ message: 'Unauthorized' }); + } + + try { + const walkthrough = await request.server.prisma.dailyWalkthrough.create({ + data: { + date: date ? new Date(date) : new Date(), + completedBy: userId, + status: 'IN_PROGRESS', + }, + include: { + user: { + select: { + id: true, + name: true, + email: true, + role: true, + }, + }, + reservoirChecks: true, + irrigationChecks: true, + plantHealthChecks: true, + }, + }); + + return reply.code(201).send(walkthrough); + } catch (error) { + request.log.error(error); + return reply.code(500).send({ message: 'Failed to create walkthrough' }); + } +}; + +/** + * Get all walkthroughs (with optional filters) + */ +export const getWalkthroughs = async (request: FastifyRequest, reply: FastifyReply) => { + const { status, startDate, endDate, userId } = request.query as any; + + try { + const walkthroughs = await request.server.prisma.dailyWalkthrough.findMany({ + where: { + ...(status && { status }), + ...(userId && { completedBy: userId }), + ...(startDate && endDate && { + date: { + gte: new Date(startDate), + lte: new Date(endDate), + }, + }), + }, + include: { + user: { + select: { + id: true, + name: true, + email: true, + role: true, + }, + }, + reservoirChecks: true, + irrigationChecks: true, + plantHealthChecks: true, + }, + orderBy: { + date: 'desc', + }, + }); + + return walkthroughs; + } catch (error) { + request.log.error(error); + return reply.code(500).send({ message: 'Failed to fetch walkthroughs' }); + } +}; + +/** + * Get a single walkthrough by ID + */ +export const getWalkthrough = async (request: FastifyRequest, reply: FastifyReply) => { + const { id } = request.params as { id: string }; + + try { + const walkthrough = await request.server.prisma.dailyWalkthrough.findUnique({ + where: { id }, + include: { + user: { + select: { + id: true, + name: true, + email: true, + role: true, + }, + }, + reservoirChecks: true, + irrigationChecks: true, + plantHealthChecks: true, + }, + }); + + if (!walkthrough) { + return reply.code(404).send({ message: 'Walkthrough not found' }); + } + + return walkthrough; + } catch (error) { + request.log.error(error); + return reply.code(500).send({ message: 'Failed to fetch walkthrough' }); + } +}; + +/** + * Complete a walkthrough + */ +export const completeWalkthrough = async (request: FastifyRequest, reply: FastifyReply) => { + const { id } = request.params as { id: string }; + + try { + const walkthrough = await request.server.prisma.dailyWalkthrough.update({ + where: { id }, + data: { + status: 'COMPLETED', + endTime: new Date(), + }, + include: { + user: { + select: { + id: true, + name: true, + email: true, + role: true, + }, + }, + reservoirChecks: true, + irrigationChecks: true, + plantHealthChecks: true, + }, + }); + + return walkthrough; + } catch (error) { + request.log.error(error); + return reply.code(500).send({ message: 'Failed to complete walkthrough' }); + } +}; + +/** + * Add a reservoir check to a walkthrough + */ +export const addReservoirCheck = async (request: FastifyRequest, reply: FastifyReply) => { + const { id } = request.params as { id: string }; + const checkData = request.body as AddReservoirCheckBody; + + try { + const check = await request.server.prisma.reservoirCheck.create({ + data: { + walkthroughId: id, + ...checkData, + }, + }); + + return reply.code(201).send(check); + } catch (error) { + request.log.error(error); + return reply.code(500).send({ message: 'Failed to add reservoir check' }); + } +}; + +/** + * Add an irrigation check to a walkthrough + */ +export const addIrrigationCheck = async (request: FastifyRequest, reply: FastifyReply) => { + const { id } = request.params as { id: string }; + const checkData = request.body as AddIrrigationCheckBody; + + try { + const check = await request.server.prisma.irrigationCheck.create({ + data: { + walkthroughId: id, + zoneName: checkData.zoneName, + drippersTotal: checkData.drippersTotal, + drippersWorking: checkData.drippersWorking, + drippersFailed: checkData.drippersFailed ? JSON.stringify(checkData.drippersFailed) : null, + waterFlow: checkData.waterFlow, + nutrientsMixed: checkData.nutrientsMixed, + scheduleActive: checkData.scheduleActive, + photoUrl: checkData.photoUrl, + issues: checkData.issues, + }, + }); + + return reply.code(201).send(check); + } catch (error) { + request.log.error(error); + return reply.code(500).send({ message: 'Failed to add irrigation check' }); + } +}; + +/** + * Add a plant health check to a walkthrough + */ +export const addPlantHealthCheck = async (request: FastifyRequest, reply: FastifyReply) => { + const { id } = request.params as { id: string }; + const checkData = request.body as AddPlantHealthCheckBody; + + try { + const check = await request.server.prisma.plantHealthCheck.create({ + data: { + walkthroughId: id, + ...checkData, + flaggedForAttention: checkData.flaggedForAttention || false, + }, + }); + + return reply.code(201).send(check); + } catch (error) { + request.log.error(error); + return reply.code(500).send({ message: 'Failed to add plant health check' }); + } +}; diff --git a/backend/src/routes/walkthrough.routes.ts b/backend/src/routes/walkthrough.routes.ts new file mode 100644 index 0000000..6d74898 --- /dev/null +++ b/backend/src/routes/walkthrough.routes.ts @@ -0,0 +1,23 @@ +import { FastifyInstance } from 'fastify'; +import { + createWalkthrough, + getWalkthroughs, + getWalkthrough, + completeWalkthrough, + addReservoirCheck, + addIrrigationCheck, + addPlantHealthCheck, +} from '../controllers/walkthrough.controller'; + +export async function walkthroughRoutes(server: FastifyInstance) { + // Walkthrough CRUD + server.post('/', createWalkthrough); + server.get('/', getWalkthroughs); + server.get('/:id', getWalkthrough); + server.post('/:id/complete', completeWalkthrough); + + // Add checks to walkthrough + server.post('/:id/reservoir-checks', addReservoirCheck); + server.post('/:id/irrigation-checks', addIrrigationCheck); + server.post('/:id/plant-health-checks', addPlantHealthCheck); +} diff --git a/backend/src/server.ts b/backend/src/server.ts index b710dc1..85f1f4e 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -6,6 +6,7 @@ import { authRoutes } from './routes/auth.routes'; import { roomRoutes } from './routes/rooms.routes'; import { batchRoutes } from './routes/batches.routes'; import { timeclockRoutes } from './routes/timeclock.routes'; +import { walkthroughRoutes } from './routes/walkthrough.routes'; dotenv.config(); @@ -24,6 +25,7 @@ server.register(authRoutes, { prefix: '/api/auth' }); server.register(roomRoutes, { prefix: '/api/rooms' }); server.register(batchRoutes, { prefix: '/api/batches' }); server.register(timeclockRoutes, { prefix: '/api/timeclock' }); +server.register(walkthroughRoutes, { prefix: '/api/walkthroughs' }); server.get('/api/healthz', async (request, reply) => { return { status: 'ok', timestamp: new Date().toISOString() }; diff --git a/docs/DAILY-WALKTHROUGH-PROGRESS.md b/docs/DAILY-WALKTHROUGH-PROGRESS.md new file mode 100644 index 0000000..88fc952 --- /dev/null +++ b/docs/DAILY-WALKTHROUGH-PROGRESS.md @@ -0,0 +1,245 @@ +# Daily Walkthrough - Implementation Progress + +**Date**: 2025-12-09 +**Status**: 🟡 In Progress (Database Complete) +**Team**: 777 Wolfpack + +--- + +## ✅ Completed + +### Database Schema (100%) + +- [x] Created 4 new models +- [x] Created 5 new enums +- [x] Added User → DailyWalkthrough relationship +- [x] Configured cascade deletes +- [x] Generated Prisma Client + +**Files Modified**: + +- `backend/prisma/schema.prisma` + +--- + +## ⏳ In Progress + +### Backend API (0%) + +- [ ] Create walkthrough controller +- [ ] Create walkthrough routes +- [ ] Implement CRUD endpoints +- [ ] Add photo upload handling +- [ ] Create notification triggers + +### Frontend UI (0%) + +- [ ] Create Daily Walkthrough page +- [ ] Build guided checklist UI +- [ ] Implement photo capture +- [ ] Add offline support +- [ ] Create summary/review screen + +--- + +## 📋 Next Steps + +### Step 1: Run Migration (On Deployment) + +```bash +# On nexus-vector or local with DATABASE_URL +cd /srv/containers/ca-grow-ops-manager/backend +npx prisma migrate dev --name add_daily_walkthrough +``` + +### Step 2: Create Backend API (3-4 hours) + +**Files to Create**: + +- `backend/src/controllers/walkthrough.controller.ts` +- `backend/src/routes/walkthrough.routes.ts` + +**Endpoints Needed**: + +- `POST /api/walkthroughs` - Start new walkthrough +- `GET /api/walkthroughs` - List walkthroughs (with filters) +- `GET /api/walkthroughs/:id` - Get walkthrough detail +- `PATCH /api/walkthroughs/:id` - Update walkthrough +- `POST /api/walkthroughs/:id/complete` - Mark complete +- `POST /api/walkthroughs/:id/reservoir-checks` - Add reservoir check +- `POST /api/walkthroughs/:id/irrigation-checks` - Add irrigation check +- `POST /api/walkthroughs/:id/plant-health-checks` - Add plant health check +- `POST /api/upload/photo` - Upload photo + +### Step 3: Create Frontend UI (4-5 hours) + +**Files to Create**: + +- `frontend/src/pages/DailyWalkthroughPage.tsx` +- `frontend/src/components/walkthrough/WalkthroughChecklist.tsx` +- `frontend/src/components/walkthrough/ReservoirCheckForm.tsx` +- `frontend/src/components/walkthrough/IrrigationCheckForm.tsx` +- `frontend/src/components/walkthrough/PlantHealthCheckForm.tsx` +- `frontend/src/components/walkthrough/PhotoCapture.tsx` + +**Features**: + +- Mobile-first, touch-friendly UI +- Photo capture with camera +- Offline support (IndexedDB) +- Progress indicator +- Summary/review screen + +### Step 4: Testing (1-2 hours) + +- [ ] Test complete walkthrough flow +- [ ] Test photo upload +- [ ] Test offline mode +- [ ] Test on actual iPad +- [ ] Get 777 Wolfpack feedback + +--- + +## 🗄️ Database Schema Reference + +### DailyWalkthrough + +```typescript +{ + id: string + date: DateTime + completedBy: string (User ID) + startTime: DateTime + endTime?: DateTime + status: 'IN_PROGRESS' | 'COMPLETED' | 'INCOMPLETE' + reservoirChecks: ReservoirCheck[] + irrigationChecks: IrrigationCheck[] + plantHealthChecks: PlantHealthCheck[] +} +``` + +### ReservoirCheck + +```typescript +{ + id: string + walkthroughId: string + tankName: string + tankType: 'VEG' | 'FLOWER' + levelPercent: number (0-100) + status: 'OK' | 'LOW' | 'CRITICAL' + photoUrl?: string + notes?: string +} +``` + +### IrrigationCheck + +```typescript +{ + id: string + walkthroughId: string + zoneName: string // "Veg Upstairs", etc. + drippersTotal: number + drippersWorking: number + drippersFailed?: string // JSON array + waterFlow: boolean + nutrientsMixed: boolean + scheduleActive: boolean + photoUrl?: string + issues?: string +} +``` + +### PlantHealthCheck + +```typescript +{ + id: string + walkthroughId: string + zoneName: string + healthStatus: 'GOOD' | 'FAIR' | 'NEEDS_ATTENTION' + pestsObserved: boolean + pestType?: string + waterAccess: 'OK' | 'ISSUES' + foodAccess: 'OK' | 'ISSUES' + flaggedForAttention: boolean + issuePhotoUrl?: string + referencePhotoUrl?: string + notes?: string +} +``` + +--- + +## 📊 Progress Tracker + +| Task | Status | Time | Assignee | +|------|--------|------|----------| +| Database Schema | ✅ Complete | 1h | Done | +| Prisma Migration | ⏳ Pending | 5min | Deployment | +| Backend API | ⬜ Not Started | 3-4h | Next | +| Frontend UI | ⬜ Not Started | 4-5h | After API | +| Testing | ⬜ Not Started | 1-2h | Final | + +**Total Estimated**: 8-12 hours remaining +**Completed**: 1 hour (10%) + +--- + +## 🚀 Deployment Notes + +### Migration on nexus-vector + +```bash +# SSH to server +ssh admin@nexus-vector + +# Navigate to project +cd /srv/containers/ca-grow-ops-manager + +# Pull latest code (when Forgejo is back up) +git pull origin main + +# Run migration +docker compose exec backend npx prisma migrate dev --name add_daily_walkthrough + +# Restart backend +docker compose restart backend +``` + +### Environment Variables + +No new environment variables needed. + +### Breaking Changes + +None. This is additive only. + +--- + +## 📝 Notes for 777 Wolfpack Team + +### What's Ready + +- ✅ Database structure for all walkthrough data +- ✅ Support for photos +- ✅ Zone-based organization +- ✅ Audit trail (timestamps, user attribution) + +### What's Next + +- Backend API (so app can save walkthrough data) +- Mobile UI (guided checklist on tablet) +- Photo upload (camera integration) + +### Timeline + +- Backend API: 3-4 hours +- Frontend UI: 4-5 hours +- Testing: 1-2 hours +- **Total**: 8-12 hours (1-1.5 days) + +--- + +**Status**: Database foundation complete! Ready for backend API implementation.