feat(sprint-6): Phase 5 — Full grow calendar (sensors, photos, feeding, harvest traceability)
Deploy to Production / test (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled

- V9 migration: grow_entries, grow_stage_logs, sensor_readings, grow_photos, feeding_logs
- 5 entities + GrowStage enum (7 stages) + SensorReadingType enum
- GrowCalendarService: CRUD + stage advancement + harvest-to-batch linking
- GrowCalendarController: 8 endpoints (/api/v1/grow/*)
- Frontend: /grow list + /grow/[id] detail (timeline, sensor charts, photo gallery, feeding log)
- Sensor chart (Recharts line: temp + humidity over time)
- Harvest completion links grow entry → batch (full traceability)
- React Query hooks for all grow operations
- Full i18n (de/en) with 7 grow stage labels
- Sidebar navigation updated with Anbau/Grow entry
This commit is contained in:
Patrick Plate
2026-06-12 22:51:45 +02:00
parent 05933a08ca
commit 076fd6f9b3
34 changed files with 1843 additions and 2 deletions
+186
View File
@@ -0,0 +1,186 @@
import type { GrowEntry, GrowEntryDetail } from "@/services/grow"
function daysAgo(days: number): string {
const d = new Date()
d.setDate(d.getDate() - days)
return d.toISOString()
}
function daysFromNow(days: number): string {
const d = new Date()
d.setDate(d.getDate() + days)
return d.toISOString()
}
export const mockGrowEntries: GrowEntry[] = [
{
id: "grow-001",
name: "Amnesia Haze Runde 3",
strainId: "s-001",
status: "FLOWERING",
startedAt: daysAgo(55),
expectedHarvestAt: daysFromNow(25),
actualHarvestAt: null,
harvestedGrams: null,
linkedBatchId: null,
notes: "Sehr kräftiger Wuchs, 4 Pflanzen",
},
{
id: "grow-002",
name: "White Widow Indoor",
strainId: "s-002",
status: "DRYING",
startedAt: daysAgo(95),
expectedHarvestAt: daysAgo(5),
actualHarvestAt: daysAgo(5),
harvestedGrams: 320,
linkedBatchId: null,
notes: "Ernte war ertragreich, jetzt in Trocknung",
},
{
id: "grow-003",
name: "Northern Lights Micro",
strainId: "s-003",
status: "COMPLETE",
startedAt: daysAgo(140),
expectedHarvestAt: daysAgo(40),
actualHarvestAt: daysAgo(42),
harvestedGrams: 185.5,
linkedBatchId: "b-001",
notes: "Fertig, verknüpft mit Charge B-2024-003",
},
]
export function mockGrowDetail(id: string): GrowEntryDetail {
const entry = mockGrowEntries.find((e) => e.id === id) ?? mockGrowEntries[0]
return {
...entry,
stages: [
{
id: "stg-1",
stage: "SEEDLING",
startedAt: entry.startedAt,
endedAt: daysAgo(40),
notes: null,
},
{
id: "stg-2",
stage: "VEGETATIVE",
startedAt: daysAgo(40),
endedAt: daysAgo(20),
notes: "Topped einmal",
},
{
id: "stg-3",
stage: "FLOWERING",
startedAt: daysAgo(20),
endedAt: null,
notes: null,
},
],
sensors: [
{
id: "sr-1",
readingType: "TEMPERATURE",
value: 24.5,
unit: "°C",
recordedAt: daysAgo(1),
},
{
id: "sr-2",
readingType: "HUMIDITY",
value: 55,
unit: "%",
recordedAt: daysAgo(1),
},
{
id: "sr-3",
readingType: "TEMPERATURE",
value: 23.8,
unit: "°C",
recordedAt: daysAgo(2),
},
{
id: "sr-4",
readingType: "HUMIDITY",
value: 58,
unit: "%",
recordedAt: daysAgo(2),
},
{
id: "sr-5",
readingType: "TEMPERATURE",
value: 25.1,
unit: "°C",
recordedAt: daysAgo(3),
},
{
id: "sr-6",
readingType: "HUMIDITY",
value: 52,
unit: "%",
recordedAt: daysAgo(3),
},
{
id: "sr-7",
readingType: "PH",
value: 6.2,
unit: "pH",
recordedAt: daysAgo(2),
},
{
id: "sr-8",
readingType: "EC",
value: 1.8,
unit: "mS/cm",
recordedAt: daysAgo(2),
},
],
photos: [
{
id: "ph-1",
filePath: "/uploads/grow-001/week6.jpg",
caption: "Woche 6 — Blüte beginnt",
takenAt: daysAgo(5),
},
{
id: "ph-2",
filePath: "/uploads/grow-001/week4.jpg",
caption: "Woche 4 — Vegetativ",
takenAt: daysAgo(20),
},
],
feedings: [
{
id: "fl-1",
nutrientName: "BioBizz Bloom",
amountMl: 4,
waterLiters: 2,
phAfter: 6.3,
ecAfter: 1.9,
fedAt: daysAgo(1),
notes: null,
},
{
id: "fl-2",
nutrientName: "BioBizz Top-Max",
amountMl: 2,
waterLiters: 2,
phAfter: 6.1,
ecAfter: 1.7,
fedAt: daysAgo(3),
notes: null,
},
{
id: "fl-3",
nutrientName: "CalMag",
amountMl: 1,
waterLiters: 2,
phAfter: 6.4,
ecAfter: 1.6,
fedAt: daysAgo(5),
notes: "Leichte Blattflecken",
},
],
}
}
@@ -24,6 +24,11 @@ export const navigationsData: NavigationType[] = [
href: "/stock",
iconName: "Package",
},
{
title: "Anbau",
href: "/grow",
iconName: "Sprout",
},
{
title: "Berichte",
href: "/reports",