#!/usr/bin/env python3 """ Reassign every Filament resource to the correct navigation group. SIDEBAR STRUCTURE: Daily (always open, no collapse) ├ Donors — "Find a donor, help them" ├ Donations — "See what came in, investigate issues" └ Regular Giving — "Monthly supporters" Fundraising ├ Review Queue (84 pending badge) ├ All Fundraisers └ Scheduled Campaigns (3 only) Setup (collapsed by default — rarely touched) ├ Causes (30) ├ Countries (12) ├ URL Builder ├ Settings ├ Users ├ Activity Log └ (hidden): Snowdon, Campaign Regs, WOH, Engage dims, Fund dims """ import re, os BASE = '/home/forge/app.charityright.org.uk' changes = { # ── DAILY (top 3 — the only pages staff use every day) ── 'app/Filament/Resources/CustomerResource.php': { 'navigationGroup': 'Daily', 'navigationIcon': 'heroicon-o-user-circle', 'navigationSort': 1, 'navigationLabel': 'Donors', }, 'app/Filament/Resources/DonationResource.php': { 'navigationGroup': 'Daily', 'navigationIcon': 'heroicon-o-banknotes', 'navigationSort': 2, 'navigationLabel': 'Donations', }, 'app/Filament/Resources/ScheduledGivingDonationResource.php': { 'navigationGroup': 'Daily', 'navigationIcon': 'heroicon-o-arrow-path', 'navigationSort': 3, 'navigationLabel': 'Regular Giving', }, # ── FUNDRAISING ── 'app/Filament/Resources/ApprovalQueueResource.php': { 'navigationGroup': 'Fundraising', 'navigationIcon': 'heroicon-o-shield-check', 'navigationSort': 1, 'navigationLabel': 'Review Queue', }, 'app/Filament/Resources/AppealResource.php': { 'navigationGroup': 'Fundraising', 'navigationIcon': 'heroicon-o-hand-raised', 'navigationSort': 2, 'navigationLabel': 'All Fundraisers', }, 'app/Filament/Resources/ScheduledGivingCampaignResource.php': { 'navigationGroup': 'Fundraising', 'navigationIcon': 'heroicon-o-calendar', 'navigationSort': 3, 'navigationLabel': 'Giving Campaigns', }, # ── SETUP (collapsed, rarely used) ── 'app/Filament/Resources/DonationTypeResource.php': { 'navigationGroup': 'Setup', 'navigationIcon': 'heroicon-o-tag', 'navigationSort': 1, 'navigationLabel': 'Causes', }, 'app/Filament/Resources/DonationCountryResource.php': { 'navigationGroup': 'Setup', 'navigationIcon': 'heroicon-o-globe-alt', 'navigationSort': 2, 'navigationLabel': 'Countries', }, 'app/Filament/Resources/UserResource.php': { 'navigationGroup': 'Setup', 'navigationIcon': 'heroicon-o-users', 'navigationSort': 3, 'navigationLabel': 'Admin Users', }, 'app/Filament/Resources/EventLogResource.php': { 'navigationGroup': 'Setup', 'navigationIcon': 'heroicon-o-exclamation-triangle', 'navigationSort': 4, 'navigationLabel': 'Activity Log', }, } # Resources to HIDE from navigation entirely (dead data, 0 recent activity) hide = [ 'app/Filament/Resources/SnowdonRegistrationResource.php', 'app/Filament/Resources/CampaignRegistrationResource.php', 'app/Filament/Resources/WOHMessageResource.php', 'app/Filament/Resources/EngageAttributionDimensionResource.php', 'app/Filament/Resources/EngageFundDimensionResource.php', ] # Pages page_changes = { 'app/Filament/Pages/DonationURLBuilder.php': { 'navigationGroup': 'Setup', 'navigationSort': 5, 'navigationLabel': 'URL Builder', }, 'app/Filament/Pages/Settings.php': { 'navigationGroup': 'Setup', 'navigationSort': 6, }, } def update_static_property(content, prop, value): """Replace a static property value in a PHP class.""" # Match: protected static ?string $prop = 'old_value'; # or: protected static ?int $prop = 123; pattern = rf"(protected\s+static\s+\?(?:string|int)\s+\${prop}\s*=\s*)('[^']*'|\d+)(\s*;)" if isinstance(value, int): replacement = rf"\g<1>{value}\3" else: replacement = rf"\g<1>'{value}'\3" new_content, count = re.subn(pattern, replacement, content) return new_content, count def apply_changes(filepath, props): full = os.path.join(BASE, filepath) with open(full, 'r') as f: content = f.read() for prop, value in props.items(): content, count = update_static_property(content, prop, value) if count == 0: print(f" WARNING: {prop} not found in {filepath}") with open(full, 'w') as f: f.write(content) print(f"Updated: {filepath}") def hide_from_nav(filepath): full = os.path.join(BASE, filepath) with open(full, 'r') as f: content = f.read() # Add shouldRegisterNavigation = false if not present if 'shouldRegisterNavigation' not in content: # Insert after the class opening content = content.replace( 'protected static ?string $navigationIcon', 'protected static bool $shouldRegisterNavigation = false;\n\n protected static ?string $navigationIcon' ) with open(full, 'w') as f: f.write(content) print(f"Hidden: {filepath}") else: print(f"Already hidden: {filepath}") # Apply all changes print("=== UPDATING NAVIGATION ===") for filepath, props in changes.items(): apply_changes(filepath, props) print("\n=== HIDING DEAD PAGES ===") for filepath in hide: hide_from_nav(filepath) print("\n=== UPDATING PAGES ===") for filepath, props in page_changes.items(): apply_changes(filepath, props) print("\nDone!")