72 lines
5.9 KiB
TypeScript
72 lines
5.9 KiB
TypeScript
import { useQuery } from '@tanstack/react-query'
|
|
import { PageLoading, ErrorState } from '@/components/ui/feedback'
|
|
import { PageHeader } from '@/components/common/PageHeader'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { Card, CardHeader } from '@/components/ui/card'
|
|
import { StatCard } from '@/components/common/StatCard'
|
|
import { LiveDataBadge } from '@/components/common/LiveDataBadge'
|
|
import { CountryFlag } from '@/components/common/CountryFlag'
|
|
import { ChartCard, TrendChart, DonutChart, FunnelChart, RankBars, SimpleBarChart } from '@/components/charts'
|
|
import { getAnalytics } from '@/services/api/org'
|
|
import { formatNumber } from '@/utils/format'
|
|
import { Activity, BadgePercent, TrendingUp, Users, Zap, Timer, AlertTriangle, Target } from 'lucide-react'
|
|
|
|
export function AnalyticsPage() {
|
|
const { data, isLoading, isError, refetch } = useQuery({
|
|
queryKey: ['analytics'],
|
|
queryFn: () => getAnalytics(),
|
|
})
|
|
|
|
if (isLoading) return <PageLoading />
|
|
if (isError || !data) return <ErrorState onRetry={() => refetch()} />
|
|
|
|
const k = data.kpis
|
|
|
|
return (
|
|
<div className="p-4 md:p-6">
|
|
<PageHeader
|
|
title="数据分析中心"
|
|
description="企业经营数据全景 · 基于当前企业实时数据"
|
|
actions={<LiveDataBadge />}
|
|
extra={
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
{['today', '7d', '30d', 'quarter'].map((p) => (
|
|
<button key={p} className="rounded-full bg-muted px-3 py-1 text-xs text-muted-foreground hover:bg-border">{p === '30d' ? '近30天' : p === '7d' ? '近7天' : p === 'today' ? '今日' : '本季度'}</button>
|
|
))}
|
|
</div>
|
|
}
|
|
/>
|
|
|
|
<div className="grid grid-cols-2 gap-3 md:grid-cols-5 mb-4">
|
|
<StatCard title="新增客户" value={k.newCustomers.value} delta={k.newCustomers.delta} icon={Users} />
|
|
<StatCard title="有效询盘" value={k.validInquiries.value} delta={k.validInquiries.delta} icon={Activity} />
|
|
<StatCard title="高意向客户" value={k.highIntent.value} delta={k.highIntent.delta} icon={Target} />
|
|
<StatCard title="转化率" value={`${k.conversionRate.value}%`} delta={k.conversionRate.delta} icon={BadgePercent} />
|
|
<StatCard title="AI采纳率" value={`${k.aiAdoptionRate.value}%`} delta={k.aiAdoptionRate.delta} icon={Zap} />
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
|
|
<ChartCard title="客户增长趋势" height={260}><TrendChart data={data.customerGrowth} series={[{ key: 'count', name: '新增客户', color: '#2563eb' }]} /></ChartCard>
|
|
<ChartCard title="各渠道询盘数量" height={260}><SimpleBarChart data={data.channelInquiries} xKey="name" barKey="count" name="询盘数" /></ChartCard>
|
|
<ChartCard title="客户国家/地区分布" height={260}><DonutChart data={data.countryDistribution} /></ChartCard>
|
|
<ChartCard title="客户阶段转化漏斗" height={260}><FunnelChart data={data.funnel} /></ChartCard>
|
|
<ChartCard title="意向分数分布" height={260}><DonutChart data={data.intentDistribution} nameKey="range" /></ChartCard>
|
|
<ChartCard title="产品关注度排行" height={260}><RankBars data={data.productRank.map((p) => ({ name: p.product, value: p.inquiries }))} /></ChartCard>
|
|
<ChartCard title="团队回复效率排行" height={240}><RankBars data={data.teamReplyRank.map((r) => ({ name: r.name, value: r.avgResponseMin }))} unit="分钟" max={30} /></ChartCard>
|
|
<ChartCard title="团队客户转化排行" height={240}><RankBars data={data.teamConversionRank.map((r) => ({ name: r.name, value: r.rate }))} unit="%" /></ChartCard>
|
|
</div>
|
|
|
|
{/* 老板看板 */}
|
|
<h2 className="mt-6 mb-3 text-lg font-semibold">老板看板 <LiveDataBadge className="align-middle" /></h2>
|
|
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
|
|
<Card><CardHeader title="🔥 今日需处理的高价值客户" /><div className="divide-y divide-border">{data.bossBoard.urgentHighValue.map((c) => (<div key={c.id} className="flex items-center justify-between px-4 py-2.5 text-sm"><span>{c.name} <CountryFlag code={c.country} withName={false} /></span><span className="text-xs text-muted-foreground">{c.reason.slice(0, 20)}…</span></div>))}</div></Card>
|
|
<Card><CardHeader title="⚠️ 存在逾期跟进的业务员" /><div className="divide-y divide-border">{data.bossBoard.overdueMembers.map((m) => (<div key={m.id} className="flex items-center justify-between px-4 py-2.5 text-sm"><span>{m.name}</span><Badge variant="danger">{m.overdueCount} 项逾期</Badge></div>))}</div></Card>
|
|
<Card><CardHeader title="📈 客户增长最快的国家" /><div className="divide-y divide-border">{data.bossBoard.fastestCountries.map((c) => (<div key={c.country} className="flex items-center justify-between px-4 py-2.5 text-sm"><CountryFlag code={c.country} /><Badge variant="success">+{(c.growth * 100).toFixed(0)}%</Badge></div>))}</div></Card>
|
|
<Card><CardHeader title="🏅 最优渠道" /><div className="p-4 text-center"><p className="text-2xl font-semibold text-primary-700">{data.bossBoard.bestChannel.name}</p><p className="text-sm text-muted-foreground">转化率 {data.bossBoard.bestChannel.conversion}%</p></div></Card>
|
|
<Card><CardHeader title="📊 最热门产品" /><div className="space-y-2 p-4">{data.bossBoard.topProducts.map((p) => (<div key={p.product} className="flex items-center justify-between text-sm"><span>{p.product}</span><span className="text-muted-foreground">{p.inquiries} 次询盘</span></div>))}</div></Card>
|
|
<Card><CardHeader title="🚨 正在流失的高意向客户" /><div className="divide-y divide-border">{data.bossBoard.churningHighIntent.map((c) => (<div key={c.id} className="px-4 py-2.5 text-sm"><span>{c.name} <CountryFlag code={c.country} withName={false} /></span><span className="text-xs text-muted-foreground ml-2">{c.daysSilent}天未联系 · 意向{c.intentScore}分</span></div>))}</div></Card>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|