'use client'; import { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { Switch } from '@/components/ui/switch'; import { useUser } from '@/components/user-context'; import { DIRECTION_LABELS } from '@/lib/constants'; import type { Metric } from '@/types/database'; import { toast } from 'sonner'; import { Plus, Pencil, Trash2, Gauge, Loader2, TrendingUp, TrendingDown } from 'lucide-react'; import { getErrorMessage } from '@/lib/error-utils'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { fetchApi } from '@/lib/api-client'; const metricSchema = z.object({ name: z.string().min(1, '名称不能为空'), unit: z.string().min(1, '单位不能为空'), direction: z.enum(['increase', 'decrease']), category: z.string().optional(), }); type MetricFormValues = z.infer; export default function AdminMetricsPage() { const { currentUser } = useUser(); const [metrics, setMetrics] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isDialogOpen, setIsDialogOpen] = useState(false); const [editingMetric, setEditingMetric] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); const [deleteMetricId, setDeleteMetricId] = useState(null); const [isDeleteOpen, setIsDeleteOpen] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const form = useForm({ resolver: zodResolver(metricSchema), defaultValues: { name: '', unit: '', direction: 'increase', category: '', }, }); const fetchMetrics = async () => { try { const res = await fetchApi('/api/metrics'); if (res.ok) { const data = await res.json(); setMetrics(data); } } catch (error) { console.error('Failed to fetch metrics:', error); } finally { setIsLoading(false); } }; useEffect(() => { fetchMetrics(); }, []); const onSubmit = async (values: MetricFormValues) => { setIsSubmitting(true); try { const url = editingMetric ? `/api/metrics/${editingMetric.id}` : '/api/metrics'; const method = editingMetric ? 'PATCH' : 'POST'; const res = await fetchApi(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(values), }); if (!res.ok) { const data = await res.json(); throw new Error(data.error || '操作失败'); } toast.success(editingMetric ? '指标已更新' : '指标已创建'); setIsDialogOpen(false); setEditingMetric(null); form.reset(); fetchMetrics(); } catch (error) { toast.error(getErrorMessage(error)); } finally { setIsSubmitting(false); } }; const handleEdit = (metric: Metric) => { setEditingMetric(metric); form.reset({ name: metric.name, unit: metric.unit, direction: metric.direction, category: metric.category || '', }); setIsDialogOpen(true); }; const handleToggleActive = async (metric: Metric) => { try { const res = await fetchApi(`/api/metrics/${metric.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ is_active: !metric.is_active }), }); if (!res.ok) throw new Error('操作失败'); toast.success(metric.is_active ? '已停用' : '已启用'); fetchMetrics(); } catch (error) { toast.error(getErrorMessage(error)); } }; const requestDelete = (metricId: string) => { setDeleteMetricId(metricId); setIsDeleteOpen(true); }; const confirmDelete = async () => { if (!deleteMetricId) return; setIsDeleting(true); try { const res = await fetchApi(`/api/metrics/${deleteMetricId}`, { method: 'DELETE' }); if (!res.ok) throw new Error('删除失败'); toast.success('指标已删除'); fetchMetrics(); } catch (error) { toast.error(getErrorMessage(error)); } finally { setIsDeleting(false); setIsDeleteOpen(false); setDeleteMetricId(null); } }; const handleDialogClose = () => { setIsDialogOpen(false); setEditingMetric(null); form.reset(); }; if (currentUser?.role !== 'owner') { return (

仅老板可访问此页面

); } if (isLoading) { return (
); } const categories = Array.from(new Set(metrics.map(m => m.category).filter(Boolean))); return (

指标库管理

管理可复用的 KR 指标

{editingMetric ? '编辑指标' : '新建指标'}
( 指标名称 * )} /> ( 单位 * )} /> ( 方向 )} /> ( 分类 )} />
指标名称 单位 方向 分类 状态 操作 {metrics.map(metric => ( {metric.name} {metric.unit}
{metric.direction === 'increase' ? ( ) : ( )} {DIRECTION_LABELS[metric.direction]}
{metric.category || '-'} handleToggleActive(metric)} />
))} {metrics.length === 0 && ( 暂无指标 )}
确认删除指标? 删除后无法恢复。 取消 {isDeleting ? '删除中...' : '确认删除'}
); }