Building an Admin Dashboard in Laravel

Lesson 48 of 50

96%
1. Introduction to an Admin Dashboard

An admin dashboard is the main control panel of a web application. It allows authorized administrators to monitor statistics and manage application data.

Our dashboard will include:

  • Total students.
  • Total courses.
  • Total fee collections.
  • Pending fee amounts.
  • Recent student registrations.
  • Quick links to management pages.

Technologies:

  • Laravel for backend processing.
  • Blade for rendering dashboard views.
  • MySQL for storing application data.
  • Bootstrap for responsive cards and tables.

We will build a dashboard for a student management application. The examples use a Student model and a Course model. Adjust the model names and database fields to match your own application.

2. Create the Admin Dashboard Controller

Generate a controller using Artisan:

php artisan make:controller AdminDashboardController

Open app/Http/Controllers/AdminDashboardController.php.

<?php

namespace App\Http\Controllers;

use App\Models\Student;
use App\Models\Course;
use Illuminate\Support\Facades\DB;

class AdminDashboardController extends Controller
{
    public function index()
    {
        $totalStudents = Student::count();

        $totalCourses = Course::count();

        $totalCollected = DB::table('payments')
            ->where('status', 'paid')
            ->sum('amount');

        $pendingFees = DB::table('student_fees')
            ->where('status', 'pending')
            ->sum('amount');

        $recentStudents = Student::latest()
            ->take(5)
            ->get();

        return view('admin.dashboard', compact(
            'totalStudents',
            'totalCourses',
            'totalCollected',
            'pendingFees',
            'recentStudents'
        ));
    }
}

This example assumes your application has students, courses, payments, and student_fees tables, and that payment records use a status field with the value paid.

Update the model names, table names, and column names if your database uses a different structure.

3. Create the Admin Dashboard Route

Open routes/web.php.

Add the following route:

use App\Http\Controllers\AdminDashboardController;
use Illuminate\Support\Facades\Route;

Route::get(
    '/admin/dashboard',
    [AdminDashboardController::class, 'index']
)->name('admin.dashboard');

The route connects the dashboard URL to the index() method of the controller.

To check the registered route, run:

php artisan route:list --name=admin.dashboard

Important: this route is not protected by authentication yet. We will discuss how to restrict access in the security section.

4. Create the Dashboard View

Create this directory if it does not exist:

resources/views/admin

Create resources/views/admin/dashboard.blade.php:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Admin Dashboard</title>

    <link
        href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
        rel="stylesheet">
</head>

<body class="bg-light">

<nav class="navbar navbar-dark bg-dark">
    <div class="container-fluid">
        <span class="navbar-brand">Student Management Admin</span>
    </div>
</nav>

<div class="container-fluid py-4">
    <h2 class="mb-4">Admin Dashboard</h2>

    @if (session('success'))
        <div class="alert alert-success">
            {{ session('success') }}
        </div>
    @endif

    <div class="row g-4">

        <div class="col-md-6 col-xl-3">
            <div class="card border-primary shadow-sm h-100">
                <div class="card-body">
                    <h6 class="text-muted">Total Students</h6>
                    <h2 class="text-primary">
                        {{ number_format($totalStudents) }}
                    </h2>
                    <a href="{{ url('/students') }}"
                       class="btn btn-outline-primary btn-sm">
                        Manage Students
                    </a>
                </div>
            </div>
        </div>

        <div class="col-md-6 col-xl-3">
            <div class="card border-success shadow-sm h-100">
                <div class="card-body">
                    <h6 class="text-muted">Total Courses</h6>
                    <h2 class="text-success">
                        {{ number_format($totalCourses) }}
                    </h2>
                    <a href="{{ url('/courses') }}"
                       class="btn btn-outline-success btn-sm">
                        Manage Courses
                    </a>
                </div>
            </div>
        </div>

        <div class="col-md-6 col-xl-3">
            <div class="card border-info shadow-sm h-100">
                <div class="card-body">
                    <h6 class="text-muted">Fees Collected</h6>
                    <h2 class="text-info">
                        ₹{{ number_format($totalCollected, 2) }}
                    </h2>
                    <a href="{{ url('/payments') }}"
                       class="btn btn-outline-info btn-sm">
                        View Payments
                    </a>
                </div>
            </div>
        </div>

        <div class="col-md-6 col-xl-3">
            <div class="card border-danger shadow-sm h-100">
                <div class="card-body">
                    <h6 class="text-muted">Pending Fees</h6>
                    <h2 class="text-danger">
                        ₹{{ number_format($pendingFees, 2) }}
                    </h2>
                    <a href="{{ url('/fees') }}"
                       class="btn btn-outline-danger btn-sm">
                        Manage Fees
                    </a>
                </div>
            </div>
        </div>

    </div>

    <div class="card shadow-sm mt-5">
        <div class="card-header bg-primary text-white">
            <h5 class="mb-0">Recent Student Registrations</h5>
        </div>

        <div class="card-body p-0">
            <div class="table-responsive">
                <table class="table table-striped table-bordered mb-0">
                    <thead class="table-dark">
                        <tr>
                            <th>ID</th>
                            <th>Student Name</th>
                            <th>Email</th>
                            <th>Course</th>
                        </tr>
                    </thead>

                    <tbody>
                        @forelse ($recentStudents as $student)
                            <tr>
                                <td>{{ $student->id }}</td>
                                <td>{{ $student->name }}</td>
                                <td>{{ $student->email }}</td>
                                <td>{{ $student->course ?? 'N/A' }}</td>
                            </tr>
                        @empty
                            <tr>
                                <td colspan="4" class="text-center">
                                    No students registered yet.
                                </td>
                            </tr>
                        @endforelse
                    </tbody>
                </table>
            </div>
        </div>
    </div>

