Skip to content

Recipe: Custom Dashboard

Build a custom dashboard view that displays analytics alongside your admin panel.

import { useRebaseContext } from "@rebasepro/app";
import { useEffect, useState } from "react";
function DashboardView() {
const context = useRebaseContext();
const [stats, setStats] = useState({
totalOrders: 0,
totalRevenue: 0,
activeProducts: 0,
recentOrders: []
});
useEffect(() => {
async function loadStats() {
// Use the data source to fetch aggregate data
// `find` resolves to { data, meta } — the rows are on `data`, and they are
// flat, so it is `o.total` rather than `o.values.total`.
const { data: orders } = await context.data
.collection<{ total: number }>("orders")
.find({ limit: 1000 });
const { data: products } = await context.data
.collection<Record<string, unknown>>("products")
.find({ where: { active: ["==", true] } });
setStats({
totalOrders: orders.length,
totalRevenue: orders.reduce((sum, o) => sum + (o.total ?? 0), 0),
activeProducts: products.length,
recentOrders: orders.slice(0, 5)
});
}
loadStats();
}, []);
return (
<div className="p-8">
<h1 className="text-xl font-semibold tracking-[-0.01em] mb-6">Dashboard</h1>
<div className="grid grid-cols-3 gap-4 mb-8">
<StatCard title="Total Orders" value={stats.totalOrders} />
<StatCard title="Revenue" value={`$${stats.totalRevenue.toFixed(2)}`} />
<StatCard title="Active Products" value={stats.activeProducts} />
</div>
<h2 className="text-sm font-semibold tracking-[-0.01em] mb-4">Recent Orders</h2>
<ul>
{stats.recentOrders.map(order => (
<li key={order.id}>
Order #{order.id} — ${order.values.total}
</li>
))}
</ul>
</div>
);
}
function StatCard({ title, value }: { title: string; value: string | number }) {
return (
<div className="bg-surface-100 dark:bg-surface-800 rounded-lg p-6">
<p className="text-xs text-surface-500">{title}</p>
<p className="text-xl font-semibold">{value}</p>
</div>
);
}
const views: AppView[] = [
{
slug: "dashboard",
name: "Dashboard",
view: <DashboardView />,
admin: {
icon: "dashboard",
group: "Analytics"
}
}
];

Pass it to the navigation controller:

const navigationStateController = useBuildNavigationStateController({
views,
collections: () => collections,
// These four are required — the controller resolves navigation against them.
authController,
data,
collectionRegistryController,
urlController
});

The dashboard now appears in the sidebar under “Analytics” and is accessible at /dashboard.

Groups named "Admin" or "Settings" sink below the others in the drawer, by string comparison on the name. That is easy to lose — translate the label and the ordering silently stops happening — so say it explicitly instead:

{ slug: "dashboard", name: "Dashboard", view: <DashboardView />, group: "Ajustes", pinToBottom: true }

Setting pinToBottom on any one view in a group pins the whole group, since the drawer orders groups rather than individual views.

A view component receives no props. To route somewhere — another custom view, a collection, an entity — reach for useUrlController, which is exported from @rebasepro/cms:

import { useUrlController } from "@rebasepro/cms";
function DashboardView() {
const urlController = useUrlController();
return (
<>
<button onClick={() => urlController.navigate(urlController.buildAppUrlPath("reports"))}>
Reports
</button>
<button onClick={() => urlController.navigate(urlController.buildUrlCollectionPath("orders"))}>
All orders
</button>
<button onClick={() => urlController.navigate(urlController.buildUrlCollectionPath("orders/B34SAP8Z"))}>
Order B34SAP8Z
</button>
</>
);
}

Build the path rather than hard-coding it: collection URLs are prefixed (orders/c/orders) and the prefix is not part of the public contract.

useSidePanel opens an entity in the side panel instead of navigating, which is usually what you want for a row in a list.

Install a charting library:

pnpm add recharts

Then use it in your dashboard:

import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip } from "recharts";
function RevenueChart({ data }) {
return (
<LineChart width={600} height={300} data={data}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="date" />
<YAxis />
<Tooltip />
<Line type="monotone" dataKey="revenue" stroke="#8884d8" />
</LineChart>
);
}