Files
calvana/temp_files/RecentDonationsWidget.php
Omair Saleh a9b3b70dfc Telepathic Collect: link-first, flattened hierarchy, embedded leaderboard
Core insight: The primary object is the LINK, not the appeal.
Aaisha doesn't think 'manage appeals' — she thinks 'share my link'.

## Collect page (/dashboard/collect) — complete rewrite
- Flattened hierarchy: single-appeal orgs see links directly (no card to click)
- Multi-appeal orgs: quiet appeal switcher at top, links below
- Inline link creation: just type a name + press Enter (no dialog)
- Quick preset buttons: 'Table 1', 'WhatsApp Group', 'Instagram', etc.
- Share buttons are THE primary CTA on every link card (Copy, WhatsApp, Email, Share)
- Each link shows: clicks, pledges, amount raised, conversion rate
- Embedded mini-leaderboard when 3+ links have pledges
- Contextual tips when pledges < 5 ('give each volunteer their own link')
- New appeal creation is inline, auto-creates 'Main link'

## Appeal detail page (/dashboard/events/[id]) — brand redesign
- Sharp edges, gap-px grids, typography-as-hero
- Same link card component with share-first design
- Embedded leaderboard section
- Inline link creation (same as Collect)
- Clone appeal button
- Appeal details in collapsed <details> (context, not hero)
- Download all QR codes link
- Public progress page link

## Leaderboard page — brand redesign
- Total raised as hero number (dark section)
- Progress bars relative to leader
- Medal badges for top 3
- Conversion rate badges
- Auto-refresh every 10 seconds (live event mode)

## Route cleanup
- /dashboard/events re-exports /dashboard/collect (backward compat)
- Old events/page.tsx removed (was duplicate)

5 files changed, 3 pages redesigned
2026-03-04 21:13:32 +08:00

87 lines
3.1 KiB
PHP

<?php
namespace App\Filament\Widgets;
use App\Filament\Resources\CustomerResource;
use App\Models\Donation;
use Filament\Tables\Actions\Action;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget as BaseWidget;
class RecentDonationsWidget extends BaseWidget
{
protected static ?int $sort = 4;
protected int | string | array $columnSpan = 'full';
protected static ?string $heading = 'Latest Donations';
protected int $defaultPaginationPageOption = 5;
public function table(Table $table): Table
{
return $table
->query(
Donation::query()
->with(['customer', 'donationType', 'donationConfirmation', 'appeal'])
->latest('created_at')
)
->columns([
IconColumn::make('is_confirmed')
->label('')
->boolean()
->getStateUsing(fn (Donation $r) => $r->isConfirmed())
->trueIcon('heroicon-o-check-circle')
->falseIcon('heroicon-o-x-circle')
->trueColor('success')
->falseColor('danger'),
TextColumn::make('customer.name')
->label('Donor')
->description(fn (Donation $d) => $d->customer?->email)
->searchable(query: function (\Illuminate\Database\Eloquent\Builder $query, string $search) {
$query->whereHas('customer', fn ($q) => $q
->where('first_name', 'like', "%{$search}%")
->orWhere('last_name', 'like', "%{$search}%")
->orWhere('email', 'like', "%{$search}%")
);
}),
TextColumn::make('amount')
->label('Amount')
->money('gbp', divideBy: 100)
->sortable()
->weight('bold'),
TextColumn::make('donationType.display_name')
->label('Cause')
->badge()
->color('success')
->limit(20),
TextColumn::make('appeal.name')
->label('Fundraiser')
->limit(20)
->placeholder('Direct donation'),
TextColumn::make('created_at')
->label('When')
->since()
->sortable(),
])
->actions([
Action::make('view_donor')
->label('View Donor')
->icon('heroicon-o-user')
->url(fn (Donation $d) => $d->customer_id
? CustomerResource::getUrl('edit', ['record' => $d->customer_id])
: null)
->visible(fn (Donation $d) => (bool) $d->customer_id),
])
->paginated([5, 10])
->defaultSort('created_at', 'desc');
}
}