Files
users/components/data-integration/data-quality-monitor.tsx
v0 2408d50cb0 refactor: overhaul UI for streamlined user experience
Redesign navigation, home overview, user portrait, and valuation pages
with improved functionality and responsive design.

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
2025-07-18 13:47:12 +00:00

988 lines
41 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 { Textarea } from "@/components/ui/textarea"
import { useState } from "react"
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Switch } from "@/components/ui/switch"
import { Badge } from "@/components/ui/badge"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Separator } from "@/components/ui/separator"
import { AlertTriangle, CheckCircle, Bell, RefreshCw, Filter, Download, Search, Clock } from "lucide-react"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
interface DataQualityIssue {
id: string
type: string
description: string
affectedRecords: number
affectedFields: string[]
severity: "high" | "medium" | "low"
status: "open" | "in_progress" | "resolved"
createdAt: string
dataSource: string
}
interface DataQualityRule {
id: string
name: string
description: string
type: string
enabled: boolean
severity: "high" | "medium" | "low"
dataSource: string
lastRun: string
issuesDetected: number
}
export function DataQualityMonitor() {
const [activeTab, setActiveTab] = useState("issues")
const [selectedIssueId, setSelectedIssueId] = useState<string | null>(null)
const [isAddingRule, setIsAddingRule] = useState(false)
const [filterSeverity, setFilterSeverity] = useState<string>("all")
const [filterStatus, setFilterStatus] = useState<string>("all")
// 模拟数据质量问题
const [dataQualityIssues, setDataQualityIssues] = useState<DataQualityIssue[]>([
{
id: "issue-1",
type: "缺失值",
description: "用户表中存在大量手机号字段为空的记录",
affectedRecords: 1250,
affectedFields: ["phoneNumber"],
severity: "high",
status: "open",
createdAt: "2023-07-20 10:15",
dataSource: "CRM系统",
},
{
id: "issue-2",
type: "格式错误",
description: "部分用户的身份证号格式不正确",
affectedRecords: 458,
affectedFields: ["identityNumber"],
severity: "medium",
status: "in_progress",
createdAt: "2023-07-19 15:30",
dataSource: "用户数据库",
},
{
id: "issue-3",
type: "重复数据",
description: "存在多条使用相同手机号的用户记录",
affectedRecords: 789,
affectedFields: ["userId", "phoneNumber"],
severity: "medium",
status: "open",
createdAt: "2023-07-18 09:45",
dataSource: "交易系统",
},
{
id: "issue-4",
type: "异常值",
description: "部分用户的年龄字段值异常超过150岁",
affectedRecords: 23,
affectedFields: ["age"],
severity: "low",
status: "resolved",
createdAt: "2023-07-17 14:20",
dataSource: "用户数据库",
},
{
id: "issue-5",
type: "不一致数据",
description: "用户在不同系统中的姓名不一致",
affectedRecords: 567,
affectedFields: ["fullName"],
severity: "high",
status: "open",
createdAt: "2023-07-16 11:05",
dataSource: "多系统",
},
])
// 模拟数据质量规则
const [dataQualityRules, setDataQualityRules] = useState<DataQualityRule[]>([
{
id: "rule-1",
name: "手机号格式检查",
description: "检查手机号是否符合中国大陆手机号格式",
type: "格式验证",
enabled: true,
severity: "high",
dataSource: "所有数据源",
lastRun: "2023-07-20 00:00",
issuesDetected: 458,
},
{
id: "rule-2",
name: "身份证号验证",
description: "验证身份证号的格式和校验码",
type: "格式验证",
enabled: true,
severity: "high",
dataSource: "所有数据源",
lastRun: "2023-07-20 00:00",
issuesDetected: 789,
},
{
id: "rule-3",
name: "必填字段检查",
description: "检查关键字段是否有值",
type: "完整性检查",
enabled: true,
severity: "medium",
dataSource: "所有数据源",
lastRun: "2023-07-20 00:00",
issuesDetected: 1250,
},
{
id: "rule-4",
name: "重复数据检测",
description: "检测并标记重复的用户记录",
type: "唯一性检查",
enabled: true,
severity: "medium",
dataSource: "所有数据源",
lastRun: "2023-07-20 00:00",
issuesDetected: 567,
},
{
id: "rule-5",
name: "数据一致性检查",
description: "检查用户在不同系统中的数据是否一致",
type: "一致性检查",
enabled: true,
severity: "high",
dataSource: "所有数据源",
lastRun: "2023-07-20 00:00",
issuesDetected: 567,
},
])
// 模拟数据质量统计
const qualityStats = {
totalIssues: 3087,
openIssues: 2606,
resolvedIssues: 481,
highSeverity: 1245,
mediumSeverity: 1356,
lowSeverity: 486,
dataQualityScore: 87,
completenessScore: 92,
accuracyScore: 85,
consistencyScore: 88,
validityScore: 84,
}
const getSeverityBadge = (severity: string) => {
switch (severity) {
case "high":
return <Badge className="bg-red-100 text-red-800"></Badge>
case "medium":
return <Badge className="bg-yellow-100 text-yellow-800"></Badge>
case "low":
return <Badge className="bg-green-100 text-green-800"></Badge>
default:
return <Badge></Badge>
}
}
const getStatusBadge = (status: string) => {
switch (status) {
case "open":
return <Badge className="bg-red-100 text-red-800"></Badge>
case "in_progress":
return <Badge className="bg-yellow-100 text-yellow-800"></Badge>
case "resolved":
return <Badge className="bg-green-100 text-green-800"></Badge>
default:
return <Badge></Badge>
}
}
const getStatusIcon = (status: string) => {
switch (status) {
case "open":
return <AlertTriangle className="h-5 w-5 text-red-500" />
case "in_progress":
return <RefreshCw className="h-5 w-5 text-yellow-500" />
case "resolved":
return <CheckCircle className="h-5 w-5 text-green-500" />
default:
return <AlertTriangle className="h-5 w-5" />
}
}
const filteredIssues = dataQualityIssues.filter((issue) => {
if (filterSeverity !== "all" && issue.severity !== filterSeverity) return false
if (filterStatus !== "all" && issue.status !== filterStatus) return false
return true
})
const selectedIssue = selectedIssueId ? dataQualityIssues.find((issue) => issue.id === selectedIssueId) : null
const updateIssueStatus = (id: string, status: "open" | "in_progress" | "resolved") => {
setDataQualityIssues(
dataQualityIssues.map((issue) => {
if (issue.id === id) {
return { ...issue, status }
}
return issue
}),
)
}
const toggleRuleStatus = (id: string) => {
setDataQualityRules(
dataQualityRules.map((rule) => {
if (rule.id === id) {
return { ...rule, enabled: !rule.enabled }
}
return rule
}),
)
}
return (
<div className="space-y-4">
<div className="flex justify-between items-center">
<h2 className="text-2xl font-bold"></h2>
<div className="flex items-center gap-2">
<Button variant="outline">
<Bell className="mr-2 h-4 w-4" />
</Button>
<Button>
<RefreshCw className="mr-2 h-4 w-4" />
</Button>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<Card>
<CardContent className="p-6">
<div className="flex flex-col space-y-2">
<p className="text-sm text-muted-foreground"></p>
<div className="flex items-end justify-between">
<h3 className="text-2xl font-bold">{qualityStats.dataQualityScore}/100</h3>
</div>
<div className="w-full bg-gray-200 rounded-full h-2.5">
<div
className="bg-green-600 h-2.5 rounded-full"
style={{ width: `${qualityStats.dataQualityScore}%` }}
></div>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex flex-col space-y-2">
<p className="text-sm text-muted-foreground"></p>
<div className="flex items-center justify-between">
<h3 className="text-2xl font-bold">{qualityStats.openIssues}</h3>
<AlertTriangle className="h-5 w-5 text-red-500" />
</div>
<p className="text-xs text-muted-foreground">: {qualityStats.highSeverity} </p>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex flex-col space-y-2">
<p className="text-sm text-muted-foreground"></p>
<div className="flex items-center justify-between">
<h3 className="text-2xl font-bold">
{dataQualityIssues.filter((issue) => issue.status === "in_progress").length}
</h3>
<RefreshCw className="h-5 w-5 text-yellow-500" />
</div>
<p className="text-xs text-muted-foreground"></p>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="p-6">
<div className="flex flex-col space-y-2">
<p className="text-sm text-muted-foreground"></p>
<div className="flex items-center justify-between">
<h3 className="text-2xl font-bold">{qualityStats.resolvedIssues}</h3>
<CheckCircle className="h-5 w-5 text-green-500" />
</div>
<p className="text-xs text-muted-foreground">
: {((qualityStats.resolvedIssues / qualityStats.totalIssues) * 100).toFixed(1)}%
</p>
</div>
</CardContent>
</Card>
</div>
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-4">
<TabsList>
<TabsTrigger value="issues"></TabsTrigger>
<TabsTrigger value="rules"></TabsTrigger>
<TabsTrigger value="metrics"></TabsTrigger>
<TabsTrigger value="reports"></TabsTrigger>
</TabsList>
<TabsContent value="issues" className="space-y-4">
<Card>
<CardHeader>
<div className="flex justify-between items-center">
<CardTitle></CardTitle>
<div className="flex items-center gap-2">
<div className="relative">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input type="text" placeholder="搜索问题..." className="pl-8 w-[200px]" />
</div>
<Select value={filterSeverity} onValueChange={setFilterSeverity}>
<SelectTrigger className="w-[130px]">
<Filter className="mr-2 h-4 w-4" />
<SelectValue placeholder="严重性" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="high"></SelectItem>
<SelectItem value="medium"></SelectItem>
<SelectItem value="low"></SelectItem>
</SelectContent>
</Select>
<Select value={filterStatus} onValueChange={setFilterStatus}>
<SelectTrigger className="w-[130px]">
<Filter className="mr-2 h-4 w-4" />
<SelectValue placeholder="状态" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="open"></SelectItem>
<SelectItem value="in_progress"></SelectItem>
<SelectItem value="resolved"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
</CardHeader>
<CardContent>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[50px]"></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredIssues.map((issue) => (
<TableRow key={issue.id}>
<TableCell>{getStatusIcon(issue.status)}</TableCell>
<TableCell className="font-medium">{issue.type}</TableCell>
<TableCell>{issue.description}</TableCell>
<TableCell>{issue.affectedRecords.toLocaleString()}</TableCell>
<TableCell>{getSeverityBadge(issue.severity)}</TableCell>
<TableCell>{issue.dataSource}</TableCell>
<TableCell>{issue.createdAt}</TableCell>
<TableCell className="text-right">
<Button variant="ghost" size="sm" onClick={() => setSelectedIssueId(issue.id)}>
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="rules" className="space-y-4">
<Card>
<CardHeader>
<div className="flex justify-between items-center">
<CardTitle></CardTitle>
<Button onClick={() => setIsAddingRule(true)}></Button>
</div>
</CardHeader>
<CardContent>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{dataQualityRules.map((rule) => (
<TableRow key={rule.id}>
<TableCell className="font-medium">{rule.name}</TableCell>
<TableCell>{rule.type}</TableCell>
<TableCell>{getSeverityBadge(rule.severity)}</TableCell>
<TableCell>{rule.dataSource}</TableCell>
<TableCell>{rule.lastRun}</TableCell>
<TableCell>{rule.issuesDetected.toLocaleString()}</TableCell>
<TableCell>
<div className="flex items-center space-x-2">
<Switch
checked={rule.enabled}
onCheckedChange={() => toggleRuleStatus(rule.id)}
aria-label="Toggle rule"
/>
<span>{rule.enabled ? "启用" : "禁用"}</span>
</div>
</TableCell>
<TableCell className="text-right">
<div className="flex justify-end space-x-2">
<Button variant="ghost" size="sm">
</Button>
<Button variant="ghost" size="sm">
</Button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="metrics" className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex justify-between items-center">
<span className="font-medium"></span>
<span className="text-green-600 font-medium">{qualityStats.completenessScore}/100</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2.5">
<div
className="bg-green-600 h-2.5 rounded-full"
style={{ width: `${qualityStats.completenessScore}%` }}
></div>
</div>
<div className="space-y-2 mt-4">
<h4 className="text-sm font-medium"></h4>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm">ID</span>
<span className="text-sm">100%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-1.5">
<div className="bg-green-600 h-1.5 rounded-full" style={{ width: "100%" }}></div>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm">87%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-1.5">
<div className="bg-green-600 h-1.5 rounded-full" style={{ width: "87%" }}></div>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm">72%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-1.5">
<div className="bg-yellow-600 h-1.5 rounded-full" style={{ width: "72%" }}></div>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm">IMEI设备号</span>
<span className="text-sm">68%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-1.5">
<div className="bg-yellow-600 h-1.5 rounded-full" style={{ width: "68%" }}></div>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex justify-between items-center">
<span className="font-medium"></span>
<span className="text-green-600 font-medium">{qualityStats.accuracyScore}/100</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2.5">
<div
className="bg-green-600 h-2.5 rounded-full"
style={{ width: `${qualityStats.accuracyScore}%` }}
></div>
</div>
<div className="space-y-2 mt-4">
<h4 className="text-sm font-medium"></h4>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm">92%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-1.5">
<div className="bg-green-600 h-1.5 rounded-full" style={{ width: "92%" }}></div>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm">85%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-1.5">
<div className="bg-green-600 h-1.5 rounded-full" style={{ width: "85%" }}></div>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm">95%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-1.5">
<div className="bg-green-600 h-1.5 rounded-full" style={{ width: "95%" }}></div>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm">78%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-1.5">
<div className="bg-yellow-600 h-1.5 rounded-full" style={{ width: "78%" }}></div>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex justify-between items-center">
<span className="font-medium"></span>
<span className="text-green-600 font-medium">{qualityStats.consistencyScore}/100</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2.5">
<div
className="bg-green-600 h-2.5 rounded-full"
style={{ width: `${qualityStats.consistencyScore}%` }}
></div>
</div>
<div className="space-y-2 mt-4">
<h4 className="text-sm font-medium"></h4>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm">CRM与交易系统</span>
<span className="text-sm">90%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-1.5">
<div className="bg-green-600 h-1.5 rounded-full" style={{ width: "90%" }}></div>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm">85%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-1.5">
<div className="bg-green-600 h-1.5 rounded-full" style={{ width: "85%" }}></div>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm">78%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-1.5">
<div className="bg-yellow-600 h-1.5 rounded-full" style={{ width: "78%" }}></div>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div className="flex justify-between items-center">
<span className="font-medium"></span>
<span className="text-green-600 font-medium">{qualityStats.validityScore}/100</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2.5">
<div
className="bg-green-600 h-2.5 rounded-full"
style={{ width: `${qualityStats.validityScore}%` }}
></div>
</div>
<div className="space-y-2 mt-4">
<h4 className="text-sm font-medium"></h4>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm">92%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-1.5">
<div className="bg-green-600 h-1.5 rounded-full" style={{ width: "92%" }}></div>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm">88%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-1.5">
<div className="bg-green-600 h-1.5 rounded-full" style={{ width: "88%" }}></div>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between items-center">
<span className="text-sm"></span>
<span className="text-sm">75%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-1.5">
<div className="bg-yellow-600 h-1.5 rounded-full" style={{ width: "75%" }}></div>
</div>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
</TabsContent>
<TabsContent value="reports" className="space-y-4">
<Card>
<CardHeader>
<div className="flex justify-between items-center">
<CardTitle></CardTitle>
<div className="flex items-center gap-2">
<Select defaultValue="weekly">
<SelectTrigger className="w-[150px]">
<Clock className="mr-2 h-4 w-4" />
<SelectValue placeholder="报告频率" />
</SelectTrigger>
<SelectContent>
<SelectItem value="daily"></SelectItem>
<SelectItem value="weekly"></SelectItem>
<SelectItem value="monthly"></SelectItem>
</SelectContent>
</Select>
<Button variant="outline">
<Download className="mr-2 h-4 w-4" />
</Button>
</div>
</div>
</CardHeader>
<CardContent>
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium"></TableCell>
<TableCell>2023-07-21</TableCell>
<TableCell>87/100</TableCell>
<TableCell>156</TableCell>
<TableCell>23</TableCell>
<TableCell className="text-right">
<Button variant="ghost" size="sm">
</Button>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium"></TableCell>
<TableCell>2023-07-14</TableCell>
<TableCell>85/100</TableCell>
<TableCell>178</TableCell>
<TableCell>31</TableCell>
<TableCell className="text-right">
<Button variant="ghost" size="sm">
</Button>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium"></TableCell>
<TableCell>2023-07-07</TableCell>
<TableCell>82/100</TableCell>
<TableCell>203</TableCell>
<TableCell>45</TableCell>
<TableCell className="text-right">
<Button variant="ghost" size="sm">
</Button>
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium"></TableCell>
<TableCell>2023-06-30</TableCell>
<TableCell>80/100</TableCell>
<TableCell>245</TableCell>
<TableCell>52</TableCell>
<TableCell className="text-right">
<Button variant="ghost" size="sm">
</Button>
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
{/* 问题详情对话框 */}
<Dialog open={!!selectedIssueId} onOpenChange={(open) => !open && setSelectedIssueId(null)}>
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
{selectedIssue && (
<div className="space-y-4 py-4">
<div className="flex items-center gap-4">
{getStatusIcon(selectedIssue.status)}
<div>
<h3 className="font-medium text-lg">{selectedIssue.type}</h3>
<p className="text-sm text-muted-foreground">{selectedIssue.description}</p>
</div>
</div>
<Separator />
<div className="grid grid-cols-2 gap-4">
<div>
<Label className="text-sm text-muted-foreground"></Label>
<p className="font-medium">{getSeverityBadge(selectedIssue.severity)}</p>
</div>
<div>
<Label className="text-sm text-muted-foreground"></Label>
<p className="font-medium">{getStatusBadge(selectedIssue.status)}</p>
</div>
<div>
<Label className="text-sm text-muted-foreground"></Label>
<p className="font-medium">{selectedIssue.dataSource}</p>
</div>
<div>
<Label className="text-sm text-muted-foreground"></Label>
<p className="font-medium">{selectedIssue.createdAt}</p>
</div>
<div>
<Label className="text-sm text-muted-foreground"></Label>
<p className="font-medium">{selectedIssue.affectedRecords.toLocaleString()}</p>
</div>
</div>
<div>
<Label className="text-sm text-muted-foreground"></Label>
<div className="flex flex-wrap gap-2 mt-1">
{selectedIssue.affectedFields.map((field, index) => (
<Badge key={index} variant="outline">
{field}
</Badge>
))}
</div>
</div>
<Separator />
<div className="space-y-2">
<Label></Label>
<Select
value={selectedIssue.status}
onValueChange={(value) =>
updateIssueStatus(selectedIssue.id, value as "open" | "in_progress" | "resolved")
}
>
<SelectTrigger>
<SelectValue placeholder="选择状态" />
</SelectTrigger>
<SelectContent>
<SelectItem value="open"></SelectItem>
<SelectItem value="in_progress"></SelectItem>
<SelectItem value="resolved"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label></Label>
<Textarea placeholder="输入处理备注..." rows={3} />
</div>
</div>
)}
<DialogFooter>
<Button variant="outline" onClick={() => setSelectedIssueId(null)}>
</Button>
<Button onClick={() => setSelectedIssueId(null)}></Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* 添加规则对话框 */}
<Dialog open={isAddingRule} onOpenChange={setIsAddingRule}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="rule-name" className="text-right">
</Label>
<Input id="rule-name" className="col-span-3" placeholder="例如:手机号格式检查" />
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="rule-type" className="text-right">
</Label>
<Select>
<SelectTrigger id="rule-type" className="col-span-3">
<SelectValue placeholder="选择规则类型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="format"></SelectItem>
<SelectItem value="completeness"></SelectItem>
<SelectItem value="uniqueness"></SelectItem>
<SelectItem value="consistency"></SelectItem>
<SelectItem value="range"></SelectItem>
<SelectItem value="custom"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="rule-severity" className="text-right">
</Label>
<Select>
<SelectTrigger id="rule-severity" className="col-span-3">
<SelectValue placeholder="选择严重性" />
</SelectTrigger>
<SelectContent>
<SelectItem value="high"></SelectItem>
<SelectItem value="medium"></SelectItem>
<SelectItem value="low"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="rule-datasource" className="text-right">
</Label>
<Select>
<SelectTrigger id="rule-datasource" className="col-span-3">
<SelectValue placeholder="选择数据源" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="crm">CRM系统</SelectItem>
<SelectItem value="transaction"></SelectItem>
<SelectItem value="user-db"></SelectItem>
<SelectItem value="behavior"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-4 items-start gap-4">
<Label htmlFor="rule-description" className="text-right pt-2">
</Label>
<Textarea
id="rule-description"
className="col-span-3"
placeholder="描述规则的用途和检查内容..."
rows={3}
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<div className="text-right">
<Label htmlFor="rule-enabled"></Label>
</div>
<div className="flex items-center space-x-2 col-span-3">
<Switch id="rule-enabled" defaultChecked />
<Label htmlFor="rule-enabled"></Label>
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsAddingRule(false)}>
</Button>
<Button onClick={() => setIsAddingRule(false)}></Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
)
}