Dashboard
The dashboard is the authenticated home page (/) — a role-aware overview with a welcome heading and slim stats strip, then a Needs attention list (what needs you today), a money snapshot (for roles that can see it) and upcoming celebrations. Every visitor sees a version of it scoped to what their role can access.
Using it
Section titled “Using it”Nav: the app’s landing page after login — no separate menu entry needed.
What’s shown, top to bottom:
- Title + stats strip: a “Welcome back” heading with one slim line of numbers (the upcoming-events label links to
/events) — total families (and how many are active), total members, families/people added in the last 30 days, and upcoming events in the next 30 days. Member figures only show for roles that can view people; the events count shows for everyone. - Finish setting up checklist (
ADMINonly, first run): a card under the stats strip listing the setup steps still to do — church name, address and ABN, the church logo, people, income and expense categories, a bank account — each linking to the page where it’s done, with an “N of 5 done” progress bar. It disappears once every step is complete (and comes back if a step is undone). - Needs attention: one row per pending item, each with a count and a link to the page that resolves it. Rows with a zero count are hidden, and when nothing is pending you see a calm “All caught up” line instead. Only items your role can see and act on appear:
- Family updates to review →
/families/updates(editors: Admin, Pastor, Office Admin) - Membership applications to review →
/memberships(editors) - Unreconciled transactions → the Reconciliation page’s pending view (Admin, Pastor — the roles that can reconcile). Counts every unreconciled transaction up to today across all bank payment accounts (deactivated ones included). Cash accounts are excluded: their transactions mirror petty-cash activity and are never reconciled. The Reconciliation page works on one account at a time, so when more than one account has pending transactions the row reads “… across N accounts” and the link opens the account with the most, starting from its oldest one (that page lists at most 1,000 rows per view, so a very large backlog is cleared in batches).
- Open petty cash sessions →
/accounting/petty-cash(Admin, Pastor — the roles that can close a session) - Birthdays in the next 7 days →
/people/birthdays(roles that view people). Shown as “N+” when the birthday source list hit its fetch cap, since the true number may be higher.
- Family updates to review →
- Money (accounting-viewing roles only): one primary figure — this month’s giving so far, with the change against the same month last year — plus a tile per payment account showing its balance since the opening balance. Balance tiles are tinted green in credit and red when overdrawn, and the figure carries a
+/−marker so the sign never relies on colour alone. - People widgets: birthdays and wedding anniversaries in the next 7 days, plus every couple with an anniversary in the current calendar month.
- A What’s New footer surfacing recent release notes.
OFFICE_ADMIN sees the money section but not the accounting rows of Needs attention — it can view accounting but not reconcile or close petty cash. AUDITOR sees no Needs attention section at all (it has nothing to act on, so a permanent “All caught up” would be noise), and also gets no member figures or people widgets (those queries aren’t even run) — it’s an accounting-only role. VIEWER sees the people side only. EVENT_ORGANISER doesn’t reach the dashboard at all (redirected to their own /my-events).
How it works
Section titled “How it works”Implemented entirely as one server component (src/app/(dashboard)/page.tsx) that fires its data queries in parallel (Promise.all) and derives everything else in memory:
- Timezone-correct “today”: uses
sydneyToday()(src/lib/dates.ts— despite the legacy name, it resolves the date in the configuredAPP_TIMEZONE) rather than the server’s raw UTC clock — for timezones ahead of UTC the server’s UTC date can be a day behind for part of the day, which would otherwise skew “last 30 days,” “upcoming events,” and the birthday/anniversary windows by a day. - Birthday/anniversary data reuses the same pure matchers described in Birthdays and Celebrations (
upcomingBirthdays/upcomingAnniversaries) — the dashboard widgets are a display-only 7-day-window preview; sending emails happens on the dedicated Birthdays/Anniversaries pages, not here. - Fetch caps: the birthday and anniversary source queries are capped (
PERSON_FETCH_CAP,src/lib/constants.ts) so the widget query doesn’t grow unbounded with membership size — one query fetches one row past the cap purely to detect truncation, and shows a “may be incomplete” note when hit. Both queries use a deterministicorderBy: { id: "asc" }so the truncated set is stable rather than an arbitrary database ordering. - Decrypt safety: dates of birth and marriage-linked emails are decrypted with a safe wrapper that returns a sentinel on a corrupt/rotated-away ciphertext; a record that decrypts to an invalid date is dropped from the widget rather than poisoning the whole sort/window calculation for everyone else.
- Needs attention: built by a pure helper (
buildAttentionItems,src/lib/dashboardAttention.ts) from data the page loads; the same file builds the unreconciled deep link. Each row’s role gate matches who can act on it. The review-queue and accounting counts are plain database counts — nothing is decrypted to produce them (except the birthdays row, described at the end of this bullet). Pending family updates and membership applications use React-cached helpers insrc/lib/pendingUpdates.ts, shared with the sidebar badges so each runs once per page load. The birthdays row reuses the birthday widget’s 7-day list, which is built from decrypted dates of birth (the same capped fetch the widget already needs). - Giving figures and balances: month-to-date and same-month-last-year income aggregates, plus per-payment-account balances derived from each account’s configured opening balance — only computed at all when the viewer can see accounting. Unreconciled transactions come from one separate per-account
groupBy(count + oldest date) over bank accounts, run only for Admin/Pastor. - Setup checklist:
getSetupChecklist()(src/lib/setupChecklist.ts) runs one cheap indexed lookup per step, in parallel: the church name, address and ABN settings (or theCHURCH_NAME/CHURCH_ADDRESS/CHURCH_ABNenv vars; the shipped “Your Church” placeholder and blank values don’t count), then existence checks for aLOGOBrandingAsset, an activePerson, an active income or expenseAccount(the auto-createdXFERtransfer account doesn’t count) and an activeBANKPaymentAccount(the auto-created Petty Cash account doesn’t count). Nothing is decrypted, and a failed read just hides the card. TheSetupChecklistcard (src/components/dashboard/SetupChecklist.tsx) streams in its own Suspense boundary and renders nothing for other roles.
Configuration
Section titled “Configuration”No dedicated environment variables. “Today” follows APP_TIMEZONE (see Regional Configuration); the person-fetch cap is a code-level constant.