PVR Tech Studio
Dashboard

Dashboards

Eight ready-made dashboard screens — a skin-dependent home plus five dedicated views — composed from the widget library, charts, and the token system.

11 min read
Updated July 15, 2026

Overview

The template ships eight dashboard screens. They are not one configurable dashboard — each is a hand-composed page that demonstrates a different layout idiom and data-visualization style:

ScreenRouteChart libsWhat it shows
CRM (CrmDashboard)/ (default home)none (motion bars)StatCard KPI grid, a sales-pipeline funnel (animated width bars), lead sources, a recent-deals table.
Bento (BentoDashboard)/ (when skin = bento)none (motion bars)A greeting hero + a mixed-size tile grid (oversized revenue tile, metric tiles, chart tile, traffic, activity, top products).
Console (ConsoleDashboard)/ (when skin = console)none (motion bars)A command-prompt bar, dense left-accent KPI cells, a terminal-styled log feed, live system metrics, and an orders table — the bespoke dashboard for the Console design.
Analytics/dashboards/analyticsECharts (+ Chart.js)Sparkline KPI strip, a traffic-by-channel stacked area with 7d/30d/12m tabs, a realtime card, a user-flow Sankey, acquisition breakdown, an engagement scatter, devices donut, a GitHub-style calendar heatmap, top countries, a bento "Highlights" grid, and a rich top-pages table with filter toolbar.
eCommerce/dashboards/ecommerceECharts + ApexChartsKPI strip, a revenue + orders dual-axis combo, a sales funnel, a category treemap, traffic donut, store-goals radialBar, top products (photos + progress), order-status breakdown, a recent-orders Panel table with row actions, and an activity timeline.
Project/dashboards/projectsApexCharts + EChartsKPI strip, a project-timeline Gantt (rangeBar), sprint burndown, completion gauge, priority breakdown, a team-workload radar, activity feed, a Kanban board strip fed by the real scrumboard seed, per-member workload, and a milestones timeline.
Finance/dashboards/financeChart.js + ApexChartsWelcome hero + 8 mini stat tiles, gradient KPI strip, a report panel, a net-profit bubble chart, expense donut, revenue-vs-expenses grouped bars, a transactions Panel with an Apex heatmap, a payment-schedule mini calendar + alerts rail, and an insights row (budget gauge, income sources, cash flow, revenue streams, top clients).
Marketing/dashboards/marketingChart.jsKPI strip, a sessions + conversions grouped-bar trend with week/month/quarter tabs, channels donut, conversion-goal gauge, weekly-clicks mini bars, audience breakdown, a campaigns Panel table (owner avatars, budget progress, ROAS trend pills), and an activity timeline.

The home route (/) is skin-dependent — the same URL renders a different dashboard depending on the active design skin (see Configuration). The five dedicated dashboards are always reachable from the sidebar (Dashboard section) and are React.lazy-loaded so their chart libraries split out of the main bundle. None of the dashboards use Recharts — it is demoed on /charts/recharts only (see Charts).

Architecture & files

The eight dashboards are page components composed from a shared widget library, with the Bento home also drawing on a small bento-grid layer.

Dashboard screens (src/pages/dashboards/)

FileResponsibility
CrmDashboard.tsxDefault home (/) — KPI + funnel, no charts library.
BentoDashboard.tsxBento-skin home — bento tile grid.
ConsoleDashboard.tsxConsole-skin home — terminal styling.
AnalyticsDashboard.tsx/dashboards/analytics (lazy) — ECharts + widget cards.
EcommerceDashboard.tsx/dashboards/ecommerce (lazy) — ECharts + ApexCharts.
ProjectDashboard.tsx/dashboards/projects (lazy) — ApexCharts + ECharts.
FinanceDashboard.tsx/dashboards/finance (lazy) — Chart.js + ApexCharts.
MarketingDashboard.tsx/dashboards/marketing (lazy) — Chart.js.

Shared building blocks

FileResponsibility
src/components/widgets/The reusable widget cards the dashboards compose (see Widgets doc).
src/components/bento/BentoGrid.tsxThe responsive grid wrapper.
src/components/bento/Tile.tsxA single bento tile (span / rowSpan / tone).
src/components/bento/index.tsBarrel — { BentoGrid, Tile, TileTone }.

Home selection lives in src/App.tsx:

// src/pages/HomeRoute.tsx (shape)
export function HomeRoute() {
    const {skin} = useLayout()
    const el =
        skin === 'bento' ? <BentoDashboard /> : skin === 'console' ? <ConsoleDashboard /> : <CrmDashboard />
    return <Suspense fallback={<PageLoader />}>{el}</Suspense>
}

The / index is handled directly in App.tsx (not as an appRoutes entry) precisely because it is skin-dependent. The five dedicated dashboards are registered in src/routes.tsx:

