import React from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Button } from '@/client/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/client/components/ui/table'; import { DataTablePagination } from '../components/DataTablePagination'; import { Plus, Edit, Trash2, Calendar, Search, Filter, Power } from 'lucide-react'; import { useState, useCallback } from 'react'; import { activityClient } from '@/client/api'; import type { InferResponseType, InferRequestType } from 'hono/client'; import { Input } from '@/client/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/client/components/ui/select'; import { Badge } from '@/client/components/ui/badge'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/client/components/ui/dialog'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/client/components/ui/alert-dialog'; import { ActivityForm } from '../components/ActivityForm'; import type { CreateActivityInput, UpdateActivityInput } from '@d8d/server/modules/activities/activity.schema'; import { LocationSelect } from '../components/LocationSelect'; // 类型提取规范 type ActivityResponse = InferResponseType['data'][0]; type CreateActivityRequest = InferRequestType['json']; type UpdateActivityRequest = InferRequestType['json']; // 统一操作处理函数 const handleOperation = async (operation: () => Promise) => { try { await operation(); // toast.success('操作成功'); console.log('操作成功'); } catch (error) { console.error('操作失败:', error); // toast.error('操作失败,请重试'); throw error; } }; // 防抖搜索函数 const debounce = (func: Function, delay: number) => { let timeoutId: NodeJS.Timeout; return (...args: any[]) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => func(...args), delay); }; }; export const ActivitiesPage: React.FC = () => { const queryClient = useQueryClient(); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(20); const [keyword, setKeyword] = useState(''); const [typeFilter, setTypeFilter] = useState('all'); const [locationFilter, setLocationFilter] = useState(undefined); const [isFormOpen, setIsFormOpen] = useState(false); const [editingActivity, setEditingActivity] = useState(null); const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); const [activityToDelete, setActivityToDelete] = useState(null); const [statusConfirmOpen, setStatusConfirmOpen] = useState(false); const [activityToToggle, setActivityToToggle] = useState(null); // 防抖搜索 const debouncedSearch = useCallback( debounce((searchKeyword: string) => { setKeyword(searchKeyword); setPage(1); // 搜索时重置到第一页 }, 300), [] ); // 获取活动列表 - 使用RPC客户端 const { data, isLoading, error } = useQuery({ queryKey: ['activities', page, pageSize, keyword, typeFilter, locationFilter], queryFn: async () => { const query: any = { page, pageSize }; if (keyword) { query.keyword = keyword; } const filters: any = {}; if (typeFilter !== 'all') { filters.type = typeFilter; } if (locationFilter) { filters.venueLocationId = locationFilter; } if (Object.keys(filters).length > 0) { query.filters = JSON.stringify(filters); } const res = await activityClient.$get({ query }); if (res.status !== 200) throw new Error('获取活动列表失败'); return await res.json(); }, staleTime: 5 * 60 * 1000, // 5分钟缓存 }); // 创建活动 - 使用RPC客户端 const createMutation = useMutation({ mutationFn: async (data: CreateActivityRequest) => { await handleOperation(async () => { const res = await activityClient.$post({ json: data }); if (res.status !== 201) throw new Error('创建活动失败'); }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['activities'] }); setIsFormOpen(false); }, }); // 更新活动 - 使用RPC客户端 const updateMutation = useMutation({ mutationFn: async ({ id, data }: { id: number; data: UpdateActivityRequest }) => { await handleOperation(async () => { const res = await activityClient[':id'].$put({ param: { id }, json: data }); if (res.status !== 200) throw new Error('更新活动失败'); }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['activities'] }); setIsFormOpen(false); setEditingActivity(null); }, }); // 启用/禁用活动 - 使用RPC客户端 const toggleStatusMutation = useMutation({ mutationFn: async ({ id, isDisabled }: { id: number; isDisabled: number }) => { await handleOperation(async () => { const res = await activityClient[':id'].$put({ param: { id }, json: { isDisabled } }); if (res.status !== 200) throw new Error('更新活动状态失败'); }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['activities'] }); }, }); // 删除活动 - 使用RPC客户端 const deleteMutation = useMutation({ mutationFn: async (id: number) => { await handleOperation(async () => { const res = await activityClient[':id'].$delete({ param: { id } }); if (res.status !== 204) throw new Error('删除活动失败'); }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['activities'] }); }, }); // 处理表单提交 const handleFormSubmit = async (data: CreateActivityInput | UpdateActivityInput) => { if (editingActivity) { await updateMutation.mutateAsync({ id: editingActivity.id, data: data as UpdateActivityRequest }); } else { await createMutation.mutateAsync(data as CreateActivityRequest); } }; // 打开创建表单 const handleCreate = () => { setEditingActivity(null); setIsFormOpen(true); }; // 打开编辑表单 const handleEdit = (activity: ActivityResponse) => { setEditingActivity(activity); setIsFormOpen(true); }; // 关闭表单 const handleFormClose = () => { setIsFormOpen(false); setEditingActivity(null); }; // 切换活动状态 const handleToggleStatus = (activity: ActivityResponse) => { setActivityToToggle(activity); setStatusConfirmOpen(true); }; if (error) { return (
加载活动数据失败: {error.message}
); } return (

活动管理

管理旅行活动,包括去程和返程活动

活动列表 当前共有 {data?.pagination.total || 0} 个活动
{/* 搜索框 */}
debouncedSearch(e.target.value)} data-testid="activity-search-input" />
{/* 类型筛选 */} {/* 地点筛选 */}
{/* 筛选标签 */} {(keyword || typeFilter !== 'all' || locationFilter) && (
{keyword && ( 搜索: {keyword} )} {typeFilter !== 'all' && ( 类型: {typeFilter === 'departure' ? '去程' : '返程'} )} {locationFilter && ( 地点: {data?.data?.find((a: ActivityResponse) => a.venueLocationId === locationFilter)?.venueLocation?.name || '未知地点'} )}
)}
活动名称 类型 举办地点 开始时间 结束时间 状态 操作 {isLoading ? ( 加载中... ) : data?.data && data.data.length > 0 ? ( data.data.map((activity: ActivityResponse) => (
{activity.name}
{activity.type === 'departure' ? '去程' : '返程'} {activity.venueLocation ? (
{activity.venueLocation.name} {activity.venueLocation.address}
) : ( 未设置地点 )}
{new Date(activity.startDate).toLocaleString('zh-CN')} {new Date(activity.endDate).toLocaleString('zh-CN')} {activity.isDisabled === 0 ? '启用' : '禁用'}
)) ) : ( 暂无活动数据 )}
{data && ( { setPage(page); setPageSize(pageSize); }} /> )}
{/* 活动表单对话框 */} {editingActivity ? '编辑活动' : '创建活动'} {editingActivity ? '修改活动信息' : '创建新的旅行活动'} {/* 删除确认对话框 */} 确认删除 确定要删除活动 "{activityToDelete?.name}" 吗?此操作不可撤销。 取消 { if (activityToDelete) { deleteMutation.mutate(activityToDelete.id); } setDeleteConfirmOpen(false); setActivityToDelete(null); }} className="bg-destructive text-destructive-foreground hover:bg-destructive/90" > 删除 {/* 启用/禁用确认对话框 */} 确认状态切换 确定要{activityToToggle?.isDisabled === 0 ? '禁用' : '启用'}活动 "{activityToToggle?.name}" 吗? 取消 { if (activityToToggle) { const newStatus = activityToToggle.isDisabled === 0 ? 1 : 0; toggleStatusMutation.mutate({ id: activityToToggle.id, isDisabled: newStatus }); } setStatusConfirmOpen(false); setActivityToToggle(null); }} > {activityToToggle?.isDisabled === 0 ? '禁用' : '启用'}
); };