Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c5e843e68 | ||
|
|
2d81d503b9 | ||
|
|
3c2845cfdf | ||
|
|
b40170f220 | ||
|
|
bab3b1939b | ||
|
|
edea5afdea | ||
|
|
9ab9dfefba | ||
|
|
403c8e4f27 | ||
|
|
6087fe4bc6 | ||
|
|
56e210d2e4 | ||
|
|
8253af4f12 | ||
|
|
54ae5b0a95 | ||
|
|
975b53167c | ||
|
|
8c09a7ce45 | ||
|
|
da20992627 | ||
|
|
328977b464 | ||
|
|
461ef5375c | ||
|
|
1f8ea6c159 | ||
|
|
a254737287 | ||
|
|
77833a9ac9 | ||
|
|
047da4e51b | ||
|
|
86279aac60 | ||
|
|
2b2598d799 | ||
|
|
8799e4e388 | ||
|
|
5d99cefde8 | ||
|
|
729ef9f5af | ||
|
|
2fe54d548e | ||
|
|
be54ee0bce | ||
|
|
14a23a9f38 | ||
|
|
a6df53bf8a | ||
|
|
e9c9a7f587 | ||
|
|
faa47959df | ||
|
|
8c6afdf3d9 | ||
|
|
83fdeb8365 | ||
|
|
5bd1050cc5 | ||
|
|
9a07273b40 | ||
|
|
ef2d9513a5 | ||
|
|
debb478acf | ||
|
|
1295752cb1 | ||
|
|
c693b9a3c9 | ||
|
|
4db23e4317 | ||
|
|
851b3613d1 | ||
|
|
4687d48132 | ||
|
|
386664137b | ||
|
|
8d3f50641f |
208
admin_classes.php
Normal file
208
admin_classes.php
Normal file
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/auth_check.php';
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
$message = '';
|
||||
$error = '';
|
||||
$editing_class = null;
|
||||
$school_id = $_SESSION['school_id'];
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Handle Delete request
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_id'])) {
|
||||
try {
|
||||
$delete_id = $_POST['delete_id'];
|
||||
$stmt = $pdo->prepare("DELETE FROM classes WHERE id = ? AND school_id = ?");
|
||||
$stmt->execute([$delete_id, $school_id]);
|
||||
$message = "Class deleted successfully.";
|
||||
} catch (PDOException $e) {
|
||||
if ($e->getCode() == '23000') { // Integrity constraint violation
|
||||
$error = "Cannot delete this class because it has subjects, workloads, or schedules associated with it. Please remove those associations before deleting.";
|
||||
} else {
|
||||
$error = "Error deleting class: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle POST request to add or update a class
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['class_name'])) {
|
||||
$className = trim($_POST['class_name']);
|
||||
$class_id = $_POST['class_id'] ?? null;
|
||||
|
||||
if (empty($className)) {
|
||||
$error = 'Class name cannot be empty.';
|
||||
} else {
|
||||
// Check for duplicates before inserting/updating
|
||||
$stmt = $pdo->prepare("SELECT id FROM classes WHERE name = ? AND school_id = ? AND id != ?");
|
||||
$stmt->execute([$className, $school_id, $class_id ?? 0]);
|
||||
if ($stmt->fetch()) {
|
||||
$error = "Error: Class '" . htmlspecialchars($className) . "' already exists.";
|
||||
} else {
|
||||
try {
|
||||
if ($class_id) {
|
||||
// Update existing class
|
||||
$stmt = $pdo->prepare("UPDATE classes SET name = ? WHERE id = ? AND school_id = ?");
|
||||
$stmt->execute([$className, $class_id, $school_id]);
|
||||
$message = "Class updated successfully!";
|
||||
} else {
|
||||
// Insert new class
|
||||
$stmt = $pdo->prepare("INSERT INTO classes (name, school_id) VALUES (?, ?)");
|
||||
$stmt->execute([$className, $school_id]);
|
||||
$message = "Class created successfully!";
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Edit request
|
||||
if (isset($_GET['edit_id'])) {
|
||||
try {
|
||||
$edit_id = $_GET['edit_id'];
|
||||
$stmt = $pdo->prepare("SELECT * FROM classes WHERE id = ? AND school_id = ?");
|
||||
$stmt->execute([$edit_id, $school_id]);
|
||||
$editing_class = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$error = "Error fetching class: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all classes to display
|
||||
$classes = [];
|
||||
try {
|
||||
$classes_stmt = $pdo->prepare("SELECT * FROM classes WHERE school_id = ? ORDER BY name ASC");
|
||||
$classes_stmt->execute([$school_id]);
|
||||
$classes = $classes_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin: Manage Classes - Haki Schedule</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white sticky-top shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="/">Haki Schedule</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
|
||||
<?php if (isset($_SESSION['user_id'])): ?>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle active" href="#" id="manageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Manage
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="manageDropdown">
|
||||
<li><a class="dropdown-item active" href="/admin_classes.php">Classes</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_subjects.php">Subjects</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_elective_groups.php">Elective Groups</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_teachers.php">Teachers</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_workloads.php">Workloads</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_timeslots.php">Timeslots</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item"><a class="nav-link" href="/timetable.php">Class Timetable</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/teacher_timetable.php">Teacher Timetable</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/logout.php">Logout</a></li>
|
||||
<?php else: ?>
|
||||
<li class="nav-item"><a class="nav-link" href="/demo.php">Demo</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/login.php">Login</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/register.php">Register</a></li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<h1 class="h2 fw-bold mb-4">Manage Classes</h1>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="alert alert-success"><?php echo $message; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger"><?php echo $error; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Create/Edit Class Form -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><?php echo $editing_class ? 'Edit Class' : 'Create a New Class'; ?></h5>
|
||||
<form action="admin_classes.php" method="POST">
|
||||
<input type="hidden" name="class_id" value="<?php echo $editing_class['id'] ?? ''; ?>">
|
||||
<div class="mb-3">
|
||||
<label for="class_name" class="form-label">Class Name</label>
|
||||
<input type="text" class="form-control" id="class_name" name="class_name" value="<?php echo htmlspecialchars($editing_class['name'] ?? ''); ?>" placeholder="e.g., Form 1 North" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><?php echo $editing_class ? 'Update Class' : 'Create Class'; ?></button>
|
||||
<?php if ($editing_class): ?>
|
||||
<a href="admin_classes.php" class="btn btn-secondary">Cancel Edit</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Existing Classes List -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Existing Classes</h5>
|
||||
<?php if (empty($classes)): ?>
|
||||
<p class="text-muted">No classes have been created yet.</p>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($classes as $class): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($class['name']); ?></td>
|
||||
<td>
|
||||
<a href="?edit_id=<?php echo $class['id']; ?>" class="btn btn-sm btn-outline-primary">Edit</a>
|
||||
<form action="admin_classes.php" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this class?');">
|
||||
<input type="hidden" name="delete_id" value="<?php echo $class['id']; ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white py-4 mt-5">
|
||||
<div class="container text-center">
|
||||
<p>© <?php echo date("Y"); ?> Haki Schedule. All Rights Reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
115
admin_data_management.php
Normal file
115
admin_data_management.php
Normal file
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'includes/auth_check.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
if ($_SESSION['role'] !== 'admin') {
|
||||
header("Location: /");
|
||||
exit;
|
||||
}
|
||||
|
||||
$school_id = $_SESSION['school_id'];
|
||||
$message = '';
|
||||
$error = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($school_id)) {
|
||||
$pdo = db();
|
||||
try {
|
||||
$pdo->exec("SET FOREIGN_KEY_CHECKS=0;");
|
||||
$pdo->beginTransaction();
|
||||
|
||||
if (isset($_POST['clear_timetable'])) {
|
||||
// Clearing timetable data only
|
||||
$pdo->prepare("DELETE FROM schedule_teachers WHERE schedule_id IN (SELECT id FROM schedules WHERE school_id = ?)")->execute([$school_id]);
|
||||
$pdo->prepare("DELETE FROM schedules WHERE school_id = ?")->execute([$school_id]);
|
||||
$message = "Timetable data has been successfully cleared.";
|
||||
|
||||
} elseif (isset($_POST['clear_all_data'])) {
|
||||
// Deleting all school data.
|
||||
|
||||
// 1. Junction tables
|
||||
$pdo->prepare("DELETE FROM schedule_teachers WHERE schedule_id IN (SELECT id FROM schedules WHERE school_id = ?)")->execute([$school_id]);
|
||||
$pdo->prepare("DELETE FROM elective_group_subjects WHERE elective_group_id IN (SELECT id FROM elective_groups WHERE school_id = ?)")->execute([$school_id]);
|
||||
|
||||
// 2. Core data tables
|
||||
$pdo->prepare("DELETE FROM schedules WHERE school_id = ?")->execute([$school_id]);
|
||||
$pdo->prepare("DELETE FROM workloads WHERE school_id = ?")->execute([$school_id]);
|
||||
$pdo->prepare("DELETE FROM subjects WHERE school_id = ?")->execute([$school_id]);
|
||||
$pdo->prepare("DELETE FROM teachers WHERE school_id = ?")->execute([$school_id]);
|
||||
$pdo->prepare("DELETE FROM classes WHERE school_id = ?")->execute([$school_id]);
|
||||
$pdo->prepare("DELETE FROM elective_groups WHERE school_id = ?")->execute([$school_id]);
|
||||
|
||||
// 3. Users (keep the logged-in admin)
|
||||
$current_user_id = $_SESSION['user_id'] ?? 0;
|
||||
$stmt = $pdo->prepare("DELETE FROM users WHERE school_id = ? AND id != ?");
|
||||
$stmt->execute([$school_id, $current_user_id]);
|
||||
|
||||
$message = "All school data has been successfully cleared.";
|
||||
}
|
||||
|
||||
$pdo->commit();
|
||||
$pdo->exec("SET FOREIGN_KEY_CHECKS=1;");
|
||||
|
||||
} catch (Exception $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
// Always re-enable foreign key checks, even on error
|
||||
$pdo->exec("SET FOREIGN_KEY_CHECKS=1;");
|
||||
$error = "An error occurred: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
$page_title = "Data Management";
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= htmlspecialchars($page_title) ?> - Haki Schedule</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<?php include 'includes/navbar.php'; ?>
|
||||
<div class="container mt-4">
|
||||
<h1 class="mb-4"><?= htmlspecialchars($page_title) ?></h1>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="alert alert-success"><?= htmlspecialchars($message) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card border-warning mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">Clear Timetable Data</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">This action will delete all generated timetable schedules. This is useful if you want to regenerate the timetable after making changes to classes, subjects, or workloads. This will allow you to delete classes and subjects if they are currently locked by the timetable.</p>
|
||||
<p class="text-danger fw-bold">This action cannot be undone.</p>
|
||||
<form method="POST" onsubmit="return confirm('Are you sure you want to clear all timetable data?');">
|
||||
<button type="submit" name="clear_timetable" class="btn btn-warning">Clear Timetable Data</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-danger mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title mb-0">Clear All School Data</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">This action will permanently delete ALL data associated with your school, including classes, subjects, teachers, workloads, and the timetable. This is for starting completely fresh.</p>
|
||||
<p class="text-danger fw-bold">This is a destructive action and cannot be undone.</p>
|
||||
<form method="POST" onsubmit="return confirm('Are you absolutely sure you want to delete all of your school's data? This cannot be reversed.');">
|
||||
<button type="submit" name="clear_all_data" class="btn btn-danger">Delete All School Data</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
208
admin_elective_groups.php
Normal file
208
admin_elective_groups.php
Normal file
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/auth_check.php';
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
$message = '';
|
||||
$error = '';
|
||||
$editing_group = null;
|
||||
$school_id = $_SESSION['school_id'];
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Handle Delete request
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_id'])) {
|
||||
try {
|
||||
$delete_id = $_POST['delete_id'];
|
||||
$stmt = $pdo->prepare("DELETE FROM elective_groups WHERE id = ? AND school_id = ?");
|
||||
$stmt->execute([$delete_id, $school_id]);
|
||||
$message = "Elective group deleted successfully.";
|
||||
} catch (PDOException $e) {
|
||||
if ($e->getCode() == '23000') { // Integrity constraint violation
|
||||
$error = "Cannot delete this elective group because it has subjects associated with it. Please remove those associations before deleting.";
|
||||
} else {
|
||||
$error = "Error deleting group: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle POST request to add or update a group
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['group_name'])) {
|
||||
$groupName = trim($_POST['group_name']);
|
||||
$group_id = $_POST['group_id'] ?? null;
|
||||
|
||||
if (empty($groupName)) {
|
||||
$error = 'Group name cannot be empty.';
|
||||
} else {
|
||||
// Check for duplicates before inserting
|
||||
$stmt = $pdo->prepare("SELECT id FROM elective_groups WHERE name = ? AND school_id = ? AND id != ?");
|
||||
$stmt->execute([$groupName, $school_id, $group_id ?? 0]);
|
||||
if ($stmt->fetch()) {
|
||||
$error = "Error: Elective group '" . htmlspecialchars($groupName) . "' already exists.";
|
||||
} else {
|
||||
try {
|
||||
if ($group_id) {
|
||||
// Update existing group
|
||||
$stmt = $pdo->prepare("UPDATE elective_groups SET name = ? WHERE id = ? AND school_id = ?");
|
||||
$stmt->execute([$groupName, $group_id, $school_id]);
|
||||
$message = "Elective group updated successfully!";
|
||||
} else {
|
||||
// Insert new group
|
||||
$stmt = $pdo->prepare("INSERT INTO elective_groups (name, school_id) VALUES (?, ?)");
|
||||
$stmt->execute([$groupName, $school_id]);
|
||||
$message = "Elective group created successfully!";
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Edit request
|
||||
if (isset($_GET['edit_id'])) {
|
||||
try {
|
||||
$edit_id = $_GET['edit_id'];
|
||||
$stmt = $pdo->prepare("SELECT * FROM elective_groups WHERE id = ? AND school_id = ?");
|
||||
$stmt->execute([$edit_id, $school_id]);
|
||||
$editing_group = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$error = "Error fetching group: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all groups to display
|
||||
$groups = [];
|
||||
try {
|
||||
$groups_stmt = $pdo->prepare("SELECT * FROM elective_groups WHERE school_id = ? ORDER BY name ASC");
|
||||
$groups_stmt->execute([$school_id]);
|
||||
$groups = $groups_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin: Manage Elective Groups - Haki Schedule</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white sticky-top shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="/">Haki Schedule</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
|
||||
<?php if (isset($_SESSION['user_id'])): ?>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle active" href="#" id="manageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Manage
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="manageDropdown">
|
||||
<li><a class="dropdown-item" href="/admin_classes.php">Classes</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_subjects.php">Subjects</a></li>
|
||||
<li><a class="dropdown-item active" href="/admin_elective_groups.php">Elective Groups</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_teachers.php">Teachers</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_workloads.php">Workloads</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_timeslots.php">Timeslots</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item"><a class="nav-link" href="/timetable.php">Class Timetable</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/teacher_timetable.php">Teacher Timetable</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/logout.php">Logout</a></li>
|
||||
<?php else: ?>
|
||||
<li class="nav-item"><a class="nav-link" href="/demo.php">Demo</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/login.php">Login</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/register.php">Register</a></li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<h1 class="h2 fw-bold mb-4">Manage Elective Groups</h1>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="alert alert-success"><?php echo $message; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger"><?php echo $error; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Create/Edit Group Form -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><?php echo $editing_group ? 'Edit Group' : 'Create a New Group'; ?></h5>
|
||||
<form action="admin_elective_groups.php" method="POST">
|
||||
<input type="hidden" name="group_id" value="<?php echo $editing_group['id'] ?? ''; ?>">
|
||||
<div class="mb-3">
|
||||
<label for="group_name" class="form-label">Group Name</label>
|
||||
<input type="text" class="form-control" id="group_name" name="group_name" value="<?php echo htmlspecialchars($editing_group['name'] ?? ''); ?>" placeholder="e.g., Humanities" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><?php echo $editing_group ? 'Update Group' : 'Create Group'; ?></button>
|
||||
<?php if ($editing_group): ?>
|
||||
<a href="admin_elective_groups.php" class="btn btn-secondary">Cancel Edit</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Existing Groups List -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Existing Elective Groups</h5>
|
||||
<?php if (empty($groups)): ?>
|
||||
<p class="text-muted">No elective groups have been created yet.</p>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($group['name']); ?></td>
|
||||
<td>
|
||||
<a href="?edit_id=<?php echo $group['id']; ?>" class="btn btn-sm btn-outline-primary">Edit</a>
|
||||
<form action="admin_elective_groups.php" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this group?');">
|
||||
<input type="hidden" name="delete_id" value="<?php echo $group['id']; ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white py-4 mt-5">
|
||||
<div class="container text-center">
|
||||
<p>© <?php echo date("Y"); ?> Haki Schedule. All Rights Reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
104
admin_school_settings.php
Normal file
104
admin_school_settings.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/auth_check.php';
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
if (!isset($_SESSION['school_id']) || $_SESSION['role'] !== 'admin') {
|
||||
// Redirect non-admins or users without a school_id
|
||||
header('Location: /dashboard.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$message = '';
|
||||
$error = '';
|
||||
$school_id = $_SESSION['school_id'];
|
||||
$pdo = db();
|
||||
|
||||
// Handle POST request
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['update_settings'])) {
|
||||
$working_days = isset($_POST['working_days']) ? implode(',', $_POST['working_days']) : '';
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("UPDATE schools SET working_days = ? WHERE id = ?");
|
||||
if ($stmt->execute([$working_days, $school_id])) {
|
||||
$message = 'Settings updated successfully!';
|
||||
} else {
|
||||
$error = 'Failed to update settings.';
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch school settings
|
||||
$school_settings = null;
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT * FROM schools WHERE id = ?");
|
||||
$stmt->execute([$school_id]);
|
||||
$school_settings = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
$current_working_days = $school_settings ? explode(',', $school_settings['working_days']) : [];
|
||||
$all_days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin: School Settings - Haki Schedule</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
<?php include 'includes/navbar.php'; ?>
|
||||
|
||||
<main class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<h1 class="h2 fw-bold mb-4">School Settings</h1>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="alert alert-success"><?php echo $message; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger"><?php echo $error; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Working Days</h5>
|
||||
<p class="card-text">Select the days your school operates. This will affect timetable generation.</p>
|
||||
<form action="admin_school_settings.php" method="POST">
|
||||
<div class="mb-3">
|
||||
<?php foreach ($all_days as $day): ?>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="working_days[]" value="<?php echo $day; ?>" id="day_<?php echo $day; ?>" <?php echo in_array($day, $current_working_days) ? 'checked' : ''; ?>>
|
||||
<label class="form-check-label" for="day_<?php echo $day; ?>">
|
||||
<?php echo $day; ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button type="submit" name="update_settings" class="btn btn-primary">Save Settings</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white py-4 mt-5">
|
||||
<div class="container text-center">
|
||||
<p>© <?php echo date("Y"); ?> Haki Schedule. All Rights Reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
254
admin_subjects.php
Normal file
254
admin_subjects.php
Normal file
@ -0,0 +1,254 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/auth_check.php';
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
$message = '';
|
||||
$error = '';
|
||||
$editing_subject = null;
|
||||
$school_id = $_SESSION['school_id'];
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Handle Delete request
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_id'])) {
|
||||
try {
|
||||
$delete_id = $_POST['delete_id'];
|
||||
$stmt = $pdo->prepare("DELETE FROM subjects WHERE id = ? AND school_id = ?");
|
||||
$stmt->execute([$delete_id, $school_id]);
|
||||
$message = "Subject deleted successfully.";
|
||||
} catch (PDOException $e) {
|
||||
if ($e->getCode() == '23000') { // Integrity constraint violation
|
||||
$error = "Cannot delete this subject because it is currently used in workloads or schedules. Please remove those associations before deleting.";
|
||||
} else {
|
||||
$error = "Error deleting subject: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle POST request to add or update a subject
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['subject_name'])) {
|
||||
$subjectName = trim($_POST['subject_name']);
|
||||
$lessons_per_week = filter_input(INPUT_POST, 'lessons_per_week', FILTER_VALIDATE_INT, ['options' => ['default' => 1, 'min_range' => 1]]);
|
||||
$has_double_lesson = isset($_POST['has_double_lesson']) ? 1 : 0;
|
||||
$is_elective = isset($_POST['is_elective']) ? 1 : 0;
|
||||
$elective_group_id = !empty($_POST['elective_group_id']) ? $_POST['elective_group_id'] : null;
|
||||
$subject_id = $_POST['subject_id'] ?? null;
|
||||
|
||||
if (empty($subjectName)) {
|
||||
$error = 'Subject name cannot be empty.';
|
||||
} else {
|
||||
// Check for duplicates before inserting/updating
|
||||
$stmt = $pdo->prepare("SELECT id FROM subjects WHERE name = ? AND school_id = ? AND id != ?");
|
||||
$stmt->execute([$subjectName, $school_id, $subject_id ?? 0]);
|
||||
if ($stmt->fetch()) {
|
||||
$error = "Error: Subject '" . htmlspecialchars($subjectName) . "' already exists.";
|
||||
} else {
|
||||
try {
|
||||
if ($subject_id) {
|
||||
// Update existing subject
|
||||
$stmt = $pdo->prepare("UPDATE subjects SET name = ?, lessons_per_week = ?, has_double_lesson = ?, is_elective = ?, elective_group_id = ? WHERE id = ? AND school_id = ?");
|
||||
$stmt->execute([$subjectName, $lessons_per_week, $has_double_lesson, $is_elective, $elective_group_id, $subject_id, $school_id]);
|
||||
$message = "Subject updated successfully!";
|
||||
} else {
|
||||
// Insert new subject
|
||||
$stmt = $pdo->prepare("INSERT INTO subjects (name, lessons_per_week, has_double_lesson, is_elective, elective_group_id, school_id) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$subjectName, $lessons_per_week, $has_double_lesson, $is_elective, $elective_group_id, $school_id]);
|
||||
$message = "Subject created successfully!";
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Edit request
|
||||
if (isset($_GET['edit_id'])) {
|
||||
try {
|
||||
$edit_id = $_GET['edit_id'];
|
||||
$stmt = $pdo->prepare("SELECT * FROM subjects WHERE id = ? AND school_id = ?");
|
||||
$stmt->execute([$edit_id, $school_id]);
|
||||
$editing_subject = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$error = "Error fetching subject: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all subjects to display
|
||||
$subjects = [];
|
||||
try {
|
||||
$subjects_stmt = $pdo->prepare("SELECT s.*, eg.name as elective_group_name FROM subjects s LEFT JOIN elective_groups eg ON s.elective_group_id = eg.id WHERE s.school_id = ? ORDER BY s.name ASC");
|
||||
$subjects_stmt->execute([$school_id]);
|
||||
$subjects = $subjects_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
// Fetch all elective groups for the dropdown
|
||||
$elective_groups = [];
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT * FROM elective_groups WHERE school_id = ? ORDER BY name ASC");
|
||||
$stmt->execute([$school_id]);
|
||||
$elective_groups = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error while fetching elective groups: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin: Manage Subjects - Haki Schedule</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white sticky-top shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="/">Haki Schedule</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
|
||||
<?php if (isset($_SESSION['user_id'])): ?>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle active" href="#" id="manageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Manage
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="manageDropdown">
|
||||
<li><a class="dropdown-item" href="/admin_classes.php">Classes</a></li>
|
||||
<li><a class="dropdown-item active" href="/admin_subjects.php">Subjects</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_teachers.php">Teachers</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_workloads.php">Workloads</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_timeslots.php">Timeslots</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item"><a class="nav-link" href="/timetable.php">Class Timetable</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/teacher_timetable.php">Teacher Timetable</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/logout.php">Logout</a></li>
|
||||
<?php else: ?>
|
||||
<li class="nav-item"><a class="nav-link" href="/demo.php">Demo</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/login.php">Login</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/register.php">Register</a></li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
<h1 class="h2 fw-bold mb-4">Manage Subjects</h1>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="alert alert-success"><?php echo $message; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger"><?php echo $error; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Create/Edit Subject Form -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><?php echo $editing_subject ? 'Edit Subject' : 'Create a New Subject'; ?></h5>
|
||||
<form action="admin_subjects.php" method="POST">
|
||||
<input type="hidden" name="subject_id" value="<?php echo $editing_subject['id'] ?? ''; ?>">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="subject_name" class="form-label">Subject Name</label>
|
||||
<input type="text" class="form-control" id="subject_name" name="subject_name" value="<?php echo htmlspecialchars($editing_subject['name'] ?? ''); ?>" placeholder="e.g., Mathematics" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="lessons_per_week" class="form-label">Lessons per Week</label>
|
||||
<input type="number" class="form-control" id="lessons_per_week" name="lessons_per_week" value="<?php echo htmlspecialchars($editing_subject['lessons_per_week'] ?? '1'); ?>" min="1" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="has_double_lesson" name="has_double_lesson" value="1" <?php echo !empty($editing_subject['has_double_lesson']) ? 'checked' : ''; ?>>
|
||||
<label class="form-check-label" for="has_double_lesson">Has one double lesson per week</label>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="is_elective" name="is_elective" value="1" <?php echo !empty($editing_subject['is_elective']) ? 'checked' : ''; ?>>
|
||||
<label class="form-check-label" for="is_elective">Is Elective</label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="elective_group_id" class="form-label">Elective Group (Optional)</label>
|
||||
<select class="form-select" id="elective_group_id" name="elective_group_id">
|
||||
<option value="">None</option>
|
||||
<?php foreach ($elective_groups as $group): ?>
|
||||
<option value="<?php echo $group['id']; ?>" <?php echo (isset($editing_subject['elective_group_id']) && $editing_subject['elective_group_id'] == $group['id']) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($group['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><?php echo $editing_subject ? 'Update Subject' : 'Create Subject'; ?></button>
|
||||
<?php if ($editing_subject): ?>
|
||||
<a href="admin_subjects.php" class="btn btn-secondary">Cancel Edit</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Existing Subjects List -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Existing Subjects</h5>
|
||||
<?php if (empty($subjects)): ?>
|
||||
<p class="text-muted">No subjects have been created yet.</p>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Lessons / Week</th>
|
||||
<th>Double Lesson</th>
|
||||
<th>Is Elective</th>
|
||||
<th>Elective Group</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($subjects as $subject): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($subject['name']); ?></td>
|
||||
<td><?php echo htmlspecialchars($subject['lessons_per_week']); ?></td>
|
||||
<td><?php echo $subject['has_double_lesson'] ? 'Yes' : 'No'; ?></td>
|
||||
<td><?php echo $subject['is_elective'] ? 'Yes' : 'No'; ?></td>
|
||||
<td><?php echo htmlspecialchars($subject['elective_group_name'] ?? 'N/A'); ?></td>
|
||||
<td>
|
||||
<a href="?edit_id=<?php echo $subject['id']; ?>" class="btn btn-sm btn-outline-primary">Edit</a>
|
||||
<form action="admin_subjects.php" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this subject?');">
|
||||
<input type="hidden" name="delete_id" value="<?php echo $subject['id']; ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white py-4 mt-5">
|
||||
<div class="container text-center">
|
||||
<p>© <?php echo date("Y"); ?> Haki Schedule. All Rights Reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
312
admin_teachers.php
Normal file
312
admin_teachers.php
Normal file
@ -0,0 +1,312 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/auth_check.php';
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
$message = '';
|
||||
$error = '';
|
||||
$editing_teacher = null;
|
||||
$school_id = $_SESSION['school_id'];
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Handle permission toggle
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['toggle_permission_id'])) {
|
||||
try {
|
||||
$teacher_id = $_POST['toggle_permission_id'];
|
||||
$new_status = $_POST['new_status'];
|
||||
$stmt = $pdo->prepare("UPDATE teachers SET can_edit_workload = ? WHERE id = ? AND school_id = ?");
|
||||
$stmt->execute([$new_status, $teacher_id, $school_id]);
|
||||
$message = "Permission updated successfully.";
|
||||
} catch (PDOException $e) {
|
||||
$error = "Error updating permission: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Handle Delete request
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_id'])) {
|
||||
try {
|
||||
$delete_id = $_POST['delete_id'];
|
||||
// Also delete the associated user account
|
||||
$stmt = $pdo->prepare("SELECT user_id FROM teachers WHERE id = ? AND school_id = ?");
|
||||
$stmt->execute([$delete_id, $school_id]);
|
||||
$user_id = $stmt->fetchColumn();
|
||||
|
||||
$pdo->beginTransaction();
|
||||
$stmt = $pdo->prepare("DELETE FROM teachers WHERE id = ?");
|
||||
$stmt->execute([$delete_id]);
|
||||
if ($user_id) {
|
||||
$stmt = $pdo->prepare("DELETE FROM users WHERE id = ?");
|
||||
$stmt->execute([$user_id]);
|
||||
}
|
||||
$pdo->commit();
|
||||
$message = "Teacher deleted successfully.";
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
if ($e->getCode() == '23000') { // Integrity constraint violation
|
||||
$error = "Cannot delete this teacher because they are assigned to workloads or schedules. Please remove those associations before deleting.";
|
||||
} else {
|
||||
$error = "Error deleting teacher: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle POST request to add or update a teacher
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['teacher_name'])) {
|
||||
$teacherName = trim($_POST['teacher_name']);
|
||||
$teacherEmail = trim($_POST['teacher_email']);
|
||||
$password = $_POST['password'] ?? null;
|
||||
$teacher_id = $_POST['teacher_id'] ?? null;
|
||||
|
||||
if (empty($teacherName) || empty($teacherEmail)) {
|
||||
$error = 'Teacher name and email are required.';
|
||||
} elseif (!filter_var($teacherEmail, FILTER_VALIDATE_EMAIL)) {
|
||||
$error = 'Invalid email format.';
|
||||
} elseif (!$teacher_id && empty($password)) {
|
||||
$error = 'Password is required for new teachers.';
|
||||
} else {
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Check for duplicate teacher name
|
||||
$stmt = $pdo->prepare("SELECT id FROM teachers WHERE name = ? AND school_id = ? AND id != ?");
|
||||
$stmt->execute([$teacherName, $school_id, $teacher_id ?? 0]);
|
||||
if ($stmt->fetch()) {
|
||||
throw new Exception("A teacher with this name already exists.");
|
||||
}
|
||||
|
||||
if ($teacher_id) {
|
||||
// Update existing teacher
|
||||
$stmt = $pdo->prepare("UPDATE teachers SET name = ? WHERE id = ? AND school_id = ?");
|
||||
$stmt->execute([$teacherName, $teacher_id, $school_id]);
|
||||
|
||||
// Also update user email
|
||||
$stmt = $pdo->prepare("SELECT user_id FROM teachers WHERE id = ?");
|
||||
$stmt->execute([$teacher_id]);
|
||||
$user_id = $stmt->fetchColumn();
|
||||
|
||||
if ($user_id) {
|
||||
$sql = "UPDATE users SET email = ?, username = ?";
|
||||
$params = [$teacherEmail, $teacherEmail];
|
||||
if (!empty($password)) {
|
||||
$sql .= ", password = ?";
|
||||
$params[] = password_hash($password, PASSWORD_DEFAULT);
|
||||
}
|
||||
$sql .= " WHERE id = ?";
|
||||
$params[] = $user_id;
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
}
|
||||
|
||||
$message = "Teacher updated successfully!";
|
||||
} else {
|
||||
// Check for duplicate email in users table
|
||||
$stmt = $pdo->prepare("SELECT id FROM users WHERE email = ?");
|
||||
$stmt->execute([$teacherEmail]);
|
||||
if ($stmt->fetch()) {
|
||||
throw new Exception("A user with this email already exists.");
|
||||
}
|
||||
|
||||
// Insert new user
|
||||
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||
$stmt = $pdo->prepare("INSERT INTO users (school_id, username, email, password, role) VALUES (?, ?, ?, ?, 'teacher')");
|
||||
$stmt->execute([$school_id, $teacherEmail, $teacherEmail, $hashed_password]);
|
||||
$user_id = $pdo->lastInsertId();
|
||||
|
||||
// Insert new teacher
|
||||
$stmt = $pdo->prepare("INSERT INTO teachers (name, school_id, user_id) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$teacherName, $school_id, $user_id]);
|
||||
$message = "Teacher created successfully!";
|
||||
}
|
||||
$pdo->commit();
|
||||
} catch (Exception $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
$error = $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Edit request
|
||||
if (isset($_GET['edit_id'])) {
|
||||
try {
|
||||
$edit_id = $_GET['edit_id'];
|
||||
$stmt = $pdo->prepare("SELECT t.*, u.email FROM teachers t LEFT JOIN users u ON t.user_id = u.id WHERE t.id = ? AND t.school_id = ?");
|
||||
$stmt->execute([$edit_id, $school_id]);
|
||||
$editing_teacher = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$error = "Error fetching teacher: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all teachers to display
|
||||
$teachers = [];
|
||||
try {
|
||||
$teachers_stmt = $pdo->prepare("SELECT t.*, u.email FROM teachers t LEFT JOIN users u ON t.user_id = u.id WHERE t.school_id = ? ORDER BY t.name ASC");
|
||||
$teachers_stmt->execute([$school_id]);
|
||||
$teachers = $teachers_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin: Manage Teachers - Haki Schedule</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white sticky-top shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="/">Haki Schedule</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
|
||||
<?php if (isset($_SESSION['user_id'])): ?>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle active" href="#" id="manageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Manage
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="manageDropdown">
|
||||
<li><a class="dropdown-item" href="/admin_classes.php">Classes</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_subjects.php">Subjects</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_elective_groups.php">Elective Groups</a></li>
|
||||
<li><a class="dropdown-item active" href="/admin_teachers.php">Teachers</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_workloads.php">Workloads</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_timeslots.php">Timeslots</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item"><a class="nav-link" href="/timetable.php">Class Timetable</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/teacher_timetable.php">Teacher Timetable</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/logout.php">Logout</a></li>
|
||||
<?php else: ?>
|
||||
<li class="nav-item"><a class="nav-link" href="/demo.php">Demo</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/login.php">Login</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/register.php">Register</a></li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
<h1 class="h2 fw-bold mb-4">Manage Teachers</h1>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="alert alert-success"><?php echo $message; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger"><?php echo $error; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Create/Edit Teacher Form -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><?php echo $editing_teacher ? 'Edit Teacher' : 'Create a New Teacher'; ?></h5>
|
||||
<form action="admin_teachers.php" method="POST">
|
||||
<input type="hidden" name="teacher_id" value="<?php echo $editing_teacher['id'] ?? ''; ?>">
|
||||
<div class="mb-3">
|
||||
<label for="teacher_name" class="form-label">Teacher Name</label>
|
||||
<input type="text" class="form-control" id="teacher_name" name="teacher_name" value="<?php echo htmlspecialchars($editing_teacher['name'] ?? ''); ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="teacher_email" class="form-label">Teacher Email</label>
|
||||
<input type="email" class="form-control" id="teacher_email" name="teacher_email" value="<?php echo htmlspecialchars($editing_teacher['email'] ?? ''); ?>" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-.form-control" id="password" name="password" <?php echo $editing_teacher ? '' : 'required'; ?>>
|
||||
<?php if ($editing_teacher): ?>
|
||||
<small class="form-text text-muted">Leave blank to keep the current password.</small>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><?php echo $editing_teacher ? 'Update Teacher' : 'Create Teacher'; ?></button>
|
||||
<?php if ($editing_teacher): ?>
|
||||
<a href="admin_teachers.php" class="btn btn-secondary">Cancel Edit</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Existing Teachers List -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Existing Teachers</h5>
|
||||
<?php if (empty($teachers)): ?>
|
||||
<p class="text-muted">No teachers have been created yet.</p>
|
||||
<?php else: ?>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Workload Editing</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($teachers as $teacher): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($teacher['name']); ?></td>
|
||||
<td><?php echo htmlspecialchars($teacher['email']); ?></td>
|
||||
<td>
|
||||
<?php if ($teacher['can_edit_workload']): ?>
|
||||
<span class="badge bg-success">Allowed</span>
|
||||
<?php else: ?>
|
||||
<span class="badge bg-secondary">Not Allowed</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<a href="?edit_id=<?php echo $teacher['id']; ?>" class="btn btn-sm btn-outline-primary mb-1">Edit</a>
|
||||
|
||||
<form action="admin_teachers.php" method="POST" class="d-inline">
|
||||
<input type="hidden" name="toggle_permission_id" value="<?php echo $teacher['id']; ?>">
|
||||
<?php if ($teacher['can_edit_workload']): ?>
|
||||
<input type="hidden" name="new_status" value="0">
|
||||
<button type="submit" class="btn btn-sm btn-warning mb-1">Revoke</button>
|
||||
<?php else: ?>
|
||||
<input type="hidden" name="new_status" value="1">
|
||||
<button type="submit" class="btn btn-sm btn-success mb-1">Allow</button>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
|
||||
<form action="admin_teachers.php" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this teacher?');">
|
||||
<input type="hidden" name="delete_id" value="<?php echo $teacher['id']; ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger mb-1">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white py-4 mt-5">
|
||||
<div class="container text-center">
|
||||
<p>© <?php echo date("Y"); ?> Haki Schedule. All Rights Reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
223
admin_timeslots.php
Normal file
223
admin_timeslots.php
Normal file
@ -0,0 +1,223 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/auth_check.php';
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
$message = '';
|
||||
$error = '';
|
||||
$editing_timeslot = null;
|
||||
|
||||
$pdo = db();
|
||||
|
||||
// Handle Edit request
|
||||
if (isset($_GET['edit_id'])) {
|
||||
try {
|
||||
$edit_id = $_GET['edit_id'];
|
||||
$stmt = $pdo->prepare("SELECT * FROM timeslots WHERE id = ?");
|
||||
$stmt->execute([$edit_id]);
|
||||
$editing_timeslot = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$error = "Error fetching timeslot: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle POST request
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['delete_id'])) {
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("DELETE FROM timeslots WHERE id = ?");
|
||||
if ($stmt->execute([$_POST['delete_id']])) {
|
||||
$message = 'Timeslot deleted successfully!';
|
||||
} else {
|
||||
$error = 'Failed to delete timeslot.';
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
if ($e->getCode() == '23000') { // Integrity constraint violation
|
||||
$error = "Cannot delete this timeslot because it is currently used in schedules. Please remove those associations before deleting.";
|
||||
} else {
|
||||
$error = "Error deleting timeslot: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($_POST['add_timeslot'])) {
|
||||
$name = trim($_POST['name']);
|
||||
$start_time = $_POST['start_time'];
|
||||
$end_time = $_POST['end_time'];
|
||||
$is_break = isset($_POST['is_break']) ? 1 : 0;
|
||||
$timeslot_id = $_POST['timeslot_id'] ?? null;
|
||||
|
||||
if (empty($name) || empty($start_time) || empty($end_time)) {
|
||||
$error = 'All fields are required.';
|
||||
} else {
|
||||
try {
|
||||
if ($timeslot_id) {
|
||||
// Update existing timeslot
|
||||
$stmt = $pdo->prepare("UPDATE timeslots SET name = ?, start_time = ?, end_time = ?, is_break = ? WHERE id = ?");
|
||||
$stmt->execute([$name, $start_time, $end_time, $is_break, $timeslot_id]);
|
||||
$message = "Timeslot updated successfully!";
|
||||
} else {
|
||||
// Insert new timeslot
|
||||
$stmt = $pdo->prepare("INSERT INTO timeslots (name, start_time, end_time, is_break) VALUES (?, ?, ?, ?)");
|
||||
$stmt->execute([$name, $start_time, $end_time, $is_break]);
|
||||
$message = "Timeslot created successfully!";
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all timeslots
|
||||
$timeslots = [];
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->query("SELECT * FROM timeslots ORDER BY start_time");
|
||||
$timeslots = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin: Manage Timeslots - Haki Schedule</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white sticky-top shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="/">Haki Schedule</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
|
||||
<?php if (isset($_SESSION['user_id'])): ?>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle active" href="#" id="manageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Manage
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="manageDropdown">
|
||||
<li><a class="dropdown-item" href="/admin_classes.php">Classes</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_subjects.php">Subjects</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_teachers.php">Teachers</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_workloads.php">Workloads</a></li>
|
||||
<li><a class="dropdown-item active" href="/admin_timeslots.php">Timeslots</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item"><a class="nav-link" href="/timetable.php">Class Timetable</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/teacher_timetable.php">Teacher Timetable</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/logout.php">Logout</a></li>
|
||||
<?php else: ?>
|
||||
<li class="nav-item"><a class="nav-link" href="/demo.php">Demo</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/login.php">Login</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/register.php">Register</a></li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<h1 class="h2 fw-bold mb-4">Manage Timeslots</h1>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="alert alert-success"><?php echo $message; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger"><?php echo $error; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Create Timeslot Form -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><?php echo $editing_timeslot ? 'Edit Timeslot' : 'Create a New Timeslot'; ?></h5>
|
||||
<form action="admin_timeslots.php" method="POST">
|
||||
<input type="hidden" name="timeslot_id" value="<?php echo $editing_timeslot['id'] ?? ''; ?>">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="name" class="form-label">Period Name</label>
|
||||
<input type="text" class="form-control" id="name" name="name" placeholder="e.g., Period 1, Lunch" value="<?php echo htmlspecialchars($editing_timeslot['name'] ?? ''); ?>" required>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="start_time" class="form-label">Start Time</label>
|
||||
<input type="time" class="form-control" id="start_time" name="start_time" value="<?php echo htmlspecialchars($editing_timeslot['start_time'] ?? ''); ?>" required>
|
||||
</div>
|
||||
<div class="col-md-3 mb-3">
|
||||
<label for="end_time" class="form-label">End Time</label>
|
||||
<input type="time" class_="form-control" id="end_time" name="end_time" value="<?php echo htmlspecialchars($editing_timeslot['end_time'] ?? ''); ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="is_break" name="is_break" value="1" <?php echo !empty($editing_timeslot['is_break']) ? 'checked' : ''; ?>>
|
||||
<label class="form-check-label" for="is_break">This is a break</label>
|
||||
</div>
|
||||
<button type="submit" name="add_timeslot" class="btn btn-primary"><?php echo $editing_timeslot ? 'Update Timeslot' : 'Create Timeslot'; ?></button>
|
||||
<?php if ($editing_timeslot): ?>
|
||||
<a href="admin_timeslots.php" class="btn btn-secondary">Cancel Edit</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Existing Timeslots List -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Existing Timeslots</h5>
|
||||
<?php if (empty($timeslots)): ?>
|
||||
<p class="text-muted">No timeslots have been created yet.</p>
|
||||
<?php else: ?>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Start Time</th>
|
||||
<th>End Time</th>
|
||||
<th>Type</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($timeslots as $timeslot): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($timeslot['name']); ?></td>
|
||||
<td><?php echo date("g:i A", strtotime($timeslot['start_time'])); ?></td>
|
||||
<td><?php echo date("g:i A", strtotime($timeslot['end_time'])); ?></td>
|
||||
<td><?php echo $timeslot['is_break'] ? 'Break' : 'Lesson'; ?></td>
|
||||
<td>
|
||||
<a href="?edit_id=<?php echo $timeslot['id']; ?>" class="btn btn-sm btn-outline-secondary">Edit</a>
|
||||
<form action="admin_timeslots.php" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this timeslot?');">
|
||||
<input type="hidden" name="delete_id" value="<?php echo $timeslot['id']; ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white py-4 mt-5">
|
||||
<div class="container text-center">
|
||||
<p>© <?php echo date("Y"); ?> Haki Schedule. All Rights Reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
250
admin_workloads.php
Normal file
250
admin_workloads.php
Normal file
@ -0,0 +1,250 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/auth_check.php';
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
|
||||
|
||||
$message = '';
|
||||
$error = '';
|
||||
$pdo = db();
|
||||
$edit_workload = null;
|
||||
$school_id = $_SESSION['school_id'];
|
||||
|
||||
// Handle Delete request
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_id'])) {
|
||||
try {
|
||||
// First, remove teacher from schedule
|
||||
|
||||
$stmt = $pdo->prepare("DELETE FROM workloads WHERE id = ? AND school_id = ?");
|
||||
if ($stmt->execute([$_POST['delete_id'], $school_id])) {
|
||||
$message = 'Workload deleted successfully!';
|
||||
} else {
|
||||
$error = 'Failed to delete workload.';
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
// Handle POST request to add or update a workload
|
||||
elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$workload_id = $_POST['workload_id'] ?? null;
|
||||
$class_id = $_POST['class_id'] ?? null;
|
||||
$subject_id = $_POST['subject_id'] ?? null;
|
||||
$teacher_id = $_POST['teacher_id'] ?? null;
|
||||
$lessons_per_week = $_POST['lessons_per_week'] ?? null;
|
||||
|
||||
if (empty($class_id) || empty($subject_id) || empty($teacher_id) || empty($lessons_per_week)) {
|
||||
$error = 'All fields are required.';
|
||||
} elseif (!filter_var($lessons_per_week, FILTER_VALIDATE_INT, ["options" => ["min_range" => 1]])) {
|
||||
$error = 'Lessons per week must be a positive number.';
|
||||
} else {
|
||||
try {
|
||||
if ($workload_id) { // Update
|
||||
$stmt = $pdo->prepare("UPDATE workloads SET class_id = ?, subject_id = ?, teacher_id = ?, lessons_per_week = ? WHERE id = ? AND school_id = ?");
|
||||
if ($stmt->execute([$class_id, $subject_id, $teacher_id, $lessons_per_week, $workload_id, $school_id])) {
|
||||
$message = 'Workload updated successfully!';
|
||||
} else {
|
||||
$error = 'Failed to update workload.';
|
||||
}
|
||||
} else { // Insert
|
||||
$stmt = $pdo->prepare("INSERT INTO workloads (class_id, subject_id, teacher_id, lessons_per_week, school_id) VALUES (?, ?, ?, ?, ?)");
|
||||
if ($stmt->execute([$class_id, $subject_id, $teacher_id, $lessons_per_week, $school_id])) {
|
||||
$new_workload_id = $pdo->lastInsertId();
|
||||
$message = 'Workload created successfully!';
|
||||
} else {
|
||||
$error = 'Failed to create workload.';
|
||||
}
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
if ($e->errorInfo[1] == 1062) {
|
||||
$error = 'Error: This workload assignment (Class, Subject, Teacher) already exists.';
|
||||
} else {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Edit request (fetch workload to edit)
|
||||
if (isset($_GET['edit_id'])) {
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT * FROM workloads WHERE id = ? AND school_id = ?");
|
||||
$stmt->execute([$_GET['edit_id'], $school_id]);
|
||||
$edit_workload = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch related data for dropdowns
|
||||
try {
|
||||
$classes_stmt = $pdo->prepare("SELECT id, name FROM classes WHERE school_id = ? ORDER BY name");
|
||||
$classes_stmt->execute([$school_id]);
|
||||
$classes = $classes_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$subjects_stmt = $pdo->prepare("SELECT id, name FROM subjects WHERE school_id = ? ORDER BY name");
|
||||
$subjects_stmt->execute([$school_id]);
|
||||
$subjects = $subjects_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$teachers_stmt = $pdo->prepare("SELECT id, name FROM teachers WHERE school_id = ? ORDER BY name");
|
||||
$teachers_stmt->execute([$school_id]);
|
||||
$teachers = $teachers_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error while fetching data: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
// Fetch all workloads to display
|
||||
$workloads = [];
|
||||
try {
|
||||
$workloads_stmt = $pdo->prepare("
|
||||
SELECT w.id, c.name as class_name, s.name as subject_name, t.name as teacher_name, w.lessons_per_week, w.class_id, w.subject_id, w.teacher_id
|
||||
FROM workloads w
|
||||
JOIN classes c ON w.class_id = c.id
|
||||
JOIN subjects s ON w.subject_id = s.id
|
||||
JOIN teachers t ON w.teacher_id = t.id
|
||||
WHERE w.school_id = ?
|
||||
ORDER BY c.name, s.name, t.name
|
||||
");
|
||||
$workloads_stmt->execute([$school_id]);
|
||||
$workloads = $workloads_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error while fetching workloads: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin: Manage Workloads - Haki Schedule</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.10.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white sticky-top shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="/">Haki Schedule</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
|
||||
<?php if (isset($_SESSION['user_id'])): ?>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle active" href="#" id="manageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Manage
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="manageDropdown">
|
||||
<li><a class="dropdown-item" href="/admin_classes.php">Classes</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_subjects.php">Subjects</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_teachers.php">Teachers</a></li>
|
||||
<li><a class="dropdown-item active" href="/admin_workloads.php">Workloads</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_timeslots.php">Timeslots</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item"><a class="nav-link" href="/timetable.php">Class Timetable</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/teacher_timetable.php">Teacher Timetable</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/logout.php">Logout</a></li>
|
||||
<?php else: ?>
|
||||
<li class="nav-item"><a class="nav-link" href="/demo.php">Demo</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/login.php">Login</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/register.php">Register</a></li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
<h1 class="h2 fw-bold mb-4">Manage Workloads</h1>
|
||||
|
||||
<?php if ($message): ?><div class="alert alert-success"><?php echo $message; ?></div><?php endif; ?>
|
||||
<?php if ($error): ?><div class="alert alert-danger"><?php echo $error; ?></div><?php endif; ?>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><?php echo $edit_workload ? 'Edit Workload' : 'Assign a New Workload'; ?></h5>
|
||||
<form action="admin_workloads.php" method="POST">
|
||||
<input type="hidden" name="workload_id" value="<?php echo $edit_workload['id'] ?? ''; ?>">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="class_id" class="form-label">Class</label>
|
||||
<select class="form-select" id="class_id" name="class_id" required>
|
||||
<?php foreach ($classes as $class): ?>
|
||||
<option value="<?php echo $class['id']; ?>" <?php echo (isset($edit_workload) && $edit_workload['class_id'] == $class['id']) ? 'selected' : ''; ?>><?php echo htmlspecialchars($class['name']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="subject_id" class="form-label">Subject</label>
|
||||
<select class="form-select" id="subject_id" name="subject_id" required>
|
||||
<?php foreach ($subjects as $subject): ?>
|
||||
<option value="<?php echo $subject['id']; ?>" <?php echo (isset($edit_workload) && $edit_workload['subject_id'] == $subject['id']) ? 'selected' : ''; ?>><?php echo htmlspecialchars($subject['name']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="teacher_id" class="form-label">Teacher</label>
|
||||
<select class="form-select" id="teacher_id" name="teacher_id" required>
|
||||
<?php foreach ($teachers as $teacher): ?>
|
||||
<option value="<?php echo $teacher['id']; ?>" <?php echo (isset($edit_workload) && $edit_workload['teacher_id'] == $teacher['id']) ? 'selected' : ''; ?>><?php echo htmlspecialchars($teacher['name']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="lessons_per_week" class="form-label">Lessons Per Week</label>
|
||||
<input type="number" class="form-control" id="lessons_per_week" name="lessons_per_week" min="1" value="<?php echo $edit_workload['lessons_per_week'] ?? ''; ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><?php echo $edit_workload ? 'Update Workload' : 'Create Workload'; ?></button>
|
||||
<?php if ($edit_workload): ?>
|
||||
<a href="admin_workloads.php" class="btn btn-secondary">Cancel Edit</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Existing Workloads</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead><tr><th>Class</th><th>Subject</th><th>Teacher</th><th class="text-center">Lessons/Week</th><th>Actions</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($workloads as $workload): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($workload['class_name']); ?></td>
|
||||
<td><?php echo htmlspecialchars($workload['subject_name']); ?></td>
|
||||
<td><?php echo htmlspecialchars($workload['teacher_name']); ?></td>
|
||||
<td class="text-center"><?php echo htmlspecialchars($workload['lessons_per_week']); ?></td>
|
||||
<td>
|
||||
<a href="?edit_id=<?php echo $workload['id']; ?>" class="btn btn-sm btn-outline-primary"><i class="bi bi-pencil-fill"></i></a>
|
||||
<form action="admin_workloads.php" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this workload?');">
|
||||
<input type="hidden" name="delete_id" value="<?php echo $workload['id']; ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger"><i class="bi bi-trash-fill"></i></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white py-4 mt-5"><div class="container text-center"><p>© <?php echo date("Y"); ?> Haki Schedule. All Rights Reserved.</p></div></footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
52
api/move_lesson.php
Normal file
52
api/move_lesson.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
require_once '../db/config.php';
|
||||
require_once '../includes/auth_check.php';
|
||||
|
||||
$response = ['success' => false, 'error' => 'Invalid request'];
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$lesson_id = $data['lesson_id'] ?? null;
|
||||
$to_class_id = $data['to_class_id'] ?? null;
|
||||
$to_day = $data['to_day'] ?? null;
|
||||
$to_timeslot_id = $data['to_timeslot_id'] ?? null;
|
||||
|
||||
if ($lesson_id && $to_class_id && isset($to_day) && $to_timeslot_id) {
|
||||
try {
|
||||
$pdo = db();
|
||||
|
||||
// TODO: Add validation logic here to check for conflicts
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
'UPDATE schedules SET class_id = :class_id, day_of_week = :day, timeslot_id = :timeslot_id WHERE id = :lesson_id'
|
||||
);
|
||||
|
||||
$stmt->execute([
|
||||
':class_id' => $to_class_id,
|
||||
':day' => $to_day,
|
||||
':timeslot_id' => $to_timeslot_id,
|
||||
':lesson_id' => $lesson_id
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
$response = ['success' => true];
|
||||
} else {
|
||||
$response['error'] = 'Failed to move lesson. No rows were updated.';
|
||||
}
|
||||
|
||||
} catch (PDOException $e) {
|
||||
// Check for unique constraint violation
|
||||
if ($e->getCode() == 23000) { // SQLSTATE for integrity constraint violation
|
||||
$response['error'] = 'This time slot is already occupied.';
|
||||
} else {
|
||||
$response['error'] = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$response['error'] = 'Missing required data.';
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode($response);
|
||||
216
assets/css/custom.css
Normal file
216
assets/css/custom.css
Normal file
@ -0,0 +1,216 @@
|
||||
body {
|
||||
background-color: #F9FAFB;
|
||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
color: #4F46E5 !important;
|
||||
}
|
||||
|
||||
.hero-section {
|
||||
background: linear-gradient(45deg, #4F46E5, #6366F1);
|
||||
padding: 6rem 0;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #4F46E5;
|
||||
border-color: #4F46E5;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #4338CA;
|
||||
border-color: #4338CA;
|
||||
}
|
||||
|
||||
.btn-outline-primary {
|
||||
border-color: #4F46E5;
|
||||
color: #4F46E5;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-outline-primary:hover {
|
||||
background-color: #4F46E5;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-light {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #EEF2FF;
|
||||
color: #4F46E5;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.feature-icon i {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
/* Timetable Demo Styles */
|
||||
.timetable-container {
|
||||
padding: 2rem;
|
||||
background-color: #fff;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
.timetable-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.timetable-header h1 {
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.timetable-table {
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.timetable-table th, .timetable-table td {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
padding: 1rem;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.timetable-table thead th {
|
||||
background-color: #EEF2FF;
|
||||
color: #4338CA;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.day-header {
|
||||
background-color: #F9FAFB;
|
||||
font-weight: bold;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.timetable-cell {
|
||||
background-color: #fff;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.timetable-cell:hover {
|
||||
background-color: #F3F4F6;
|
||||
}
|
||||
|
||||
.timetable-cell strong {
|
||||
display: block;
|
||||
color: #4F46E5;
|
||||
}
|
||||
|
||||
.timetable-cell span {
|
||||
font-size: 0.875rem;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
.break-cell {
|
||||
background-color: #F3F4F6;
|
||||
font-style: italic;
|
||||
color: #6B7280;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body * {
|
||||
visibility: hidden;
|
||||
}
|
||||
.printable-area, .printable-area * {
|
||||
visibility: visible;
|
||||
}
|
||||
.printable-area {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.btn {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* New Timetable Styles */
|
||||
.timetable-table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 5px;
|
||||
}
|
||||
|
||||
.time-col {
|
||||
width: 100px;
|
||||
font-weight: bold;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.timetable-cell-container {
|
||||
padding: 2px !important;
|
||||
background-color: #F9FAFB;
|
||||
}
|
||||
|
||||
.timetable-cell {
|
||||
border-radius: 8px;
|
||||
padding: 12px 8px;
|
||||
color: #111827;
|
||||
height: 100%;
|
||||
min-height: 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.timetable-cell:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.subject-name {
|
||||
font-weight: bold;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.teacher-name {
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.break-cell {
|
||||
background-color: #F3F4F6;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.bg-light-blue {
|
||||
background-color: #E0E7FF;
|
||||
}
|
||||
|
||||
.bg-light-green {
|
||||
background-color: #e0f2e9;
|
||||
}
|
||||
|
||||
.bg-light-purple {
|
||||
background-color: #e9e7f5;
|
||||
}
|
||||
79
assets/css/print.css
Normal file
79
assets/css/print.css
Normal file
@ -0,0 +1,79 @@
|
||||
@page {
|
||||
size: landscape;
|
||||
margin: 0.5cm; /* Reduce page margins */
|
||||
}
|
||||
|
||||
/*
|
||||
Simple, direct print styles for timetables, with AGGRESSIVE compression and landscape mode.
|
||||
*/
|
||||
|
||||
/* 1. By default, hide everything when printing. */
|
||||
@media print {
|
||||
body * {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* 2. Make ONLY the timetable containers and their contents visible. */
|
||||
#timetables-container, #timetables-container *,
|
||||
#timetable-container, #timetable-container * {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* 3. Position the container to take up the whole page and scale it down SIGNIFICANTLY. */
|
||||
#timetables-container,
|
||||
#timetable-container {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
transform: scale(0.8); /* Aggressively scale down the entire container */
|
||||
transform-origin: top left;
|
||||
}
|
||||
|
||||
/* 4. Force each timetable to start on a new page. */
|
||||
.timetable-wrapper {
|
||||
page-break-after: always;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* 5. Don't add a page break after the very last one. */
|
||||
.timetable-wrapper:last-child {
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
/* 6. Ensure table fits the page and has basic styling. */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 7pt; /* EXTREMELY reduced font size */
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #666;
|
||||
padding: 1px; /* MINIMAL padding */
|
||||
text-align: center;
|
||||
overflow-wrap: break-word; /* Break long words */
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
text-align: center;
|
||||
font-size: 10pt; /* Reduced header font size */
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 7. Explicitly hide any elements that should never be printed. */
|
||||
.no-print, .navbar, #print-btn, form {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Remove bootstrap card styling */
|
||||
.card, .card-body {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
55
dashboard.php
Normal file
55
dashboard.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
$role = $_SESSION['role'] ?? 'teacher';
|
||||
$user_id = $_SESSION['user_id'];
|
||||
$can_edit_workload = false;
|
||||
|
||||
if ($role === 'teacher') {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT can_edit_workload FROM teachers WHERE user_id = ?");
|
||||
$stmt->execute([$user_id]);
|
||||
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($teacher && $teacher['can_edit_workload']) {
|
||||
$can_edit_workload = true;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Dashboard - Haki Schedule</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
<?php require_once __DIR__ . '/includes/navbar.php'; ?>
|
||||
|
||||
<main class="container py-5">
|
||||
<h1 class="h3 fw-bold">Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>!</h1>
|
||||
<p>You are logged in as a <?php echo htmlspecialchars($role); ?>.</p>
|
||||
|
||||
<?php if ($role === 'admin'): ?>
|
||||
<p>You can manage the school's data using the links in the navigation.</p>
|
||||
<?php else: ?>
|
||||
<p>You can view your timetable using the link in the navigation.</p>
|
||||
<?php if ($can_edit_workload): ?>
|
||||
<p>You can also <a href="teacher_workload.php">manage your workload</a>.</p>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white py-4 mt-5"><div class="container text-center"><p>© <?php echo date("Y"); ?> Haki Schedule. All Rights Reserved.</p></div></footer>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
58
db/migrate.php
Normal file
58
db/migrate.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
// Simple migration runner
|
||||
require_once __DIR__ . '/config.php';
|
||||
|
||||
try {
|
||||
$pdo = db();
|
||||
if (!$pdo) {
|
||||
throw new Exception("PDO connection is not valid. Check db/config.php");
|
||||
}
|
||||
|
||||
// Create migrations table if it doesn't exist
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `migrations` (
|
||||
`migration` VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (`migration`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
|
||||
|
||||
// Get all migration files
|
||||
$migrationsDir = __DIR__ . '/migrations';
|
||||
if (!is_dir($migrationsDir)) {
|
||||
echo "No migrations directory found. Nothing to do.\n";
|
||||
exit(0);
|
||||
}
|
||||
$files = glob($migrationsDir . '/*.sql');
|
||||
sort($files);
|
||||
|
||||
if (empty($files)) {
|
||||
echo "No migration files found. Nothing to do.\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Get already run migrations
|
||||
$stmt = $pdo->query("SELECT `migration` FROM `migrations`");
|
||||
$runMigrations = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
foreach ($files as $file) {
|
||||
$migrationName = basename($file);
|
||||
if (in_array($migrationName, $runMigrations)) {
|
||||
continue; // Skip already run migration
|
||||
}
|
||||
|
||||
echo "Running migration: " . $migrationName . "\n";
|
||||
$sql = file_get_contents($file);
|
||||
$pdo->exec($sql);
|
||||
|
||||
// Record the migration
|
||||
$stmt = $pdo->prepare("INSERT INTO `migrations` (`migration`) VALUES (?)");
|
||||
$stmt->execute([$migrationName]);
|
||||
}
|
||||
|
||||
echo "Migrations completed successfully.\n";
|
||||
|
||||
} catch (PDOException $e) {
|
||||
http_response_code(500);
|
||||
die("Migration failed: " . $e->getMessage() . "\n");
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
die("An error occurred: " . $e->getMessage() . "\n");
|
||||
}
|
||||
6
db/migrations/001_create_schools_table.sql
Normal file
6
db/migrations/001_create_schools_table.sql
Normal file
@ -0,0 +1,6 @@
|
||||
CREATE TABLE IF NOT EXISTS `schools` (
|
||||
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
12
db/migrations/002_create_users_table.sql
Normal file
12
db/migrations/002_create_users_table.sql
Normal file
@ -0,0 +1,12 @@
|
||||
CREATE TABLE IF NOT EXISTS `users` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`username` varchar(50) NOT NULL,
|
||||
`password` varchar(255) NOT NULL,
|
||||
`email` varchar(100) NOT NULL,
|
||||
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
`school_id` INT,
|
||||
`role` VARCHAR(255) NOT NULL DEFAULT 'teacher',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `username` (`username`),
|
||||
CONSTRAINT `fk_users_school_id` FOREIGN KEY (`school_id`) REFERENCES `schools`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
7
db/migrations/003_create_classes_table.sql
Normal file
7
db/migrations/003_create_classes_table.sql
Normal file
@ -0,0 +1,7 @@
|
||||
CREATE TABLE IF NOT EXISTS `classes` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`school_id` INT,
|
||||
PRIMARY KEY (`id`),
|
||||
CONSTRAINT `fk_classes_school_id` FOREIGN KEY (`school_id`) REFERENCES `schools`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
9
db/migrations/004_create_subjects_table.sql
Normal file
9
db/migrations/004_create_subjects_table.sql
Normal file
@ -0,0 +1,9 @@
|
||||
CREATE TABLE IF NOT EXISTS `subjects` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`is_elective` tinyint(1) DEFAULT '0',
|
||||
`has_double_lesson` tinyint(1) DEFAULT '0',
|
||||
`school_id` INT,
|
||||
PRIMARY KEY (`id`),
|
||||
CONSTRAINT `fk_subjects_school_id` FOREIGN KEY (`school_id`) REFERENCES `schools`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
10
db/migrations/005_create_teachers_table.sql
Normal file
10
db/migrations/005_create_teachers_table.sql
Normal file
@ -0,0 +1,10 @@
|
||||
CREATE TABLE IF NOT EXISTS `teachers` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`school_id` INT,
|
||||
`user_id` INT,
|
||||
`can_edit_workload` BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
PRIMARY KEY (`id`),
|
||||
CONSTRAINT `fk_teachers_school_id` FOREIGN KEY (`school_id`) REFERENCES `schools`(`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `fk_teachers_user_id` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
16
db/migrations/006_create_workloads_table.sql
Normal file
16
db/migrations/006_create_workloads_table.sql
Normal file
@ -0,0 +1,16 @@
|
||||
CREATE TABLE IF NOT EXISTS `workloads` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`teacher_id` int(11) NOT NULL,
|
||||
`subject_id` int(11) NOT NULL,
|
||||
`class_id` int(11) NOT NULL,
|
||||
`lessons_per_week` int(11) NOT NULL,
|
||||
`school_id` INT,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `teacher_id` (`teacher_id`),
|
||||
KEY `subject_id` (`subject_id`),
|
||||
KEY `class_id` (`class_id`),
|
||||
CONSTRAINT `workloads_ibfk_1` FOREIGN KEY (`teacher_id`) REFERENCES `teachers` (`id`),
|
||||
CONSTRAINT `workloads_ibfk_2` FOREIGN KEY (`subject_id`) REFERENCES `subjects` (`id`),
|
||||
CONSTRAINT `workloads_ibfk_3` FOREIGN KEY (`class_id`) REFERENCES `classes` (`id`),
|
||||
CONSTRAINT `fk_workloads_school_id` FOREIGN KEY (`school_id`) REFERENCES `schools`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
7
db/migrations/007_create_timeslots_table.sql
Normal file
7
db/migrations/007_create_timeslots_table.sql
Normal file
@ -0,0 +1,7 @@
|
||||
CREATE TABLE IF NOT EXISTS `timeslots` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`day_of_week` VARCHAR(20) NOT NULL,
|
||||
`start_time` TIME NOT NULL,
|
||||
`end_time` TIME NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
15
db/migrations/008_create_schedules_table.sql
Normal file
15
db/migrations/008_create_schedules_table.sql
Normal file
@ -0,0 +1,15 @@
|
||||
CREATE TABLE IF NOT EXISTS `schedules` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`class_id` INT NOT NULL,
|
||||
`timeslot_id` INT NOT NULL,
|
||||
`teacher_id` INT,
|
||||
`subject_id` INT,
|
||||
`subject_name_override` VARCHAR(255),
|
||||
`school_id` INT,
|
||||
PRIMARY KEY (`id`),
|
||||
FOREIGN KEY (`class_id`) REFERENCES `classes`(`id`),
|
||||
FOREIGN KEY (`timeslot_id`) REFERENCES `timeslots`(`id`),
|
||||
FOREIGN KEY (`teacher_id`) REFERENCES `teachers`(`id`),
|
||||
FOREIGN KEY (`subject_id`) REFERENCES `subjects`(`id`),
|
||||
CONSTRAINT `fk_schedules_school_id` FOREIGN KEY (`school_id`) REFERENCES `schools`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
23
db/migrations/009_add_multischool_and_roles.sql
Normal file
23
db/migrations/009_add_multischool_and_roles.sql
Normal file
@ -0,0 +1,23 @@
|
||||
-- Add school_id and role to users table
|
||||
ALTER TABLE `users`
|
||||
ADD COLUMN `school_id` INT(11) NULL,
|
||||
ADD COLUMN `role` VARCHAR(50) NOT NULL DEFAULT 'teacher';
|
||||
|
||||
-- Add workload_editable to users table
|
||||
ALTER TABLE `users`
|
||||
ADD COLUMN `workload_editable` BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
-- Add school_id to other tables
|
||||
ALTER TABLE `classes` ADD COLUMN `school_id` INT(11) NULL;
|
||||
ALTER TABLE `subjects` ADD COLUMN `school_id` INT(11) NULL;
|
||||
ALTER TABLE `teachers` ADD COLUMN `school_id` INT(11) NULL;
|
||||
ALTER TABLE `workloads` ADD COLUMN `school_id` INT(11) NULL;
|
||||
ALTER TABLE `schedules` ADD COLUMN `school_id` INT(11) NULL;
|
||||
|
||||
-- Add foreign key constraints
|
||||
ALTER TABLE `users` ADD CONSTRAINT `fk_users_school_id` FOREIGN KEY (`school_id`) REFERENCES `schools`(`id`) ON DELETE CASCADE;
|
||||
ALTER TABLE `classes` ADD CONSTRAINT `fk_classes_school_id` FOREIGN KEY (`school_id`) REFERENCES `schools`(`id`) ON DELETE CASCADE;
|
||||
ALTER TABLE `subjects` ADD CONSTRAINT `fk_subjects_school_id` FOREIGN KEY (`school_id`) REFERENCES `schools`(`id`) ON DELETE CASCADE;
|
||||
ALTER TABLE `teachers` ADD CONSTRAINT `fk_teachers_school_id` FOREIGN KEY (`school_id`) REFERENCES `schools`(`id`) ON DELETE CASCADE;
|
||||
ALTER TABLE `workloads` ADD CONSTRAINT `fk_workloads_school_id` FOREIGN KEY (`school_id`) REFERENCES `schools`(`id`) ON DELETE CASCADE;
|
||||
ALTER TABLE `schedules` ADD CONSTRAINT `fk_schedules_school_id` FOREIGN KEY (`school_id`) REFERENCES `schools`(`id`) ON DELETE CASCADE;
|
||||
9
db/migrations/010_create_elective_groups.sql
Normal file
9
db/migrations/010_create_elective_groups.sql
Normal file
@ -0,0 +1,9 @@
|
||||
CREATE TABLE IF NOT EXISTS `elective_groups` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`name` VARCHAR(255) NOT NULL,
|
||||
`school_id` INT NOT NULL,
|
||||
FOREIGN KEY (`school_id`) REFERENCES `schools`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
ALTER TABLE `subjects` ADD COLUMN `elective_group_id` INT DEFAULT NULL,
|
||||
ADD FOREIGN KEY (`elective_group_id`) REFERENCES `elective_groups`(`id`) ON DELETE SET NULL;
|
||||
5
db/migrations/011_add_unique_constraints.sql
Normal file
5
db/migrations/011_add_unique_constraints.sql
Normal file
@ -0,0 +1,5 @@
|
||||
ALTER TABLE `elective_groups` ADD UNIQUE `unique_school_name`(`school_id`, `name`);
|
||||
ALTER TABLE `subjects` ADD UNIQUE `unique_school_name`(`school_id`, `name`);
|
||||
ALTER TABLE `classes` ADD UNIQUE `unique_school_name`(`school_id`, `name`);
|
||||
ALTER TABLE `teachers` ADD UNIQUE `unique_school_name`(`school_id`, `name`);
|
||||
ALTER TABLE `teachers` ADD UNIQUE `unique_school_teacher`(`school_id`, `name`);
|
||||
1
db/migrations/012_add_lessons_per_week_to_subjects.sql
Normal file
1
db/migrations/012_add_lessons_per_week_to_subjects.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE subjects ADD COLUMN lessons_per_week INT NOT NULL DEFAULT 1;
|
||||
4
db/migrations/013_update_timeslots_table.sql
Normal file
4
db/migrations/013_update_timeslots_table.sql
Normal file
@ -0,0 +1,4 @@
|
||||
ALTER TABLE timeslots
|
||||
ADD COLUMN name VARCHAR(255) NOT NULL,
|
||||
ADD COLUMN is_break TINYINT(1) NOT NULL DEFAULT 0,
|
||||
DROP COLUMN day_of_week;
|
||||
1
db/migrations/014_add_day_of_week_to_schedules.sql
Normal file
1
db/migrations/014_add_day_of_week_to_schedules.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE schedules ADD COLUMN day_of_week INT NOT NULL AFTER class_id;
|
||||
@ -0,0 +1 @@
|
||||
ALTER TABLE `schedules` ADD `lesson_display_name` VARCHAR(255);
|
||||
@ -0,0 +1 @@
|
||||
ALTER TABLE `schedules` ADD `teacher_display_name` VARCHAR(255);
|
||||
1
db/migrations/017_add_is_double_to_schedules.sql
Normal file
1
db/migrations/017_add_is_double_to_schedules.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE schedules ADD COLUMN is_double BOOLEAN NOT NULL DEFAULT 0;
|
||||
1
db/migrations/018_add_elective_flags_to_schedules.sql
Normal file
1
db/migrations/018_add_elective_flags_to_schedules.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE schedules ADD COLUMN is_elective BOOLEAN NOT NULL DEFAULT 0, ADD COLUMN is_horizontal_elective BOOLEAN NOT NULL DEFAULT 0;
|
||||
8
db/migrations/019_create_schedule_teachers_table.sql
Normal file
8
db/migrations/019_create_schedule_teachers_table.sql
Normal file
@ -0,0 +1,8 @@
|
||||
CREATE TABLE IF NOT EXISTS schedule_teachers (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
schedule_id INT NOT NULL,
|
||||
teacher_id INT NOT NULL,
|
||||
FOREIGN KEY (schedule_id) REFERENCES schedules(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (teacher_id) REFERENCES teachers(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY (schedule_id, teacher_id)
|
||||
);
|
||||
@ -0,0 +1,7 @@
|
||||
CREATE TABLE IF NOT EXISTS `elective_group_subjects` (
|
||||
`elective_group_id` INT NOT NULL,
|
||||
`subject_id` INT NOT NULL,
|
||||
PRIMARY KEY (`elective_group_id`, `subject_id`),
|
||||
FOREIGN KEY (`elective_group_id`) REFERENCES `elective_groups`(`id`) ON DELETE CASCADE,
|
||||
FOREIGN KEY (`subject_id`) REFERENCES `subjects`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
@ -0,0 +1,8 @@
|
||||
CREATE TABLE IF NOT EXISTS elective_group_subjects (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
elective_group_id INT NOT NULL,
|
||||
subject_id INT NOT NULL,
|
||||
FOREIGN KEY (elective_group_id) REFERENCES elective_groups(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (subject_id) REFERENCES subjects(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY (elective_group_id, subject_id)
|
||||
);
|
||||
2
db/migrations/022_add_can_edit_workload_to_teachers.sql
Normal file
2
db/migrations/022_add_can_edit_workload_to_teachers.sql
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE `teachers`
|
||||
ADD COLUMN `can_edit_workload` BOOLEAN NOT NULL DEFAULT 0 COMMENT 'If true, the teacher can edit their own workload';
|
||||
1
db/migrations/023_add_working_days_to_schools.sql
Normal file
1
db/migrations/023_add_working_days_to_schools.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE `schools` ADD COLUMN `working_days` VARCHAR(255) NOT NULL DEFAULT 'Monday,Tuesday,Wednesday,Thursday,Friday';
|
||||
1
db/migrations/024_add_is_active_to_users.sql
Normal file
1
db/migrations/024_add_is_active_to_users.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE users ADD COLUMN is_active BOOLEAN NOT NULL DEFAULT 0;
|
||||
161
demo.php
Normal file
161
demo.php
Normal file
@ -0,0 +1,161 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Timetable Demo - Haki Schedule</title>
|
||||
<meta name="description" content="Built with Flatlogic Generator">
|
||||
<meta name="keywords" content="timetable app, school scheduling, automated timetable, class schedule, teacher workload, subscription timetable, school administration, education tech, Haki schedule, Built with Flatlogic Generator">
|
||||
<meta property="og:title" content="Haki Schedule">
|
||||
<meta property="og:description" content="Built with Flatlogic Generator">
|
||||
<meta property="og:image" content="">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:image" content="">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white sticky-top">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="/">Haki Schedule</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/#features">Features</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Pricing</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/demo.php">Demo</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/timetable.php">Timetable</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Manage
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<li><a class="dropdown-item" href="/admin_classes.php">Manage Classes</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_subjects.php">Manage Subjects</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_teachers.php">Manage Teachers</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_workloads.php">Manage Workloads</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav ms-lg-3">
|
||||
<li class="nav-item">
|
||||
<a class="btn btn-outline-primary" href="#">Login</a>
|
||||
</li>
|
||||
<li class="nav-item ms-lg-2 mt-2 mt-lg-0">
|
||||
<a class="btn btn-primary" href="#">Sign Up</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container py-5">
|
||||
<div class="printable-area">
|
||||
<div class="timetable-container">
|
||||
<div class="timetable-header">
|
||||
<div>
|
||||
<h1 class="h3 fw-bold">Class Timetable: Grade 10A</h1>
|
||||
<p class="text-muted">A visual demonstration of a generated schedule.</p>
|
||||
</div>
|
||||
<div>
|
||||
<button onclick="window.print();" class="btn btn-primary">Print Timetable</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered timetable-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Day</th>
|
||||
<th>08:00 - 09:00</th>
|
||||
<th>09:00 - 10:00</th>
|
||||
<th>10:00 - 10:30</th>
|
||||
<th>10:30 - 11:30</th>
|
||||
<th>11:30 - 12:30</th>
|
||||
<th>12:30 - 13:30</th>
|
||||
<th>13:30 - 14:30</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="day-header">Monday</td>
|
||||
<td class="timetable-cell"><strong>Mathematics</strong><span>Mr. Smith</span></td>
|
||||
<td class="timetable-cell"><strong>Physics</strong><span>Ms. Jones</span></td>
|
||||
<td class="break-cell">Break</td>
|
||||
<td class="timetable-cell"><strong>English</strong><span>Mr. Doe</span></td>
|
||||
<td class="timetable-cell"><strong>History</strong><span>Mrs. Dane</span></td>
|
||||
<td class="break-cell">Lunch</td>
|
||||
<td class="timetable-cell"><strong>Biology</strong><span>Ms. Jones</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="day-header">Tuesday</td>
|
||||
<td class="timetable-cell"><strong>Chemistry</strong><span>Mr. White</span></td>
|
||||
<td class="timetable-cell"><strong>English</strong><span>Mr. Doe</span></td>
|
||||
<td class="break-cell">Break</td>
|
||||
<td class="timetable-cell"><strong>Mathematics</strong><span>Mr. Smith</span></td>
|
||||
<td class="timetable-cell"><strong>Geography</strong><span>Mr. Green</span></td>
|
||||
<td class="break-cell">Lunch</td>
|
||||
<td class="timetable-cell"><strong>Art</strong><span>Ms. Black</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="day-header">Wednesday</td>
|
||||
<td class="timetable-cell"><strong>Physics (Double)</strong><span>Ms. Jones</span></td>
|
||||
<td class="timetable-cell"><strong>Physics (Double)</strong><span>Ms. Jones</span></td>
|
||||
<td class="break-cell">Break</td>
|
||||
<td class="timetable-cell"><strong>History</strong><span>Mrs. Dane</span></td>
|
||||
<td class="timetable-cell"><strong>Mathematics</strong><span>Mr. Smith</span></td>
|
||||
<td class="break-cell">Lunch</td>
|
||||
<td class="timetable-cell"><strong>Music</strong><span>Mr. Brown</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="day-header">Thursday</td>
|
||||
<td class="timetable-cell"><strong>English</strong><span>Mr. Doe</span></td>
|
||||
<td class="timetable-cell"><strong>Biology</strong><span>Ms. Jones</span></td>
|
||||
<td class="break-cell">Break</td>
|
||||
<td class="timetable-cell"><strong>Chemistry</strong><span>Mr. White</span></td>
|
||||
<td class="timetable-cell"><strong>Physical Ed.</strong><span>Mr. Blue</span></td>
|
||||
<td class="break-cell">Lunch</td>
|
||||
<td class="timetable-cell"><strong>Mathematics</strong><span>Mr. Smith</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="day-header">Friday</td>
|
||||
<td class="timetable-cell"><strong>History</strong><span>Mrs. Dane</span></td>
|
||||
<td class="timetable-cell"><strong>Geography</strong><span>Mr. Green</span></td>
|
||||
<td class="break-cell">Break</td>
|
||||
<td class="timetable-cell"><strong>English</strong><span>Mr. Doe</span></td>
|
||||
<td class="timetable-cell"><strong>Mathematics</strong><span>Mr. Smith</span></td>
|
||||
<td class="break-cell">Lunch</td>
|
||||
<td class="timetable-cell"><strong>Elective</strong><span>Various</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white py-4 mt-auto">
|
||||
<div class="container text-center">
|
||||
<p>© <?php echo date("Y"); ?> Haki Schedule. All Rights Reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
48
includes/auth_check.php
Normal file
48
includes/auth_check.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// If user is not logged in, redirect to login page
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check if the user's account is active
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT is_active FROM users WHERE id = ?");
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
$is_active = $stmt->fetchColumn();
|
||||
|
||||
if (!$is_active) {
|
||||
// User is not active, log them out and redirect to subscription page
|
||||
$_SESSION['user_id_for_activation'] = $_SESSION['user_id']; // Preserve user ID for activation
|
||||
|
||||
// Unset all other session variables
|
||||
unset($_SESSION['user_id']);
|
||||
unset($_SESSION['username']);
|
||||
unset($_SESSION['role']);
|
||||
unset($_SESSION['school_id']);
|
||||
if (isset($_SESSION['can_edit_workload'])) {
|
||||
unset($_SESSION['can_edit_workload']);
|
||||
}
|
||||
|
||||
header("Location: /subscription.php?reason=inactive");
|
||||
exit;
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
// On DB error, log out user for safety
|
||||
session_destroy();
|
||||
header("Location: /login.php?error=db_error");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Role-based access check (existing logic)
|
||||
$allowed_roles = ['admin', 'teacher'];
|
||||
if (!isset($_SESSION['role']) || !in_array($_SESSION['role'], $allowed_roles)) {
|
||||
header("Location: /login.php?error=unauthorized");
|
||||
exit;
|
||||
}
|
||||
10
includes/auth_check_teacher.php
Normal file
10
includes/auth_check_teacher.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// If user is not logged in, redirect to login page
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
}
|
||||
26
includes/auth_check_workload.php
Normal file
26
includes/auth_check_workload.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
// If user is not logged in, redirect to login page
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check if user is a teacher and can edit their workload
|
||||
if ($_SESSION['role'] !== 'teacher') {
|
||||
header("Location: dashboard.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT can_edit_workload FROM teachers WHERE user_id = ?");
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$teacher || !$teacher['can_edit_workload']) {
|
||||
header("Location: dashboard.php?error=workload_not_editable");
|
||||
exit;
|
||||
}
|
||||
59
includes/navbar.php
Normal file
59
includes/navbar.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
error_log("Navbar role check: " . ($_SESSION['role'] ?? 'not set'));
|
||||
// Note: This file assumes session_start() has been called by the including file.
|
||||
$current_page = basename($_SERVER['SCRIPT_NAME']);
|
||||
$role = $_SESSION['role'] ?? '';
|
||||
?>
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white sticky-top shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="/">Haki Schedule</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item"><a class="nav-link <?php echo ($current_page == 'index.php') ? 'active' : ''; ?>" href="/">Home</a></li>
|
||||
<?php if (isset($_SESSION['user_id'])) : ?>
|
||||
<?php if ($role === 'admin'): ?>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle <?php echo (strpos($current_page, 'admin_') === 0) ? 'active' : ''; ?>" href="#" id="manageDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Manage
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="manageDropdown">
|
||||
<li><a class="dropdown-item" href="/admin_classes.php">Classes</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_subjects.php">Subjects</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_teachers.php">Teachers</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_workloads.php">Workloads</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_timeslots.php">Timeslots</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_elective_groups.php">Elective Groups</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="/admin_school_settings.php">School Settings</a></li>
|
||||
<li><a class="dropdown-item" href="/admin_data_management.php">Data Management</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($role === 'admin' || $role === 'teacher'): ?>
|
||||
<li class="nav-item"><a class="nav-link <?php echo ($current_page == 'timetable.php') ? 'active' : ''; ?>" href="/timetable.php">Class Timetable</a></li>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($role === 'admin'): ?>
|
||||
<li class="nav-item"><a class="nav-link <?php echo ($current_page == 'teacher_timetable.php') ? 'active' : ''; ?>" href="/teacher_timetable.php">Teacher Timetable</a></li>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($role === 'teacher'): ?>
|
||||
<?php if (!empty($_SESSION['can_edit_workload'])): ?>
|
||||
<li class="nav-item"><a class="nav-link <?php echo ($current_page == 'teacher_workload.php') ? 'active' : ''; ?>" href="/teacher_workload.php">My Workload</a></li>
|
||||
<?php endif; ?>
|
||||
<li class="nav-item"><a class="nav-link <?php echo ($current_page == 'teacher_timetable.php') ? 'active' : ''; ?>" href="/teacher_timetable.php">My Timetable</a></li>
|
||||
<?php endif; ?>
|
||||
<li class="nav-item"><a class="nav-link" href="/logout.php">Logout</a></li>
|
||||
<?php else : ?>
|
||||
<li class="nav-item"><a class="nav-link <?php echo ($current_page == 'demo.php') ? 'active' : ''; ?>" href="/demo.php">Demo</a></li>
|
||||
<li class="nav-item"><a class="nav-link <?php echo ($current_page == 'login.php') ? 'active' : ''; ?>" href="/login.php">Login</a></li>
|
||||
<li class="nav-item"><a class="nav-link <?php echo ($current_page == 'register.php') ? 'active' : ''; ?>" href="/register.php">Register</a></li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
214
index.php
214
index.php
@ -1,150 +1,74 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
?>
|
||||
<!doctype html>
|
||||
<?php session_start(); ?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<?php
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
@keyframes bg-pan {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.loader {
|
||||
margin: 1.25rem auto 1.25rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.hint {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
code {
|
||||
background: rgba(0,0,0,0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Haki Schedule - Automated Timetable Generator</title>
|
||||
<meta name="description" content="Haki Schedule is a simple, elegant, and powerful automated timetable generator for educational institutions.">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.10.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
</div>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/navbar.php'; ?>
|
||||
|
||||
<header class="hero-section text-white text-center">
|
||||
<div class="container">
|
||||
<h1 class="display-4 fw-bold">Intelligent Timetabling, Simplified.</h1>
|
||||
<p class="lead my-4">Automate school schedules, eliminate conflicts, and empower your staff. <br>Haki Schedule is the all-in-one solution for modern educational institutions.</p>
|
||||
<a href="/demo.php" class="btn btn-light btn-lg">See a Demo</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section id="features" class="py-5">
|
||||
<div class="container">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="fw-bold">Everything you need to run a seamless school schedule</h2>
|
||||
<p class="text-muted">From automated generation to easy access for teachers, we've got you covered.</p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card h-100 text-center p-4">
|
||||
<div class="feature-icon mx-auto mb-3">
|
||||
<i data-feather="cpu"></i>
|
||||
</div>
|
||||
<h5 class="card-title fw-bold">Automated Scheduling</h5>
|
||||
<p class="card-text">Our powerful algorithm generates optimized, conflict-free timetables in minutes, not days. Handle complex workloads and constraints with ease.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card h-100 text-center p-4">
|
||||
<div class="feature-icon mx-auto mb-3">
|
||||
<i data-feather="users"></i>
|
||||
</div>
|
||||
<h5 class="card-title fw-bold">Collaborative & Transparent</h5>
|
||||
<p class="card-text">Empower teachers to view their schedules anytime. Admins can delegate workload entry and manage everything from a central dashboard.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card h-100 text-center p-4">
|
||||
<div class="feature-icon mx-auto mb-3">
|
||||
<i data-feather="printer"></i>
|
||||
</div>
|
||||
<h5 class="card-title fw-bold">Accessible & Printable</h5>
|
||||
<p class="card-text">View timetables on any device, web or mobile. Export and print beautiful, clean schedules for classes and teachers with a single click.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="bg-dark text-white py-4">
|
||||
<div class="container text-center">
|
||||
<p>© <?php echo date("Y"); ?> Haki Schedule. All Rights Reserved.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
feather.replace()
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
117
login.php
Normal file
117
login.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
$error = '';
|
||||
|
||||
// If user is already logged in, redirect to home
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
header("Location: index.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$username = $_POST['username'] ?? null;
|
||||
$password = $_POST['password'] ?? null;
|
||||
|
||||
if (empty($username) || empty($password)) {
|
||||
$error = 'Username and password are required.';
|
||||
} else {
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
|
||||
$stmt->execute([$username]);
|
||||
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($user && password_verify($password, $user['password'])) {
|
||||
// Check if account is active
|
||||
if ($user['is_active']) {
|
||||
// Password is correct, start session
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['username'] = $user['username'];
|
||||
$_SESSION['role'] = $user['role'];
|
||||
$_SESSION['school_id'] = $user['school_id'];
|
||||
|
||||
// If the user is a teacher, fetch their workload editing permission
|
||||
if ($user['role'] === 'teacher') {
|
||||
$stmt = $pdo->prepare("SELECT can_edit_workload FROM teachers WHERE user_id = ?");
|
||||
$stmt->execute([$user['id']]);
|
||||
$teacher_permission = $stmt->fetchColumn();
|
||||
$_SESSION['can_edit_workload'] = (bool)$teacher_permission;
|
||||
}
|
||||
|
||||
// Redirect to the main page
|
||||
header("Location: dashboard.php");
|
||||
exit;
|
||||
} else {
|
||||
// Account is not active, redirect to subscription page
|
||||
$_SESSION['user_id_for_activation'] = $user['id'];
|
||||
header("Location: subscription.php");
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
$error = 'Invalid username or password.';
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login - Haki Schedule</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white sticky-top shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="/">Haki Schedule</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-5">
|
||||
<div class="card">
|
||||
<div class="card-body p-4">
|
||||
<h1 class="h3 fw-bold text-center mb-4">Login</h1>
|
||||
|
||||
<?php if (isset($_GET['status']) && $_GET['status'] === 'activated'): ?>
|
||||
<div class="alert alert-success">Your account has been activated! Please log in to continue.</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger"><?php echo $error; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="login.php" method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary">Login</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="text-center mt-3">
|
||||
<p>Don't have an account? <a href="register.php">Register here</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white py-4 mt-5"><div class="container text-center"><p>© <?php echo date("Y"); ?> Haki Schedule. All Rights Reserved.</p></div></footer>
|
||||
</body>
|
||||
</html>
|
||||
6
logout.php
Normal file
6
logout.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
session_start();
|
||||
session_unset();
|
||||
session_destroy();
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
133
register.php
Normal file
133
register.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
$message = '';
|
||||
$error = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$username = $_POST['username'] ?? null;
|
||||
$password = $_POST['password'] ?? null;
|
||||
$school_name = $_POST['school_name'] ?? null;
|
||||
$email = $_POST['email'] ?? null;
|
||||
|
||||
if (empty($username) || empty($password) || empty($school_name) || empty($email)) {
|
||||
$error = 'All fields are required.';
|
||||
} else {
|
||||
try {
|
||||
$pdo = db();
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Check if school name already exists
|
||||
$stmt = $pdo->prepare("SELECT id FROM schools WHERE name = ?");
|
||||
$stmt->execute([$school_name]);
|
||||
if ($stmt->fetch()) {
|
||||
$error = 'School name already taken. Please choose another one.';
|
||||
$pdo->rollBack();
|
||||
} else {
|
||||
// Insert new school
|
||||
$stmt = $pdo->prepare("INSERT INTO schools (name) VALUES (?)" );
|
||||
$stmt->execute([$school_name]);
|
||||
$school_id = $pdo->lastInsertId();
|
||||
|
||||
// Check if username already exists
|
||||
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = ?");
|
||||
$stmt->execute([$username]);
|
||||
if ($stmt->fetch()) {
|
||||
$error = 'Username already taken. Please choose another one.';
|
||||
$pdo->rollBack();
|
||||
} else {
|
||||
// Hash the password
|
||||
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
// Insert new user
|
||||
$stmt = $pdo->prepare("INSERT INTO users (username, password, email, school_id, role) VALUES (?, ?, ?, ?, 'admin')");
|
||||
if ($stmt->execute([$username, $hashed_password, $email, $school_id])) {
|
||||
$pdo->commit();
|
||||
|
||||
// Start session and store user ID for activation
|
||||
session_start();
|
||||
$_SESSION['user_id_for_activation'] = $pdo->lastInsertId();
|
||||
|
||||
// Redirect to subscription page
|
||||
header("Location: subscription.php");
|
||||
exit;
|
||||
} else {
|
||||
$error = 'Failed to register user.';
|
||||
$pdo->rollBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Register - Haki Schedule</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-white sticky-top shadow-sm">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="/">Haki Schedule</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-5">
|
||||
<div class="card">
|
||||
<div class="card-body p-4">
|
||||
<h1 class="h3 fw-bold text-center mb-4">Create an Admin Account</h1>
|
||||
|
||||
<?php if ($message): ?>
|
||||
<div class="alert alert-success"><?php echo $message; ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger"><?php echo $error; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!$message): ?>
|
||||
<form action="register.php" method="POST">
|
||||
<div class="mb-3">
|
||||
<label for="school_name" class="form-label">School Name</label>
|
||||
<input type="text" class="form-control" id="school_name" name="school_name" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Admin Username</label>
|
||||
<input type="text" class="form-control" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Admin Email</label>
|
||||
<input type="email" class="form-control" id="email" name="email" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary">Register</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="text-center mt-3">
|
||||
<p>Already have an account? <a href="login.php">Login here</a>.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white py-4 mt-5"><div class="container text-center"><p>© <?php echo date("Y"); ?> Haki Schedule. All Rights Reserved.</p></div></footer>
|
||||
</body>
|
||||
</html>
|
||||
80
subscription.php
Normal file
80
subscription.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
// Ensure user has just registered
|
||||
if (!isset($_SESSION['user_id_for_activation'])) {
|
||||
header('Location: register.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
$user_id = $_SESSION['user_id_for_activation'];
|
||||
$message = '';
|
||||
$error = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['activate'])) {
|
||||
try {
|
||||
$pdo = db();
|
||||
$stmt = $pdo->prepare("UPDATE users SET is_active = 1 WHERE id = ?");
|
||||
if ($stmt->execute([$user_id])) {
|
||||
// Activation successful
|
||||
unset($_SESSION['user_id_for_activation']);
|
||||
session_destroy(); // Clean up session
|
||||
|
||||
// Redirect to login with a success message
|
||||
header('Location: login.php?status=activated');
|
||||
exit;
|
||||
} else {
|
||||
$error = "Failed to activate your account. Please contact support.";
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$error = "Database error: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Activate Your Account - Haki Schedule</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
<?php include 'includes/navbar.php'; ?>
|
||||
|
||||
<main class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-6">
|
||||
<div class="card text-center shadow">
|
||||
<div class="card-body p-5">
|
||||
<h1 class="h2 fw-bold mb-3">One More Step!</h1>
|
||||
<p class="lead mb-4">Your account has been created, but you need to activate it to get access to the application.</p>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger"><?php echo $error; ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="p-4 bg-light rounded border">
|
||||
<h3 class="h5 fw-bold">Basic Plan</h3>
|
||||
<p class="fs-1 fw-bold mb-2">$10<span class="fs-6 fw-normal">/month</span></p>
|
||||
<p class="text-muted">Full access for one school administrator.</p>
|
||||
<form action="subscription.php" method="POST">
|
||||
<div class="d-grid">
|
||||
<button type="submit" name="activate" class="btn btn-primary btn-lg">Activate Your Account</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<p class="mt-4 text-muted small">For now, clicking 'Activate' will simulate a successful payment. In a real application, you would be redirected to a payment processor.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white py-4 mt-5"><div class="container text-center"><p>© <?php echo date("Y"); ?> Haki Schedule. All Rights Reserved.</p></div></footer>
|
||||
</body>
|
||||
</html>
|
||||
357
teacher_timetable.php
Normal file
357
teacher_timetable.php
Normal file
@ -0,0 +1,357 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'includes/auth_check_teacher.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
// --- Database Fetch Functions ---
|
||||
function get_teachers($pdo, $school_id) {
|
||||
$stmt = $pdo->prepare("SELECT * FROM teachers WHERE school_id = ? ORDER BY name");
|
||||
$stmt->execute([$school_id]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
function get_timeslots($pdo) {
|
||||
return $pdo->query("SELECT * FROM timeslots ORDER BY start_time")->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
function get_teacher_schedule($pdo, $teacher_id) {
|
||||
$sql = "
|
||||
-- Get all lessons (elective and non-elective) for a teacher
|
||||
SELECT
|
||||
s.id,
|
||||
s.day_of_week,
|
||||
s.timeslot_id,
|
||||
s.lesson_display_name,
|
||||
c.name as class_name,
|
||||
s.is_double,
|
||||
s.is_elective,
|
||||
eg.name as elective_group_name
|
||||
FROM schedules s
|
||||
JOIN schedule_teachers st ON s.id = st.schedule_id
|
||||
LEFT JOIN classes c ON s.class_id = c.id
|
||||
LEFT JOIN elective_groups eg ON s.elective_group_id = eg.id
|
||||
WHERE st.teacher_id = :teacher_id
|
||||
ORDER BY s.day_of_week, s.timeslot_id
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([':teacher_id' => $teacher_id]);
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
|
||||
// --- Main Logic ---
|
||||
$pdoconn = db();
|
||||
$school_id = $_SESSION['school_id'];
|
||||
$role = $_SESSION['role'];
|
||||
$user_id = $_SESSION['user_id'];
|
||||
|
||||
$teachers = [];
|
||||
if ($role === 'admin') {
|
||||
$stmt = $pdoconn->prepare("SELECT * FROM teachers WHERE school_id = ? ORDER BY name");
|
||||
$stmt->execute([$school_id]);
|
||||
$teachers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} else { // Teacher
|
||||
$stmt = $pdoconn->prepare("SELECT * FROM teachers WHERE user_id = ? AND school_id = ?");
|
||||
$stmt->execute([$user_id, $school_id]);
|
||||
$teachers = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
$timeslots = get_timeslots($pdoconn);
|
||||
$days_of_week = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];
|
||||
|
||||
$selected_teacher_id = null;
|
||||
if ($role === 'admin') {
|
||||
$selected_teacher_id = isset($_GET['teacher_id']) ? $_GET['teacher_id'] : null;
|
||||
} else { // Teacher
|
||||
if (!empty($teachers)) {
|
||||
$selected_teacher_id = $teachers[0]['id'];
|
||||
}
|
||||
}
|
||||
|
||||
$selected_teacher_name = '';
|
||||
$teacher_schedule_raw = [];
|
||||
if ($selected_teacher_id) {
|
||||
foreach ($teachers as $teacher) {
|
||||
if ($teacher['id'] == $selected_teacher_id) {
|
||||
$selected_teacher_name = $teacher['name'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
$teacher_schedule_raw = get_teacher_schedule($pdoconn, $selected_teacher_id);
|
||||
}
|
||||
|
||||
// Organize schedule for easy display
|
||||
$non_break_periods = array_values(array_filter($timeslots, function($ts) { return !$ts['is_break']; }));
|
||||
$timeslot_id_to_period_idx = [];
|
||||
foreach($non_break_periods as $idx => $period) {
|
||||
$timeslot_id_to_period_idx[$period['id']] = $idx;
|
||||
}
|
||||
|
||||
$teacher_timetable_by_period = [];
|
||||
// Initialize the timetable array
|
||||
foreach ($days_of_week as $day_idx => $day) {
|
||||
$teacher_timetable_by_period[$day_idx] = array_fill(0, count($non_break_periods), null);
|
||||
}
|
||||
|
||||
foreach ($teacher_schedule_raw as $lesson) {
|
||||
$day_idx = $lesson['day_of_week'];
|
||||
if (isset($timeslot_id_to_period_idx[$lesson['timeslot_id']])) {
|
||||
$period_idx = $timeslot_id_to_period_idx[$lesson['timeslot_id']];
|
||||
|
||||
if (isset($teacher_timetable_by_period[$day_idx][$period_idx])) {
|
||||
if (!is_array($teacher_timetable_by_period[$day_idx][$period_idx]) || !isset($teacher_timetable_by_period[$day_idx][$period_idx][0])) {
|
||||
$teacher_timetable_by_period[$day_idx][$period_idx] = [$teacher_timetable_by_period[$day_idx][$period_idx]];
|
||||
}
|
||||
$teacher_timetable_by_period[$day_idx][$period_idx][] = $lesson;
|
||||
} else {
|
||||
$teacher_timetable_by_period[$day_idx][$period_idx] = $lesson;
|
||||
}
|
||||
|
||||
if (!empty($lesson['is_double']) && isset($non_break_periods[$period_idx + 1])) {
|
||||
$next_period_idx = $period_idx + 1;
|
||||
if (isset($teacher_timetable_by_period[$day_idx][$next_period_idx])) {
|
||||
if (!is_array($teacher_timetable_by_period[$day_idx][$next_period_idx]) || !isset($teacher_timetable_by_period[$day_idx][$next_period_idx][0])) {
|
||||
$teacher_timetable_by_period[$day_idx][$next_period_idx] = [$teacher_timetable_by_period[$day_idx][$next_period_idx]];
|
||||
}
|
||||
$teacher_timetable_by_period[$day_idx][$next_period_idx][] = $lesson;
|
||||
} else {
|
||||
$teacher_timetable_by_period[$day_idx][$next_period_idx] = $lesson;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Teacher Timetable - Haki Schedule</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
<link rel="stylesheet" href="assets/css/print.css?v=<?php echo time(); ?>" media="print">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<?php require_once 'includes/navbar.php'; ?>
|
||||
|
||||
<div class="container mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4 no-print">
|
||||
<h1>Teacher Timetable</h1>
|
||||
<?php if ($selected_teacher_id): ?>
|
||||
<div class="d-flex gap-2">
|
||||
<button id="print-btn" class="btn btn-secondary">Print</button>
|
||||
<button id="download-btn" class="btn btn-secondary">Download as PDF</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($role === 'admin'): ?>
|
||||
<form method="GET" action="" class="mb-4 no-print">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<label for="teacher_id" class="form-label">Select Teacher</label>
|
||||
<select name="teacher_id" id="teacher_id" class="form-select" onchange="this.form.submit()">
|
||||
<option value="">-- Select a Teacher --</option>
|
||||
<?php foreach ($teachers as $teacher): ?>
|
||||
<option value="<?php echo $teacher['id']; ?>" <?php echo ($selected_teacher_id == $teacher['id']) ? 'selected' : ''; ?> >
|
||||
<?php echo htmlspecialchars($teacher['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<div id="timetable-container" class="timetable-container">
|
||||
<?php if ($selected_teacher_id && !empty($teacher_schedule_raw)): ?>
|
||||
<div class="timetable-wrapper">
|
||||
<h3 class="mt-4">Timetable for <?php echo htmlspecialchars($selected_teacher_name); ?></h3>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<table class="table table-bordered text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 12%;">Time</th>
|
||||
<?php foreach ($days_of_week as $day): ?>
|
||||
<th><?php echo $day; ?></th>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
$period_indices = array_keys($non_break_periods);
|
||||
foreach ($period_indices as $period_idx) {
|
||||
$current_timeslot_id = $non_break_periods[$period_idx]['id'];
|
||||
$timeslot_info = null;
|
||||
foreach ($timeslots as $ts) {
|
||||
if ($ts['id'] === $current_timeslot_id) {
|
||||
$timeslot_info = $ts;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$break_html = '';
|
||||
$last_period_end_time = '00:00:00';
|
||||
if ($period_idx > 0) {
|
||||
$prev_period_id = $non_break_periods[$period_idx - 1]['id'];
|
||||
foreach($timeslots as $ts) {
|
||||
if ($ts['id'] === $prev_period_id) {
|
||||
$last_period_end_time = $ts['end_time'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($timeslots as $ts) {
|
||||
if ($ts['is_break'] && $ts['start_time'] >= $last_period_end_time && $ts['start_time'] < $timeslot_info['start_time']) {
|
||||
$break_html .= '<tr>';
|
||||
$break_html .= '<td><strong>' . htmlspecialchars($ts['name']) . '</strong><br><small class="text-muted">' . date("g:i A", strtotime($ts['start_time'])) . ' - ' . date("g:i A", strtotime($ts['end_time'])) . '</small></td>';
|
||||
$break_html .= '<td colspan="' . count($days_of_week) . '" class="text-center table-secondary"><strong>Break</strong></td>';
|
||||
$break_html .= '</tr>';
|
||||
}
|
||||
}
|
||||
echo $break_html;
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<strong><?php echo htmlspecialchars($timeslot_info['name']); ?></strong><br>
|
||||
<small class="text-muted"><?php echo date("g:i A", strtotime($timeslot_info['start_time'])); ?> - <?php echo date("g:i A", strtotime($timeslot_info['end_time'])); ?></small>
|
||||
</td>
|
||||
<?php foreach ($days_of_week as $day_idx => $day): ?>
|
||||
<td class="timetable-slot align-middle">
|
||||
<?php
|
||||
$lesson_data = $teacher_timetable_by_period[$day_idx][$period_idx] ?? null;
|
||||
if ($lesson_data) {
|
||||
$lessons_to_display = (is_array($lesson_data) && isset($lesson_data[0])) ? $lesson_data : [$lesson_data];
|
||||
|
||||
$lessons_by_group = [];
|
||||
foreach ($lessons_to_display as $single_lesson) {
|
||||
if ($single_lesson) {
|
||||
if (!empty($single_lesson['is_elective'])) {
|
||||
$group_name = $single_lesson['elective_group_name'];
|
||||
if (empty($group_name) && !empty($single_lesson['lesson_display_name'])) {
|
||||
$parts = explode(' / ', $single_lesson['lesson_display_name']);
|
||||
$group_name = $parts[0];
|
||||
}
|
||||
if (empty($group_name)) {
|
||||
$group_name = 'Elective';
|
||||
}
|
||||
|
||||
if (!isset($lessons_by_group[$group_name])) {
|
||||
$lessons_by_group[$group_name] = [
|
||||
'is_elective' => true,
|
||||
'classes' => []
|
||||
];
|
||||
}
|
||||
if (!empty($single_lesson['class_name'])) {
|
||||
$lessons_by_group[$group_name]['classes'][] = $single_lesson['class_name'];
|
||||
}
|
||||
} else {
|
||||
$display_subject = $single_lesson['lesson_display_name'];
|
||||
if (!isset($lessons_by_group[$display_subject])) {
|
||||
$lessons_by_group[$display_subject] = [
|
||||
'is_elective' => false,
|
||||
'classes' => []
|
||||
];
|
||||
}
|
||||
if (!empty($single_lesson['class_name'])) {
|
||||
$lessons_by_group[$display_subject]['classes'][] = $single_lesson['class_name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($lessons_by_group as $name => $data) {
|
||||
echo '<div class="lesson p-1 mb-1">';
|
||||
echo '<strong>' . htmlspecialchars($name) . '</strong><br>';
|
||||
if (!empty($data['classes'])) {
|
||||
$class_names_to_display = $data['classes'];
|
||||
if ($data['is_elective']) {
|
||||
$processed_classes = [];
|
||||
foreach ($data['classes'] as $class_name) {
|
||||
// Extracts "Grade X" from "Grade X Y"
|
||||
$parts = explode(' ', $class_name);
|
||||
if (count($parts) > 2) {
|
||||
$processed_classes[] = $parts[0] . ' ' . $parts[1];
|
||||
} else {
|
||||
$processed_classes[] = $class_name;
|
||||
}
|
||||
}
|
||||
$class_names_to_display = array_unique($processed_classes);
|
||||
}
|
||||
echo '<small>' . htmlspecialchars(implode(', ', $class_names_to_display)) . '</small>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
<?php
|
||||
}
|
||||
$last_timeslot_end_time = end($non_break_periods)['end_time'];
|
||||
$final_break_html = '';
|
||||
foreach ($timeslots as $ts) {
|
||||
if ($ts['is_break'] && $ts['start_time'] >= $last_timeslot_end_time) {
|
||||
$final_break_html .= '<tr>';
|
||||
$final_break_html .= '<td><strong>' . htmlspecialchars($ts['name']) . '</strong><br><small class="text-muted">' . date("g:i A", strtotime($ts['start_time'])) . ' - ' . date("g:i A", strtotime($ts['end_time'])) . '</small></td>';
|
||||
$final_break_html .= '<td colspan="' . count($days_of_week) . '" class="text-center table-secondary"><strong>Break</strong></td>';
|
||||
$final_break_html .= '</tr>';
|
||||
}
|
||||
}
|
||||
echo $final_break_html;
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php elseif ($selected_teacher_id): ?>
|
||||
<div class="alert alert-info">No lessons are scheduled for you at the moment.</div>
|
||||
<?php else: ?>
|
||||
<?php if ($role === 'admin'): ?>
|
||||
<div class="alert alert-info">Please select a teacher to view their timetable.</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
<?php if ($selected_teacher_id): ?>
|
||||
const { jsPDF } = window.jspdf;
|
||||
|
||||
document.getElementById('print-btn').addEventListener('click', function () {
|
||||
window.print();
|
||||
});
|
||||
|
||||
document.getElementById('download-btn').addEventListener('click', function () {
|
||||
const element = document.getElementById('timetable-container');
|
||||
const teacherName = "<?php echo htmlspecialchars($selected_teacher_name, ENT_QUOTES, 'UTF-8'); ?>";
|
||||
const fileName = `timetable-${teacherName.replace(/\s+/g, '-').toLowerCase()}.pdf`;
|
||||
|
||||
html2canvas(element, { scale: 2 }).then(canvas => {
|
||||
const imgData = canvas.toDataURL('image/png');
|
||||
const doc = new jsPDF({
|
||||
orientation: 'l',
|
||||
unit: 'pt',
|
||||
format: 'a4'
|
||||
});
|
||||
const imgWidth = doc.internal.pageSize.getWidth() - 40;
|
||||
const imgHeight = canvas.height * imgWidth / canvas.width;
|
||||
doc.addImage(imgData, 'PNG', 20, 20, imgWidth, imgHeight);
|
||||
doc.save(fileName);
|
||||
});
|
||||
});
|
||||
<?php endif; ?>
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
204
teacher_workload.php
Normal file
204
teacher_workload.php
Normal file
@ -0,0 +1,204 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/auth_check_workload.php';
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
$message = '';
|
||||
$error = '';
|
||||
$pdo = db();
|
||||
$edit_workload = null;
|
||||
$school_id = $_SESSION['school_id'];
|
||||
$user_id = $_SESSION['user_id'];
|
||||
|
||||
// Get teacher_id from user_id
|
||||
$stmt = $pdo->prepare("SELECT id FROM teachers WHERE user_id = ?");
|
||||
$stmt->execute([$user_id]);
|
||||
$teacher = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$teacher_id = $teacher['id'];
|
||||
|
||||
// Handle Delete request
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_id'])) {
|
||||
try {
|
||||
$stmt = $pdo->prepare("DELETE FROM workloads WHERE id = ? AND teacher_id = ? AND school_id = ?");
|
||||
if ($stmt->execute([$_POST['delete_id'], $teacher_id, $school_id])) {
|
||||
$message = 'Workload deleted successfully!';
|
||||
} else {
|
||||
$error = 'Failed to delete workload.';
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
// Handle POST request to add or update a workload
|
||||
elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$workload_id = $_POST['workload_id'] ?? null;
|
||||
$class_id = $_POST['class_id'] ?? null;
|
||||
$subject_id = $_POST['subject_id'] ?? null;
|
||||
$lessons_per_week = $_POST['lessons_per_week'] ?? null;
|
||||
|
||||
if (empty($class_id) || empty($subject_id) || empty($lessons_per_week)) {
|
||||
$error = 'All fields are required.';
|
||||
} elseif (!filter_var($lessons_per_week, FILTER_VALIDATE_INT, ["options" => ["min_range" => 1]])) {
|
||||
$error = 'Lessons per week must be a positive number.';
|
||||
} else {
|
||||
try {
|
||||
if ($workload_id) { // Update
|
||||
$stmt = $pdo->prepare("UPDATE workloads SET class_id = ?, subject_id = ?, lessons_per_week = ? WHERE id = ? AND teacher_id = ? AND school_id = ?");
|
||||
if ($stmt->execute([$class_id, $subject_id, $lessons_per_week, $workload_id, $teacher_id, $school_id])) {
|
||||
$message = 'Workload updated successfully!';
|
||||
} else {
|
||||
$error = 'Failed to update workload.';
|
||||
}
|
||||
} else { // Insert
|
||||
$stmt = $pdo->prepare("INSERT INTO workloads (class_id, subject_id, teacher_id, lessons_per_week, school_id) VALUES (?, ?, ?, ?, ?)");
|
||||
if ($stmt->execute([$class_id, $subject_id, $teacher_id, $lessons_per_week, $school_id])) {
|
||||
$message = 'Workload created successfully!';
|
||||
} else {
|
||||
$error = 'Failed to create workload.';
|
||||
}
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
if ($e->errorInfo[1] == 1062) {
|
||||
$error = 'Error: This workload assignment (Class, Subject) already exists for you.';
|
||||
} else {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Edit request (fetch workload to edit)
|
||||
if (isset($_GET['edit_id'])) {
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT * FROM workloads WHERE id = ? AND teacher_id = ? AND school_id = ?");
|
||||
$stmt->execute([$_GET['edit_id'], $teacher_id, $school_id]);
|
||||
$edit_workload = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch related data for dropdowns
|
||||
try {
|
||||
$classes_stmt = $pdo->prepare("SELECT id, name FROM classes WHERE school_id = ? ORDER BY name");
|
||||
$classes_stmt->execute([$school_id]);
|
||||
$classes = $classes_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$subjects_stmt = $pdo->prepare("SELECT id, name FROM subjects WHERE school_id = ? ORDER BY name");
|
||||
$subjects_stmt->execute([$school_id]);
|
||||
$subjects = $subjects_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error while fetching data: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
// Fetch all workloads for this teacher to display
|
||||
$workloads = [];
|
||||
try {
|
||||
$workloads_stmt = $pdo->prepare("
|
||||
SELECT w.id, c.name as class_name, s.name as subject_name, w.lessons_per_week, w.class_id, w.subject_id
|
||||
FROM workloads w
|
||||
JOIN classes c ON w.class_id = c.id
|
||||
JOIN subjects s ON w.subject_id = s.id
|
||||
WHERE w.teacher_id = ? AND w.school_id = ?
|
||||
ORDER BY c.name, s.name
|
||||
");
|
||||
$workloads_stmt->execute([$teacher_id, $school_id]);
|
||||
$workloads = $workloads_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
$error = 'Database error while fetching workloads: ' . $e->getMessage();
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>My Workload - Haki Schedule</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.10.3/font/bootstrap-icons.min.css">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<?php require_once __DIR__ . '/includes/navbar.php'; ?>
|
||||
|
||||
<main class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
<h1 class="h2 fw-bold mb-4">My Workload</h1>
|
||||
|
||||
<?php if ($message): ?><div class="alert alert-success"><?php echo $message; ?></div><?php endif; ?>
|
||||
<?php if ($error): ?><div class="alert alert-danger"><?php echo $error; ?></div><?php endif; ?>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><?php echo $edit_workload ? 'Edit Workload' : 'Add New Workload'; ?></h5>
|
||||
<form action="teacher_workload.php" method="POST">
|
||||
<input type="hidden" name="workload_id" value="<?php echo $edit_workload['id'] ?? ''; ?>">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="class_id" class="form-label">Class</label>
|
||||
<select class="form-select" id="class_id" name="class_id" required>
|
||||
<?php foreach ($classes as $class): ?>
|
||||
<option value="<?php echo $class['id']; ?>" <?php echo (isset($edit_workload) && $edit_workload['class_id'] == $class['id']) ? 'selected' : ''; ?>><?php echo htmlspecialchars($class['name']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="subject_id" class="form-label">Subject</label>
|
||||
<select class="form-select" id="subject_id" name="subject_id" required>
|
||||
<?php foreach ($subjects as $subject): ?>
|
||||
<option value="<?php echo $subject['id']; ?>" <?php echo (isset($edit_workload) && $edit_workload['subject_id'] == $subject['id']) ? 'selected' : ''; ?>><?php echo htmlspecialchars($subject['name']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="lessons_per_week" class="form-label">Lessons Per Week</label>
|
||||
<input type="number" class="form-control" id="lessons_per_week" name="lessons_per_week" min="1" value="<?php echo $edit_workload['lessons_per_week'] ?? ''; ?>" required>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><?php echo $edit_workload ? 'Update Workload' : 'Add Workload'; ?></button>
|
||||
<?php if ($edit_workload): ?>
|
||||
<a href="teacher_workload.php" class="btn btn-secondary">Cancel Edit</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">My Existing Workloads</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead><tr><th>Class</th><th>Subject</th><th class="text-center">Lessons/Week</th><th>Actions</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($workloads as $workload): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($workload['class_name']); ?></td>
|
||||
<td><?php echo htmlspecialchars($workload['subject_name']); ?></td>
|
||||
<td class="text-center"><?php echo htmlspecialchars($workload['lessons_per_week']); ?></td>
|
||||
<td>
|
||||
<a href="?edit_id=<?php echo $workload['id']; ?>" class="btn btn-sm btn-outline-primary"><i class="bi bi-pencil-fill"></i></a>
|
||||
<form action="teacher_workload.php" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this workload?');">
|
||||
<input type="hidden" name="delete_id" value="<?php echo $workload['id']; ?>">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger"><i class="bi bi-trash-fill"></i></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="bg-dark text-white py-4 mt-5"><div class="container text-center"><p>© <?php echo date("Y"); ?> Haki Schedule. All Rights Reserved.</p></div></footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
1
test_file.txt
Normal file
1
test_file.txt
Normal file
@ -0,0 +1 @@
|
||||
This is a temporary file to test the file system.
|
||||
722
timetable.php
Normal file
722
timetable.php
Normal file
@ -0,0 +1,722 @@
|
||||
<?php
|
||||
session_start();
|
||||
require_once 'includes/auth_check.php';
|
||||
require_once 'db/config.php';
|
||||
|
||||
// --- Database Fetch ---
|
||||
function get_all_data($pdo) {
|
||||
$data = [];
|
||||
$data['classes'] = $pdo->query("SELECT * FROM classes ORDER BY name")->fetchAll(PDO::FETCH_ASSOC);
|
||||
$data['subjects'] = $pdo->query("SELECT id, name FROM subjects")->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
$data['teachers'] = $pdo->query("SELECT id, name FROM teachers")->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||
$data['timeslots'] = $pdo->query("SELECT * FROM timeslots ORDER BY start_time")->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$workloads_stmt = $pdo->query("
|
||||
SELECT
|
||||
w.class_id, w.subject_id, w.teacher_id, w.lessons_per_week,
|
||||
s.name as subject_name, s.has_double_lesson, s.elective_group_id,
|
||||
c.name as class_name,
|
||||
t.name as teacher_name,
|
||||
eg.name as elective_group_name
|
||||
FROM workloads w
|
||||
JOIN subjects s ON w.subject_id = s.id
|
||||
JOIN classes c ON w.class_id = c.id
|
||||
JOIN teachers t ON w.teacher_id = t.id
|
||||
LEFT JOIN elective_groups eg ON s.elective_group_id = eg.id
|
||||
");
|
||||
$data['workloads'] = $workloads_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
// --- Main Scheduling Engine ---
|
||||
function generate_timetable($data, $days_of_week) {
|
||||
|
||||
$periods = array_values(array_filter($data['timeslots'], function($ts) { return !$ts['is_break']; }));
|
||||
$periods_per_day = count($periods);
|
||||
|
||||
|
||||
// 1. Initialize Timetables
|
||||
$class_timetables = [];
|
||||
$teacher_timetables = [];
|
||||
foreach ($data['classes'] as $class) {
|
||||
$class_timetables[$class['id']] = array_fill(0, count($days_of_week), array_fill(0, $periods_per_day, null));
|
||||
}
|
||||
foreach ($data['teachers'] as $teacher_id => $teacher_name) {
|
||||
$teacher_timetables[$teacher_id] = array_fill(0, count($days_of_week), array_fill(0, $periods_per_day, null));
|
||||
}
|
||||
|
||||
|
||||
// 2. Prepare Lessons
|
||||
$lessons_to_schedule = [];
|
||||
$electives_by_group_grade = [];
|
||||
|
||||
// First, group elective workloads
|
||||
foreach ($data['workloads'] as $workload) {
|
||||
if (!empty($workload['elective_group_id'])) {
|
||||
$grade = preg_replace('/[^0-9]/', '', $workload['class_name']);
|
||||
$key = $workload['elective_group_id'] . '_grade_' . $grade;
|
||||
if (!isset($electives_by_group_grade[$key])) {
|
||||
$electives_by_group_grade[$key] = [
|
||||
'type' => 'elective_group',
|
||||
'display_name' => $workload['elective_group_name'],
|
||||
'lessons_per_week' => $workload['lessons_per_week'],
|
||||
'has_double_lesson' => $workload['has_double_lesson'],
|
||||
'is_elective' => true,
|
||||
'component_lessons' => []
|
||||
];
|
||||
}
|
||||
$electives_by_group_grade[$key]['component_lessons'][] = $workload;
|
||||
}
|
||||
}
|
||||
|
||||
// Process regular lessons
|
||||
foreach ($data['workloads'] as $workload) {
|
||||
if (empty($workload['elective_group_id'])) { // Regular lesson
|
||||
$lessons_to_create = (int)$workload['lessons_per_week'];
|
||||
$has_double = (bool)$workload['has_double_lesson'];
|
||||
$num_singles = $lessons_to_create;
|
||||
|
||||
if ($has_double && $lessons_to_create >= 2) {
|
||||
// Create one double lesson
|
||||
$lessons_to_schedule[] = [
|
||||
'type' => 'single', 'class_id' => $workload['class_id'], 'subject_id' => $workload['subject_id'],
|
||||
'teacher_ids' => [$workload['teacher_id']], 'display_name' => $workload['subject_name'],
|
||||
'teacher_name' => $workload['teacher_name'], 'is_double' => true, 'is_elective' => false
|
||||
];
|
||||
// The rest are singles
|
||||
$num_singles = $lessons_to_create - 2;
|
||||
}
|
||||
|
||||
// Create the single lessons
|
||||
for ($i = 0; $i < $num_singles; $i++) {
|
||||
$lessons_to_schedule[] = [
|
||||
'type' => 'single', 'class_id' => $workload['class_id'], 'subject_id' => $workload['subject_id'],
|
||||
'teacher_ids' => [$workload['teacher_id']], 'display_name' => $workload['subject_name'],
|
||||
'teacher_name' => $workload['teacher_name'], 'is_double' => false, 'is_elective' => false
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process elective groups
|
||||
foreach ($electives_by_group_grade as $group) {
|
||||
$lessons_to_create = (int)$group['lessons_per_week'];
|
||||
$has_double = (bool)$group['has_double_lesson'];
|
||||
$class_ids = array_unique(array_column($group['component_lessons'], 'class_id'));
|
||||
$teacher_ids = array_unique(array_column($group['component_lessons'], 'teacher_id'));
|
||||
$num_singles = $lessons_to_create;
|
||||
|
||||
if ($has_double && $lessons_to_create >= 2) {
|
||||
// Create one double lesson
|
||||
$lessons_to_schedule[] = [
|
||||
'type' => 'elective_group', 'class_id' => $class_ids, 'subject_id' => null,
|
||||
'teacher_ids' => $teacher_ids, 'display_name' => $group['display_name'],
|
||||
'is_double' => true, 'is_elective' => true, 'component_lessons' => $group['component_lessons']
|
||||
];
|
||||
// The rest are singles
|
||||
$num_singles = $lessons_to_create - 2;
|
||||
}
|
||||
|
||||
// Create the single lessons
|
||||
for ($i = 0; $i < $num_singles; $i++) {
|
||||
$lessons_to_schedule[] = [
|
||||
'type' => 'elective_group', 'class_id' => $class_ids, 'subject_id' => null,
|
||||
'teacher_ids' => $teacher_ids, 'display_name' => $group['display_name'],
|
||||
'is_double' => false, 'is_elective' => true, 'component_lessons' => $group['component_lessons']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 3. Shuffle and then sort lessons (place doubles and electives first)
|
||||
shuffle($lessons_to_schedule);
|
||||
usort($lessons_to_schedule, function($a, $b) {
|
||||
if ($b['is_double'] != $a['is_double']) return $b['is_double'] <=> $a['is_double'];
|
||||
$a_count = is_array($a['class_id']) ? count($a['class_id']) : 1;
|
||||
$b_count = is_array($b['class_id']) ? count($b['class_id']) : 1;
|
||||
return $b_count <=> $a_count;
|
||||
});
|
||||
|
||||
// 4. Placement
|
||||
$lessons_placed = 0;
|
||||
$lessons_failed = 0;
|
||||
$period_popularity = array_fill(0, $periods_per_day, 0);
|
||||
foreach ($lessons_to_schedule as $index => $lesson) {
|
||||
$lesson_label = $lesson['display_name'] . (is_array($lesson['class_id']) ? ' for ' . count($lesson['class_id']) . ' classes' : ' for class ' . $lesson['class_id']);
|
||||
|
||||
|
||||
$best_slot = find_best_slot_for_lesson($lesson, $class_timetables, $teacher_timetables, $days_of_week, $periods_per_day, $data['workloads'], $data['timeslots'], $period_popularity);
|
||||
|
||||
if ($best_slot) {
|
||||
$lessons_placed++;
|
||||
$day = $best_slot['day'];
|
||||
$period = $best_slot['period'];
|
||||
$period_popularity[$period]++;
|
||||
if ($lesson['is_double']) {
|
||||
$period_popularity[$period + 1]++;
|
||||
}
|
||||
|
||||
|
||||
if ($lesson['type'] === 'single') {
|
||||
$class_id = $lesson['class_id'];
|
||||
$teacher_id = $lesson['teacher_ids'][0];
|
||||
|
||||
$lesson_data = [
|
||||
'subject_name' => $lesson['display_name'],
|
||||
'teacher_name' => $lesson['teacher_name'],
|
||||
'subject_id' => $lesson['subject_id'],
|
||||
'teacher_ids' => $lesson['teacher_ids'],
|
||||
'is_double' => $lesson['is_double'],
|
||||
'is_elective' => false,
|
||||
];
|
||||
|
||||
$class_timetables[$class_id][$day][$period] = $lesson_data;
|
||||
$teacher_timetables[$teacher_id][$day][$period] = true;
|
||||
if ($lesson['is_double']) {
|
||||
$class_timetables[$class_id][$day][$period + 1] = $lesson_data;
|
||||
$teacher_timetables[$teacher_id][$day][$period + 1] = true;
|
||||
}
|
||||
} else { // Elective Group
|
||||
// Mark all teachers in the group as busy for this slot
|
||||
foreach ($lesson['teacher_ids'] as $teacher_id) {
|
||||
$teacher_timetables[$teacher_id][$day][$period] = true;
|
||||
if ($lesson['is_double']) {
|
||||
$teacher_timetables[$teacher_id][$day][$period + 1] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Populate the class-specific data for display
|
||||
foreach($lesson['component_lessons'] as $comp_lesson) {
|
||||
$class_id = $comp_lesson['class_id'];
|
||||
$teacher_id = $comp_lesson['teacher_id'];
|
||||
|
||||
$lesson_data = [
|
||||
'subject_name' => $comp_lesson['subject_name'],
|
||||
'teacher_name' => $comp_lesson['teacher_name'],
|
||||
'subject_id' => $comp_lesson['subject_id'],
|
||||
'teacher_ids' => $lesson['teacher_ids'], // All teachers in the elective group
|
||||
'is_double' => $lesson['is_double'],
|
||||
'is_elective' => true,
|
||||
'group_name' => $lesson['display_name']
|
||||
];
|
||||
|
||||
$class_timetables[$class_id][$day][$period] = $lesson_data;
|
||||
if ($lesson['is_double']) {
|
||||
$class_timetables[$class_id][$day][$period + 1] = $lesson_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$lessons_failed++;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $class_timetables;
|
||||
}
|
||||
|
||||
function find_best_slot_for_lesson($lesson, &$class_timetables, &$teacher_timetables, $days_of_week, $periods_per_day, $workloads, $all_timeslots, $period_popularity) {
|
||||
$best_slot = null;
|
||||
$best_score = -1;
|
||||
|
||||
$is_double = $lesson['is_double'];
|
||||
$class_ids = is_array($lesson['class_id']) ? $lesson['class_id'] : [$lesson['class_id']];
|
||||
$teacher_ids = $lesson['teacher_ids'];
|
||||
$subject_id = $lesson['subject_id'];
|
||||
|
||||
// Rule 1: Get total lessons per week for this subject to check if we can repeat on the same day
|
||||
$lessons_per_week_for_subject = 0;
|
||||
if ($lesson['type'] === 'single') {
|
||||
foreach ($workloads as $w) {
|
||||
if ($w['class_id'] == $class_ids[0] && $w['subject_id'] == $subject_id) {
|
||||
$lessons_per_week_for_subject = $w['lessons_per_week'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else { // Elective group
|
||||
$lessons_per_week_for_subject = $lesson['component_lessons'][0]['lessons_per_week'];
|
||||
}
|
||||
|
||||
for ($day = 0; $day < count($days_of_week); $day++) {
|
||||
for ($period = 0; $period < $periods_per_day; $period++) {
|
||||
// Basic availability check
|
||||
if ($is_double && $period + 1 >= $periods_per_day) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prevent placing double lessons across breaks
|
||||
if ($is_double) {
|
||||
$non_break_periods = array_values(array_filter($all_timeslots, function($ts) { return !$ts['is_break']; }));
|
||||
$first_period_timeslot = $non_break_periods[$period];
|
||||
$second_period_timeslot = $non_break_periods[$period + 1];
|
||||
|
||||
$first_original_index = -1;
|
||||
$second_original_index = -1;
|
||||
$all_timeslots_values = array_values($all_timeslots);
|
||||
foreach ($all_timeslots_values as $index => $ts) {
|
||||
if ($ts['id'] == $first_period_timeslot['id']) $first_original_index = $index;
|
||||
if ($ts['id'] == $second_period_timeslot['id']) $second_original_index = $index;
|
||||
}
|
||||
|
||||
if ($first_original_index === -1 || $second_original_index === -1 || $second_original_index !== $first_original_index + 1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$slot_available = true;
|
||||
foreach ($class_ids as $cid) {
|
||||
if (!isset($class_timetables[$cid])) {
|
||||
$slot_available = false;
|
||||
break;
|
||||
}
|
||||
if ($class_timetables[$cid][$day][$period] !== null) {
|
||||
$slot_available = false;
|
||||
break;
|
||||
}
|
||||
if ($is_double && $class_timetables[$cid][$day][$period + 1] !== null) {
|
||||
$slot_available = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$slot_available) continue;
|
||||
|
||||
foreach ($teacher_ids as $tid) {
|
||||
if (!isset($teacher_timetables[$tid])) {
|
||||
$slot_available = false;
|
||||
break;
|
||||
}
|
||||
if ($teacher_timetables[$tid][$day][$period] !== null) {
|
||||
$slot_available = false;
|
||||
break;
|
||||
}
|
||||
if ($is_double && isset($teacher_timetables[$tid][$day][$period + 1]) && $teacher_timetables[$tid][$day][$period + 1] !== null) {
|
||||
$slot_available = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($slot_available) {
|
||||
$current_score = 100;
|
||||
|
||||
// Rule 1: Penalize placing the same subject on the same day for a class
|
||||
$subject_on_day_count = 0;
|
||||
foreach ($class_ids as $cid) {
|
||||
for ($p = 0; $p < $periods_per_day; $p++) {
|
||||
$existing_lesson = $class_timetables[$cid][$day][$p];
|
||||
if ($existing_lesson !== null) {
|
||||
if ($lesson['type'] === 'single' && isset($existing_lesson['subject_id']) && $existing_lesson['subject_id'] == $subject_id) {
|
||||
$subject_on_day_count++;
|
||||
}
|
||||
if ($lesson['type'] === 'elective_group' && isset($existing_lesson['group_name']) && $existing_lesson['group_name'] == $lesson['display_name']) {
|
||||
$subject_on_day_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($lessons_per_week_for_subject <= count($days_of_week)) {
|
||||
if ($subject_on_day_count > 0) {
|
||||
$current_score -= 50;
|
||||
}
|
||||
} else {
|
||||
if ($subject_on_day_count > 1) {
|
||||
$current_score -= 25;
|
||||
}
|
||||
}
|
||||
|
||||
// Rule 2: Penalize days that are already "full" for the class
|
||||
$lessons_on_day = 0;
|
||||
foreach ($class_ids as $cid) {
|
||||
$lessons_on_day += count(array_filter($class_timetables[$cid][$day]));
|
||||
}
|
||||
$day_fullness_penalty = $lessons_on_day * 2; // Reduced penalty
|
||||
$current_score -= $day_fullness_penalty;
|
||||
|
||||
// Rule 3: Penalize consecutive lessons for a teacher
|
||||
foreach ($teacher_ids as $tid) {
|
||||
if ($period > 0 && $teacher_timetables[$tid][$day][$period - 1] !== null) {
|
||||
$current_score -= 15;
|
||||
}
|
||||
if (!$is_double && $period < $periods_per_day - 1 && $teacher_timetables[$tid][$day][$period + 1] !== null) {
|
||||
$current_score -= 15;
|
||||
}
|
||||
}
|
||||
|
||||
// Rule 4: Penalize popular timeslots
|
||||
$popularity_penalty = $period_popularity[$period] * 5;
|
||||
$current_score -= $popularity_penalty;
|
||||
|
||||
if ($current_score > $best_score) {
|
||||
$best_score = $current_score;
|
||||
$best_slot = ['day' => $day, 'period' => $period];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $best_slot;
|
||||
}
|
||||
|
||||
|
||||
// --- Timetable Persistence ---
|
||||
function save_timetable($pdo, $class_timetables, $timeslots) {
|
||||
if (empty($class_timetables)) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
|
||||
// It's better to delete from the child table first to avoid foreign key issues.
|
||||
$pdo->exec('DELETE FROM schedule_teachers');
|
||||
|
||||
|
||||
$pdo->exec('DELETE FROM schedules');
|
||||
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
'INSERT INTO schedules (class_id, day_of_week, timeslot_id, subject_id, lesson_display_name, teacher_display_name, is_double, is_elective) ' .
|
||||
'VALUES (:class_id, :day_of_week, :timeslot_id, :subject_id, :lesson_display_name, :teacher_display_name, :is_double, :is_elective)'
|
||||
);
|
||||
$teacher_stmt = $pdo->prepare(
|
||||
'INSERT INTO schedule_teachers (schedule_id, teacher_id) VALUES (:schedule_id, :teacher_id)'
|
||||
);
|
||||
|
||||
$lesson_periods = array_values(array_filter($timeslots, function($ts) { return !$ts['is_break']; }));
|
||||
|
||||
foreach ($class_timetables as $class_id => $day_schedule) {
|
||||
foreach ($day_schedule as $day_idx => $period_schedule) {
|
||||
$processed_periods = [];
|
||||
foreach ($period_schedule as $period_idx => $lesson) {
|
||||
if ($lesson && !in_array($period_idx, $processed_periods)) {
|
||||
if (!isset($lesson_periods[$period_idx]['id'])) {
|
||||
|
||||
continue;
|
||||
}
|
||||
$timeslot_id = $lesson_periods[$period_idx]['id'];
|
||||
|
||||
$display_name = !empty($lesson['is_elective']) ? ($lesson['group_name'] . ' / ' . $lesson['subject_name']) : $lesson['subject_name'];
|
||||
|
||||
$stmt->execute([
|
||||
':class_id' => $class_id,
|
||||
':day_of_week' => $day_idx,
|
||||
':timeslot_id' => $timeslot_id,
|
||||
':subject_id' => $lesson['subject_id'],
|
||||
':lesson_display_name' => $display_name,
|
||||
':teacher_display_name' => $lesson['teacher_name'],
|
||||
':is_double' => (int)$lesson['is_double'],
|
||||
':is_elective' => (int)$lesson['is_elective']
|
||||
]);
|
||||
|
||||
$schedule_id = $pdo->lastInsertId();
|
||||
|
||||
if (!empty($lesson['teacher_ids'])) {
|
||||
foreach ($lesson['teacher_ids'] as $teacher_id) {
|
||||
$teacher_stmt->execute([':schedule_id' => $schedule_id, ':teacher_id' => $teacher_id]);
|
||||
}
|
||||
}
|
||||
|
||||
$processed_periods[] = $period_idx;
|
||||
if ($lesson['is_double']) {
|
||||
$processed_periods[] = $period_idx + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->commit();
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
// Re-throw the exception to see the error on the screen if display_errors is on
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
function get_timetable_from_db($pdo, $classes, $timeslots, $days_of_week) {
|
||||
$stmt = $pdo->query('SELECT * FROM schedules ORDER BY id');
|
||||
$saved_lessons = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($saved_lessons)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$periods = array_values(array_filter($timeslots, function($ts) { return !$ts['is_break']; }));
|
||||
$periods_per_day = count($periods);
|
||||
|
||||
$class_timetables = [];
|
||||
foreach ($classes as $class) {
|
||||
$class_timetables[$class['id']] = array_fill(0, count($days_of_week), array_fill(0, $periods_per_day, null));
|
||||
}
|
||||
|
||||
$timeslot_id_to_period_idx = [];
|
||||
foreach($periods as $idx => $period) {
|
||||
$timeslot_id_to_period_idx[$period['id']] = $idx;
|
||||
}
|
||||
|
||||
foreach ($saved_lessons as $lesson) {
|
||||
$class_id = $lesson['class_id'];
|
||||
$day_idx = $lesson['day_of_week'];
|
||||
$timeslot_id = $lesson['timeslot_id'];
|
||||
|
||||
if ($day_idx >= count($days_of_week)) {
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($timeslot_id_to_period_idx[$timeslot_id])) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($class_timetables[$class_id])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$period_idx = $timeslot_id_to_period_idx[$timeslot_id];
|
||||
|
||||
$lesson_data = [
|
||||
'id' => $lesson['id'],
|
||||
'subject_name' => $lesson['lesson_display_name'],
|
||||
'teacher_name' => $lesson['teacher_display_name'],
|
||||
'is_double' => (bool)$lesson['is_double'],
|
||||
'is_elective' => (bool)$lesson['is_elective'],
|
||||
];
|
||||
|
||||
$class_timetables[$class_id][$day_idx][$period_idx] = $lesson_data;
|
||||
if ($lesson_data['is_double'] && isset($class_timetables[$class_id][$day_idx][$period_idx + 1])) {
|
||||
$class_timetables[$class_id][$day_idx][$period_idx + 1] = $lesson_data;
|
||||
}
|
||||
}
|
||||
|
||||
return $class_timetables;
|
||||
}
|
||||
|
||||
// --- Main Logic ---
|
||||
$pdoconn = db();
|
||||
$all_data = get_all_data($pdoconn);
|
||||
$classes = $all_data['classes'];
|
||||
$timeslots = $all_data['timeslots'];
|
||||
$workloads = $all_data['workloads'];
|
||||
|
||||
// Fetch working days from school settings and apply a strict filter
|
||||
$school_id = $_SESSION['school_id'] ?? null;
|
||||
$days_of_week = [];
|
||||
$allowed_days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];
|
||||
|
||||
if ($school_id) {
|
||||
$stmt = $pdoconn->prepare("SELECT working_days FROM schools WHERE id = ?");
|
||||
$stmt->execute([$school_id]);
|
||||
$working_days_str = $stmt->fetchColumn();
|
||||
if ($working_days_str) {
|
||||
$days_from_db = array_map('trim', explode(',', $working_days_str));
|
||||
// Intersect with the allowed days to filter out anything else
|
||||
$days_of_week = array_intersect($days_from_db, $allowed_days);
|
||||
}
|
||||
}
|
||||
|
||||
// If after all that, the list is empty, fall back to the default
|
||||
if (empty($days_of_week)) {
|
||||
$days_of_week = $allowed_days;
|
||||
}
|
||||
// Ensure the array is numerically indexed from 0
|
||||
$days_of_week = array_values($days_of_week);
|
||||
|
||||
$class_timetables = [];
|
||||
|
||||
if (isset($_POST['generate'])) {
|
||||
if (!empty($workloads)) {
|
||||
$class_timetables = generate_timetable($all_data, $days_of_week);
|
||||
save_timetable($pdoconn, $class_timetables, $timeslots);
|
||||
// Redirect to the same page to prevent form resubmission and show the new timetable
|
||||
header("Location: timetable.php");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Always fetch the latest timetable from the database for display
|
||||
$class_timetables = get_timetable_from_db($pdoconn, $classes, $timeslots, $days_of_week);
|
||||
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Timetable - Haki Schedule</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||
<link rel="stylesheet" href="assets/css/print.css?v=<?php echo time(); ?>" media="print">
|
||||
<style>
|
||||
.lesson { min-height: 60px; }
|
||||
.lesson.is-elective { background-color: #e0f7fa; border-left: 3px solid #00bcd4; }
|
||||
.lesson.is-double { background-color: #fce4ec; }
|
||||
.table-bordered th, .table-bordered td { vertical-align: middle; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<?php include 'includes/navbar.php'; ?>
|
||||
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4 px-3 no-print">
|
||||
<h1>Class Timetable</h1>
|
||||
<div class="d-flex gap-2">
|
||||
<?php if (isset($_SESSION['role']) && $_SESSION['role'] === 'admin') : ?>
|
||||
<form method="POST" action="">
|
||||
<button type="submit" name="generate" class="btn btn-primary" <?php echo empty($workloads) ? 'disabled' : ''; ?>>Generate Timetable</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<button id="print-btn" class="btn btn-secondary">Print</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="timetables-container" class="timetable-container">
|
||||
<?php if (empty($class_timetables)) : ?>
|
||||
<div class="alert alert-info mx-3">
|
||||
<?php if (empty($workloads)) : ?>
|
||||
No workloads found. Please add classes, subjects, teachers, and workloads in the "Manage" section first. The "Generate Timetable" button is disabled.
|
||||
<?php else : ?>
|
||||
Click the "Generate Timetable" button to create a schedule.
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<?php
|
||||
// This map is crucial for finding the correct lesson in the data array.
|
||||
$lesson_periods = array_values(array_filter($timeslots, function($ts) { return !$ts['is_break']; }));
|
||||
$timeslot_id_to_period_idx = [];
|
||||
foreach($lesson_periods as $idx => $period) {
|
||||
$timeslot_id_to_period_idx[$period['id']] = $idx;
|
||||
}
|
||||
?>
|
||||
<?php foreach ($classes as $class) : ?>
|
||||
<?php
|
||||
// Skip rendering a table if there's no schedule for this class
|
||||
if (!isset($class_timetables[$class['id']]) || empty(array_filter(array_merge(...$class_timetables[$class['id']])))) {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<div class="timetable-wrapper mb-5">
|
||||
<h3 class="mt-5 px-3"><?php echo htmlspecialchars($class['name']); ?></h3>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<table class="table table-bordered text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 12%;">Time</th>
|
||||
<?php foreach ($days_of_week as $day) : ?>
|
||||
<th><?php echo $day; ?></th>
|
||||
<?php endforeach; ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
// This array will track which cells to skip due to a rowspan from a double lesson
|
||||
$skip_cells = [];
|
||||
|
||||
foreach ($timeslots as $timeslot_index => $timeslot) :
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<strong><?php echo htmlspecialchars($timeslot['name']); ?></strong><br>
|
||||
<small class="text-muted"><?php echo date("g:i A", strtotime($timeslot['start_time'])); ?> - <?php echo date("g:i A", strtotime($timeslot['end_time'])); ?></small>
|
||||
</td>
|
||||
|
||||
<?php if ($timeslot['is_break']) : ?>
|
||||
<td colspan="<?php echo count($days_of_week); ?>" class="text-center table-secondary"><strong>Break</strong></td>
|
||||
<?php else : ?>
|
||||
<?php
|
||||
// Get the correct index for the current non-break period
|
||||
$current_period_idx = $timeslot_id_to_period_idx[$timeslot['id']] ?? -1;
|
||||
|
||||
if ($current_period_idx !== -1) {
|
||||
foreach ($days_of_week as $day_idx => $day_name) {
|
||||
// If the cell is marked to be skipped by a rowspan above, do nothing and continue
|
||||
if (!empty($skip_cells[$day_idx][$current_period_idx])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$lesson = $class_timetables[$class['id']][$day_idx][$current_period_idx] ?? null;
|
||||
$rowspan = 1;
|
||||
|
||||
if ($lesson && !empty($lesson['is_double'])) {
|
||||
// Check if the next timeslot is also not a break
|
||||
$next_timeslot_is_break = isset($timeslots[$timeslot_index + 1]) && $timeslots[$timeslot_index + 1]['is_break'];
|
||||
if (!$next_timeslot_is_break) {
|
||||
$rowspan = 2;
|
||||
// Mark the cell below this one to be skipped in the next iteration
|
||||
$next_period_idx = $current_period_idx + 1;
|
||||
$skip_cells[$day_idx][$next_period_idx] = true;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<td rowspan="<?php echo $rowspan; ?>">
|
||||
<?php if ($lesson) :
|
||||
$css_class = 'lesson p-1 h-100 d-flex flex-column justify-content-center';
|
||||
if (!empty($lesson['is_elective'])) $css_class .= ' is-elective';
|
||||
if (!empty($lesson['is_double'])) $css_class .= ' is-double';
|
||||
?>
|
||||
<div class="<?php echo $css_class; ?>" data-lesson-id="<?php echo $lesson['id'] ?? ''; ?>">
|
||||
<?php
|
||||
$display_subject = $lesson['subject_name'];
|
||||
$display_teacher = $lesson['teacher_name'];
|
||||
|
||||
if (!empty($lesson['is_elective'])) {
|
||||
// For electives, subject_name can be "Group / Subject". We want to show only the group name.
|
||||
$parts = explode(' / ', $lesson['subject_name'], 2);
|
||||
$display_subject = $parts[0]; // The group name
|
||||
$display_teacher = ''; // Don't show any specific teacher in the class view
|
||||
}
|
||||
?>
|
||||
<strong><?php echo htmlspecialchars($display_subject); ?></strong><br>
|
||||
<?php if (!empty($display_teacher)) : ?>
|
||||
<small><?php echo htmlspecialchars($display_teacher); ?></small>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<?php
|
||||
} // end foreach day
|
||||
} // end if period found
|
||||
?>
|
||||
<?php endif; // is_break ?>
|
||||
</tr>
|
||||
<?php endforeach; // timeslots ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; // classes ?>
|
||||
<?php endif; // empty timetables ?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.getElementById('print-btn').addEventListener('click', function () {
|
||||
window.print();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user