feat: reminder status counts in dashboard card

Напоминания card now shows: не спрош. (gold), придёт (green),
не придёт (red) — same visual style as booking cards. Clickable
to navigate to reminders tab. Skipped "нет ответа" per user request.
This commit is contained in:
2026-03-24 19:08:37 +03:00
parent a95676ea6a
commit 87ba4d232a

View File

@@ -582,6 +582,9 @@ interface DashboardCounts {
od: TabCounts;
remindersToday: number;
remindersTomorrow: number;
remindersNotAsked: number;
remindersComing: number;
remindersCancelled: number;
}
function countByStatus(items: { status: string }[]): TabCounts {
@@ -621,13 +624,17 @@ function DashboardSummary({ statusFilter, onNavigate, onFilter }: {
return adminFetch(`/api/admin/open-day/bookings?eventId=${active.id}`).then((r) => r.json());
}),
adminFetch("/api/admin/reminders").then((r) => r.json()).catch(() => []),
]).then(([gb, mc, od, rem]: [{ status: string }[], { status: string }[], { status: string }[], { eventDate: string }[]]) => {
]).then(([gb, mc, od, rem]: [{ status: string }[], { status: string }[], { status: string }[], { eventDate: string; reminderStatus?: string }[]]) => {
const upcoming = rem.filter((r) => r.eventDate === today || r.eventDate === tomorrow);
setCounts({
classes: countByStatus(gb),
mc: countByStatus(mc),
od: countByStatus(od),
remindersToday: rem.filter((r) => r.eventDate === today).length,
remindersTomorrow: rem.filter((r) => r.eventDate === tomorrow).length,
remindersNotAsked: upcoming.filter((r) => !r.reminderStatus).length,
remindersComing: upcoming.filter((r) => r.reminderStatus === "coming").length,
remindersCancelled: upcoming.filter((r) => r.reminderStatus === "cancelled").length,
});
}).catch(() => {});
}, []);
@@ -667,6 +674,35 @@ function DashboardSummary({ statusFilter, onNavigate, onFilter }: {
{counts.remindersToday > 0 && <><span className={`text-lg font-bold ${c.urgentColor}`}>{counts.remindersToday}</span><span className="text-[10px] text-neutral-500">сегодня</span></>}
{counts.remindersTomorrow > 0 && <><span className="text-sm font-medium text-neutral-400">{counts.remindersTomorrow}</span><span className="text-[10px] text-neutral-500">завтра</span></>}
</div>
<div className="flex items-baseline gap-2 mt-1">
{counts.remindersNotAsked > 0 && (
<span className="inline-flex items-baseline gap-1 cursor-pointer hover:underline decoration-neutral-500 underline-offset-2 transition-all"
onClick={(e) => { e.stopPropagation(); onNavigate(c.tab); }}>
<span className="text-sm font-medium text-gold">{counts.remindersNotAsked}</span>
<span className="text-[10px] text-neutral-500">не спрош.</span>
</span>
)}
{counts.remindersComing > 0 && (
<>
{counts.remindersNotAsked > 0 && <span className="text-neutral-700">·</span>}
<span className="inline-flex items-baseline gap-1 cursor-pointer hover:underline decoration-neutral-500 underline-offset-2 transition-all"
onClick={(e) => { e.stopPropagation(); onNavigate(c.tab); }}>
<span className="text-sm font-medium text-emerald-400">{counts.remindersComing}</span>
<span className="text-[10px] text-neutral-500">придёт</span>
</span>
</>
)}
{counts.remindersCancelled > 0 && (
<>
{(counts.remindersNotAsked > 0 || counts.remindersComing > 0) && <span className="text-neutral-700">·</span>}
<span className="inline-flex items-baseline gap-1 cursor-pointer hover:underline decoration-neutral-500 underline-offset-2 transition-all"
onClick={(e) => { e.stopPropagation(); onNavigate(c.tab); }}>
<span className="text-sm font-medium text-red-400">{counts.remindersCancelled}</span>
<span className="text-[10px] text-neutral-500">не придёт</span>
</span>
</>
)}
</div>
</button>
);
}