// src/routes.tsx (excerpt)
{path: '/dashboards/analytics', element: <AnalyticsDashboard />},
{path: '/dashboards/ecommerce', element: <EcommerceDashboard />},
{path: '/dashboards/projects', element: <ProjectDashboard />},
{path: '/dashboards/finance', element: <FinanceDashboard />},
{path: '/dashboards/marketing', element: <MarketingDashboard />},

Building blocks

Every dashboard is composed from the same shared pieces — there is no dashboard-specific framework:

  • The widget library (src/components/widgets/, barrel index.ts) does most of the work on the dedicated dashboards: KpiTile (the KPI strips — each dashboard picks a different variant: Analytics flat+tint, Marketing minimal, Finance gradient, Project glass, eCommerce backgroundIcon), DonutWidget/GaugeWidget (Chart.js doughnuts), BreakdownWidget, BarStatWidget, StatListChartWidget, ReportPanelWidget, WelcomeWidget, ActivityFeedWidget, TimelineWidget, PeopleListWidget, MiniCalendarWidget, NotificationsWidget, plus the TrendPill/Sparkline/MiniBarChart primitives. See Widgets.
  • Bespoke inline charts where a widget doesn't fit: each dashboard defines small chart components at the top of its own file (e.g. Analytics' TrafficArea/FlowSankey/EngagementScatter/ ActivityCalendar on ECharts, Finance's RevExpChart/NetProfitChart on Chart.js and TxnHeatmap on ApexCharts, Project's GanttChart on ApexCharts). All pull colors from useChartTokens() (src/hooks/useChartTokens.ts), which reads the raw --* CSS variables (including a categorical series palette) and re-reads on theme/skin change — so every chart stays theme- and skin-aware. Chart.js animations branch on useReducedMotion().
  • Card / CardBody (src/components/ui) for chart cards; the big data tables (Finance transactions, Marketing campaigns, eCommerce recent orders) sit in a Panel with fill + refreshable (see Panel).
  • StatCard (src/components/ui) is the KPI tile on the CRM home (label, animated value, delta, hint). KPI strips carry the marker class stat-grid (the Prism skin colours each .stat-icon a different hue via that class).
  • Interactive controls on the richer dashboards: segmented Tabs for chart periods (Analytics 7d/30d/12m, Finance month/quarter/year, Marketing week/month/quarter), and the Analytics top-pages toolbar (Select channel filter, DateRangePicker, export Button, overflow Dropdown with toast-backed actions). eCommerce order rows have a portaled Dropdown action menu.
  • Motion — sections are Stagger/StaggerItem grids; CRM/Bento/Console draw bars with animated-width/scale motion divs; count-ups use AnimatedNumber (src/components/motion).
  • Dates and i18n — labels are keys in the dashboard namespace; axis/date labels come from the Intl helpers in src/lib/dates.ts (monthsShort, monthDayShort, relTimeShort) fed i18n.language, never from i18n keys.
  • Cross-app data reuse — the Project dashboard's Kanban strip and team-workload card read the real scrumboard seed (initialBoard(), members, labelKeyFor, formatDueDate from src/data/scrumboard.ts), so it always matches the Scrumboard app.
  • Decorative art — the Analytics tinted cards use small illustration PNGs from public/illustrations/ (hl-*.png, aria-hidden), country rows reuse the language-menu flag SVGs in public/flags/, and eCommerce product imagery comes from public/photos/.

The Bento tile system

BentoDashboard uses two components from src/components/bento/:

  • <BentoGrid> — the responsive CSS grid wrapper.
  • <Tile span rowSpan tone> — a single tile. span is 1 | 2 | 3 | 4, rowSpan is 1 | 2, and tone is a TileTone: 'surface' | 'accent' | 'sage' | 'butter' | 'sky' | 'blush'. The tint tones (sage/butter/sky/blush) map to --tint-* tokens defined under [data-skin="bento"], so they only render as intended within the Bento skin (harmless elsewhere).

Usage

The dashboards are reached through the sidebar or by navigating to their routes. To see the skin-dependent home, switch skins from the Layout Customizer, the ⌘K palette, the header DesignSystemMenu, or the Layout Settings page.

