Files
webappAIO/frontend/src/features/users/UsersListPage.mobile.tsx
T
2026-08-04 11:26:25 +03:00

119 lines
4.2 KiB
TypeScript

import { useState } from 'react'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { useConfirm } from '../../components/shared/ConfirmDialog'
import { useToast } from '../../components/shared/Toast/ToastProvider'
import { usePermission } from '../../hooks/usePermission'
import { fetchUsers, deleteUser } from './usersApi'
import { useUsersRealtime } from './useUsersRealtime'
import { UserFormModal } from './UserFormModal'
import { AssignRoleModal } from './AssignRoleModal'
import type { User } from '../../types/user'
export function UsersListPageMobile() {
const { can } = usePermission()
const confirm = useConfirm()
const toast = useToast()
const queryClient = useQueryClient()
useUsersRealtime()
const [page, setPage] = useState(1)
const [formUser, setFormUser] = useState<User | null | undefined>(undefined)
const [roleUser, setRoleUser] = useState<User | null>(null)
const { data, isLoading } = useQuery({
queryKey: ['users', { page, sort: 'created_at', direction: 'desc' as const }],
queryFn: () => fetchUsers({ page, sort: 'created_at', direction: 'desc' }),
})
async function handleDelete(user: User) {
const ok = await confirm({
title: 'Delete user',
message: `Delete ${user.name}? This cannot be undone.`,
confirmLabel: 'Delete',
danger: true,
})
if (!ok) return
await deleteUser(user.id)
queryClient.invalidateQueries({ queryKey: ['users'] })
toast.success(`${user.name} was deleted.`)
}
return (
<div>
<div className="mb-4 flex items-center justify-between">
<h1 className="text-lg font-semibold text-gray-900 dark:text-gray-100">Users</h1>
{can('users.create') && (
<button
onClick={() => setFormUser(null)}
className="rounded-md bg-blue-600 px-3 py-2 text-sm font-medium text-white hover:bg-blue-700"
>
Add
</button>
)}
</div>
{isLoading ? (
<p className="text-sm text-gray-500">Loading…</p>
) : (
<div className="flex flex-col gap-3">
{(data?.data ?? []).map((u) => (
<div
key={u.id}
className="rounded-lg border border-gray-200 bg-white p-4 dark:border-gray-800 dark:bg-gray-900"
>
<p className="font-medium text-gray-900 dark:text-gray-100">{u.name}</p>
<p className="text-sm text-gray-500 dark:text-gray-500">{u.email}</p>
<p className="mt-1 text-xs text-gray-400 dark:text-gray-600">{u.roles.join(', ')}</p>
<div className="mt-3 flex gap-4 text-sm">
{u.can_edit && (
<button onClick={() => setFormUser(u)} className="text-blue-600 dark:text-blue-400">
Edit
</button>
)}
{can('roles.assign') && (
<button onClick={() => setRoleUser(u)} className="text-blue-600 dark:text-blue-400">
Role
</button>
)}
{u.can_delete && (
<button onClick={() => handleDelete(u)} className="text-red-600 dark:text-red-400">
Delete
</button>
)}
</div>
</div>
))}
</div>
)}
{data && data.meta.last_page > 1 && (
<div className="mt-4 flex items-center justify-between text-sm">
<button
disabled={page <= 1}
onClick={() => setPage((p) => p - 1)}
className="text-gray-600 disabled:opacity-40 dark:text-gray-400"
>
Previous
</button>
<span className="text-gray-500">
Page {page} of {data.meta.last_page}
</span>
<button
disabled={page >= data.meta.last_page}
onClick={() => setPage((p) => p + 1)}
className="text-gray-600 disabled:opacity-40 dark:text-gray-400"
>
Next
</button>
</div>
)}
{formUser !== undefined && (
<UserFormModal open onClose={() => setFormUser(undefined)} user={formUser ?? undefined} />
)}
{roleUser && <AssignRoleModal open onClose={() => setRoleUser(null)} user={roleUser} />}
</div>
)
}