mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
205 lines
6.7 KiB
TypeScript
205 lines
6.7 KiB
TypeScript
import { DataTable } from '@/components/admin/DataTable.tsx';
|
|
import { Badge, Button, Input } from '@/components/ui';
|
|
import { useDeactivateUser, useUpdateUserRole, useUsers } from '@/hooks/api/useAdminAPI.ts';
|
|
import { useTranslation } from '@/hooks/useI18n.tsx';
|
|
import type { User } from '@/services/admin-api.ts';
|
|
import { Edit, Plus, UserCheck, UserX } from 'lucide-react';
|
|
import { useState } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
|
const UsersListPage = () => {
|
|
const { t } = useTranslation();
|
|
const navigate = useNavigate();
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [roleFilter, setRoleFilter] = useState<string | undefined>();
|
|
const [statusFilter, setStatusFilter] = useState<boolean | undefined>();
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const pageSize = 25;
|
|
|
|
const { data, isLoading } = useUsers({
|
|
search: searchTerm || undefined,
|
|
role: roleFilter,
|
|
isActive: statusFilter,
|
|
limit: pageSize,
|
|
offset: (currentPage - 1) * pageSize,
|
|
});
|
|
|
|
const { mutate: deactivateUser } = useDeactivateUser();
|
|
const { mutate: updateRole } = useUpdateUserRole();
|
|
|
|
const handleDeactivate = (user: User) => {
|
|
if (window.confirm(t('adminPage.users.confirmDeactivate', { name: user.name }) || `Deactivate ${user.name}?`)) {
|
|
deactivateUser(user.id, {
|
|
onSuccess: () => {
|
|
// Query will refetch automatically
|
|
},
|
|
});
|
|
}
|
|
};
|
|
|
|
const handleRoleChange = (user: User, newRole: string) => {
|
|
updateRole(
|
|
{ id: user.id, role: newRole },
|
|
{
|
|
onSuccess: () => {
|
|
// Query will refetch automatically
|
|
},
|
|
}
|
|
);
|
|
};
|
|
|
|
const formatDate = (dateString: string | null | undefined) => {
|
|
if (!dateString) return t('adminPage.users.never') || 'Never';
|
|
try {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
} catch {
|
|
return t('adminPage.users.never') || 'Never';
|
|
}
|
|
};
|
|
|
|
const columns = [
|
|
{
|
|
key: 'name',
|
|
header: t('adminPage.users.table.name') || 'Name',
|
|
render: (user: User) => <span className="font-medium">{user.name}</span>,
|
|
},
|
|
{
|
|
key: 'email',
|
|
header: t('adminPage.users.table.email') || 'Email',
|
|
render: (user: User) => <span className="text-muted-foreground">{user.email}</span>,
|
|
},
|
|
{
|
|
key: 'role',
|
|
header: t('adminPage.users.table.role') || 'Role',
|
|
render: (user: User) => (
|
|
<Badge variant={user.role === 'admin' ? 'default' : 'secondary'}>
|
|
{user.role}
|
|
</Badge>
|
|
),
|
|
},
|
|
{
|
|
key: 'status',
|
|
header: t('adminPage.users.table.status') || 'Status',
|
|
render: (user: User) => (
|
|
<Badge variant={user.isActive ? 'success' : 'destructive'}>
|
|
{user.isActive ? (t('adminPage.users.active') || 'Active') : (t('adminPage.users.inactive') || 'Inactive')}
|
|
</Badge>
|
|
),
|
|
},
|
|
{
|
|
key: 'lastLogin',
|
|
header: t('adminPage.users.table.lastLogin') || 'Last Login',
|
|
render: (user: User) => <span className="text-sm text-muted-foreground">{formatDate(user.lastLoginAt)}</span>,
|
|
},
|
|
];
|
|
|
|
const actions = [
|
|
{
|
|
label: t('adminPage.users.edit') || 'Edit',
|
|
icon: <Edit className="h-4 w-4" />,
|
|
onClick: (user: User) => navigate(`/admin/users/${user.id}/edit`),
|
|
},
|
|
{
|
|
label: t('adminPage.users.changeRole') || 'Change Role',
|
|
onClick: (user: User) => handleRoleChange(user, user.role === 'admin' ? 'user' : 'admin'),
|
|
},
|
|
{
|
|
label: t('adminPage.users.toggleStatus') || 'Toggle Status',
|
|
variant: 'destructive' as const,
|
|
onClick: (user: User) => handleDeactivate(user),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-2xl font-bold">{t('adminPage.users.title') || 'Users'}</h1>
|
|
<p className="text-muted-foreground">{t('adminPage.users.description') || 'Manage user accounts, roles, and permissions'}</p>
|
|
</div>
|
|
<Button onClick={() => navigate('/admin/users/new')}>
|
|
<Plus className="h-4 w-4 mr-2" />
|
|
{t('adminPage.users.newUser') || 'New User'}
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Filters */}
|
|
<div className="flex flex-col sm:flex-row gap-4">
|
|
<div className="flex-1">
|
|
<Input
|
|
placeholder={t('adminPage.users.searchPlaceholder') || 'Search by name or email...'}
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button
|
|
variant={roleFilter === undefined ? 'default' : 'outline'}
|
|
onClick={() => setRoleFilter(undefined)}
|
|
>
|
|
{t('adminPage.users.allRoles') || 'All Roles'}
|
|
</Button>
|
|
<Button
|
|
variant={roleFilter === 'admin' ? 'default' : 'outline'}
|
|
onClick={() => setRoleFilter('admin')}
|
|
>
|
|
{t('adminPage.users.admin') || 'Admin'}
|
|
</Button>
|
|
<Button
|
|
variant={roleFilter === 'user' ? 'default' : 'outline'}
|
|
onClick={() => setRoleFilter('user')}
|
|
>
|
|
{t('adminPage.users.user') || 'User'}
|
|
</Button>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button
|
|
variant={statusFilter === undefined ? 'default' : 'outline'}
|
|
onClick={() => setStatusFilter(undefined)}
|
|
>
|
|
{t('adminPage.users.allStatuses') || 'All'}
|
|
</Button>
|
|
<Button
|
|
variant={statusFilter === true ? 'default' : 'outline'}
|
|
onClick={() => setStatusFilter(true)}
|
|
>
|
|
<UserCheck className="h-4 w-4 mr-2" />
|
|
{t('adminPage.users.active') || 'Active'}
|
|
</Button>
|
|
<Button
|
|
variant={statusFilter === false ? 'default' : 'outline'}
|
|
onClick={() => setStatusFilter(false)}
|
|
>
|
|
<UserX className="h-4 w-4 mr-2" />
|
|
{t('adminPage.users.inactive') || 'Inactive'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Table */}
|
|
<DataTable
|
|
columns={columns}
|
|
data={data?.users || []}
|
|
getRowId={(user) => user.id}
|
|
isLoading={isLoading}
|
|
actions={actions}
|
|
pagination={
|
|
data
|
|
? {
|
|
currentPage,
|
|
totalPages: Math.ceil(data.total / pageSize),
|
|
pageSize,
|
|
totalItems: data.total,
|
|
onPageChange: setCurrentPage,
|
|
}
|
|
: undefined
|
|
}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default UsersListPage;
|