</div>

</body>
</html>

The dashboard displays four summary cards and a table containing the five most recently registered students.

The example links to /courses, /payments, and /fees are placeholders; create those routes before using them.

5. Understand Dashboard Statistics

Dashboard statistics are calculated from database records. Common Eloquent methods include:

// Count all students
$totalStudents = Student::count();

// Count students admitted today
$todayStudents = Student::whereDate(
    'created_at',
    today()
)->count();

// Get the five latest students
$recentStudents = Student::latest()
    ->take(5)
    ->get();

// Count students enrolled in a particular course
$adcaStudents = Student::where(
    'course',
    'ADCA'
)->count();

You can display these values in additional dashboard cards.

Example:

<div class="card border-warning shadow-sm">
    <div class="card-body">
        <h6>Today's Admissions</h6>
        <h3 class="text-warning">
            {{ number_format($todayStudents) }}
        </h3>
    </div>
</div>

If you add this card, remember to calculate $todayStudents in the controller and pass it to the view.

6. Add a Monthly Fee Collection Report

A dashboard can also show how much money was collected each month.

For this example, assume the payments table contains amount, status, and created_at columns.

Add this query inside the controller:

use Illuminate\Support\Facades\DB;

$monthlyCollections = DB::table('payments')
    ->selectRaw('MONTH(created_at) AS month_number')
    ->selectRaw('SUM(amount) AS total_amount')
    ->where('status', 'paid')
    ->whereYear('created_at', now()->year)
    ->groupByRaw('MONTH(created_at)')
    ->orderByRaw('MONTH(created_at)')
    ->get();

Include $monthlyCollections in the controller's compact() call.

Display the results in Blade:

<div class="card border-info shadow-sm mt-4">
    <div class="card-header bg-info text-white">
        Monthly Fee Collection
    </div>

    <div class="card-body">
        <table class="table table-bordered">
            <thead>
                <tr>
                    <th>Month Number</th>
                    <th>Collected Amount</th>
                </tr>
            </thead>

            <tbody>
                @forelse ($monthlyCollections as $collection)
                    <tr>
                        <td>{{ $collection->month_number }}</td>
                        <td>
                            ₹{{ number_format($collection->total_amount, 2) }}
                        </td>
                    </tr>
                @empty
                    <tr>
                        <td colspan="2">No collections found.</td>
                    </tr>
                @endforelse
            </tbody>
        </table>
    </div>
</div>

This query uses MySQL month functions. If your payment date is stored in a separate column, use that column instead of created_at.

7. Add Authentication and Protect the Dashboard

An admin dashboard should not be accessible to every visitor. Protect it with authentication and, where appropriate, an admin authorization check.

If your project has authentication configured, a basic authenticated route can be written as follows:

use App\Http\Controllers\AdminDashboardController;
use Illuminate\Support\Facades\Route;

Route::middleware('auth')->group(function () {
    Route::get(
        '/admin/dashboard',
        [AdminDashboardController::class, 'index']
    )->name('admin.dashboard');
});

The auth middleware requires the visitor to be authenticated. It does not, by itself, prove that the user is an administrator.

For an application with multiple user roles, create an authorization policy, gate, or admin middleware that checks the authenticated user's role.

Example concept:

// Example only: requires a role field and
// appropriate authentication setup.

if (! auth()->user()->is_admin) {
    abort(403);
}

Place authorization checks in appropriate middleware, policies, or controller authorization logic rather than relying only on hiding dashboard links.

8. Improve the Dashboard UI

Some useful dashboard design improvements include:

  • Use different colored borders for statistics cards.
  • Keep the layout responsive with Bootstrap grid classes.
  • Use tables for recent records.
  • Add pagination to long lists.
  • Use clear labels for collected and pending fees.
  • Display validation errors and success messages.
  • Protect all management routes with appropriate authorization.
  • Use reusable Blade layouts and components to avoid duplicated markup.

Example of a reusable Blade component:

<x-dashboard-card
    title="Total Students"
    :value="$totalStudents"
    color="primary"
/>

This is an example of how a custom Blade component might be used. You must create the component and define its accepted properties before using it.

9. Practice Assignment

Build your own Laravel admin dashboard.

  1. Create an AdminDashboardController.
  2. Create a dashboard route and Blade view.
  3. Display the total number of students.
  4. Display the total number of courses.
  5. Display the total collected fees.
  6. Display the pending fee amount using your database's actual schema.
  7. Show the five latest student registrations.
  8. Add a monthly fee collection report.
  9. Protect the dashboard with authentication and admin authorization.

Bonus Task:

Add a year filter to the fee report and display the selected year's monthly collection totals.

Quick Quiz

1. What is the main purpose of an admin dashboard?




2. Which Eloquent method counts database records?




3. Does the auth middleware alone guarantee that a user is an administrator?