haixunMaster/app/(dashboard)/notifications/page.tsx

140 lines
4.9 KiB
TypeScript

"use client";
import Link from "next/link";
import { useEffect } from "react";
import { Bell, CheckCheck, Trash2 } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { EmptyState } from "@/components/layout/empty-state";
import { PageHeader } from "@/components/layout/page-header";
import { ActiveJobsPanel } from "@/components/layout/active-jobs-panel";
import { useJobs } from "@/components/layout/jobs-provider";
import { useNotifications } from "@/lib/notifications/use-notifications";
import type { NotificationType } from "@/lib/notifications/store";
import { cn } from "@/lib/utils";
const typeStyle: Record<NotificationType, string> = {
success: "border-success-border bg-success-bg",
error: "border-danger-border bg-danger-bg",
warning: "border-warning-border bg-warning-bg",
info: "border-border bg-muted",
};
const typeLabel: Record<NotificationType, string> = {
success: "成功",
error: "失敗",
warning: "提醒",
info: "訊息",
};
export default function NotificationsPage() {
const { activeJobs } = useJobs();
const { notifications, unreadCount, markRead, markAllRead, clearAll, removeNotification } =
useNotifications();
useEffect(() => {
markAllRead();
}, [markAllRead]);
return (
<div>
<PageHeader
title="通知"
description="背景任務完成、發布結果與系統提醒都會記錄在這裡。"
action={
notifications.length > 0 ? (
<div className="flex gap-2">
{unreadCount > 0 && (
<Button size="sm" variant="outline" onClick={markAllRead}>
<CheckCheck className="h-3.5 w-3.5" />
</Button>
)}
<Button size="sm" variant="ghost" onClick={clearAll}>
<Trash2 className="h-3.5 w-3.5" />
</Button>
</div>
) : undefined
}
/>
<ActiveJobsPanel />
{notifications.length === 0 && activeJobs.length === 0 ? (
<EmptyState
icon={Bell}
title="尚無通知"
description="完成海巡、生成草稿或發布貼文後,相關結果會顯示在這裡。"
/>
) : notifications.length === 0 ? null : (
<div className="space-y-2">
<p className="mb-2 text-[13px] font-medium text-muted-foreground"></p>
{notifications.map((item) => (
<Card
key={item.id}
className={cn(
"transition-opacity",
typeStyle[item.type],
!item.read && "ring-1 ring-foreground/10"
)}
>
<CardContent className="flex items-start gap-3 p-3.5">
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-2">
{!item.read && (
<span className="h-2 w-2 shrink-0 rounded-full bg-foreground" />
)}
<p className="text-[14px] font-medium">{item.title}</p>
<Badge variant="outline" className="text-[10px]">
{typeLabel[item.type]}
</Badge>
</div>
{item.message && (
<p className="mt-1 text-[13px] leading-relaxed text-muted-foreground">
{item.message}
</p>
)}
<p className="mt-1.5 text-[11px] text-muted-foreground">
{new Date(item.createdAt).toLocaleString("zh-TW")}
</p>
{item.href && (
<Link
href={item.href}
className="mt-2 inline-block text-[12px] underline"
onClick={() => markRead(item.id)}
>
</Link>
)}
</div>
<div className="flex shrink-0 flex-col gap-1">
{!item.read && (
<Button
size="sm"
variant="ghost"
className="h-7 px-2 text-[11px]"
onClick={() => markRead(item.id)}
>
</Button>
)}
<Button
size="sm"
variant="ghost"
className="h-7 px-2 text-[11px] text-muted-foreground"
onClick={() => removeNotification(item.id)}
>
</Button>
</div>
</CardContent>
</Card>
))}
</div>
)}
</div>
);
}