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>© <?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
- Malaysian Public Holidays Integration
- Dedicated table to store Malaysian public holidays
- Displayed on the dashboard for reference
- Role-Based Access Control
- Employees: Apply for leave, view balances
- Managers: Approve/reject leave applications
- Admins: Full system management
- Leave Balance Tracking
- Visual representation of available leave days
- Automatic calculation of leave duration
- Responsive Design
- Mobile-friendly interface
- Clean, modern UI without Bootstrap
- Security
- Password hashing
- Session-based authentication
- Form validation
- 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.

Leave a Reply