Leave Management System for Malaysian Companies

I’ll create a comprehensive, standalone leave management system that meets your requirements: PHP/MySQL backend with a clean, modern UI without jQuery or Bootstrap.

Features to Implement:

  • Employee authentication and role management
  • Leave application and approval workflow
  • Malaysian public holiday integration
  • Leave balance tracking
  • Responsive design with mobile support

Solution Overview

Database Structure (MySQL)

CREATE DATABASE leave_management;
USE leave_management;

CREATE TABLE employees (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    role ENUM('employee', 'manager', 'admin') DEFAULT 'employee',
    manager_id INT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (manager_id) REFERENCES employees(id) ON DELETE SET NULL
);

CREATE TABLE leave_types (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    default_days INT NOT NULL
);

CREATE TABLE leave_balances (
    id INT AUTO_INCREMENT PRIMARY KEY,
    employee_id INT NOT NULL,
    leave_type_id INT NOT NULL,
    balance INT NOT NULL,
    year INT NOT NULL,
    FOREIGN KEY (employee_id) REFERENCES employees(id) ON DELETE CASCADE,
    FOREIGN KEY (leave_type_id) REFERENCES leave_types(id) ON DELETE CASCADE
);

CREATE TABLE public_holidays (
    id INT AUTO_INCREMENT PRIMARY KEY,
    date DATE NOT NULL UNIQUE,
    name VARCHAR(100) NOT NULL
);

CREATE TABLE leave_applications (
    id INT AUTO_INCREMENT PRIMARY KEY,
    employee_id INT NOT NULL,
    leave_type_id INT NOT NULL,
    start_date DATE NOT NULL,
    end_date DATE NOT NULL,
    days INT NOT NULL,
    reason TEXT,
    status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
    applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    manager_id INT,
    approved_at TIMESTAMP NULL,
    FOREIGN KEY (employee_id) REFERENCES employees(id) ON DELETE CASCADE,
    FOREIGN KEY (leave_type_id) REFERENCES leave_types(id) ON DELETE CASCADE,
    FOREIGN KEY (manager_id) REFERENCES employees(id) ON DELETE SET NULL
);

PHP Implementation

<?php
// Database configuration
define('DB_HOST', 'localhost');
define('DB_NAME', 'leave_management');
define('DB_USER', 'root');
define('DB_PASS', '');

// Create database connection
function db_connect() {
    $conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
    if ($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    }
    return $conn;
}

session_start();

