Dashboard overview of total revenue, new orders, active users, conversion rate, sales performance chart, traffic sources pie chart, and recent orders table

App 05: Building a Static Business Dashboard with React, TypeScript, Vite, and Fluent UI

Dashboard overview of total revenue, new orders, active users, conversion rate, sales performance chart, traffic sources pie chart, and recent orders table
Dashboard showing revenue, orders, user activity, and traffic sources

App 05 is Static Business Dashboard, part of Block 1 of the 100 React + Fluent UI project. In the project roadmap, App 05 belongs to Block 1 — Fundamentals and UI, where the focus is JSX, components, props, lists, composition, and declarative UI structure.


1. What App 05 teaches

This app looks simple, but it introduces a very important React architecture pattern:

data -> typed model -> reusable component -> page composition -> final UI

The dashboard is not built by manually copying four cards inside App.tsx.

Instead, the project uses:

DashboardMetric.ts
dashboardMetrics.ts
MetricCard.tsx
App.tsx
main.tsx
Browser UI

This relationship between files is the most important lesson of App 05.

React is not only about putting JSX on the screen. React is about organizing UI as a composition of small, predictable pieces.


2. Project creation

PowerShell commands:

cd C:\ReactApps
npm create vite@latest bloco01-app05-static-dashboard -- --template react-ts
cd bloco01-app05-static-dashboard
npm install
npm install @fluentui/react-components @fluentui/react-icons
code .

This creates a React + TypeScript project using Vite.

Vite gives us the development server, build system, and project structure. TypeScript gives type safety. Fluent UI gives Microsoft-style components such as Card, Button, Badge, Text, and typography components.


3. Folder structure

PowerShell commands:

New-Item -ItemType Directory -Force -Path src\components
New-Item -ItemType Directory -Force -Path src\data
New-Item -ItemType Directory -Force -Path src\models
New-Item -ItemType Directory -Force -Path src\styles

Final structure:

src/
components/
MetricCard.tsx
data/
dashboardMetrics.ts
models/
DashboardMetric.ts
styles/
global.css
App.tsx
main.tsx

Each folder has a responsibility:

FolderResponsibility
modelsDefines the shape of the data
dataStores the dashboard information
componentsStores reusable visual pieces
stylesStores CSS layout and visual rules
App.tsxComposes the page
main.tsxStarts React and applies Fluent UI theme

This prevents the app from becoming a single giant file.


4. The model file: DashboardMetric.ts

export type DashboardMetric = {
id: number;
title: string;
value: string;
description: string;
};

This file defines the contract for a dashboard metric.

It says that every metric must have:

PropertyTypeMeaning
idnumberUnique identifier
titlestringCard title
valuestringMain KPI value
descriptionstringExplanation text

This is very important because TypeScript now protects the data structure.

For example, this would be valid:

{
id: 1,
title: "Active Users",
value: "12,480",
description: "Users currently registered in the platform"
}

But this would be wrong:

{
id: 1,
name: "Active Users"
}

Why? Because the model expects title, value, and description.

So the model file acts like a rulebook for the dashboard data.


5. The data file: dashboardMetrics.ts

import type { DashboardMetric } from "../models/DashboardMetric";
export const dashboardMetrics: DashboardMetric[] = [
{
id: 1,
title: "Active Users",
value: "12,480",
description: "Users currently registered in the platform",
},
{
id: 2,
title: "Monthly Revenue",
value: "$84,250",
description: "Estimated revenue for the current month",
},
{
id: 3,
title: "Open Tickets",
value: "136",
description: "Support tickets waiting for resolution",
},
{
id: 4,
title: "System Health",
value: "98.7%",
description: "Current operational availability",
},
];

This file imports the type:

import type { DashboardMetric } from "../models/DashboardMetric";

The word type is important. It tells TypeScript:

This import is only used for typing.
It does not need to exist in the final JavaScript bundle.

Then the data is exported:

export const dashboardMetrics: DashboardMetric[] = [...]

This means:

dashboardMetrics is an array of DashboardMetric objects.

So TypeScript knows that every item inside the array has:

id
title
value
description

This is why, later in App.tsx, when we write:

dashboardMetrics.map((metric) => ...)

TypeScript understands that metric is a DashboardMetric.

That is why the metric parameter does not need manual typing.


6. The reusable component: MetricCard.tsx

import {
Card,
CardHeader,
Text,
Title2,
Body1,
} from "@fluentui/react-components";
import { DataTrending24Regular } from "@fluentui/react-icons";
import type { DashboardMetric } from "../models/DashboardMetric";
type MetricCardProps = {
metric: DashboardMetric;
};
export function MetricCard({ metric }: MetricCardProps) {
return (
<Card className="metric-card">
<CardHeader
image={<DataTrending24Regular />}
header={<Text weight="semibold">{metric.title}</Text>}
/>
<Title2>{metric.value}</Title2>
<Body1 className="metric-description">
{metric.description}
</Body1>
</Card>
);
}

