Dashboard
A dashboard that fetches data from APIs, displays stats, and updates in real time. Demonstrates resource(), multiple components sharing state, and composing widgets.
What You’ll Build
Section titled “What You’ll Build”- Stats overview with key metrics
- A list of recent orders fetched from an API
- A revenue summary widget
- Refresh button to reload all data
Data Layer
Section titled “Data Layer”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();};Stat Card Widget
Section titled “Stat Card Widget”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;Orders Table
Section titled “Orders Table”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;Dashboard Layout
Section titled “Dashboard Layout”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;Entry Point
Section titled “Entry Point”import { mount } from "@whisq/core";import Dashboard from "./components/Dashboard";
mount(Dashboard({}), document.getElementById("app")!);Key Concepts
Section titled “Key Concepts”- resource() — fetches data and exposes reactive
loading(),error(),data()states - computed() —
totalRevenueandordersByStatusderive from fetched data automatically - Component composition —
StatCardis 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
Next Steps
Section titled “Next Steps”- Data Fetching — More on resource() patterns
- Real-time Data — WebSocket-based live updates
- State Management — Composing stores