import { useState } from 'react'; import { Card, Table, Button, Space, Tag, Popconfirm, Tooltip, Typography, message } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { PlusOutlined, EditOutlined, DeleteOutlined, ReloadOutlined } from '@ant-design/icons'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { listCustomers, createCustomer, updateCustomer, rotateCustomerToken, deleteCustomer, type CustomerListItem, type CreateCustomerPayload, type UpdateCustomerPayload, } from '../api/customers'; import { CustomerFormModal } from '../components/customers/CustomerFormModal'; import { TokenShownOnceModal } from '../components/customers/TokenShownOnceModal'; const { Text } = Typography; type FormMode = { kind: 'create' } | { kind: 'edit'; customer: CustomerListItem }; export function AdminCustomersPage() { const qc = useQueryClient(); const [formMode, setFormMode] = useState(null); const [formError, setFormError] = useState(null); const [tokenModal, setTokenModal] = useState<{ open: boolean; code: string | null; token: string | null }>({ open: false, code: null, token: null, }); const { data: customers = [], isLoading } = useQuery({ queryKey: ['admin', 'customers'], queryFn: listCustomers, }); const invalidate = () => qc.invalidateQueries({ queryKey: ['admin', 'customers'] }); const createMut = useMutation({ mutationFn: (p: CreateCustomerPayload) => createCustomer(p), onSuccess: (result) => { setFormMode(null); setFormError(null); setTokenModal({ open: true, code: result.customer.code, token: result.token }); invalidate(); }, onError: (err: unknown) => setFormError(extractError(err)), }); const updateMut = useMutation({ mutationFn: ({ id, payload }: { id: string; payload: UpdateCustomerPayload }) => updateCustomer(id, payload), onSuccess: () => { message.success('Customer updated'); setFormMode(null); setFormError(null); invalidate(); }, onError: (err: unknown) => setFormError(extractError(err)), }); const rotateMut = useMutation({ mutationFn: (id: string) => rotateCustomerToken(id), onSuccess: (result) => { setTokenModal({ open: true, code: result.customer.code, token: result.token }); invalidate(); }, onError: (err: unknown) => message.error(extractError(err)), }); const deleteMut = useMutation({ mutationFn: (id: string) => deleteCustomer(id), onSuccess: () => { message.success('Customer deleted'); invalidate(); }, onError: (err: unknown) => message.error(extractError(err)), }); const handleSubmit = (payload: CreateCustomerPayload | UpdateCustomerPayload) => { setFormError(null); if (formMode?.kind === 'edit') { updateMut.mutate({ id: formMode.customer.id, payload: payload as UpdateCustomerPayload }); } else { createMut.mutate(payload as CreateCustomerPayload); } }; const columns: ColumnsType = [ { title: 'Code', dataIndex: 'code', key: 'code', render: (v) => {v} }, { title: 'Name', dataIndex: 'name', key: 'name' }, { title: 'Status', dataIndex: 'isActive', key: 'isActive', render: (v: boolean) => (v ? Active : Disabled), }, { title: 'Last push', dataIndex: 'lastSeenAt', key: 'lastSeenAt', render: (v: string | null) => v ? new Date(v).toLocaleString() : Never, }, { title: 'Token issued', key: 'token', render: (_, c) => { const ts = c.tokenRotatedAt ?? c.tokenIssuedAt; return new Date(ts).toLocaleDateString(); }, }, { title: 'Actions', key: 'actions', render: (_, c) => ( rotateMut.mutate(c.id)} > deleteMut.mutate(c.id)} > ), }, ]; return ( } onClick={() => { setFormError(null); setFormMode({ kind: 'create' }); }} > Register customer } > rowKey="id" columns={columns} dataSource={customers} loading={isLoading} pagination={{ pageSize: 25 }} /> { setFormMode(null); setFormError(null); }} onSubmit={handleSubmit} /> setTokenModal({ open: false, code: null, token: null })} /> ); } function extractError(err: unknown): string { if (typeof err === 'object' && err !== null && 'response' in err) { const data = (err as { response?: { data?: { error?: string } } }).response?.data; if (data?.error) return data.error; } return 'Request failed.'; }