Files
users/app/data-governance/quality/page.tsx
v0 b17b488f8e refactor: restructure navigation and module layout
Reorganize navigation and module structure based on new requirements.

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
2026-01-31 04:32:36 +00:00

266 lines
10 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 { useState } from "react"
import { Shield, TrendingUp, TrendingDown, RefreshCw, Download } from "lucide-react"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Progress } from "@/components/ui/progress"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
interface QualityMetric {
name: string
score: number
trend: "up" | "down" | "stable"
change: number
description: string
}
interface TableQuality {
tableName: string
completeness: number
uniqueness: number
timeliness: number
accuracy: number
overallScore: number
issues: number
lastCheck: string
}
const QUALITY_METRICS: QualityMetric[] = [
{ name: "完整性", score: 96.8, trend: "up", change: 0.5, description: "字段非空率" },
{ name: "唯一性", score: 99.2, trend: "stable", change: 0, description: "主键重复率" },
{ name: "及时性", score: 94.5, trend: "down", change: -1.2, description: "数据更新延迟" },
{ name: "准确性", score: 97.3, trend: "up", change: 0.8, description: "格式校验通过率" },
]
const TABLE_QUALITY: TableQuality[] = [
{
tableName: "users",
completeness: 98.5,
uniqueness: 100,
timeliness: 96.2,
accuracy: 99.1,
overallScore: 98.5,
issues: 2,
lastCheck: "2025-12-12 14:30:00",
},
{
tableName: "transactions",
completeness: 99.8,
uniqueness: 99.9,
timeliness: 98.5,
accuracy: 99.5,
overallScore: 99.4,
issues: 0,
lastCheck: "2025-12-12 14:30:00",
},
{
tableName: "user_behaviors",
completeness: 92.3,
uniqueness: 98.5,
timeliness: 89.2,
accuracy: 95.6,
overallScore: 93.9,
issues: 8,
lastCheck: "2025-12-12 14:25:00",
},
{
tableName: "accounts",
completeness: 97.2,
uniqueness: 100,
timeliness: 95.8,
accuracy: 98.3,
overallScore: 97.8,
issues: 1,
lastCheck: "2025-12-12 14:30:00",
},
{
tableName: "tags",
completeness: 100,
uniqueness: 100,
timeliness: 100,
accuracy: 100,
overallScore: 100,
issues: 0,
lastCheck: "2025-12-12 14:30:00",
},
]
export default function DataQualityPage() {
const [selectedPeriod, setSelectedPeriod] = useState("today")
const [isRefreshing, setIsRefreshing] = useState(false)
const [overallScore, setOverallScore] = useState(96.95)
const getScoreColor = (score: number) => {
if (score >= 95) return "text-green-600"
if (score >= 80) return "text-yellow-600"
return "text-red-600"
}
const getScoreBg = (score: number) => {
if (score >= 95) return "bg-green-100"
if (score >= 80) return "bg-yellow-100"
return "bg-red-100"
}
const refreshData = async () => {
setIsRefreshing(true)
await new Promise((r) => setTimeout(r, 1500))
setOverallScore((prev) => prev + (Math.random() - 0.5) * 0.5)
setIsRefreshing(false)
}
return (
<div className="space-y-6 p-6">
{/* Header */}
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-bold text-gray-900"></h1>
<p className="text-gray-500 mt-1"></p>
</div>
<div className="flex items-center gap-3">
<Select value={selectedPeriod} onValueChange={setSelectedPeriod}>
<SelectTrigger className="w-32 bg-white/60">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="today"></SelectItem>
<SelectItem value="week"></SelectItem>
<SelectItem value="month"></SelectItem>
</SelectContent>
</Select>
<Button variant="outline" onClick={refreshData} disabled={isRefreshing}>
<RefreshCw className={`w-4 h-4 mr-2 ${isRefreshing ? "animate-spin" : ""}`} />
</Button>
<Button variant="outline">
<Download className="w-4 h-4 mr-2" />
</Button>
</div>
</div>
{/* Overall Score */}
<Card className="border-none shadow-lg bg-gradient-to-br from-green-500 to-emerald-600 text-white">
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<div className="flex items-center gap-2 text-green-100 mb-2">
<Shield className="w-5 h-5" />
<span></span>
</div>
<div className="text-6xl font-bold">{overallScore.toFixed(1)}</div>
<p className="text-green-100 mt-2">100</p>
</div>
<div className="hidden md:block">
<div className="w-32 h-32 rounded-full border-8 border-white/20 flex items-center justify-center">
<div className="text-center">
<div className="text-2xl font-bold">A+</div>
<div className="text-xs text-green-100"></div>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
{/* Quality Metrics */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
{QUALITY_METRICS.map((metric, i) => (
<Card key={i} className="border-none shadow-sm bg-white/60 backdrop-blur-sm">
<CardContent className="p-4">
<div className="flex items-center justify-between mb-2">
<span className="text-sm text-gray-500">{metric.name}</span>
<div className="flex items-center gap-1">
{metric.trend === "up" ? (
<TrendingUp className="w-4 h-4 text-green-500" />
) : metric.trend === "down" ? (
<TrendingDown className="w-4 h-4 text-red-500" />
) : null}
<span
className={`text-xs ${metric.change > 0 ? "text-green-600" : metric.change < 0 ? "text-red-600" : "text-gray-500"}`}
>
{metric.change > 0 ? "+" : ""}
{metric.change}%
</span>
</div>
</div>
<div className={`text-3xl font-bold ${getScoreColor(metric.score)}`}>{metric.score}%</div>
<p className="text-xs text-gray-400 mt-1">{metric.description}</p>
<Progress value={metric.score} className="h-2 mt-2" />
</CardContent>
</Card>
))}
</div>
{/* Table Quality Details */}
<Card className="border-none shadow-sm bg-white/60 backdrop-blur-sm">
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
</CardHeader>
<CardContent>
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-gray-200">
<th className="text-left py-3 px-4 text-sm font-medium text-gray-500"></th>
<th className="text-center py-3 px-4 text-sm font-medium text-gray-500"></th>
<th className="text-center py-3 px-4 text-sm font-medium text-gray-500"></th>
<th className="text-center py-3 px-4 text-sm font-medium text-gray-500"></th>
<th className="text-center py-3 px-4 text-sm font-medium text-gray-500"></th>
<th className="text-center py-3 px-4 text-sm font-medium text-gray-500"></th>
<th className="text-center py-3 px-4 text-sm font-medium text-gray-500"></th>
<th className="text-right py-3 px-4 text-sm font-medium text-gray-500"></th>
</tr>
</thead>
<tbody>
{TABLE_QUALITY.map((table, i) => (
<tr key={i} className="border-b border-gray-100 hover:bg-gray-50">
<td className="py-3 px-4">
<code className="text-sm font-medium text-gray-900">{table.tableName}</code>
</td>
<td className="py-3 px-4 text-center">
<span className={`text-sm font-medium ${getScoreColor(table.completeness)}`}>
{table.completeness}%
</span>
</td>
<td className="py-3 px-4 text-center">
<span className={`text-sm font-medium ${getScoreColor(table.uniqueness)}`}>
{table.uniqueness}%
</span>
</td>
<td className="py-3 px-4 text-center">
<span className={`text-sm font-medium ${getScoreColor(table.timeliness)}`}>
{table.timeliness}%
</span>
</td>
<td className="py-3 px-4 text-center">
<span className={`text-sm font-medium ${getScoreColor(table.accuracy)}`}>{table.accuracy}%</span>
</td>
<td className="py-3 px-4 text-center">
<Badge className={`${getScoreBg(table.overallScore)} ${getScoreColor(table.overallScore)}`}>
{table.overallScore}
</Badge>
</td>
<td className="py-3 px-4 text-center">
{table.issues > 0 ? (
<Badge variant="destructive">{table.issues}</Badge>
) : (
<Badge variant="secondary" className="bg-green-100 text-green-700">
0
</Badge>
)}
</td>
<td className="py-3 px-4 text-right text-xs text-gray-500">{table.lastCheck}</td>
</tr>
))}
</tbody>
</table>
</div>
</CardContent>
</Card>
</div>
)
}