From 6f9a5885e17f0bd48bd5e2f75bba796a6529bea5 Mon Sep 17 00:00:00 2001 From: Jordan Ramos Date: Fri, 26 Jun 2026 10:06:54 -0600 Subject: [PATCH] Add search, filter, and sort to Admin Panel user management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same UX improvements as the modal — search bar, group filter chips, sortable column headers, and filtered count. Applied to the in-page UserManagementPanel in AdminPage.js so the Admin Panel tab has parity with the modal. --- frontend/src/components/pages/AdminPage.js | 98 ++++++++++++++++++++-- 1 file changed, 91 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/pages/AdminPage.js b/frontend/src/components/pages/AdminPage.js index da85787..f79a10d 100644 --- a/frontend/src/components/pages/AdminPage.js +++ b/frontend/src/components/pages/AdminPage.js @@ -117,6 +117,10 @@ function UserManagementPanel() { const [formError, setFormError] = useState(''); const [successMessage, setSuccessMessage] = useState(''); const [pendingConfirm, setPendingConfirm] = useState(null); + const [searchTerm, setSearchTerm] = useState(''); + const [groupFilter, setGroupFilter] = useState(''); + const [sortField, setSortField] = useState('username'); + const [sortDir, setSortDir] = useState('asc'); const fetchUsers = useCallback(async () => { setLoading(true); @@ -148,6 +152,43 @@ function UserManagementPanel() { } }, [successMessage]); + // Filtered and sorted user list + const filteredUsers = users + .filter(u => { + if (searchTerm) { + const term = searchTerm.toLowerCase(); + const matchesName = u.username?.toLowerCase().includes(term); + const matchesEmail = u.email?.toLowerCase().includes(term); + const matchesTeam = (u.teams || []).some(t => t.toLowerCase().includes(term)); + if (!matchesName && !matchesEmail && !matchesTeam) return false; + } + if (groupFilter === 'no-teams') return !u.teams || u.teams.length === 0; + if (groupFilter && u.group !== groupFilter) return false; + return true; + }) + .sort((a, b) => { + let aVal, bVal; + switch (sortField) { + case 'username': aVal = a.username || ''; bVal = b.username || ''; break; + case 'email': aVal = a.email || ''; bVal = b.email || ''; break; + case 'group': aVal = a.group || ''; bVal = b.group || ''; break; + case 'active': aVal = a.is_active ? 'a' : 'z'; bVal = b.is_active ? 'a' : 'z'; break; + case 'last_login': aVal = a.last_login || ''; bVal = b.last_login || ''; break; + default: aVal = a.username || ''; bVal = b.username || ''; + } + const cmp = String(aVal).localeCompare(String(bVal)); + return sortDir === 'asc' ? cmp : -cmp; + }); + + const handleSort = (field) => { + if (sortField === field) { + setSortDir(d => d === 'asc' ? 'desc' : 'asc'); + } else { + setSortField(field); + setSortDir('asc'); + } + }; + const handleAddClick = () => { setEditingUser(null); setFormData({ username: '', email: '', password: '', group: 'Read_Only' }); @@ -433,6 +474,48 @@ function UserManagementPanel() { {/* User table */} {!loading && !error && ( +
+ {/* Search and filter controls */} +
+
+ + setSearchTerm(e.target.value)} + style={{ + width: '100%', padding: '0.5rem 0.75rem 0.5rem 2.25rem', + background: 'rgba(15, 23, 42, 0.6)', + border: '1px solid rgba(14, 165, 233, 0.2)', + borderRadius: '0.375rem', color: '#E2E8F0', + fontSize: '0.8rem', fontFamily: 'monospace', outline: 'none', + }} + /> +
+
+ Filter: + {['', 'Admin', 'Standard_User', 'Leadership', 'Read_Only', 'no-teams'].map(g => ( + + ))} + + {filteredUsers.length} of {users.length} users + +
+
+
- Username - Email - Group - Active - Last Login + handleSort('username')}>Username {sortField === 'username' ? (sortDir === 'asc' ? '▲' : '▼') : ''} + handleSort('email')}>Email {sortField === 'email' ? (sortDir === 'asc' ? '▲' : '▼') : ''} + handleSort('group')}>Group {sortField === 'group' ? (sortDir === 'asc' ? '▲' : '▼') : ''} + handleSort('active')}>Active {sortField === 'active' ? (sortDir === 'asc' ? '▲' : '▼') : ''} + handleSort('last_login')}>Last Login {sortField === 'last_login' ? (sortDir === 'asc' ? '▲' : '▼') : ''} Actions
{/* Rows */} - {users.length === 0 ? ( + {filteredUsers.length === 0 ? (
No users found
) : ( - users.map(u => { + filteredUsers.map(u => { const badge = getGroupBadgeStyle(u.group); const self = isSelfUser(currentUser?.id, u.id); return ( @@ -575,6 +658,7 @@ function UserManagementPanel() { }) )}
+ )} {/* Confirmation Modal */}