Skip to content

Dashboard

A dashboard that fetches data from APIs, displays stats, and updates in real time. Demonstrates resource(), multiple components sharing state, and composing widgets.

  • Stats overview with key metrics
  • A list of recent orders fetched from an API
  • A revenue summary widget
  • Refresh button to reload all data
stores/dashboard.ts
import { signal, computed, resource } from "@whisq/core";
export interface Order {
id: number;
customer: string;
amount: number;
status: "pending" | "shipped" | "delivered";
}
export const orders = resource<Order[]>(() =>
fetch("/api/orders").then(r => r.json())
);
export const totalRevenue = computed(() =>
(orders.data() ?? []).reduce((sum, o) => sum + o.amount, 0)
);
export const ordersByStatus = computed(() => {
const list = orders.data() ?? [];
return {
pending: list.filter(o => o.status === "pending").length,
shipped: list.filter(o => o.status === "shipped").length,
delivered: list.filter(o => o.status === "delivered").length,
};
});
export const refreshAll = () => {
orders.refetch();
};
components/StatCard.ts
import { component, div, span, h3 } from "@whisq/core";
interface StatCardProps {
label: string;
value: () => string;
color?: string;
}
const StatCard = component((props: StatCardProps) =>
div({ class: "stat-card" },
h3(props.label),
span({ class: "stat-value", style: () => props.color ? `color: ${props.color}` : "" },
props.value,
),
)
);
export default StatCard;
components/OrdersTable.ts
import { component, div, table, thead, tbody, tr, th, td, p, when, each } from "@whisq/core";
import { orders } from "../stores/dashboard";
const OrdersTable = component(() =>
div({ class: "orders-table" },
when(() => orders.loading(), () => p("Loading orders...")),
when(() => !!orders.error(), () => p({ class: "error" }, "Failed to load orders")),
when(() => !!orders.data(), () =>
table(
thead(
tr(
th("ID"),
th("Customer"),
th("Amount"),
th("Status"),
),
),
tbody(
each(() => orders.data()!, (order) =>
tr(
td(`#${order.id}`),
td(order.customer),
td(`$${order.amount.toFixed(2)}`),
td({ class: () => `status-${order.status}` }, order.status),
)
),
),
)
),
)
);
export default OrdersTable;
components/Dashboard.ts
import { component, div, h1, button } from "@whisq/core";
import { totalRevenue, ordersByStatus, orders, refreshAll } from "../stores/dashboard";
import StatCard from "./StatCard";
import OrdersTable from "./OrdersTable";
const Dashboard = component(() =>
div({ class: "dashboard" },
div({ class: "header" },
h1("Dashboard"),
button({ onclick: refreshAll }, "Refresh"),
),
div({ class: "stats-grid" },
StatCard({
label: "Total Revenue",
value: () => `$${totalRevenue.value.toFixed(2)}`,
color: "#10b981",
}),
StatCard({
label: "Total Orders",
value: () => `${(orders.data() ?? []).length}`,
}),
StatCard({
label: "Pending",
value: () => `${ordersByStatus.value.pending}`,
color: "#f59e0b",
}),
StatCard({
label: "Delivered",
value: () => `${ordersByStatus.value.delivered}`,
color: "#5ce0f2",
}),
),
OrdersTable({}),
)
);
export default Dashboard;
main.ts
import { mount } from "@whisq/core";
import Dashboard from "./components/Dashboard";
mount(Dashboard({}), document.getElementById("app")!);
  • resource() — fetches data and exposes reactive loading(), error(), data() states
  • computed()totalRevenue and ordersByStatus derive from fetched data automatically
  • Component compositionStatCard is reused with different props for each metric
  • refetch() — the refresh button triggers a new API call, UI updates automatically
  • when() + each() — conditional loading/error states with list rendering for table rows