schema([ Forms\Components\TextInput::make('title') ->label('Name') ->required(), Forms\Components\TextInput::make('lot_prefix') ->label('Chargen-Präfix') ->placeholder('z.B. 112') ->unique(ignoreRecord: true) ->maxLength(20), Placeholder::make('shopify_product_id') ->label('Shopify ID') ->content(fn ($record) => $record?->shopify_product_id ? new HtmlString('' . e($record->shopify_product_id) . '') : new HtmlString('Noch nicht in Shopify erstellt') ), FileUpload::make('images') ->label('Bilder') ->multiple() ->image() ->maxFiles(15) ->directory('product-images') ->panelLayout('grid') ->imagePreviewHeight('200') ->columnSpanFull(), Placeholder::make('description') ->label('Beschreibung') ->content(fn ($record) => $record?->description ?? '—') ->columnSpanFull(), Forms\Components\Section::make('Rezept') ->schema([ Placeholder::make('recipe_version') ->label('Version') ->content(fn ($record) => $record?->recipe ? 'v' . $record->recipe->version . ' · ' . $record->recipe->created_at->format('d.m.Y') : '—'), Repeater::make('recipe_spices') ->label('Zutaten') ->schema([ Select::make('spice_id') ->label('Rohstoff') ->options(Spice::all()->pluck('name', 'id')) ->searchable() ->required(), TextInput::make('amount_in_percent') ->label('Anteil (%)') ->numeric() ->minValue(0.01) ->maxValue(100) ->required(), ]) ->columns(2) ->addActionLabel('Zutat hinzufügen') ->dehydrated(false) ->rules([new SumEquals100()]) ->afterStateHydrated(function ($component, $record) { if ($record?->recipe) { $record->recipe->load('spices'); $component->state( $record->recipe->spices->map(fn ($s) => [ 'spice_id' => $s->id, 'amount_in_percent' => $s->pivot->amount_in_percent, ])->toArray() ); } }), ]) ->columns(2) ->columnSpanFull(), Repeater::make('sizes') ->schema([ Hidden::make('product_size_id'), Hidden::make('shopify_variant_id'), Placeholder::make('shopify_status') ->label('Shopify') ->content(fn (Get $get): HtmlString => $get('shopify_variant_id') ? new HtmlString('✓ Shopify') : new HtmlString('Lokal') ), Select::make('packing_id') ->label('Verpackung') ->options(function () { return Packing::all()->mapWithKeys(function ($packing) { return [$packing->id => $packing->name . ' (' . $packing->size . ')']; }); }) ->searchable() ->required(), TextInput::make('price') ->label('Preis (in EUR)') ->numeric() ->required(), TextInput::make('sku') ->label('SKU'), Placeholder::make('barcode_preview') ->label('Barcode') ->content(function (Get $get): HtmlString { $gtin = preg_replace('/\D/', '', (string) $get('gtin')); if (strlen($gtin) < 8) { return new HtmlString(''); } $type = match (strlen($gtin)) { 8 => BarcodeGeneratorSVG::TYPE_EAN_8, 13 => BarcodeGeneratorSVG::TYPE_EAN_13, 12 => BarcodeGeneratorSVG::TYPE_UPC_A, 14 => BarcodeGeneratorSVG::TYPE_CODABAR, default => BarcodeGeneratorSVG::TYPE_CODE_128, }; try { $svg = (new BarcodeGeneratorSVG())->getBarcode($gtin, $type, 2, 60); return new HtmlString($svg); } catch (\Throwable) { return new HtmlString(''); } }) ->columnSpanFull() ->visible(fn (Get $get) => strlen(preg_replace('/\D/', '', (string) $get('gtin'))) >= 8), TextInput::make('gtin') ->label('GTIN') ->mask('9 999999 999999') ->dehydrateStateUsing(fn ($state) => preg_replace('/\D/', '', (string) $state)) ->live(debounce: 500), ]) ->label('Größen & Preise') ->columns(2) ->addActionLabel('Option hinzufügen') ->extraItemActions([ FormAction::make('shopify') ->label('Shopify') ->icon('heroicon-o-arrow-path') ->color('gray') ->requiresConfirmation() ->action(function (array $arguments, Repeater $component): void { $item = $component->getState()[$arguments['item']] ?? []; $sizeId = (int) ($item['product_size_id'] ?? 0); if (! $sizeId) { \Filament\Notifications\Notification::make() ->title('Zuerst speichern') ->warning() ->send(); return; } $page = $component->getContainer()->getLivewire(); if ($item['shopify_variant_id'] ?? null) { $page->syncVariantWithShopify($sizeId); } else { $page->publishVariantToShopify($sizeId); } }), ]) ->afterStateHydrated(function ($state, $set, $record) { if ($record) { $sizesData = ProductSize::with('packing') ->where('product_id', $record->id) ->get() ->map(fn ($ps) => [ 'product_size_id' => $ps->id, 'shopify_variant_id' => $ps->shopify_variant_id, 'packing_id' => $ps->packing_id, 'price' => $ps->price, 'sku' => $ps->sku, 'gtin' => $ps->gtin, 'description' => $ps->description, ]) ->toArray(); $set('sizes', $sizesData); } return $state; }), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\ImageColumn::make('images') ->label('Bild') ->disk('public') ->limit(1), Tables\Columns\TextColumn::make('title') ->label('Name') ->sortable() ->searchable(), Tables\Columns\TextColumn::make('recipe.name') ->label('Rezept'), ]); } public static function getRelations(): array { return [ RelationManagers\BatchesRelationManager::class, ]; } public static function getPages(): array { return [ 'index' => Pages\ListProducts::route('/'), 'create' => Pages\CreateProduct::route('/create'), 'edit' => Pages\EditProduct::route('/{record}/edit'), 'recipe-history' => Pages\RecipeHistory::route('/{record}/rezept-versionen'), ]; } public static function getPluralLabel(): string { return 'Produkte'; } }