221 lines
7.2 KiB
TypeScript
221 lines
7.2 KiB
TypeScript
'use client'
|
||
|
||
import { useEffect, useState, useCallback } from 'react'
|
||
import LoadingCard from '@/components/ui/loadingCard'
|
||
import ErrorCard from '@/components/ui/errorCard'
|
||
import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table'
|
||
import { getAllocationStatus, type AllocationStatus } from '@/actions/stats'
|
||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||
import { Form, FormField } from '@/components/ui/form'
|
||
import { z } from 'zod'
|
||
import { useForm } from 'react-hook-form'
|
||
import { zodResolver } from '@hookform/resolvers/zod'
|
||
import { Button } from '@/components/ui/button'
|
||
import { ArrowUpDownIcon, ArrowUpIcon, ArrowDownIcon } from 'lucide-react'
|
||
import { Page } from '@/components/page'
|
||
|
||
const filterSchema = z.object({
|
||
timeFilter: z.string(),
|
||
})
|
||
type FilterSchema = z.infer<typeof filterSchema>
|
||
|
||
type SortKey = 'city' | 'count' | 'assigned' | 'overage'
|
||
|
||
export default function AllocationStatus({ detailed = false }: { detailed?: boolean }) {
|
||
const [data, setData] = useState<AllocationStatus[]>([])
|
||
const [loading, setLoading] = useState(true)
|
||
const [error, setError] = useState<string | null>(null)
|
||
// 3.添加状态管理
|
||
const [sortKey, setSortKey] = useState<SortKey>('count')
|
||
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc')
|
||
|
||
const form = useForm<FilterSchema>({
|
||
resolver: zodResolver(filterSchema),
|
||
defaultValues: {
|
||
timeFilter: '24',
|
||
},
|
||
})
|
||
const timeFilter = form.watch('timeFilter')
|
||
|
||
const getTimeHours = useCallback(() => {
|
||
return parseInt(timeFilter) || 24
|
||
}, [timeFilter])
|
||
// 1.准备计算超额量的工具函数
|
||
const calculateOverage = (assigned: number, count: number) => {
|
||
return Math.max(0, assigned - count)
|
||
}
|
||
|
||
// 2.写一个排序数据的函数
|
||
// 5.计算排序后的数据
|
||
const sortedData = [...data].sort((a, b) => {
|
||
let aValue: string | number
|
||
let bValue: string | number
|
||
|
||
switch (sortKey) {
|
||
case 'city':
|
||
aValue = a.city || '未知'
|
||
bValue = b.city || '未知'
|
||
break
|
||
case 'count':
|
||
aValue = Number(a.count)
|
||
bValue = Number(b.count)
|
||
break
|
||
case 'assigned':
|
||
aValue = Number(a.assigned)
|
||
bValue = Number(b.assigned)
|
||
break
|
||
case 'overage':
|
||
aValue = calculateOverage(Number(a.assigned), Number(a.count))
|
||
bValue = calculateOverage(Number(b.assigned), Number(b.count))
|
||
break
|
||
}
|
||
|
||
if (typeof aValue === 'string') {
|
||
return sortDirection === 'asc'
|
||
? aValue.localeCompare(bValue as string)
|
||
: (bValue as string).localeCompare(aValue)
|
||
}
|
||
else {
|
||
return sortDirection === 'asc'
|
||
? aValue - (bValue as number)
|
||
: (bValue as number) - aValue
|
||
}
|
||
})
|
||
|
||
const fetchData = useCallback(async () => {
|
||
try {
|
||
setError(null)
|
||
setLoading(true)
|
||
|
||
const hours = getTimeHours()
|
||
const result = await getAllocationStatus(hours)
|
||
|
||
const validatedData = result.data.map(item => ({
|
||
city: item.city || '未知',
|
||
count: item.count,
|
||
assigned: item.assigned,
|
||
}))
|
||
|
||
setData(validatedData)
|
||
}
|
||
catch (error) {
|
||
console.error('Failed to fetch allocation status:', error)
|
||
setError(error instanceof Error ? error.message : 'Unknown error')
|
||
}
|
||
finally {
|
||
setLoading(false)
|
||
}
|
||
}, [getTimeHours])
|
||
|
||
useEffect(() => {
|
||
fetchData()
|
||
}, [fetchData])
|
||
|
||
// 4.封装一个函数用于处理排序问题
|
||
const handleSort = (key: SortKey) => {
|
||
if (sortKey === key) {
|
||
setSortDirection(direction => direction === 'asc' ? 'desc' : 'asc')
|
||
}
|
||
else {
|
||
setSortKey(key)
|
||
setSortDirection('desc')
|
||
}
|
||
}
|
||
|
||
// 6. 渲染排序图标,然后集成到ui里
|
||
const renderSortIcon = (key: SortKey) => {
|
||
if (sortKey !== key) {
|
||
return <ArrowUpDownIcon className="h-4 w-4 text-gray-400" />
|
||
}
|
||
return sortDirection === 'asc'
|
||
? <ArrowUpIcon className="h-4 w-4 text-blue-600" />
|
||
: <ArrowDownIcon className="h-4 w-4 text-blue-600" />
|
||
}
|
||
|
||
const onSubmit = (data: FilterSchema) => {
|
||
fetchData()
|
||
}
|
||
|
||
if (loading) return <LoadingCard title="节点分配状态" />
|
||
if (error) return <ErrorCard title="节点分配状态" error={error} onRetry={fetchData} />
|
||
|
||
return (
|
||
<Page>
|
||
<h2 className="flex-none text-lg font-semibold mb-4">节点分配状态</h2>
|
||
<div className="mb-4 flex flex-wrap items-center gap-3">
|
||
<Form {...form}>
|
||
<form onSubmit={form.handleSubmit(onSubmit)} className="flex items-center gap-4">
|
||
<FormField
|
||
name="timeFilter"
|
||
render={({ field }) => (
|
||
<div className="flex items-center">
|
||
<span className="text-sm mr-2">时间筛选:</span>
|
||
<Select value={field.value} onValueChange={field.onChange}>
|
||
<SelectTrigger className="h-9 w-36">
|
||
<SelectValue placeholder="选择时间范围" />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
<SelectItem value="1">最近1小时</SelectItem>
|
||
<SelectItem value="4">最近4小时</SelectItem>
|
||
<SelectItem value="12">最近12小时</SelectItem>
|
||
<SelectItem value="24">最近24小时</SelectItem>
|
||
<SelectItem value="168">最近7天</SelectItem>
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
)}
|
||
/>
|
||
|
||
<Button type="submit" className="py-2 bg-blue-600 hover:bg-blue-700">
|
||
查询
|
||
</Button>
|
||
</form>
|
||
</Form>
|
||
</div>
|
||
|
||
<Table>
|
||
<TableHeader>
|
||
<TableRow>
|
||
<TableHead>城市</TableHead>
|
||
<TableHead onClick={() => handleSort('count')}>
|
||
<div className="flex items-center gap-2">
|
||
<span>可用IP量</span>
|
||
{renderSortIcon('count')}
|
||
</div>
|
||
</TableHead>
|
||
<TableHead onClick={() => handleSort('assigned')}>
|
||
<div className="flex items-center gap-2">
|
||
<span>分配IP量</span>
|
||
{renderSortIcon('assigned')}
|
||
</div>
|
||
</TableHead>
|
||
<TableHead onClick={() => handleSort('overage')}>
|
||
<div className="flex items-center gap-2">
|
||
<span>超额量</span>
|
||
{renderSortIcon('overage')}
|
||
</div>
|
||
</TableHead>
|
||
</TableRow>
|
||
</TableHeader>
|
||
<TableBody>
|
||
{sortedData.map((item, index) => {
|
||
const overage = calculateOverage(Number(item.assigned), Number(item.count))
|
||
return (
|
||
<TableRow key={index}>
|
||
<TableCell>{item.city}</TableCell>
|
||
<TableCell>{item.count}</TableCell>
|
||
<TableCell>{item.assigned}</TableCell>
|
||
<TableCell>
|
||
<span className={overage > 0 ? 'text-red-600 font-medium' : ''}>
|
||
{overage}
|
||
</span>
|
||
</TableCell>
|
||
</TableRow>
|
||
)
|
||
})}
|
||
</TableBody>
|
||
</Table>
|
||
</Page>
|
||
)
|
||
}
|