This file is the visual card component.

It receives one prop:

metric

The prop type is defined here:

type MetricCardProps = {
metric: DashboardMetric;
};

This means the component expects this shape:

MetricCard receives:
metric:
id
title
value
description

Then the component uses the received data:

{metric.title}
{metric.value}
{metric.description}

This is the key React concept:

The component does not own the data.
The component receives data through props.
The component renders UI based on those props.

That is why MetricCard is reusable.

It can render:

Active Users
Monthly Revenue
Open Tickets
System Health

using the same component.

Without this component, you would probably repeat four cards manually in App.tsx.

That would be bad because the layout would become duplicated and harder to maintain.


7. The page file: App.tsx

import {
Badge,
Button,
Card,
Text,
Title1,
Title3,
} from "@fluentui/react-components";
import { MetricCard } from "./components/MetricCard";
import { dashboardMetrics } from "./data/dashboardMetrics";
function App() {
return (
<main className="page">
<section className="dashboard-shell">
<header className="dashboard-header">
<div>
<Badge appearance="filled" color="brand">
App 05
</Badge>
<Title1>Static Business Dashboard</Title1>
<Text>
A Microsoft-style static dashboard built with React, TypeScript,
Vite, and Fluent UI.
</Text>
</div>
<Button appearance="primary">Export Report</Button>
</header>
<section className="metrics-grid">
{dashboardMetrics.map((metric) => (
<MetricCard key={metric.id} metric={metric} />
))}
</section>
<section className="content-grid">
<Card className="large-card">
<Title3>Business Overview</Title3>
<Text>
This section represents a static executive summary. In future apps,
this area will evolve into dynamic content loaded from APIs and
controlled by React state.
</Text>
</Card>
<Card className="large-card">
<Title3>Learning Goal</Title3>
<Text>
The main goal of this app is to practice composition, component
extraction, typed data, list rendering, and clean dashboard layout.
</Text>
</Card>
</section>
</section>
</main>
);
}
export default App;

App.tsx is the page composer.

It imports:

import { MetricCard } from "./components/MetricCard";

That gives the page access to the reusable card component.

It also imports:

import { dashboardMetrics } from "./data/dashboardMetrics";

That gives the page access to the array of metric data.

Then the most important line is:

{dashboardMetrics.map((metric) => (
<MetricCard key={metric.id} metric={metric} />
))}

This line means:

For each item inside dashboardMetrics,
create one MetricCard component,
pass the current item as the metric prop,
and use metric.id as the React key.

The flow is:

dashboardMetrics[0] -> MetricCard -> Active Users card
dashboardMetrics[1] -> MetricCard -> Monthly Revenue card
dashboardMetrics[2] -> MetricCard -> Open Tickets card
dashboardMetrics[3] -> MetricCard -> System Health card

So the final four cards on the screen are generated from data.

That is not a black box.

The exact relationship is:

dashboardMetrics.ts provides the array
App.tsx loops through the array
MetricCard.tsx renders each object
global.css controls the layout
Fluent UI controls the visual style

8. Why key={metric.id} is important

React needs a key when rendering lists:

<MetricCard key={metric.id} metric={metric} />

The key helps React identify each item between renders.

Even though this app is static, this is still the correct habit.

Later, when you build apps with filters, delete buttons, search, sorting, or API data, keys become essential.

Bad:

<MetricCard metric={metric} />

Good:

<MetricCard key={metric.id} metric={metric} />

9. The entry point: main.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import { FluentProvider, webLightTheme } from "@fluentui/react-components";
import App from "./App";
import "./styles/global.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<FluentProvider theme={webLightTheme}>
<App />
</FluentProvider>
</React.StrictMode>
);

This file starts the app.

The flow is:

main.tsx loads React
main.tsx loads FluentProvider
main.tsx loads App
main.tsx loads global CSS
React renders App into the HTML root element

This line starts React:

ReactDOM.createRoot(document.getElementById("root")!)

The root element exists in index.html.

This part applies the Fluent UI theme:

<FluentProvider theme={webLightTheme}>
<App />
</FluentProvider>

Without FluentProvider, Fluent UI components would not receive the correct theme.

So the relationship is:

main.tsx wraps App with FluentProvider
App uses Fluent UI components
FluentProvider gives them the Microsoft theme

This is why the dashboard has the Microsoft-style appearance.


10. The CSS file: global.css

* {
box-sizing: border-box;
}

This makes sizing more predictable.

body {
margin: 0;
font-family: "Segoe UI", Arial, sans-serif;
background: #f5f5f5;
}

This removes browser default margin and applies a Microsoft-like font.

