application [cat-admin-web] view page [pages/reservation-view] development
This commit is contained in:
296
apps/cat-admin-web/src/pages/reservation-view.tsx
Normal file
296
apps/cat-admin-web/src/pages/reservation-view.tsx
Normal file
@@ -0,0 +1,296 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { entities } from '@/lib/nvwa';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
||||
// 定义预约数据类型
|
||||
interface Reservation {
|
||||
id: number;
|
||||
userId: string;
|
||||
userName: string;
|
||||
userEmail: string;
|
||||
catId: number | null;
|
||||
catName: string | null;
|
||||
deposit: number;
|
||||
status: 'queuing' | 'reserved' | 'completed' | 'cancelled';
|
||||
queueOrder: number | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
const ReservationView: React.FC = () => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [reservations, setReservations] = useState<Reservation[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [total, setTotal] = useState(0);
|
||||
|
||||
// 获取查询参数
|
||||
const status = searchParams.get('status') || '';
|
||||
const page = parseInt(searchParams.get('page') || '1');
|
||||
|
||||
const pageSize = 10; // 每页显示数量
|
||||
|
||||
// 获取预约数据
|
||||
const fetchReservations = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// 构建查询,使用关联查询获取用户和猫宠信息
|
||||
let query = entities.from('reservation').select(`
|
||||
*,
|
||||
user!inner(name, email),
|
||||
cat(name)
|
||||
`);
|
||||
|
||||
// 添加筛选条件
|
||||
if (status) {
|
||||
query = query.eq('status', status);
|
||||
}
|
||||
|
||||
// 分页
|
||||
const offset = (page - 1) * pageSize;
|
||||
query = query.range(offset, offset + pageSize - 1);
|
||||
|
||||
const { data: reservationData, error } = await query;
|
||||
|
||||
if (error) {
|
||||
console.error('Error fetching reservations:', error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理数据
|
||||
const reservationsWithDetails: Reservation[] = (reservationData || []).map((res) => ({
|
||||
id: res.id,
|
||||
userId: res.user_id,
|
||||
userName: res.user?.name || 'Unknown',
|
||||
userEmail: res.user?.email || '',
|
||||
catId: res.cat_id,
|
||||
catName: res.cat?.name || null,
|
||||
deposit: parseFloat(res.deposit),
|
||||
status: res.status,
|
||||
queueOrder: res.queue_order,
|
||||
createdAt: res.created_at,
|
||||
updatedAt: res.updated_at,
|
||||
}));
|
||||
|
||||
setReservations(reservationsWithDetails);
|
||||
|
||||
// 获取总数
|
||||
let countQuery = entities.from('reservation').select('*', { count: 'exact', head: true });
|
||||
if (status) {
|
||||
countQuery = countQuery.eq('status', status);
|
||||
}
|
||||
const { count } = await countQuery;
|
||||
|
||||
setTotal(count || 0);
|
||||
} catch (error) {
|
||||
console.error('Error fetching reservations:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchReservations();
|
||||
}, [status, page]);
|
||||
|
||||
// 处理状态筛选
|
||||
const handleStatusChange = (newStatus: string) => {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
if (newStatus) {
|
||||
params.set('status', newStatus);
|
||||
} else {
|
||||
params.delete('status');
|
||||
}
|
||||
params.set('page', '1'); // 重置页码
|
||||
setSearchParams(params);
|
||||
};
|
||||
|
||||
// 处理分页
|
||||
const handlePageChange = (newPage: number) => {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
params.set('page', newPage.toString());
|
||||
setSearchParams(params);
|
||||
};
|
||||
|
||||
// 更新预约状态
|
||||
const updateReservationStatus = async (reservationId: number, newStatus: Reservation['status']) => {
|
||||
try {
|
||||
const { error } = await entities
|
||||
.from('reservation')
|
||||
.update({ status: newStatus })
|
||||
.eq('id', reservationId);
|
||||
|
||||
if (error) {
|
||||
console.error('Error updating reservation status:', error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 刷新数据
|
||||
fetchReservations();
|
||||
} catch (error) {
|
||||
console.error('Error updating reservation status:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 状态映射
|
||||
const statusLabels = {
|
||||
queuing: '排队中',
|
||||
reserved: '已预约',
|
||||
completed: '已完成',
|
||||
cancelled: '已取消',
|
||||
};
|
||||
|
||||
const statusColors = {
|
||||
queuing: 'secondary',
|
||||
reserved: 'default',
|
||||
completed: 'outline',
|
||||
cancelled: 'destructive',
|
||||
};
|
||||
|
||||
const totalPages = Math.ceil(total / pageSize);
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>预约查看</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{/* 筛选器 */}
|
||||
<div className="flex items-center space-x-4 mb-6">
|
||||
<div className="flex items-center space-x-2">
|
||||
<label className="text-sm font-medium">状态筛选:</label>
|
||||
<Select value={status} onValueChange={handleStatusChange}>
|
||||
<SelectTrigger className="w-40">
|
||||
<SelectValue placeholder="全部状态" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="">全部状态</SelectItem>
|
||||
<SelectItem value="queuing">排队中</SelectItem>
|
||||
<SelectItem value="reserved">已预约</SelectItem>
|
||||
<SelectItem value="completed">已完成</SelectItem>
|
||||
<SelectItem value="cancelled">已取消</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 数据表格 */}
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>用户</TableHead>
|
||||
<TableHead>猫宠</TableHead>
|
||||
<TableHead>押金</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>排队顺序</TableHead>
|
||||
<TableHead>创建时间</TableHead>
|
||||
<TableHead>操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{loading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={7} className="text-center">
|
||||
加载中...
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : reservations.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={7} className="text-center">
|
||||
暂无预约记录
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
reservations.map((reservation) => (
|
||||
<TableRow key={reservation.id}>
|
||||
<TableCell>
|
||||
<div>
|
||||
<div className="font-medium">{reservation.userName}</div>
|
||||
<div className="text-sm text-muted-foreground">{reservation.userEmail}</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{reservation.catName || '-'}</TableCell>
|
||||
<TableCell>¥{reservation.deposit.toFixed(2)}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={statusColors[reservation.status] as any}>
|
||||
{statusLabels[reservation.status]}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>{reservation.queueOrder || '-'}</TableCell>
|
||||
<TableCell>{new Date(reservation.createdAt).toLocaleString()}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex space-x-2">
|
||||
{reservation.status === 'queuing' && (
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() => updateReservationStatus(reservation.id, 'reserved')}
|
||||
>
|
||||
确认预约
|
||||
</Button>
|
||||
)}
|
||||
{reservation.status === 'reserved' && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => updateReservationStatus(reservation.id, 'completed')}
|
||||
>
|
||||
完成
|
||||
</Button>
|
||||
)}
|
||||
{reservation.status !== 'completed' && reservation.status !== 'cancelled' && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
onClick={() => updateReservationStatus(reservation.id, 'cancelled')}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
{/* 分页 */}
|
||||
{totalPages > 1 && (
|
||||
<div className="flex justify-between items-center mt-4">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
显示第 {Math.min((page - 1) * pageSize + 1, total)} - {Math.min(page * pageSize, total)} 条,共 {total} 条
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handlePageChange(page - 1)}
|
||||
disabled={page <= 1}
|
||||
>
|
||||
上一页
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => handlePageChange(page + 1)}
|
||||
disabled={page >= totalPages}
|
||||
>
|
||||
下一页
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReservationView;
|
||||
Reference in New Issue
Block a user