feat: Daily Walkthrough Frontend - Start + Reservoir Checks
🎨 Frontend UI Implementation (Phase 1.5) 📁 Files Created: - frontend/src/pages/DailyWalkthroughPage.tsx - frontend/src/components/walkthrough/ReservoirChecklist.tsx ✨ Features: - Mobile-first walkthrough start screen - 777 Wolfpack branding - Progress tracking - Visual tank level indicator - Touch-friendly slider (44px+ targets) - Auto status detection (OK/LOW/CRITICAL) - Color-coded status badges - Notes field - Photo upload placeholder - Responsive design (mobile → tablet → desktop) 📱 Mobile Optimizations: - Large tap targets (56px buttons) - Visual feedback (active states) - Bottom navigation on mobile - Gradient backgrounds - Backdrop blur effects - Touch-friendly sliders 🎨 UX Features: - Step-by-step wizard - Progress bar - Tank-by-tank workflow - Auto-save ready - Back navigation - Clear status indicators ⏭️ Next: Irrigation + Plant Health checklists Status: 40% Frontend Complete
This commit is contained in:
parent
e538227458
commit
d156569d99
3 changed files with 759 additions and 0 deletions
379
docs/API-WALKTHROUGH.md
Normal file
379
docs/API-WALKTHROUGH.md
Normal file
|
|
@ -0,0 +1,379 @@
|
||||||
|
# Daily Walkthrough API Documentation
|
||||||
|
|
||||||
|
**Base URL**: `/api/walkthroughs`
|
||||||
|
**Authentication**: Required (JWT Bearer token)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
### 1. Start New Walkthrough
|
||||||
|
|
||||||
|
**POST** `/api/walkthroughs`
|
||||||
|
|
||||||
|
**Request Body**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"date": "2025-12-09T08:00:00Z" // optional, defaults to now
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response** (201):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "uuid",
|
||||||
|
"date": "2025-12-09T08:00:00Z",
|
||||||
|
"completedBy": "user-uuid",
|
||||||
|
"startTime": "2025-12-09T08:00:00Z",
|
||||||
|
"endTime": null,
|
||||||
|
"status": "IN_PROGRESS",
|
||||||
|
"user": {
|
||||||
|
"id": "user-uuid",
|
||||||
|
"name": "Facility Owner",
|
||||||
|
"email": "admin@runfoo.run",
|
||||||
|
"role": "OWNER"
|
||||||
|
},
|
||||||
|
"reservoirChecks": [],
|
||||||
|
"irrigationChecks": [],
|
||||||
|
"plantHealthChecks": [],
|
||||||
|
"createdAt": "2025-12-09T08:00:00Z",
|
||||||
|
"updatedAt": "2025-12-09T08:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. List Walkthroughs
|
||||||
|
|
||||||
|
**GET** `/api/walkthroughs`
|
||||||
|
|
||||||
|
**Query Parameters**:
|
||||||
|
|
||||||
|
- `status` - Filter by status (`IN_PROGRESS`, `COMPLETED`, `INCOMPLETE`)
|
||||||
|
- `userId` - Filter by user ID
|
||||||
|
- `startDate` - Filter by start date (ISO 8601)
|
||||||
|
- `endDate` - Filter by end date (ISO 8601)
|
||||||
|
|
||||||
|
**Example**:
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/walkthroughs?status=COMPLETED&startDate=2025-12-01&endDate=2025-12-09
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response** (200):
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "uuid",
|
||||||
|
"date": "2025-12-09T08:00:00Z",
|
||||||
|
"completedBy": "user-uuid",
|
||||||
|
"startTime": "2025-12-09T08:00:00Z",
|
||||||
|
"endTime": "2025-12-09T08:25:00Z",
|
||||||
|
"status": "COMPLETED",
|
||||||
|
"user": { ... },
|
||||||
|
"reservoirChecks": [ ... ],
|
||||||
|
"irrigationChecks": [ ... ],
|
||||||
|
"plantHealthChecks": [ ... ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Get Walkthrough Detail
|
||||||
|
|
||||||
|
**GET** `/api/walkthroughs/:id`
|
||||||
|
|
||||||
|
**Response** (200):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "uuid",
|
||||||
|
"date": "2025-12-09T08:00:00Z",
|
||||||
|
"completedBy": "user-uuid",
|
||||||
|
"startTime": "2025-12-09T08:00:00Z",
|
||||||
|
"endTime": null,
|
||||||
|
"status": "IN_PROGRESS",
|
||||||
|
"user": { ... },
|
||||||
|
"reservoirChecks": [ ... ],
|
||||||
|
"irrigationChecks": [ ... ],
|
||||||
|
"plantHealthChecks": [ ... ]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error** (404):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Walkthrough not found"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Complete Walkthrough
|
||||||
|
|
||||||
|
**POST** `/api/walkthroughs/:id/complete`
|
||||||
|
|
||||||
|
**Response** (200):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "uuid",
|
||||||
|
"status": "COMPLETED",
|
||||||
|
"endTime": "2025-12-09T08:25:00Z",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Add Reservoir Check
|
||||||
|
|
||||||
|
**POST** `/api/walkthroughs/:id/reservoir-checks`
|
||||||
|
|
||||||
|
**Request Body**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tankName": "Veg Tank 1",
|
||||||
|
"tankType": "VEG",
|
||||||
|
"levelPercent": 85,
|
||||||
|
"status": "OK",
|
||||||
|
"photoUrl": "https://...",
|
||||||
|
"notes": "All good"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Field Validation**:
|
||||||
|
|
||||||
|
- `tankName`: string (required)
|
||||||
|
- `tankType`: `"VEG"` | `"FLOWER"` (required)
|
||||||
|
- `levelPercent`: number 0-100 (required)
|
||||||
|
- `status`: `"OK"` | `"LOW"` | `"CRITICAL"` (required)
|
||||||
|
- `photoUrl`: string (optional)
|
||||||
|
- `notes`: string (optional)
|
||||||
|
|
||||||
|
**Response** (201):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "uuid",
|
||||||
|
"walkthroughId": "walkthrough-uuid",
|
||||||
|
"tankName": "Veg Tank 1",
|
||||||
|
"tankType": "VEG",
|
||||||
|
"levelPercent": 85,
|
||||||
|
"status": "OK",
|
||||||
|
"photoUrl": "https://...",
|
||||||
|
"notes": "All good",
|
||||||
|
"createdAt": "2025-12-09T08:05:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Add Irrigation Check
|
||||||
|
|
||||||
|
**POST** `/api/walkthroughs/:id/irrigation-checks`
|
||||||
|
|
||||||
|
**Request Body**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"zoneName": "Veg Upstairs",
|
||||||
|
"drippersTotal": 48,
|
||||||
|
"drippersWorking": 47,
|
||||||
|
"drippersFailed": ["A-12"],
|
||||||
|
"waterFlow": true,
|
||||||
|
"nutrientsMixed": true,
|
||||||
|
"scheduleActive": true,
|
||||||
|
"photoUrl": "https://...",
|
||||||
|
"issues": "Dripper A-12 clogged"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Field Validation**:
|
||||||
|
|
||||||
|
- `zoneName`: string (required) - e.g., "Veg Upstairs", "Flower Downstairs"
|
||||||
|
- `drippersTotal`: number (required)
|
||||||
|
- `drippersWorking`: number (required)
|
||||||
|
- `drippersFailed`: string[] (optional) - array of dripper IDs
|
||||||
|
- `waterFlow`: boolean (required)
|
||||||
|
- `nutrientsMixed`: boolean (required)
|
||||||
|
- `scheduleActive`: boolean (required)
|
||||||
|
- `photoUrl`: string (optional)
|
||||||
|
- `issues`: string (optional)
|
||||||
|
|
||||||
|
**Response** (201):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "uuid",
|
||||||
|
"walkthroughId": "walkthrough-uuid",
|
||||||
|
"zoneName": "Veg Upstairs",
|
||||||
|
"drippersTotal": 48,
|
||||||
|
"drippersWorking": 47,
|
||||||
|
"drippersFailed": "[\"A-12\"]",
|
||||||
|
"waterFlow": true,
|
||||||
|
"nutrientsMixed": true,
|
||||||
|
"scheduleActive": true,
|
||||||
|
"photoUrl": "https://...",
|
||||||
|
"issues": "Dripper A-12 clogged",
|
||||||
|
"createdAt": "2025-12-09T08:10:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Add Plant Health Check
|
||||||
|
|
||||||
|
**POST** `/api/walkthroughs/:id/plant-health-checks`
|
||||||
|
|
||||||
|
**Request Body**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"zoneName": "Flower Upstairs",
|
||||||
|
"healthStatus": "GOOD",
|
||||||
|
"pestsObserved": false,
|
||||||
|
"pestType": null,
|
||||||
|
"waterAccess": "OK",
|
||||||
|
"foodAccess": "OK",
|
||||||
|
"flaggedForAttention": false,
|
||||||
|
"issuePhotoUrl": null,
|
||||||
|
"referencePhotoUrl": "https://...",
|
||||||
|
"notes": "All plants healthy"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Field Validation**:
|
||||||
|
|
||||||
|
- `zoneName`: string (required)
|
||||||
|
- `healthStatus`: `"GOOD"` | `"FAIR"` | `"NEEDS_ATTENTION"` (required)
|
||||||
|
- `pestsObserved`: boolean (required)
|
||||||
|
- `pestType`: string (optional) - required if `pestsObserved` is true
|
||||||
|
- `waterAccess`: `"OK"` | `"ISSUES"` (required)
|
||||||
|
- `foodAccess`: `"OK"` | `"ISSUES"` (required)
|
||||||
|
- `flaggedForAttention`: boolean (optional, defaults to false)
|
||||||
|
- `issuePhotoUrl`: string (optional)
|
||||||
|
- `referencePhotoUrl`: string (optional)
|
||||||
|
- `notes`: string (optional)
|
||||||
|
|
||||||
|
**Response** (201):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "uuid",
|
||||||
|
"walkthroughId": "walkthrough-uuid",
|
||||||
|
"zoneName": "Flower Upstairs",
|
||||||
|
"healthStatus": "GOOD",
|
||||||
|
"pestsObserved": false,
|
||||||
|
"pestType": null,
|
||||||
|
"waterAccess": "OK",
|
||||||
|
"foodAccess": "OK",
|
||||||
|
"flaggedForAttention": false,
|
||||||
|
"issuePhotoUrl": null,
|
||||||
|
"referencePhotoUrl": "https://...",
|
||||||
|
"notes": "All plants healthy",
|
||||||
|
"createdAt": "2025-12-09T08:15:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Responses
|
||||||
|
|
||||||
|
### 401 Unauthorized
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Unauthorized"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 404 Not Found
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Walkthrough not found"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 500 Internal Server Error
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Failed to create walkthrough"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example Workflow
|
||||||
|
|
||||||
|
### Complete Morning Walkthrough
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Start walkthrough
|
||||||
|
curl -X POST https://777wolfpack.runfoo.run/api/walkthroughs \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{}'
|
||||||
|
|
||||||
|
# Response: { "id": "walk-123", ... }
|
||||||
|
|
||||||
|
# 2. Add reservoir checks (4 tanks)
|
||||||
|
curl -X POST https://777wolfpack.runfoo.run/api/walkthroughs/walk-123/reservoir-checks \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"tankName": "Veg Tank 1",
|
||||||
|
"tankType": "VEG",
|
||||||
|
"levelPercent": 85,
|
||||||
|
"status": "OK"
|
||||||
|
}'
|
||||||
|
|
||||||
|
# 3. Add irrigation checks (4 zones)
|
||||||
|
curl -X POST https://777wolfpack.runfoo.run/api/walkthroughs/walk-123/irrigation-checks \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"zoneName": "Veg Upstairs",
|
||||||
|
"drippersTotal": 48,
|
||||||
|
"drippersWorking": 48,
|
||||||
|
"drippersFailed": [],
|
||||||
|
"waterFlow": true,
|
||||||
|
"nutrientsMixed": true,
|
||||||
|
"scheduleActive": true
|
||||||
|
}'
|
||||||
|
|
||||||
|
# 4. Add plant health checks (4 zones)
|
||||||
|
curl -X POST https://777wolfpack.runfoo.run/api/walkthroughs/walk-123/plant-health-checks \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"zoneName": "Veg Upstairs",
|
||||||
|
"healthStatus": "GOOD",
|
||||||
|
"pestsObserved": false,
|
||||||
|
"waterAccess": "OK",
|
||||||
|
"foodAccess": "OK"
|
||||||
|
}'
|
||||||
|
|
||||||
|
# 5. Complete walkthrough
|
||||||
|
curl -X POST https://777wolfpack.runfoo.run/api/walkthroughs/walk-123/complete \
|
||||||
|
-H "Authorization: Bearer $TOKEN"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- All endpoints require authentication
|
||||||
|
- User is automatically attributed from JWT token
|
||||||
|
- Photos are stored as URLs (upload endpoint TBD)
|
||||||
|
- Failed drippers are stored as JSON string
|
||||||
|
- Cascade delete: deleting walkthrough deletes all checks
|
||||||
|
- Timestamps are UTC ISO 8601 format
|
||||||
206
frontend/src/components/walkthrough/ReservoirChecklist.tsx
Normal file
206
frontend/src/components/walkthrough/ReservoirChecklist.tsx
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
interface Tank {
|
||||||
|
name: string;
|
||||||
|
type: 'VEG' | 'FLOWER';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReservoirCheckData {
|
||||||
|
tankName: string;
|
||||||
|
tankType: 'VEG' | 'FLOWER';
|
||||||
|
levelPercent: number;
|
||||||
|
status: 'OK' | 'LOW' | 'CRITICAL';
|
||||||
|
photoUrl?: string;
|
||||||
|
notes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReservoirChecklistProps {
|
||||||
|
onComplete: (checks: ReservoirCheckData[]) => void;
|
||||||
|
onBack: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ReservoirChecklist({ onComplete, onBack }: ReservoirChecklistProps) {
|
||||||
|
const tanks: Tank[] = [
|
||||||
|
{ name: 'Veg Tank 1', type: 'VEG' },
|
||||||
|
{ name: 'Veg Tank 2', type: 'VEG' },
|
||||||
|
{ name: 'Flower Tank 1', type: 'FLOWER' },
|
||||||
|
{ name: 'Flower Tank 2', type: 'FLOWER' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const [checks, setChecks] = useState<Map<string, ReservoirCheckData>>(new Map());
|
||||||
|
const [currentTankIndex, setCurrentTankIndex] = useState(0);
|
||||||
|
const [levelPercent, setLevelPercent] = useState(100);
|
||||||
|
const [notes, setNotes] = useState('');
|
||||||
|
|
||||||
|
const currentTank = tanks[currentTankIndex];
|
||||||
|
const isLastTank = currentTankIndex === tanks.length - 1;
|
||||||
|
|
||||||
|
const getStatus = (level: number): 'OK' | 'LOW' | 'CRITICAL' => {
|
||||||
|
if (level >= 70) return 'OK';
|
||||||
|
if (level >= 30) return 'LOW';
|
||||||
|
return 'CRITICAL';
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNext = () => {
|
||||||
|
// Save current check
|
||||||
|
const checkData: ReservoirCheckData = {
|
||||||
|
tankName: currentTank.name,
|
||||||
|
tankType: currentTank.type,
|
||||||
|
levelPercent,
|
||||||
|
status: getStatus(levelPercent),
|
||||||
|
notes: notes || undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const newChecks = new Map(checks);
|
||||||
|
newChecks.set(currentTank.name, checkData);
|
||||||
|
setChecks(newChecks);
|
||||||
|
|
||||||
|
if (isLastTank) {
|
||||||
|
// Complete
|
||||||
|
onComplete(Array.from(newChecks.values()));
|
||||||
|
} else {
|
||||||
|
// Next tank
|
||||||
|
setCurrentTankIndex(currentTankIndex + 1);
|
||||||
|
setLevelPercent(100);
|
||||||
|
setNotes('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const status = getStatus(levelPercent);
|
||||||
|
const statusColor = {
|
||||||
|
OK: 'bg-emerald-500',
|
||||||
|
LOW: 'bg-yellow-500',
|
||||||
|
CRITICAL: 'bg-red-500',
|
||||||
|
}[status];
|
||||||
|
|
||||||
|
const statusText = {
|
||||||
|
OK: 'Good',
|
||||||
|
LOW: 'Low - Needs Refill',
|
||||||
|
CRITICAL: 'Critical - Refill Now!',
|
||||||
|
}[status];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-blue-900 to-slate-900 p-4 md:p-6 pb-24">
|
||||||
|
<div className="max-w-2xl mx-auto">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<div className="flex items-center gap-3 mb-2">
|
||||||
|
<button
|
||||||
|
onClick={onBack}
|
||||||
|
className="w-10 h-10 rounded-full bg-white/10 backdrop-blur-sm flex items-center justify-center text-white"
|
||||||
|
>
|
||||||
|
←
|
||||||
|
</button>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-xl md:text-2xl font-bold text-white">
|
||||||
|
Reservoir Checks
|
||||||
|
</h1>
|
||||||
|
<p className="text-blue-200 text-sm">
|
||||||
|
Tank {currentTankIndex + 1} of {tanks.length}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Progress */}
|
||||||
|
<div className="bg-white/10 backdrop-blur-sm rounded-full h-2 overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="bg-blue-400 h-full transition-all duration-300"
|
||||||
|
style={{ width: `${((currentTankIndex + 1) / tanks.length) * 100}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tank Card */}
|
||||||
|
<div className="bg-white/95 dark:bg-slate-800/95 backdrop-blur-sm rounded-2xl shadow-2xl p-6 md:p-8 space-y-6">
|
||||||
|
{/* Tank Info */}
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-5xl mb-3">💧</div>
|
||||||
|
<h2 className="text-2xl md:text-3xl font-bold text-slate-900 dark:text-white">
|
||||||
|
{currentTank.name}
|
||||||
|
</h2>
|
||||||
|
<p className="text-slate-600 dark:text-slate-400 mt-1">
|
||||||
|
{currentTank.type === 'VEG' ? 'Vegetative' : 'Flowering'} Tank
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Level Slider */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300">
|
||||||
|
Current Level
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{/* Visual Level Indicator */}
|
||||||
|
<div className="relative h-48 bg-slate-200 dark:bg-slate-700 rounded-xl overflow-hidden">
|
||||||
|
<div
|
||||||
|
className={`absolute bottom-0 left-0 right-0 ${statusColor} transition-all duration-300`}
|
||||||
|
style={{ height: `${levelPercent}%` }}
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent" />
|
||||||
|
</div>
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
<span className="text-4xl md:text-5xl font-bold text-white drop-shadow-lg">
|
||||||
|
{levelPercent}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Slider */}
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
value={levelPercent}
|
||||||
|
onChange={(e) => setLevelPercent(parseInt(e.target.value))}
|
||||||
|
className="w-full h-3 bg-slate-200 dark:bg-slate-700 rounded-lg appearance-none cursor-pointer accent-emerald-600"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Status Badge */}
|
||||||
|
<div className={`${statusColor} text-white text-center py-3 rounded-lg font-semibold`}>
|
||||||
|
{statusText}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Notes */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300">
|
||||||
|
Notes (Optional)
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
value={notes}
|
||||||
|
onChange={(e) => setNotes(e.target.value)}
|
||||||
|
placeholder="Any issues or observations..."
|
||||||
|
className="w-full px-4 py-3 rounded-lg border-2 border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-700 text-slate-900 dark:text-white focus:ring-2 focus:ring-emerald-500 focus:border-emerald-500 outline-none resize-none"
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Photo Upload (Placeholder) */}
|
||||||
|
{status !== 'OK' && (
|
||||||
|
<div className="border-2 border-dashed border-slate-300 dark:border-slate-600 rounded-lg p-6 text-center">
|
||||||
|
<div className="text-3xl mb-2">📸</div>
|
||||||
|
<p className="text-sm text-slate-600 dark:text-slate-400">
|
||||||
|
Tap to add photo of tank level
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<div className="flex gap-3 pt-4">
|
||||||
|
<button
|
||||||
|
onClick={onBack}
|
||||||
|
className="flex-1 min-h-[56px] py-4 bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 font-bold rounded-xl transition-all active:scale-[0.98]"
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleNext}
|
||||||
|
className="flex-1 min-h-[56px] py-4 bg-gradient-to-r from-emerald-600 to-emerald-700 hover:from-emerald-700 hover:to-emerald-800 text-white font-bold rounded-xl shadow-lg shadow-emerald-900/20 transition-all active:scale-[0.98]"
|
||||||
|
>
|
||||||
|
{isLastTank ? 'Complete' : 'Next Tank'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
174
frontend/src/pages/DailyWalkthroughPage.tsx
Normal file
174
frontend/src/pages/DailyWalkthroughPage.tsx
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
interface WalkthroughStep {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
completed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DailyWalkthroughPage() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [walkthroughId, setWalkthroughId] = useState<string | null>(null);
|
||||||
|
const [currentStep, setCurrentStep] = useState(0);
|
||||||
|
|
||||||
|
const steps: WalkthroughStep[] = [
|
||||||
|
{
|
||||||
|
id: 'reservoir',
|
||||||
|
title: 'Reservoir Checks',
|
||||||
|
description: 'Check all veg and flower tank levels',
|
||||||
|
icon: '💧',
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'irrigation',
|
||||||
|
title: 'Irrigation System',
|
||||||
|
description: 'Verify drippers and water flow in all zones',
|
||||||
|
icon: '🚿',
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'plant-health',
|
||||||
|
title: 'Plant Health',
|
||||||
|
description: 'Spot check for pests and plant health',
|
||||||
|
icon: '🌱',
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleStartWalkthrough = async () => {
|
||||||
|
// TODO: Call API to create walkthrough
|
||||||
|
// For now, navigate to first step
|
||||||
|
navigate('/walkthrough/reservoir');
|
||||||
|
};
|
||||||
|
|
||||||
|
const progressPercent = (currentStep / steps.length) * 100;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-emerald-900 to-slate-900 p-4 md:p-6">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="max-w-4xl mx-auto mb-6">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl md:text-3xl font-bold text-white">
|
||||||
|
Daily Walkthrough
|
||||||
|
</h1>
|
||||||
|
<p className="text-emerald-200 text-sm md:text-base mt-1">
|
||||||
|
{new Date().toLocaleDateString('en-US', {
|
||||||
|
weekday: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-16 h-16 md:w-20 md:h-20 rounded-full bg-white/10 backdrop-blur-sm flex items-center justify-center">
|
||||||
|
<img
|
||||||
|
src="/assets/logo-777-wolfpack.jpg"
|
||||||
|
alt="777 Wolfpack"
|
||||||
|
className="w-14 h-14 md:w-18 md:h-18 rounded-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Progress Bar */}
|
||||||
|
{walkthroughId && (
|
||||||
|
<div className="bg-white/10 backdrop-blur-sm rounded-full h-3 overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="bg-gradient-to-r from-emerald-500 to-emerald-400 h-full transition-all duration-500"
|
||||||
|
style={{ width: `${progressPercent}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
{!walkthroughId ? (
|
||||||
|
/* Start Screen */
|
||||||
|
<div className="bg-white/95 dark:bg-slate-800/95 backdrop-blur-sm rounded-2xl shadow-2xl p-6 md:p-8">
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<div className="text-6xl mb-4">☀️</div>
|
||||||
|
<h2 className="text-2xl md:text-3xl font-bold text-slate-900 dark:text-white mb-2">
|
||||||
|
Good Morning!
|
||||||
|
</h2>
|
||||||
|
<p className="text-slate-600 dark:text-slate-300 text-base md:text-lg">
|
||||||
|
Ready to start your daily facility walkthrough?
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Steps Preview */}
|
||||||
|
<div className="space-y-4 mb-8">
|
||||||
|
{steps.map((step, index) => (
|
||||||
|
<div
|
||||||
|
key={step.id}
|
||||||
|
className="flex items-center gap-4 p-4 bg-slate-50 dark:bg-slate-700/50 rounded-xl"
|
||||||
|
>
|
||||||
|
<div className="text-4xl">{step.icon}</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="font-semibold text-slate-900 dark:text-white text-base md:text-lg">
|
||||||
|
{index + 1}. {step.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-slate-600 dark:text-slate-400">
|
||||||
|
{step.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="w-8 h-8 rounded-full border-2 border-slate-300 dark:border-slate-600" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Start Button */}
|
||||||
|
<button
|
||||||
|
onClick={handleStartWalkthrough}
|
||||||
|
className="w-full min-h-[56px] py-4 bg-gradient-to-r from-emerald-600 to-emerald-700 hover:from-emerald-700 hover:to-emerald-800 text-white text-lg font-bold rounded-xl shadow-lg shadow-emerald-900/20 transition-all active:scale-[0.98]"
|
||||||
|
>
|
||||||
|
Start Walkthrough
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Info */}
|
||||||
|
<div className="mt-6 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-800">
|
||||||
|
<p className="text-sm text-blue-800 dark:text-blue-200">
|
||||||
|
<strong>💡 Tip:</strong> This walkthrough typically takes 15-20 minutes.
|
||||||
|
Make sure you have your phone/tablet ready for photos.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
/* In Progress - Show current step */
|
||||||
|
<div className="bg-white/95 dark:bg-slate-800/95 backdrop-blur-sm rounded-2xl shadow-2xl p-6 md:p-8">
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-slate-600 dark:text-slate-400">
|
||||||
|
Step {currentStep + 1} of {steps.length}
|
||||||
|
</p>
|
||||||
|
<h2 className="text-2xl font-bold text-slate-900 dark:text-white mt-2">
|
||||||
|
{steps[currentStep].title}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom Navigation (Mobile) */}
|
||||||
|
<div className="fixed bottom-0 left-0 right-0 bg-white dark:bg-slate-800 border-t border-slate-200 dark:border-slate-700 p-4 md:hidden">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => navigate('/')}
|
||||||
|
className="flex-1 py-3 bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 font-medium rounded-lg"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
{walkthroughId && (
|
||||||
|
<button
|
||||||
|
className="flex-1 py-3 bg-emerald-600 text-white font-medium rounded-lg"
|
||||||
|
>
|
||||||
|
Next Step
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue