TableRepeater::make('budgetProducts') ->label(__('budget_products')) ->addActionLabel(__('add_budget_products')) ->emptyLabel(__('no_data')) ->extraActions([ Action::make('addSection') ->label('Añadir sección') ->icon('heroicon-m-plus') ->action(function (Get $get, Set $set) { $items = $get('budgetProducts') ?? []; $items[] = [ 'line_type' => 'section', 'section' => '', 'quantity' => null, 'price' => null, 'iva' => null, 'discount' => null, 'subtotal' => null, 'is_custom_product' => false, 'product_id' => null, 'custom_name' => null, ]; $set('budgetProducts', $items); }), ]) ->headers(function (Forms\Get $get) { return [ Header::make('Personalizado')->width('5%')->label(__('custom')), Header::make('Product')->width('30%')->label(__('product')), Header::make('Quantity')->width('10%')->label(__('quantity')), Header::make('Price')->width('15%')->label(__('price')), Header::make('IVA')->width('10%')->label(__('iva')), Header::make('Discount')->width('10%')->label(__('discount')), Header::make('Subtotal')->width('15%')->align('left')->label(__('subtotal')), ]; }) ->default(fn() => CreateBudget::getDefaultProducts()) ->defaultItems(self::$isFromTemplate ? 0 : 1) ->schema([ Forms\Components\Hidden::make('line_type')->default('product'), Toggle::make('is_custom_product') ->label('¿Producto personalizado?') ->default(false) ->columnSpan(2) ->disabled(fn(Get $get) => $get('line_type') === 'section') ->live() ->afterStateUpdated(function ($state, Set $set, Get $get) { if ($state) { // Si se activa el toggle, limpiar product_id $set('product_id', null); } else { // Si se desactiva el toggle, limpiar custom_name $set('custom_name', null); } // Recalcular totales InvoiceBudgetCalculator::updateItemTotals($get, $set, 'budget'); InvoiceBudgetCalculator::updateTotals($get, $set, 'budget'); }), TextInput::make('section') ->label('Sección') ->placeholder(__('section_title')) ->visible(fn(Get $get) => $get('line_type') === 'section') ->required(fn(Get $get) => $get('line_type') === 'section') ->columnSpan(4), BelongsToSelect::make('product_id') ->label(__('product')) ->options($usarOptions ? $products : []) ->searchable(!$usarOptions) ->preload($usarOptions) ->visible(fn(Get $get) => $get('line_type') !== 'section' && !$get('is_custom_product')) ->live() ->afterStateUpdated(function ($state, Set $set, Get $get) { if ($state) { $product = Product::find($state); if ($product) { $set('price', $product->price); $set('quantity', 1); $set('iva', $product->iva); $set('custom_name', null); // Asegurar que el toggle esté desactivado $set('is_custom_product', false); } } else { $set('price', null); $set('quantity', null); $set('iva', null); } InvoiceBudgetCalculator::updateItemTotals($get, $set, 'budget'); InvoiceBudgetCalculator::updateTotals($get, $set, 'budget'); }) ->required(fn(Get $get) => !$get('is_custom_product' && $get('line_type') !== 'section')), TextInput::make('custom_name') ->label('Nombre producto') ->visible(fn(Get $get) => $get('line_type') !== 'section' && $get('is_custom_product')) ->required(fn(Get $get) => $get('is_custom_product') && $get('line_type') !== 'section') ->columnSpan(4), TextInput::make('quantity') ->label(__('quantity')) ->numeric() ->default(0) ->rules(['regex:/^-?\d+(\.\d{1,2})?$/']) ->live(onBlur: true) ->afterStateUpdated(function (Set $set, Get $get) { InvoiceBudgetCalculator::updateItemTotals($get, $set, 'budget'); InvoiceBudgetCalculator::updateTotals($get, $set, 'budget'); }) ->required() ->extraInputAttributes(['wire:loading.attr' => 'readonly']) ->visible(fn(Get $get) => $get('line_type') !== 'section'), TextInput::make('price') ->label(__('price')) ->suffix(fn() => ' ' . SettingGlobal::get('currency', '€')) ->numeric() ->default(0) ->live(onBlur: true) ->afterStateUpdated(function (Set $set, Get $get) { InvoiceBudgetCalculator::updateItemTotals($get, $set, 'budget'); InvoiceBudgetCalculator::updateTotals($get, $set, 'budget'); }) ->required() ->extraInputAttributes(['wire:loading.attr' => 'readonly']) ->visible(fn(Get $get) => $get('line_type') !== 'section'), TextInput::make('iva') ->label(__('iva')) ->numeric() ->suffix('%') ->live(onBlur: true) ->afterStateUpdated(function (Set $set, Get $get) { InvoiceBudgetCalculator::updateItemTotals($get, $set, 'budget'); InvoiceBudgetCalculator::updateTotals($get, $set, 'budget'); }) ->required() ->extraInputAttributes(['wire:loading.attr' => 'readonly']) ->visible(fn(Get $get) => $get('line_type') !== 'section'), TextInput::make('discount') ->label(__('discount')) ->numeric() ->default(0) ->suffix('%') ->live(onBlur: true) ->afterStateUpdated(function (Set $set, Get $get) { InvoiceBudgetCalculator::updateItemTotals($get, $set, 'budget'); InvoiceBudgetCalculator::updateTotals($get, $set, 'budget'); }) ->required() ->extraInputAttributes(['wire:loading.attr' => 'readonly']) ->visible(fn(Get $get) => $get('line_type') !== 'section'), TextInput::make('subtotal') ->label(__('subtotal')) ->numeric() ->readOnly() ->default(0) ->suffix(fn() => ' ' . SettingGlobal::get('currency', '€')) ->afterStateHydrated(fn(Get $get, Set $set) => InvoiceBudgetCalculator::updateItemTotals($get, $set, 'budget')) ->dehydrated(false) ->visible(fn(Get $get) => $get('line_type') !== 'section'), ]) ->afterStateHydrated(function (Set $set, Get $get) { $data = $get('budgetProducts'); if (self::$isFromTemplate && (empty($data) || collect($data)->every(fn($item) => empty($item['product_id'])))) { $set('budgetProducts', CreateBudget::getDefaultProducts()); } elseif (empty($data)) { $set('budgetProducts', [ [ 'product_id' => null, 'quantity' => 1, 'price' => 0, 'iva' => 21, 'discount' => 0, 'subtotal' => 0, 'line_type' => 'product', 'position' => 0, ] ]); } }) ->live() ->afterStateUpdated(fn(Get $get, Set $set) => InvoiceBudgetCalculator::updateTotals($get, $set, 'budget')) ->deleteAction( fn(Action $action) => $action ->after(function (Get $get, Set $set) { $budgetProducts = $get('budgetProducts') ?? []; if (empty($budgetProducts)) { $set('budgetProducts', []); } InvoiceBudgetCalculator::updateTotals($get, $set, 'budget', true); }) ) ->reorderable(false) ->minItems(1) ->columnSpan(12) ->defaultItems(1), TextInput::make('subtotal') ->label(__('subtotal')) ->numeric() ->default(fn() => self::$isFromTemplate ? InvoiceTemplate::find(request()->query('template_id'))->subtotal ?? 0 : 0) ->readOnly() ->suffix(fn() => ' ' . SettingGlobal::get('currency', '€')) ->columnSpan(2) ->afterStateHydrated(fn(Get $get, Set $set) => InvoiceBudgetCalculator::updateTotals($get, $set, 'budget')), TextInput::make('iva') ->label(__('iva')) ->suffix('%') ->required() ->readOnly() ->numeric() ->default(fn() => self::$isFromTemplate ? InvoiceTemplate::find(request()->query('template_id'))->iva ?? 0 : 0) ->live() ->columnSpan(2) ->afterStateUpdated(fn(Get $get, Set $set) => InvoiceBudgetCalculator::updateTotals($get, $set, 'budget')), TextInput::make('total') ->label(__('total')) ->numeric() ->default(fn() => self::$isFromTemplate ? InvoiceTemplate::find(request()->query('template_id'))->total ?? 0 : 0) ->readOnly() ->columnSpan(2) ->suffix(fn() => ' ' . SettingGlobal::get('currency', '€')), ]),