// Initialize the system with sample data if needed
function initialize_system() {
    $conn = db_connect();

    // Insert admin user
    $password = password_hash('admin123', PASSWORD_DEFAULT);
    $conn->query("INSERT IGNORE INTO employees (name, email, password, role) 
                  VALUES ('Admin User', 'admin@company.com', '$password', 'admin')");

    // Insert leave types
    $leave_types = [
        ['Annual Leave', 14],
        ['Medical Leave', 14],
        ['Emergency Leave', 5],
        ['Maternity Leave', 60],
        ['Paternity Leave', 7]
    ];

    foreach ($leave_types as $type) {
        $name = $conn->real_escape_string($type[0]);
        $days = (int)$type[1];
        $conn->query("INSERT IGNORE INTO leave_types (name, default_days) 
                      VALUES ('$name', $days)");
    }

    // Insert Malaysian public holidays (sample for 2023)
    $holidays = [
        ['2023-01-01', 'New Year\'s Day'],
        ['2023-01-22', 'Chinese New Year'],
        ['2023-01-23', 'Chinese New Year Holiday'],
        ['2023-04-21', 'Hari Raya Puasa'],
        ['2023-04-22', 'Hari Raya Puasa Holiday'],
        ['2023-05-01', 'Labour Day'],
        ['2023-05-04', 'Wesak Day'],
        ['2023-06-05', 'Agong\'s Birthday'],
        ['2023-06-29', 'Hari Raya Haji'],
        ['2023-08-31', 'National Day'],
        ['2023-09-16', 'Malaysia Day'],
        ['2023-11-12', 'Deepavali'],
        ['2023-12-25', 'Christmas Day']
    ];

    foreach ($holidays as $holiday) {
        $date = $conn->real_escape_string($holiday[0]);
        $name = $conn->real_escape_string($holiday[1]);
        $conn->query("INSERT IGNORE INTO public_holidays (date, name) 
                      VALUES ('$date', '$name')");
    }

    $conn->close();
}

initialize_system();

// Handle authentication
if (isset($_POST['login'])) {
    $email = $_POST['email'];
    $password = $_POST['password'];

    $conn = db_connect();
    $stmt = $conn->prepare("SELECT * FROM employees WHERE email = ?");
    $stmt->bind_param("s", $email);
    $stmt->execute();
    $result = $stmt->get_result();

    if ($result->num_rows > 0) {
        $user = $result->fetch_assoc();
        if (password_verify($password, $user['password'])) {
            $_SESSION['user_id'] = $user['id'];
            $_SESSION['name'] = $user['name'];
            $_SESSION['role'] = $user['role'];
            header("Location: index.php");
            exit();
        }
    }
    $error = "Invalid email or password";
}

if (isset($_GET['logout'])) {
    session_destroy();
    header("Location: index.php");
    exit();
}

// Helper functions
function get_current_user() {
    if (!isset($_SESSION['user_id'])) return null;

    $conn = db_connect();
    $stmt = $conn->prepare("SELECT * FROM employees WHERE id = ?");
    $stmt->bind_param("i", $_SESSION['user_id']);
    $stmt->execute();
    $result = $stmt->get_result();
    return $result->fetch_assoc();
}

function is_admin() {
    return isset($_SESSION['role']) && $_SESSION['role'] === 'admin';
}

function is_manager() {
    return isset($_SESSION['role']) && ($_SESSION['role'] === 'manager' || $_SESSION['role'] === 'admin');
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Leave Management System</title>
    <style>
        :root {
            --primary: #0066B3;
            --secondary: #F58220;
            --success: #28a745;
            --warning: #ffc107;
            --danger: #dc3545;
            --light: #f8f9fa;
            --dark: #343a40;
            --gray: #6c757d;
            --light-gray: #e9ecef;
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }

        body {
            background-color: #f5f7fa;
            color: #333;
            line-height: 1.6;
        }

        .container {
            width: 100%;
            max-width: 1200px;
            margin: 0 auto;
            padding: 0 15px;
        }

        header {
            background-color: white;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            padding: 1rem 0;
            position: sticky;
            top: 0;
            z-index: 100;
        }

        .header-content {
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .logo {
            display: flex;
            align-items: center;
            gap: 10px;
        }

        .logo h1 {
            font-size: 1.5rem;
            color: var(--primary);
        }

        .logo-icon {
            background-color: var(--primary);
            color: white;
            width: 40px;
            height: 40px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-weight: bold;
        }

        nav ul {
            display: flex;
            list-style: none;
            gap: 1.5rem;
        }

        nav a {
            text-decoration: none;
            color: var(--dark);
            font-weight: 500;
            padding: 0.5rem 0;
            position: relative;
        }

        nav a:hover::after,
        nav a.active::after {
            content: '';
            position: absolute;
            bottom: 0;
            left: 0;
            width: 100%;
            height: 3px;
            background-color: var(--primary);
        }

        .user-menu {
            display: flex;
            align-items: center;
            gap: 1rem;
        }

        .user-info {
            display: flex;
            align-items: center;
            gap: 0.5rem;
        }

        .avatar {
            width: 35px;
            height: 35px;
            background-color: var(--primary);
            color: white;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-weight: bold;
        }

        .btn {
            display: inline-block;
            padding: 0.5rem 1rem;
            background-color: var(--primary);
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            text-decoration: none;
            font-weight: 500;
            transition: all 0.3s ease;
        }

        .btn:hover {
            background-color: #0056a3;
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
        }

        .btn-secondary {
            background-color: var(--secondary);
        }

        .btn-secondary:hover {
            background-color: #e06d00;
        }

        .btn-success {
            background-color: var(--success);
        }

        .btn-warning {
            background-color: var(--warning);
            color: var(--dark);
        }

        .btn-danger {
            background-color: var(--danger);
        }

        .btn-outline {
            background-color: transparent;
            border: 1px solid var(--primary);
            color: var(--primary);
        }

        .btn-outline:hover {
            background-color: var(--primary);
            color: white;
        }

        .card {
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
            padding: 1.5rem;
            margin-bottom: 1.5rem;
        }

        .card-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 1.5rem;
            padding-bottom: 0.75rem;
            border-bottom: 1px solid var(--light-gray);
        }

        .card-title {
            font-size: 1.25rem;
            font-weight: 600;
            color: var(--dark);
        }

        .dashboard {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 1.5rem;
            margin: 2rem 0;
        }

        .stat-card {
            text-align: center;
            padding: 1.5rem;
        }

        .stat-value {
            font-size: 2.5rem;
            font-weight: 700;
            color: var(--primary);
            margin: 0.5rem 0;
        }

        .stat-label {
            color: var(--gray);
            font-size: 0.9rem;
        }

        table {
            width: 100%;
            border-collapse: collapse;
            margin: 1rem 0;
        }

        th, td {
            padding: 0.75rem;
            text-align: left;
            border-bottom: 1px solid var(--light-gray);
        }

        th {
            background-color: var(--light);
            font-weight: 600;
            color: var(--dark);
        }

        tr:hover {
            background-color: rgba(0, 102, 179, 0.03);
        }

        .status {
            padding: 0.25rem 0.5rem;
            border-radius: 20px;
            font-size: 0.85rem;
            font-weight: 500;
        }

        .status-pending {
            background-color: rgba(255, 193, 7, 0.2);
            color: #b38b00;
        }

        .status-approved {
            background-color: rgba(40, 167, 69, 0.2);
            color: #218838;
        }

        .status-rejected {
            background-color: rgba(220, 53, 69, 0.2);
            color: #c82333;
        }

        .form-group {
            margin-bottom: 1.25rem;
        }

        label {
            display: block;
            margin-bottom: 0.5rem;
            font-weight: 500;
        }

        input, select, textarea {
            width: 100%;
            padding: 0.75rem;
            border: 1px solid #ced4da;
            border-radius: 4px;
            font-size: 1rem;
            transition: border-color 0.15s ease-in-out;
        }

        input:focus, select:focus, textarea:focus {
            border-color: var(--primary);
            outline: none;
            box-shadow: 0 0 0 0.2rem rgba(0, 102, 179, 0.25);
        }

        .form-row {
            display: flex;
            gap: 1rem;
            margin-bottom: 1.25rem;
        }

        .form-row .form-group {
            flex: 1;
            margin-bottom: 0;
        }

        .alert {
            padding: 1rem;
            border-radius: 4px;
            margin-bottom: 1.5rem;
        }

        .alert-success {
            background-color: rgba(40, 167, 69, 0.1);
            border: 1px solid rgba(40, 167, 69, 0.3);
            color: var(--success);
        }

        .alert-danger {
            background-color: rgba(220, 53, 69, 0.1);
            border: 1px solid rgba(220, 53, 69, 0.3);
            color: var(--danger);
        }

        .alert-warning {
            background-color: rgba(255, 193, 7, 0.1);
            border: 1px solid rgba(255, 193, 7, 0.3);
            color: #b38b00;
        }

        .login-container {
            max-width: 400px;
            margin: 5rem auto;
            padding: 2rem;
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
        }

        .login-container h2 {
            text-align: center;
            margin-bottom: 1.5rem;
            color: var(--primary);
        }

        footer {
            background-color: var(--dark);
            color: white;
            padding: 2rem 0;
            margin-top: 3rem;
        }

        .footer-content {
            display: flex;
            justify-content: space-between;
            flex-wrap: wrap;
            gap: 2rem;
        }

        .footer-section {
            flex: 1;
            min-width: 250px;
        }

        .footer-section h3 {
            margin-bottom: 1rem;
            font-size: 1.25rem;
        }

        .copyright {
            text-align: center;
            padding-top: 1.5rem;
            margin-top: 1.5rem;
            border-top: 1px solid rgba(255, 255, 255, 0.1);
        }

        @media (max-width: 768px) {
            .header-content {
                flex-direction: column;
                gap: 1rem;
            }

            nav ul {
                gap: 1rem;
            }

            .user-menu {
                margin-top: 1rem;
            }

            .form-row {
                flex-direction: column;
                gap: 0;
            }
        }
    </style>
</head>
<body>
    <?php if (!isset($_SESSION['user_id'])): ?>
        <div class="login-container card">
            <h2>Leave Management System</h2>
            <form method="POST">
                <div class="form-group">
                    <label for="email">Email</label>
                    <input type="email" id="email" name="email" required>
                </div>
                <div class="form-group">
                    <label for="password">Password</label>
                    <input type="password" id="password" name="password" required>
                </div>
                <?php if (isset($error)): ?>
                    <div class="alert alert-danger"><?php echo $error; ?></div>
                <?php endif; ?>
                <button type="submit" name="login" class="btn" style="width: 100%;">Login</button>
            </form>
        </div>
    <?php else: 
        $user = get_current_user();
    ?>
    <header>
        <div class="container">
            <div class="header-content">
                <div class="logo">
                    <div class="logo-icon">LMS</div>
                    <h1>Leave Management System</h1>
                </div>
                <nav>
                    <ul>
                        <li><a href="index.php" class="active">Dashboard</a></li>
                        <li><a href="index.php?page=leaves">My Leaves</a></li>
                        <li><a href="index.php?page=apply">Apply Leave</a></li>
                        <?php if (is_manager()): ?>
                            <li><a href="index.php?page=approvals">Approvals</a></li>
                        <?php endif; ?>
                        <?php if (is_admin()): ?>
                            <li><a href="index.php?page=admin">Admin</a></li>
                        <?php endif; ?>
                    </ul>
                </nav>
                <div class="user-menu">
                    <div class="user-info">
                        <div class="avatar"><?php echo strtoupper(substr($user['name'], 0, 1)); ?></div>
                        <span><?php echo $user['name']; ?></span>
                    </div>
                    <a href="?logout" class="btn btn-outline">Logout</a>
                </div>
            </div>
        </div>
    </header>

    <main class="container">
        <?php
        $page = isset($_GET['page']) ? $_GET['page'] : 'dashboard';

        switch ($page) {
            case 'dashboard':
                include 'dashboard.php';
                break;
            case 'leaves':
                include 'leaves.php';
                break;
            case 'apply':
                include 'apply.php';
                break;
            case 'approvals':
                if (is_manager()) include 'approvals.php';
                else include 'dashboard.php';
                break;
            case 'admin':
                if (is_admin()) include 'admin.php';
                else include 'dashboard.php';
                break;
            default:
                include 'dashboard.php';
        }
        ?>
    </main>

    <footer>
        <div class="container">
            <div class="footer-content">
                <div class="footer-section">
                    <h3>About LMS</h3>
                    <p>Streamline your company's leave management process with our easy-to-use system designed specifically for Malaysian businesses.</p>
                </div>
                <div class="footer-section">
                    <h3>Quick Links</h3>
                    <ul>
                        <li><a href="index.php">Dashboard</a></li>
                        <li><a href="index.php?page=apply">Apply Leave</a></li>
                        <li><a href="index.php?page=leaves">My Leaves</a></li>
                    </ul>
                </div>
                <div class="footer-section">
                    <h3>Contact</h3>
                    <p>Email: support@lms.com</p>
                    <p>Phone: +60 3-1234 5678</p>
                </div>
            </div>
            <div class="copyright">
                <p>&copy; <?php echo date('Y'); ?> Leave Management System. All rights reserved.</p>
            </div>
        </div>
    </footer>
    <?php endif; ?>

    <script>
        // Client-side validation and date calculation
        document.addEventListener('DOMContentLoaded', function() {
            // Calculate leave days when dates change
            const startDate = document.getElementById('start_date');
            const endDate = document.getElementById('end_date');
            const daysField = document.getElementById('days');

            if (startDate && endDate && daysField) {
                startDate.addEventListener('change', calculateDays);
                endDate.addEventListener('change', calculateDays);

                function calculateDays() {
                    if (startDate.value && endDate.value) {
                        const start = new Date(startDate.value);
                        const end = new Date(endDate.value);

                        // Calculate difference in days
                        const diffTime = Math.abs(end - start);
                        const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
                        daysField.value = diffDays;
                    }
                }
            }

            // Form validation
            const forms = document.querySelectorAll('form');
            forms.forEach(form => {
                form.addEventListener('submit', function(e) {
                    let valid = true;
                    const requiredFields = form.querySelectorAll('[required]');

                    requiredFields.forEach(field => {
                        if (!field.value.trim()) {
                            valid = false;
                            field.style.borderColor = 'var(--danger)';
                        } else {
                            field.style.borderColor = '';
                        }
                    });

                    if (!valid) {
                        e.preventDefault();
                        alert('Please fill in all required fields.');
                    }
                });
            });
        });
    </script>
</body>
</html>

Sample Dashboard Page (dashboard.php)

<div class="dashboard">
    <div class="card">
        <div class="card-header">
            <h2 class="card-title">My Leave Balances</h2>
        </div>
        <div class="dashboard">
            <div class="stat-card card">
                <div class="stat-value">14</div>
                <div class="stat-label">Annual Leave</div>
            </div>
            <div class="stat-card card">
                <div class="stat-value">14</div>
                <div class="stat-label">Medical Leave</div>
            </div>
            <div class="stat-card card">
                <div class="stat-value">5</div>
                <div class="stat-label">Emergency Leave</div>
            </div>
        </div>
    </div>

    <div class="card">
        <div class="card-header">
            <h2 class="card-title">Pending Approvals</h2>
            <a href="index.php?page=approvals" class="btn">View All</a>
        </div>
        <table>
            <thead>
                <tr>
                    <th>Employee</th>
                    <th>Leave Type</th>
                    <th>Dates</th>
                    <th>Days</th>
                    <th>Status</th>
                    <th>Actions</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>Ali bin Ahmad</td>
                    <td>Annual Leave</td>
                    <td>15/12/2023 - 18/12/2023</td>
                    <td>4</td>
                    <td><span class="status status-pending">Pending</span></td>
                    <td>
                        <a href="#" class="btn btn-success" style="padding: 0.25rem 0.5rem;">Approve</a>
                        <a href="#" class="btn btn-danger" style="padding: 0.25rem 0.5rem;">Reject</a>
                    </td>
                </tr>
                <tr>
                    <td>Siti binti Ramli</td>
                    <td>Medical Leave</td>
                    <td>20/12/2023 - 22/12/2023</td>
                    <td>3</td>
                    <td><span class="status status-pending">Pending</span></td>
                    <td>
                        <a href="#" class="btn btn-success" style="padding: 0.25rem 0.5rem;">Approve</a>
                        <a href="#" class="btn btn-danger" style="padding: 0.25rem 0.5rem;">Reject</a>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>

    <div class="card">
        <div class="card-header">
            <h2 class="card-title">Upcoming Public Holidays</h2>
        </div>
        <table>
            <thead>
                <tr>
                    <th>Date</th>
                    <th>Holiday Name</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>25/12/2023</td>
                    <td>Christmas Day</td>
                </tr>
                <tr>
                    <td>01/01/2024</td>
                    <td>New Year's Day</td>
                </tr>
                <tr>
                    <td>10/02/2024</td>
                    <td>Chinese New Year</td>
                </tr>
            </tbody>
        </table>
    </div>
</div>

Key Features Implemented

  1. Malaysian Public Holidays Integration
  • Dedicated table to store Malaysian public holidays
  • Displayed on the dashboard for reference
  1. Role-Based Access Control
  • Employees: Apply for leave, view balances
  • Managers: Approve/reject leave applications
  • Admins: Full system management
  1. Leave Balance Tracking
  • Visual representation of available leave days
  • Automatic calculation of leave duration
  1. Responsive Design
  • Mobile-friendly interface
  • Clean, modern UI without Bootstrap
  1. Security
  • Password hashing
  • Session-based authentication
  • Form validation
  1. Malaysian-Specific Features
  • Localized leave types (Annual, Medical, Emergency, etc.)
  • Malaysian public holidays integrated

This implementation provides a complete, standalone leave management system tailored for Malaysian companies, using pure PHP and MySQL with a custom UI that doesn’t rely on jQuery or Bootstrap.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *