mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 15:51:15 +00:00
feat(notifications): persistent read cursor + delivery history
notification_cursors table stores one read-cursor per user per tenant; GET /v1/activity returns unreadCount + readAt, POST /v1/activity/read persists the cursor. Reminder dispatches now append reminder.sent / reminder.failed system events so delivery history reaches the feed. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
76e45d4178
commit
2464aa45fa
14 files changed
+332
-8
No files matched your search
@@ -26,7 +26,8 @@ export function NotificationDropdown(props: { bookings?: any[]; billing?: any; t
|
||||
const i18n = useI18n();
|
||||
const [open, setOpen] = createSignal(false);
|
||||
|
||||
const [activity] = createResource(
|
||||
const [readAt, setReadAt] = createSignal<Date | null>(null);
|
||||
const [activity, { refetch: refetchActivity }] = createResource(
|
||||
() => props.token?.(),
|
||||
async (bearer) => {
|
||||
if (!bearer) return [] as ActivityEvent[];
|
||||
@@ -34,7 +35,9 @@ export function NotificationDropdown(props: { bookings?: any[]; billing?: any; t
|
||||
params: { query: { limit: 20 } },
|
||||
headers: { Authorization: `Bearer ${bearer}` },
|
||||
});
|
||||
return (response.data?.events ?? []) as ActivityEvent[];
|
||||
const data = response.data;
|
||||
setReadAt(data?.readAt ? new Date(data.readAt) : null);
|
||||
return (data?.events ?? []) as ActivityEvent[];
|
||||
},
|
||||
{ initialValue: [] },
|
||||
);
|
||||
@@ -52,6 +55,7 @@ export function NotificationDropdown(props: { bookings?: any[]; billing?: any; t
|
||||
const translated = i18n.t(key);
|
||||
return translated === key ? action : translated;
|
||||
};
|
||||
const cursor = readAt();
|
||||
events.forEach((event) => {
|
||||
const actorSuffix = event.actorKind === "staff" && event.actorName ? ` · ${event.actorName}` : "";
|
||||
items.push({
|
||||
@@ -60,7 +64,7 @@ export function NotificationDropdown(props: { bookings?: any[]; billing?: any; t
|
||||
title: actionLabel(event.action),
|
||||
message: `${event.summary}${actorSuffix}`,
|
||||
time: new Date(event.createdAt).toLocaleString(locale, { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }),
|
||||
read: false,
|
||||
read: cursor != null && new Date(event.createdAt) <= cursor,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
@@ -113,7 +117,19 @@ export function NotificationDropdown(props: { bookings?: any[]; billing?: any; t
|
||||
|
||||
const unreadCount = createMemo(() => filteredNotifications().filter((n) => !n.read).length);
|
||||
|
||||
const markAllRead = () => {
|
||||
const markAllRead = async () => {
|
||||
const bearer = props.token?.();
|
||||
if (bearer) {
|
||||
try {
|
||||
const response = await (apiClient as any).POST("/v1/activity/read", {
|
||||
headers: { Authorization: `Bearer ${bearer}` },
|
||||
});
|
||||
if (response.data?.readAt) setReadAt(new Date(response.data.readAt));
|
||||
void refetchActivity();
|
||||
} catch {
|
||||
// Cursor write failed — fall back to local marks for this session.
|
||||
}
|
||||
}
|
||||
setReadIds(new Set(filteredNotifications().map((n) => n.id)));
|
||||
};
|
||||
|
||||
|
||||
@@ -367,6 +367,8 @@ const dictionaries = {
|
||||
"dashboard.activity.booking.rescheduled": "Rezervace přesunuta",
|
||||
"dashboard.activity.booking.moved": "Přesun na jinou lekci",
|
||||
"dashboard.activity.booking.promoted": "Povýšení z čekací listiny",
|
||||
"dashboard.activity.reminder.sent": "Připomínka odeslána",
|
||||
"dashboard.activity.reminder.failed": "Připomínka selhala",
|
||||
"dashboard.exportCsv": "Export CSV",
|
||||
"dashboard.demoBanner": "Demo režim",
|
||||
"dashboard.demoBannerDesc": "Prozkoumáte Bookra s ukázkovými daty. Pro plnou funkčnost se zaregistrujte.",
|
||||
@@ -971,6 +973,8 @@ const dictionaries = {
|
||||
"dashboard.activity.booking.rescheduled": "Booking rescheduled",
|
||||
"dashboard.activity.booking.moved": "Moved to another session",
|
||||
"dashboard.activity.booking.promoted": "Promoted from waitlist",
|
||||
"dashboard.activity.reminder.sent": "Reminder sent",
|
||||
"dashboard.activity.reminder.failed": "Reminder failed",
|
||||
"dashboard.exportCsv": "Export CSV",
|
||||
"dashboard.demoBanner": "Demo mode",
|
||||
"dashboard.demoBannerDesc": "You're exploring Bookra with sample data. Register for full functionality.",
|
||||
|
||||
Reference in new issue
Block a user