geo/frontend/components/dashboard/NextActionCard.tsx

198 lines
5.7 KiB
TypeScript

"use client";
import React from "react";
import Link from "next/link";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import type { ActionItemProps, NextAction, NextActionCardProps } from "@/types/next-action";
import { Target, Lightbulb, Bell } from "lucide-react";
// 优先级图标映射
const PRIORITY_ICONS: Record<NextAction["priority"], React.ReactNode> = {
primary: <Target className="h-5 w-5" />,
secondary: <Lightbulb className="h-5 w-5" />,
optional: <Bell className="h-5 w-5" />,
};
// 优先级标签映射
const PRIORITY_LABELS: Record<NextAction["priority"], string> = {
primary: "主要",
secondary: "次要",
optional: "可选",
};
// 优先级颜色配置
const PRIORITY_COLORS: Record<NextAction["priority"], { border: string; bg: string; icon: string }> = {
primary: {
border: "border-l-4 border-l-orange-500 border-orange-200",
bg: "bg-orange-50",
icon: "text-orange-500",
},
secondary: {
border: "border-l-4 border-l-blue-500 border-blue-200",
bg: "bg-blue-50",
icon: "text-blue-500",
},
optional: {
border: "border-l-4 border-l-gray-400 border-gray-200",
bg: "bg-gray-50",
icon: "text-gray-500",
},
};
/**
* 单个行动项组件
*/
function ActionItem({ action, onClick }: ActionItemProps) {
const colors = PRIORITY_COLORS[action.priority];
return (
<div
className={cn(
"rounded-lg border p-4 transition-all hover:shadow-md",
colors.border,
)}
>
<div className="flex items-start gap-3">
{/* 优先级图标 */}
<div className={cn("mt-0.5", colors.icon)}>
{PRIORITY_ICONS[action.priority]}
</div>
{/* 内容区域 */}
<div className="flex-1 min-w-0">
{/* 标题和描述 */}
<div className="mb-2">
<div className="flex items-center gap-2 mb-1">
<span className="text-xs font-medium text-muted-foreground">
{PRIORITY_LABELS[action.priority]}
</span>
<span className="text-sm">{action.icon}</span>
</div>
<h4 className="font-medium text-sm">{action.title}</h4>
<p className="text-sm text-muted-foreground mt-1 line-clamp-2">
{action.description}
</p>
</div>
{/* 操作按钮 */}
<div className="mt-3">
{action.actionUrl.startsWith("/") ? (
<Link href={action.actionUrl}>
<Button
variant="outline"
size="sm"
className={cn(
"text-xs h-7",
action.priority === "primary" &&
"border-orange-300 bg-orange-100 hover:bg-orange-200 text-orange-700",
action.priority === "secondary" &&
"border-blue-300 bg-blue-100 hover:bg-blue-200 text-blue-700",
)}
onClick={onClick}
>
{action.actionText}
<span className="ml-1"></span>
</Button>
</Link>
) : (
<a
href={action.actionUrl}
target="_blank"
rel="noopener noreferrer"
>
<Button
variant="outline"
size="sm"
className="text-xs h-7"
onClick={onClick}
>
{action.actionText}
<span className="ml-1"></span>
</Button>
</a>
)}
</div>
</div>
</div>
</div>
);
}
/**
* 下一步行动建议卡片组件
*/
export function NextActionCard({ context, className, onActionClick }: NextActionCardProps) {
// 动态导入以避免循环依赖
const [actions, setActions] = React.useState<NextAction[]>([]);
const [loading, setLoading] = React.useState(true);
React.useEffect(() => {
async function loadActions() {
try {
const { generateNextActions } = await import("@/lib/next-action");
const generatedActions = generateNextActions(context);
setActions(generatedActions);
} catch (error) {
console.error("生成行动建议失败:", error);
} finally {
setLoading(false);
}
}
loadActions();
}, [context]);
if (loading) {
return (
<Card className={className}>
<CardHeader className="pb-3">
<CardTitle className="text-base flex items-center gap-2">
<Target className="h-5 w-5 text-primary" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
{[1, 2, 3].map((i) => (
<div
key={i}
className="h-24 animate-pulse rounded-lg bg-muted"
/>
))}
</div>
</CardContent>
</Card>
);
}
if (actions.length === 0) {
return null;
}
return (
<Card className={className}>
<CardHeader className="pb-3">
<CardTitle className="text-base flex items-center gap-2">
<Target className="h-5 w-5 text-primary" />
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
{actions.map((action) => (
<ActionItem
key={action.id}
action={action}
onClick={() => onActionClick?.(action)}
/>
))}
</div>
</CardContent>
</Card>
);
}
export { ActionItem };