schema([ Forms\Components\Section::make('Budget Details') ->columns() ->schema([ Forms\Components\TextInput::make('name') ->required() ->maxLength(255), Forms\Components\Select::make('interval_type') ->label('Budget Interval') ->options(BudgetIntervalType::class) ->default(BudgetIntervalType::Month->value) ->required() ->live(), Forms\Components\DatePicker::make('start_date') ->required() ->default(now()->startOfYear()) ->live(), Forms\Components\DatePicker::make('end_date') ->required() ->default(now()->endOfYear()) ->live() ->disabled(static fn (Forms\Get $get) => blank($get('start_date'))) ->minDate(fn (Forms\Get $get) => match (BudgetIntervalType::parse($get('interval_type'))) { BudgetIntervalType::Week => Carbon::parse($get('start_date'))->addWeek(), BudgetIntervalType::Month => Carbon::parse($get('start_date'))->addMonth(), BudgetIntervalType::Quarter => Carbon::parse($get('start_date'))->addQuarter(), BudgetIntervalType::Year => Carbon::parse($get('start_date'))->addYear(), default => Carbon::parse($get('start_date'))->addDay(), }) ->maxDate(fn (Forms\Get $get) => Carbon::parse($get('start_date'))->endOfYear()), Forms\Components\Textarea::make('notes') ->columnSpanFull(), ]), Forms\Components\Section::make('Budget Items') ->headerActions([ Forms\Components\Actions\Action::make('addAllAccounts') ->label('Add All Accounts') ->icon('heroicon-m-plus') ->outlined() ->color('primary') ->action(static fn (Forms\Set $set, Forms\Get $get) => self::addAllAccounts($set, $get)) ->hidden(static fn (Forms\Get $get) => filled($get('budgetItems'))), ]) ->schema([ Forms\Components\Repeater::make('budgetItems') ->columns(4) ->hiddenLabel() ->schema([ Forms\Components\Select::make('account_id') ->label('Account') ->options(Account::query()->pluck('name', 'id')) ->searchable() ->disableOptionsWhenSelectedInSiblingRepeaterItems() ->columnSpan(1) ->required(), Forms\Components\TextInput::make('total_amount') ->label('Total Amount') ->numeric() ->columnSpan(1) ->suffixAction( Forms\Components\Actions\Action::make('disperse') ->label('Disperse') ->icon('heroicon-m-bars-arrow-down') ->color('primary') ->action(static fn (Forms\Set $set, Forms\Get $get, $state) => self::disperseTotalAmount($set, $get, $state)) ), CustomSection::make('Budget Allocations') ->contained(false) ->columns(4) ->schema(static fn (Forms\Get $get) => self::getAllocationFields($get('../../start_date'), $get('../../end_date'), $get('../../interval_type'))), ]) ->defaultItems(0) ->addActionLabel('Add Budget Item'), ]), ]); } public static function table(Table $table): Table { return $table ->columns([ Tables\Columns\TextColumn::make('name') ->sortable() ->searchable(), Tables\Columns\TextColumn::make('interval_type') ->label('Interval') ->sortable() ->badge(), Tables\Columns\TextColumn::make('start_date') ->label('Start Date') ->date() ->sortable(), Tables\Columns\TextColumn::make('end_date') ->label('End Date') ->date() ->sortable(), Tables\Columns\TextColumn::make('total_budgeted_amount') ->label('Total Budgeted') ->money() ->sortable() ->alignEnd() ->getStateUsing(fn (Budget $record) => $record->budgetItems->sum(fn ($item) => $item->allocations->sum('amount'))), ]) ->filters([ // ]) ->actions([ Tables\Actions\ActionGroup::make([ Tables\Actions\ViewAction::make(), Tables\Actions\EditAction::make(), ]), ]) ->bulkActions([ Tables\Actions\BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make(), ]), ]); } private static function addAllAccounts(Forms\Set $set, Forms\Get $get): void { $accounts = Account::query() ->pluck('id'); $budgetItems = $accounts->map(static fn ($accountId) => [ 'account_id' => $accountId, 'total_amount' => 0, // Default to 0 until the user inputs amounts 'amounts' => self::generateDefaultAllocations($get('start_date'), $get('end_date'), $get('interval_type')), ])->toArray(); $set('budgetItems', $budgetItems); } private static function generateDefaultAllocations(?string $startDate, ?string $endDate, ?string $intervalType): array { if (! $startDate || ! $endDate || ! $intervalType) { return []; } $labels = self::generateFormattedLabels($startDate, $endDate, $intervalType); return collect($labels)->mapWithKeys(static fn ($label) => [$label => 0])->toArray(); } private static function disperseTotalAmount(Forms\Set $set, Forms\Get $get, float $totalAmount): void { $startDate = $get('../../start_date'); $endDate = $get('../../end_date'); $intervalType = $get('../../interval_type'); if (! $startDate || ! $endDate || ! $intervalType || $totalAmount <= 0) { return; } $labels = self::generateFormattedLabels($startDate, $endDate, $intervalType); $numPeriods = count($labels); if ($numPeriods === 0) { return; } $baseAmount = floor($totalAmount / $numPeriods); $remainder = $totalAmount - ($baseAmount * $numPeriods); foreach ($labels as $index => $label) { $amount = $baseAmount + ($index === 0 ? $remainder : 0); $set("amounts.{$label}", $amount); } } private static function generateFormattedLabels(string $startDate, string $endDate, string $intervalType): array { $start = Carbon::parse($startDate); $end = Carbon::parse($endDate); $intervalTypeEnum = BudgetIntervalType::parse($intervalType); $labels = []; while ($start->lte($end)) { $labels[] = match ($intervalTypeEnum) { BudgetIntervalType::Week => 'W' . $start->weekOfYear . ' ' . $start->year, // Example: W10 2024 BudgetIntervalType::Month => $start->format('M'), // Example: Jan, Feb, Mar BudgetIntervalType::Quarter => 'Q' . $start->quarter, // Example: Q1, Q2, Q3 BudgetIntervalType::Year => (string) $start->year, // Example: 2024, 2025 default => '', }; match ($intervalTypeEnum) { BudgetIntervalType::Week => $start->addWeek(), BudgetIntervalType::Month => $start->addMonth(), BudgetIntervalType::Quarter => $start->addQuarter(), BudgetIntervalType::Year => $start->addYear(), default => null, }; } return $labels; } private static function getAllocationFields(?string $startDate, ?string $endDate, ?string $intervalType): array { if (! $startDate || ! $endDate || ! $intervalType) { return []; } $start = Carbon::parse($startDate); $end = Carbon::parse($endDate); $intervalTypeEnum = BudgetIntervalType::parse($intervalType); $fields = []; while ($start->lte($end)) { $label = match ($intervalTypeEnum) { BudgetIntervalType::Week => 'W' . $start->weekOfYear . ' ' . $start->year, // Example: W10 2024 BudgetIntervalType::Month => $start->format('M'), // Example: Jan, Feb, Mar BudgetIntervalType::Quarter => 'Q' . $start->quarter, // Example: Q1, Q2, Q3 BudgetIntervalType::Year => (string) $start->year, // Example: 2024, 2025 default => '', }; $fields[] = Forms\Components\TextInput::make("amounts.{$label}") ->label($label) ->numeric() ->required(); match ($intervalTypeEnum) { BudgetIntervalType::Week => $start->addWeek(), BudgetIntervalType::Month => $start->addMonth(), BudgetIntervalType::Quarter => $start->addQuarter(), BudgetIntervalType::Year => $start->addYear(), default => null, }; } return $fields; } public static function getRelations(): array { return [ // ]; } public static function getPages(): array { return [ 'index' => Pages\ListBudgets::route('/'), 'create' => Pages\CreateBudget::route('/create'), 'view' => Pages\ViewBudget::route('/{record}'), 'edit' => Pages\EditBudget::route('/{record}/edit'), ]; } }