import React, { useEffect, useState, useCallback } from 'react'; import { Box, Typography, Button, CircularProgress, Alert, Chip, TextField, Select, MenuItem, FormControl, InputLabel, Table, TableHead, TableBody, TableRow, TableCell, IconButton, Tooltip, Divider, } from '@mui/material'; import { PersonAdd, Cancel, Send, Refresh, } from '@mui/icons-material'; import { useAuth } from '../../contexts/AuthContext'; const API_BASE = import.meta.env.VITE_API_BASE || import.meta.env.VITE_API_URL || '/api'; // ─── Types ──────────────────────────────────────────────────────────────────── interface StaffMember { profile_id: string; email: string | null; username: string | null; display_name: string | null; role: string; joined_at: string; } interface Invitation { id: string; email: string; role: string; status: string; created_at: string; expires_at: string; } const ROLE_COLORS: Record = { school_admin: 'primary', department_head: 'warning', teacher: 'success', }; const STATUS_COLORS: Record = { pending: 'warning', accepted: 'success', expired: 'error', cancelled: 'default', }; // ─── Component ─────────────────────────────────────────────────────────────── const StaffManagerPage: React.FC = () => { const { accessToken } = useAuth(); const [staff, setStaff] = useState([]); const [invitations, setInvitations] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [successMsg, setSuccessMsg] = useState(null); // Invite form const [inviteEmail, setInviteEmail] = useState(''); const [inviteRole, setInviteRole] = useState('teacher'); const [inviting, setInviting] = useState(false); const headers = { Authorization: `Bearer ${accessToken}` }; const loadData = useCallback(async () => { if (!accessToken) return; setLoading(true); setError(null); try { const [staffRes, invRes] = await Promise.all([ fetch(`${API_BASE}/users/staff`, { headers }), fetch(`${API_BASE}/users/invitations?role=teacher&status=pending`, { headers }) .then(r => r.json()) .catch(() => ({ invitations: [] })), ].map((p, i) => i === 0 ? (p as Promise).then(r => r.json()) : p)); if (staffRes.status === 'ok') setStaff(staffRes.staff || []); else setError(staffRes.detail || 'Failed to load staff'); setInvitations(invRes.invitations || []); } catch (e: any) { setError(e.message); } finally { setLoading(false); } }, [accessToken]); useEffect(() => { loadData(); }, [loadData]); const handleInvite = async () => { if (!inviteEmail.trim() || !accessToken) return; setInviting(true); setError(null); setSuccessMsg(null); try { const res = await fetch(`${API_BASE}/users/invite`, { method: 'POST', headers: { ...headers, 'Content-Type': 'application/json' }, body: JSON.stringify({ email: inviteEmail.trim().toLowerCase(), role: inviteRole }), }); const data = await res.json(); if (data.status === 'ok' || data.status === 'already_pending') { setSuccessMsg( data.status === 'already_pending' ? `Invitation already pending for ${inviteEmail}` : `Invitation sent to ${inviteEmail}` ); setInviteEmail(''); loadData(); } else { setError(data.detail || data.message || 'Invite failed'); } } catch (e: any) { setError(e.message); } finally { setInviting(false); } }; const handleCancel = async (id: string, email: string) => { if (!accessToken) return; try { await fetch(`${API_BASE}/users/invitations/${id}`, { method: 'DELETE', headers }); setSuccessMsg(`Cancelled invitation for ${email}`); loadData(); } catch (e: any) { setError(e.message); } }; const handleResend = async (id: string, email: string) => { if (!accessToken) return; try { const res = await fetch(`${API_BASE}/users/invitations/${id}/resend`, { method: 'POST', headers }); const data = await res.json(); if (data.status === 'ok') { setSuccessMsg(`Re-sent invitation to ${email}`); } else { setError(data.detail || 'Resend failed'); } } catch (e: any) { setError(e.message); } }; return ( Staff Manager Invite teachers, department heads, and school admins {error && setError(null)}>{error}} {successMsg && setSuccessMsg(null)}>{successMsg}} {/* Invite form */} setInviteEmail(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleInvite()} size="small" sx={{ flex: 1 }} placeholder="teacher@school.edu" disabled={inviting} /> Role {/* Pending invitations */} {invitations.length > 0 && ( Pending Invitations ({invitations.length}) Email Role Status Sent Actions {invitations.map(inv => ( {inv.email} {new Date(inv.created_at).toLocaleDateString('en-GB', { day: 'numeric', month: 'short' })} handleResend(inv.id, inv.email)}> handleCancel(inv.id, inv.email)}> ))}
)} {/* Active staff */} Active Staff ({loading ? '…' : staff.length}) {loading ? ( ) : staff.length === 0 ? ( No staff members yet. ) : ( Name Email Role Joined {staff.map(s => ( {s.display_name || s.username || '—'} {s.email || '—'} {new Date(s.joined_at).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' })} ))}
)}
); }; export default StaffManagerPage;