record; $total = $customer->donations() ->whereHas('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at')) ->sum('amount') / 100; $giftAid = $customer->donations() ->whereHas('donationPreferences', fn ($q) => $q->where('is_gift_aid', true)) ->exists(); $badges = ''; if ($total >= 1000) { $badges .= ' ⭐ Major Donor'; } if ($giftAid) { $badges .= ' Gift Aid'; } $sg = $customer->scheduledGivingDonations()->where('is_active', true)->first(); if ($sg) { $amt = '£' . number_format($sg->total_amount, 0); $badges .= ' 💙 ' . $amt . '/month'; } // Check for incomplete (problem) donations in last 7 days $incompleteRecent = $customer->donations() ->whereDoesntHave('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at')) ->where('created_at', '>=', now()->subDays(7)) ->count(); if ($incompleteRecent > 0) { $badges .= ' ⚠ ' . $incompleteRecent . ' incomplete'; } return new HtmlString($customer->name . $badges); } // ─── Subheading: The one-line story of this donor ──────────── public function getSubheading(): ?string { $customer = $this->record; $confirmed = $customer->donations() ->whereHas('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at')); $total = $confirmed->sum('amount') / 100; $count = $confirmed->count(); $first = $customer->donations()->oldest()->first(); $since = $first ? $first->created_at->format('M Y') : null; $parts = []; if ($total > 0) { $parts[] = '£' . number_format($total, 2) . ' donated across ' . $count . ' donations'; } else { $parts[] = 'No confirmed donations yet'; } if ($since) { $parts[] = 'Supporter since ' . $since; } return implode(' · ', $parts); } // ─── Header Actions: What staff DO when they find a donor ──── // These are the actual tasks that take 4+ clicks today: // "Add a note", "Resend a receipt", "Look them up in Stripe" protected function getHeaderActions(): array { $customer = $this->record; return [ // Quick note — the #1 thing support staff do Action::make('add_note') ->label('Add Note') ->icon('heroicon-o-chat-bubble-left-ellipsis') ->color('gray') ->form([ Textarea::make('body') ->label('Note') ->placeholder("e.g. Called on " . now()->format('d M') . " — wants to update their address") ->required() ->rows(3), ]) ->action(function (array $data) use ($customer) { $customer->internalNotes()->create([ 'user_id' => auth()->id(), 'body' => $data['body'], ]); Notification::make()->title('Note added')->success()->send(); }), // Resend receipt — second most common request Action::make('resend_receipt') ->label('Resend Receipt') ->icon('heroicon-o-envelope') ->color('info') ->form([ Select::make('donation_id') ->label('Which donation?') ->options(function () use ($customer) { return $customer->donations() ->whereHas('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at')) ->latest() ->take(10) ->get() ->mapWithKeys(function ($d) { $label = '£' . number_format($d->amount / 100, 2) . ' on ' . $d->created_at->format('d M Y') . ' — ' . ($d->donationType?->display_name ?? 'Unknown'); return [$d->id => $label]; }); }) ->required() ->helperText('Select the donation to resend the receipt for'), ]) ->visible(fn () => $customer->donations() ->whereHas('donationConfirmation', fn ($q) => $q->whereNotNull('confirmed_at')) ->exists()) ->action(function (array $data) use ($customer) { $donation = $customer->donations()->find($data['donation_id']); if ($donation) { try { Mail::to($customer->email) ->send(new \App\Mail\DonationConfirmed($donation)); Notification::make() ->title('Receipt sent to ' . $customer->email) ->body('For donation of £' . number_format($donation->amount / 100, 2) . ' on ' . $donation->created_at->format('d M Y')) ->success() ->send(); } catch (\Throwable $e) { Notification::make()->title('Failed to send receipt')->body($e->getMessage())->danger()->send(); } } }), // View in Stripe — for investigating payment issues Action::make('view_in_stripe') ->label('Stripe') ->icon('heroicon-o-arrow-top-right-on-square') ->color('gray') ->url('https://dashboard.stripe.com/search?query=' . urlencode($customer->email)) ->openUrlInNewTab(), // Email the donor directly Action::make('email_donor') ->label('Email') ->icon('heroicon-o-at-symbol') ->color('gray') ->url('mailto:' . $customer->email) ->openUrlInNewTab(), ]; } }