feat: conditional & match funding pledges — deeply integrated across entire product
- Schema: isConditional, conditionType, conditionText, conditionThreshold, conditionMet, conditionMetAt on Pledge - Pledge form: 'This is a match pledge' toggle after amount selection - Two modes: threshold (if target is reached) and match (match funding) - Goal amount passed through from event - Auto-trigger: when total raised hits threshold, conditional pledges unlock automatically - WhatsApp notification sent to donor when unlocked - Threshold check runs after every pledge creation AND every status change - Cron: skips conditional pledges until conditionMet=true (no premature reminders) - Dashboard Home: progress bar shows conditional segment (amber), stats grid adds Conditional column - Dashboard Money: conditional/unlocked badge on pledge rows - Dashboard Collect: hero shows conditional total in amber - Dashboard Reports: financial summary shows conditional breakdown - Donor 'My Pledges': conditional card with condition text + activation status - Confirmation step: specialized messaging for match pledges - CRM export: includes is_conditional, condition_type, condition_text, condition_met columns - Status guide: conditional status explained in human language
This commit is contained in:
@@ -16,29 +16,33 @@
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<div class="grid grid-cols-2 md:grid-cols-5 gap-4">
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold text-primary-600">{{ number_format($global['current_subscribers']) }}</div>
|
||||
<div class="text-sm text-gray-500 mt-1">Active This Season</div>
|
||||
<div class="text-xs text-gray-400">{{ number_format($global['expired_subscribers']) }} from past seasons</div>
|
||||
<div class="text-sm text-gray-500 mt-1">Active</div>
|
||||
<div class="text-xs text-gray-400">{{ number_format($global['expired_subscribers']) }} past seasons</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold text-success-600">£{{ number_format($global['current_collected'], 0) }}</div>
|
||||
<div class="text-sm text-gray-500 mt-1">Collected This Season</div>
|
||||
<div class="text-xs text-gray-400">£{{ number_format($global['all_time_collected'], 0) }} all-time</div>
|
||||
<div class="text-3xl font-bold text-success-600">£{{ number_format($global['collected'], 0) }}</div>
|
||||
<div class="text-sm text-gray-500 mt-1">Collected</div>
|
||||
<div class="text-xs text-gray-400">{{ number_format($global['paid_payments']) }}/{{ number_format($global['due_payments']) }} due paid</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold text-warning-600">£{{ number_format($global['current_pending'], 0) }}</div>
|
||||
<div class="text-sm text-gray-500 mt-1">Pending</div>
|
||||
@if ($global['current_failed'] > 0)
|
||||
<div class="text-xs text-danger-500">{{ $global['current_failed'] }} failed</div>
|
||||
@endif
|
||||
<div class="text-3xl font-bold {{ $global['failed_count'] > 0 ? 'text-danger-600' : 'text-gray-400' }}">£{{ number_format($global['failed_amount'], 0) }}</div>
|
||||
<div class="text-sm text-gray-500 mt-1">Failed</div>
|
||||
<div class="text-xs text-danger-500">{{ number_format($global['failed_count']) }} payments</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold text-gray-500">£{{ number_format($global['scheduled_amount'], 0) }}</div>
|
||||
<div class="text-sm text-gray-500 mt-1">Upcoming</div>
|
||||
<div class="text-xs text-gray-400">{{ number_format($global['scheduled_count']) }} not yet due</div>
|
||||
</div>
|
||||
<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 class="text-xs text-gray-400">of due payments</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-filament::section>
|
||||
@@ -49,8 +53,11 @@
|
||||
@php
|
||||
$c = $data['campaign'];
|
||||
$hasCurrent = $data['current_subscribers'] > 0;
|
||||
$progressPct = $data['current_payments'] > 0
|
||||
? round($data['current_paid'] / $data['current_payments'] * 100)
|
||||
$duePct = $data['due_payments'] > 0
|
||||
? round($data['paid_payments'] / $data['due_payments'] * 100)
|
||||
: 0;
|
||||
$overallPct = $data['total_payments'] > 0
|
||||
? round($data['paid_payments'] / $data['total_payments'] * 100)
|
||||
: 0;
|
||||
@endphp
|
||||
|
||||
@@ -67,8 +74,6 @@
|
||||
</x-slot>
|
||||
|
||||
@if ($hasCurrent)
|
||||
{{-- Current Season --}}
|
||||
<div class="text-xs font-medium text-primary-600 uppercase tracking-wide mb-2">This Season</div>
|
||||
<div class="grid grid-cols-2 gap-3 mb-4">
|
||||
<div>
|
||||
<div class="text-xs text-gray-500 uppercase tracking-wide">Subscribers</div>
|
||||
@@ -80,23 +85,35 @@
|
||||
</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['current_collected'], 0) }}</div>
|
||||
<div class="text-lg font-semibold text-success-600">£{{ number_format($data['collected'], 0) }}</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['current_pending_amount'], 0) }}</div>
|
||||
<div class="text-xs text-gray-500 uppercase tracking-wide">
|
||||
{{ $data['collection_rate'] }}% Rate
|
||||
</div>
|
||||
<div class="text-lg font-semibold {{ $data['collection_rate'] >= 80 ? 'text-success-600' : ($data['collection_rate'] >= 60 ? 'text-warning-600' : 'text-danger-600') }}">
|
||||
{{ $data['paid_payments'] }}/{{ $data['due_payments'] }} due
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Payment progress bar --}}
|
||||
{{-- Progress bar: paid / total (including future) --}}
|
||||
<div class="mb-3">
|
||||
<div class="flex justify-between text-xs text-gray-500 mb-1">
|
||||
<span>{{ number_format($data['current_paid']) }} / {{ number_format($data['current_payments']) }} payments</span>
|
||||
<span>{{ $progressPct }}%</span>
|
||||
<span>{{ number_format($data['paid_payments']) }} paid, {{ number_format($data['failed_payments']) }} failed, {{ number_format($data['scheduled_payments']) }} upcoming</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700">
|
||||
<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 class="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700 flex overflow-hidden">
|
||||
@if ($data['total_payments'] > 0)
|
||||
<div class="h-2.5 bg-success-500" style="width: {{ $data['paid_payments'] / $data['total_payments'] * 100 }}%"></div>
|
||||
<div class="h-2.5 bg-danger-400" style="width: {{ $data['failed_payments'] / $data['total_payments'] * 100 }}%"></div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex justify-between text-xs mt-1">
|
||||
<span class="text-success-600">■ Paid</span>
|
||||
@if ($data['failed_payments'] > 0)
|
||||
<span class="text-danger-500">■ Failed (£{{ number_format($data['failed_amount'], 0) }})</span>
|
||||
@endif
|
||||
<span class="text-gray-400">■ Upcoming (£{{ number_format($data['scheduled_amount'], 0) }})</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -111,12 +128,12 @@
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs text-gray-500">Failed</div>
|
||||
<div class="font-semibold {{ $data['current_failed'] > 0 ? 'text-danger-600' : 'text-gray-400' }}">{{ $data['current_failed'] }}</div>
|
||||
<div class="font-semibold {{ $data['failed_payments'] > 0 ? 'text-danger-600' : 'text-gray-400' }}">{{ $data['failed_payments'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- All-time summary --}}
|
||||
{{-- All-time --}}
|
||||
<div class="{{ $hasCurrent ? 'mt-3 pt-3 border-t dark:border-gray-700' : '' }}">
|
||||
<div class="text-xs font-medium text-gray-400 uppercase tracking-wide mb-1">All Time</div>
|
||||
<div class="flex justify-between text-sm text-gray-500">
|
||||
@@ -220,7 +237,6 @@
|
||||
<p class="text-sm text-gray-500 mb-3">
|
||||
{{ number_format($quality['total_records']) }} total records in database.
|
||||
Only {{ number_format($global['total_subscribers']) }} are real subscribers with payments.
|
||||
The rest are incomplete sign-ups, test data, or soft-deleted.
|
||||
</p>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 text-center text-sm">
|
||||
<div class="p-2 bg-gray-50 dark:bg-gray-800 rounded">
|
||||
|
||||
Reference in New Issue
Block a user