- Add UserRole type and AppUser interface to types.ts - Create UserContext with useUser() hook providing role-based permission booleans - Split App into App (auth wrapper) + AppContent (uses UserContext) - Update Sidebar to filter nav items by UserRole instead of boolean isAdmin - Add User Management nav item (super_admin only) - Add AgencyFilterBar component for oversight_admin/super_admin session-level filtering - Pass agencyId to getCampaigns, getAnalytics, audit endpoints in apiService - Add getMe, getUsers, updateUser, createAgency to apiService - Build UserManagement page with user table (role/agency dropdowns) and agency CRUD - Add readOnly prop to Campaigns (hides create/delete/status-toggle for oversight_admin) - Add readOnly prop to Settings (disables all ManagementCards, shows view-only banner) - Pass agencyId to Analytics component for filtered data - Update urlState with Knowledge Base and User Management views Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
46 lines
1.9 KiB
TypeScript
46 lines
1.9 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import apiService from '../services/apiService';
|
|
import type { AgencyResponse } from '../services/apiService';
|
|
import { ChevronDownIcon } from './icons/ChevronDownIcon';
|
|
|
|
interface AgencyFilterBarProps {
|
|
selectedAgencyId: string | null;
|
|
onAgencyChange: (agencyId: string | null) => void;
|
|
}
|
|
|
|
export const AgencyFilterBar: React.FC<AgencyFilterBarProps> = ({ selectedAgencyId, onAgencyChange }) => {
|
|
const [agencies, setAgencies] = useState<AgencyResponse[]>([]);
|
|
|
|
useEffect(() => {
|
|
const loadAgencies = async () => {
|
|
try {
|
|
const data = await apiService.getAgencies();
|
|
setAgencies(data);
|
|
} catch (err) {
|
|
console.error('Failed to load agencies for filter:', err);
|
|
}
|
|
};
|
|
loadAgencies();
|
|
}, []);
|
|
|
|
return (
|
|
<div className="bg-white border-b border-grey-300 px-6 py-3 flex items-center gap-3 flex-shrink-0">
|
|
<label className="text-sm font-medium text-primary-blue whitespace-nowrap">
|
|
Filter by Agency:
|
|
</label>
|
|
<div className="relative max-w-xs">
|
|
<select
|
|
value={selectedAgencyId || ''}
|
|
onChange={(e) => onAgencyChange(e.target.value || null)}
|
|
className="bg-white border-2 border-active-blue text-black-title py-1.5 pl-3 pr-8 rounded-[10px] text-sm focus:outline-none focus:ring-2 focus:ring-active-blue appearance-none min-w-[200px]"
|
|
>
|
|
<option value="">All Agencies</option>
|
|
{agencies.map(a => (
|
|
<option key={a.id} value={a.id}>{a.name}</option>
|
|
))}
|
|
</select>
|
|
<ChevronDownIcon className="absolute right-2 top-1/2 -translate-y-1/2 h-3 w-3 text-active-blue pointer-events-none" />
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|