feat: enhance user profile with detailed tags and asset evaluation
Optimize user detail page for asset assessment and tag info. #VERCEL_SKIP Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { CardDescription } from "@/components/ui/card"
|
||||
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
@@ -27,6 +30,18 @@ import {
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"
|
||||
|
||||
type U = {
|
||||
id: string
|
||||
city: string
|
||||
store: string
|
||||
privateDomain: string
|
||||
project: string
|
||||
team: string
|
||||
rfmScore: number
|
||||
}
|
||||
|
||||
type R = { items: (U & { valueScore: number })[] }
|
||||
|
||||
// 使用通用公司名称和当天时间线的用户数据
|
||||
const mockUsers = [
|
||||
{
|
||||
@@ -162,6 +177,64 @@ export default function UserValuationPage() {
|
||||
analysis: true,
|
||||
upgradePaths: false,
|
||||
})
|
||||
const [data, setData] = useState<R | null>(null)
|
||||
|
||||
const city = useMemo(() => {
|
||||
const m = new Map<string, { count: number; value: number }>()
|
||||
mockUsers.forEach((i) => {
|
||||
const k = String(i.city)
|
||||
const cur = m.get(k) ?? { count: 0, value: 0 }
|
||||
cur.count += 1
|
||||
cur.value += i.rfmScore * 100
|
||||
m.set(k, cur)
|
||||
})
|
||||
return Array.from(m.entries()).sort((a, b) => b[1].value - a[1].value)
|
||||
}, [])
|
||||
|
||||
const store = useMemo(() => {
|
||||
const m = new Map<string, { count: number; value: number }>()
|
||||
mockUsers.forEach((i) => {
|
||||
const k = String(i.store)
|
||||
const cur = m.get(k) ?? { count: 0, value: 0 }
|
||||
cur.count += 1
|
||||
cur.value += i.rfmScore * 100
|
||||
m.set(k, cur)
|
||||
})
|
||||
return Array.from(m.entries()).sort((a, b) => b[1].value - a[1].value)
|
||||
}, [])
|
||||
|
||||
const project = useMemo(() => {
|
||||
const m = new Map<string, { count: number; value: number }>()
|
||||
mockUsers.forEach((i) => {
|
||||
const k = String(i.project)
|
||||
const cur = m.get(k) ?? { count: 0, value: 0 }
|
||||
cur.count += 1
|
||||
cur.value += i.rfmScore * 100
|
||||
m.set(k, cur)
|
||||
})
|
||||
return Array.from(m.entries()).sort((a, b) => b[1].value - a[1].value)
|
||||
}, [])
|
||||
|
||||
const team = useMemo(() => {
|
||||
const m = new Map<string, { count: number; value: number }>()
|
||||
mockUsers.forEach((i) => {
|
||||
const k = String(i.team)
|
||||
const cur = m.get(k) ?? { count: 0, value: 0 }
|
||||
cur.count += 1
|
||||
cur.value += i.rfmScore * 100
|
||||
m.set(k, cur)
|
||||
})
|
||||
return Array.from(m.entries()).sort((a, b) => b[1].value - a[1].value)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/users?pageSize=500")
|
||||
.then((r) => r.json())
|
||||
.then((res) => {
|
||||
const items = res.data.items.map((x: any) => ({ ...x, valueScore: x.rfmScore * 100 }))
|
||||
setData({ items })
|
||||
})
|
||||
}, [])
|
||||
|
||||
const filteredUsers = mockUsers.filter(
|
||||
(user) =>
|
||||
@@ -225,6 +298,20 @@ export default function UserValuationPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 用户资产估值 */}
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-xl">用户资产估值</CardTitle>
|
||||
<p className="text-sm text-muted-foreground">基于流量池维度进行价值分布</p>
|
||||
</CardHeader>
|
||||
<CardContent className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Section title="按城市分布" items={city} />
|
||||
<Section title="按门店分布" items={store} />
|
||||
<Section title="按项目分布" items={project} />
|
||||
<Section title="按团队分布" items={team} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 用户估值概览 */}
|
||||
<Collapsible
|
||||
open={expandedSections.overview}
|
||||
@@ -869,3 +956,23 @@ export default function UserValuationPage() {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Section({ title, items }: { title: string; items: [string, { count: number; value: number }][] }) {
|
||||
return (
|
||||
<div className="rounded-md border">
|
||||
<div className="px-3 py-2 border-b text-sm font-medium">{title}</div>
|
||||
<div className="p-3 space-y-2">
|
||||
{items.slice(0, 8).map(([k, v]) => (
|
||||
<div key={k} className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{k}</span>
|
||||
<Badge variant="secondary">{v.count} 人</Badge>
|
||||
</div>
|
||||
<div className="font-semibold">¥ {Math.round(v.value).toLocaleString("zh-CN")}</div>
|
||||
</div>
|
||||
))}
|
||||
{items.length === 0 && <div className="text-xs text-muted-foreground">暂无数据</div>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user