function ($query) { $query->latest('created_at'); }, 'sprayings' => function ($query) { $query->with('plannedTask')->orderByDesc('created_at'); }, 'harvests' => function ($query) { $query->with('plannedTask')->orderByDesc('date'); }, ])->get(); $actionGroups = [ 'Treatment' => [ 'watering', 'pruning', 'fertilisation', 'pesticide', ], 'Manage Crops' => [ 'harvest', 'add-plants', 'discard-plants', ], ]; return view('vineyard.map.index', [ 'rows' => $this->formatRows($rows), 'actions' => array_merge(...array_values($actionGroups)), 'actionGroups' => $actionGroups, ]); } /** * Display the editable vineyard map. */ public function edit() { $rows = VineyardRow::with(['varietyVariation.grapeVariety'])->get(); $variations = VarietyVariation::with('grapeVariety') ->orderBy('grape_variety_id') ->get() ->map(function (VarietyVariation $variation) { $grape = $variation->grapeVariety; $label = $grape ? sprintf('%s — %s', $grape->variety_name, ucfirst($variation->color)) : ucfirst($variation->color); return [ 'id' => $variation->getKey(), 'label' => $label, ]; }); return view('vineyard.map.edit', [ 'rows' => $this->formatRows($rows), 'statuses' => ['active', 'inactive', 'replanting', 'discarded'], 'variationOptions' => $variations, ]); } /** * Transform rows into a structure suitable for the front-end map. */ protected function formatRows(Collection $rows): Collection { return $rows->map(function (VineyardRow $row) { [$x, $y] = $this->parseLocation($row->location); $variation = $row->varietyVariation; $grapeVariety = $variation?->grapeVariety; $lastTreatmentDate = optional($row->treatments->first())->created_at; $pesticideCompleted = $row->sprayings->map(function ($spraying) { $executionDate = optional($spraying->plannedTask)->execution_date; if ($executionDate) { return Carbon::parse($executionDate)->toDateString(); } return optional($spraying->created_at)?->toDateString(); })->filter()->unique()->sort()->values()->all(); $pesticidePlanned = $row->sprayings->map(function ($spraying) { $plannedDate = optional($spraying->plannedTask)->planned_date; if ($plannedDate) { return Carbon::parse($plannedDate)->toDateString(); } return null; })->filter()->unique()->sort()->values()->all(); $harvestCompleted = $row->harvests->map(function ($harvest) { $executionDate = optional($harvest->plannedTask)->execution_date; if ($executionDate) { return Carbon::parse($executionDate)->toDateString(); } $harvestDate = $harvest->date; return $harvestDate ? Carbon::parse($harvestDate)->toDateString() : null; })->filter()->unique()->sort()->values()->all(); $harvestPlanned = $row->harvests->map(function ($harvest) { $plannedDate = optional($harvest->plannedTask)->planned_date; if ($plannedDate) { return Carbon::parse($plannedDate)->toDateString(); } return null; })->filter()->unique()->sort()->values()->all(); return [ 'id' => $row->getKey(), 'location' => [ 'x' => $x, 'y' => $y, ], 'dimensions' => [ 'width' => 1, 'height' => 1, ], 'vine_count' => $row->vine_count, 'status' => $row->status, 'notes' => $row->notes, 'planting_year' => $row->planting_year, 'variety' => $variation ? [ 'id' => $variation->getKey(), 'color' => $variation->color, 'label' => $grapeVariety ? sprintf('%s — %s', $grapeVariety->variety_name, ucfirst($variation->color)) : ucfirst($variation->color), ] : null, 'last_treatment_at' => $lastTreatmentDate, 'timeline' => [ 'pesticide' => [ 'completed' => $pesticideCompleted, 'planned' => $pesticidePlanned, ], 'harvest' => [ 'completed' => $harvestCompleted, 'planned' => $harvestPlanned, ], ], ]; }); } /** * Parse the "x,y" string stored on vineyard rows. */ protected function parseLocation(?string $location): array { if (blank($location)) { return [0, 0]; } $parts = Str::of($location)->explode(',')->map(fn ($fragment) => (int) trim($fragment)); return [ $parts->get(0, 0), $parts->get(1, 0), ]; } }