import { useState } from 'react' import { Button, Form, Input, Modal, Select, Space, Switch, Tag, Tooltip, Typography, message, } from 'antd' import type { ColumnsType } from 'antd/es/table' import { KeyOutlined, PlusOutlined, TeamOutlined } from '@ant-design/icons' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { useTranslation } from 'react-i18next' import apiClient, { isEnvelope } from '../../api/client' import { useAuthStore } from '../../stores/auth' import PageHeader from '../../components/PageHeader' import DataTable from '../../components/DataTable' import EmptyState from '../../components/EmptyState' import ActionButtons from '../../components/ActionButtons' import StatusDot from '../../components/StatusDot' const { Text } = Typography interface User { id: number email: string role: string active: boolean last_login_at: string | null created_at: string } interface CreateValues { email: string; password: string; role: string; active: boolean } interface EditValues { email: string; role: string; active: boolean } interface PwValues { password: string } async function listUsers(): Promise { const r = await apiClient.get('/users') if (!isEnvelope(r.data)) return [] return (r.data.data as { users?: User[] }).users ?? [] } const ROLE_COLORS: Record = { admin: 'blue', viewer: 'default' } export default function UsersPage() { const { t } = useTranslation() const qc = useQueryClient() const me = useAuthStore((s) => s.user) const { data: users, isLoading } = useQuery({ queryKey: ['users'], queryFn: listUsers }) const [creating, setCreating] = useState(false) const [editing, setEditing] = useState(null) const [pwTarget, setPwTarget] = useState(null) const [createForm] = Form.useForm() const [editForm] = Form.useForm() const [pwForm] = Form.useForm() const invalidate = () => void qc.invalidateQueries({ queryKey: ['users'] }) const createMut = useMutation({ mutationFn: (v: CreateValues) => apiClient.post('/users', v), onSuccess: () => { message.success(t('common.save')); setCreating(false); createForm.resetFields(); invalidate() }, onError: (e: Error) => message.error(e.message), }) const updateMut = useMutation({ mutationFn: ({ id, v }: { id: number; v: EditValues }) => apiClient.put(`/users/${id}`, v), onSuccess: () => { message.success(t('common.save')); setEditing(null); editForm.resetFields(); invalidate() }, onError: (e: Error) => message.error(e.message), }) const pwMut = useMutation({ mutationFn: ({ id, v }: { id: number; v: PwValues }) => apiClient.post(`/users/${id}/password`, v), onSuccess: () => { message.success(t('common.save')); setPwTarget(null); pwForm.resetFields() }, onError: (e: Error) => message.error(e.message), }) const delMut = useMutation({ mutationFn: (id: number) => apiClient.delete(`/users/${id}`), onSuccess: invalidate, onError: (e: Error) => message.error(e.message), }) const roleOptions = [ { value: 'admin', label: t('users.roleAdmin') }, { value: 'viewer', label: t('users.roleViewer') }, ] const columns: ColumnsType = [ { title: t('users.email'), dataIndex: 'email', key: 'email', render: (s: string) => ( {s} {me?.actor === s && You} ), }, { title: t('users.role'), dataIndex: 'role', key: 'role', render: (r: string) => ( {r === 'admin' ? t('users.roleAdmin') : t('users.roleViewer')} ), }, { title: t('users.active'), dataIndex: 'active', key: 'active', width: 70, render: (v: boolean) => , }, { title: t('users.lastLogin'), key: 'lastLogin', width: 160, render: (_, row) => row.last_login_at ? {new Date(row.last_login_at).toLocaleDateString()} : {t('users.never')}, }, { title: t('common.actions'), key: 'actions', width: 120, render: (_, row) => ( } emptyContent={ } title={t('users.emptyTitle')} description={t('users.emptyDesc')} action={ } /> } /> {/* Create modal */} { setCreating(false); createForm.resetFields() }} onOk={() => void createForm.submit()} confirmLoading={createMut.isPending} destroyOnHidden >
createMut.mutate(v)}>