Settings + Admin redesign + Community Leader role
## New: Community Leader role Who: Imam Yusuf, Sister Mariam, Uncle Tariq — the person who rallies their mosque, WhatsApp group, neighbourhood to pledge. Not an admin. Not a volunteer. A logged-in coordinator who needs more than a live feed but less than full admin access. /dashboard/community — their scoped dashboard: - 'How are WE doing?' — their stats vs the whole appeal (dark hero section) - Contribution percentage bar - Their links with full share buttons (Copy/WhatsApp/Email/QR) - Create new links (auto-tagged with their name) - Leaderboard: 'How communities compare' with 'You' badge - Read-only pledge list (no status changes, no bank details) Navigation changes for community_leader role: - Sees: My Community → Share Links → Reports (3 items) - Does NOT see: Home, Money, Settings, New Appeal button - Does NOT see: Bank details, WhatsApp config, reconciliation ## New: Team management API + UI GET/POST/PATCH/DELETE /api/team — CRUD for team members - Only org_admin/super_admin can invite - Temp password generated on invite (shown once) - Copy credentials or send via WhatsApp button - Role selector with descriptions (Admin, Community Leader, Staff, Volunteer) - Role change via dropdown, remove with trash icon - Can't change own role or remove self ## Settings page redesign Reordered by Aaisha's thinking: 1. WhatsApp (unchanged — most important) 2. Team (NEW — 'who has access? invite community leaders') 3. Bank account 4. Charity details 5. Direct Debit (collapsed in <details>) Team section shows: - All members with role icons (Crown/Users/Eye) - Inline role change dropdown - Remove button - Invite form with role cards and descriptions - Credentials shown once with copy + WhatsApp share buttons ## Admin page redesign Brand-consistent: no more shadcn Card/Badge/Table - Dark hero section with 7 platform stats - Pipeline status breakdown (gap-px grid) - Pill tab switcher (not shadcn Tabs) - Grid tables matching the rest of the dashboard - Role badges color-coded (blue super, green admin, amber leader) 6 files changed, 4 new routes/pages
This commit is contained in:
127
temp_files/v3/FundraiserNurtureWidget.php
Normal file
127
temp_files/v3/FundraiserNurtureWidget.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Filament\Resources\AppealResource;
|
||||
use App\Models\Appeal;
|
||||
use Filament\Tables\Actions\Action;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Filament\Widgets\TableWidget as BaseWidget;
|
||||
|
||||
/**
|
||||
* Shows fundraisers that need staff attention right now.
|
||||
*
|
||||
* This is the "nurture queue" — it surfaces fundraisers that are
|
||||
* stalling, almost succeeding, or brand new, so Jasmine can
|
||||
* proactively help supporters succeed.
|
||||
*/
|
||||
class FundraiserNurtureWidget extends BaseWidget
|
||||
{
|
||||
protected static ?int $sort = 5;
|
||||
|
||||
protected int | string | array $columnSpan = 'full';
|
||||
|
||||
protected static ?string $heading = 'Fundraisers That Need Your Help';
|
||||
|
||||
protected int $defaultPaginationPageOption = 5;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->query(
|
||||
Appeal::query()
|
||||
->where('status', 'confirmed')
|
||||
->where('is_accepting_donations', true)
|
||||
->where(function ($q) {
|
||||
$q->where(function ($q2) {
|
||||
// Needs outreach: £0 raised, 7+ days old
|
||||
$q2->where('amount_raised', 0)
|
||||
->where('created_at', '<', now()->subDays(7))
|
||||
->where('created_at', '>', now()->subDays(90)); // Not ancient
|
||||
})
|
||||
->orWhere(function ($q2) {
|
||||
// Almost there: 80%+ of target
|
||||
$q2->where('amount_raised', '>', 0)
|
||||
->whereRaw('amount_raised >= amount_to_raise * 0.8')
|
||||
->whereRaw('amount_raised < amount_to_raise');
|
||||
})
|
||||
->orWhere(function ($q2) {
|
||||
// New this week
|
||||
$q2->where('created_at', '>=', now()->subDays(7));
|
||||
});
|
||||
})
|
||||
->with('user')
|
||||
->orderByRaw("
|
||||
CASE
|
||||
WHEN amount_raised > 0 AND amount_raised >= amount_to_raise * 0.8 AND amount_raised < amount_to_raise THEN 1
|
||||
WHEN created_at >= NOW() - INTERVAL '7 days' THEN 2
|
||||
WHEN amount_raised = 0 AND created_at < NOW() - INTERVAL '7 days' THEN 3
|
||||
ELSE 4
|
||||
END ASC
|
||||
")
|
||||
)
|
||||
->columns([
|
||||
TextColumn::make('priority')
|
||||
->label('')
|
||||
->getStateUsing(function (Appeal $a) {
|
||||
$raised = $a->amount_raised;
|
||||
$target = $a->amount_to_raise;
|
||||
$age = $a->created_at?->diffInDays(now()) ?? 0;
|
||||
|
||||
if ($raised > 0 && $raised >= $target * 0.8 && $raised < $target) return '🟡 Almost there';
|
||||
if ($age <= 7) return '🆕 New';
|
||||
if ($raised == 0) return '🔴 Needs help';
|
||||
return '—';
|
||||
})
|
||||
->badge()
|
||||
->color(function (Appeal $a) {
|
||||
$raised = $a->amount_raised;
|
||||
$target = $a->amount_to_raise;
|
||||
$age = $a->created_at?->diffInDays(now()) ?? 0;
|
||||
|
||||
if ($raised > 0 && $raised >= $target * 0.8) return 'warning';
|
||||
if ($age <= 7) return 'info';
|
||||
return 'danger';
|
||||
}),
|
||||
|
||||
TextColumn::make('name')
|
||||
->label('Fundraiser')
|
||||
->limit(35)
|
||||
->weight('bold')
|
||||
->description(fn (Appeal $a) => $a->user?->name ? 'by ' . $a->user->name : ''),
|
||||
|
||||
TextColumn::make('progress')
|
||||
->label('Progress')
|
||||
->getStateUsing(function (Appeal $a) {
|
||||
$raised = $a->amount_raised / 100;
|
||||
$target = $a->amount_to_raise / 100;
|
||||
$pct = $target > 0 ? round($raised / $target * 100) : 0;
|
||||
return '£' . number_format($raised, 0) . ' / £' . number_format($target, 0) . " ({$pct}%)";
|
||||
}),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label('Created')
|
||||
->since(),
|
||||
])
|
||||
->actions([
|
||||
Action::make('view')
|
||||
->label('Open')
|
||||
->icon('heroicon-o-arrow-right')
|
||||
->url(fn (Appeal $a) => AppealResource::getUrl('edit', ['record' => $a]))
|
||||
->color('gray'),
|
||||
|
||||
Action::make('email')
|
||||
->label('Email Owner')
|
||||
->icon('heroicon-o-envelope')
|
||||
->url(fn (Appeal $a) => $a->user?->email ? 'mailto:' . $a->user->email : null)
|
||||
->visible(fn (Appeal $a) => (bool) $a->user?->email)
|
||||
->openUrlInNewTab()
|
||||
->color('info'),
|
||||
])
|
||||
->paginated([5, 10])
|
||||
->emptyStateHeading('All fundraisers are doing well!')
|
||||
->emptyStateDescription('No fundraisers need attention right now.')
|
||||
->emptyStateIcon('heroicon-o-face-smile');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user