45 KiB
Admin Settings Frontend Implementation Plan - Execution Document
Last Updated: 2025-11-06 Status: Ready for Parallel Execution Priority Order: Vehicle Catalog → Station Oversight → Admin Users → Audit Logs
Executive Summary
This document is the canonical execution plan for implementing the admin settings UI across desktop and mobile. It is designed to enable multiple AI agents to work in parallel on different phases.
Current State:
- Frontend: All infrastructure (API hooks, types, auth gates) exists; pages are routed but use placeholder content
- Backend: Phase 0 requires implementation of 5 missing batch operation endpoints
Key Decisions:
- Implement polling-based audit logs (5-second intervals) until SSE backend is ready
- Use client-side batch loops until backend batch endpoints exist
- All features MUST be implemented on both desktop and mobile
- Mobile routing integration is Phase 5 (after UI implementation)
Phase 0 - Backend Coordination (BLOCKING)
Status: Investigation Complete Owner: Backend Team (Coordination)
Required Backend Implementation
The following endpoints are MISSING and must be created before bulk UI can be tested:
POST /api/admin/admins/bulk
Request: { admins: [{ email: string, role?: 'admin' | 'super_admin' }] }
Response: { created: AdminUser[], failed: { email: string, error: string }[] }
PATCH /api/admin/admins/bulk-revoke
Request: { auth0Subs: string[] }
Response: { revoked: AdminUser[], failed: { auth0Sub: string, error: string }[] }
PATCH /api/admin/admins/bulk-reinstate
Request: { auth0Subs: string[] }
Response: { reinstated: AdminUser[], failed: { auth0Sub: string, error: string }[] }
DELETE /api/admin/catalog/{entity}/bulk-delete
Path: entity = 'makes' | 'models' | 'years' | 'trims' | 'engines'
Request: { ids: string[] }
Response: { deleted: string[], failed: { id: string, error: string }[] }
DELETE /api/admin/stations/bulk-delete
Request: { ids: string[], force?: boolean }
Response: { deleted: string[], failed: { id: string, error: string }[] }
GET /api/admin/audit-logs/stream (SSE)
Content-Type: text/event-stream
Payload: { id: string, data: AdminAuditLog }
Interim Strategy
Frontend agents can proceed with implementation using client-side batch loops against existing single-operation endpoints:
PATCH /api/admin/admins/:auth0Sub/revoke(revoke loop)DELETE /api/admin/catalog/makes/:makeId(delete loop)DELETE /api/admin/stations/:stationId(delete loop)
When backend batch endpoints are ready, frontend simply swaps the API calls without changing UI logic.
Polling Fallback for Audit Logs
Until GET /api/admin/audit-logs/stream exists, use polling:
GET /api/admin/audit-logs?limit=50&offset=0&sort=createdAt:desc
Phase 1 - Vehicle Catalog (Priority 1)
Owner: Any AI Agent Dependency: None (uses existing hooks) Status: Ready to Start
Overview
Implement the vehicle catalog management interface for both desktop and mobile with hierarchical navigation, multi-select bulk delete, and cascading delete warnings.
Desktop UI - AdminCatalogPage.tsx
File: frontend/src/pages/admin/AdminCatalogPage.tsx (currently placeholder)
Layout:
┌─ Header: "Vehicle Catalog" + Stats Cards ──────────────────────┐
│ Makes: 100 | Models: 500 | Years: 20 | Trims: 5000 | Engines: 8000
├─ Main Content ──────────────────────────────────────────────────┤
│ ┌──────────────────────┐ ┌──────────────────────────────────┐ │
│ │ Left Panel: Tree │ │ Right Panel: DataGrid │ │
│ │ │ │ │ │
│ │ [✓] Makes (100) │ │ □ Name │ Created │ Updated │ │
│ │ ├─ [✓] Model (500) │ │ ─────────────────────────────────│ │
│ │ │ ├─ Year (20) │ │ □ Honda │ 2024 │ 2025 │ │
│ │ │ ├─ Trim (5000) │ │ □ Toyota │ 2024 │ 2025 │ │
│ │ │ └─ Engine (8000) │ │ □ Ford │ 2024 │ 2025 │ │
│ │ └─ [✓] Model 2 │ │ │ │
│ │ │ │ [Add] [Edit] [Delete] [Bulk Del] │ │
│ └──────────────────────┘ └──────────────────────────────────┘ │
│ │
│ ┌─ Audit Log Panel (Collapsible) ──────────────────────────────┐
│ │ Catalog Changes (Last 50) [collapse]
│ │ ──────────────────────────────────────────────────────────── │
│ │ 2025-11-06 14:32 | admin@example.com | Created Make | Honda │
│ │ 2025-11-06 14:31 | admin@example.com | Deleted Make | Toyota │
│ └────────────────────────────────────────────────────────────────┘
└──────────────────────────────────────────────────────────────────┘
Key Features:
- Left panel: Hierarchical tree with expandable levels (Makes → Models → Years → Trims → Engines)
- Right panel: Data grid for selected level with columns: [checkbox], Name, Created, Updated, Actions
- Selection:
- Individual checkbox for each row
- "Select All" checkbox (selects across ALL pages, not just current page)
- Toolbar buttons: Add, Edit, Delete (single), Bulk Delete
- Breadcrumb: "Catalog > Makes > (Name)" to show current selection context
- Timestamps: Show created/updated times with formatting
- Audit log sidebar: Filtered to catalog-related actions only
Forms & Dialogs:
- Create/Edit form modal with validation (name required, parent context shown)
- Confirm bulk delete dialog: "Delete X items? This will also delete Y dependent items. Continue?"
- Loading states and error displays
API Hooks to Use:
// From frontend/src/features/admin/hooks/useCatalog.ts
useMakes() / useCreateMake() / useUpdateMake() / useDeleteMake()
useModels() / useCreateModel() / useUpdateModel() / useDeleteModel()
useYears() / useCreateYear() / useDeleteYear()
useTrims() / useCreateTrim() / useUpdateTrim() / useDeleteTrim()
useEngines() / useCreateEngine() / useUpdateEngine() / useDeleteEngine()
State Management:
- Track selected level in URL or state (e.g.,
?level=models&parentId=123) - Implement
useBulkSelectionhook for multi-select state - Query invalidation strategy: Deleting a Make should invalidate Models, Years, Trims, Engines queries
Implementation Steps:
- Create shared admin components (Phase 1A)
- Implement tree navigation panel
- Implement data grid with selection
- Wire forms and dialogs
- Test all CRUD flows
Mobile UI - AdminCatalogMobileScreen.tsx
File: frontend/src/features/admin/mobile/AdminCatalogMobileScreen.tsx (currently placeholder)
Layout:
┌─ Header: "Catalog" + level indicator ─────────────────┐
│ Makes (100) [filters] [sort] │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─ Card ──────────────────────────────────────────┐ │
│ │ □ Honda Civic │ │
│ │ Created: 2024-01-15 | Updated: 2025-11-01 │ │
│ │ Models: 25 │ │
│ │ [Edit] [Delete] [View Models →] │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─ Card ──────────────────────────────────────────┐ │
│ │ □ Honda CR-V │ │
│ │ Created: 2024-02-10 | Updated: 2025-11-02 │ │
│ │ Models: 18 │ │
│ │ [Edit] [Delete] [View Models →] │ │
│ └─────────────────────────────────────────────────┘ │
│ │
├─ Sticky Bottom Action Bar (Multi-Select Mode) ────────┤
│ Selected: 3 [Delete] [Cancel] │
└─────────────────────────────────────────────────────────┘
Key Features:
- Drill-down navigation: List → Select item → View children → Navigate deeper
- Card-based list showing entity name, metadata, count of children
- Long-press or "Select" button to enter multi-select mode
- Checkboxes appear in multi-select mode
- Sticky bottom action bar: "Selected: N | [Delete] [Cancel]"
- Quick action buttons: Edit, Delete (single), View Children
- Breadcrumb navigation at top showing: "Catalog > Makes > Honda > Models"
- Bottom sheet for bulk actions confirmation
- "Recent Changes" sheet: Tap icon to open audit log filtered to catalog
- Touch targets ≥44px
Mobile-Specific Patterns:
- Avoid cramped UI - use bottom sheets for actions, not inline toolbars
- Swipe-to-delete option (optional, test in QA)
- Collapsible breadcrumb if needed to save space
- Loading skeleton cards
Implementation Steps:
- Build card component for catalog items
- Implement drill-down navigation state
- Add multi-select mode toggle
- Wire sticky action bar
- Add breadcrumb navigation
- Integrate audit log bottom sheet
Shared Components & Hooks (Phase 1A)
Create these components first - they'll be reused in all phases:
Hooks
File: frontend/src/features/admin/hooks/useBulkSelection.ts
export function useBulkSelection<T extends { id: string }>(items: T[]) {
// Returns: {
// selected: Set<string>,
// toggleItem(id: string): void,
// toggleAll(items: T[]): void,
// isSelected(id: string): boolean,
// reset(): void,
// count: number,
// }
}
File: frontend/src/features/admin/hooks/useAuditLogStream.ts
export function useAuditLogStream(filters?: {
resourceType?: string;
actionType?: string;
limit?: number;
}) {
// Uses polling (GET /api/admin/audit-logs) until SSE available
// Returns: {
// logs: AdminAuditLog[],
// loading: boolean,
// error: any,
// pagination: { page: number, limit: number, total: number },
// hasMore: boolean,
// nextPage(): void,
// prevPage(): void,
// refetch(): void,
// }
}
Components
File: frontend/src/features/admin/components/AdminSectionHeader.tsx
- Props: title, stats (array of { label, value })
- Displays: "Vehicle Catalog" with stats cards
File: frontend/src/features/admin/components/AdminDataGrid.tsx
- Props: rows, columns, onSelectChange, selectedIds, loading, error, toolbar slot
- Features: Checkbox column, sortable headers, pagination controls
- Reusable for all data tables
File: frontend/src/features/admin/components/SelectionToolbar.tsx
- Props: selectedCount, onSelectAll, onClear, children (action buttons)
- Displays: "Selected: N | [Select All] [Clear] + custom actions"
File: frontend/src/features/admin/components/BulkActionDialog.tsx
- Props: open, title, message, items, onConfirm, onCancel, loading
- For confirming destructive bulk actions
File: frontend/src/features/admin/components/AuditLogPanel.tsx (Desktop)
- Props: resourceType, filters
- Sidebar panel showing paginated audit logs
- Collapse/expand toggle
File: frontend/src/features/admin/components/AuditLogDrawer.tsx (Mobile)
- Props: resourceType, filters, open, onClose
- Bottom sheet for mobile audit log view
File: frontend/src/features/admin/components/EmptyState.tsx
- Props: icon, title, description, action?
- Reusable empty state display
File: frontend/src/features/admin/components/ErrorState.tsx
- Props: error, onRetry
- Reusable error display with retry button
File: frontend/src/features/admin/components/AdminSkeleton.tsx
- Loading skeleton cards/rows for all views
Bulk Delete Pattern (Interim - Client-Side Loops)
Until backend batch endpoints exist, implement bulk operations like this:
async function bulkDeleteMakes(makeIds: string[]) {
const results = { deleted: [], failed: [] };
for (const makeId of makeIds) {
try {
await deleteM Make(makeId);
results.deleted.push(makeId);
} catch (error) {
results.failed.push({ makeId, error: error.message });
}
}
// Invalidate queries to refresh UI
queryClient.invalidateQueries({ queryKey: ['makes'] });
queryClient.invalidateQueries({ queryKey: ['models'] }); // Cascade
return results;
}
When backend batch endpoints are ready, swap to:
const { deleted, failed } = await api.deleteMakesBulk(makeIds);
Testing Checklist (Phase 1)
- Tree navigation works (expand/collapse, selection)
- Data grid displays correctly with sorting
- Select All checkbox selects across all pages
- Bulk delete shows confirmation with cascade warning
- Single delete works with confirmation
- Create/Edit forms validate and submit
- Mobile drill-down navigation works smoothly
- Multi-select mode on mobile toggles correctly
- Audit log panel updates (or shows cached data)
- Loading/error states render properly
- Touch targets ≥44px on mobile
- Desktop ≥1280px and mobile ≤480px viewports tested
Phase 2 - Station Oversight (Priority 2)
Owner: Any AI Agent Dependency: Phase 1A components complete Status: Ready to Start After Phase 1A
Overview
Implement station management with soft/hard delete, restore functionality, and search/filter.
Desktop UI - AdminStationsPage.tsx
File: frontend/src/pages/admin/AdminStationsPage.tsx (currently placeholder)
Layout:
┌─ Header: "Station Management" + Stats ──────────────────────────┐
│ Total: 500 | Active: 480 | Deleted: 20 | Archived: 0 │
├─ Search & Filters ─────────────────────────────────────────────┤
│ [Search by name...] [Active] [Deleted] [Archived] [✕ Clear All] │
├─ DataGrid ─────────────────────────────────────────────────────┤
│ □ Name │ Address │ Status │ Modified │ CreatedBy │
│ ─────────────────────────────────────────────────────────────── │
│ □ Shell Station │ 123 Main St │ Active │ 2025-11-01│ admin1 │
│ □ BP Station │ 456 Oak Ave │ Deleted │ 2025-10-15│ admin2 │
│ □ Chevron │ 789 Pine Rd │ Active │ 2025-11-03│ admin1 │
│ │
│ [Add Station] [Delete] [Restore] [Hard Delete] [Export CSV] │
│ │
├─ Detail Drawer (Right Side) ───────────────────────────────────┤
│ Shell Station [Close] │
│ ──────────────────────────────────────────────────────────── │
│ Address: 123 Main St, Springfield, IL 62701 │
│ Coordinates: 39.7817, -89.6501 │
│ Status: Active │
│ Created: 2024-01-15 by admin@example.com │
│ Last Modified: 2025-11-01 by admin@example.com │
│ [Edit] [Delete] [Hard Delete] │
│ │
├─ Audit Log Panel (Collapsible) ────────────────────────────────┤
│ Station Events (Last 50) [collapse] │
│ ────────────────────────────────────────────────────────────── │
│ 2025-11-06 14:32 | admin1 | Hard deleted | Shell Station │
│ 2025-11-06 14:31 | admin2 | Deleted | BP Station │
└────────────────────────────────────────────────────────────────┘
Key Features:
- Search bar: Filter by station name
- Filter chips: Active, Deleted, Archived (soft-deleted showing as "Deleted")
- Multi-select with bulk delete (toggle soft/hard via dialog)
- Single-item quick actions: Delete, Restore (if deleted), Hard Delete
- Detail drawer on right showing full metadata
- Status column shows: Active, Deleted (soft), or Archived
- Toast notifications for hard deletes (warning)
- Audit log sidebar filtered to station events
- "Export CSV" button (stub for now)
Forms & Dialogs:
- Create/Edit station form (name, address, coordinates)
- Confirm soft delete dialog: "This will soft-delete the station. Data can be restored."
- Confirm hard delete dialog: "This will permanently delete the station and cannot be undone. Continue?"
- Confirm restore dialog: "Restore this station to active?"
API Hooks:
useStationOverview() / useCreateStation() / useUpdateStation() / useDeleteStation()
Status & Delete Logic:
deletedAt === null→ "Active"deletedAt !== null→ "Deleted" (soft delete)- Hard delete: Pass
?force=trueto delete endpoint
Implementation Steps:
- Build data grid with search and filter chips
- Implement detail drawer
- Add create/edit form modal
- Wire soft/hard delete dialogs
- Add restore functionality
- Integrate audit log panel
- Test all flows
Mobile UI - AdminStationsMobileScreen.tsx
File: frontend/src/features/admin/mobile/AdminStationsMobileScreen.tsx (currently placeholder)
Layout:
┌─ Header: "Stations" + Filters ────────────────────┐
│ Filter: [All] [Active] [Deleted] [sort by ▼] │
├───────────────────────────────────────────────────┤
│ │
│ ┌─ Card ──────────────────────────────────────┐ │
│ │ Shell Station [status: Active]│
│ │ 123 Main St, Springfield, IL 62701 │ │
│ │ Created: 2024-01-15 | Modified: 2025-11-01 │ │
│ │ [Edit] [Delete] [Restore] [Details →] │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ ┌─ Card ──────────────────────────────────────┐ │
│ │ BP Station [status: Deleted] │
│ │ 456 Oak Ave, Shelbyville, IL 62702 │ │
│ │ Created: 2024-02-20 | Modified: 2025-10-15 │ │
│ │ [Edit] [Delete] [Restore] [Details →] │ │
│ └─────────────────────────────────────────────┘ │
│ │
├─ Sticky Bottom Action Bar (Multi-Select Mode) ──┤
│ Selected: 2 [Delete] [Cancel] │
└───────────────────────────────────────────────────┘
Key Features:
- Card-based list with station name, address, status badge
- Filter tabs: All / Active / Deleted
- Multi-select mode via long-press or "Select" button
- Sticky bottom action bar when items selected
- Quick action buttons: Edit, Delete, Restore, Details
- Details bottom sheet: Full metadata and actions
- Audit log bottom sheet: "Recent Changes" showing station events
- Touch targets ≥44px
Implementation Steps:
- Build card component for stations
- Add filter tab navigation
- Implement multi-select mode
- Wire sticky action bar
- Create details bottom sheet
- Add edit form modal
- Integrate audit log
Testing Checklist (Phase 2)
- Search filters stations correctly
- Filter chips work (Active, Deleted, Archived)
- Multi-select and bulk delete work
- Soft delete works (status changes to "Deleted")
- Restore works (status back to "Active")
- Hard delete confirms and removes permanently
- Detail drawer shows correct metadata
- Create/Edit forms validate
- Audit log filters to station events
- Toast shows for hard deletes
- Mobile filter tabs work
- Mobile quick actions respond correctly
- Desktop and mobile tested at stated breakpoints
Phase 3 - Admin Users (Priority 3)
Owner: Any AI Agent Dependency: Phase 1A components + Phase 2 complete Status: Ready to Start After Phase 2
Overview
Implement admin user management with invite, revoke, reinstate, and deletion with "last admin" protection.
Desktop UI - AdminUsersPage.tsx
File: frontend/src/pages/admin/AdminUsersPage.tsx (currently placeholder)
Layout:
┌─ Header: "Admin Users" + Stats ────────────────────────────────┐
│ Total: 5 | Active: 4 | Revoked: 1 │
├─ Search & Actions ────────────────────────────────────────────┤
│ [Search by email...] [Invite Admin] [Revoke] [Reinstate] [Del] │
├─ DataGrid ────────────────────────────────────────────────────┤
│ □ Email │ Role │ Status │ Created │ Last │
│ ─────────────────────────────────────────────────────────────── │
│ □ alice@example.com │ super_admin│ Active │ 2024-01-01│ now │
│ □ bob@example.com │ admin │ Active │ 2024-02-15│ 1h │
│ □ charlie@example.com│ admin │ Revoked │ 2024-03-01│ 5d │
│ □ diana@example.com │ admin │ Active │ 2025-01-01│ now │
│ │
├─ Audit Log Panel (Collapsible) ───────────────────────────────┤
│ Admin Actions (Last 50) [collapse] │
│ ────────────────────────────────────────────────────────────── │
│ 2025-11-06 14:32 | alice | Revoked admin | charlie@example.com │
│ 2025-11-06 14:15 | alice | Invited admin | diana@example.com │
└──────────────────────────────────────────────────────────────────┘
Key Features:
- Search bar: Filter by email
- Toolbar actions: Invite Admin, Revoke (bulk), Reinstate (bulk), Delete (bulk)
- Multi-select with confirmation dialogs
- Status column: Active or Revoked
- Role column: admin or super_admin
- Last Activity column: Relative time (e.g., "5m ago", "2d ago")
- Last Admin Protection: Show error if trying to revoke/delete the last active admin
- Bulk action confirmation: "Revoke N admins? They will lose access immediately."
- Audit log sidebar: Filtered to admin actions
Forms & Dialogs:
- Invite Admin modal:
- Email field (required, validated)
- Role dropdown (admin / super_admin)
- Submit button with loading state
- Revoke Confirmation dialog: Show who will be revoked, warn if last admin
- Reinstate Confirmation dialog: Confirm reinstate
- Delete Confirmation dialog: "Delete N admins? This cannot be undone."
API Hooks:
useAdmins() / useCreateAdmin() / useRevokeAdmin() / useReinstateAdmin()
Last Admin Logic:
- Before revoke/delete, check if admin is the last active one
- If revoke: Show error "Cannot revoke the last active admin"
- UI should disable action or show clear message
Implementation Steps:
- Build data grid with email search
- Implement invite form modal
- Add revoke/reinstate/delete confirmation dialogs
- Wire last admin protection
- Add bulk action handling
- Integrate audit log panel
- Test all flows including last admin edge case
Mobile UI - AdminUsersMobileScreen.tsx
File: frontend/src/features/admin/mobile/AdminUsersMobileScreen.tsx (currently placeholder)
Layout:
┌─ Header: "Admins" + Filter ────────────────────────┐
│ Filter: [All] [Active] [Revoked] [Invite + ] │
├────────────────────────────────────────────────────┤
│ │
│ ┌─ Card ────────────────────────────────────────┐ │
│ │ alice@example.com [super_admin] │
│ │ Status: Active | Created: 2024-01-01 │
│ │ Last Activity: Now │
│ │ [Revoke] [Delete] [Details →] │
│ └────────────────────────────────────────────────┘ │
│ │
│ ┌─ Card ────────────────────────────────────────┐ │
│ │ bob@example.com [admin] │
│ │ Status: Active | Created: 2024-02-15 │
│ │ Last Activity: 1h ago │
│ │ [Revoke] [Delete] [Details →] │
│ └────────────────────────────────────────────────┘ │
│ │
├─ Sticky Bottom Action Bar (Multi-Select Mode) ───┤
│ Selected: 2 [Revoke] [Delete] [Cancel] │
└────────────────────────────────────────────────────┘
Key Features:
- Card-based list: Email, role badge, status, created date, last activity
- Filter tabs: All / Active / Revoked
- "Invite +" button in header for quick access
- Multi-select via long-press or "Select" button
- Sticky bottom action bar: Revoke, Delete, Cancel
- Quick action buttons: Revoke, Delete, Details
- Details bottom sheet: Full metadata, role, created by, last activity
- Invite modal: Email + role selection
- Confirmation for revoke/delete
- Last admin protection warning
- Touch targets ≥44px
Implementation Steps:
- Build card component for admins
- Add filter tab navigation
- Wire invite form modal
- Implement multi-select mode
- Add sticky action bar with revoke/delete
- Create details bottom sheet
- Add confirmation dialogs with last admin check
- Integrate audit log
Testing Checklist (Phase 3)
- Search filters admins by email
- Invite form validates email and role
- Invite submits successfully
- Revoke shows confirmation and updates status
- Reinstate shows confirmation and updates status
- Delete confirms and removes admin
- Last admin protection prevents revoke/delete (shows error)
- Bulk operations work with confirmation
- Audit log filters to admin actions
- Mobile filter tabs and quick actions work
- Details bottom sheet shows full info
- Desktop and mobile tested at stated breakpoints
- Role badges display correctly (admin vs super_admin)
Phase 4 - Audit Log Streaming (Priority 4)
Owner: Any AI Agent Dependency: Phases 1-3 complete Status: Ready to Start After Phase 3
Overview
Implement real-time (polling-based) audit log display across all admin pages with filtering and search.
Implementation Strategy
Polling Approach (Until SSE Backend):
- Use
GET /api/admin/audit-logs?limit=50&offset=0&sort=createdAt:desc - Poll every 5 seconds
- Cache results in React Query
- Show "Last updated: X seconds ago"
Hook Implementation
File: frontend/src/features/admin/hooks/useAuditLogStream.ts
interface AuditLogStreamOptions {
resourceType?: 'admin' | 'catalog' | 'station';
actionType?: 'CREATE' | 'UPDATE' | 'DELETE' | 'REVOKE' | 'REINSTATE';
limit?: number;
pollIntervalMs?: number;
}
export function useAuditLogStream(options: AuditLogStreamOptions = {}) {
// Returns: {
// logs: AdminAuditLog[],
// loading: boolean,
// error: any,
// pagination: { offset: number, limit: number, total: number },
// hasMore: boolean,
// nextPage(): void,
// prevPage(): void,
// refetch(): void,
// lastUpdated: Date,
// }
}
Component Integration
Update AuditLogPanel.tsx and AuditLogDrawer.tsx to use the hook:
// Desktop
<AuditLogPanel
resourceType="catalog"
onRefresh={() => refetch()}
logs={logs}
loading={loading}
pagination={pagination}
onNextPage={nextPage}
/>
// Mobile
<AuditLogDrawer
open={open}
onClose={onClose}
resourceType="station"
logs={logs}
loading={loading}
/>
Audit Log Table Component
File: frontend/src/features/admin/components/AuditLogTable.tsx
interface AuditLogTableProps {
logs: AdminAuditLog[];
loading?: boolean;
pagination?: PaginationState;
onNextPage?: () => void;
onPrevPage?: () => void;
filters?: {
actor?: string;
action?: string;
resource?: string;
};
onFilterChange?: (filters: any) => void;
}
// Display columns: Timestamp | Actor | Action | Resource | Details
// Sortable by timestamp
// Paginated with prev/next buttons
// Show "Last updated: 5s ago"
Filter Implementation
Add filter controls to audit log UI:
- Actor (actor admin email)
- Action (CREATE, DELETE, REVOKE, etc.)
- Resource Type (admin, make, station, etc.)
- Date range picker (optional, start with basic)
Integration Across Pages
Update Phase 1-3 pages to wire audit logs:
- AdminCatalogPage: Filter
resourceType: 'catalog' - AdminStationsPage: Filter
resourceType: 'station' - AdminUsersPage: Filter
resourceType: 'admin'
Testing Checklist (Phase 4)
- useAuditLogStream polls correctly every 5s
- Logs display in correct time order (newest first)
- Filters work (resourceType, actionType, actor)
- Pagination controls work (next/prev)
- "Last updated" timestamp shows current time
- Loading state shows skeleton
- Error state shows with retry button
- Mobile drawer opens/closes
- Desktop panel collapses/expands
- Real-time updates visible when actions performed on other tabs
- Audit log accessible only to admins (no leakage)
Phase 5 - Mobile Routing Integration
Owner: Any AI Agent Dependency: Phases 1-4 complete Status: Ready to Start After Phase 4
Overview
Wire mobile admin screens into the mobile navigation system.
Current Mobile Navigation:
- Bottom navigation bar with fixed items: Dashboard, Vehicles, Log Fuel, Stations, Documents, Settings
- Admin screens currently exist but are not routable
Changes Required:
- Create Route Wrappers (Optional but Recommended)
File: frontend/src/features/admin/routes/AdminCatalogRoute.tsx
export function AdminCatalogRoute() {
const { isMobile } = useMediaQuery();
return isMobile ? <AdminCatalogMobileScreen /> : <AdminCatalogPage />;
}
Repeat for AdminStationsRoute and AdminUsersRoute.
- Update Mobile Navigation
File: frontend/src/components/MobileBottomNav.tsx (or similar)
Add admin access check and menu expansion:
const { isAdmin } = useAdminAccess();
// In bottom nav items:
{
isAdmin && (
<NavItem label="Admin" icon={<AdminIcon />}>
<NestedMenu>
<NavLink to="/admin/catalog">Catalog</NavLink>
<NavLink to="/admin/stations">Stations</NavLink>
<NavLink to="/admin/users">Users</NavLink>
</NestedMenu>
</NavItem>
)
}
Alternative (simpler): Add "Admin" to Settings submenu.
- Update Mobile Routes
File: frontend/src/routes.tsx or router configuration
{
path: '/admin/catalog',
element: <AdminCatalogRoute />,
private: true,
requiresAdmin: true,
},
{
path: '/admin/stations',
element: <AdminStationsRoute />,
private: true,
requiresAdmin: true,
},
{
path: '/admin/users',
element: <AdminUsersRoute />,
private: true,
requiresAdmin: true,
},
- Verify Route Protection
- All admin routes should check
useAdminAccess()and redirect to/garage/settingsif not admin - Routes should not render until auth check completes
Implementation Steps:
- Create route wrapper components
- Update mobile bottom navigation
- Add routes to router configuration
- Test navigation on mobile viewport
- Verify auth protection
Testing Checklist (Phase 5)
- Admin menu items visible to admins only
- Route navigation works on mobile
- Back button returns to previous screen
- Auth redirects non-admins correctly
- All 3 admin screens accessible from mobile nav
- Returning to mobile nav from admin screen works
- Viewport ≤480px tested
Phase 6 - Testing & QA
Owner: Any AI Agent (or dedicated QA) Dependency: All phases 1-5 complete Status: Ready to Start After Phase 5
Unit Tests
File: frontend/src/features/admin/__tests__/
Test new hooks:
// useBulkSelection.test.ts
- Selecting/deselecting items
- Select all / clear all
- Toggle item
- Reset state
// useAuditLogStream.test.ts
- Polling interval (mock timer)
- Parsing paginated response
- Filter by resourceType
- Pagination state updates
- Error handling and retry
Test Setup:
- Mock API calls using MSW (Mock Service Worker)
- Mock React Query cache
- Use
renderHookfrom testing library - Mock EventSource for SSE upgrade testing
Component Tests
File: frontend/src/features/admin/__tests__/
Desktop components:
// AdminDataGrid.test.tsx
- Render rows and columns
- Select/deselect rows
- Select all checkbox
- Toolbar slot renders
- Sorting works
- Pagination controls
// BulkActionDialog.test.tsx
- Confirm button triggers callback
- Cancel closes dialog
- Loading state disables buttons
- Message displays correctly
// AdminCatalogPage.test.tsx
- Tree navigation expands/collapses
- Grid updates when level selected
- Create form validates
- Delete shows confirmation
- Bulk delete loops through items
Mobile components:
// AdminCatalogMobileScreen.test.tsx
- Card list renders
- Drill-down navigation
- Multi-select mode toggle
- Bottom sheet opens/closes
- Breadcrumb updates
// AuditLogDrawer.test.tsx
- Opens/closes
- Displays logs
- Pagination works
Test Coverage Goal: >80% for admin feature
Integration Tests
File: frontend/src/features/admin/__tests__/integration/
End-to-end flows:
describe('Admin Catalog E2E', () => {
test('Select multiple makes, confirm delete, verify API called', async () => {
// 1. Render AdminCatalogPage
// 2. Select 3 makes via checkboxes
// 3. Click Bulk Delete button
// 4. Confirm in dialog
// 5. Verify deleteM ake() called 3 times
// 6. Verify query invalidated
// 7. Verify grid updated
});
test('Cascade warning shows when deleting make with models', async () => {
// 1. Select make that has 5 models
// 2. Click delete
// 3. Confirm dialog shows "This will delete 5 models"
});
});
describe('Admin Users E2E', () => {
test('Last admin protection prevents revoke', async () => {
// 1. Try to revoke last active admin
// 2. Show error message
// 3. Button disabled or error shown
});
test('Bulk revoke multiple admins', async () => {
// 1. Select 3 non-last admins
// 2. Click Bulk Revoke
// 3. Confirm dialog
// 4. Verify all revoked
});
});
describe('Admin Stations E2E', () => {
test('Soft delete then restore station', async () => {
// 1. Delete station
// 2. Verify status is "Deleted"
// 3. Click restore
// 4. Verify status is "Active"
});
test('Hard delete shows warning', async () => {
// 1. Click hard delete
// 2. Confirm shows warning
// 3. After delete, toast shows critical warning
});
});
Manual QA Matrix
Test Environments:
- Desktop: Chrome, Firefox, Safari at ≥1280px
- Mobile: iOS Safari, Android Chrome at ≤480px
- Tablet: iPad at 768px (optional)
Critical Flows to Test:
| Flow | Desktop | Mobile | Notes |
|---|---|---|---|
| Invite admin, verify in list | ✓ | ✓ | Check email sends (stub for now) |
| Revoke admin, verify status | ✓ | ✓ | Check audit log shows action |
| Bulk revoke 3 admins | ✓ | ✓ | Confirm all revoked |
| Last admin protection | ✓ | ✓ | Try to revoke/delete last admin |
| Create catalog make | ✓ | ✓ | Check hierarchy updates |
| Bulk delete 5 makes | ✓ | ✓ | Check models/years cascade invalidated |
| Cascade warning on delete | ✓ | ✓ | Delete make with 10 models, show warning |
| Delete station (soft) | ✓ | ✓ | Check status changes to "Deleted" |
| Restore station | ✓ | ✓ | Check status back to "Active" |
| Hard delete station | ✓ | ✓ | Check permanent removal, toast warning |
| Audit log filters | ✓ | ✓ | Filter by resource type, action |
| Audit log pagination | ✓ | ✓ | Page through logs |
| Mobile navigation | - | ✓ | Admin menu access from settings |
| Search/filter on desktop | ✓ | - | Catalog search, station search |
Accessibility Checklist:
- Touch targets ≥44px on mobile
- Keyboard navigation on desktop (Tab, Enter, Esc)
- Focus indicators visible
- Color contrast WCAG AA compliant
- Screen reader compatible (alt text, labels, ARIA)
- No console errors or warnings
Browser Compatibility
- Chrome 90+
- Firefox 88+
- Safari 14+
- Mobile Safari (iOS 14+)
- Android Chrome 90+
Parallel Execution Strategy
This document enables parallel development by multiple AI agents. Here's how:
Team 1: Shared Components (Phase 1A)
- Build all reusable components
- Types and hooks
- Expected output: Reviewed and merged shared component library
Team 2: Vehicle Catalog (Phase 1)
- Desktop and mobile implementation
- Depends on Phase 1A
- Parallel with Team 3 after Phase 1A complete
Team 3: Station Oversight (Phase 2)
- Desktop and mobile implementation
- Depends on Phase 1A
- Can start while Team 2 finishes Phase 1
Team 4: Admin Users (Phase 3)
- Desktop and mobile implementation
- Depends on Phases 1-2
- Starts when Teams 2-3 complete
Team 5: Audit Logs & Mobile Routing (Phases 4-5)
- Integrates polling + streaming
- Mobile routing setup
- Depends on Phases 1-3
- Can start when Teams 2-4 in progress
Team 6: Testing & QA (Phase 6)
- Unit and integration tests
- Manual QA matrix
- Runs in parallel with Teams 2-5 after their components merge
- Creates final sign-off
Success Criteria
All phases complete when:
- ✅ All linters pass (ESLint, TypeScript) - Zero issues
- ✅ All tests pass (>80% coverage for admin feature)
- ✅ Feature works end-to-end on desktop (≥1280px) and mobile (≤480px)
- ✅ Old placeholder code deleted
- ✅ No console errors or warnings
- ✅ Manual QA matrix completed
- ✅ Code reviewed and merged to main
Known Risks & Mitigations
| Risk | Mitigation |
|---|---|
| Backend batch endpoints delayed | Use client-side loops (already implemented pattern); swap endpoints when ready |
| SSE not implemented | Poll audit logs every 5s; upgrade when backend ready; feature flag if needed |
| Mobile routing not clear | Implement route wrappers with useMediaQuery for viewport detection |
| Query invalidation cache issues | Use structured queryKeys; test cascade deletes thoroughly |
| Performance with large lists | Implement virtualization if >500 items; use pagination |
| Last admin constraint edge case | Disable action and show clear error message; test thoroughly |
| Touch targets <44px on mobile | Verify all buttons/interactable elements in QA phase |
Questions / Blockers
If any AI agent encounters:
- Missing API endpoint → Document in Phase 0 summary; use interim workaround
- Component design unclear → Reference existing designs in codebase
- Type mismatch → Check
admin.types.tsfor canonical types - Routing issues → Check
src/routes.tsxfor routing pattern
Appendix: File Checklist
New Files to Create
frontend/src/features/admin/components/
├── AdminSectionHeader.tsx
├── AdminDataGrid.tsx
├── SelectionToolbar.tsx
├── BulkActionDialog.tsx
├── AuditLogPanel.tsx (Desktop)
├── AuditLogDrawer.tsx (Mobile)
├── AuditLogTable.tsx
├── EmptyState.tsx
├── ErrorState.tsx
└── AdminSkeleton.tsx
frontend/src/features/admin/hooks/
├── useBulkSelection.ts (NEW)
└── useAuditLogStream.ts (NEW - extends existing hooks)
frontend/src/features/admin/routes/ (Optional, Phase 5)
├── AdminCatalogRoute.tsx
├── AdminStationsRoute.tsx
└── AdminUsersRoute.tsx
frontend/src/features/admin/__tests__/
├── useBulkSelection.test.ts
├── useAuditLogStream.test.ts
├── AdminDataGrid.test.tsx
├── BulkActionDialog.test.tsx
├── AdminCatalogPage.test.tsx
├── AdminCatalogMobileScreen.test.tsx
├── AdminStationsPage.test.tsx
├── AdminStationsMobileScreen.test.tsx
├── AdminUsersPage.test.tsx
├── AdminUsersMobileScreen.test.tsx
└── integration/
├── catalog.e2e.test.ts
├── stations.e2e.test.ts
└── users.e2e.test.ts
Files to Update
frontend/src/pages/admin/
├── AdminCatalogPage.tsx (Replace placeholder)
├── AdminStationsPage.tsx (Replace placeholder)
└── AdminUsersPage.tsx (Replace placeholder)
frontend/src/features/admin/mobile/
├── AdminCatalogMobileScreen.tsx (Replace placeholder)
├── AdminStationsMobileScreen.tsx (Replace placeholder)
└── AdminUsersMobileScreen.tsx (Replace placeholder)
frontend/src/routes.tsx (Add mobile admin routes, Phase 5)
frontend/src/components/MobileBottomNav.tsx (Add admin menu, Phase 5)
Existing Files (No Changes)
frontend/src/features/admin/api/admin.api.ts (Ready to use)
frontend/src/features/admin/hooks/useAdmins.ts (Ready to use)
frontend/src/features/admin/hooks/useCatalog.ts (Ready to use)
frontend/src/features/admin/hooks/useStationOverview.ts (Ready to use)
frontend/src/features/admin/types/admin.types.ts (Ready to use)
frontend/src/core/auth/useAdminAccess.ts (Ready to use)
Document History
| Date | Author | Changes |
|---|---|---|
| 2025-11-06 | AI Planning | Initial creation for parallel execution |
Next Step: Assign agents to Phase 1A (Shared Components) to unblock all subsequent phases.