Create an admin dashboard that displays KPIs, charts, and a data table. Practice Vue 3 Composition API, routing, centralized state, and data visualization.

Requirements

  • Node.js 18+ and npm
  • Basic Vue 3 knowledge — components, reactivity, and templates
  • Familiarity with REST APIs or mock data

Features

  • Sidebar navigation with multiple views
  • KPI cards (revenue, users, orders, growth)
  • Line and bar charts for trends
  • Sortable data table
  • Responsive layout

Step 1: Scaffold the Project

  npm create vue@latest dashboard -- --typescript --router --pinia
cd dashboard
npm install chart.js vue-chartjs
npm run dev
  

Step 2: Mock Data

src/data/mock.ts

  export const kpis = [
  { label: 'Revenue', value: '$48,290', change: '+12.5%', positive: true },
  { label: 'Users', value: '2,847', change: '+8.2%', positive: true },
  { label: 'Orders', value: '1,203', change: '-3.1%', positive: false },
  { label: 'Growth', value: '24.8%', change: '+4.6%', positive: true },
];

export const salesData = {
  labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
  datasets: [{ label: 'Sales', data: [12, 19, 8, 15, 22, 30], borderColor: '#3b82f6' }],
};

export const orders = [
  { id: 1001, customer: 'Alice Chen', product: 'Pro Plan', amount: 99, status: 'Completed' },
  { id: 1002, customer: 'Bob Smith', product: 'Basic Plan', amount: 29, status: 'Pending' },
  { id: 1003, customer: 'Carol Lee', product: 'Enterprise', amount: 299, status: 'Completed' },
];
  

Step 3: KPI Card Component

src/components/KpiCard.vue

  <script setup lang="ts">
defineProps<{ label: string; value: string; change: string; positive: boolean }>();
</script>

<template>
  <div class="kpi-card">
    <p class="label">{{ label }}</p>
    <p class="value">{{ value }}</p>
    <p :class="['change', positive ? 'up' : 'down']">{{ change }}</p>
  </div>
</template>

<style scoped>
.kpi-card { background: #fff; border-radius: 8px; padding: 1.5rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.value { font-size: 1.75rem; font-weight: bold; margin: 0.25rem 0; }
.up { color: #16a34a; } .down { color: #dc2626; }
</style>
  

Step 4: Chart Component

src/components/SalesChart.vue

  <script setup lang="ts">
import { Line } from 'vue-chartjs';
import { Chart as ChartJS, LineElement, PointElement, LinearScale, CategoryScale, Title, Tooltip, Legend } from 'chart.js';

ChartJS.register(LineElement, PointElement, LinearScale, CategoryScale, Title, Tooltip, Legend);

defineProps<{ chartData: object }>();
</script>

<template>
  <div class="chart-container">
    <Line :data="chartData" :options="{ responsive: true, plugins: { legend: { display: false } } }" />
  </div>
</template>
  

Step 5: Dashboard View

src/views/DashboardView.vue

  <script setup lang="ts">
import KpiCard from '@/components/KpiCard.vue';
import SalesChart from '@/components/SalesChart.vue';
import { kpis, salesData } from '@/data/mock';
</script>

<template>
  <div class="dashboard">
    <h1>Overview</h1>
    <div class="kpi-grid">
      <KpiCard v-for="kpi in kpis" :key="kpi.label" v-bind="kpi" />
    </div>
    <div class="chart-section">
      <h2>Sales Trend</h2>
      <SalesChart :chart-data="salesData" />
    </div>
  </div>
</template>

<style scoped>
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin: 1.5rem 0; }
.chart-section { background: #fff; border-radius: 8px; padding: 1.5rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
</style>
  

Step 6: Layout and Orders Store

src/App.vue — sidebar with RouterLink to Overview and Orders views. src/stores/orders.ts — Pinia store that holds the mock orders array and a setSort(key) action to toggle ascending/descending column sort.

Extension Ideas

  • Live data — fetch KPIs from a REST API with fetch in onMounted
  • Date range filter — let users select a time window to update charts
  • Dark mode — toggle theme with a Pinia store and CSS variables
  • Export CSV — download the orders table as a spreadsheet
  • Notifications panel — show recent alerts in a dropdown
  • Role-based views — restrict certain pages based on user permissions