From a7e7375edd180f941e48a90a0c1066a425cadf13 Mon Sep 17 00:00:00 2001 From: Malek Tellissi Date: Sat, 12 Sep 2026 05:52:59 +0200 Subject: [PATCH] Add recipe versioning: auto-bump on change, history sub-page Each time the recipe name or spices are changed on the product edit page, a new version is created instead of overwriting the current one. Unchanged saves are skipped. A 'Rezept-Versionen' button opens a sub-page (/produkt/{id}/rezept-versionen) listing all versions with their spices and timestamps; the active version is highlighted in green. Co-Authored-By: Claude Sonnet 4.6 --- .../User/Resources/ProductResource.php | 7 +- .../ProductResource/Pages/EditProduct.php | 62 ++++++++++----- .../ProductResource/Pages/RecipeHistory.php | 77 +++++++++++++++++++ .../pages/recipe-history.blade.php | 3 + 4 files changed, 128 insertions(+), 21 deletions(-) create mode 100644 app/Filament/User/Resources/ProductResource/Pages/RecipeHistory.php create mode 100644 resources/views/filament/user/resources/product-resource/pages/recipe-history.blade.php diff --git a/app/Filament/User/Resources/ProductResource.php b/app/Filament/User/Resources/ProductResource.php index a9dceff..c79a029 100644 --- a/app/Filament/User/Resources/ProductResource.php +++ b/app/Filament/User/Resources/ProductResource.php @@ -193,9 +193,10 @@ class ProductResource extends Resource { public static function getPages(): array { return [ - 'index' => Pages\ListProducts::route('/'), - 'create' => Pages\CreateProduct::route('/create'), - 'edit' => Pages\EditProduct::route('/{record}/edit'), + 'index' => Pages\ListProducts::route('/'), + 'create' => Pages\CreateProduct::route('/create'), + 'edit' => Pages\EditProduct::route('/{record}/edit'), + 'recipe-history' => Pages\RecipeHistory::route('/{record}/rezept-versionen'), ]; } diff --git a/app/Filament/User/Resources/ProductResource/Pages/EditProduct.php b/app/Filament/User/Resources/ProductResource/Pages/EditProduct.php index 6828054..9999f53 100644 --- a/app/Filament/User/Resources/ProductResource/Pages/EditProduct.php +++ b/app/Filament/User/Resources/ProductResource/Pages/EditProduct.php @@ -14,16 +14,23 @@ class EditProduct extends EditRecord protected function getHeaderActions(): array { return [ + Actions\Action::make('rezept-versionen') + ->label('Rezept-Versionen') + ->icon('heroicon-o-clock') + ->color('gray') + ->url(fn () => ProductResource::getUrl('recipe-history', ['record' => $this->record])), Actions\DeleteAction::make(), ]; } - protected function afterSave(): void { + protected function afterSave(): void + { $this->syncSizes(); $this->syncRecipe(); } - protected function syncSizes(): void { + protected function syncSizes(): void + { $product = $this->record; $sizes = collect($this->data['sizes'] ?? []) @@ -45,29 +52,48 @@ class EditProduct extends EditRecord protected function syncRecipe(): void { $product = $this->record; - $name = $this->data['recipe_name'] ?? null; - $spices = $this->data['recipe_spices'] ?? []; + $name = $this->data['recipe_name'] ?? null; + $spices = $this->data['recipe_spices'] ?? []; if (! $name) { return; } - $pivotData = collect($spices)->mapWithKeys(fn ($s) => [ - $s['spice_id'] => ['amount_in_percent' => $s['amount_in_percent']], + $newPivot = collect($spices)->mapWithKeys(fn ($s) => [ + (int) $s['spice_id'] => (float) $s['amount_in_percent'], ])->toArray(); - if ($product->recipe) { - $product->recipe->update(['name' => $name]); - $product->recipe->spices()->sync($pivotData); - } else { - $nextVersion = Recipe::where('product_id', $product->id)->max('version') + 1; - $recipe = Recipe::create([ - 'name' => $name, - 'product_id' => $product->id, - 'version' => $nextVersion, - ]); - $recipe->spices()->sync($pivotData); - $product->update(['recipes_id' => $recipe->id]); + $current = $product->recipe; + + if ($current) { + $current->load('spices'); + + $existingPivot = $current->spices->mapWithKeys(fn ($s) => [ + $s->id => (float) $s->pivot->amount_in_percent, + ])->toArray(); + + $nameChanged = $current->name !== $name; + $spicesChanged = $existingPivot != $newPivot; + + if (! $nameChanged && ! $spicesChanged) { + return; + } } + + $nextVersion = Recipe::where('product_id', $product->id)->max('version') + 1; + + $recipe = Recipe::create([ + 'name' => $name, + 'product_id' => $product->id, + 'version' => $nextVersion, + ]); + + $recipe->spices()->sync( + collect($newPivot)->mapWithKeys(fn ($pct, $id) => [ + $id => ['amount_in_percent' => $pct], + ])->toArray() + ); + + $product->update(['recipes_id' => $recipe->id]); } } diff --git a/app/Filament/User/Resources/ProductResource/Pages/RecipeHistory.php b/app/Filament/User/Resources/ProductResource/Pages/RecipeHistory.php new file mode 100644 index 0000000..8df1a05 --- /dev/null +++ b/app/Filament/User/Resources/ProductResource/Pages/RecipeHistory.php @@ -0,0 +1,77 @@ +record = Product::with('recipe')->findOrFail($record); + } + + public function getTitle(): string + { + return 'Rezept-Versionen · ' . $this->record->title; + } + + protected function getHeaderActions(): array + { + return [ + Action::make('back') + ->label('Zurück zum Produkt') + ->icon('heroicon-o-arrow-left') + ->color('gray') + ->url(ProductResource::getUrl('edit', ['record' => $this->record])), + ]; + } + + public function table(Table $table): Table + { + $currentRecipeId = $this->record->recipes_id; + + return $table + ->query( + Recipe::query() + ->where('product_id', $this->record->id) + ->with('spices') + ->orderByDesc('version') + ) + ->columns([ + TextColumn::make('version') + ->label('Version') + ->formatStateUsing(fn ($state) => 'v' . $state) + ->badge() + ->color(fn (Recipe $recipe) => $recipe->id === $currentRecipeId ? 'success' : 'gray'), + TextColumn::make('name') + ->label('Name'), + TextColumn::make('spices_summary') + ->label('Zutaten') + ->state(fn (Recipe $recipe) => + $recipe->spices->map(fn ($s) => $s->name . ' ' . $s->pivot->amount_in_percent . '%')->implode(' · ') + ), + TextColumn::make('created_at') + ->label('Erstellt am') + ->dateTime('d.m.Y H:i') + ->sortable(), + ]) + ->paginated(false); + } +} diff --git a/resources/views/filament/user/resources/product-resource/pages/recipe-history.blade.php b/resources/views/filament/user/resources/product-resource/pages/recipe-history.blade.php new file mode 100644 index 0000000..ce096a2 --- /dev/null +++ b/resources/views/filament/user/resources/product-resource/pages/recipe-history.blade.php @@ -0,0 +1,3 @@ + + {{ $this->table }} +