feat: p2 batch 28-31 reminders lists comments bang stats

Calendar events support multiple reminder offsets with presets and per-user defaults.

Lists index adds inline task entry and list property editing.

Generic entity comments on list detail. New bangs.stats dashboard widget.

Closes Gitea #28, #29, #30, #31. Migrations 0022 and 0023.
This commit is contained in:
ginnoir
2026-07-04 22:17:35 -05:00
parent 7714b1187c
commit e1c2a090fb
31 changed files with 1287 additions and 92 deletions
+8 -2
View File
@@ -1,5 +1,9 @@
import { CalendarShell } from "@/modules/calendar/components/calendar-shell";
import { listCalendars, listEvents } from "@/modules/calendar/server/queries";
import {
getDefaultEventReminderOffsets,
listCalendars,
listEvents,
} from "@/modules/calendar/server/queries";
import { getCurrentSession } from "@/lib/session";
import type { CalView } from "@/modules/_core/themes";
@@ -10,10 +14,11 @@ export default async function CalendarPage() {
const to = new Date(now);
to.setMonth(to.getMonth() + 10);
const [{ user }, calendars, events] = await Promise.all([
const [{ user }, calendars, events, defaultReminderOffsets] = await Promise.all([
getCurrentSession(),
listCalendars(),
listEvents({ from, to, calendarIds: "all" }),
getDefaultEventReminderOffsets(),
]);
return (
@@ -21,6 +26,7 @@ export default async function CalendarPage() {
calendars={calendars}
events={events}
defaultView={user.themeCalView as CalView}
defaultReminderOffsets={defaultReminderOffsets}
/>
);
}
+3 -2
View File
@@ -1,11 +1,12 @@
import { notFound } from "next/navigation";
import { getCurrentSession } from "@/lib/session";
import { ListDetail } from "@/modules/lists/components/list-detail";
import { getList } from "@/modules/lists/server/queries";
export default async function ListPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const list = await getList(id).catch(() => null);
const [{ user }, list] = await Promise.all([getCurrentSession(), getList(id).catch(() => null)]);
if (!list) notFound();
return <ListDetail initialList={list} />;
return <ListDetail initialList={list} currentUserId={user.id} />;
}
+21 -1
View File
@@ -14,6 +14,7 @@ import { revokeShareLinkAction } from "./actions";
import { getHouseholdApiTokenStatus } from "@/modules/_core/api-token";
import { ApiTokenSettings } from "@/components/api-token-settings";
import { AssistantOptIn } from "@/components/assistant-opt-in";
import { DefaultEventRemindersSetting } from "@/components/default-event-reminders-setting";
import { listCalendars } from "@/modules/calendar/server/queries";
import { listLists } from "@/modules/lists/server/queries";
import Link from "next/link";
@@ -61,7 +62,12 @@ export default async function SettingsPage({
{section === "household" && <HouseholdSection household={household} user={user} />}
{section === "sharing" && <SharingSection />}
{section === "notifications" && (
<NotificationsSection user={user} vapidKey={vapidKey} ntfyConfigured={ntfyConfigured} />
<NotificationsSection
user={user}
defaultEventReminderOffsets={user.defaultEventReminderOffsets ?? [30]}
vapidKey={vapidKey}
ntfyConfigured={ntfyConfigured}
/>
)}
{section === "calendars" && <CalendarsAndListsSection />}
{section === "appearance" && <AppearanceSection user={user} />}
@@ -185,10 +191,12 @@ async function SharingSection() {
function NotificationsSection({
user,
defaultEventReminderOffsets,
vapidKey,
ntfyConfigured,
}: {
user: { notifPush: boolean; notifInApp: boolean; notifNtfy: boolean };
defaultEventReminderOffsets: number[];
vapidKey: string;
ntfyConfigured: boolean;
}) {
@@ -216,6 +224,18 @@ function NotificationsSection({
/>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Default event reminders</CardTitle>
</CardHeader>
<CardContent>
<DefaultEventRemindersSetting
key={defaultEventReminderOffsets.join(",")}
initialOffsets={defaultEventReminderOffsets}
/>
</CardContent>
</Card>
</>
);
}
+21
View File
@@ -0,0 +1,21 @@
"use server";
import { eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { z } from "zod";
import { db } from "@/lib/db";
import { getCurrentSession } from "@/lib/session";
import { normalizeReminderOffsets } from "@/lib/reminder-offsets";
import { users } from "@/modules/_core/schema";
const offsetsSchema = z.array(z.number().int().min(0)).max(20);
export async function setDefaultEventReminderOffsets(offsets: number[]) {
const { user } = await getCurrentSession();
const parsed = normalizeReminderOffsets(offsetsSchema.parse(offsets));
await db.update(users).set({ defaultEventReminderOffsets: parsed }).where(eq(users.id, user.id));
revalidatePath("/calendar");
revalidatePath("/settings");
}