The most common customization is editing the seed arrays at the top of a dashboard file — every dashboard keeps its demo data in module-level const arrays (KPIS, TRAFFIC, TRANSACTIONS, CAMPAIGNS, GANTT, TOP_PRODUCTS, …) whose text fields are i18n keys resolved at render (proper nouns / numbers stay literal). Period-switchable charts keep one dataset per period in a keyed record (e.g. Finance's REVEXP.month/quarter/year) selected by the segmented-tabs state.

API / Props

The dashboards are route screens with no props. The reusable pieces they compose:

ComponentSourcePurpose
KpiTile, DonutWidget, GaugeWidget, …src/components/widgetsThe widget cards the dedicated dashboards are built from — see Widgets for props tables.
TrendPill / Sparkline / MiniBarChartsrc/components/widgets/primitivesDelta pill, inline sparkline (line/bar/area), mini weekly bars.
StatCardsrc/components/uiCRM KPI tile — label, value, delta, prefix/suffix/decimals, hint, icon.
Card / CardHeader / CardBody, Panelsrc/components/uiContent panels; Panel adds the portlet toolbar (fill, refreshable, …).
Stagger / StaggerItem / AnimatedNumbersrc/components/motionEntrance stagger + count-up.
BentoGridsrc/components/bentoBento grid wrapper.
Tilesrc/components/bentoBento tile — span?: 1..4, rowSpan?: 1..2, tone?: TileTone.
useChartTokens()src/hooks/useChartTokens.tsTheme/skin-aware chart colors (raw --* tokens + a categorical series palette).

SectionHeading (src/components/ui/SectionHeading.tsx)

The house section-divider heading for grouping panels inside a page's Stagger grid — an icon chip + title with a muted hint over a bottom border. It renders a StaggerItem, so it drops straight into the grid as a full-width row. Exported from the src/components/ui barrel; the form pages (src/pages/forms/*) use it throughout, and it is the primitive to reach for when a dashboard page needs labelled section groups.

PropTypeDefaultDescription
titleReactNode— (required)The heading text.
hintReactNodeMuted one-line description under the title.
iconReactNodeOptional leading icon, shown in a primary-tinted chip.
classNamestring'lg:col-span-2'Wrapper class — override for non-2-col grids (e.g. lg:col-span-3).

PageHeader vs. direct title: the CRM, Console, and dedicated dashboards render <PageHeader title … breadcrumbs … action /> (which sets the tab title automatically; the action slot holds an Export button). Bento has its own greeting hero instead, so it calls useDocumentTitle(...) directly.

Configuration & customization

  • Change which dashboard is home — edit HomeRoute() in src/pages/HomeRoute.tsx, or simply switch the active skin (Bento → BentoDashboard, Console → ConsoleDashboard, any other skin → CrmDashboard).
  • Edit the numbers/labels — the seed arrays live at the top of each dashboard file; the labels are dashboard: i18n keys (add or edit them in src/locales/en/dashboard.json).
  • Recolour charts — never hardcode a hex. Pull from useChartTokens() so light/dark/skins stay in sync. See Charts.
  • Swap a KPI strip's look — change the KpiTile variant (flat/minimal/gradient/glass/ backgroundIcon/…); the data props stay identical.
  • Add a new dashboard — create src/pages/dashboards/MyDashboard.tsx, add a lazy appRoutes entry in src/routes.tsx, a menu leaf in src/data/menu.ts, and a nav: label. See Architecture & Routing.

Examples

A minimal dashboard following the current house pattern — a KpiTile strip plus a token-themed ECharts card:

import {useTranslation} from '@/platform/i18n'
import ReactECharts from 'echarts-for-react'
import {Activity, Users} from 'lucide-react'
import {PageHeader} from '@/layout/PageHeader'
import {Card, CardBody} from '@/components/ui'
import {Stagger, StaggerItem} from '@/components/motion'
import {KpiTile} from '@/components/widgets'
import {useChartTokens} from '@/hooks/useChartTokens'
 
const KPIS = [
    {labelKey: 'anSessions', value: 84200, icon: Activity, tone: 'info', delta: 7.8},
    {labelKey: 'anUsers', value: 21400, icon: Users, tone: 'primary', delta: 4.3},
] as const
 
export function MyDashboard() {
    const {t} = useTranslation(['dashboard', 'nav'])
    const tk = useChartTokens() // theme/skin-aware colors
 
    return (
        <>
            <PageHeader title={t('nav:analyticsDashboard')} />
 
            <Stagger className="stat-grid grid grid-cols-1 gap-5 sm:grid-cols-2 xl:grid-cols-4">
                {KPIS.map((k) => (
                    <KpiTile key={k.labelKey} label={t(k.labelKey)} value={k.value} icon={k.icon}
                        variant="flat" tone={k.tone} delta={k.delta} />
                ))}
            </Stagger>
 
            <Stagger className="mt-5 grid grid-cols-1">
                <StaggerItem>
                    <Card>
                        <CardBody>
                            <ReactECharts
                                notMerge
                                style={{height: 300, width: '100%'}}
                                option={{
                                    backgroundColor: 'transparent',
                                    xAxis: {type: 'category', data: ['Mon', 'Tue', 'Wed'],
                                        axisLabel: {color: tk.mutedForeground}},
                                    yAxis: {type: 'value', splitLine: {lineStyle: {color: tk.border}}},
                                    series: [{type: 'line', data: [220, 340, 290], smooth: true,
                                        lineStyle: {color: tk.primary}, itemStyle: {color: tk.primary},
                                        areaStyle: {opacity: 0.2, color: tk.primary}}],
                                }}
                            />
                        </CardBody>
                    </Card>
                </StaggerItem>
            </Stagger>
        </>
    )
}

A Bento tile grid:

import {BentoGrid, Tile} from '@/components/bento'
import {AnimatedNumber} from '@/components/motion'
 
<BentoGrid>
    <Tile span={2} rowSpan={2} tone="accent">
        <AnimatedNumber value={48210} prefix="$" />
    </Tile>
    <Tile tone="sky">…</Tile>
    <Tile tone="sage">…</Tile>
    <Tile span={3} rowSpan={2} tone="surface">…</Tile>
</BentoGrid>

Best practices

  • Keep demo data in module-level const arrays with i18n keys for text — never hardcode user-facing strings inline. Dates/months come from src/lib/dates.ts (never i18n keys).
  • Pull chart colors from useChartTokens(); never a raw hex or a Tailwind palette color. Gate Chart.js animation on useReducedMotion() (see Finance/Marketing chart components).
  • Reach for a widget (src/components/widgets/) before hand-building a card; write a bespoke inline chart component only when no widget fits, and keep it co-located in the dashboard file.
  • Put big data tables in a Panel (fill + refreshable), not a bare Card.
  • Lazy-load any new heavy (chart-driven) dashboard so it splits out of the main bundle.
  • For KPI tiles, group them in a .stat-grid Stagger so the entrance stagger and the Prism-skin per-icon coloring both work.
  • Give every chart card the equal-height flex-fill chain (Card className="flex h-full flex-col"CardBody className="flex flex-1 flex-col" → chart wrapper flex-1) so grid rows stay level.

Troubleshooting

SymptomCause / fix
Home shows the wrong dashboardThe active skin drives / — Bento/Console skins swap the home. Switch skin or edit HomeRoute().
Chart colors don't follow the themeYou hardcoded colors. Read them from useChartTokens() instead.
Chart colors stale after switching skinEnsure the component calls useChartTokens() in render — it re-reads the CSS vars on theme/skin change.
ApexCharts tooltip/labels stay light in dark modePass theme: {mode: isDark ? 'dark' : 'light'} from the .dark class on <html> (see TxnHeatmap, GanttChart, StoreGoals).
Bento tint tiles look greyThe sage/butter/sky/blush tints resolve only under the Bento skin's --tint-* tokens.
Project board strip is empty/staleIt renders initialBoard() from src/data/scrumboard.ts (the seed, not the user's persisted board) — edit the seed to change it.
New dashboard 404sAdd an appRoutes entry in src/routes.tsx (and a menu leaf + nav: label).

FAQ

Why is / handled in App.tsx and not routes.tsx? Because the home is skin-dependent — the same URL renders CrmDashboard, BentoDashboard, or ConsoleDashboard depending on the active skin.

Which dashboards use which chart library? Analytics and eCommerce lean on ECharts (area/Sankey/ scatter/calendar-heatmap; combo/funnel/treemap), Project mixes ApexCharts (Gantt) with ECharts (burndown/gauge/radar), Finance mixes Chart.js (bars/bubble) with ApexCharts (heatmap), and Marketing is Chart.js. The widget cards they all share (DonutWidget, GaugeWidget, Sparkline, MiniBarChart) render with Chart.js or plain SVG. CRM, Bento, and Console draw their bars with animated motion divs (no chart dependency). Recharts is demoed only on the /charts/* showcase pages — see Charts.

Can I make a dashboard the default that isn't tied to a skin? Yes — change the fallback in HomeRoute() (the <CrmDashboard /> fallback).

Are the interactive controls wired to real data? The period tabs really switch datasets, and the Panel refresh spins a simulated async refresh; the export buttons, dropdown row actions, and the Analytics channel/date filters are presentational (they fire toasts) — swap in src/lib/api.ts calls to make them live.

Notes for designers & content editors

  • All dashboard copy (labels, headings, table columns) lives in the dashboard: and nav: i18n namespaces — edit src/locales/en/dashboard.json to reword, not the component files.
  • Numbers, company names, and product names in the seed arrays are literal demo content; localize only through the *Key fields. Dates/weekday/month labels localize automatically via Intl.
  • The Analytics page's tinted "engage" cards pair each section with a small illustration from public/illustrations/hl-*.png and a semantic tone tint (bg-primary/10, bg-info/10, …) — reuse that pattern (image aria-hidden, tint + matching ring) when adding sections there.
  • The Bento and Console dashboards carry the personality of their designs — Bento uses the display font and warm tints; Console uses the monospace font and terminal styling. They are meant to be shown when those skins are active.

Was this page helpful?