From ef4fa016e629aa8bdcb26f231dad96fecba8b52f Mon Sep 17 00:00:00 2001 From: Noa Virellia Date: Fri, 27 Mar 2026 18:43:58 +0800 Subject: [PATCH] fix(attendance): remove KYC status filter/column, show KYC info col based on event.enable_kyc; fix search input focus loss - Remove KYC status filter dropdown and status column from attendance list - Conditionally render KYC type and operation columns based on event's enable_kyc field - Add keepPreviousData to useAdminAttendance to prevent input focus loss on filter change Co-Authored-By: Claude Sonnet 4.6 --- .../attendance/attendance-list.container.tsx | 28 ++--- .../admin/attendance/attendance-list.view.tsx | 115 +++++++++--------- src/hooks/data/useAdminAttendance.ts | 27 ++-- 3 files changed, 82 insertions(+), 88 deletions(-) diff --git a/src/components/admin/attendance/attendance-list.container.tsx b/src/components/admin/attendance/attendance-list.container.tsx index 48e12a2..0f51304 100644 --- a/src/components/admin/attendance/attendance-list.container.tsx +++ b/src/components/admin/attendance/attendance-list.container.tsx @@ -2,6 +2,7 @@ import type { AttendanceRow } from './attendance-list.view'; import { useNavigate } from '@tanstack/react-router'; import { useEffect, useState } from 'react'; import { useAdminAttendance } from '@/hooks/data/useAdminAttendance'; +import { useEventInfo } from '@/hooks/data/useEventInfo'; import { useUserInfo } from '@/hooks/data/useUserInfo'; import { canManageEvents } from '@/lib/permissions'; import { AttendanceListSkeleton } from './attendance-list.skeleton'; @@ -14,8 +15,8 @@ interface AttendanceListContainerProps { interface AttendanceApiRow { attendance_id: string; kyc_type?: string; - kyc_status?: string; - joined_at?: string; + kyc_info?: Record | null; + joined_at?: string | null; checked_in_at?: string | null; user_info: { user_id: string; @@ -25,28 +26,23 @@ interface AttendanceApiRow { }; } -export function mapKycStatusFilterToQuery(status: string | undefined) { - if (status === 'approved') { - return 'with_kyc'; - } - return status; -} export function AttendanceListContainer({ eventId }: AttendanceListContainerProps) { const { data: userData } = useUserInfo(); const permissionLevel = userData.data?.permission_level ?? 0; const navigate = useNavigate(); + const { data: eventInfoData } = useEventInfo(eventId); + const enableKyc = (eventInfoData as unknown as { data?: { enable_kyc?: boolean } })?.data?.enable_kyc ?? false; + const [page, setPage] = useState(1); const [sortBy, setSortBy] = useState('joined_at'); const [sortDir, setSortDir] = useState<'asc' | 'desc'>('desc'); const [nameFilter, setNameFilter] = useState(''); - const [kycStatusFilter, setKycStatusFilter] = useState(undefined); const { data, isPending } = useAdminAttendance({ eventId, name: nameFilter || undefined, - kyc_status: mapKycStatusFilterToQuery(kycStatusFilter), page, page_size: 50, sort_by: sortBy, @@ -74,11 +70,6 @@ export function AttendanceListContainer({ eventId }: AttendanceListContainerProp setPage(1); } - function handleKycStatusFilterChange(status: string | undefined) { - setKycStatusFilter(status); - setPage(1); - } - if (!canManageEvents(permissionLevel)) { return null; } @@ -95,8 +86,8 @@ export function AttendanceListContainer({ eventId }: AttendanceListContainerProp nickname: row.user_info.nickname ?? '', avatar: row.user_info.avatar ?? '', kyc_type: row.kyc_type ?? null, - kyc_status: row.kyc_status ?? null, - joined_at: row.joined_at ?? '', + kyc_info: row.kyc_info ?? null, + joined_at: row.joined_at ?? null, checked_in_at: row.checked_in_at ?? null, })); @@ -109,11 +100,10 @@ export function AttendanceListContainer({ eventId }: AttendanceListContainerProp sortBy={sortBy} sortDir={sortDir} nameFilter={nameFilter} - kycStatusFilter={kycStatusFilter} + enableKyc={enableKyc} onPageChange={setPage} onSortChange={handleSortChange} onNameFilterChange={handleNameFilterChange} - onKycStatusFilterChange={handleKycStatusFilterChange} /> ); } diff --git a/src/components/admin/attendance/attendance-list.view.tsx b/src/components/admin/attendance/attendance-list.view.tsx index 35b457b..7ebb2e9 100644 --- a/src/components/admin/attendance/attendance-list.view.tsx +++ b/src/components/admin/attendance/attendance-list.view.tsx @@ -1,14 +1,13 @@ import type React from 'react'; -import { Badge } from '@/components/ui/badge'; +import { useState } from 'react'; import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; import { Table, TableBody, @@ -24,8 +23,8 @@ export interface AttendanceRow { nickname: string; avatar: string; kyc_type: string | null; - kyc_status: string | null; - joined_at: string; + kyc_info: Record | null; + joined_at: string | null; checked_in_at: string | null; } @@ -37,11 +36,10 @@ interface AttendanceListViewProps { sortBy: string; sortDir: 'asc' | 'desc'; nameFilter: string; - kycStatusFilter: string | undefined; + enableKyc: boolean; onPageChange: (page: number) => void; onSortChange: (column: string) => void; onNameFilterChange: (name: string) => void; - onKycStatusFilterChange: (status: string | undefined) => void; } interface SortableHeaderProps { @@ -67,26 +65,16 @@ function SortableHeader({ column, sortBy, sortDir, onSortChange, children }: Sor ); } -function kycStatusBadge(status: string | null) { - if (status === null) - return ; - const map: Record = { - pending: { variant: 'secondary', label: '待审核' }, - approved: { variant: 'default', label: '已通过' }, - rejected: { variant: 'destructive', label: '已驳回' }, - }; - const entry = map[status]; - if (entry === undefined) - return {status}; - return {entry.label}; -} +const KYC_TYPE_MAP: Record = { + cnrid: '身份证', + passport: '护照', +}; -const KYC_STATUS_OPTIONS = [ - { label: '全部', value: '__all__' }, - { label: '待审核', value: 'pending' }, - { label: '已通过', value: 'approved' }, - { label: '已驳回', value: 'rejected' }, -] as const; +function kycTypeLabel(type: string | null) { + if (type === null) + return ; + return {KYC_TYPE_MAP[type] ?? type}; +} export function AttendanceListView({ attendees, @@ -96,13 +84,13 @@ export function AttendanceListView({ sortBy, sortDir, nameFilter, - kycStatusFilter, + enableKyc, onPageChange, onSortChange, onNameFilterChange, - onKycStatusFilterChange, }: AttendanceListViewProps) { const totalPages = Math.ceil(total / pageSize); + const [kycInfoTarget, setKycInfoTarget] = useState(null); return (
@@ -115,21 +103,6 @@ export function AttendanceListView({ value={nameFilter} onChange={e => onNameFilterChange(e.target.value)} /> -
@@ -137,10 +110,10 @@ export function AttendanceListView({ 用户名 - KYC 类型 - KYC 状态 + {enableKyc && KYC 类型} 报名时间 签到时间 + {enableKyc && 操作} @@ -155,20 +128,31 @@ export function AttendanceListView({
{row.username}
{row.nickname}
+ {enableKyc && ( + + {kycTypeLabel(row.kyc_type)} + + )} - {row.kyc_type ?? '—'} - - - {kycStatusBadge(row.kyc_status)} - - - {new Date(row.joined_at).toLocaleDateString('zh-CN')} + {row.joined_at ? new Date(row.joined_at).toLocaleDateString('zh-CN') : '—'} {row.checked_in_at !== null ? new Date(row.checked_in_at).toLocaleDateString('zh-CN') : '未签到'} + {enableKyc && ( + + + + )} ))}
@@ -201,6 +185,23 @@ export function AttendanceListView({ )} + + !open && setKycInfoTarget(null)}> + + + + KYC 信息 — + {' '} + {kycInfoTarget?.username} + + +
+            {kycInfoTarget?.kyc_info !== null && kycInfoTarget?.kyc_info !== undefined
+              ? JSON.stringify(kycInfoTarget.kyc_info, null, 2)
+              : '无 KYC 信息'}
+          
+
+
); } diff --git a/src/hooks/data/useAdminAttendance.ts b/src/hooks/data/useAdminAttendance.ts index e2ee01a..0278f84 100644 --- a/src/hooks/data/useAdminAttendance.ts +++ b/src/hooks/data/useAdminAttendance.ts @@ -1,4 +1,4 @@ -import { useQuery } from '@tanstack/react-query'; +import { keepPreviousData, useQuery } from '@tanstack/react-query'; import { getEventAttendanceOptions } from '@/client/@tanstack/react-query.gen'; export interface AdminAttendanceParams { @@ -14,15 +14,18 @@ export interface AdminAttendanceParams { export function useAdminAttendance(params: AdminAttendanceParams) { const page = params.page ?? 1; const pageSize = params.page_size ?? 50; - return useQuery(getEventAttendanceOptions({ - query: { - event_id: params.eventId, - name: params.name, - kyc_status: params.kyc_status, - limit: pageSize, - offset: (page - 1) * pageSize, - sort_by: params.sort_by, - sort_order: params.sort_dir, - }, - })); + return useQuery({ + ...getEventAttendanceOptions({ + query: { + event_id: params.eventId, + name: params.name, + kyc_status: params.kyc_status, + limit: pageSize, + offset: (page - 1) * pageSize, + sort_by: params.sort_by, + sort_order: params.sort_dir, + }, + }), + placeholderData: keepPreviousData, + }); }