Skip to content
Mog is in active development. The GitHub repo, SDK packages, and community channels are not yet available. Follow for launch updates

Note: SDK packages will be published at launch. This tutorial previews the API.

Build a Budget Tracker

Build a complete budget tracking application using Mog and React. You will set up a spreadsheet with budget categories and formulas, add conditional formatting to highlight over-budget items, create a summary chart, and export everything to XLSX.

intermediate~30 min

Prerequisites

bash
npm install @mog-sdk/embed @mog-sdk/node

1Set up the spreadsheet with categories and formulas

Create a helper that initializes the budget spreadsheet with categories, budget amounts, and formulas that calculate actual spend and variance.

typescript
// lib/budget-setup.ts
import { createWorkbook, type Workbook } from "@mog-sdk/node";

export interface BudgetCategory {
  name: string;
  budget: number;
}

const DEFAULT_CATEGORIES: BudgetCategory[] = [
  { name: "Housing", budget: 1500 },
  { name: "Food & Groceries", budget: 600 },
  { name: "Transportation", budget: 300 },
  { name: "Utilities", budget: 200 },
  { name: "Entertainment", budget: 150 },
  { name: "Healthcare", budget: 100 },
  { name: "Savings", budget: 500 },
];

export async function createBudgetWorkbook(
  categories = DEFAULT_CATEGORIES
): Promise<Workbook> {
  const wb = await createWorkbook();
  const ws = wb.getActiveSheet();
  ws.name = "Budget";

  // Headers
  const headers = ["Category", "Budget", "Actual", "Variance", "Status"];
  for (let col = 0; col < headers.length; col++) {
    await ws.setCellByIndex(0, col, headers[col]);
  }

  // Data rows with formulas
  for (let i = 0; i < categories.length; i++) {
    const row = i + 2; // 1-indexed, skip header
    await ws.setCell(`A${row}`, categories[i].name);
    await ws.setCell(`B${row}`, categories[i].budget);
    await ws.setCell(`C${row}`, 0); // Actual — user fills this in
    await ws.setCell(`D${row}`, `=B${row}-C${row}`);
    await ws.setCell(`E${row}`, `=IF(D${row}<0,"Over Budget","On Track")`);
  }

  // Totals row
  const lastRow = categories.length + 1;
  const totalRow = lastRow + 1;
  await ws.setCell(`A${totalRow}`, "TOTAL");
  await ws.setCell(`B${totalRow}`, `=SUM(B2:B${lastRow})`);
  await ws.setCell(`C${totalRow}`, `=SUM(C2:C${lastRow})`);
  await ws.setCell(`D${totalRow}`, `=SUM(D2:D${lastRow})`);
  await ws.setCell(
    `E${totalRow}`,
    `=IF(D${totalRow}<0,"Over Budget","On Track")`
  );

  await wb.calculate();
  return wb;
}

2Add conditional formatting

Highlight over-budget items in red and on-track items in green using Mog's conditional formatting API.

typescript
// lib/budget-formatting.ts
import type { Workbook } from "@mog-sdk/node";

export async function applyBudgetFormatting(wb: Workbook) {
  const ws = wb.getActiveSheet();
  const lastDataRow = (await ws.getUsedRange()).length;

  // Red background for negative variance (over budget)
  await ws.addConditionalFormat({
    range: `D2:D${lastDataRow}`,
    type: "cellValue",
    operator: "lessThan",
    value: 0,
    style: {
      background: "#fecaca",
      color: "#991b1b",
      bold: true,
    },
  });

  // Green background for positive variance (on track)
  await ws.addConditionalFormat({
    range: `D2:D${lastDataRow}`,
    type: "cellValue",
    operator: "greaterThanOrEqual",
    value: 0,
    style: {
      background: "#bbf7d0",
      color: "#166534",
    },
  });

  // Bold the header row
  await ws.setRowStyle(0, {
    bold: true,
    background: "#1e293b",
    color: "#f8fafc",
  });

  // Bold the totals row
  await ws.setRowStyle(lastDataRow - 1, { bold: true });
}

3Create a summary chart

Add a bar chart that compares budget vs. actual spend for each category.

typescript
// lib/budget-chart.ts
import type { Workbook } from "@mog-sdk/node";

export async function addBudgetChart(wb: Workbook) {
  const ws = wb.getActiveSheet();
  const categoryCount = (await ws.getUsedRange()).length - 2; // minus header and total

  const chart = await ws.addChart({
    type: "bar",
    title: "Budget vs. Actual",
    position: { sheet: ws.name, cell: "G2", width: 500, height: 350 },
    series: [
      {
        name: "Budget",
        categories: `A2:A${categoryCount + 1}`,
        values: `B2:B${categoryCount + 1}`,
        color: "#6366f1",
      },
      {
        name: "Actual",
        categories: `A2:A${categoryCount + 1}`,
        values: `C2:C${categoryCount + 1}`,
        color: "#f97316",
      },
    ],
    legend: { position: "bottom" },
  });

  return chart;
}

4Wire it up in React with XLSX export

Bring everything together in a React component. The user can edit actual spend values, see live variance calculations, and export to XLSX.

tsx
"use client";

import { MogSheet } from "@mog-sdk/embed/react";
import type { CellChangeEvent } from "@mog-sdk/embed";
import { useCallback, useRef, useState } from "react";

export default function BudgetTracker() {
  const sheetRef = useRef<any>(null);
  const [totalVariance, setTotalVariance] = useState<number | null>(null);

  const handleReady = useCallback(async (api: any) => {
    sheetRef.current = api;

    // Read the initial total variance
    const variance = await api.getCellValue("D9");
    setTotalVariance(variance as number);
  }, []);

  const handleCellChange = useCallback(async (e: CellChangeEvent) => {
    // When actual spend changes, re-read the total variance
    if (e.ref.startsWith("C")) {
      const api = sheetRef.current;
      if (api) {
        const variance = await api.getCellValue("D9");
        setTotalVariance(variance as number);
      }
    }
  }, []);

  const handleExport = useCallback(async () => {
    const api = sheetRef.current;
    if (!api) return;

    const blob = await api.exportXlsx();
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = "budget-tracker.xlsx";
    a.click();
    URL.revokeObjectURL(url);
  }, []);

  return (
    <div className="mx-auto max-w-5xl p-8">
      <div className="mb-6 flex items-center justify-between">
        <div>
          <h1 className="text-2xl font-bold">Budget Tracker</h1>
          {totalVariance !== null && (
            <p
              className={`mt-1 text-sm font-medium ${
                totalVariance >= 0 ? "text-green-600" : "text-red-600"
              }`}
            >
              Total variance: ${totalVariance >= 0 ? "+" : ""}
              {totalVariance.toLocaleString()}
            </p>
          )}
        </div>
        <button
          onClick={handleExport}
          className="rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-500"
        >
          Export XLSX
        </button>
      </div>

      <MogSheet
        width={1100}
        height={500}
        onReady={handleReady}
        onCellChange={handleCellChange}
        toolbar={{
          show: true,
          items: ["bold", "italic", "separator", "align", "separator", "undo", "redo"],
        }}
        features={{
          formulaBar: true,
          sheetTabs: false,
          contextMenu: true,
        }}
      />
    </div>
  );
}

To initialize the spreadsheet with budget data on mount, call the setup functions from Step 1 and Step 2 inside the onReady callback, or pre-generate an XLSX file and pass it as the src prop.

Next steps