schema([ Toggle::make('active'), Section::make('Info')->schema([ TextInput::make('title') ->required() ->maxLength(255) ->live() ->debounce(1000) ->afterStateUpdated(function (\Filament\Forms\Get $get, \Filament\Forms\Set $set) { $set('slug', Str::slug($get('title'))); }) ->placeholder('30 Nights of Giving'), TextInput::make('slug') ->required() ->maxLength(255) ->placeholder('30-nights-of-giving'), FileUpload::make('logo_image') ->image() ->helperText('Leave blank to use Charity Right logo.'), Grid::make()->schema([ ColorPicker::make('primary_colour') ->label('Primary Colour') ->hex() ->placeholder('#E42281'), ColorPicker::make('text_colour') ->label('Text Colour') ->hex() ->placeholder('#000000'), ])->columns(1)->columnSpan(1), TextInput::make('minimum_donation') ->required() ->label('Minimum Donation Amount') ->prefix('£') ->numeric() ->default(30.0), Toggle::make('catch_up_payments') ->required() ->label('Should we attempt to take out missed payments if a subscription is made during the dates?'), Toggle::make('accepts_appeals') ->required() ->label('Can donors set up subscriptions against appeals via this campaign?'), Select::make('split_types') ->label('Use-able Split Options') ->required() ->multiple() ->options([ ScheduledGivingSplitTypeFlag::EQUAL => 'Equal', ScheduledGivingSplitTypeFlag::DOUBLE_ON_ODD => 'Double on odd', ScheduledGivingSplitTypeFlag::DOUBLE_ON_EVEN => 'Double on even', ScheduledGivingSplitTypeFlag::DOUBLE_ON_FRIDAYS => 'Double on Fridays', ScheduledGivingSplitTypeFlag::DOUBLE_ON_LAST_10_NIGHTS => 'Double on last 10', ScheduledGivingSplitTypeFlag::DOUBLE_ON27TH_NIGHT => 'Double on 27th', ScheduledGivingSplitTypeFlag::DOUBLE_ON_DAY_OF_ARAFAH => 'Double on 9th', ]), ]) ->columns(), Section::make('Date Configuration')->collapsible()->schema([ Select::make('date_config_type') ->options([ -1 => 'Custom Configuration', DefaultCalendarSchedule::RAMADAN_DAYS => 'Ramadan Days', DefaultCalendarSchedule::RAMADAN_NIGHTS => 'Ramadan Nights', DefaultCalendarSchedule::DHUL_HIJJAH_DAYS => 'Dhul Hijjah Days', DefaultCalendarSchedule::DHUL_HIJJAH_NIGHTS => 'Dhul Hijjah Nights', DefaultCalendarSchedule::RAMADAN_NIGHTS_LAST_10 => 'Ramadan Nights (Last 10)', DefaultCalendarSchedule::DHUL_HIJJAH_DAYS_FIRST_10 => 'Dhul Hijjah Days (First 10)', ]) ->label('Builtin Date Configuration') ->helperText('Select the built-in date configuration - based on previous campaigns - or choose \'Custom Configuration\' to set up the timings manually. Please note it may take some time to generate the dates when selecting anything other than Custom.') ->live() ->afterStateUpdated(function ($old, \Filament\Forms\Get $get, \Filament\Forms\Set $set) { $configType = (int) $get('date_config_type'); if ($configType === -1) { return; } $data = app(ScheduleGeneratorService::class)->generate($configType); $set('dates', $data); // This becomes VERY heavy when we are doing it reactively. // I don't know why, but it seems to fry the server when it passes the data into the repeater. // Let's just use it when creating the resource and indicate to the user to wait. }) ->visibleOn('create'), Repeater::make('dates') ->label('Dates') ->schema([ Select::make('month') ->options(self::formatHijriMonths()) ->live(), Select::make('day') ->visible(fn (\Filament\Forms\Get $get) => $get('month')) ->live() ->options(fn (\Filament\Forms\Get $get) => static::formatHijriDays($get('month'))), Select::make('timing') ->options([ ScheduledGivingTime::DAYTIME => 'Daytime (GMT) / 10:00am', ScheduledGivingTime::NIGHTTIME => 'Nighttime (GMT) / 10:00pm', ScheduledGivingTime::CUSTOM => 'Custom', ]) ->required() ->live() ->visible(fn (\Filament\Forms\Get $get) => $get('day')) ->required(fn (\Filament\Forms\Get $get) => $get('day')), TimePicker::make('timing_custom') ->visible(fn (\Filament\Forms\Get $get) => $get('timing') == 2) ->required(fn (\Filament\Forms\Get $get) => $get('timing') == 2) ->live() ->columnSpanFull(), ]) ->grid(2) ->columns(2) ->columnSpanFull() ->visible(fn (\Filament\Forms\Get $get) => (bool) $get('date_config_type')) ->live() ->helperText('Select the dates to use for this campaign. Ordering does not matter as it will be chronologically ordered once you save this page.'), ]) ->columns(1), Section::make('Allocations')->collapsible()->schema([ Repeater::make('allocation_types')->schema([ Select::make('donation_type_id') ->label('Donation Item') ->options(function (\Filament\Forms\Get $get) { $donationTypes = []; $firstDonationCountryId = static::donationCountries()->first()->id; if (($countryTypeId = $get('country_type_id')) && ($countryTypeId != $firstDonationCountryId)) { $donationTypes = DonationCountry::find($countryTypeId) ?->donationTypes() ->where('is_scheduling', true) ->get() ?? collect([]); } else { $donationTypes = static::donationTypes(); } return $donationTypes->mapWithKeys( fn ($record) => [$record->id => $record->display_name] )->toArray(); }) ->required() ->live(), Select::make('country_type_id') ->label('Country') ->options(function (\Filament\Forms\Get $get) { $donationCountries = []; $firstDonationTypeId = static::donationTypes()->first()->id; if (($donationTypeId = $get('donation_type_id')) && ($donationTypeId != $firstDonationTypeId)) { $donationCountries = DonationType::find($donationTypeId) ?->donationCountries ?? collect([]); } else { $donationCountries = DonationCountry::all(); } return $donationCountries->mapWithKeys( fn ($record) => [$record->id => $record->name] )->toArray(); }) ->required() ->live(), TextInput::make('title') ->placeholder('Hunger After Eid') ->required() ->columnSpanFull(), TextInput::make('description') ->placeholder('Make a contribution towards causes with the greatest need, including emergencies.') ->required() ->columnSpanFull(), ]) ->columns() ->helperText('Set up the possible allocation types for this donation.'), ]), Section::make('Page Metadata')->schema([ TextInput::make('meta_title') ->label('Title') ->maxLength(512) ->placeholder(fn (\Filament\Forms\Get $get) => $get('title')), Textarea::make('meta_description') ->label('Description') ->columnSpanFull(), TextInput::make('meta_keywords') ->label('Keywords') ->maxLength(512), ]) ->collapsible(), ]); } public static function table(Table $table): Table { return $table ->columns([ ToggleColumn::make('active'), TextColumn::make('user.name') ->label('Creator') ->numeric() ->sortable() ->toggleable(isToggledHiddenByDefault: false), TextColumn::make('title') ->description(fn ($record) => $record->slug) ->searchable(), TextColumn::make('split_types_formatted') ->label('Split Types'), TextColumn::make('created_at') ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('updated_at') ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), ]) ->actions([ EditAction::make() ->visible(fn () => Auth::user()?->hasRole('Superadmin')), // On Asim's request, this is only available to top-level administrators. ]); } public static function getRelations(): array { return [ ScheduledGivingDonationsRelationManager::class, ]; } public static function getPages(): array { return [ 'index' => ListScheduledGivingCampaigns::route('/'), 'create' => CreateScheduledGivingCampaign::route('/create'), 'edit' => EditScheduledGivingCampaign::route('/{record}/edit'), ]; } protected static function formatHijriMonths(): array { $c = new HijriCalendar; $islamicMonths = config('calendar.hijri.months'); $monthOffset = $c->getCurrentIslamicMonth(); $hijriYear = $c->getCurrentIslamicYear(); $rotatedMonths = array_merge( array_slice($islamicMonths, $monthOffset - 1, null, true), // From current month to the end array_slice($islamicMonths, 0, $monthOffset - 1, true) // From start to just before current month ); $output = []; foreach ($rotatedMonths as $index => $monthInfo) { $englishName = $monthInfo['en']; $arabicName = $monthInfo['ar']; $output[$monthInfo['number']] = "{$arabicName} ({$englishName}) {$hijriYear}"; if ($monthInfo['number'] == 12) { $hijriYear++; } } return $output; } /** * Will always return 30 days. This formats the days of the week to show. */ protected static function formatHijriDays(int $monthIndex): array { $c = new HijriCalendar; $monthIndex = Str::padLeft($monthIndex, 2, '0'); $currentYear = $c->getCurrentIslamicYear(); $currentDate = "01-{$monthIndex}-{$currentYear}"; $dates = []; for ($i = 1; $i <= 30; $i++) { $dates[$i] = $currentDate; $currentDate = $c->adjustHijriDate($currentDate, 1); } return $dates; } private static function donationTypes(): Collection { return DonationType::where('is_scheduling', true)->orderBy('display_name')->get(); } private static function donationCountries(): Collection { return DonationCountry::orderBy('name')->get(); } }