diff --git a/frontend/src/components/UserManagement.js b/frontend/src/components/UserManagement.js index 95680d5..eecb98b 100644 --- a/frontend/src/components/UserManagement.js +++ b/frontend/src/components/UserManagement.js @@ -9,7 +9,8 @@ // - JSX that uses the imported lucide-react icons (X, Plus, Edit2, Trash2, etc.) // - The ConfirmModal integration for delete/group-change confirmations import React, { useState, useEffect } from 'react'; -import { X, Plus, Edit2, Trash2, Loader, AlertCircle, CheckCircle, User, Mail, Shield, Eye } from 'lucide-react'; +// ⚠️ CONVENTION: 'Search' icon is used below but missing from this import — add it to avoid a ReferenceError +import { X, Plus, Edit2, Trash2, Loader, AlertCircle, CheckCircle, User, Mail, Shield, Eye, Search } from 'lucide-react'; import { useAuth } from '../contexts/AuthContext'; import ConfirmModal from './ConfirmModal'; @@ -188,6 +189,10 @@ export default function UserManagement({ onClose }) { const [formError, setFormError] = useState(''); const [formSuccess, setFormSuccess] = useState(''); const [pendingConfirm, setPendingConfirm] = useState(null); + const [searchTerm, setSearchTerm] = useState(''); + const [groupFilter, setGroupFilter] = useState(''); + const [sortField, setSortField] = useState('username'); + const [sortDir, setSortDir] = useState('asc'); useEffect(() => { fetchUsers(); @@ -208,6 +213,45 @@ export default function UserManagement({ onClose }) { } }; + // Filtered and sorted user list + const filteredUsers = users + .filter(u => { + // Search filter + 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; + } + // Group filter + 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 'group': aVal = a.group || ''; bVal = b.group || ''; break; + case 'teams': aVal = (a.teams || []).join(','); bVal = (b.teams || []).join(','); break; + case 'status': 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 doSubmit = async () => { setFormError(''); setFormSuccess(''); @@ -581,6 +625,51 @@ export default function UserManagement({ onClose }) { )} + {/* Search, Filters & Sort Controls */} +
| User | -Group | -Teams | -Status | -Last Login | +handleSort('username')}>User {sortField === 'username' ? (sortDir === 'asc' ? '▲' : '▼') : ''} | +handleSort('group')}>Group {sortField === 'group' ? (sortDir === 'asc' ? '▲' : '▼') : ''} | +handleSort('teams')}>Teams {sortField === 'teams' ? (sortDir === 'asc' ? '▲' : '▼') : ''} | +handleSort('status')}>Status {sortField === 'status' ? (sortDir === 'asc' ? '▲' : '▼') : ''} | +handleSort('last_login')}>Last Login {sortField === 'last_login' ? (sortDir === 'asc' ? '▲' : '▼') : ''} | Actions |
|---|---|---|---|---|---|---|---|---|---|---|