You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
memos/web/src/pages/Inboxes.tsx

132 lines
5.5 KiB
TypeScript

import { sortBy } from "lodash-es";
import { ArchiveIcon, BellIcon, InboxIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { useEffect, useState } from "react";
import Empty from "@/components/Empty";
import MemoCommentMessage from "@/components/Inbox/MemoCommentMessage";
import MobileHeader from "@/components/MobileHeader";
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import { cn } from "@/lib/utils";
import { userStore } from "@/store";
import { UserNotification, UserNotification_Status, UserNotification_Type } from "@/types/proto/api/v1/user_service";
import { useTranslate } from "@/utils/i18n";
const Inboxes = observer(() => {
const t = useTranslate();
const { md } = useResponsiveWidth();
const [filter, setFilter] = useState<"all" | "unread" | "archived">("all");
const allNotifications = sortBy(userStore.state.notifications, (notification: UserNotification) => {
return -(notification.createTime?.getTime() || 0);
});
const notifications = allNotifications.filter((notification) => {
if (filter === "unread") return notification.status === UserNotification_Status.UNREAD;
if (filter === "archived") return notification.status === UserNotification_Status.ARCHIVED;
return true;
});
const unreadCount = allNotifications.filter((n) => n.status === UserNotification_Status.UNREAD).length;
const archivedCount = allNotifications.filter((n) => n.status === UserNotification_Status.ARCHIVED).length;
const fetchNotifications = async () => {
try {
await userStore.fetchNotifications();
} catch (error) {
console.error("Failed to fetch notifications:", error);
}
};
useEffect(() => {
fetchNotifications();
}, []);
return (
<section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
{!md && <MobileHeader />}
<div className="w-full px-4 sm:px-6">
<div className="w-full border border-border flex flex-col justify-start items-start rounded-xl bg-background text-foreground overflow-hidden">
{/* Header */}
<div className="w-full px-4 py-4 border-b border-border">
<div className="flex flex-row justify-between items-center">
<div className="flex flex-row items-center gap-2">
<BellIcon className="w-5 h-auto text-muted-foreground" />
<h1 className="text-xl font-semibold">{t("common.inbox")}</h1>
{unreadCount > 0 && (
<span className="ml-1 px-2 py-0.5 text-xs font-medium rounded-full bg-primary text-primary-foreground">
{unreadCount}
</span>
)}
</div>
</div>
</div>
{/* Filter Tabs */}
<div className="w-full px-4 py-2 border-b border-border bg-muted/30">
<div className="flex flex-row gap-1">
<button
onClick={() => setFilter("all")}
className={cn(
"px-3 py-1.5 text-sm font-medium rounded-md transition-colors",
filter === "all"
? "bg-background text-foreground shadow-sm"
: "text-muted-foreground hover:text-foreground hover:bg-background/50",
)}
>
{t("common.all")} ({allNotifications.length})
</button>
<button
onClick={() => setFilter("unread")}
className={cn(
"px-3 py-1.5 text-sm font-medium rounded-md transition-colors flex items-center gap-1.5",
filter === "unread"
? "bg-background text-foreground shadow-sm"
: "text-muted-foreground hover:text-foreground hover:bg-background/50",
)}
>
<InboxIcon className="w-3.5 h-auto" />
{t("inbox.unread")} ({unreadCount})
</button>
<button
onClick={() => setFilter("archived")}
className={cn(
"px-3 py-1.5 text-sm font-medium rounded-md transition-colors flex items-center gap-1.5",
filter === "archived"
? "bg-background text-foreground shadow-sm"
: "text-muted-foreground hover:text-foreground hover:bg-background/50",
)}
>
<ArchiveIcon className="w-3.5 h-auto" />
{t("common.archived")} ({archivedCount})
</button>
</div>
</div>
{/* Notifications List */}
<div className="w-full">
{notifications.length === 0 ? (
<div className="w-full py-16 flex flex-col justify-center items-center">
<Empty />
<p className="mt-4 text-sm text-muted-foreground">
{filter === "unread" ? t("inbox.no-unread") : filter === "archived" ? t("inbox.no-archived") : t("message.no-data")}
</p>
</div>
) : (
<div className="flex flex-col">
{notifications.map((notification: UserNotification) => {
if (notification.type === UserNotification_Type.MEMO_COMMENT) {
return <MemoCommentMessage key={notification.name} notification={notification} />;
}
return null;
})}
</div>
)}
</div>
</div>
</div>
</section>
);
});
export default Inboxes;