import { useState, useEffect, useCallback } from 'react'; import { api } from '../utils/api.js'; import { useToast } from '../contexts/ToastContext.jsx'; import Avatar from './Avatar.jsx'; // ── Shared user checkbox list ───────────────────────────────────────────────── function UserCheckList({ allUsers, selectedIds, onChange }) { const [search, setSearch] = useState(''); const filtered = allUsers.filter(u => (u.display_name || u.name).toLowerCase().includes(search.toLowerCase()) ); return (
setSearch(e.target.value)} style={{ marginBottom: 8 }} autoComplete="new-password" />
{filtered.map(u => ( ))} {filtered.length === 0 &&
No users found
}
); } // ── User Group checkbox list ────────────────────────────────────────────────── function GroupCheckList({ allGroups, selectedIds, onChange }) { return (
{allGroups.map(g => ( ))} {allGroups.length === 0 &&
No user groups yet
}
); } // ── All Groups tab ──────────────────────────────────────────────────────────── function AllGroupsTab({ allUsers, onRefresh }) { const toast = useToast(); const [groups, setGroups] = useState([]); const [selected, setSelected] = useState(null); const [savedMembers, setSavedMembers] = useState(new Set()); // members as last saved const [members, setMembers] = useState(new Set()); // current checkbox state const [editName, setEditName] = useState(''); const [saving, setSaving] = useState(false); const [deleting, setDeleting] = useState(false); const [showDelete, setShowDelete] = useState(false); const load = useCallback(() => api.getUserGroups().then(({ groups }) => setGroups(groups)).catch(() => {}), []); useEffect(() => { load(); }, [load]); const selectGroup = async (g) => { setShowDelete(false); const { members: mems } = await api.getUserGroup(g.id); const ids = new Set(mems.map(m => m.id)); setSelected(g); setEditName(g.name); setMembers(ids); setSavedMembers(ids); }; const clearSelection = () => { setSelected(null); setEditName(''); setMembers(new Set()); setSavedMembers(new Set()); setShowDelete(false); }; const handleSave = async () => { if (!editName.trim()) return toast('Name required', 'error'); setSaving(true); try { if (selected) { await api.updateUserGroup(selected.id, { name: editName.trim(), memberIds: [...members] }); toast('Group updated', 'success'); // Refresh saved state const { members: fresh } = await api.getUserGroup(selected.id); const freshIds = new Set(fresh.map(m => m.id)); setSavedMembers(freshIds); setMembers(freshIds); setSelected(prev => ({ ...prev, name: editName.trim() })); } else { await api.createUserGroup({ name: editName.trim(), memberIds: [...members] }); toast(`Group "${editName.trim()}" created`, 'success'); clearSelection(); } load(); onRefresh(); } catch (e) { toast(e.message, 'error'); } finally { setSaving(false); } }; const handleDelete = async () => { setDeleting(true); try { await api.deleteUserGroup(selected.id); toast('Group deleted', 'success'); clearSelection(); load(); onRefresh(); } catch (e) { toast(e.message, 'error'); } finally { setDeleting(false); } }; // Delete only enabled when group selected AND no saved members remain const canDelete = selected && savedMembers.size === 0; const isCreating = !selected; return (
{/* Group list */}
User Groups
{groups.map(g => ( ))} {groups.length === 0 &&
No groups yet
}
{/* Form panel */}
setEditName(e.target.value)} placeholder="e.g. Coaches" style={{ marginTop: 6 }} autoComplete="new-password" /> {isCreating &&

A matching Direct Message group will be created automatically.

}

{members.size} selected

{!isCreating && } {!isCreating && ( )}
{showDelete && (

Delete {selected?.name}? This also deletes the associated direct message group. Cannot be undone.

)}
); } // ── Direct Messages tab ─────────────────────────────────────────────────────── function DirectMessagesTab({ allUserGroups, onRefresh, refreshKey }) { const toast = useToast(); const [dms, setDms] = useState([]); const [selected, setSelected] = useState(null); const [savedGroupIds, setSavedGroupIds] = useState(new Set()); const [groupIds, setGroupIds] = useState(new Set()); const [dmName, setDmName] = useState(''); const [saving, setSaving] = useState(false); const [deleting, setDeleting] = useState(false); const [showDelete, setShowDelete] = useState(false); const load = useCallback(() => api.getMultiGroupDms().then(({ dms }) => setDms(dms || [])).catch(e => console.error('multigroup load error:', e)), []); // Reload whenever parent refreshes (e.g. after user group changes that affect membership) useEffect(() => { load(); }, [load, refreshKey]); const clearSelection = () => { setSelected(null); setDmName(''); setGroupIds(new Set()); setSavedGroupIds(new Set()); setShowDelete(false); }; const selectDm = (dm) => { setShowDelete(false); setSelected(dm); setDmName(dm.name); const ids = new Set(dm.memberGroupIds || []); setGroupIds(ids); setSavedGroupIds(ids); }; const handleSave = async () => { if (!dmName.trim()) return toast('Name required', 'error'); if (!selected && groupIds.size < 2) return toast('Select at least two user groups', 'error'); setSaving(true); try { if (selected) { await api.updateMultiGroupDm(selected.id, { name: dmName.trim(), userGroupIds: [...groupIds] }); toast('Multi-group DM updated', 'success'); const freshDms = await api.getMultiGroupDms(); const fresh = freshDms.dms.find(d => d.id === selected.id); if (fresh) { const ids = new Set(fresh.memberGroupIds || []); setSavedGroupIds(ids); setGroupIds(ids); setSelected(fresh); } } else { await api.createMultiGroupDm({ name: dmName.trim(), userGroupIds: [...groupIds] }); toast(`Multi-group DM "${dmName.trim()}" created`, 'success'); clearSelection(); } load(); onRefresh(); } catch (e) { toast(e.message, 'error'); } finally { setSaving(false); } }; const handleDelete = async () => { setDeleting(true); try { await api.deleteMultiGroupDm(selected.id); toast('Deleted', 'success'); clearSelection(); load(); onRefresh(); } catch (e) { toast(e.message, 'error'); } finally { setDeleting(false); } }; const canDelete = selected && savedGroupIds.size === 0; const isCreating = !selected; return (
{/* DM list */}
Managed Multi Group DMs
{dms.map(dm => ( ))} {dms.length === 0 &&
No multi-group DMs yet
}
{/* Form panel */}
setDmName(e.target.value)} placeholder="e.g. Coaches + Players" style={{ marginTop: 6 }} autoComplete="new-password" />

Select two or more user groups. All members of each group will have access to this conversation.

{groupIds.size} group{groupIds.size !== 1 ? 's' : ''} selected

{!isCreating && } {!isCreating && ( )}
{showDelete && (

Delete {selected?.name}? This also deletes the associated direct message group. Cannot be undone.

)}
); } // ── Main modal ──────────────────────────────────────────────────────────────── export default function GroupManagerModal({ onClose }) { const [tab, setTab] = useState('all'); const [allUsers, setAllUsers] = useState([]); const [allUserGroups, setAllUserGroups] = useState([]); const [refreshKey, setRefreshKey] = useState(0); const onRefresh = () => setRefreshKey(k => k + 1); useEffect(() => { api.searchUsers('').then(({ users }) => setAllUsers(users.filter(u => u.status === 'active'))).catch(() => {}); api.getUserGroups().then(({ groups }) => setAllUserGroups(groups)).catch(() => {}); }, [refreshKey]); return (
e.target === e.currentTarget && onClose()}>

Group Manager

{tab === 'all' && } {tab === 'dm' && }
); }