Documentation Index
Fetch the complete documentation index at: https://grantmaster.dev/llms.txt
Use this file to discover all available pages before exploring further.
Portfolio Dashboard
Overview
The Portfolio Dashboard is a role-aware, widget-based analytics hub that provides each user with a curated, domain-organized view of their organization’s data. It is divided into eight tabs — one personal daily-briefing tab and seven specialty tabs — each showing widgets relevant to a specific operational domain.
Users can customize the Personal tab and reorganize widgets. SuperAdmins can additionally customize all specialty tabs. Widgets are role-gated: each user sees only widgets appropriate to their system role.
Navigation and Routing
The dashboard is accessible at /overview/:tab. The available tab IDs map to:
| Tab ID | URL Segment | Domain |
|---|
dashboard | /overview/dashboard | Personal (daily briefing) |
projects | /overview/projects | Project lifecycle |
finance | /overview/finance | Financial health |
grants | /overview/grants | Grant pipeline |
users | /overview/users | Team capacity |
mission | /overview/mission | Impact and strategy |
compliance | /overview/compliance | Audit and risk |
platform | /overview/platform | Platform intelligence (SuperAdmin only) |
Legacy ?tab= query parameter URLs are automatically redirected to the path-based format.
The active tab ID is persisted in currentUserProfile.widgetPreferences.activeDashboardId.
Component Architecture
DashboardTabs (src/features/dashboard/DashboardTabs.tsx)
└── DynamicDashboard (src/components/DynamicDashboard.tsx)
├── useWidgetDashboard (src/hooks/useWidgetDashboard.ts)
└── WidgetDashboardBase (src/components/widgets/WidgetDashboardBase.tsx)
└── [Widget Components] (lazy-loaded per category)
DashboardTabs
src/features/dashboard/DashboardTabs.tsx is the entry point. It:
- Reads
:tab from the URL and maps it to a DashboardTabId
- Builds the tab list (SuperAdmins get the extra
platform tab)
- Renders
<PageTabs> for navigation
- Renders the active tab’s
<DynamicDashboard> with the appropriate props
- Passes domain-specific
infoWidgets (quick stat cards) where applicable
DynamicDashboard
src/components/DynamicDashboard.tsx is the shared dashboard container. It accepts:
| Prop | Type | Description |
|---|
dashboardId | DashboardTabId | Identifies which dashboard to load (default: 'projects') |
categoryFilter | (category: WidgetCategory) => boolean | Optional override; falls back to DashboardPresetService |
showGreeting | boolean | Show personalized greeting header (default: true on projects) |
title | string | Title override |
infoWidgets | ReactNode | Quick stat cards rendered above the widget grid |
Permission model: Only the dashboard (Personal) tab is editable by all users. All other tabs require the isSuperAdmin RBAC check to enable edit controls.
Density preference: The grid gap class is derived from currentUserProfile.dashboardPreferences.density:
| Density | Gap |
|---|
compact | gap-2 md:gap-3 (8px / 12px) |
comfortable (default) | gap-3 md:gap-4 (12px / 16px) |
spacious | gap-4 md:gap-6 (16px / 24px) |
The component renders via WidgetDashboardBase, which handles drag-and-drop reordering, loading skeletons, the widget library panel, and empty states.
src/components/widgets/WidgetDashboardBase.tsx is the shared rendering engine for all dashboards. It consumes the useWidgetDashboard hook’s state and actions to provide:
- Drag-and-drop widget reordering (via dnd-kit or similar)
- Loading skeleton layout
- Widget library slide-in panel (
setShowLibrary)
- Empty state with optional “Add Widgets” call-to-action
- Lazy-loaded widget rendering
Each widget belongs to exactly one WidgetCategory. The category determines which dashboard tab the widget appears on:
| Category | Tab |
|---|
PERSONAL | Personal (dashboard) |
PROJECTS | Projects |
FINANCE | Finance |
GRANTS | Grants |
USERS | Users |
MISSION | Mission |
COMPLIANCE | Compliance |
PLATFORM | Platform |
The TAB_CATEGORY_FILTERS map in DashboardPresetService defines the filter function for each tab. The Personal tab uses c === WidgetCategory.PERSONAL, ensuring only personal-briefing widgets appear there, not all 80+ widgets across other categories.
| Tab | Approximate Widget Count |
|---|
| Personal | ~16 available; ~10 shown by default (role-dependent) |
| Projects | 7 |
| Finance | 13 |
| Grants | 12 |
| Users | 11 |
| Mission | 9 |
| Compliance | 10 |
| Platform | 1 (quota-usage) |
Widgets are assigned to roles at the registry level. The useWidgetDashboard hook filters the full widget registry down to those assigned to the current user’s role before handing them to DashboardPresetService.
Roles and their typical access breadth:
| Role | Access Level |
|---|
SUPER_ADMIN | All widgets on all tabs |
ADMIN | All widgets except advanced SuperAdmin-only widgets |
MANAGER | Operational widgets relevant to team management |
MEMBER | Limited view — basic project and personal widgets |
AUDITOR | Compliance, audit, and reporting widgets |
Dashboard Preset Service
src/shared/platform/dashboardPresetService.ts is the source of truth for default widget configurations.
Computes the initial visible/hidden widget split for a user when they first access a dashboard tab.
Algorithm:
- Look up the curated order for
(dashboardId, role) from CURATED_ORDERS.
- Filter
assignedWidgets by the tab’s category filter.
- Add curated widgets first (in curated order), if available for the role.
- For specialty tabs (not
'dashboard'): append remaining category widgets sorted by assignment.priority.
- For the Personal tab (
'dashboard'): skip step 4 — only curated widgets are shown by default. Remaining widgets go to the library.
- Return
{ visibleOrder: string[], hidden: string[] }.
The Personal tab’s restriction prevents users from being shown all 80+ widgets on first load. They can add more from the widget library.
CURATED_ORDERS defines the default visible order per (DashboardTabId, SystemRole). Highlights:
Personal tab — SUPER_ADMIN / ADMIN:
organization-health-scorecard, ai-insights-feed, my-deadlines, action-items, quick-actions, personal-contract, deadline-summary, notification-preview, quick-time-entry
Projects tab — SUPER_ADMIN / ADMIN:
project-portfolio, project-phase-distribution, budget-vs-timeline, deliverable-tracker, project-risk-register, report-submission-pipeline, project-insights
Finance tab — SUPER_ADMIN:
cash-flow-forecast, operating-reserve-months, restricted-unrestricted-funds, fund-drawdown-schedule, burn-rate-comparison, donor-revenue-concentration, overhead-ratio, indirect-cost-recovery, subcontractor-spend, budget-modification-tracker, budget-utilization, budget-health, budget-composition
Grants tab — SUPER_ADMIN:
grant-revenue-pipeline, grant-application-calendar, grant-milestone-tracker, reporting-burden-heatmap, grant-health-matrix, grant-closeout-checklist, funder-relationship-health, grant-success-rate, benchmarking, predictive-closeout-risk, proposal-win-rate, active-grant-map
Users tab — SUPER_ADMIN / ADMIN:
team-capacity, team-capacity-forecast, staff-allocation-sankey, team-availability, leave-calendar-overview, grant-expertise-map, volunteer-contribution, cost-per-fte, user-utilization, submission-rate, grant-admin-workload
Mission tab — SUPER_ADMIN / ADMIN:
theory-of-change-tracker, impact-metrics-summary, mission-pillar-progress, strategic-impact-radar, sdg-alignment-dashboard, grant-portfolio-matrix, donor-stewardship-pipeline, grant-diversification, beneficiary-reach
Compliance tab — SUPER_ADMIN / ADMIN / AUDITOR (same set):
compliance-calendar, grantor-compliance-matrix, audit-readiness-score, compliance-score, policy-violation-trends, expense-eligibility-heatmap, document-compliance, funder-compliance, reporting-schedules, regulatory-risk-heatmap
The RESERVED_LIBRARY_WIDGETS set contains widget IDs that are never shown by default and are only available for manual addition from the library. Currently: key-metrics.
Dashboard Customization
Edit Mode
When edit mode is active (isEditing === true), the header shows two buttons:
- Add Widget — opens the widget library panel (
setShowLibrary(true))
- Done — exits edit mode (
setIsEditing(false))
In non-edit mode, a single Edit button (with an animated pencil icon) enters edit mode.
Edit mode is only available when canCustomizeDashboard is true:
const canCustomizeDashboard = dashboardId === 'dashboard' || isSuperAdmin;
A pill badge shows {visibleWidgets.length}/{allCategoryWidgets.length} Widgets. If the user can customize the dashboard, clicking the pill opens the widget library. Otherwise, it is a static display.
User Preferences Storage
Widget visibility and order are persisted in currentUserProfile (the employees Firestore collection document for the user):
widgetPreferences — per-tab visible/hidden widget arrays and order
dashboardPreferences — display settings (density, showGreeting, showTooltips, showOnboardingProgress)
Personal Tab Features
The Personal tab (dashboardId === 'dashboard') has additional header elements not present on specialty tabs:
Personalized Greeting
When dashboardPreferences.showGreeting is enabled, the h1 displays a time-aware personalized greeting generated by getPersonalizedGreeting(currentUserProfile?.name) from src/lib/greetings.ts.
DashboardTooltip (src/components/DashboardTooltip.tsx) renders a rotating contextual tip below the header when dashboardPreferences.showTooltips is enabled.
InfoWidgets are quick-stat cards rendered between the header and the widget grid. They are not part of the draggable widget system — they are always shown and cannot be hidden or reordered.
Each specialty tab in DashboardTabs passes its own infoWidgets prop:
| Tab | InfoWidgets Component |
|---|
| Finance | FinanceInfoWidgets |
| Projects | ProjectsInfoWidgets |
| Grants | GrantsInfoWidgets |
| Users | UsersInfoWidgets |
| Mission | MissionInfoWidgets |
| Compliance | ComplianceInfoWidgets |
Dashboard Template Editor
DashboardTemplateEditor is a separate admin-only component that allows SuperAdmins to configure the default widget layout templates for each tab and role. It is distinct from the user-facing edit mode — it modifies the templates that are applied when new users (or users without existing preferences) first visit a dashboard tab.
src/hooks/useWidgetDashboard.ts is the primary state management hook. It accepts:
{
categoryFilter: (category: WidgetCategory) => boolean;
dashboardId: DashboardTabId;
}
It returns { state, actions }:
State:
isEditing — whether edit mode is active
visibleWidgets — ordered array of widget instances currently shown
hiddenWidgets — widget instances available in the library
allCategoryWidgets — all widgets for this category (visible + hidden)
isLoading — loading state for widget registry/preferences
Actions:
setIsEditing(boolean)
setShowLibrary(boolean)
moveWidget(fromIndex, toIndex) — reorder within the grid
showWidget(widgetId) / hideWidget(widgetId) — toggle library membership
resetToDefaults() — reapply DashboardPresetService.computePreset for the user’s role
Data Flow
1. User navigates to /overview/:tab
2. DashboardTabs reads :tab → selects DashboardTabId
3. DynamicDashboard resolves effectiveCategoryFilter via DashboardPresetService
4. useWidgetDashboard loads:
a. User's role from RBACContext
b. Widget registry (filtered by role + category)
c. User preferences from currentUserProfile (or computes preset if none)
5. WidgetDashboardBase renders visible widgets (lazy-loaded)
6. User can drag to reorder or open widget library to show/hide
7. Changes are persisted back to currentUserProfile in Firestore
Key Files Reference
| File | Purpose |
|---|
src/features/dashboard/DashboardTabs.tsx | Tab navigation, tab → category mapping |
src/features/dashboard/index.ts | Public API barrel export |
src/components/DynamicDashboard.tsx | Dashboard container with header, edit controls |
src/components/widgets/WidgetDashboardBase.tsx | Shared DnD/rendering engine |
src/hooks/useWidgetDashboard.ts | State management hook |
src/shared/platform/dashboardPresetService.ts | Preset service: category filters, curated orders |
src/lib/greetings.ts | Personalized greeting generator |
src/components/DashboardTooltip.tsx | Rotating contextual tip |