1598 lines
69 KiB
HTML
1598 lines
69 KiB
HTML
{% extends "admin/base_site.html" %}
|
|
{% load static %}
|
|
{% load app_filters %}
|
|
{% block extrahead %}
|
|
{{ block.super }}
|
|
<meta name="csrf-token" content="{{ csrf_token }}">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes">
|
|
<!-- Bootstrap CSS -->
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<!-- Font Awesome -->
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet" />
|
|
<!-- JavaScript Dependencies -->
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" defer></script>
|
|
|
|
<!-- Firebase SDK -->
|
|
<script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-app-compat.js"></script>
|
|
<script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-messaging-compat.js"></script>
|
|
|
|
<!-- Firebase Configuration -->
|
|
<script src="{% static 'js/firebase-config.js' %}"></script>
|
|
|
|
<!-- Firebase Initialization -->
|
|
<script src="{% static 'js/firebase-init.js' %}"></script>
|
|
|
|
<!-- Admin Dashboard JS -->
|
|
<script src="{% static 'js/admin_dashboard.js' %}" defer></script>
|
|
|
|
<script>
|
|
// Initialize Firebase and request notification permission when the page loads
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Call getDeviceToken to initialize Firebase and request notification permission
|
|
if (typeof getDeviceToken === 'function') {
|
|
getDeviceToken().catch(error => {
|
|
console.error('Error initializing Firebase:', error);
|
|
});
|
|
}
|
|
});
|
|
|
|
// Add CSRF token to all AJAX requests
|
|
function getCookie(name) {
|
|
let cookieValue = null;
|
|
if (document.cookie && document.cookie !== '') {
|
|
const cookies = document.cookie.split(';');
|
|
for (let i = 0; i < cookies.length; i++) {
|
|
const cookie = cookies[i].trim();
|
|
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
|
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return cookieValue;
|
|
}
|
|
|
|
// Set up CSRF token for AJAX requests
|
|
const csrftoken = getCookie('csrftoken');
|
|
|
|
// Add CSRF token to all AJAX requests
|
|
$(document).ready(function() {
|
|
$.ajaxSetup({
|
|
beforeSend: function(xhr, settings) {
|
|
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
|
|
xhr.setRequestHeader("X-CSRFToken", csrftoken);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// Update all form submissions to include CSRF token
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Add CSRF token to fetch requests
|
|
window.fetch = new Proxy(window.fetch, {
|
|
apply: function(fetch, that, args) {
|
|
if (args.length > 0 && typeof args[0] === 'object' && args[0].method !== 'GET') {
|
|
if (args[0].headers) {
|
|
args[0].headers['X-CSRFToken'] = csrftoken;
|
|
} else {
|
|
args[0].headers = { 'X-CSRFToken': csrftoken };
|
|
}
|
|
}
|
|
return fetch.apply(that, args);
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
{% block extrastyle %}
|
|
{{ block.super }}
|
|
<style>
|
|
:root {
|
|
--primary: #0D6EFD;
|
|
--primary-light: #E7F1FF;
|
|
--primary-dark: #0B5ED7;
|
|
--secondary: #6C757D;
|
|
--success: #198754;
|
|
--info: #0DCAF0;
|
|
--warning: #FFC107;
|
|
--danger: #DC3545;
|
|
--light: #F8F9FA;
|
|
--dark: #212529;
|
|
--gray: #6C757D;
|
|
--card-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
--card-hover-shadow: 0 8px 15px rgba(0, 0, 0, 0.15);
|
|
--transition: all 0.3s ease;
|
|
}
|
|
|
|
body {
|
|
background-color: #f5f7fa;
|
|
color: var(--dark);
|
|
}
|
|
|
|
.content-card {
|
|
background: white;
|
|
border-radius: 12px;
|
|
box-shadow: var(--card-shadow);
|
|
padding: 24px;
|
|
margin-bottom: 24px;
|
|
transition: var(--transition);
|
|
border: none;
|
|
}
|
|
|
|
.content-card:hover {
|
|
box-shadow: var(--card-hover-shadow);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.welcome-section {
|
|
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
|
|
color: white;
|
|
padding: 32px;
|
|
border-radius: 12px;
|
|
margin-bottom: 32px;
|
|
box-shadow: var(--card-shadow);
|
|
}
|
|
|
|
.summary-card {
|
|
background: white;
|
|
border-radius: 12px;
|
|
box-shadow: var(--card-shadow);
|
|
transition: var(--transition);
|
|
border: none;
|
|
height: 100%;
|
|
}
|
|
|
|
.summary-card:hover {
|
|
transform: translateY(-4px);
|
|
box-shadow: var(--card-hover-shadow);
|
|
}
|
|
|
|
.summary-card .card-header {
|
|
background: none;
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
padding: 16px;
|
|
border-radius: 12px 12px 0 0;
|
|
}
|
|
|
|
.summary-card .card-body {
|
|
padding: 24px;
|
|
}
|
|
|
|
.stat-number {
|
|
font-size: 2.5rem;
|
|
font-weight: 700;
|
|
color: var(--primary);
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.stat-label {
|
|
color: var(--secondary);
|
|
font-size: 0.9rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.status-badge {
|
|
padding: 4px 10px;
|
|
border-radius: 16px;
|
|
font-size: 0.8125rem;
|
|
font-weight: 500;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.status-badge:hover {
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.btn {
|
|
border-radius: 6px;
|
|
padding: 6px 12px;
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
transition: var(--transition);
|
|
border: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
line-height: 1.5;
|
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.btn i {
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.btn:hover {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.btn-sm {
|
|
padding: 4px 8px;
|
|
font-size: 0.8125rem;
|
|
}
|
|
|
|
.btn-sm i {
|
|
font-size: 0.8125rem;
|
|
}
|
|
|
|
.btn-schedule {
|
|
background-color: var(--primary);
|
|
color: white;
|
|
padding: 8px 16px;
|
|
}
|
|
|
|
.btn-schedule:hover {
|
|
background-color: var(--primary-dark);
|
|
color: white;
|
|
}
|
|
|
|
.btn-action {
|
|
padding: 4px 8px;
|
|
font-size: 0.8125rem;
|
|
margin: 0 2px;
|
|
}
|
|
|
|
.badge.bg-info {
|
|
background-color: #0DCAF0 !important;
|
|
color: white;
|
|
}
|
|
|
|
.badge.bg-success {
|
|
background-color: #198754 !important;
|
|
color: white;
|
|
}
|
|
|
|
.badge.bg-warning {
|
|
background-color: #FFC107 !important;
|
|
color: #000;
|
|
}
|
|
|
|
.badge.bg-danger {
|
|
background-color: #DC3545 !important;
|
|
color: white;
|
|
}
|
|
|
|
.badge.bg-secondary {
|
|
background-color: #6C757D !important;
|
|
color: white;
|
|
}
|
|
|
|
.meeting-type {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 4px 10px;
|
|
border-radius: 16px;
|
|
font-size: 0.8125rem;
|
|
background-color: #ECEFF1;
|
|
color: #455A64;
|
|
}
|
|
|
|
.meeting-type.online {
|
|
background-color: #0DCAF0;
|
|
color: white;
|
|
}
|
|
|
|
.meeting-type i {
|
|
font-size: 0.8125rem;
|
|
}
|
|
|
|
.action-buttons {
|
|
display: flex;
|
|
gap: 4px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.meeting-link {
|
|
color: var(--primary);
|
|
text-decoration: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.meeting-link:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.table td {
|
|
padding: 12px;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.table th {
|
|
padding: 12px;
|
|
font-weight: 600;
|
|
color: #455A64;
|
|
background-color: #F5F7FA;
|
|
}
|
|
|
|
@media (max-width: 1200px) {
|
|
.welcome-section {
|
|
padding: 28px;
|
|
}
|
|
.content-card {
|
|
padding: 20px;
|
|
}
|
|
.stat-number {
|
|
font-size: 2.2rem;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 992px) {
|
|
.welcome-section {
|
|
padding: 24px;
|
|
}
|
|
.content-card {
|
|
padding: 16px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.welcome-section {
|
|
padding: 20px;
|
|
margin-bottom: 24px;
|
|
}
|
|
.welcome-section h2 {
|
|
font-size: 1.5rem;
|
|
}
|
|
.summary-card .card-body {
|
|
padding: 16px;
|
|
}
|
|
.stat-number {
|
|
font-size: 2rem;
|
|
}
|
|
.action-buttons {
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
}
|
|
|
|
.btn-action {
|
|
width: 100%;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 576px) {
|
|
.welcome-section {
|
|
padding: 16px;
|
|
margin-bottom: 20px;
|
|
}
|
|
.welcome-section h2 {
|
|
font-size: 1.3rem;
|
|
}
|
|
.content-card {
|
|
padding: 12px;
|
|
}
|
|
.stat-number {
|
|
font-size: 1.8rem;
|
|
}
|
|
}
|
|
|
|
@media (hover: none) {
|
|
.summary-card:hover,
|
|
.content-card:hover,
|
|
.btn:hover {
|
|
transform: none;
|
|
}
|
|
}
|
|
|
|
@media (prefers-contrast: high) {
|
|
.summary-card,
|
|
.content-card {
|
|
border: 2px solid currentColor;
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.summary-card,
|
|
.content-card,
|
|
.btn,
|
|
.status-badge,
|
|
.rating-stars i {
|
|
transition: none;
|
|
}
|
|
}
|
|
|
|
/* Meeting Section Scroll */
|
|
.meetings-container {
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
margin: -12px;
|
|
padding: 12px;
|
|
}
|
|
|
|
.meetings-table {
|
|
position: relative;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.meetings-table thead {
|
|
position: sticky;
|
|
top: 0;
|
|
background-color: white;
|
|
z-index: 1;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
/* Bright Button Colors */
|
|
.btn-primary {
|
|
background-color: #0D6EFD;
|
|
border-color: #0D6EFD;
|
|
color: white;
|
|
}
|
|
|
|
.btn-success {
|
|
background-color: #198754;
|
|
border-color: #198754;
|
|
color: white;
|
|
}
|
|
|
|
.btn-info {
|
|
background-color: #0DCAF0;
|
|
border-color: #0DCAF0;
|
|
color: white;
|
|
}
|
|
|
|
.btn-warning {
|
|
background-color: #FFC107;
|
|
border-color: #FFC107;
|
|
color: #000;
|
|
}
|
|
|
|
.btn-danger {
|
|
background-color: #DC3545;
|
|
border-color: #DC3545;
|
|
color: white;
|
|
}
|
|
|
|
.btn-secondary {
|
|
background-color: #6C757D;
|
|
border-color: #6C757D;
|
|
color: white;
|
|
}
|
|
|
|
/* Button Hover States */
|
|
.btn-primary:hover {
|
|
background-color: #0B5ED7;
|
|
border-color: #0A58CA;
|
|
}
|
|
|
|
.btn-success:hover {
|
|
background-color: #157347;
|
|
border-color: #146C43;
|
|
}
|
|
|
|
.btn-info:hover {
|
|
background-color: #31D2F2;
|
|
border-color: #25CFF2;
|
|
}
|
|
|
|
.btn-warning:hover {
|
|
background-color: #FFCA2C;
|
|
border-color: #FFC720;
|
|
}
|
|
|
|
.btn-danger:hover {
|
|
background-color: #BB2D3B;
|
|
border-color: #B02A37;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background-color: #5C636A;
|
|
border-color: #565E64;
|
|
}
|
|
|
|
/* Meeting Type Badge */
|
|
.meeting-type.online {
|
|
background-color: #0DCAF0;
|
|
color: white;
|
|
}
|
|
|
|
.meeting-type.in-person {
|
|
background-color: #6C757D;
|
|
color: white;
|
|
}
|
|
|
|
/* Status Badges */
|
|
.status-badge.completed {
|
|
background-color: #198754;
|
|
color: white;
|
|
}
|
|
|
|
.status-badge.cancelled {
|
|
background-color: #DC3545;
|
|
color: white;
|
|
}
|
|
|
|
.status-badge.rescheduled {
|
|
background-color: #FFC107;
|
|
color: #000;
|
|
}
|
|
|
|
/* Scrollbar Styling */
|
|
.meetings-container::-webkit-scrollbar {
|
|
width: 6px;
|
|
}
|
|
|
|
.meetings-container::-webkit-scrollbar-track {
|
|
background: #f1f1f1;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.meetings-container::-webkit-scrollbar-thumb {
|
|
background: #888;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.meetings-container::-webkit-scrollbar-thumb:hover {
|
|
background: #555;
|
|
}
|
|
|
|
/* Scrollable Container Styles */
|
|
.scroll-container {
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
margin: -12px;
|
|
padding: 12px;
|
|
position: relative;
|
|
}
|
|
|
|
.scroll-container::-webkit-scrollbar {
|
|
width: 6px;
|
|
}
|
|
|
|
.scroll-container::-webkit-scrollbar-track {
|
|
background: #f1f1f1;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.scroll-container::-webkit-scrollbar-thumb {
|
|
background: #888;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.scroll-container::-webkit-scrollbar-thumb:hover {
|
|
background: #555;
|
|
}
|
|
|
|
/* Card Heights */
|
|
.stats-card {
|
|
height: 300px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.stats-card .card-header {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.stats-card .card-body {
|
|
flex-grow: 1;
|
|
overflow-y: auto;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.content-card {
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.content-card .card-header {
|
|
flex-shrink: 0;
|
|
padding: 1rem;
|
|
border-bottom: 1px solid rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.content-card .card-body {
|
|
flex-grow: 1;
|
|
overflow-y: auto;
|
|
padding: 1rem;
|
|
}
|
|
|
|
/* Table Header Sticky */
|
|
.sticky-header {
|
|
position: sticky;
|
|
top: 0;
|
|
background-color: white;
|
|
z-index: 1;
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
/* Meeting Action Buttons */
|
|
.meeting-actions {
|
|
display: flex;
|
|
gap: 4px;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
}
|
|
|
|
.btn-action {
|
|
padding: 4px 8px;
|
|
font-size: 0.75rem;
|
|
border-radius: 4px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-weight: 500;
|
|
min-width: 28px;
|
|
justify-content: center;
|
|
border: none;
|
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.btn-action:hover {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.btn-action i {
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.btn-action.btn-complete {
|
|
background-color: #198754;
|
|
color: white;
|
|
}
|
|
|
|
.btn-action.btn-reschedule {
|
|
background-color: #FFC107;
|
|
color: #000;
|
|
}
|
|
|
|
.btn-action.btn-cancel {
|
|
background-color: #DC3545;
|
|
color: white;
|
|
}
|
|
|
|
.btn-action.btn-notes {
|
|
background-color: #0DCAF0;
|
|
color: white;
|
|
}
|
|
|
|
.btn-action.btn-agenda {
|
|
background-color: #0D6EFD;
|
|
color: white;
|
|
}
|
|
|
|
.btn-action.btn-reason {
|
|
background-color: #6C757D;
|
|
color: white;
|
|
}
|
|
|
|
/* Meeting Status Badges */
|
|
.meeting-status {
|
|
padding: 4px 8px;
|
|
border-radius: 12px;
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
min-width: 80px;
|
|
justify-content: center;
|
|
}
|
|
|
|
.meeting-status i {
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.meeting-status.scheduled {
|
|
background-color: #E7F1FF;
|
|
color: #0D6EFD;
|
|
}
|
|
|
|
.meeting-status.completed {
|
|
background-color: #E8F5E9;
|
|
color: #198754;
|
|
}
|
|
|
|
.meeting-status.cancelled {
|
|
background-color: #FFEBEE;
|
|
color: #DC3545;
|
|
}
|
|
|
|
.meeting-status.rescheduled {
|
|
background-color: #FFF3E0;
|
|
color: #F57C00;
|
|
}
|
|
|
|
/* Meeting Type Badge */
|
|
.meeting-type {
|
|
padding: 4px 8px;
|
|
border-radius: 12px;
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
min-width: 80px;
|
|
justify-content: center;
|
|
}
|
|
|
|
.meeting-type.online {
|
|
background-color: #E1F5FE;
|
|
color: #0277BD;
|
|
}
|
|
|
|
.meeting-type.in-person {
|
|
background-color: #ECEFF1;
|
|
color: #455A64;
|
|
}
|
|
|
|
/* Responsive adjustments */
|
|
@media (max-width: 768px) {
|
|
.meeting-actions {
|
|
flex-direction: row;
|
|
justify-content: flex-start;
|
|
margin-top: 8px;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block branding %}
|
|
<h1 id="site-name">
|
|
<a href="{% url 'admin:index' %}">
|
|
<img src="{% static site_logo %}" alt="{{ site_header }}" class="brand-image" style="height: 30px;">
|
|
{{ site_header }}
|
|
</a>
|
|
</h1>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid py-4">
|
|
<!-- Welcome Section -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="content-card welcome-section">
|
|
<div class="d-flex justify-content-between align-items-center flex-wrap">
|
|
<div class="flex-grow-1">
|
|
<h2 class="mb-1">Welcome, Admin!</h2>
|
|
<p class="mb-0">Student Management Dashboard</p>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Stats Row -->
|
|
<div class="row mb-4">
|
|
<!-- Student Stats -->
|
|
<div class="col-xl-3 col-md-6 mb-3">
|
|
<div class="summary-card stats-card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0 small"><i class="fas fa-users text-primary me-2"></i>Students</h5>
|
|
</div>
|
|
<div class="card-body scroll-container">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<span class="d-block text-muted small">Total Students</span>
|
|
<h3>{{ total_students }}</h3>
|
|
</div>
|
|
<div class="text-end">
|
|
<span class="d-block text-muted small">Active</span>
|
|
<h4 class="text-success">{{ active_students }}</h4>
|
|
</div>
|
|
</div>
|
|
<div class="mt-3">
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<small class="mb-0">Male Students</small>
|
|
<span class="badge bg-info p-2">{{ male_students }}</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<small class="mb-0">Female Students</small>
|
|
<span class="badge bg-warning p-2">{{ female_students }}</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between">
|
|
<small class="mb-0">Inactive</small>
|
|
<span class="badge bg-danger p-2">{{ inactive_students }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Staff Stats -->
|
|
<div class="col-xl-3 col-md-6 mb-3">
|
|
<div class="summary-card stats-card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0 small"><i class="fas fa-chalkboard-teacher text-info me-2"></i>Staff</h5>
|
|
</div>
|
|
<div class="card-body scroll-container">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<span class="d-block text-muted small">Total Staff</span>
|
|
<h3>{{ total_staff }}</h3>
|
|
</div>
|
|
<div class="text-end">
|
|
<span class="d-block text-muted small">Active</span>
|
|
<h4 class="text-success">{{ active_staff }}</h4>
|
|
</div>
|
|
</div>
|
|
<div class="mt-3">
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<small class="mb-0">Teachers</small>
|
|
<span class="badge bg-info p-2">{{ total_teachers }}</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<small class="mb-0">Non-Teaching</small>
|
|
<span class="badge bg-warning p-2">{{ non_teaching_staff }}</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between">
|
|
<small class="mb-0">Inactive</small>
|
|
<span class="badge bg-danger p-2">{{ inactive_staff }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Parent Stats -->
|
|
<div class="col-xl-3 col-md-6 mb-3">
|
|
<div class="summary-card stats-card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0 small"><i class="fas fa-user-friends text-warning me-2"></i>Parents</h5>
|
|
</div>
|
|
<div class="card-body scroll-container">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<span class="d-block text-muted small">Total Parents</span>
|
|
<h3>{{ total_parents }}</h3>
|
|
</div>
|
|
<div class="text-end">
|
|
<span class="d-block text-muted small">Active</span>
|
|
<h4 class="text-success">{{ active_parents }}</h4>
|
|
</div>
|
|
</div>
|
|
<div class="mt-3">
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<small class="mb-0">Parents with Students</small>
|
|
<span class="badge bg-info p-2">{{ parents_with_students }}</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<small class="mb-0">Parents with Multiple Students</small>
|
|
<span class="badge bg-warning p-2">{{ parents_with_multiple_students }}</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between">
|
|
<small class="mb-0">Inactive</small>
|
|
<span class="badge bg-danger p-2">{{ inactive_parents }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Course Stats -->
|
|
<div class="col-xl-3 col-md-6 mb-3">
|
|
<div class="summary-card stats-card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0 small"><i class="fas fa-book text-success me-2"></i>Courses</h5>
|
|
</div>
|
|
<div class="card-body scroll-container">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<span class="d-block text-muted small">Total Courses</span>
|
|
<h3>{{ total_courses }}</h3>
|
|
</div>
|
|
<div class="text-end">
|
|
<span class="d-block text-muted small">Active</span>
|
|
<h4 class="text-success">{{ active_courses }}</h4>
|
|
</div>
|
|
</div>
|
|
<div class="mt-3">
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<small class="mb-0">Total Subjects</small>
|
|
<span class="badge bg-info p-2">{{ total_subjects }}</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between mb-2">
|
|
<small class="mb-0">Total Batches</small>
|
|
<span class="badge bg-warning p-2">{{ total_batches }}</span>
|
|
</div>
|
|
<div class="d-flex justify-content-between">
|
|
<small class="mb-0">Inactive</small>
|
|
<span class="badge bg-danger p-2">{{ inactive_courses }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Teacher Ratings -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="content-card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-star text-warning me-2"></i>Top Rated Teachers
|
|
</h5>
|
|
</div>
|
|
<div class="card-body scroll-container">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead class="sticky-header">
|
|
<tr>
|
|
<th>Teacher</th>
|
|
<th>Student Rating</th>
|
|
<th>Parent Rating</th>
|
|
<th>Total Feedbacks</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for teacher in teacher_ratings %}
|
|
<tr>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
{% if teacher.image %}
|
|
<img src="{{ teacher.image.url }}" alt="{{ teacher.name }}" class="rounded-circle me-2" style="width: 40px; height: 40px; object-fit: cover;">
|
|
{% else %}
|
|
<div class="rounded-circle bg-light d-flex align-items-center justify-content-center me-2" style="width: 40px; height: 40px;">
|
|
<i class="fas fa-user-tie text-secondary"></i>
|
|
</div>
|
|
{% endif %}
|
|
<div>
|
|
<h6 class="mb-0">{{ teacher.name }}</h6>
|
|
<small class="text-muted">{{ teacher.designation }}</small>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<span class="me-2">{{ teacher.avg_student_rating|floatformat:1 }}</span>
|
|
<div class="rating-stars">
|
|
{% for i in "12345"|make_list %}
|
|
{% if forloop.counter <= teacher.avg_student_rating %}
|
|
<i class="fas fa-star text-warning"></i>
|
|
{% else %}
|
|
<i class="far fa-star text-warning"></i>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<span class="me-2">{{ teacher.avg_parent_rating|floatformat:1 }}</span>
|
|
<div class="rating-stars">
|
|
{% for i in "12345"|make_list %}
|
|
{% if forloop.counter <= teacher.avg_parent_rating %}
|
|
<i class="fas fa-star text-warning"></i>
|
|
{% else %}
|
|
<i class="far fa-star text-warning"></i>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-info">{{ teacher.total_feedbacks }}</span>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="4" class="text-center">
|
|
<small class="text-muted">No teacher ratings available</small>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Activities -->
|
|
<div class="row mb-4">
|
|
<!-- Recent Notices -->
|
|
<div class="col-12 mb-4">
|
|
<div class="content-card">
|
|
<div class="card-header">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-bullhorn text-primary me-2"></i>Recent Notices
|
|
</h5>
|
|
<button type="button" class="btn btn-primary btn-sm" onclick="openAddNoticeModal()">
|
|
<i class="fas fa-plus me-2"></i>Add Notice
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body scroll-container">
|
|
{% for notice in recent_notices %}
|
|
<div class="d-flex align-items-start mb-3 pb-3 border-bottom">
|
|
<div class="flex-shrink-0 me-3">
|
|
{% if notice.image %}
|
|
<img src="{{ notice.image.url }}" alt="{{ notice.title }}" class="rounded" style="width: 60px; height: 60px; object-fit: cover;">
|
|
{% else %}
|
|
<div class="rounded bg-light d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
|
|
<i class="fas fa-bullhorn text-primary"></i>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<small class="mb-0 d-block fw-medium">{{ notice.title }}</small>
|
|
<small class="text-muted">{{ notice.message|truncatechars:50 }}</small>
|
|
<small class="d-block text-muted">{{ notice.created_at|timesince }} ago</small>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="text-center">
|
|
<small class="text-muted">No recent notices</small>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Leave Requests -->
|
|
<div class="row mb-4">
|
|
<!-- Staff Leave Requests -->
|
|
<div class="col-xl-6 mb-4">
|
|
<div class="content-card">
|
|
<h5 class="mb-3">
|
|
<i class="fas fa-user-tie text-info me-2"></i>Staff Leave Requests
|
|
</h5>
|
|
<div class="activity-scroll" id="staff-leaves-container">
|
|
{% for leave in staff_leaves %}
|
|
<div class="d-flex align-items-center mb-3 pb-3 border-bottom">
|
|
<div class="flex-shrink-0 me-3">
|
|
<div class="rounded bg-light d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
|
|
<i class="fas fa-calendar-times text-info"></i>
|
|
</div>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<small class="mb-0 d-block fw-medium">{{ leave.staff.name }}</small>
|
|
<small class="text-muted">From: {{ leave.start_date|date:"M d, Y" }}</small>
|
|
<small class="text-muted d-block">To: {{ leave.end_date|date:"M d, Y" }}</small>
|
|
<small class="text-muted d-block">{{ leave.message|truncatechars:50 }}</small>
|
|
<div class="mt-2">
|
|
<form method="post" action="{% url 'approve_staff_leave' leave.id %}" class="d-inline">
|
|
{% csrf_token %}
|
|
<button type="submit" class="btn btn-success btn-sm">
|
|
<i class="fas fa-check me-1"></i>Approve
|
|
</button>
|
|
</form>
|
|
<form method="post" action="{% url 'reject_staff_leave' leave.id %}" class="d-inline ms-2">
|
|
{% csrf_token %}
|
|
<button type="submit" class="btn btn-danger btn-sm">
|
|
<i class="fas fa-times me-1"></i>Reject
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="text-center">
|
|
<small class="text-muted">No pending staff leave requests</small>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Student Leave Requests -->
|
|
<div class="col-xl-6 mb-4">
|
|
<div class="content-card">
|
|
<h5 class="mb-3">
|
|
<i class="fas fa-user-graduate text-warning me-2"></i>Student Leave Requests
|
|
</h5>
|
|
<div class="activity-scroll" id="student-leaves-container">
|
|
{% for leave in student_leaves %}
|
|
<div class="d-flex align-items-center mb-3 pb-3 border-bottom">
|
|
<div class="flex-shrink-0 me-3">
|
|
<div class="rounded bg-light d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
|
|
<i class="fas fa-calendar-times text-warning"></i>
|
|
</div>
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
<small class="mb-0 d-block fw-medium">{{ leave.student.name }}</small>
|
|
<small class="text-muted">From: {{ leave.start_date|date:"M d, Y" }}</small>
|
|
<small class="text-muted d-block">To: {{ leave.end_date|date:"M d, Y" }}</small>
|
|
<small class="text-muted d-block">{{ leave.message|truncatechars:50 }}</small>
|
|
<div class="mt-2">
|
|
<form method="post" action="{% url 'approve_student_leave' leave.id %}" class="d-inline">
|
|
{% csrf_token %}
|
|
<button type="submit" class="btn btn-success btn-sm">
|
|
<i class="fas fa-check me-1"></i>Approve
|
|
</button>
|
|
</form>
|
|
<form method="post" action="{% url 'reject_student_leave' leave.id %}" class="d-inline ms-2">
|
|
{% csrf_token %}
|
|
<button type="submit" class="btn btn-danger btn-sm">
|
|
<i class="fas fa-times me-1"></i>Reject
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="text-center">
|
|
<small class="text-muted">No pending student leave requests</small>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Feedback and Meetings Row -->
|
|
<div class="row mb-4">
|
|
<!-- Recent Institute Feedback -->
|
|
<div class="col-xl-6 mb-4">
|
|
<div class="content-card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-comments text-success me-2"></i>Recent Institute Feedback
|
|
</h5>
|
|
</div>
|
|
<div class="card-body scroll-container">
|
|
{% for feedback in recent_feedback %}
|
|
<div class="d-flex align-items-center mb-2 pb-2 border-bottom">
|
|
<div class="flex-shrink-0 me-3">
|
|
{% if not feedback.is_anonymous %}
|
|
{% if feedback.student %}
|
|
{% if feedback.student.image %}
|
|
<img src="{{ feedback.student.image.url }}" alt="{{ feedback.student.name }}" class="rounded-circle" style="width: 40px; height: 40px; object-fit: cover;">
|
|
{% else %}
|
|
<div class="rounded-circle bg-light d-flex align-items-center justify-content: center" style="width: 40px; height: 40px;">
|
|
<i class="fas fa-user-graduate text-secondary"></i>
|
|
</div>
|
|
{% endif %}
|
|
{% elif feedback.staff %}
|
|
{% if feedback.staff.image %}
|
|
<img src="{{ feedback.staff.image.url }}" alt="{{ feedback.staff.name }}" class="rounded-circle" style="width: 40px; height: 40px; object-fit: cover;">
|
|
{% else %}
|
|
<div class="rounded-circle bg-light d-flex align-items-center justify-content: center" style="width: 40px; height: 40px;">
|
|
<i class="fas fa-user-tie text-secondary"></i>
|
|
</div>
|
|
{% endif %}
|
|
{% elif feedback.parent %}
|
|
{% if feedback.parent.image %}
|
|
<img src="{{ feedback.parent.image.url }}" alt="{{ feedback.parent.name }}" class="rounded-circle" style="width: 40px; height: 40px; object-fit: cover;">
|
|
{% else %}
|
|
<div class="rounded-circle bg-light d-flex align-items-center justify-content: center" style="width: 40px; height: 40px;">
|
|
<i class="fas fa-user-friends text-secondary"></i>
|
|
</div>
|
|
{% endif %}
|
|
{% endif %}
|
|
{% else %}
|
|
<div class="rounded-circle bg-light d-flex align-items-center justify-content: center" style="width: 40px; height: 40px;">
|
|
<i class="fas fa-user-secret text-secondary"></i>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
<div class="flex-grow-1">
|
|
{% if not feedback.is_anonymous %}
|
|
{% if feedback.student %}
|
|
<small class="mb-0 d-block fw-medium">{{ feedback.student.name }} (Student)</small>
|
|
<small class="text-muted d-block">
|
|
{{ feedback.student.course.name }} -
|
|
{% if feedback.student.course.duration_type == 'Semester' %}
|
|
Semester {{ feedback.student.current_period }}
|
|
{% else %}
|
|
Year {{ feedback.student.current_period }}
|
|
{% endif %}
|
|
</small>
|
|
{% elif feedback.staff %}
|
|
<small class="mb-0 d-block fw-medium">{{ feedback.staff.name }} ({{ feedback.staff.designation }})</small>
|
|
{% elif feedback.parent %}
|
|
<small class="mb-0 d-block fw-medium">{{ feedback.parent.name }} (Parent)</small>
|
|
{% with student=feedback.parent.students.first %}
|
|
{% if student %}
|
|
<small class="text-muted d-block">
|
|
Parent of {{ student.name }} -
|
|
{{ student.course.name }}
|
|
{% if student.course.duration_type == 'Semester' %}
|
|
(Semester {{ student.current_period }})
|
|
{% else %}
|
|
(Year {{ student.current_period }})
|
|
{% endif %}
|
|
</small>
|
|
{% endif %}
|
|
{% endwith %}
|
|
{% endif %}
|
|
{% else %}
|
|
<small class="mb-0 d-block fw-medium">Anonymous</small>
|
|
{% endif %}
|
|
<div class="d-flex align-items-center mt-1">
|
|
<span class="badge bg-info me-2">{{ feedback.get_feedback_type_display }}</span>
|
|
<div class="rating-stars">
|
|
{% for i in "12345"|make_list %}
|
|
<i class="{% if forloop.counter <= feedback.rating %}fas{% else %}far{% endif %} fa-star text-warning"></i>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% if feedback.feedback_text %}
|
|
<small class="d-block text-muted mt-1">{{ feedback.feedback_text|truncatechars:100 }}</small>
|
|
{% endif %}
|
|
<small class="d-block text-muted mt-1">{{ feedback.created_at|timesince }} ago</small>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="text-center">
|
|
<small class="text-muted">No recent institute feedback</small>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Parent-Teacher Meetings -->
|
|
<div class="col-xl-6 mb-4">
|
|
<div class="content-card">
|
|
<div class="card-header">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-calendar-check text-primary me-2"></i>Parent-Teacher Meetings
|
|
</h5>
|
|
<button type="button" class="btn btn-primary btn-sm" onclick="openScheduleMeetingModal()">
|
|
<i class="fas fa-plus me-2"></i>Schedule Meeting
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body scroll-container">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead class="sticky-header">
|
|
<tr>
|
|
<th>Date</th>
|
|
<th>Time</th>
|
|
<th>Duration</th>
|
|
<th>Type</th>
|
|
<th>Status</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for meeting in meetings %}
|
|
<tr>
|
|
<td>{{ meeting.meeting_date|date:"M d, Y" }}</td>
|
|
<td>{{ meeting.meeting_time|time:"h:i A" }}</td>
|
|
<td>{{ meeting.duration }} minutes</td>
|
|
<td>
|
|
{% if meeting.is_online %}
|
|
<span class="meeting-type online">
|
|
<i class="fas fa-video"></i>Online
|
|
{% if meeting.meeting_link %}
|
|
<a href="{{ meeting.meeting_link }}" target="_blank" class="ms-1">
|
|
<i class="fas fa-external-link-alt"></i>
|
|
{% endif %}
|
|
</span>
|
|
{% else %}
|
|
<span class="meeting-type in-person">
|
|
<i class="fas fa-user-friends"></i>In-person
|
|
</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<span class="meeting-status {{ meeting.status }}">
|
|
{% if meeting.status == 'scheduled' %}
|
|
<i class="fas fa-calendar-check"></i>Scheduled
|
|
{% elif meeting.status == 'completed' %}
|
|
<i class="fas fa-check-circle"></i>Completed
|
|
{% elif meeting.status == 'cancelled' %}
|
|
<i class="fas fa-times-circle"></i>Cancelled
|
|
{% else %}
|
|
<i class="fas fa-calendar-alt"></i>Rescheduled
|
|
{% endif %}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<div class="meeting-actions">
|
|
{% if meeting.status == 'scheduled' %}
|
|
<button class="btn-action btn-complete" onclick="completeMeeting({{ meeting.id }})" title="Complete">
|
|
<i class="fas fa-check"></i>
|
|
</button>
|
|
<button class="btn-action btn-reschedule" onclick="rescheduleMeeting({{ meeting.id }})" title="Reschedule">
|
|
<i class="fas fa-calendar-alt"></i>
|
|
</button>
|
|
<button class="btn-action btn-cancel" onclick="cancelMeeting({{ meeting.id }})" title="Cancel">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
{% endif %}
|
|
{% if meeting.notes or meeting.status == 'completed' %}
|
|
<button class="btn-action btn-notes" onclick="viewMeetingNotes({{ meeting.id }})" title="View Notes">
|
|
<i class="fas fa-sticky-note"></i>
|
|
</button>
|
|
{% endif %}
|
|
{% if meeting.agenda %}
|
|
<button class="btn-action btn-agenda" onclick="viewMeetingAgenda({{ meeting.id }})" title="View Agenda">
|
|
<i class="fas fa-clipboard-list"></i>
|
|
</button>
|
|
{% endif %}
|
|
{% if meeting.status == 'cancelled' and meeting.cancellation_reason %}
|
|
<button class="btn-action btn-reason" onclick="viewCancellationReason({{ meeting.id }})" title="View Reason">
|
|
<i class="fas fa-question-circle"></i>
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="6" class="text-center">
|
|
<small class="text-muted">No meetings scheduled</small>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Schedule Meeting Modal -->
|
|
<div id="scheduleMeetingModal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.5); z-index: 10000;" onclick="if(event.target === this) closeScheduleMeetingModal();">
|
|
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 600px; background-color: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2);">
|
|
<div style="background-color: #3F51B5; color: white; padding: 15px; border-top-left-radius: 8px; border-top-right-radius: 8px; display: flex; justify-content: space-between; align-items: center;">
|
|
<h5 style="margin: 0; font-size: 18px;">Schedule New Meeting</h5>
|
|
<button style="background: none; border: none; color: white; font-size: 24px; cursor: pointer; line-height: 24px;" onclick="closeScheduleMeetingModal()">×</button>
|
|
</div>
|
|
<div style="padding: 20px;">
|
|
<div id="scheduleMeetingError" class="alert alert-danger" style="display: none;"></div>
|
|
<div id="scheduleMeetingLoading" class="text-center" style="display: none;">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
<p class="mt-2">Scheduling meeting...</p>
|
|
</div>
|
|
<form id="scheduleMeetingForm" onsubmit="return false;">
|
|
{% csrf_token %}
|
|
<div class="mb-3">
|
|
<label for="meetingDate" class="form-label">Date</label>
|
|
<input type="date" class="form-control" id="meetingDate" name="meeting_date" required min="{{ today|date:'Y-m-d' }}">
|
|
<div class="invalid-feedback">Please select a valid date</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="meetingTime" class="form-label">Time</label>
|
|
<input type="time" class="form-control" id="meetingTime" name="meeting_time" required>
|
|
<div class="invalid-feedback">Please select a valid time</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="meetingDuration" class="form-label">Duration (minutes)</label>
|
|
<input type="number" class="form-control" id="meetingDuration" name="duration" min="15" max="120" value="30" required>
|
|
<div class="invalid-feedback">Duration must be between 15 and 120 minutes</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="isOnline" name="is_online">
|
|
<label class="form-check-label" for="isOnline">Online Meeting</label>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3" id="meetingLinkContainer" style="display: none;">
|
|
<label for="meetingLink" class="form-label">Meeting Link</label>
|
|
<input type="url" class="form-control" id="meetingLink" name="meeting_link" placeholder="https://meet.google.com/...">
|
|
<div class="invalid-feedback">Please enter a valid meeting link</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="scheduleMeetingAgenda" class="form-label">Agenda</label>
|
|
<textarea class="form-control" id="scheduleMeetingAgenda" name="agenda" rows="3" placeholder="Brief description of what you'd like to discuss"></textarea>
|
|
</div>
|
|
<div class="d-flex justify-content-end gap-2">
|
|
<button type="button" class="btn" style="background-color: #9E9E9E; color: white;" onclick="closeScheduleMeetingModal()">Cancel</button>
|
|
<button type="button" class="btn" style="background-color: #3F51B5; color: white;" onclick="submitMeetingForm()">Schedule Meeting</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Meeting Notes Modal -->
|
|
<div id="viewNotesModal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.5); z-index: 10000;" onclick="if(event.target === this) closeNotesModal();">
|
|
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 600px; background-color: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2);">
|
|
<div style="background-color: #3F51B5; color: white; padding: 15px; border-top-left-radius: 8px; border-top-right-radius: 8px; display: flex; justify-content: space-between; align-items: center;">
|
|
<h5 style="margin: 0; font-size: 18px;">Meeting Notes</h5>
|
|
<button style="background: none; border: none; color: white; font-size: 24px; cursor: pointer; line-height: 24px;" onclick="closeNotesModal()">×</button>
|
|
</div>
|
|
<div style="padding: 20px;">
|
|
<div id="notesError" class="alert alert-danger" style="display: none;"></div>
|
|
<div id="notesLoading" class="text-center" style="display: none;">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
<p class="mt-2">Loading notes...</p>
|
|
</div>
|
|
<div id="meetingNotesContent" style="display: none;">
|
|
<p id="viewMeetingNotesText" style="margin: 0; white-space: pre-wrap;"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Meeting Agenda Modal -->
|
|
<div id="viewAgendaModal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.5); z-index: 10000;" onclick="if(event.target === this) closeAgendaModal();">
|
|
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 600px; background-color: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2);">
|
|
<div style="background-color: #3F51B5; color: white; padding: 15px; border-top-left-radius: 8px; border-top-right-radius: 8px; display: flex; justify-content: space-between; align-items: center;">
|
|
<h5 style="margin: 0; font-size: 18px;">Meeting Agenda</h5>
|
|
<button style="background: none; border: none; color: white; font-size: 24px; cursor: pointer; line-height: 24px;" onclick="closeAgendaModal()">×</button>
|
|
</div>
|
|
<div style="padding: 20px;">
|
|
<div id="agendaError" class="alert alert-danger" style="display: none;"></div>
|
|
<div id="agendaLoading" class="text-center" style="display: none;">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
<p class="mt-2">Loading agenda...</p>
|
|
</div>
|
|
<div id="meetingAgendaContent" style="display: none;">
|
|
<p id="viewMeetingAgendaText" style="margin: 0; white-space: pre-wrap;"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Cancel Meeting Modal -->
|
|
<div id="cancelMeetingModal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.5); z-index: 10000;" onclick="if(event.target === this) closeCancelModal();">
|
|
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 600px; background-color: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2);">
|
|
<div style="background-color: #3F51B5; color: white; padding: 15px; border-top-left-radius: 8px; border-top-right-radius: 8px; display: flex; justify-content: space-between; align-items: center;">
|
|
<h5 style="margin: 0; font-size: 18px;">Cancel Meeting</h5>
|
|
<button style="background: none; border: none; color: white; font-size: 24px; cursor: pointer; line-height: 24px;" onclick="closeCancelModal()">×</button>
|
|
</div>
|
|
<div style="padding: 20px;">
|
|
<div id="cancelError" class="alert alert-danger" style="display: none;"></div>
|
|
<div id="cancelLoading" class="text-center" style="display: none;">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
<p class="mt-2">Cancelling meeting...</p>
|
|
</div>
|
|
<form id="cancelMeetingForm" onsubmit="return false;">
|
|
{% csrf_token %}
|
|
<input type="hidden" id="cancelMeetingId" name="meeting_id">
|
|
<div class="mb-3">
|
|
<label for="cancellationReason" class="form-label">Reason for Cancellation</label>
|
|
<textarea class="form-control" id="cancellationReason" name="reason" rows="3" required></textarea>
|
|
<div class="invalid-feedback">Please provide a reason for cancellation</div>
|
|
</div>
|
|
<div class="d-flex justify-content-end gap-2">
|
|
<button type="button" class="btn" style="background-color: #9E9E9E; color: white;" onclick="closeCancelModal()">Close</button>
|
|
<button type="button" class="btn" style="background-color: #F44336; color: white;" onclick="submitCancelForm()">Cancel Meeting</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Reschedule Meeting Modal -->
|
|
<div id="rescheduleMeetingModal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.5); z-index: 10000;" onclick="if(event.target === this) closeRescheduleModal();">
|
|
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 600px; background-color: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2);">
|
|
<div style="background-color: #3F51B5; color: white; padding: 15px; border-top-left-radius: 8px; border-top-right-radius: 8px; display: flex; justify-content: space-between; align-items: center;">
|
|
<h5 style="margin: 0; font-size: 18px;">Reschedule Meeting</h5>
|
|
<button style="background: none; border: none; color: white; font-size: 24px; cursor: pointer; line-height: 24px;" onclick="closeRescheduleModal()">×</button>
|
|
</div>
|
|
<div style="padding: 20px;">
|
|
<div id="rescheduleError" class="alert alert-danger" style="display: none;"></div>
|
|
<div id="rescheduleLoading" class="text-center" style="display: none;">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
<p class="mt-2">Rescheduling meeting...</p>
|
|
</div>
|
|
<form id="rescheduleMeetingForm" onsubmit="return false;">
|
|
{% csrf_token %}
|
|
<input type="hidden" id="rescheduleMeetingId" name="meeting_id">
|
|
<div class="mb-3">
|
|
<label for="rescheduleDate" class="form-label">New Date</label>
|
|
<input type="date" class="form-control" id="rescheduleDate" name="meeting_date" required>
|
|
<div class="invalid-feedback">Please select a valid date</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="rescheduleTime" class="form-label">New Time</label>
|
|
<input type="time" class="form-control" id="rescheduleTime" name="meeting_time" required>
|
|
<div class="invalid-feedback">Please select a valid time</div>
|
|
</div>
|
|
<div class="d-flex justify-content-end gap-2">
|
|
<button type="button" class="btn" style="background-color: #9E9E9E; color: white;" onclick="closeRescheduleModal()">Close</button>
|
|
<button type="button" class="btn" style="background-color: #3F51B5; color: white;" onclick="submitRescheduleForm()">Reschedule Meeting</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Complete Meeting Modal -->
|
|
<div id="completeMeetingModal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.5); z-index: 10000;" onclick="if(event.target === this) closeCompleteModal();">
|
|
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 600px; background-color: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2);">
|
|
<div style="background-color: #3F51B5; color: white; padding: 15px; border-top-left-radius: 8px; border-top-right-radius: 8px; display: flex; justify-content: space-between; align-items: center;">
|
|
<h5 style="margin: 0; font-size: 18px;">Complete Meeting</h5>
|
|
<button style="background: none; border: none; color: white; font-size: 24px; cursor: pointer; line-height: 24px;" onclick="closeCompleteModal()">×</button>
|
|
</div>
|
|
<div style="padding: 20px;">
|
|
<div id="completeError" class="alert alert-danger" style="display: none;"></div>
|
|
<div id="completeLoading" class="text-center" style="display: none;">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
<p class="mt-2">Completing meeting...</p>
|
|
</div>
|
|
<form id="completeMeetingForm" onsubmit="return false;">
|
|
{% csrf_token %}
|
|
<input type="hidden" id="completeMeetingId" name="meeting_id">
|
|
<div class="mb-3">
|
|
<label for="completeMeetingNotes" class="form-label">Meeting Notes</label>
|
|
<textarea class="form-control" id="completeMeetingNotes" name="notes" rows="3" placeholder="Enter any notes or summary from the meeting"></textarea>
|
|
</div>
|
|
<div class="d-flex justify-content-end gap-2">
|
|
<button type="button" class="btn" style="background-color: #9E9E9E; color: white;" onclick="closeCompleteModal()">Close</button>
|
|
<button type="button" class="btn" style="background-color: #4CAF50; color: white;" onclick="submitCompleteForm()">Complete Meeting</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Cancellation Reason Modal -->
|
|
<div id="viewReasonModal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.5); z-index: 10000;" onclick="if(event.target === this) closeReasonModal();">
|
|
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 600px; background-color: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2);">
|
|
<div style="background-color: #3F51B5; color: white; padding: 15px; border-top-left-radius: 8px; border-top-right-radius: 8px; display: flex; justify-content: space-between; align-items: center;">
|
|
<h5 style="margin: 0; font-size: 18px;">Cancellation Reason</h5>
|
|
<button style="background: none; border: none; color: white; font-size: 24px; cursor: pointer; line-height: 24px;" onclick="closeReasonModal()">×</button>
|
|
</div>
|
|
<div style="padding: 20px;">
|
|
<div id="reasonError" class="alert alert-danger" style="display: none;"></div>
|
|
<div id="reasonLoading" class="text-center" style="display: none;">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
<p class="mt-2">Loading reason...</p>
|
|
</div>
|
|
<div id="meetingReasonContent" style="display: none;">
|
|
<p id="viewMeetingReasonText" style="margin: 0; white-space: pre-wrap;"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Notice Modal -->
|
|
<div id="addNoticeModal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.5); z-index: 10000;" onclick="if(event.target === this) closeAddNoticeModal();">
|
|
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 600px; background-color: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2);">
|
|
<div style="background-color: #3F51B5; color: white; padding: 15px; border-top-left-radius: 8px; border-top-right-radius: 8px; display: flex; justify-content: space-between; align-items: center;">
|
|
<h5 style="margin: 0; font-size: 18px;">Add New Notice</h5>
|
|
<button style="background: none; border: none; color: white; font-size: 24px; cursor: pointer; line-height: 24px;" onclick="closeAddNoticeModal()">×</button>
|
|
</div>
|
|
<div style="padding: 20px;">
|
|
<div id="addNoticeError" class="alert alert-danger" style="display: none;"></div>
|
|
<div id="addNoticeLoading" class="text-center" style="display: none;">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
<p class="mt-2">Adding notice...</p>
|
|
</div>
|
|
<form id="addNoticeForm" onsubmit="return false;">
|
|
{% csrf_token %}
|
|
<div class="mb-3">
|
|
<label for="noticeTitle" class="form-label">Title</label>
|
|
<input type="text" class="form-control" id="noticeTitle" name="title" required>
|
|
<div class="invalid-feedback">Please enter a title</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="noticeMessage" class="form-label">Message</label>
|
|
<textarea class="form-control" id="noticeMessage" name="message" rows="3" required></textarea>
|
|
<div class="invalid-feedback">Please enter a message</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="noticeImage" class="form-label">Image (Optional)</label>
|
|
<input type="file" class="form-control" id="noticeImage" name="image" accept="image/*">
|
|
</div>
|
|
<div class="d-flex justify-content-end gap-2">
|
|
<button type="button" class="btn" style="background-color: #9E9E9E; color: white;" onclick="closeAddNoticeModal()">Cancel</button>
|
|
<button type="button" class="btn" style="background-color: #3F51B5; color: white;" onclick="submitNoticeForm()">Post Notice</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|