.page {
min-height: 100vh;
padding: 48px;
background: linear-gradient(135deg, #f5f5f5 0%, #eef3f8 100%);
}

This controls the full page.

It gives:

full viewport height
external spacing
light dashboard background
.dashboard-shell {
max-width: 1200px;
margin: 0 auto;
}

This centers the dashboard and prevents it from becoming too wide.

.dashboard-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 24px;
margin-bottom: 32px;
}

This creates the top header layout.

Left side:

Badge
Title
Description

Right side:

Export Report button
.metrics-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
}

This is what creates the four KPI cards in one row.

The CSS means:

Create 4 equal columns.
Each column takes 1 fraction of the available width.
Put 20px space between cards.
.content-grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 20px;
margin-top: 20px;
}

This creates the bottom section.

The first card is wider:

Business Overview = 2 parts
Learning Goal = 1 part

That is why the bottom layout looks like a real dashboard.


11. Responsive behavior

@media (max-width: 900px) {
.dashboard-header {
flex-direction: column;
}
.metrics-grid {
grid-template-columns: repeat(2, 1fr);
}
.content-grid {
grid-template-columns: 1fr;
}
}

When the screen becomes smaller:

Header stacks vertically
Metric cards become 2 columns
Bottom cards become 1 column

Then for mobile:

@media (max-width: 600px) {
.page {
padding: 24px;
}
.metrics-grid {
grid-template-columns: 1fr;
}
}

Now the metric cards become one per row.

This is important because enterprise dashboards must work across different screen sizes.


12. Complete file relationship

This is the complete mental model:

1. DashboardMetric.ts
Defines what a metric is.
2. dashboardMetrics.ts
Creates an array of metrics using that type.
3. MetricCard.tsx
Receives one metric and renders one card.
4. App.tsx
Imports the metric array.
Loops over the array.
Creates one MetricCard per item.
Adds the dashboard header and bottom cards.
5. global.css
Controls spacing, grid, responsiveness, and page layout.
6. main.tsx
Starts React.
Applies Fluent UI theme.
Renders App into the browser.

This is the real architecture.

There is no magic.

Every piece has a responsibility.


13. Why this app does not use state

App 05 does not use:

useState

because nothing changes yet.

The dashboard is static.

The data is fixed.

The user does not interact with the cards.

So state would be unnecessary.

This is a very important React lesson:

Do not add state just because you are using React.
Add state only when the UI must remember or react to changes.

State will appear later in the project.


14. Why this app does not use useEffect

App 05 also does not use:

useEffect

because the app is not synchronizing with an external system.

There is no:

API call
timer
browser storage
external event
subscription
DOM integration

So useEffect is unnecessary.

This is modern React thinking.

Many beginners use useEffect too early. This app correctly avoids that.


15. Final result explained

The final screen appears as a business dashboard because multiple layers work together:

LayerFileResult
Data modelDashboardMetric.tsDefines the metric shape
Data sourcedashboardMetrics.tsProvides the dashboard values
UI componentMetricCard.tsxRenders each KPI card
Page compositionApp.tsxBuilds the dashboard screen
Theme providermain.tsxApplies Fluent UI theme
Layout stylingglobal.cssCreates grid, spacing, and responsive layout

The dashboard is the result of file collaboration.

It is not a single isolated page.

That is the main architectural lesson.


16. Technical summary

ConceptExplanation
ComponentA reusable UI function
PropsData passed into a component
TypeScript typeContract that defines data shape
Array renderingCreating UI from data with map()
React keyUnique identity for rendered list items
FluentProviderGlobal Fluent UI theme wrapper
CardFluent UI container component
CSS GridLayout system for dashboard cards
Responsive designLayout adapts to screen size
Static UINo state because nothing changes
No effectNo external synchronization needed

17. Official documentation

TopicDocumentation
React Learnhttps://react.dev/learn
Describing the UIhttps://react.dev/learn/describing-the-ui
Your First Componenthttps://react.dev/learn/your-first-component
Passing Propshttps://react.dev/learn/passing-props-to-a-component
Rendering Listshttps://react.dev/learn/rendering-lists
Keeping Components Purehttps://react.dev/learn/keeping-components-pure
You Might Not Need an Effecthttps://react.dev/learn/you-might-not-need-an-effect
Fluent UI Reacthttps://react.fluentui.dev
Vite Guidehttps://vite.dev/guide
TypeScript Docshttps://www.typescriptlang.org/docs

18. Current project position

BlockAppNameStatus
Block 101Hello React FluentCompleted
Block 102Profile CardCompleted
Block 103Product ListCompleted
Block 104Microsoft Style User CardCompleted
Block 105Static DashboardCompleted
Block 106Corporate Sidebar MenuNext
Edvaldo Guimrães Filho Avatar

Published by