Files
govoplan-postbox/webui/src/features/postbox/PostboxInboxWidget.tsx
T

106 lines
3.3 KiB
TypeScript

import { useCallback } from "react";
import { Inbox, Paperclip } from "lucide-react";
import { Link } from "react-router";
import {
DashboardWidgetList,
DismissibleAlert,
LoadingFrame,
i18nMessage,
useDashboardWidgetData,
usePlatformLanguage,
type ApiSettings,
type DashboardWidgetConfiguration
} from "@govoplan/core-webui";
import {
listPostboxMessages,
listPostboxes
} from "../../api/postbox";
export default function PostboxInboxWidget({
settings,
refreshKey,
configuration
}: {
settings: ApiSettings;
refreshKey: number;
configuration: DashboardWidgetConfiguration;
}) {
const { language } = usePlatformLanguage();
const maxItems = numberSetting(configuration.maxItems, 5, 1, 12);
const load = useCallback(async () => {
const postboxes = await listPostboxes(settings);
const eligible = postboxes.filter(
(postbox) => postbox.grouping_policy.mode === "allow"
);
const separatedCount = postboxes.length - eligible.length;
if (!eligible.length) {
return { messages: [], total: 0, separatedCount };
}
const response = await listPostboxMessages(
settings,
eligible.map((postbox) => postbox.id),
maxItems,
0,
"",
"unread"
);
return { ...response, separatedCount };
}, [maxItems, settings]);
const { data, loading, error } = useDashboardWidgetData(load, refreshKey);
return (
<LoadingFrame loading={loading} label="Loading unread Postbox messages">
{error && (
<DismissibleAlert tone="warning" resetKey={error}>
{error}
</DismissibleAlert>
)}
{data?.separatedCount ? (
<DismissibleAlert tone="info" dismissible={false}>
{data.separatedCount} governed Postbox source{data.separatedCount === 1 ? " is" : "s are"} shown only in separated inbox views.
</DismissibleAlert>
) : null}
<DashboardWidgetList
emptyText="No unread Postbox messages."
items={(data?.messages ?? []).map((message) => ({
id: message.id,
title: message.subject,
detail: message.sender_label || message.producer_module || "Postbox",
meta: new Intl.DateTimeFormat(language, {
day: "2-digit",
month: "short",
hour: "2-digit",
minute: "2-digit"
}).format(new Date(message.delivered_at)),
leading: <Inbox size={17} aria-hidden="true" />,
trailing: message.attachments.length ? (
<span title={i18nMessage("i18n:govoplan-postbox.attachments_label", { count: message.attachments.length })}>
<Paperclip size={15} aria-hidden="true" />
</span>
) : undefined,
to: `/postbox?message=${encodeURIComponent(message.id)}`
}))}
/>
<div className="dashboard-contribution-footer">
<Link className="btn btn-secondary" to="/postbox">
{data?.total
? i18nMessage("i18n:govoplan-postbox.open_unread", { count: data.total })
: "Open Postbox"}
</Link>
</div>
</LoadingFrame>
);
}
function numberSetting(
value: unknown,
fallback: number,
minimum: number,
maximum: number
): number {
const numeric = typeof value === "number" ? value : Number(value);
return Number.isFinite(numeric)
? Math.max(minimum, Math.min(maximum, Math.floor(numeric)))
: fallback;
}