130 lines
4.4 KiB
TypeScript
130 lines
4.4 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useMemo, useState } from "react";
|
||
import Link from "next/link";
|
||
import { AuthGuard } from "@/components/AuthGuard";
|
||
import { Button } from "@/components/ui";
|
||
import { loadCommercialNotifications } from "@/modules/commercial/adapters";
|
||
import type { AdapterResult, CommercialNotification } from "@/modules/commercial/types";
|
||
import {
|
||
CommercialEmptyState,
|
||
CommercialSkeleton,
|
||
PlatformShell,
|
||
} from "@/modules/commercial/components";
|
||
|
||
type Filter = "all" | "unread" | "high" | "medium" | "low";
|
||
|
||
function NotificationsInner() {
|
||
const [result, setResult] = useState<AdapterResult<CommercialNotification[]> | null>(null);
|
||
const [filter, setFilter] = useState<Filter>("all");
|
||
const [loading, setLoading] = useState(true);
|
||
|
||
useEffect(() => {
|
||
void loadCommercialNotifications().then((r) => {
|
||
setResult(r);
|
||
setLoading(false);
|
||
});
|
||
}, []);
|
||
|
||
const items = useMemo(() => {
|
||
const list = result?.data || [];
|
||
return list.filter((n) => {
|
||
if (filter === "unread") return !n.read;
|
||
if (filter === "high" || filter === "medium" || filter === "low") {
|
||
const p = (n.priority || n.severity || "").toLowerCase();
|
||
return p === filter || (filter === "high" && (p === "critical" || p === "urgent"));
|
||
}
|
||
return true;
|
||
});
|
||
}, [result, filter]);
|
||
|
||
return (
|
||
<PlatformShell title="اعلانها" subtitle="فقط از commercial notifications API — بدون داده جعلی">
|
||
<div className="mb-4 flex flex-wrap gap-2" role="tablist" aria-label="فیلتر اعلان">
|
||
{(
|
||
[
|
||
["all", "همه"],
|
||
["unread", "خواندهنشده"],
|
||
["high", "اولویت بالا"],
|
||
["medium", "متوسط"],
|
||
["low", "پایین"],
|
||
] as const
|
||
).map(([id, label]) => (
|
||
<button
|
||
key={id}
|
||
type="button"
|
||
role="tab"
|
||
aria-selected={filter === id}
|
||
className={`rounded-full px-3 py-1.5 text-xs font-medium ${
|
||
filter === id
|
||
? "bg-primary text-white"
|
||
: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-300"
|
||
}`}
|
||
onClick={() => setFilter(id)}
|
||
>
|
||
{label}
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
{loading ? <CommercialSkeleton rows={4} /> : null}
|
||
|
||
{!loading && (!result || result.availability !== "ready" || !items.length) ? (
|
||
<CommercialEmptyState
|
||
title="اعلانی نیست"
|
||
description={result?.message || "وقتی اعلان از بکاند برسد اینجا نمایش داده میشود."}
|
||
actionHref="/dashboard"
|
||
actionLabel="داشبورد"
|
||
secondaryHref="/help"
|
||
secondaryLabel="راهنما"
|
||
/>
|
||
) : null}
|
||
|
||
{!loading && items.length ? (
|
||
<ul className="space-y-2">
|
||
{items.map((n) => (
|
||
<li
|
||
key={n.id}
|
||
className={`rounded-xl border px-4 py-3 text-sm ${
|
||
n.read
|
||
? "border-gray-100 bg-white dark:border-gray-800 dark:bg-gray-900"
|
||
: "border-primary/30 bg-[var(--color-primary-soft)]"
|
||
}`}
|
||
>
|
||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||
<p className="font-semibold text-secondary dark:text-gray-100">{n.title}</p>
|
||
<span className="text-[10px] uppercase text-gray-400">
|
||
{n.priority || n.severity || "normal"}
|
||
{!n.read ? " · unread" : ""}
|
||
</span>
|
||
</div>
|
||
{n.body ? <p className="mt-1 text-xs text-gray-600 dark:text-gray-400">{n.body}</p> : null}
|
||
{n.href ? (
|
||
<Link href={n.href} className="mt-2 inline-block text-xs font-semibold text-primary">
|
||
باز کردن
|
||
</Link>
|
||
) : null}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
) : null}
|
||
|
||
<div className="mt-6">
|
||
<Link href="/dashboard">
|
||
<Button variant="outline" size="sm">
|
||
بازگشت
|
||
</Button>
|
||
</Link>
|
||
</div>
|
||
</PlatformShell>
|
||
);
|
||
}
|
||
|
||
export default function NotificationsPage() {
|
||
return (
|
||
<AuthGuard>
|
||
<NotificationsInner />
|
||
</AuthGuard>
|
||
);
|
||
}
|