application [cat-admin-web] view page [pages/queue-management] development
This commit is contained in:
277
apps/cat-admin-web/src/pages/queue-management.tsx
Normal file
277
apps/cat-admin-web/src/pages/queue-management.tsx
Normal file
@@ -0,0 +1,277 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { entities } from '@/lib/nvwa';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
|
||||
interface Cat {
|
||||
id: number;
|
||||
name: string;
|
||||
age: number;
|
||||
gender: string;
|
||||
grade: string;
|
||||
type: string;
|
||||
is_available: boolean;
|
||||
description?: string;
|
||||
image_ids: number[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
interface Reservation {
|
||||
id: number;
|
||||
user_id: string;
|
||||
cat_id?: number;
|
||||
deposit: string;
|
||||
status: string;
|
||||
queue_order?: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
user: { name: string };
|
||||
cat?: { name: string };
|
||||
}
|
||||
|
||||
interface QueueRules {
|
||||
maxQueueLength: number;
|
||||
defaultDeposit: number;
|
||||
ruleType: 'fifo' | 'priority';
|
||||
}
|
||||
|
||||
export default function QueueManagement() {
|
||||
const [cats, setCats] = useState<Cat[]>([]);
|
||||
const [reservations, setReservations] = useState<Reservation[]>([]);
|
||||
const [rules, setRules] = useState<QueueRules>({
|
||||
maxQueueLength: 10,
|
||||
defaultDeposit: 100,
|
||||
ruleType: 'fifo'
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
fetchCats();
|
||||
fetchReservations();
|
||||
}, []);
|
||||
|
||||
const fetchCats = async () => {
|
||||
const { data, error } = await entities.from('cat').select('*');
|
||||
if (error) {
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
setCats(data || []);
|
||||
};
|
||||
|
||||
const fetchReservations = async () => {
|
||||
const { data, error } = await entities
|
||||
.from('reservation')
|
||||
.select('*, user!inner(name), cat(name)')
|
||||
.eq('status', 'queuing')
|
||||
.order('queue_order');
|
||||
if (error) {
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
setReservations(data || []);
|
||||
};
|
||||
|
||||
const toggleQueuing = async (cat: Cat) => {
|
||||
// Assuming enabling queuing sets is_available to true
|
||||
const { error } = await entities.from('cat').update({ is_available: !cat.is_available }).eq('id', cat.id);
|
||||
if (error) {
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
setCats(cats.map(c => c.id === cat.id ? { ...c, is_available: !c.is_available } : c));
|
||||
};
|
||||
|
||||
const updateRules = (key: keyof QueueRules, value: any) => {
|
||||
setRules(prev => ({ ...prev, [key]: value }));
|
||||
};
|
||||
|
||||
const moveQueue = async (reservation: Reservation, direction: 'up' | 'down') => {
|
||||
const currentIndex = reservations.findIndex(r => r.id === reservation.id);
|
||||
if (direction === 'up' && currentIndex > 0) {
|
||||
const prev = reservations[currentIndex - 1];
|
||||
await swapOrder(reservation, prev);
|
||||
} else if (direction === 'down' && currentIndex < reservations.length - 1) {
|
||||
const next = reservations[currentIndex + 1];
|
||||
await swapOrder(reservation, next);
|
||||
}
|
||||
fetchReservations();
|
||||
};
|
||||
|
||||
const swapOrder = async (res1: Reservation, res2: Reservation) => {
|
||||
const { error: error1 } = await entities.from('reservation').update({ queue_order: res2.queue_order }).eq('id', res1.id);
|
||||
const { error: error2 } = await entities.from('reservation').update({ queue_order: res1.queue_order }).eq('id', res2.id);
|
||||
if (error1 || error2) {
|
||||
console.error(error1 || error2);
|
||||
}
|
||||
};
|
||||
|
||||
const changeStatus = async (reservation: Reservation, newStatus: string) => {
|
||||
const { error } = await entities.from('reservation').update({ status: newStatus }).eq('id', reservation.id);
|
||||
if (error) {
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
fetchReservations();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<h1 className="text-2xl font-bold mb-6">排队管理</h1>
|
||||
<Tabs defaultValue="enable" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-3">
|
||||
<TabsTrigger value="enable">开启排队</TabsTrigger>
|
||||
<TabsTrigger value="rules">设置规则</TabsTrigger>
|
||||
<TabsTrigger value="manage">管理队列</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="enable">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>开启猫咪排队功能</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>名称</TableHead>
|
||||
<TableHead>年龄</TableHead>
|
||||
<TableHead>性别</TableHead>
|
||||
<TableHead>等级</TableHead>
|
||||
<TableHead>类型</TableHead>
|
||||
<TableHead>排队开启</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{cats.map(cat => (
|
||||
<TableRow key={cat.id}>
|
||||
<TableCell>{cat.name}</TableCell>
|
||||
<TableCell>{cat.age} 月</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={cat.gender === 'male' ? 'default' : 'secondary'}>
|
||||
{cat.gender === 'male' ? '雄' : '雌'}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline">{cat.grade}</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline">
|
||||
{cat.type === 'breeding_male' ? '种公' : cat.type === 'breeding_female' ? '种母' : '幼猫'}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Switch
|
||||
checked={cat.is_available}
|
||||
onCheckedChange={() => toggleQueuing(cat)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
<TabsContent value="rules">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>设置排队规则</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<Label htmlFor="maxQueue">最大排队人数</Label>
|
||||
<Input
|
||||
id="maxQueue"
|
||||
type="number"
|
||||
value={rules.maxQueueLength}
|
||||
onChange={(e) => updateRules('maxQueueLength', parseInt(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="deposit">默认定金</Label>
|
||||
<Input
|
||||
id="deposit"
|
||||
type="number"
|
||||
value={rules.defaultDeposit}
|
||||
onChange={(e) => updateRules('defaultDeposit', parseFloat(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label>排队规则类型</Label>
|
||||
<Select
|
||||
value={rules.ruleType}
|
||||
onValueChange={(value: 'fifo' | 'priority') => updateRules('ruleType', value)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="fifo">先进先出</SelectItem>
|
||||
<SelectItem value="priority">优先级</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
<TabsContent value="manage">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>管理排队队列</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>队列顺序</TableHead>
|
||||
<TableHead>用户</TableHead>
|
||||
<TableHead>猫咪</TableHead>
|
||||
<TableHead>定金</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{reservations.map((res, index) => (
|
||||
<TableRow key={res.id}>
|
||||
<TableCell>{res.queue_order}</TableCell>
|
||||
<TableCell>{res.user.name}</TableCell>
|
||||
<TableCell>{res.cat?.name || 'N/A'}</TableCell>
|
||||
<TableCell>{res.deposit}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline">{res.status}</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex space-x-2">
|
||||
<Button size="sm" onClick={() => moveQueue(res, 'up')} disabled={index === 0}>
|
||||
↑
|
||||
</Button>
|
||||
<Button size="sm" onClick={() => moveQueue(res, 'down')} disabled={index === reservations.length - 1}>
|
||||
↓
|
||||
</Button>
|
||||
<Button size="sm" onClick={() => changeStatus(res, 'reserved')}>
|
||||
预订
|
||||
</Button>
|
||||
<Button size="sm" variant="destructive" onClick={() => changeStatus(res, 'cancelled')}>
|
||||
取消
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user