140 lines
4.9 KiB
TypeScript
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>
|
|
);
|
|
}
|