/saas/globalGlobal analytics dashboard
A worldwide traffic page: sessions, countries, weighted p95 and booked revenue, a dotted world map with a marker per country, the busiest countries as share bars, revenue by region, and the newest orders from anywhere.
Traffic is db.sessions — one row per country per hour over the last day, each carrying where the country is — and money is db.orders joined to db.customers. The region an order belongs to is not a column on the order: it is the region the session rows already record for that country, so the map and the revenue split agree by construction rather than through a second list written by hand. Latency and error rate are weighted by the sessions behind them, because an hour with twelve sessions cannot pull a day's p95 as hard as one with nine hundred. The map panel is a server component with no "use client" of its own: WorldMap lays its dot lattice out here, so neither that work nor dotted-map itself ever reaches the browser — Widget is the client boundary above it and the finished map arrives as its children. Composes AppShell, PageHeader, StatCardGroup, StatCard, DashboardGrid, Widget, WorldMap, RankList, ChartCard, BarChart, SimpleTable and StatusBadge.
Preview
import { REFERENCE_DATE } from "@/lib/sample-data"
import { AppShell } from "@/components/ui/app-shell"
import { DashboardGrid, DashboardGridItem } from "@/components/ui/dashboard-grid"
import { PageHeader } from "@/components/ui/page-header"
import { signOut } from "./actions"
import { CountrySessions } from "./components/country-sessions"
import { GlobalStats } from "./components/global-stats"
import { RecentOrders } from "./components/recent-orders"
import { RevenueByRegion } from "./components/revenue-by-region"
import { SessionsMap } from "./components/sessions-map"
import {
countryTraffic,
currentUser,
globalStats,
lastUpdated,
mapMarkers,
recentOrders,
revenueByRegion,
shellNotifications,
} from "./data"
import { NAV, ROUTES } from "@/lib/dashboards/saas/nav"
/**
* Global analytics. A server component: it reads the traffic and the book
* through `db` once and hands plain rows down, and the map is rendered here too
* — its dot lattice is laid out on the server, so neither the work nor the
* library it needs ever reaches the browser.
*/
export default function GlobalPage() {
return (
<AppShell
nav={NAV}
activeHref={ROUTES.global}
user={currentUser()}
notifications={shellNotifications()}
now={REFERENCE_DATE}
onSignOut={signOut}
>
<PageHeader
title="Global"
description="Where the traffic came from today, and what it was worth."
meta={lastUpdated()}
/>
<GlobalStats stats={globalStats()} />
<DashboardGrid>
<DashboardGridItem colSpan={{ base: 12, lg: 8 }}>
<SessionsMap markers={mapMarkers()} />
</DashboardGridItem>
<DashboardGridItem colSpan={{ base: 12, lg: 4 }}>
<CountrySessions rows={countryTraffic()} />
</DashboardGridItem>
<DashboardGridItem colSpan={{ base: 12, lg: 5 }}>
<RevenueByRegion regions={revenueByRegion()} />
</DashboardGridItem>
<DashboardGridItem colSpan={{ base: 12, lg: 7 }}>
<RecentOrders rows={recentOrders()} />
</DashboardGridItem>
</DashboardGrid>
</AppShell>
)
}Install
npx shadcn@latest add @vibra/saas-globalNeeds the @vibra registry in your components.json — set it up once.
Source
import { REFERENCE_DATE } from "@/lib/sample-data"
import { AppShell } from "@/components/ui/app-shell"
import { DashboardGrid, DashboardGridItem } from "@/components/ui/dashboard-grid"
import { PageHeader } from "@/components/ui/page-header"
import { signOut } from "./actions"
import { CountrySessions } from "./components/country-sessions"
import { GlobalStats } from "./components/global-stats"
import { RecentOrders } from "./components/recent-orders"
import { RevenueByRegion } from "./components/revenue-by-region"
import { SessionsMap } from "./components/sessions-map"
import {
countryTraffic,
currentUser,
globalStats,
lastUpdated,
mapMarkers,
recentOrders,
revenueByRegion,
shellNotifications,
} from "./data"
import { NAV, ROUTES } from "@/lib/dashboards/saas/nav"
/**
* Global analytics. A server component: it reads the traffic and the book
* through `db` once and hands plain rows down, and the map is rendered here too
* — its dot lattice is laid out on the server, so neither the work nor the
* library it needs ever reaches the browser.
*/
export default function GlobalPage() {
return (
<AppShell
nav={NAV}
activeHref={ROUTES.global}
user={currentUser()}
notifications={shellNotifications()}
now={REFERENCE_DATE}
onSignOut={signOut}
>
<PageHeader
title="Global"
description="Where the traffic came from today, and what it was worth."
meta={lastUpdated()}
/>
<GlobalStats stats={globalStats()} />
<DashboardGrid>
<DashboardGridItem colSpan={{ base: 12, lg: 8 }}>
<SessionsMap markers={mapMarkers()} />
</DashboardGridItem>
<DashboardGridItem colSpan={{ base: 12, lg: 4 }}>
<CountrySessions rows={countryTraffic()} />
</DashboardGridItem>
<DashboardGridItem colSpan={{ base: 12, lg: 5 }}>
<RevenueByRegion regions={revenueByRegion()} />
</DashboardGridItem>
<DashboardGridItem colSpan={{ base: 12, lg: 7 }}>
<RecentOrders rows={recentOrders()} />
</DashboardGridItem>
</DashboardGrid>
</AppShell>
)
}/**
* What /global reads. Traffic is `db.sessions` — one row per country per hour
* over the last day, each carrying where the country is — and money is
* `db.orders`, joined to `db.customers` for the account behind an order. The
* region an order belongs to is not a column on the order: it is the region the
* session rows already record for that country, so the two halves of the page
* agree by construction rather than by a second list written by hand. Nothing
* reads a clock: the window ends at REFERENCE_DATE.
*/
import { getInitials } from "@/lib/format"
import {
db,
REFERENCE_DATE,
type Member,
type Order,
type Session,
} from "@/lib/sample-data"
// The sessions are a read-only day of traffic. The orders and the accounts are
// read per call, never held at module scope: a sale rung up or refunded since
// the server started is in the next render's figures.
const SESSIONS = db.sessions.all()
const orders = (): Order[] => db.orders.all()
/** How many hours of traffic the page covers. */
export const WINDOW_HOURS = 24
const dollars = (cents: number): number => cents / 100
/** An order counts towards revenue once it is paid and until it is refunded. */
const billable = (order: Order): boolean =>
order.status === "paid" || order.status === "fulfilled"
type Place = { country: string; region: Session["region"]; lat: number; lng: number }
// One entry per country, folded out of the hourly rows: every hour of a country
// carries the same place, so the first one seen settles it.
const PLACES = new Map<string, Place>()
for (const row of SESSIONS) {
if (!PLACES.has(row.country)) {
PLACES.set(row.country, { country: row.country, region: row.region, lat: row.lat, lng: row.lng })
}
}
/** The region a country trades in, read off the traffic rather than a second list. */
export function regionOf(country: string): string {
return PLACES.get(country)?.region ?? "Rest of world"
}
export type CountryTraffic = {
country: string
region: string
lat: number
lng: number
sessions: number
/** Sessions weighted p95, in milliseconds. */
p95Ms: number
errorRate: number
}
// Every country's day, rolled up once: sessions summed, and latency and errors
// weighted by the sessions behind them — an hour with 12 sessions cannot pull
// the day's p95 as hard as one with 900.
const BY_COUNTRY: CountryTraffic[] = [...PLACES.values()]
.map((place) => {
const hours = SESSIONS.filter((row) => row.country === place.country)
const sessions = hours.reduce((total, row) => total + row.sessions, 0)
const weighted = (read: (row: Session) => number) =>
sessions === 0
? 0
: hours.reduce((total, row) => total + read(row) * row.sessions, 0) / sessions
return {
country: place.country,
region: place.region,
lat: place.lat,
lng: place.lng,
sessions,
p95Ms: Math.round(weighted((row) => row.p95Ms)),
errorRate: weighted((row) => row.errorRate),
}
})
.sort((a, b) => b.sessions - a.sessions)
/** Every country's day, busiest first. */
export function countryTraffic(): CountryTraffic[] {
return BY_COUNTRY
}
export type MapMarker = { id: string; lat: number; lng: number; value: number; label: string }
/** The same rows as markers, which is all the map needs. */
export function mapMarkers(): MapMarker[] {
return BY_COUNTRY.map((row) => ({
id: row.country,
lat: row.lat,
lng: row.lng,
value: row.sessions,
label: row.country,
}))
}
export type GlobalStat = {
key: string
label: string
value: number
description: string
format: "number" | "currency" | "duration" | "percent"
}
const TOTAL_SESSIONS = BY_COUNTRY.reduce((total, row) => total + row.sessions, 0)
/** The four headline numbers: how much traffic, from how far, how fast, worth how much. */
export function globalStats(): GlobalStat[] {
const p95 =
TOTAL_SESSIONS === 0
? 0
: BY_COUNTRY.reduce((total, row) => total + row.p95Ms * row.sessions, 0) / TOTAL_SESSIONS
const paid = orders().filter(billable)
const revenue = paid.reduce((total, order) => total + order.totalCents, 0)
return [
{
key: "sessions",
label: "Sessions",
value: TOTAL_SESSIONS,
description: `in the last ${WINDOW_HOURS} hours`,
format: "number",
},
{
key: "countries",
label: "Countries",
value: BY_COUNTRY.length,
description: `led by ${BY_COUNTRY[0]?.country ?? "nowhere"}`,
format: "number",
},
{
key: "p95",
label: "p95 response",
value: Math.round(p95),
description: "weighted by where the traffic came from",
format: "duration",
},
{
key: "revenue",
label: "Booked revenue",
value: Math.round(dollars(revenue)),
description: `across ${paid.length} paid orders`,
format: "currency",
},
]
}
export type RegionRevenue = { region: string; revenue: number }
const REGION_ORDER = ["Americas", "EMEA", "APAC", "Rest of world"]
/** Booked value per region, in the order a reader reads a map. */
export function revenueByRegion(): RegionRevenue[] {
const totals = new Map<string, number>()
for (const order of orders()) {
if (!billable(order)) continue
const region = regionOf(order.country)
totals.set(region, (totals.get(region) ?? 0) + order.totalCents)
}
return REGION_ORDER.filter((region) => totals.has(region)).map((region) => ({
region,
revenue: Math.round(dollars(totals.get(region) ?? 0)),
}))
}
export type OrderRow = {
id: string
number: string
account: string
country: string
region: string
status: Order["status"]
total: number
placedAt: Date
}
/** How many of the newest orders the table shows. */
const RECENT = 8
/** The newest orders, wherever they came from. */
export function recentOrders(): OrderRow[] {
const accounts = new Map(db.customers.all().map((customer) => [customer.id, customer]))
return orders()
.sort((a, b) => b.placedAt.getTime() - a.placedAt.getTime())
.slice(0, RECENT)
.map((order) => ({
id: order.id,
number: order.number,
// A sale the till rang up for nobody in particular has no account.
account: accounts.get(order.customerId)?.company ?? (order.customerId || "Walk-in"),
country: order.country,
region: regionOf(order.country),
status: order.status,
total: dollars(order.totalCents),
placedAt: order.placedAt,
}))
}
/** The bell's contents: the newest notifications, unread first in the panel. */
export function shellNotifications() {
return db.notifications
.all()
.sort((a, b) => b.at.getTime() - a.at.getTime())
.slice(0, 6)
.map(({ id, title, description, at, read, href }) => ({ id, title, description, at, read, href }))
}
function ownerRow(): Member {
return db.members.all().find((member) => member.role === "owner") ?? db.members.all()[0]
}
/** The person looking at the page: whoever owns this workspace. */
export function currentUser() {
const owner = ownerRow()
return { name: owner.name, email: owner.email, initials: getInitials(owner.name), avatarUrl: owner.avatarUrl }
}
const STAMP = new Intl.DateTimeFormat("en-US", {
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
timeZone: "UTC",
})
/** The window the traffic covers, as a line under the title. */
export function lastUpdated(): string {
return `${WINDOW_HOURS} hours to ${STAMP.format(REFERENCE_DATE)} UTC`
}"use server"
import { mockAuthAdapter } from "@/lib/auth-adapter"
import { type Result } from "@/lib/sample-data"
/**
* The one thing this page changes. A server action so the page can stay a
* server component and still hand the shell something to call, and a `Result`
* so the caller reads the same success-or-error shape every mutation returns.
*/
export async function signOut(): Promise<Result<{ signedOut: true }>> {
await mockAuthAdapter.signOut()
return { ok: true, data: { signedOut: true } }
}"use client"
import { formatCompact } from "@/lib/format"
import { RankList } from "@/components/ui/rank-list"
import { Widget } from "@/components/ui/widget"
import { type CountryTraffic } from "../data"
/** How many of the busiest countries the list names. */
const SHOWN = 8
/** The busiest countries, as share bars against the biggest of them. */
export function CountrySessions({ rows }: { rows: CountryTraffic[] }) {
const top = rows.slice(0, SHOWN)
return (
<Widget
data-widget="widget-saas-global-country-sessions"
title="Sessions by country"
description="The last 24 hours, busiest first"
className="h-full"
footer={`${top.length} of ${rows.length} countries`}
>
<RankList
items={top.map((row) => ({ label: row.country, value: row.sessions }))}
showRank
color="chart-1"
format={(value) => formatCompact(value)}
/>
</Widget>
)
}import { BanknoteIcon, GaugeIcon, MapPinnedIcon, UsersIcon } from "lucide-react"
import { formatCurrency, formatNumber } from "@/lib/format"
import { StatCard } from "@/components/ui/stat-card"
import { StatCardGroup } from "@/components/ui/stat-card-group"
import { type GlobalStat } from "../data"
// One icon per headline, keyed by the stat rather than its position, so the row
// can be reordered without the icons following the wrong numbers.
const ICONS: Record<string, React.ReactNode> = {
sessions: <UsersIcon />,
countries: <MapPinnedIcon />,
p95: <GaugeIcon />,
revenue: <BanknoteIcon />,
}
function print(stat: GlobalStat): string {
if (stat.format === "currency")
return formatCurrency(stat.value, "USD", { maximumFractionDigits: 0 })
if (stat.format === "duration") return `${formatNumber(stat.value, { maximumFractionDigits: 0 })} ms`
return formatNumber(stat.value, { maximumFractionDigits: 0 })
}
/** How much traffic, from how many places, how fast, and worth how much. */
export function GlobalStats({ stats }: { stats: GlobalStat[] }) {
return (
<StatCardGroup data-widget="widget-saas-global-global-stats" columns={4}>
{stats.map((stat) => (
<StatCard
key={stat.key}
label={stat.label}
value={print(stat)}
description={stat.description}
icon={ICONS[stat.key]}
/>
))}
</StatCardGroup>
)
}"use client"
import { SimpleTable, type SimpleTableColumn } from "@/components/ui/simple-table"
import { StatusBadge, type StatusVariant } from "@/components/ui/status-badge"
import { CurrencyCell, DateCell, TruncateCell } from "@/components/ui/table-cells"
import { Widget } from "@/components/ui/widget"
import { type OrderRow } from "../data"
// StatusBadge has no default for "fulfilled" or "refunded"; the other three
// resolve on their own and are named here so the whole vocabulary reads in one
// place.
const STATUS_MAP: Record<string, StatusVariant> = {
fulfilled: "success",
paid: "success",
pending: "warning",
refunded: "danger",
cancelled: "neutral",
}
const COLUMNS: SimpleTableColumn<OrderRow>[] = [
{
key: "number",
header: "Order",
cell: (row) => <span className="font-mono text-xs">{row.number}</span>,
},
{
key: "account",
header: "Account",
cell: (row) => <TruncateCell maxWidth={180}>{row.account}</TruncateCell>,
},
{
key: "country",
header: "Country",
cell: (row) => (
<span className="text-muted-foreground">
{row.country} · {row.region}
</span>
),
},
{
key: "status",
header: "Status",
cell: (row) => <StatusBadge status={row.status} map={STATUS_MAP} />,
},
{
key: "total",
header: "Total",
align: "right",
cell: (row) => <CurrencyCell value={row.total} />,
},
{
key: "placedAt",
header: "Placed",
align: "right",
cell: (row) => <DateCell date={row.placedAt} className="text-muted-foreground" />,
},
]
/** The newest orders, wherever in the world they came from. */
export function RecentOrders({ rows }: { rows: OrderRow[] }) {
return (
<Widget data-widget="widget-saas-global-recent-orders" title="Recent orders" description="The newest eight, from anywhere">
<SimpleTable columns={COLUMNS} rows={rows} rowKey="id" size="sm" hoverable />
</Widget>
)
}"use client"
import { formatCurrency } from "@/lib/format"
import { BarChart } from "@/components/ui/bar-chart"
import { ChartCard } from "@/components/ui/chart-card"
import { type RegionRevenue } from "../data"
const money = (value: number) => formatCurrency(value, "USD", { maximumFractionDigits: 0 })
/** Booked value split by the region the order's country trades in. */
export function RevenueByRegion({ regions }: { regions: RegionRevenue[] }) {
return (
<ChartCard
data-widget="widget-saas-global-revenue-by-region"
title="Revenue by region"
description="Booked value, by where the account is"
height={280}
className="h-full"
>
<BarChart
data={regions}
index="region"
series={[{ key: "revenue", label: "Booked", color: "chart-2" }]}
valueFormatter={money}
height={260}
showLegend={false}
/>
</ChartCard>
)
}import { formatNumber } from "@/lib/format"
import { Widget } from "@/components/ui/widget"
import { WorldMap } from "@/components/ui/world-map"
import { type MapMarker } from "../data"
const sessions = (value: number) =>
`${formatNumber(value, { maximumFractionDigits: 0 })} sessions`
/**
* A server component on purpose: the map lays its dot lattice out with
* `dotted-map`, and rendering it here means neither the work nor the library
* ever reaches the browser. Widget is the client boundary above it, and the
* finished map arrives as its children.
*/
export function SessionsMap({ markers }: { markers: MapMarker[] }) {
return (
<Widget
data-widget="widget-saas-global-sessions-map"
title="Where today came from"
description="Sessions in the last 24 hours, sized by how many"
className="h-full"
footer={`${markers.length} countries with traffic`}
>
<WorldMap markers={markers} height={320} valueFormatter={sessions} />
</Widget>
)
}