Add recipe versioning: auto-bump on change, history sub-page
Deploy / deploy (push) Successful in 16s
Deploy / deploy (push) Successful in 16s
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 <noreply@anthropic.com>
This commit is contained in:
1 parent
4775cffaae
commit
a7e7375edd
4 files changed
+128
-21
No files matched your search
@@ -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'),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\User\Resources\ProductResource\Pages;
|
||||
|
||||
use App\Filament\User\Resources\ProductResource;
|
||||
use App\Models\Product;
|
||||
use App\Models\Recipe;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Resources\Pages\Page;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Concerns\InteractsWithTable;
|
||||
use Filament\Tables\Contracts\HasTable;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class RecipeHistory extends Page implements HasTable
|
||||
{
|
||||
use InteractsWithTable;
|
||||
|
||||
protected static string $resource = ProductResource::class;
|
||||
protected static string $view = 'filament.user.resources.product-resource.pages.recipe-history';
|
||||
|
||||
public Product $record;
|
||||
|
||||
public function mount(int|string $record): void
|
||||
{
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
<x-filament-panels::page>
|
||||
{{ $this->table }}
|
||||
</x-filament-panels::page>
|
||||
Reference in new issue
Block a user