application [cat-admin-web] view page [pages/reservation-view] development

This commit is contained in:
NVWA Code Agent
2025-12-11 16:46:48 +00:00
parent dd17bad4c4
commit fb4ef01791

View 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;