Files
Edu/apps/portal-shell/src/widgets/universal/notifications-widget/index.tsx
SpecialX a28a6bd6ea feat(portal-shell): clean widget design tokens and fix lint:tokens (P1-6)
516 mechanical replacements across 25 widget files:
- spacing xs/sm/md/lg/xl to numeric 1/2/3/4/6
- text-heading-3 to text-lg font-semibold
- bg-danger to bg-destructive
- border border dedup

Fix .eslintrc.tokens.js to use typescript-eslint parser (was importing
uninstalled @typescript-eslint/parser). lint:tokens now passes.
2026-07-22 15:07:38 +08:00

90 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
/**
* notifications-widgetuniversal / main
*
* 通过 useNotifications 查询 apollo-router → msg 子图的 notifications 数据。
* 与 topbar 的 notification-bell 区别:本插件位于 main 区,展示完整列表
* 含标题、正文、时间、类型徽章bell 仅在顶栏做徽标提示。
*
* 关联portal-shell spec §5.6 统一 Hook、M8 验收
*/
import { useNotifications } from "@/lib/api/universal";
import { PluginSkeleton } from "@/shell/PluginLoader";
import type { PluginProps } from "@/lib/types";
const TYPE_LABEL: Record<string, string> = {
info: "通知",
warning: "提醒",
urgent: "紧急",
};
function TypeBadge({ type }: { type: string }): React.ReactElement {
const label = TYPE_LABEL[type] ?? type;
if (type === "urgent") {
return (
<span className="rounded-md bg-destructive px-2 py-1 text-xs text-primary-foreground">
{label}
</span>
);
}
if (type === "warning") {
return (
<span className="rounded-md bg-primary px-2 py-1 text-xs text-primary-foreground">
{label}
</span>
);
}
return (
<span className="rounded-md bg-muted px-2 py-1 text-xs text-muted-foreground">
{label}
</span>
);
}
export default function NotificationsWidget(
props: PluginProps,
): React.ReactElement {
const rawLimit = props.props.limit;
const limit = typeof rawLimit === "number" ? rawLimit : 20;
const { data, loading } = useNotifications({ limit, offset: 0 });
if (loading && !data) {
return <PluginSkeleton variant="list" />;
}
const items = data?.items ?? [];
const total = data?.total ?? 0;
return (
<section className="rounded-xl border bg-card p-4">
<div className="flex">
<h3 className="flex-1 text-lg font-semibold text-foreground"></h3>
<span className="text-sm text-muted-foreground"> {total} </span>
</div>
{items.length === 0 ? (
<p className="mt-2 text-sm text-muted-foreground"></p>
) : (
<ul className="mt-2 space-y-2">
{items.map((item) => (
<li
key={item.id}
className="border-b border py-1 text-sm text-foreground"
>
<div className="flex items-center gap-4">
<span className="flex-1">{item.title}</span>
<TypeBadge type={item.type} />
</div>
<p className="mt-1 text-xs text-muted-foreground">{item.body}</p>
<p className="mt-1 text-xs text-muted-foreground">
{item.createdAt}
</p>
</li>
))}
</ul>
)}
</section>
);
}