Files
jh-monitor/src/app/(root)/allocationStatus/page.tsx
2025-10-15 11:43:47 +08:00

221 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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>
)
}