187 lines
6.2 KiB
TypeScript
187 lines
6.2 KiB
TypeScript
'use client'
|
|
import {Tabs, TabsList, TabsTrigger, TabsContent} from '@/components/ui/tabs'
|
|
import Image from 'next/image'
|
|
import soon from '../_assets/coming-soon.svg'
|
|
import DatePicker from '@/components/date-picker'
|
|
import {Card, CardContent} from '@/components/ui/card'
|
|
import {Form, FormField} from '@/components/ui/form'
|
|
import {Input} from '@/components/ui/input'
|
|
import {zodResolver} from '@hookform/resolvers/zod'
|
|
import {useForm} from 'react-hook-form'
|
|
import zod from 'zod'
|
|
import {merge} from '@/lib/utils'
|
|
import {Button} from '@/components/ui/button'
|
|
import {useState} from 'react'
|
|
import {statisticsResourceUsage} from '@/actions/dashboard'
|
|
import {ExtraResp} from '@/lib/api'
|
|
import {toast} from 'sonner'
|
|
import {addDays, compareAsc, format, subDays} from 'date-fns'
|
|
import {Label} from '@/components/ui/label'
|
|
import {ChartConfig, ChartContainer} from '@/components/ui/chart'
|
|
import {CartesianGrid, XAxis, YAxis, Tooltip, Area, AreaChart, Legend} from 'recharts'
|
|
|
|
type ChartsProps = {
|
|
initialData?: ExtraResp<typeof statisticsResourceUsage>
|
|
}
|
|
|
|
export default function Charts({initialData}: ChartsProps) {
|
|
// const [submittedData, setSubmittedData] = useState<ExtraReq<typeof listAccount>>()
|
|
const [submittedData, setSubmittedData] = useState<ExtraResp<typeof statisticsResourceUsage>>(initialData || [])
|
|
const formSchema = zod.object({
|
|
resource_no: zod.string().optional(),
|
|
create_after: zod.date().optional(),
|
|
create_before: zod.date().optional(),
|
|
})
|
|
type FormValues = zod.infer<typeof formSchema>
|
|
|
|
const form = useForm<FormValues>({
|
|
resolver: zodResolver(formSchema),
|
|
defaultValues: {
|
|
},
|
|
})
|
|
const handler = form.handleSubmit(
|
|
async (value) => {
|
|
const today = new Date()
|
|
const sevenDaysAgo = subDays(today, 7)
|
|
const res = {
|
|
resource_no: value.resource_no ?? '',
|
|
create_after: value.create_after ?? sevenDaysAgo,
|
|
create_before: value.create_before ?? today,
|
|
}
|
|
|
|
const resp = await statisticsResourceUsage(res)
|
|
if (!resp.success) {
|
|
toast.error('接口请求失败:' + resp.message)
|
|
return
|
|
}
|
|
if (!resp.data || resp.data.length === 0) {
|
|
toast.info('没有查询到相关数据')
|
|
setSubmittedData([])
|
|
return
|
|
}
|
|
const formattedData = resp.data.map(item => ({
|
|
...item,
|
|
date: item.date,
|
|
count: item.count,
|
|
}))
|
|
formattedData.sort((a, b) => compareAsc(a.date, b.date))
|
|
setSubmittedData(formattedData)
|
|
},
|
|
)
|
|
|
|
return (
|
|
<Card className="h-full">
|
|
<CardContent className="overflow-hidden">
|
|
<Tabs defaultValue="dynamic" className="h-full gap-4">
|
|
<TabsList className="h-9">
|
|
<TabsTrigger value="dynamic" className="data-[state=active]:text-primary">
|
|
短效动态
|
|
</TabsTrigger>
|
|
<TabsTrigger value="static" className="data-[state=active]:text-primary">
|
|
长效动态
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
<Form<FormValues> className={merge(`flex items-end gap-4 flex-wrap`)} handler={handler} form={form} >
|
|
<FormField name="resource_no" label={<span className="text-sm">套餐编号</span>}>
|
|
{({field}) => (
|
|
<Input {...field} className="h-9"/>
|
|
)}
|
|
</FormField>
|
|
<div className="flex flex-col gap-2">
|
|
<Label className="text-sm">时间范围筛选</Label>
|
|
<div className="flex items-center">
|
|
<FormField name="create_after">
|
|
{({field}) => (
|
|
<DatePicker
|
|
{...field}
|
|
className="w-36"
|
|
placeholder="开始时间"
|
|
format="yyyy-MM-dd"
|
|
/>
|
|
)}
|
|
</FormField>
|
|
<span className="px-1">-</span>
|
|
<FormField name="create_before">
|
|
{({field}) => (
|
|
<DatePicker
|
|
{...field}
|
|
className="w-36"
|
|
placeholder="结束时间"
|
|
format="yyyy-MM-dd"
|
|
/>
|
|
)}
|
|
</FormField>
|
|
</div>
|
|
</div>
|
|
<div className="flex gap-4">
|
|
<Button className="h-9" type="submit">
|
|
<span>查询</span>
|
|
</Button>
|
|
</div>
|
|
</Form>
|
|
<TabsContent value="dynamic" className="overflow-hidden">
|
|
{submittedData && <DashboardChart data={submittedData}/>}
|
|
</TabsContent>
|
|
<TabsContent value="static" className="flex flex-col items-center justify-center gap-2">
|
|
<Image alt="coming soon" src={soon}/>
|
|
<p>敬请期待</p>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
const config = {
|
|
count: {
|
|
label: `套餐使用量`,
|
|
color: `var(--color-primary)`,
|
|
},
|
|
} satisfies ChartConfig
|
|
|
|
type DashboardChartProps = {
|
|
data: ExtraResp<typeof statisticsResourceUsage>
|
|
}
|
|
|
|
function DashboardChart(props: DashboardChartProps) {
|
|
const sortedData = [...props.data].sort((a, b) => {
|
|
return new Date(a.date).getTime() - new Date(b.date).getTime()
|
|
})
|
|
|
|
const chartData = sortedData.map((item) => {
|
|
const date = new Date(item.date.split('T')[0])
|
|
return {
|
|
...item,
|
|
formattedTime: format(date, 'MM-dd'),
|
|
fullDate: format(date, 'yyyy-MM-dd'),
|
|
}
|
|
})
|
|
return (
|
|
<ChartContainer config={config} className="w-full h-full">
|
|
<AreaChart data={chartData} margin={{top: 0, right: 20, left: 0, bottom: 0}}>
|
|
<CartesianGrid vertical={false}/>
|
|
<XAxis
|
|
dataKey="formattedTime"
|
|
tickLine={false}
|
|
/>
|
|
<YAxis tickLine={false}/>
|
|
<Tooltip
|
|
animationDuration={100}
|
|
labelFormatter={value => `日期: ${chartData.find(item => item.formattedTime === value)?.fullDate || value}`}
|
|
formatter={value => [`${value}`, '套餐使用量']}
|
|
/>
|
|
<Area
|
|
type="monotone"
|
|
dataKey="count"
|
|
stroke="#8884d8"
|
|
fill="#8884d8"
|
|
fillOpacity={0.2}
|
|
strokeWidth={2}
|
|
name="套餐使用量"
|
|
/>
|
|
<Legend/>
|
|
</AreaChart>
|
|
</ChartContainer>
|
|
)
|
|
}
|