406 lines
20 KiB
HTML
406 lines
20 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Today's Schedule - Edu-Space Admin{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid pt-5 mt-4">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h1 class="h3">
|
|
<i class="bi bi-calendar-today text-info"></i> Today's Schedule
|
|
<small class="text-muted ms-2">{{ today.strftime('%B %d, %Y') }}</small>
|
|
</h1>
|
|
<div>
|
|
<a href="{{ url_for('schedule.add_reservation') }}" class="btn btn-success">
|
|
<i class="bi bi-plus-circle"></i> New Reservation
|
|
</a>
|
|
<a href="{{ url_for('schedule.list_reservations') }}" class="btn btn-outline-primary ms-2">
|
|
<i class="bi bi-list"></i> All Reservations
|
|
</a>
|
|
{# Access to Genetic Algorithm Optimizer - Only for admin/teacher roles #}
|
|
{% if current_user and current_user.role in ['admin', 'teacher'] %}
|
|
<a href="{{ url_for('genetic_algorithm.genetic_optimizer') }}" class="btn btn-info text-white ms-2">
|
|
<i class="bi bi-cpu"></i> AI Optimizer
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Stats -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-3 mb-3">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="text-muted mb-2">Total Reservations</h6>
|
|
<h2 class="mb-0">{{ stats.total_reservations }}</h2>
|
|
</div>
|
|
<div class="bg-info bg-opacity-10 rounded-circle p-3">
|
|
<i class="bi bi-calendar3 text-info fs-4"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3 mb-3">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="text-muted mb-2">Confirmed</h6>
|
|
<h2 class="mb-0">{{ stats.confirmed }}</h2>
|
|
</div>
|
|
<div class="bg-success bg-opacity-10 rounded-circle p-3">
|
|
<i class="bi bi-check-circle text-success fs-4"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3 mb-3">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="text-muted mb-2">Pending</h6>
|
|
<h2 class="mb-0">{{ stats.pending }}</h2>
|
|
</div>
|
|
<div class="bg-warning bg-opacity-10 rounded-circle p-3">
|
|
<i class="bi bi-clock text-warning fs-4"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3 mb-3">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h6 class="text-muted mb-2">Active Classrooms</h6>
|
|
<h2 class="mb-0">{{ stats.occupied_classrooms }}/{{ stats.total_classrooms }}</h2>
|
|
</div>
|
|
<div class="bg-primary bg-opacity-10 rounded-circle p-3">
|
|
<i class="bi bi-door-open text-primary fs-4"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Today's Schedule -->
|
|
{% if time_blocks %}
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-header bg-white border-0">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="bi bi-clock-history text-info"></i> Timeline View
|
|
</h5>
|
|
<div>
|
|
<button class="btn btn-sm btn-outline-primary" id="refresh-btn">
|
|
<i class="bi bi-arrow-clockwise"></i> Refresh
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-secondary ms-1" id="toggle-view-btn">
|
|
<i class="bi bi-grid"></i> Grid View
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="timeline-view">
|
|
{% for hour in range(7, 22) %}
|
|
<div class="row border-bottom py-3 align-items-start">
|
|
<div class="col-md-2 col-3">
|
|
<div class="text-center">
|
|
<strong>{{ "%02d:00"|format(hour) }}</strong>
|
|
<br>
|
|
<small class="text-muted">{{ hour+1 }}:00</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-10 col-9">
|
|
{% if time_blocks[hour] %}
|
|
<div class="row">
|
|
{% for item in time_blocks[hour] %}
|
|
<div class="col-md-6 mb-3">
|
|
<div class="card border-0 shadow-sm h-100">
|
|
<div class="card-body p-3">
|
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
<div>
|
|
<h6 class="card-title mb-1">
|
|
{{ item.commission.get_full_code() }}
|
|
</h6>
|
|
<p class="text-muted small mb-2 mb-md-0">
|
|
<i class="bi bi-geo-alt"></i> {{ item.classroom.code }}
|
|
<br class="d-md-none">
|
|
<i class="bi bi-person ms-md-2"></i> {{ item.user.first_name }} {{ item.user.last_name }}
|
|
</p>
|
|
</div>
|
|
<span class="badge bg-{% if item.reservation.status.value == 'CONFIRMED' %}success{% elif item.reservation.status.value == 'PENDING' %}warning{% else %}secondary{% endif %}">
|
|
{{ item.reservation.status.value }}
|
|
</span>
|
|
</div>
|
|
|
|
<div class="row small">
|
|
<div class="col-md-6">
|
|
<i class="bi bi-clock"></i>
|
|
{{ item.reservation.start_time.strftime('%H:%M') }} - {{ item.reservation.end_time.strftime('%H:%M') }}
|
|
</div>
|
|
<div class="col-md-6">
|
|
<i class="bi bi-people"></i>
|
|
{{ item.reservation.expected_attendees }} attendees
|
|
</div>
|
|
</div>
|
|
|
|
{% if item.reservation.purpose %}
|
|
<p class="card-text small text-muted mt-2">
|
|
{{ item.reservation.purpose }}
|
|
</p>
|
|
{% endif %}
|
|
|
|
<div class="mt-2">
|
|
<a href="{{ url_for('schedule.view_reservation', id=item.reservation.id) }}" class="btn btn-sm btn-outline-primary">
|
|
<i class="bi bi-eye"></i> Details
|
|
</a>
|
|
{% if item.reservation.status.value == 'PENDING' %}
|
|
<a href="{{ url_for('schedule.confirm_reservation', id=item.reservation.id) }}"
|
|
class="btn btn-sm btn-outline-success ms-1"
|
|
onclick="return confirm('Confirm this reservation?')">
|
|
<i class="bi bi-check-circle"></i> Confirm
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<div class="text-center text-muted py-3">
|
|
<i class="bi bi-circle"></i> No reservations scheduled
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Grid View (hidden by default) -->
|
|
<div id="grid-view" style="display: none;">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Time</th>
|
|
<th>Classroom</th>
|
|
<th>Subject</th>
|
|
<th>Teacher</th>
|
|
<th>Purpose</th>
|
|
<th>Attendees</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for reservation in reservations %}
|
|
<tr>
|
|
<td>
|
|
{{ reservation.start_time.strftime('%H:%M') }} -
|
|
{{ reservation.end_time.strftime('%H:%M') }}
|
|
</td>
|
|
<td>
|
|
<i class="bi bi-geo-alt"></i>
|
|
{{ reservation.classroom.code }}
|
|
</td>
|
|
<td>{{ reservation.commission.get_full_code() }}</td>
|
|
<td>{{ reservation.user.first_name }} {{ reservation.user.last_name }}</td>
|
|
<td>{{ reservation.purpose }}</td>
|
|
<td>{{ reservation.expected_attendees }}</td>
|
|
<td>
|
|
<span class="badge bg-{% if reservation.status.value == 'CONFIRMED' %}success{% elif reservation.status.value == 'PENDING' %}warning{% else %}secondary{% endif %}">
|
|
{{ reservation.status.value }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<a href="{{ url_for('schedule.view_reservation', id=reservation.id) }}"
|
|
class="btn btn-sm btn-outline-primary">
|
|
<i class="bi bi-eye"></i>
|
|
</a>
|
|
{% if reservation.status.value == 'PENDING' %}
|
|
<a href="{{ url_for('schedule.confirm_reservation', id=reservation.id) }}"
|
|
class="btn btn-sm btn-outline-success ms-1"
|
|
onclick="return confirm('Confirm this reservation?')">
|
|
<i class="bi bi-check-circle"></i>
|
|
</a>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Daily Summary -->
|
|
<div class="row mt-4">
|
|
<div class="col-12">
|
|
<div class="card border-0 shadow-sm">
|
|
<div class="card-header bg-white border-0">
|
|
<h5 class="mb-0">
|
|
<i class="bi bi-graph-up text-warning"></i> Daily Analytics
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<div class="text-center">
|
|
<div class="mb-2">
|
|
<i class="bi bi-door-open-fill text-success" style="font-size: 2rem;"></i>
|
|
</div>
|
|
<h6>Most Active Hours</h6>
|
|
<!-- Calculate peak hours from data -->
|
|
{% set peak_hours = [] %}
|
|
{% for item in reservations %}
|
|
{% set slot = item.start_time.hour %}
|
|
{% if slot not in peak_hours %}
|
|
{% set _ = peak_hours.append(slot) %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
<p class="text-muted">
|
|
{% if peak_hours %}
|
|
{% for hour in peak_hours[:3]|sort %}
|
|
{{ "%02d:00"|format(hour) }}{% if not loop.last %}, {% endif %}
|
|
{% endfor %}
|
|
{% else %}
|
|
No data available
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="text-center">
|
|
<div class="mb-2">
|
|
<i class="bi bi-building text-info" style="font-size: 2rem;"></i>
|
|
</div>
|
|
<h6>Busiest Classrooms</h6>
|
|
<p class="text-muted">
|
|
{% if reservations %}
|
|
{% set classroom_codes = [] %}
|
|
{% for item in reservations %}
|
|
{% if item.classroom.code not in classroom_codes %}
|
|
{% set _ = classroom_codes.append(item.classroom.code) %}
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% for code in classroom_codes[:3] %}
|
|
{{ code }}{% if not loop.last %}, {% endif %}
|
|
{% endfor %}
|
|
{% else %}
|
|
No data available
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="text-center">
|
|
<div class="mb-2">
|
|
<i class="bi bi-clock-fill text-primary" style="font-size: 2rem;"></i>
|
|
</div>
|
|
<h6>Total Hours Booked</h6>
|
|
<p class="text-muted">
|
|
{% set total_hours = 0 %}
|
|
{% for item in reservations %}
|
|
{% set duration = (item.end_time - item.start_time).total_seconds() / 3600 %}
|
|
{% set total_hours = (total_hours + duration) %}
|
|
{% endfor %}
|
|
{{ total_hours|round(1) }} hours total
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% else %}
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="text-center py-5">
|
|
<i class="bi bi-calendar-x text-muted" style="font-size: 4rem;"></i>
|
|
<h4 class="mt-3 text-muted">No reservations scheduled for today</h4>
|
|
<p class="text-muted">
|
|
There are no classroom reservations for {{ today.strftime('%B %d, %Y') }}.
|
|
</p>
|
|
<div class="mt-3">
|
|
<a href="{{ url_for('schedule.add_reservation') }}" class="btn btn-success">
|
|
<i class="bi bi-plus-circle"></i> Schedule First Class
|
|
</a>
|
|
<a href="{{ url_for('schedule.list_reservations') }}" class="btn btn-outline-primary ms-2">
|
|
<i class="bi bi-calendar-week"></i>View Week Schedule
|
|
</a>
|
|
{# Access to Genetic Algorithm Optimizer - Only for admin/teacher roles #}
|
|
{% if current_user and current_user.role in ['admin', 'teacher'] %}
|
|
<a href="{{ url_for('genetic_algorithm.genetic_optimizer') }}" class="btn btn-info text-white ms-2">
|
|
<i class="bi bi-cpu"></i> AI Room Optimizer
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
$(document).ready(function() {
|
|
// Toggle between timeline and grid view
|
|
$('#toggle-view-btn').on('click', function() {
|
|
const timelineView = $('#timeline-view');
|
|
const gridView = $('#grid-view');
|
|
const btn = $(this);
|
|
|
|
if (timelineView.is(':visible')) {
|
|
timelineView.hide();
|
|
gridView.show();
|
|
btn.html('<i class="bi bi-list"></i> Timeline View');
|
|
} else {
|
|
timelineView.show();
|
|
gridView.hide();
|
|
btn.html('<i class="bi bi-grid"></i> Grid View');
|
|
}
|
|
});
|
|
|
|
// Refresh the page
|
|
$('#refresh-btn').on('click', function() {
|
|
location.reload();
|
|
});
|
|
|
|
// Auto-refresh every 5 minutes
|
|
setInterval(function() {
|
|
location.reload();
|
|
}, 300000); // 5 minutes = 300000 milliseconds
|
|
|
|
// Highlight current time
|
|
const currentHour = new Date().getHours();
|
|
if (currentHour >= 7 && currentHour < 22) {
|
|
const timeBlockIndex = currentHour - 7;
|
|
$('.row.border-bottom').eq(timeBlockIndex).addClass('bg-primary bg-opacity-10');
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |