Paramclasses Academy - Blog

Tutorials Details

←
Post 27 of 29
→

Final Project — Developing a Multi-Page React Admin Dashboard in react.js

Final Project — Developing a Multi-Page React Admin Dashboard in react.js

October 30, 2026

1. Introduction

In this tutorial, we will develop a professional multi-page React Admin Dashboard. Admin dashboards are commonly used to manage business operations, products, orders, customers, and application settings.

We will build a responsive dashboard with reusable components and multiple pages. The project will use mock data, so it will work without a backend or database.

What We Will Build

Our application will include:

  1. Dashboard Overview

  2. Products Management

  3. Orders Management

  4. Customers Management

  5. Settings Page

  6. Responsive Sidebar Navigation

  7. Reusable Header and Layout

  8. Search and Filtering

  9. Dashboard Statistics

  10. Analytics Charts

Technologies Used

Technology Purpose
React.js Building the user interface
JavaScript ES6+ Application logic
React Router Multi-page navigation
Vite Development environment
CSS3 Styling and responsive design
React Hooks Managing state and component behavior

2. Learning Objectives

After completing this tutorial, you will understand:

  • How to build a multi-page React application.

  • How to organize a professional React project.

  • How to create reusable components.

  • How to configure React Router.

  • How to build a dashboard layout.

  • How to display statistics and charts.

  • How to search and filter data.

  • How to manage component state.

  • How to make a dashboard responsive.

  • How to structure a larger React project.


3. Understanding an Admin Dashboard

An Admin Dashboard is a web-based interface that allows administrators to monitor and manage an application's data.

For example, an e-commerce administrator can use a dashboard to:

  • Monitor total sales.

  • View the number of orders.

  • Manage products.

  • Monitor customers.

  • Review recent transactions.

  • Configure application settings.

Basic Dashboard Structure

A typical admin dashboard contains:

Sidebar: Displays navigation links to different sections.

Header: Displays the page title, notifications, and administrator profile.

Main Content: Displays the information associated with the selected page.

Analytics Section: Displays statistics, charts, and reports.


4. Create a React Project Using Vite

Open your terminal and execute:

npm create vite@latest react-admin-dashboard

Select:

Framework: React
Variant: JavaScript

Navigate to the project:

cd react-admin-dashboard

Install dependencies:

npm install

Install React Router:

npm install react-router

Start the development server:

npm run dev

Open the local URL displayed in your terminal.


5. Project Folder Structure

Organize the project as follows:

react-admin-dashboard/
│
├── public/
│
├── src/
│   │
│   ├── components/
│   │   ├── Sidebar.jsx
│   │   ├── Header.jsx
│   │   ├── StatCard.jsx
│   │   └── Layout.jsx
│   │
│   ├── pages/
│   │   ├── Dashboard.jsx
│   │   ├── Products.jsx
│   │   ├── Orders.jsx
│   │   ├── Customers.jsx
│   │   └── Settings.jsx
│   │
│   ├── data/
│   │   └── mockData.js
│   │
│   ├── App.jsx
│   ├── main.jsx
│   └── index.css
│
├── package.json
└── vite.config.js

This structure separates reusable components, individual pages, and application data.


6. Create Mock Data

Before developing the dashboard pages, we need sample data.

Create:

src/data/mockData.js

export const stats = [
  {
    title: "Total Revenue",
    value: "$48,250",
    change: "+12.5%",
    icon: "????"
  },
  {
    title: "Total Orders",
    value: "1,284",
    change: "+8.2%",
    icon: "????"
  },
  {
    title: "Total Customers",
    value: "856",
    change: "+5.4%",
    icon: "????"
  },
  {
    title: "Total Products",
    value: "248",
    change: "+3.1%",
    icon: "????️"
  }
];

export const products = [
  {
    id: 1,
    name: "Wireless Headphones",
    category: "Electronics",
    price: 89,
    stock: 45,
    status: "Active"
  },
  {
    id: 2,
    name: "Smart Watch",
    category: "Electronics",
    price: 129,
    stock: 32,
    status: "Active"
  },
  {
    id: 3,
    name: "Gaming Keyboard",
    category: "Accessories",
    price: 75,
    stock: 12,
    status: "Active"
  },
  {
    id: 4,
    name: "Bluetooth Speaker",
    category: "Electronics",
    price: 59,
    stock: 0,
    status: "Out of Stock"
  },
  {
    id: 5,
    name: "Laptop Stand",
    category: "Accessories",
    price: 35,
    stock: 20,
    status: "Active"
  }
];

export const orders = [
  {
    id: "ORD-1001",
    customer: "John Smith",
    date: "2026-10-20",
    amount: 249,
    status: "Completed"
  },
  {
    id: "ORD-1002",
    customer: "Sarah Wilson",
    date: "2026-10-21",
    amount: 129,
    status: "Pending"
  },
  {
    id: "ORD-1003",
    customer: "David Brown",
    date: "2026-10-22",
    amount: 89,
    status: "Processing"
  },
  {
    id: "ORD-1004",
    customer: "Emma Davis",
    date: "2026-10-23",
    amount: 159,
    status: "Completed"
  },
  {
    id: "ORD-1005",
    customer: "Michael Lee",
    date: "2026-10-24",
    amount: 75,
    status: "Cancelled"
  }
];

export const customers = [
  {
    id: 1,
    name: "John Smith",
    email: "john@example.com",
    orders: 12,
    spent: 1250
  },
  {
    id: 2,
    name: "Sarah Wilson",
    email: "sarah@example.com",
    orders: 8,
    spent: 850
  },
  {
    id: 3,
    name: "David Brown",
    email: "david@example.com",
    orders: 15,
    spent: 1750
  },
  {
    id: 4,
    name: "Emma Davis",
    email: "emma@example.com",
    orders: 5,
    spent: 420
  }
];

Explanation:

We have created four data collections:

  • stats stores dashboard statistics.

  • products stores product information.

  • orders stores order information.

  • customers stores customer information.

We will import these arrays into our components and display their data dynamically.

Note: These values are demonstration data, not actual business transactions.


7. Create the Sidebar Component

The sidebar will provide navigation between the dashboard pages.

Create:

src/components/Sidebar.jsx

import { NavLink } from "react-router";

function Sidebar({ isOpen, closeSidebar }) {
  const links = [
    { path: "/", label: "Dashboard", icon: "????", end: true },
    { path: "/products", label: "Products", icon: "????" },
    { path: "/orders", label: "Orders", icon: "????" },
    { path: "/customers", label: "Customers", icon: "????" },
    { path: "/settings", label: "Settings", icon: "⚙️" }
  ];

  return (
    <>
      {isOpen && (
        <div
          className="sidebar-overlay"
          onClick={closeSidebar}
        />
      )}

      <aside className={`sidebar ${isOpen ? "open" : ""}`}>
        <div className="brand">
          <span className="brand-icon">A</span>
          <h2>AdminPro</h2>
        </div>

        <p className="menu-label">MAIN MENU</p>

        <nav>
          {links.map((link) => (
            <NavLink
              key={link.path}
              to={link.path}
              end={link.end}
              onClick={closeSidebar}
              className={({ isActive }) =>
                isActive ? "nav-link active" : "nav-link"
              }
            >
              <span>{link.icon}</span>
              {link.label}
            </NavLink>
          ))}
        </nav>

        <div className="sidebar-bottom">
          <p>Admin Dashboard</p>
          <small>Version 1.0.0</small>
        </div>
      </aside>
    </>
  );
}

export default Sidebar;

Important Concepts

NavLink: A React Router component used to navigate between pages. It also provides an active state for the current route.

map(): Used to generate navigation links from an array.

isActive: A value provided by NavLink that tells us whether a link matches the current route.

Conditional Class: We apply the active class to highlight the current page.


8. Create the Header Component

The header will display the page title, menu button, notifications, and administrator information.

Create:

src/components/Header.jsx

import { useLocation } from "react-router";

function Header({ toggleSidebar }) {
  const location = useLocation();

  const pageTitles = {
    "/": "Dashboard Overview",
    "/products": "Products Management",
    "/orders": "Orders Management",
    "/customers": "Customer Management",
    "/settings": "Application Settings"
  };

  const title = pageTitles[location.pathname] || "Admin Dashboard";

  return (
    <header className="header">
      <div className="header-left">
        <button
          className="menu-button"
          onClick={toggleSidebar}
          aria-label="Toggle navigation"
        >
          ☰
        </button>

        <div>
          <h2>{title}</h2>
          <p>Welcome back, Administrator!</p>
        </div>
      </div>

      <div className="header-right">
        <button className="notification-button" aria-label="Notifications">
          ????
        </button>

        <div className="profile">
          <div className="avatar">A</div>

          <div>
            <strong>Admin User</strong>
            <small>Administrator</small>
          </div>
        </div>
      </div>
    </header>
  );
}

export default Header;

Important Concepts

  • useLocation() returns information about the current route.

  • location.pathname identifies the current URL path.

  • We use the path to display the appropriate page title.

  • toggleSidebar is a function received through props.


9. Create a Reusable StatCard Component

Instead of writing the same statistics card multiple times, we will create a reusable component.

Create:

src/components/StatCard.jsx

function StatCard({ title, value, change, icon }) {
  return (
    <div className="stat-card">
      <div className="stat-top">
        <span>{title}</span>
        <span className="stat-icon">{icon}</span>
      </div>

      <h2>{value}</h2>

      <p className="stat-change">
        {change} <span>from last month</span>
      </p>
    </div>
  );
}

export default StatCard;

Why Are Reusable Components Important?

Reusable components help us:

  1. Reduce code duplication.

  2. Maintain consistent UI design.

  3. Improve code readability.

  4. Simplify future updates.

  5. Build scalable applications.

For example, the same StatCard component can display revenue, orders, customers, and products.


10. Create the Layout Component

The layout component will combine the sidebar, header, and page content.

Create:

src/components/Layout.jsx

import { useState } from "react";
import { Outlet } from "react-router";

import Sidebar from "./Sidebar";
import Header from "./Header";

function Layout() {
  const [isOpen, setIsOpen] = useState(false);

  function toggleSidebar() {
    setIsOpen((previous) => !previous);
  }

  function closeSidebar() {
    setIsOpen(false);
  }

  return (
    <div className="app-layout">
      <Sidebar
        isOpen={isOpen}
        closeSidebar={closeSidebar}
      />

      <div className="main-area">
        <Header toggleSidebar={toggleSidebar} />

        <main className="page-content">
          <Outlet />
        </main>
      </div>
    </div>
  );
}

export default Layout;

Understanding Outlet

Outlet is a React Router component that renders the matched child route inside a parent layout.

For example:

  • / renders the Dashboard page.

  • /products renders the Products page.

  • /orders renders the Orders page.

The sidebar and header remain part of the shared layout.


11. Create the Dashboard Page

Now we will build the main dashboard overview.

Create:

src/pages/Dashboard.jsx

import { stats, orders } from "../data/mockData";
import StatCard from "../components/StatCard";

function Dashboard() {
  const salesData = [
    { month: "Jan", value: 45 },
    { month: "Feb", value: 65 },
    { month: "Mar", value: 50 },
    { month: "Apr", value: 85 },
    { month: "May", value: 70 },
    { month: "Jun", value: 95 }
  ];

  const maxValue = Math.max(
    ...salesData.map((item) => item.value)
  );

  return (
    <div>
      <div className="page-heading">
        <div>
          <h1>Dashboard Overview</h1>
          <p>Monitor your business performance.</p>
        </div>

        <span className="date-label">October 2026</span>
      </div>

      <div className="stats-grid">
        {stats.map((item) => (
          <StatCard
            key={item.title}
            {...item}
          />
        ))}
      </div>

      <div className="dashboard-grid">
        <section className="panel">
          <div className="panel-heading">
            <div>
              <h3>Sales Overview</h3>
              <p>Monthly sales performance</p>
            </div>

            <span className="badge">Last 6 Months</span>
          </div>

          <div className="chart">
            {salesData.map((item) => (
              <div className="chart-column" key={item.month}>
                <div className="chart-bar-area">
                  <div
                    className="chart-bar"
                    style={{
                      height: `${(item.value / maxValue) * 100}%`
                    }}
                    title={`${item.value} units`}
                  />
                </div>

                <span>{item.month}</span>
              </div>
            ))}
          </div>
        </section>

        <section className="panel">
          <div className="panel-heading">
            <div>
              <h3>Quick Summary</h3>
              <p>Business highlights</p>
            </div>
          </div>

          <div className="summary-item">
            <span>Conversion Rate</span>
            <strong>4.8%</strong>
          </div>

          <div className="summary-item">
            <span>Average Order Value</span>
            <strong>$126</strong>
          </div>

          <div className="summary-item">
            <span>Refund Rate</span>
            <strong>1.2%</strong>
          </div>

          <div className="summary-item">
            <span>Customer Growth</span>
            <strong>+12%</strong>
          </div>
        </section>
      </div>

      <section className="panel recent-orders">
        <div className="panel-heading">
          <div>
            <h3>Recent Orders</h3>
            <p>Latest customer transactions</p>
          </div>
        </div>

        <div className="table-wrapper">
          <table>
            <thead>
              <tr>
                <th>Order ID</th>
                <th>Customer</th>
                <th>Date</th>
                <th>Amount</th>
                <th>Status</th>
              </tr>
            </thead>

            <tbody>
              {orders.slice(0, 4).map((order) => (
                <tr key={order.id}>
                  <td>{order.id}</td>
                  <td>{order.customer}</td>
                  <td>{order.date}</td>
                  <td>${order.amount}</td>
                  <td>
                    <span
                      className={`status ${order.status.toLowerCase()}`}
                    >
                      {order.status}
                    </span>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </section>
    </div>
  );
}

export default Dashboard;

Dashboard Explanation

Stats Grid: Displays the four statistics using the reusable StatCard.

Sales Overview: Displays a simple CSS bar chart based on monthly sample values.

Quick Summary: Displays additional business metrics.

Recent Orders: Displays a table containing the latest orders.

The chart is a visual demonstration using static sample values, not a live analytics report.


12. Create the Products Page

The Products page will display product information and provide search functionality.

Create:

src/pages/Products.jsx

import { useState } from "react";
import { products as initialProducts } from "../data/mockData";

function Products() {
  const [search, setSearch] = useState("");
  const [category, setCategory] = useState("All");

  const categories = [
    "All",
    ...new Set(initialProducts.map((product) => product.category))
  ];

  const filteredProducts = initialProducts.filter((product) => {
    const matchesSearch = product.name
      .toLowerCase()
      .includes(search.toLowerCase());

    const matchesCategory =
      category === "All" || product.category === category;

    return matchesSearch && matchesCategory;
  });

  return (
    <div>
      <div className="page-heading">
        <div>
          <h1>Products</h1>
          <p>Manage your product inventory.</p>
        </div>

        <span className="badge">
          {filteredProducts.length} Products
        </span>
      </div>

      <section className="panel">
        <div className="toolbar">
          <input
            type="search"
            placeholder="Search products..."
            value={search}
            onChange={(event) => setSearch(event.target.value)}
          />

          <select
            value={category}
            onChange={(event) => setCategory(event.target.value)}
          >
            {categories.map((item) => (
              <option key={item} value={item}>
                {item}
              </option>
            ))}
          </select>
        </div>

        <div className="table-wrapper">
          <table>
            <thead>
              <tr>
                <th>Product</th>
                <th>Category</th>
                <th>Price</th>
                <th>Stock</th>
                <th>Status</th>
              </tr>
            </thead>

            <tbody>
              {filteredProducts.map((product) => (
                <tr key={product.id}>
                  <td>{product.name}</td>
                  <td>{product.category}</td>
                  <td>${product.price}</td>
                  <td>{product.stock}</td>
                  <td>
                    <span
                      className={`status ${
                        product.stock === 0
                          ? "cancelled"
                          : "completed"
                      }`}
                    >
                      {product.stock === 0
                        ? "Out of Stock"
                        : product.status}
                    </span>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>

          {filteredProducts.length === 0 && (
            <p className="empty-message">
              No products found.
            </p>
          )}
        </div>
      </section>
    </div>
  );
}

export default Products;

Concepts Used

  • useState() manages search and category selection.

  • filter() selects products matching the conditions.

  • toLowerCase() makes the search case-insensitive.

  • Set removes duplicate category names.

  • Conditional rendering displays a message when no products match.


13. Create the Orders Page

Create:

src/pages/Orders.jsx

import { useState } from "react";
import { orders } from "../data/mockData";

function Orders() {
  const [status, setStatus] = useState("All");

  const filteredOrders = orders.filter((order) => {
    return status === "All" || order.status === status;
  });

  return (
    <div>
      <div className="page-heading">
        <div>
          <h1>Orders</h1>
          <p>Track and review customer orders.</p>
        </div>
      </div>

      <section className="panel">
        <div className="toolbar">
          <h3>Order List</h3>

          <select
            value={status}
            onChange={(event) => setStatus(event.target.value)}
          >
            <option value="All">All Orders</option>
            <option value="Completed">Completed</option>
            <option value="Pending">Pending</option>
            <option value="Processing">Processing</option>
            <option value="Cancelled">Cancelled</option>
          </select>
        </div>

        <div className="table-wrapper">
          <table>
            <thead>
              <tr>
                <th>Order ID</th>
                <th>Customer</th>
                <th>Date</th>
                <th>Amount</th>
                <th>Status</th>
              </tr>
            </thead>

            <tbody>
              {filteredOrders.map((order) => (
                <tr key={order.id}>
                  <td>{order.id}</td>
                  <td>{order.customer}</td>
                  <td>{order.date}</td>
                  <td>${order.amount}</td>
                  <td>
                    <span
                      className={`status ${order.status.toLowerCase()}`}
                    >
                      {order.status}
                    </span>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>

          {filteredOrders.length === 0 && (
            <p className="empty-message">
              No orders found.
            </p>
          )}
        </div>
      </section>
    </div>
  );
}

export default Orders;

How Order Filtering Works

When the administrator selects a status, React updates the status state.

The filter() method then returns only the orders that match the selected status.

For example:

  • Completed displays completed orders.

  • Pending displays pending orders.

  • Processing displays processing orders.

  • Cancelled displays cancelled orders.


14. Create the Customers Page

Create:

src/pages/Customers.jsx

import { customers } from "../data/mockData";

function Customers() {
  return (
    <div>
      <div className="page-heading">
        <div>
          <h1>Customers</h1>
          <p>View customer information and purchase history.</p>
        </div>

        <span className="badge">
          {customers.length} Customers
        </span>
      </div>

      <section className="panel">
        <div className="table-wrapper">
          <table>
            <thead>
              <tr>
                <th>Customer</th>
                <th>Email</th>
                <th>Total Orders</th>
                <th>Total Spent</th>
              </tr>
            </thead>

            <tbody>
              {customers.map((customer) => (
                <tr key={customer.id}>
                  <td>{customer.name}</td>
                  <td>{customer.email}</td>
                  <td>{customer.orders}</td>
                  <td>${customer.spent}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </section>
    </div>
  );
}

export default Customers;

Explanation

We use map() to display every customer in the table.

The key property helps React identify individual list items when the data changes.


15. Create the Settings Page

The Settings page will demonstrate how to manage configuration options using React state.

Create:

src/pages/Settings.jsx

import { useState } from "react";

function Settings() {
  const [settings, setSettings] = useState({
    storeName: "AdminPro Store",
    email: "admin@example.com",
    notifications: true,
    maintenanceMode: false
  });

  function handleChange(event) {
    const { name, value, type, checked } = event.target;

    setSettings((previous) => ({
      ...previous,
      [name]: type === "checkbox" ? checked : value
    }));
  }

  function handleSubmit(event) {
    event.preventDefault();

    alert("Settings saved successfully!");
    console.log("Updated settings:", settings);
  }

  return (
    <div>
      <div className="page-heading">
        <div>
          <h1>Settings</h1>
          <p>Configure your application preferences.</p>
        </div>
      </div>

      <section className="panel settings-panel">
        <h3>General Settings</h3>

        <form onSubmit={handleSubmit}>
          <label>
            Store Name
            <input
              type="text"
              name="storeName"
              value={settings.storeName}
              onChange={handleChange}
            />
          </label>

          <label>
            Admin Email
            <input
              type="email"
              name="email"
              value={settings.email}
              onChange={handleChange}
            />
          </label>

          <label className="checkbox-row">
            <input
              type="checkbox"
              name="notifications"
              checked={settings.notifications}
              onChange={handleChange}
            />
            Enable Notifications
          </label>

          <label className="checkbox-row">
            <input
              type="checkbox"
              name="maintenanceMode"
              checked={settings.maintenanceMode}
              onChange={handleChange}
            />
            Enable Maintenance Mode
          </label>

          <button className="primary-button" type="submit">
            Save Settings
          </button>
        </form>
      </section>
    </div>
  );
}

export default Settings;

Important Concepts

Controlled Components: The input values are controlled by React state.

Computed Property Names: [name] allows us to update the correct settings property dynamically.

Spread Operator: ...previous preserves the other properties while updating one field.

preventDefault(): Prevents the browser's default form submission behavior.

Note: This example demonstrates frontend state only. It does not permanently save settings to a database.


16. Configure Application Routes

Now we need to connect all pages through React Router.

Open:

src/App.jsx

import { BrowserRouter, Routes, Route } from "react-router";

import Layout from "./components/Layout";

import Dashboard from "./pages/Dashboard";
import Products from "./pages/Products";
import Orders from "./pages/Orders";
import Customers from "./pages/Customers";
import Settings from "./pages/Settings";

function NotFound() {
  return (
    <div className="not-found">
      <h1>404</h1>
      <h2>Page Not Found</h2>
      <p>The page you are looking for does not exist.</p>
    </div>
  );
}

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route element={<Layout />}>
          <Route path="/" element={<Dashboard />} />
          <Route path="/products" element={<Products />} />
          <Route path="/orders" element={<Orders />} />
          <Route path="/customers" element={<Customers />} />
          <Route path="/settings" element={<Settings />} />
        </Route>

        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;

Understanding the Route Configuration

BrowserRouter: Enables browser-based routing.

Routes: Contains the application's route definitions.

Route: Connects a URL path with a React component.

Nested Routes: Allow multiple pages to share the same parent layout.

Wildcard Route (*): Displays the 404 page when no route matches.


17. Configure the Main Entry Point

Open:

src/main.jsx

import React from "react";
import ReactDOM from "react-dom/client";

import App from "./App";
import "./index.css";

ReactDOM.createRoot(
  document.getElementById("root")
).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

This file connects the React application to the HTML document.


18. Design the Complete Dashboard

Now we will style the application.

Open:

src/index.css

@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');

:root {
  font-family: 'Inter', sans-serif;
  color: #202b3c;
  background: #f4f6fb;
  font-synthesis: none;
  text-rendering: optimizeLegibility;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  min-width: 320px;
  background: #f4f6fb;
}

button,
input,
select {
  font: inherit;
}

button {
  cursor: pointer;
}

.app-layout {
  min-height: 100vh;
}

.sidebar {
  position: fixed;
  inset: 0 auto 0 0;
  width: 250px;
  background: #172238;
  color: white;
  padding: 28px 18px;
  display: flex;
  flex-direction: column;
  z-index: 20;
}

.brand {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 48px;
}

.brand-icon {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 12px;
  background: linear-gradient(135deg, #5277ff, #9b5cff);
  font-size: 22px;
  font-weight: 800;
}

.brand h2 {
  font-size: 21px;
}

.menu-label {
  color: #8491a8;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1.2px;
  margin: 0 12px 16px;
}

.sidebar nav {
  display: grid;
  gap: 8px;
}

.nav-link {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 13px 14px;
  border-radius: 10px;
  color: #b9c4d6;
  text-decoration: none;
  transition: 0.2s;
}

.nav-link:hover,
.nav-link.active {
  background: #30415f;
  color: white;
}

.nav-link.active {
  box-shadow: inset 3px 0 #7592ff;
}

.sidebar-bottom {
  margin-top: auto;
  color: #b9c4d6;
  line-height: 1.8;
}

.sidebar-bottom small {
  color: #8491a8;
}

.main-area {
  min-height: 100vh;
  margin-left: 250px;
}

.header {
  min-height: 85px;
  background: white;
  padding: 15px 30px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid #e9edf4;
}

.header-left,
.header-right,
.profile {
  display: flex;
  align-items: center;
}

.header-left {
  gap: 16px;
}

.header-left h2 {
  font-size: 19px;
}

.header-left p {
  color: #8994a6;
  font-size: 12px;
  margin-top: 5px;
}

.header-right {
  gap: 25px;
}

.menu-button,
.notification-button {
  border: 0;
  background: #f1f4fa;
  border-radius: 10px;
  padding: 10px 13px;
  font-size: 18px;
}

.menu-button {
  display: none;
}

.profile {
  gap: 10px;
}

.avatar {
  width: 39px;
  height: 39px;
  border-radius: 50%;
  background: #e5eaff;
  color: #4565dc;
  display: grid;
  place-items: center;
  font-weight: 800;
}

.profile strong,
.profile small {
  display: block;
}

.profile strong {
  font-size: 13px;
}

.profile small {
  color: #8994a6;
  font-size: 11px;
  margin-top: 4px;
}

.page-content {
  padding: 30px;
  max-width: 1600px;
  margin: auto;
}

.page-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 15px;
  margin-bottom: 25px;
}

.page-heading h1 {
  font-size: 25px;
  margin-bottom: 7px;
}

.page-heading p {
  color: #8994a6;
  font-size: 13px;
}

.date-label {
  background: white;
  padding: 11px 15px;
  border-radius: 9px;
  font-size: 12px;
  border: 1px solid #e9edf4;
}

.stats-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 18px;
  margin-bottom: 22px;
}

.stat-card,
.panel {
  background: white;
  border: 1px solid #e9edf4;
  border-radius: 14px;
  padding: 22px;
}

.stat-top {
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: #8994a6;
  font-size: 12px;
}

.stat-icon {
  font-size: 23px;
}

.stat-card h2 {
  font-size: 27px;
  margin: 18px 0 9px;
}

.stat-change {
  color: #13a875;
  font-size: 12px;
  font-weight: 700;
}

.stat-change span {
  color: #8994a6;
  font-weight: 400;
}

.dashboard-grid {
  display: grid;
  grid-template-columns: 1.6fr 1fr;
  gap: 20px;
  margin-bottom: 22px;
}

.panel-heading {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 10px;
  margin-bottom: 25px;
}

.panel-heading h3,
.panel h3 {
  font-size: 16px;
}

.panel-heading p {
  color: #8994a6;
  font-size: 12px;
  margin-top: 6px;
}

.badge {
  background: #edf1ff;
  color: #536ee2;
  padding: 8px 11px;
  border-radius: 7px;
  font-size: 11px;
  font-weight: 700;
}

.chart {
  height: 220px;
  display: flex;
  justify-content: space-around;
  align-items: stretch;
  gap: 15px;
  border-bottom: 1px solid #e9edf4;
}

.chart-column {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  color: #8994a6;
  font-size: 11px;
}

.chart-bar-area {
  height: 180px;
  width: 100%;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.chart-bar {
  width: min(38px, 75%);
  min-height: 8px;
  background: linear-gradient(180deg, #7792ff, #5368d9);
  border-radius: 7px 7px 0 0;
}

.summary-item {
  display: flex;
  justify-content: space-between;
  padding: 16px 0;
  border-bottom: 1px solid #edf0f5;
  font-size: 12px;
}

.summary-item span {
  color: #8994a6;
}

.summary-item strong {
  font-size: 13px;
}

.recent-orders {
  margin-bottom: 25px;
}

.table-wrapper {
  width: 100%;
  overflow-x: auto;
}

table {
  width: 100%;
  border-collapse: collapse;
  text-align: left;
  white-space: nowrap;
}

th {
  color: #8994a6;
  font-size: 11px;
  font-weight: 600;
  background: #f8f9fc;
}

th,
td {
  padding: 15px 13px;
  border-bottom: 1px solid #edf0f5;
}

td {
  font-size: 12px;
}

.status {
  display: inline-block;
  padding: 6px 9px;
  border-radius: 6px;
  font-size: 10px;
  font-weight: 700;
}

.status.completed {
  background: #e5f8ef;
  color: #159563;
}

.status.pending {
  background: #fff4dd;
  color: #c58a20;
}

.status.processing {
  background: #e9efff;
  color: #536ee2;
}

.status.cancelled {
  background: #ffe9e9;
  color: #d95353;
}

.toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 15px;
  margin-bottom: 20px;
}

.toolbar input,
.toolbar select,
.settings-panel input:not([type="checkbox"]) {
  border: 1px solid #e1e6ef;
  border-radius: 8px;
  padding: 11px 13px;
  outline: none;
  background: white;
  color: #202b3c;
}

.toolbar input {
  width: min(100%, 300px);
}

.toolbar input:focus,
.toolbar select:focus,
.settings-panel input:focus {
  border-color: #7189f4;
}

.empty-message {
  padding: 25px;
  text-align: center;
  color: #8994a6;
  font-size: 13px;
}

.settings-panel {
  max-width: 650px;
}

.settings-panel h3 {
  margin-bottom: 22px;
}

.settings-panel form {
  display: grid;
  gap: 18px;
}

.settings-panel form > label:not(.checkbox-row) {
  display: grid;
  gap: 9px;
  font-size: 12px;
  font-weight: 600;
}

.checkbox-row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12px;
}

.checkbox-row input {
  accent-color: #536ee2;
}

.primary-button {
  justify-self: start;
  background: #536ee2;
  color: white;
  border: 0;
  border-radius: 8px;
  padding: 12px 19px;
  font-weight: 600;
}

.primary-button:hover {
  background: #4058c4;
}

.not-found {
  text-align: center;
  padding: 100px 20px;
}

.not-found h1 {
  font-size: 70px;
  color: #536ee2;
}

.not-found p {
  margin-top: 10px;
  color: #8994a6;
}

.sidebar-overlay {
  display: none;
}

/* Responsive Design */

@media (max-width: 1100px) {
  .stats-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .dashboard-grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 768px) {
  .sidebar {
    transform: translateX(-100%);
    transition: transform 0.25s ease;
  }

  .sidebar.open {
    transform: translateX(0);
  }

  .sidebar-overlay {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(10, 20, 40, 0.45);
    z-index: 15;
  }

  .main-area {
    margin-left: 0;
  }

  .menu-button {
    display: block;
  }

  .header {
    padding: 15px;
  }

  .header-right {
    gap: 10px;
  }

  .profile > div:last-child {
    display: none;
  }

  .page-content {
    padding: 20px 15px;
  }
}

@media (max-width: 520px) {
  .stats-grid {
    grid-template-columns: 1fr;
  }

  .page-heading {
    align-items: flex-start;
    flex-direction: column;
  }

  .toolbar {
    align-items: stretch;
    flex-direction: column;
  }

  .toolbar input {
    width: 100%;
  }

  .header-left h2 {
    font-size: 15px;
  }

  .header-left p {
    font-size: 10px;
  }

  .notification-button {
    padding: 9px;
  }
}

19. Run the Complete Application

Open your terminal and execute:

npm run dev

Open the local development URL provided by Vite.

You should now be able to navigate between all five pages.

Expected Application Features

Page Functionality
Dashboard Statistics, chart, recent orders
Products Product table, search, category filtering
Orders Order table, status filtering
Customers Customer information and purchase data
Settings Controlled form inputs and settings state

Application Navigation

Dashboard
   |
   ├── Products
   |
   ├── Orders
   |
   ├── Customers
   |
   └── Settings

All these pages share the same sidebar and header through the Layout component.


20. How the Complete Project Works

Let's understand the application's overall architecture.

Step 1: Application Starts

The main.jsx file renders the App component.

Step 2: React Router Checks the URL

React Router identifies which page the user wants to access.

Step 3: Layout Component Loads

The common sidebar and header are displayed.

Step 4: Outlet Renders the Selected Page

The Outlet component displays the matching child route.

Step 5: Page Displays Its Data

Each page imports the required information from mockData.js and renders it.

Step 6: React State Handles User Interactions

Search fields, dropdowns, checkboxes, and the mobile sidebar use React state to update the interface.


21. Important Concepts Used in This Project

Concept Where We Used It
Components Sidebar, Header, StatCard
Props Passing data and functions
useState Search, filters, settings, sidebar
map() Rendering products, orders, and customers
filter() Searching and filtering records
useLocation() Reading the current route
NavLink Sidebar navigation
Outlet Rendering nested routes
BrowserRouter Enabling browser routing
Conditional Rendering Active links and empty states
CSS Grid Dashboard statistics and page layout
Responsive CSS Mobile and tablet layouts

22. Common Mistakes and Best Practices

Mistake 1: Writing the Same UI Repeatedly

Problem: Repeated code makes an application difficult to maintain.

Solution: Create reusable components such as StatCard, Header, and Sidebar.

Mistake 2: Forgetting the React Key

Incorrect:

products.map((product) => (
  <div>{product.name}</div>
))

Correct:

products.map((product) => (
  <div key={product.id}>
    {product.name}
  </div>
))

Mistake 3: Updating State Directly

Incorrect:

settings.email = "new@example.com";

Correct:

setSettings((previous) => ({
  ...previous,
  email: "new@example.com"
}));

Mistake 4: Forgetting Nested Route Layouts

If the parent layout does not contain an Outlet, nested pages will not render inside it.

Mistake 5: Ignoring Mobile Responsiveness

A dashboard should work on desktop, tablets, and mobile devices.

Use CSS Grid, flexible layouts, media queries, and horizontally scrollable tables where necessary.

Mistake 6: Treating Frontend Data as Secure

Mock data and frontend state are not a secure database.

For a real production dashboard:

  • Connect to a backend API.

  • Store business data in a database.

  • Authenticate administrators on the server.

  • Validate permissions on every protected API.

  • Validate and sanitize incoming data.

  • Never expose private API keys in frontend code.


23. Practice Exercises

Complete these exercises to improve your React skills.

Exercise 1: Add a new Analytics page to the sidebar.

Exercise 2: Add a search field to the Customers page.

Exercise 3: Add a date filter to the Orders page.

Exercise 4: Create a reusable PageHeading component.

Exercise 5: Add a dark mode toggle using React state.

Exercise 6: Add pagination to the Products page.

Exercise 7: Create a product details page using a dynamic route.

Exercise 8: Connect the dashboard to a real backend API.

Exercise 9: Add a logout button and administrator authentication.

Exercise 10: Store settings in a backend database instead of temporary React state.


24. Key Takeaways

In this tutorial, we developed a multi-page React Admin Dashboard using React.js and React Router.

You learned how to:

  1. Create a professional React project using Vite.

  2. Organize files and folders.

  3. Develop reusable components.

  4. Configure nested routes.

  5. Build a responsive sidebar and header.

  6. Display dashboard statistics and charts.

  7. Search and filter products.

  8. Filter orders by status.

  9. Display customer records.

  10. Manage controlled settings forms.

  11. Build responsive layouts with CSS.

  12. Understand the architecture of a larger React application.

Important: This project is a frontend demonstration with static mock data. It is not yet a complete production admin system connected to a backend or database.


25. What's Next?

In Tutorial 29, we will prepare for React.js technical interviews.

Next Tutorial: React.js Interview Questions and Answers — Beginner to Advanced

We will cover important React interview questions, clear definitions, practical examples, and interview-ready answers to help you prepare for technical interviews.

←
Post 27 of 29
→