Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | import { useMemo } from 'react'; import { useAuth } from '@/contexts/AuthContext'; import type { AdminUserFilters } from '@/models/admin'; import { deactivateAdminUser, listAdminUsers } from '@/services/adminService'; import { useAdminListViewModel } from '@/viewmodels/admin/useAdminListViewModel'; import { useAdminMutation } from '@/viewmodels/admin/useAdminMutation'; import { BackofficeConfirmAction, BackofficeIdCell, BackofficePageShell, BackofficePagination, BackofficeStatusPill, BackofficeTableState, formatAdminDate } from './BackofficeListParts'; const INITIAL_FILTERS: AdminUserFilters = { q: '', status: '', role: undefined, created_from: '', created_to: '', }; export default function UsersAdminPage() { const { token } = useAuth(); const initialFilters = useMemo(() => INITIAL_FILTERS, []); const vm = useAdminListViewModel({ token, initialFilters, fetchPage: listAdminUsers, }); const mutation = useAdminMutation(vm.retry); return ( <BackofficePageShell title="Users" subtitle="Search accounts by identity, status, role, and creation window." filters={( <> <input aria-label="Search users" placeholder="Search username, email, phone" value={vm.filters.q ?? ''} onChange={(event) => vm.setFilter('q', event.target.value)} /> <select aria-label="User status" value={vm.filters.status ?? ''} onChange={(event) => vm.setFilter('status', event.target.value as AdminUserFilters['status'])}> <option value="">Any status</option> <option value="active">active</option> <option value="deactivated">deactivated</option> </select> <select aria-label="User role" value={vm.filters.role ?? ''} onChange={(event) => vm.setFilter('role', event.target.value === '' ? undefined : event.target.value as AdminUserFilters['role'])}> <option value="">Any role</option> <option value="USER">USER</option> <option value="ADMIN">ADMIN</option> </select> <input aria-label="Created from" type="datetime-local" value={vm.filters.created_from ?? ''} onChange={(event) => vm.setFilter('created_from', event.target.value)} /> <input aria-label="Created to" type="datetime-local" value={vm.filters.created_to ?? ''} onChange={(event) => vm.setFilter('created_to', event.target.value)} /> <button type="button" onClick={vm.applyFilters}>Apply</button> <button type="button" className="bo-secondary-button" onClick={vm.clearFilters}>Clear</button> </> )} > <BackofficeTableState isLoading={vm.isLoading} error={vm.error} empty={vm.items.length === 0} onRetry={vm.retry} /> <div className="bo-table-wrap"> <table className="bo-table"> <thead> <tr> <th>ID</th> <th>Username</th> <th>Email</th> <th>Phone</th> <th>Role</th> <th>Status</th> <th>Verified</th> <th>Created</th> <th>Updated</th> <th>Actions</th> </tr> </thead> <tbody> {vm.items.map((user) => ( <tr key={user.id}> <td><BackofficeIdCell id={user.id} /></td> <td>{user.username}</td> <td>{user.email}</td> <td>{user.phone_number ?? '-'}</td> <td>{user.role}</td> <td><BackofficeStatusPill status={user.status} /></td> <td>{user.email_verified ? 'Yes' : 'No'}</td> <td>{formatAdminDate(user.created_at)}</td> <td>{formatAdminDate(user.updated_at)}</td> <td> <BackofficeConfirmAction label="Deactivate" confirmLabel="Deactivate" disabled={user.status === 'deactivated'} busy={mutation.busyId === user.id} onConfirm={() => void mutation.run(user.id, () => deactivateAdminUser(token!, user.id), 'User deactivated.')} /> </td> </tr> ))} </tbody> </table> </div> {mutation.error && <div className="bo-state bo-state-error">{mutation.error}</div>} {mutation.message && <div className="bo-result" role="status"><span>{mutation.message}</span></div>} <BackofficePagination {...vm} onPrevious={vm.previousPage} onNext={vm.nextPage} onLimitChange={vm.changeLimit} /> </BackofficePageShell> ); } |