Sales Dashboard
A complete, working MView built from a three-column sales CSV.
Example: Sales Dashboard
A minimal but complete MView, end to end. Source file:
Month,Client,Total
Jan,Acme,100
Feb,Globex,250
Mar,Initech,175mview.json
{
"$schema": "https://schemas.carmenlabs.com/mview/v1/schema.json",
"id": "sales-dashboard",
"name": "Sales Dashboard",
"description": "Executive dashboard generated from sales.csv.",
"version": "1.0.0",
"prompt": "Create an executive dashboard from sales.csv showing monthly sales, top clients, and total sales.",
"runtime": {
"environment": "web",
"renderer": "react",
"version": "^19.0.0"
},
"author": "Victor Avila",
"license": "MIT",
"icon": "./resources/icon.svg",
"accepts": ["table"],
"source": {
"path": "../sales.csv",
"contentHash": "sha256:8d969eef6ecad3c29a3a629280e686cff8ca58e3f3f2d4d0c7d7f9f2e2f0f4ab"
},
"entry": {
"parser": "./parser.ts",
"component": "./Dashboard.tsx"
},
"resources": {
"path": "./resources"
},
"settings": {
"currency": "USD",
"topClientsLimit": 10
},
"metadata": {
"createdBy": "llm",
"model": "gpt-5.5",
"createdAt": "2026-07-11T05:00:00Z"
}
}parser.ts
export default async function parse(input: any, context: any) {
const rows = input.rows ?? [];
const totalSales = rows.reduce((sum: number, row: any) => {
return sum + Number(row.Total ?? row.total ?? 0);
}, 0);
const salesByMonth = rows.map((row: any) => ({
month: row.Month ?? row.month,
total: Number(row.Total ?? row.total ?? 0)
}));
const topClients = [...rows]
.sort((a: any, b: any) => Number(b.Total ?? b.total ?? 0) - Number(a.Total ?? a.total ?? 0))
.slice(0, context.settings?.topClientsLimit ?? 10);
return { totalSales, salesByMonth, topClients };
}Dashboard.tsx
import React from "react";
export default function Dashboard({ data, settings }: any) {
return (
<div style={{ padding: 24, fontFamily: "system-ui, sans-serif" }}>
<h1>Sales Dashboard</h1>
<section>
<h2>Total Sales</h2>
<strong>
{settings?.currency ?? "USD"} {data.totalSales.toLocaleString()}
</strong>
</section>
<section>
<h2>Sales by Month</h2>
<ul>
{data.salesByMonth.map((item: any) => (
<li key={item.month}>
{item.month}: {item.total}
</li>
))}
</ul>
</section>
<section>
<h2>Top Clients</h2>
<table>
<thead>
<tr><th>Client</th><th>Total</th></tr>
</thead>
<tbody>
{data.topClients.map((row: any, index: number) => (
<tr key={index}>
<td>{row.Client ?? row.client ?? "Unknown"}</td>
<td>{row.Total ?? row.total ?? 0}</td>
</tr>
))}
</tbody>
</table>
</section>
</div>
);
}Resulting file tree
.mviews/sales-dashboard/
├── mview.json
├── parser.ts
├── Dashboard.tsx
└── resources/
└── icon.svgWhat happens when sales.csv changes
Add a row, and the runtime re-reads the file, re-normalizes it to { columns, rows }, re-runs
parse, and re-renders Dashboard with the new data. parser.ts and Dashboard.tsx don't
change unless the shape of the CSV itself changes (e.g. a renamed column) — in which case only
the parser needs regenerating. See Security Model → Regeneration.