Home: - Empty state: 2-column with 'How it works' 5-step guide - Has data: 7/5 grid — pledges left, education right - Right column: status breakdown, sources, 'What to do next' contextual links, 'What the statuses mean' guide Money: - 8/4 two-column layout: table left, education right - Right column: 'How matching works' 4-step guide, status explainer, collection tips, quick action buttons - No more wasted right margin Reports: - 7/5 two-column layout: downloads left, education right - Right column: 'For your treasurer' 3-step guide, Gift Aid FAQ, 'Understanding your numbers' explainer - Activity log moved to right column Settings: - Removed max-w-2xl constraint, now uses full width - 7/5 two-column: checklist left, education right - Right column: 'What you're setting up' (5 items with Required badges), Privacy & data assurance, Common questions FAQ, 'Need help?' CTA - Every setting explained in human language
200 lines
10 KiB
PHP
200 lines
10 KiB
PHP
<x-filament-panels::page>
|
|
@php
|
|
$global = $this->getGlobalStats();
|
|
$campaigns = $this->getCampaignData();
|
|
$failed = $this->getFailedPayments();
|
|
$upcoming = $this->getUpcomingPayments();
|
|
@endphp
|
|
|
|
{{-- ── Global Overview ──────────────────────────────────────── --}}
|
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
|
<x-filament::section>
|
|
<div class="text-center">
|
|
<div class="text-3xl font-bold text-primary-600">{{ number_format($global['active_subscribers']) }}</div>
|
|
<div class="text-sm text-gray-500 mt-1">Active Subscribers</div>
|
|
</div>
|
|
</x-filament::section>
|
|
|
|
<x-filament::section>
|
|
<div class="text-center">
|
|
<div class="text-3xl font-bold text-success-600">£{{ number_format($global['collected'], 2) }}</div>
|
|
<div class="text-sm text-gray-500 mt-1">Total Collected</div>
|
|
</div>
|
|
</x-filament::section>
|
|
|
|
<x-filament::section>
|
|
<div class="text-center">
|
|
<div class="text-3xl font-bold text-warning-600">£{{ number_format($global['pending'], 2) }}</div>
|
|
<div class="text-sm text-gray-500 mt-1">Pending Collection</div>
|
|
</div>
|
|
</x-filament::section>
|
|
|
|
<x-filament::section>
|
|
<div class="text-center">
|
|
<div class="text-3xl font-bold {{ $global['collection_rate'] >= 80 ? 'text-success-600' : ($global['collection_rate'] >= 60 ? 'text-warning-600' : 'text-danger-600') }}">
|
|
{{ $global['collection_rate'] }}%
|
|
</div>
|
|
<div class="text-sm text-gray-500 mt-1">Collection Rate</div>
|
|
</div>
|
|
</x-filament::section>
|
|
</div>
|
|
|
|
{{-- ── Campaign Cards ──────────────────────────────────────── --}}
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
|
@foreach ($campaigns as $data)
|
|
@php
|
|
$c = $data['campaign'];
|
|
$isActive = $c->active;
|
|
$progressPct = $data['total_payments'] > 0
|
|
? round($data['paid_payments'] / $data['total_payments'] * 100)
|
|
: 0;
|
|
@endphp
|
|
|
|
<x-filament::section>
|
|
<x-slot name="heading">
|
|
<div class="flex items-center justify-between">
|
|
<span>{{ $c->title }}</span>
|
|
@if ($isActive)
|
|
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">● Live</span>
|
|
@else
|
|
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-600">○ Ended</span>
|
|
@endif
|
|
</div>
|
|
</x-slot>
|
|
|
|
{{-- Stats grid --}}
|
|
<div class="grid grid-cols-2 gap-3 mb-4">
|
|
<div>
|
|
<div class="text-xs text-gray-500 uppercase tracking-wide">Subscribers</div>
|
|
<div class="text-lg font-semibold">{{ number_format($data['active']) }} <span class="text-xs text-gray-400 font-normal">/ {{ number_format($data['subscribers']) }}</span></div>
|
|
</div>
|
|
<div>
|
|
<div class="text-xs text-gray-500 uppercase tracking-wide">Avg / Night</div>
|
|
<div class="text-lg font-semibold">£{{ number_format($data['avg_per_night'], 2) }}</div>
|
|
</div>
|
|
<div>
|
|
<div class="text-xs text-gray-500 uppercase tracking-wide">Collected</div>
|
|
<div class="text-lg font-semibold text-success-600">£{{ number_format($data['collected'], 2) }}</div>
|
|
</div>
|
|
<div>
|
|
<div class="text-xs text-gray-500 uppercase tracking-wide">Pending</div>
|
|
<div class="text-lg font-semibold text-warning-600">£{{ number_format($data['pending_amount'], 2) }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Payment progress bar --}}
|
|
<div class="mb-3">
|
|
<div class="flex justify-between text-xs text-gray-500 mb-1">
|
|
<span>{{ number_format($data['paid_payments']) }} / {{ number_format($data['total_payments']) }} payments</span>
|
|
<span>{{ $progressPct }}%</span>
|
|
</div>
|
|
<div class="w-full bg-gray-200 rounded-full h-2.5">
|
|
<div class="h-2.5 rounded-full {{ $progressPct >= 80 ? 'bg-success-500' : ($progressPct >= 50 ? 'bg-warning-500' : 'bg-primary-500') }}"
|
|
style="width: {{ $progressPct }}%"></div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Key metrics --}}
|
|
<div class="grid grid-cols-3 gap-2 text-center border-t pt-3">
|
|
<div>
|
|
<div class="text-xs text-gray-500">Nights</div>
|
|
<div class="font-semibold">{{ $data['total_nights'] }}</div>
|
|
</div>
|
|
<div>
|
|
<div class="text-xs text-gray-500">Completed</div>
|
|
<div class="font-semibold text-success-600">{{ $data['fully_completed'] ?? 0 }}</div>
|
|
</div>
|
|
<div>
|
|
<div class="text-xs text-gray-500">Failed</div>
|
|
<div class="font-semibold {{ $data['failed_payments'] > 0 ? 'text-danger-600' : 'text-gray-400' }}">{{ $data['failed_payments'] }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Quick links --}}
|
|
<div class="flex gap-2 mt-3 pt-3 border-t">
|
|
<a href="{{ url('/admin/scheduled-giving-donations?tableFilters[scheduled_giving_campaign_id][value]=' . $c->id) }}"
|
|
class="text-xs text-primary-600 hover:underline">View Subscribers →</a>
|
|
<a href="{{ url('/admin/scheduled-giving-campaigns/' . $c->id . '/edit') }}"
|
|
class="text-xs text-gray-500 hover:underline ml-auto">Edit Campaign</a>
|
|
</div>
|
|
</x-filament::section>
|
|
@endforeach
|
|
</div>
|
|
|
|
{{-- ── Upcoming Payments ───────────────────────────────────── --}}
|
|
@if (count($upcoming) > 0)
|
|
<x-filament::section>
|
|
<x-slot name="heading">
|
|
<div class="flex items-center gap-2">
|
|
<x-heroicon-o-clock class="w-5 h-5 text-primary-500" />
|
|
Upcoming Payments (Next 48 Hours)
|
|
</div>
|
|
</x-slot>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
@foreach ($upcoming as $u)
|
|
<div class="flex items-center justify-between p-3 bg-primary-50 rounded-lg">
|
|
<div>
|
|
<div class="font-semibold">{{ $u->campaign }}</div>
|
|
<div class="text-sm text-gray-500">{{ $u->payment_count }} payments</div>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="font-bold text-primary-600">£{{ number_format($u->total_amount, 2) }}</div>
|
|
<div class="text-xs text-gray-500">{{ \Carbon\Carbon::parse($u->earliest)->diffForHumans() }}</div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</x-filament::section>
|
|
@endif
|
|
|
|
{{-- ── Failed Payments (Needs Attention) ───────────────────── --}}
|
|
@if (count($failed) > 0)
|
|
<x-filament::section class="mt-6">
|
|
<x-slot name="heading">
|
|
<div class="flex items-center gap-2">
|
|
<x-heroicon-o-exclamation-triangle class="w-5 h-5 text-danger-500" />
|
|
Failed Payments — Needs Attention ({{ count($failed) }})
|
|
</div>
|
|
</x-slot>
|
|
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-sm">
|
|
<thead>
|
|
<tr class="text-left text-xs text-gray-500 uppercase border-b">
|
|
<th class="pb-2">Donor</th>
|
|
<th class="pb-2">Campaign</th>
|
|
<th class="pb-2">Amount</th>
|
|
<th class="pb-2">Expected</th>
|
|
<th class="pb-2">Attempts</th>
|
|
<th class="pb-2"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach ($failed as $f)
|
|
<tr class="border-b border-gray-100 hover:bg-gray-50">
|
|
<td class="py-2">
|
|
<div class="font-medium">{{ $f->donor_name }}</div>
|
|
<div class="text-xs text-gray-400">{{ $f->donor_email }}</div>
|
|
</td>
|
|
<td class="py-2">{{ $f->campaign }}</td>
|
|
<td class="py-2 font-semibold">£{{ number_format($f->amount / 100, 2) }}</td>
|
|
<td class="py-2 text-gray-500">{{ \Carbon\Carbon::parse($f->expected_at)->format('d M Y') }}</td>
|
|
<td class="py-2">
|
|
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-danger-100 text-danger-800">
|
|
{{ $f->attempts }}x failed
|
|
</span>
|
|
</td>
|
|
<td class="py-2">
|
|
<a href="{{ url('/admin/scheduled-giving-donations/' . $f->donation_id . '/edit') }}"
|
|
class="text-xs text-primary-600 hover:underline">View →</a>
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</x-filament::section>
|
|
@endif
|
|
</x-filament-panels::page>
|