updates
This commit is contained in:
1 parent
0868e7d9fb
commit
eee09e2e47
15 files changed
+1445
-137
No files matched your search
+92
-12
@@ -15,6 +15,7 @@ from app.models import (
|
||||
Student_Leave,
|
||||
StudentFeedback,
|
||||
CourseTracking,
|
||||
InstituteFeedback,
|
||||
)
|
||||
from django.forms import ModelForm
|
||||
from django.contrib.auth.models import User, Group
|
||||
@@ -29,6 +30,7 @@ class CustomAdminSite(admin.AdminSite):
|
||||
app_list = super().get_app_list(request)
|
||||
|
||||
model_map = {
|
||||
# Authentication Models
|
||||
"User": {
|
||||
"group": "Authentication",
|
||||
"group_label": "authentication",
|
||||
@@ -37,22 +39,66 @@ class CustomAdminSite(admin.AdminSite):
|
||||
"group": "Authentication",
|
||||
"group_label": "authentication",
|
||||
},
|
||||
"Institute": {"group": "Organization", "group_label": "organization"},
|
||||
"Batch": {"group": "Organization", "group_label": "organization"},
|
||||
"Notice": {"group": "Organization", "group_label": "organization"},
|
||||
"Course": {"group": "Organization", "group_label": "organization"},
|
||||
"Subject": {"group": "Organization", "group_label": "organization"},
|
||||
"Student": {"group": "Human Management", "group_label": "human_management"},
|
||||
"Staff": {"group": "Human Management", "group_label": "human_management"},
|
||||
"Routine": {"group": "Human Management", "group_label": "human_management"},
|
||||
"Staff_leave": {"group": "Human Management", "group_label": "human_management"},
|
||||
"Student_Leave": {"group": "Human Management", "group_label": "human_management"},
|
||||
"StudentFeedback": {"group": "Human Management", "group_label": "human_management"},
|
||||
"CourseTracking": {"group": "Human Management", "group_label": "human_management"},
|
||||
|
||||
# Organization Models
|
||||
"Institute": {
|
||||
"group": "Organization",
|
||||
"group_label": "organization",
|
||||
},
|
||||
"Batch": {
|
||||
"group": "Organization",
|
||||
"group_label": "organization",
|
||||
},
|
||||
"Notice": {
|
||||
"group": "Organization",
|
||||
"group_label": "organization",
|
||||
},
|
||||
"Course": {
|
||||
"group": "Organization",
|
||||
"group_label": "organization",
|
||||
},
|
||||
"Subject": {
|
||||
"group": "Organization",
|
||||
"group_label": "organization",
|
||||
},
|
||||
|
||||
# Human Management Models
|
||||
"Student": {
|
||||
"group": "Human Management",
|
||||
"group_label": "human_management",
|
||||
},
|
||||
"Staff": {
|
||||
"group": "Human Management",
|
||||
"group_label": "human_management",
|
||||
},
|
||||
"Routine": {
|
||||
"group": "Human Management",
|
||||
"group_label": "human_management",
|
||||
},
|
||||
"Staff_leave": {
|
||||
"group": "Human Management",
|
||||
"group_label": "human_management",
|
||||
},
|
||||
"Student_Leave": {
|
||||
"group": "Human Management",
|
||||
"group_label": "human_management",
|
||||
},
|
||||
"StudentFeedback": {
|
||||
"group": "Human Management",
|
||||
"group_label": "human_management",
|
||||
},
|
||||
"CourseTracking": {
|
||||
"group": "Human Management",
|
||||
"group_label": "human_management",
|
||||
},
|
||||
"Attendance": {
|
||||
"group": "Human Management",
|
||||
"group_label": "human_management",
|
||||
},
|
||||
"InstituteFeedback": {
|
||||
"group": "Human Management",
|
||||
"group_label": "human_management",
|
||||
},
|
||||
}
|
||||
|
||||
grouped_app_list = [{"name": "Main", "app_label": "dashboard", "models": []}]
|
||||
@@ -378,6 +424,40 @@ class CourseTrackingAdmin(admin.ModelAdmin):
|
||||
js = ("js/course_tracking_admin.js",)
|
||||
|
||||
|
||||
@admin.register(InstituteFeedback, site=custom_admin_site)
|
||||
class InstituteFeedbackAdmin(admin.ModelAdmin):
|
||||
list_display = ('display_name', 'institute', 'feedback_type', 'rating', 'is_anonymous', 'is_public', 'created_at')
|
||||
list_filter = ('feedback_type', 'rating', 'is_anonymous', 'is_public', 'created_at', 'institute')
|
||||
search_fields = ('user__name', 'institute__name', 'feedback_text')
|
||||
readonly_fields = ('created_at', 'updated_at')
|
||||
date_hierarchy = 'created_at'
|
||||
|
||||
def display_name(self, obj):
|
||||
return obj.display_name
|
||||
display_name.short_description = 'Student Name'
|
||||
|
||||
fieldsets = (
|
||||
('Feedback Information', {
|
||||
'fields': ('institute', 'user', 'feedback_type', 'rating', 'feedback_text')
|
||||
}),
|
||||
('Privacy Settings', {
|
||||
'fields': ('is_anonymous', 'is_public'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
('Timestamps', {
|
||||
'fields': ('created_at', 'updated_at'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
|
||||
def get_queryset(self, request):
|
||||
return super().get_queryset(request).select_related('user', 'institute')
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('admin/css/forms.css',)
|
||||
}
|
||||
js = ('admin/js/collapse.js',)
|
||||
|
||||
|
||||
custom_admin_site.register(User, UserAdmin)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 5.1.7 on 2025-03-25 19:41
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('app', '0004_alter_studentfeedback_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterUniqueTogether(
|
||||
name='studentfeedback',
|
||||
unique_together={('student', 'teacher')},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='studentfeedback',
|
||||
name='subject',
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,35 @@
|
||||
# Generated by Django 5.1.7 on 2025-03-25 19:44
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('app', '0005_alter_studentfeedback_unique_together_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='InstituteFeedback',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('feedback_type', models.CharField(choices=[('General', 'General'), ('Facilities', 'Facilities'), ('Teaching', 'Teaching'), ('Infrastructure', 'Infrastructure'), ('Administration', 'Administration')], default='General', max_length=20)),
|
||||
('rating', models.IntegerField(choices=[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)])),
|
||||
('feedback_text', models.TextField()),
|
||||
('is_anonymous', models.BooleanField(default=False)),
|
||||
('is_public', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('institute', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedbacks', to='app.institute')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='institute_feedbacks', to='app.student')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Institute Feedback',
|
||||
'verbose_name_plural': 'Institute Feedbacks',
|
||||
'ordering': ['-created_at'],
|
||||
'unique_together': {('user', 'institute', 'feedback_type')},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.1.7 on 2025-03-25 20:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('app', '0006_institutefeedback'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='student',
|
||||
name='is_superuser',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='student',
|
||||
name='is_active',
|
||||
field=models.BooleanField(default=True),
|
||||
),
|
||||
]
|
||||
+48
-3
@@ -76,6 +76,11 @@ class Student(AbstractUser):
|
||||
username = None
|
||||
email = None
|
||||
|
||||
# Add these fields
|
||||
is_active = models.BooleanField(default=True)
|
||||
is_staff = models.BooleanField(default=True) # This ensures admin access
|
||||
is_superuser = models.BooleanField(default=False)
|
||||
|
||||
user_permissions = models.ManyToManyField(
|
||||
"auth.Permission",
|
||||
verbose_name="user permissions",
|
||||
@@ -130,6 +135,11 @@ class Student(AbstractUser):
|
||||
"""Check if student has any groups (permissions)"""
|
||||
return self.groups.exists()
|
||||
|
||||
@property
|
||||
def is_authenticated(self):
|
||||
"""Required by Django's authentication system"""
|
||||
return True
|
||||
|
||||
def has_perm(self, perm, obj=None):
|
||||
"""Check if student has a specific permission"""
|
||||
if not self.is_active:
|
||||
@@ -431,7 +441,6 @@ class Student_Leave(models.Model):
|
||||
|
||||
class StudentFeedback(models.Model):
|
||||
student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='feedbacks')
|
||||
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, related_name='feedbacks')
|
||||
teacher = models.ForeignKey(Staff, on_delete=models.CASCADE, related_name='feedbacks')
|
||||
rating = models.IntegerField(choices=[(i, i) for i in range(1, 6)]) # 1-5 rating
|
||||
feedback_text = models.TextField()
|
||||
@@ -439,13 +448,49 @@ class StudentFeedback(models.Model):
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.student.name} - {self.subject.name} - {self.teacher.name}"
|
||||
return f"{self.student.name} - {self.teacher.name}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Student Feedback"
|
||||
verbose_name_plural = "Student Feedbacks"
|
||||
ordering = ['-created_at']
|
||||
unique_together = ['student', 'subject', 'teacher'] # Prevent duplicate feedbacks
|
||||
unique_together = ['student', 'teacher'] # Updated unique constraint
|
||||
|
||||
|
||||
class InstituteFeedback(models.Model):
|
||||
FEEDBACK_TYPE_CHOICES = (
|
||||
('General', 'General'),
|
||||
('Facilities', 'Facilities'),
|
||||
('Teaching', 'Teaching'),
|
||||
('Infrastructure', 'Infrastructure'),
|
||||
('Administration', 'Administration'),
|
||||
)
|
||||
|
||||
institute = models.ForeignKey(Institute, on_delete=models.CASCADE, related_name='feedbacks')
|
||||
user = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='institute_feedbacks')
|
||||
feedback_type = models.CharField(max_length=20, choices=FEEDBACK_TYPE_CHOICES, default='General')
|
||||
rating = models.IntegerField(choices=[(i, i) for i in range(1, 6)]) # 1-5 rating
|
||||
feedback_text = models.TextField()
|
||||
is_anonymous = models.BooleanField(default=False)
|
||||
is_public = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
if self.is_anonymous:
|
||||
return f"Anonymous - {self.institute.name} - {self.feedback_type}"
|
||||
return f"{self.user.name} - {self.institute.name} - {self.feedback_type}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Institute Feedback"
|
||||
verbose_name_plural = "Institute Feedbacks"
|
||||
ordering = ['-created_at']
|
||||
unique_together = ['user', 'institute', 'feedback_type'] # One feedback per type per user
|
||||
|
||||
@property
|
||||
def display_name(self):
|
||||
"""Return anonymous if feedback is anonymous, otherwise return user's name"""
|
||||
return 'Anonymous' if self.is_anonymous else self.user.name
|
||||
|
||||
|
||||
class CourseTracking(models.Model):
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import django.db
|
||||
from rest_framework import serializers
|
||||
from .models import (Attendance, AttendanceRecord, Batch, Course, Expenses, FCMDevice, Fee,
|
||||
Institute, Routine, Salary, Staff, Student, StudentRoutine, Subtopic, Topic, Vendor)
|
||||
|
||||
class FCMDeviceSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = FCMDevice
|
||||
fields = ['id', 'token']
|
||||
depth = 10
|
||||
|
||||
class InstituteSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Institute
|
||||
depth = 10
|
||||
fields = ['id', 'name', 'phone', 'email', 'address', 'pan_no', 'reg_no', 'logo', 'description']
|
||||
|
||||
|
||||
class SubtopicSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Subtopic
|
||||
depth = 10
|
||||
fields = ['id', 'name']
|
||||
|
||||
|
||||
class TopicSerializer(serializers.ModelSerializer):
|
||||
subtopics = SubtopicSerializer(read_only=True, many=True).data
|
||||
class Meta:
|
||||
model = Topic
|
||||
fields = ['id', 'name', 'subtopics']
|
||||
depth = 10
|
||||
|
||||
|
||||
class CourseSerializer(serializers.ModelSerializer):
|
||||
topics = TopicSerializer(read_only=True, many=True).data
|
||||
class Meta:
|
||||
model = Course
|
||||
depth = 3
|
||||
fields = ['id', 'name', 'amount', 'duration', 'duration_type', 'remarks', 'topics']
|
||||
|
||||
|
||||
|
||||
class StudentSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Student
|
||||
depth = 10
|
||||
fields = [
|
||||
'id', 'name', 'status', 'gender', 'birth_date', 'email', 'phone',
|
||||
'temporary_address', 'permanent_address', 'marital_status', 'parent_name',
|
||||
'parent_phone', 'citizenship_no', 'qualification', 'image', 'batches', 'courses',
|
||||
'subtotal', 'discount', 'total', 'paid', 'remaining', 'joining_date', 'file', 'remarks'
|
||||
]
|
||||
|
||||
|
||||
|
||||
class BatchSerializer(serializers.ModelSerializer):
|
||||
students = StudentSerializer(many=True).data
|
||||
class Meta:
|
||||
model = Batch
|
||||
depth = 10
|
||||
fields = ['id', 'name', 'start_date', 'end_date', 'students']
|
||||
|
||||
|
||||
|
||||
class FeeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Fee
|
||||
fields = [
|
||||
'id', 'student', 'old_total', 'old_paid', 'old_remaining', 'balance_amount',
|
||||
'payment_date', 'payment_method', 'total', 'paid', 'remaining', 'remarks'
|
||||
]
|
||||
depth = 10
|
||||
|
||||
class VendorSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Vendor
|
||||
depth = 10
|
||||
fields = ['id', 'name', 'address', 'phone']
|
||||
|
||||
class ExpensesSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Expenses
|
||||
depth = 10
|
||||
fields = [
|
||||
'id', 'name', 'amount', 'date', 'vendor', 'paid', 'credit', 'credit_date', 'remarks'
|
||||
]
|
||||
|
||||
class SalarySerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Salary
|
||||
depth = 10
|
||||
fields = '__all__'
|
||||
|
||||
class StaffSerializer(serializers.ModelSerializer):
|
||||
salaries = SalarySerializer(many=True).data
|
||||
class Meta:
|
||||
model = Staff
|
||||
depth = 10
|
||||
fields = '__all__'
|
||||
# fields = [
|
||||
# 'id', 'name', 'gender', 'designation', 'birth_date', 'phone', 'email',
|
||||
# 'temporary_address', 'permanent_address', 'marital_status', 'parent_name',
|
||||
# 'parent_phone', 'citizenship_no', 'passport', 'image', 'monthly', 'pending_salary', 'remarks', 'salaries'
|
||||
# ]
|
||||
|
||||
class RoutineSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Routine
|
||||
depth = 10
|
||||
fields = ['id', 'name', 'start_time', 'end_time', 'teacher']
|
||||
|
||||
class StudentRoutineSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = StudentRoutine
|
||||
depth = 10
|
||||
fields = ['id', 'student', 'routine', 'course', 'course_status']
|
||||
|
||||
|
||||
class AttendanceRecordSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AttendanceRecord
|
||||
depth = 10
|
||||
fields = ['id', 'attendance', 'student', 'student_attend']
|
||||
|
||||
|
||||
class AttendanceSerializer(serializers.ModelSerializer):
|
||||
records = AttendanceRecordSerializer(many=True).data
|
||||
class Meta:
|
||||
model = Attendance
|
||||
depth = 1
|
||||
fields = ['id', 'date', 'routine', 'teacher', 'teacher_attend', 'class_status', 'records']
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1,11 @@
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter
|
||||
def get_range(value):
|
||||
"""
|
||||
Returns a range of numbers from 0 to value-1
|
||||
Used for creating star ratings
|
||||
"""
|
||||
return range(value)
|
||||
@@ -0,0 +1,8 @@
|
||||
from django.urls import path
|
||||
from app import views
|
||||
|
||||
urlpatterns = [
|
||||
path('dashboard/', views.dashboard, name='dashboard'),
|
||||
path('student-dashboard/', views.studentDashboard, name='studentDashboard'),
|
||||
path('teacher-dashboard/', views.teacherDashboard, name='teacherDashboard'),
|
||||
]
|
||||
+330
-8
@@ -1,6 +1,19 @@
|
||||
from app.models import FCMDevice
|
||||
from django.shortcuts import render
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.http import JsonResponse, HttpResponse
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib import messages
|
||||
from django.db.models import Count, Q, Avg
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
from .models import (
|
||||
Student, Course, CourseTracking,
|
||||
Student_Leave, Staff_leave,
|
||||
Attendance, AttendanceRecord,
|
||||
StudentFeedback, Routine, StudentRoutine,
|
||||
Staff
|
||||
)
|
||||
from app.admin import custom_admin_site
|
||||
# Create your views here.
|
||||
|
||||
|
||||
@@ -19,13 +32,13 @@ def showFirebaseJS(request):
|
||||
importScripts('https://www.gstatic.com/firebasejs/11.0.2/firebase-messaging-compat.js');
|
||||
|
||||
firebase.initializeApp({
|
||||
apiKey: "",
|
||||
authDomain: "",
|
||||
projectId: "",
|
||||
storageBucket: "",
|
||||
messagingSenderId: "",
|
||||
appId: "",
|
||||
measurementId: ""
|
||||
apiKey: "AIzaSyAVzOJh5v8_oZg4MoQkq0CzrAbeyHGn4z4",
|
||||
authDomain: "institute-management-sys-a2d20.firebaseapp.com",
|
||||
projectId: "institute-management-sys-a2d20",
|
||||
storageBucket: "institute-management-sys-a2d20.firebasestorage.app",
|
||||
messagingSenderId: "777235542100",
|
||||
appId: "1:777235542100:web:4d0cb9553918cb3cc23dc1",
|
||||
measurementId: "G-4QP9GYV0SJ"
|
||||
});
|
||||
|
||||
const messaging = firebase.messaging();
|
||||
@@ -42,3 +55,312 @@ def showFirebaseJS(request):
|
||||
});
|
||||
"""
|
||||
return HttpResponse(data, content_type="text/javascript")
|
||||
|
||||
@login_required
|
||||
def dashboard(request):
|
||||
user = request.user
|
||||
user_groups = user.groups.values_list('name', flat=True)
|
||||
if 'Student' in user_groups:
|
||||
return redirect('studentDashboard')
|
||||
elif 'Teacher' in user_groups:
|
||||
return redirect('teacherDashboard')
|
||||
# Get current date and date ranges
|
||||
today = timezone.now().date()
|
||||
last_week = today - timedelta(days=7)
|
||||
last_month = today - timedelta(days=30)
|
||||
|
||||
# Student Statistics
|
||||
total_students = Student.objects.count()
|
||||
active_students = Student.objects.filter(status='Active').count()
|
||||
completed_students = Student.objects.filter(status='Completed').count()
|
||||
on_leave_students = Student.objects.filter(status='Leave').count()
|
||||
|
||||
# Course Statistics
|
||||
total_courses = Course.objects.count()
|
||||
active_courses = Course.objects.filter(student_trackings__progress_status='In Progress').distinct().count()
|
||||
completed_courses = Course.objects.filter(student_trackings__progress_status='Completed').distinct().count()
|
||||
|
||||
# Course Tracking Statistics
|
||||
course_trackings = CourseTracking.objects.all()
|
||||
in_progress_trackings = course_trackings.filter(progress_status='In Progress')
|
||||
completed_trackings = course_trackings.filter(progress_status='Completed')
|
||||
dropped_trackings = course_trackings.filter(progress_status='Dropped')
|
||||
|
||||
# Recent Leave Requests
|
||||
recent_student_leaves = Student_Leave.objects.filter(
|
||||
created_at__gte=last_week
|
||||
).order_by('-created_at')[:5]
|
||||
|
||||
recent_staff_leaves = Staff_leave.objects.filter(
|
||||
created_at__gte=last_week
|
||||
).order_by('-created_at')[:5]
|
||||
|
||||
# Attendance Statistics
|
||||
today_attendance = Attendance.objects.filter(date=today).count()
|
||||
today_present = AttendanceRecord.objects.filter(
|
||||
attendance__date=today,
|
||||
student_attend=True
|
||||
).count()
|
||||
|
||||
# Recent Feedback
|
||||
recent_feedback = StudentFeedback.objects.order_by('-created_at')[:5]
|
||||
|
||||
# Course Progress Overview
|
||||
course_progress = []
|
||||
for course in Course.objects.all():
|
||||
trackings = course.student_trackings.all()
|
||||
if trackings.exists():
|
||||
avg_progress = trackings.aggregate(avg_progress=Avg('completion_percentage'))['avg_progress']
|
||||
course_progress.append({
|
||||
'course': course,
|
||||
'total_students': trackings.count(),
|
||||
'in_progress': trackings.filter(progress_status='In Progress').count(),
|
||||
'completed': trackings.filter(progress_status='Completed').count(),
|
||||
'average_progress': round(avg_progress, 1) if avg_progress else 0
|
||||
})
|
||||
|
||||
# Monthly Statistics
|
||||
monthly_stats = {
|
||||
'new_students': Student.objects.filter(joining_date__gte=last_month).count(),
|
||||
'completed_courses': completed_trackings.filter(actual_end_date__gte=last_month).count(),
|
||||
'attendance_rate': round(
|
||||
(AttendanceRecord.objects.filter(
|
||||
attendance__date__gte=last_month,
|
||||
student_attend=True
|
||||
).count() /
|
||||
AttendanceRecord.objects.filter(
|
||||
attendance__date__gte=last_month
|
||||
).count() * 100) if AttendanceRecord.objects.filter(
|
||||
attendance__date__gte=last_month
|
||||
).exists() else 0,
|
||||
1
|
||||
)
|
||||
}
|
||||
|
||||
context = {
|
||||
**custom_admin_site.each_context(request), # This adds the admin context, including the sidebar.
|
||||
# Student Statistics
|
||||
'total_students': total_students,
|
||||
'active_students': active_students,
|
||||
'completed_students': completed_students,
|
||||
'on_leave_students': on_leave_students,
|
||||
|
||||
# Course Statistics
|
||||
'total_courses': total_courses,
|
||||
'active_courses': active_courses,
|
||||
'completed_courses': completed_courses,
|
||||
|
||||
# Course Tracking Statistics
|
||||
'in_progress_trackings': in_progress_trackings.count(),
|
||||
'completed_trackings': completed_trackings.count(),
|
||||
'dropped_trackings': dropped_trackings.count(),
|
||||
|
||||
# Leave Requests
|
||||
'recent_student_leaves': recent_student_leaves,
|
||||
'recent_staff_leaves': recent_staff_leaves,
|
||||
|
||||
# Attendance Statistics
|
||||
'today_attendance': today_attendance,
|
||||
'today_present': today_present,
|
||||
'attendance_rate': round(
|
||||
(today_present / today_attendance * 100) if today_attendance > 0 else 0,
|
||||
1
|
||||
),
|
||||
|
||||
# Recent Feedback
|
||||
'recent_feedback': recent_feedback,
|
||||
|
||||
# Course Progress
|
||||
'course_progress': course_progress,
|
||||
|
||||
# Monthly Statistics
|
||||
'monthly_stats': monthly_stats,
|
||||
}
|
||||
|
||||
return render(request, 'dashboard.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def studentDashboard(request):
|
||||
"""View to display the student dashboard with their personal information and progress"""
|
||||
student = request.user
|
||||
today = timezone.now().date()
|
||||
last_week = today - timedelta(days=7)
|
||||
last_month = today - timedelta(days=30)
|
||||
|
||||
# Course Progress
|
||||
course_trackings = CourseTracking.objects.filter(student=student)
|
||||
total_courses = course_trackings.count()
|
||||
completed_courses = course_trackings.filter(progress_status='Completed').count()
|
||||
in_progress_courses = course_trackings.filter(progress_status='In Progress').count()
|
||||
completion_percentage = (completed_courses / total_courses * 100) if total_courses > 0 else 0
|
||||
|
||||
# Course Details
|
||||
course_details = {}
|
||||
for tracking in course_trackings:
|
||||
course_details[tracking.course.name] = {
|
||||
'start_date': tracking.start_date,
|
||||
'end_date': tracking.end_date,
|
||||
'completion_percentage': tracking.completion_percentage,
|
||||
'progress_status': tracking.progress_status,
|
||||
'book': tracking.course.book if hasattr(tracking.course, 'book') else None,
|
||||
'topics': [] # This will be populated if you have topic tracking
|
||||
}
|
||||
|
||||
# Attendance Statistics
|
||||
attendance_records = AttendanceRecord.objects.filter(student=student)
|
||||
total_classes = attendance_records.count()
|
||||
classes_attended = attendance_records.filter(student_attend=True).count()
|
||||
attendance_percentage = (classes_attended / total_classes * 100) if total_classes > 0 else 0
|
||||
|
||||
# Calculate attendance streaks
|
||||
current_streak = 0
|
||||
longest_streak = 0
|
||||
temp_streak = 0
|
||||
|
||||
for record in attendance_records.order_by('-attendance__date'):
|
||||
if record.student_attend:
|
||||
temp_streak += 1
|
||||
if temp_streak > longest_streak:
|
||||
longest_streak = temp_streak
|
||||
if record.attendance.date >= last_week:
|
||||
current_streak = temp_streak
|
||||
else:
|
||||
temp_streak = 0
|
||||
|
||||
# Recent Leave Requests
|
||||
recent_leaves = Student_Leave.objects.filter(
|
||||
student=student
|
||||
).order_by('-created_at')[:5]
|
||||
|
||||
# Recent Feedback
|
||||
recent_feedback = StudentFeedback.objects.filter(
|
||||
student=student
|
||||
).order_by('-created_at')[:5]
|
||||
|
||||
context = {
|
||||
**custom_admin_site.each_context(request), # This adds the admin context, including the sidebar.
|
||||
'student': student,
|
||||
'title': 'Student Dashboard',
|
||||
|
||||
# Course Summary
|
||||
'course_summary': {
|
||||
'total_courses': total_courses,
|
||||
'completed_courses': completed_courses,
|
||||
'ongoing_courses': in_progress_courses,
|
||||
'completion_percentage': completion_percentage,
|
||||
'details': course_details
|
||||
},
|
||||
|
||||
# Attendance Summary
|
||||
'attendance_summary': {
|
||||
'total_classes': total_classes,
|
||||
'classes_attended': classes_attended,
|
||||
'attendance_percentage': attendance_percentage,
|
||||
'current_streak': current_streak,
|
||||
'longest_streak': longest_streak
|
||||
},
|
||||
|
||||
# Recent Activity
|
||||
'recent_leaves': recent_leaves,
|
||||
'recent_feedback': recent_feedback,
|
||||
}
|
||||
|
||||
return render(request, 'student_dashboard.html', context)
|
||||
|
||||
@login_required
|
||||
def teacherDashboard(request):
|
||||
"""View to display the teacher dashboard with their classes and student information"""
|
||||
teacher = Staff.objects.get(phone=request.user.phone) # Get the Staff object
|
||||
today = timezone.now().date()
|
||||
current_time = timezone.now().time()
|
||||
|
||||
# Get today's routines through attendance
|
||||
today_routines = Routine.objects.filter(
|
||||
teacher=teacher,
|
||||
attendances__date=today
|
||||
).order_by('start_time')
|
||||
|
||||
# Process routines to include status and attendance
|
||||
processed_routines = []
|
||||
for routine in today_routines:
|
||||
attendance = routine.attendances.filter(date=today).first()
|
||||
|
||||
total_students = StudentRoutine.objects.filter(routine=routine).count()
|
||||
attendance_count = 0 if not attendance else AttendanceRecord.objects.filter(
|
||||
attendance=attendance,
|
||||
student_attend=True
|
||||
).count()
|
||||
|
||||
processed_routines.append({
|
||||
'name': routine.name,
|
||||
'start_time': routine.start_time,
|
||||
'end_time': routine.end_time,
|
||||
'is_completed': routine.end_time < current_time,
|
||||
'is_ongoing': routine.start_time <= current_time <= routine.end_time,
|
||||
'total_students': total_students,
|
||||
'attendance_count': attendance_count
|
||||
})
|
||||
|
||||
# Calculate class statistics
|
||||
total_classes = today_routines.count()
|
||||
completed_classes = len([r for r in processed_routines if r['is_completed']])
|
||||
|
||||
# Get student statistics
|
||||
total_students = StudentRoutine.objects.filter(
|
||||
routine__teacher=teacher
|
||||
).values('student').distinct().count()
|
||||
|
||||
# Calculate present students for today
|
||||
present_students = AttendanceRecord.objects.filter(
|
||||
attendance__routine__teacher=teacher,
|
||||
attendance__date=today,
|
||||
student_attend=True
|
||||
).count()
|
||||
|
||||
# Get teacher's feedback statistics
|
||||
feedback = StudentFeedback.objects.filter(teacher=teacher)
|
||||
avg_rating = feedback.aggregate(Avg('rating'))['rating__avg'] or 0
|
||||
avg_rating_rounded = int(avg_rating)
|
||||
remaining_stars = 5 - avg_rating_rounded
|
||||
|
||||
# Get leave statistics
|
||||
leaves = Staff_leave.objects.filter(staff=teacher)
|
||||
pending_leaves = leaves.filter(status=0).count()
|
||||
approved_leaves = leaves.filter(status=1).count()
|
||||
rejected_leaves = leaves.filter(status=2).count()
|
||||
|
||||
# Get recent activity
|
||||
recent_leaves = leaves.order_by('-created_at')[:5]
|
||||
recent_feedback = feedback.order_by('-created_at')[:5]
|
||||
|
||||
context = {
|
||||
**custom_admin_site.each_context(request), # This adds the admin context, including the sidebar.
|
||||
'teacher': teacher,
|
||||
'title': 'Teacher Dashboard',
|
||||
|
||||
# Class Statistics
|
||||
'today_classes': total_classes,
|
||||
'completed_classes': completed_classes,
|
||||
'today_routines': processed_routines,
|
||||
|
||||
# Student Statistics
|
||||
'total_students': total_students,
|
||||
'present_students': present_students,
|
||||
|
||||
# Feedback Statistics
|
||||
'avg_rating': avg_rating,
|
||||
'avg_rating_rounded': avg_rating_rounded,
|
||||
'remaining_stars': remaining_stars,
|
||||
|
||||
# Leave Statistics
|
||||
'pending_leaves': pending_leaves,
|
||||
'approved_leaves': approved_leaves,
|
||||
'rejected_leaves': rejected_leaves,
|
||||
|
||||
# Recent Activity
|
||||
'recent_leaves': recent_leaves,
|
||||
'recent_feedback': recent_feedback,
|
||||
}
|
||||
|
||||
return render(request, 'teacher_dashboard.html', context)
|
||||
@@ -112,7 +112,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||
|
||||
LANGUAGE_CODE = "en-us"
|
||||
|
||||
TIME_ZONE = "UTC"
|
||||
TIME_ZONE = "Asia/Kathmandu"
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
@@ -123,147 +123,84 @@ USE_TZ = True
|
||||
# https://docs.djangoproject.com/en/5.1/howto/static-files/
|
||||
|
||||
STATIC_URL = "static/"
|
||||
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(BASE_DIR, "static"),
|
||||
]
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
|
||||
STATICFILES_FINDERS = [
|
||||
"django.contrib.staticfiles.finders.FileSystemFinder",
|
||||
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
|
||||
]
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
MEDIA_URL = "/media/"
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||
|
||||
JAZZMIN_SETTINGS = {
|
||||
# title of the window (Will default to current_admin_site.site_title if absent or None)
|
||||
"site_title": "SMS",
|
||||
# Title on the login screen (19 chars max) (defaults to current_admin_site.site_header if absent or None)
|
||||
"site_header": "SMS",
|
||||
# Title on the brand (19 chars max) (defaults to current_admin_site.site_header if absent or None)
|
||||
"site_brand": "SMS",
|
||||
# Logo to use for your site, must be present in static files, used for brand on top left
|
||||
# "site_logo": BASE_DIR / "static/img/logo.jpg",
|
||||
# Logo to use for your site, must be present in static files, used for login form logo (defaults to site_logo)
|
||||
"login_logo": None,
|
||||
# Logo to use for login form in dark themes (defaults to login_logo)
|
||||
"login_logo_dark": None,
|
||||
# CSS classes that are applied to the logo above
|
||||
"site_logo_classes": "img-circle",
|
||||
# Relative path to a favicon for your site, will default to site_logo if absent (ideally 32x32 px)
|
||||
# "site_icon": BASE_DIR / "static/img/logo.jpg",
|
||||
# Welcome text on the login screen
|
||||
"welcome_sign": "WELCOME TO SMS",
|
||||
# Copyright on the footer
|
||||
"copyright": "S4NKALP: SMS",
|
||||
# List of model admins to search from the search bar, search bar omitted if excluded
|
||||
# If you want to use a single search field you dont need to use a list, you can use a simple string
|
||||
"search_model": ["app.Student", "app.Course"],
|
||||
# Field name on user model that contains avatar ImageField/URLField/Charfield or a callable that receives the user
|
||||
# "user_avatar": get_user_avatar,
|
||||
############
|
||||
# Top Menu #
|
||||
############
|
||||
# Links to put along the top menu
|
||||
"topmenu_links": [
|
||||
# Url that gets reversed (Permissions can be added)
|
||||
{"name": "Home", "url": "admin:index", "permissions": ["auth.view_user"]},
|
||||
{"name": "Dashboard", "url": "/app/dashboard", "permissions": []},
|
||||
# model admin to link to (Permissions checked against model)
|
||||
{"model": "app.Student"},
|
||||
# App with dropdown menu to all its models pages (Permissions checked against models)
|
||||
{"app": "app"},
|
||||
],
|
||||
#############
|
||||
# User Menu #
|
||||
#############
|
||||
# Additional links to include in the user menu on the top right ("app" url type is not allowed)
|
||||
"usermenu_links": [
|
||||
# {"name": "Support", "url": "https://github.com/farridav/django-jazzmin/issues", "new_window": True},
|
||||
{"model": "auth.user"}
|
||||
],
|
||||
#############
|
||||
# Side Menu #
|
||||
#############
|
||||
# Whether to display the side menu
|
||||
"show_sidebar": True,
|
||||
# Whether to aut expand the menu
|
||||
"navigation_expanded": True,
|
||||
# Hide these apps when generating side menu e.g (auth)
|
||||
"hide_apps": [],
|
||||
# Hide these models when generating side menu (e.g auth.user)
|
||||
"hide_models": [],
|
||||
# List of apps (and/or models) to base side menu ordering off of (does not need to contain all apps/models)
|
||||
"order_with_respect_to": [
|
||||
"dashboard",
|
||||
"authentication",
|
||||
"organization",
|
||||
"human_management",
|
||||
"transaction",
|
||||
"report",
|
||||
"organization.institute",
|
||||
"organization.batch",
|
||||
"organization.course",
|
||||
"human_management.student",
|
||||
"human_management.staff",
|
||||
"human_management.vendor",
|
||||
"human_management.routine",
|
||||
"human_management.attendance",
|
||||
"human_management.course_tracking",
|
||||
"transaction.fee",
|
||||
"transaction.salary",
|
||||
"transaction.expenses",
|
||||
"authentication","organization","human_management"
|
||||
"organization.institute", "organization.batch", "organization.course",
|
||||
"human_management.student", "human_management.staff", "human_management.routine",
|
||||
"human_management.staff_leave", "human_management.student_leave",
|
||||
"human_management.studentfeedback", "human_management.coursetracking",
|
||||
"human_management.attendance", "human_management.institutefeedback"
|
||||
],
|
||||
# Custom links to append to app groups, keyed on app name
|
||||
"icons": {
|
||||
"auth": "fas fa-users-cog",
|
||||
"auth.user": "fas fa-user",
|
||||
"auth.Group": "fas fa-users",
|
||||
"app.Student": "fas fa-user-graduate",
|
||||
"app.Course": "fas fa-book",
|
||||
"app.Staff": "fas fa-chalkboard-teacher",
|
||||
"app.Attendance": "fas fa-calendar-check",
|
||||
"app.CourseTracking": "fas fa-chart-line",
|
||||
"app.StudentFeedback": "fas fa-comment",
|
||||
},
|
||||
"default_icon_parents": "fas fa-chevron-circle-right",
|
||||
"default_icon_children": "fas fa-circle",
|
||||
"related_modal_active": False,
|
||||
"custom_css": None,
|
||||
"custom_js": None,
|
||||
"show_ui_builder": False,
|
||||
"changeform_format": "horizontal_tabs",
|
||||
"changeform_format_overrides": {"auth.user": "collapsible", "auth.group": "vertical_tabs"},
|
||||
"language_chooser": False,
|
||||
"custom_links": {
|
||||
"app": [
|
||||
{
|
||||
"name": "Dashboard",
|
||||
"url": "/app/dashboard",
|
||||
"icon": "fas fa-circle",
|
||||
"permissions": [],
|
||||
"icon": "fas fa-tachometer-alt",
|
||||
"permissions": []
|
||||
}
|
||||
]
|
||||
},
|
||||
# Custom icons for side menu apps/models See https://fontawesome.com/icons?d=gallery&m=free&v=5.0.0,5.0.1,5.0.10,5.0.11,5.0.12,5.0.13,5.0.2,5.0.3,5.0.4,5.0.5,5.0.6,5.0.7,5.0.8,5.0.9,5.1.0,5.1.1,5.2.0,5.3.0,5.3.1,5.4.0,5.4.1,5.4.2,5.13.0,5.12.0,5.11.2,5.11.1,5.10.0,5.9.0,5.8.2,5.8.1,5.7.2,5.7.1,5.7.0,5.6.3,5.5.0,5.4.2
|
||||
# for the full list of 5.13.0 free icon classes
|
||||
"icons": {
|
||||
"auth": "fas fa-users-cog",
|
||||
"auth.user": "fas fa-user",
|
||||
"auth.Group": "fas fa-users",
|
||||
},
|
||||
# Icons that are used when one is not manually specified
|
||||
"default_icon_parents": "fas fa-chevron-circle-right",
|
||||
"default_icon_children": "fas fa-circle",
|
||||
#################
|
||||
# Related Modal #
|
||||
#################
|
||||
# Use modals instead of popups
|
||||
"related_modal_active": False,
|
||||
#############
|
||||
# UI Tweaks #
|
||||
#############
|
||||
# Relative paths to custom CSS/JS scripts (must be present in static files)
|
||||
"custom_css": "css/main.css",
|
||||
"custom_js": None,
|
||||
# Whether to link font from fonts.googleapis.com (use custom_css to supply font otherwise)
|
||||
"use_google_fonts_cdn": True,
|
||||
# Whether to show the UI customizer on the sidebar
|
||||
"show_ui_builder": False,
|
||||
###############
|
||||
# Change view #
|
||||
###############
|
||||
# Render out the change view as a single form, or in tabs, current options are
|
||||
# - single
|
||||
# - horizontal_tabs (default)
|
||||
# - vertical_tabs
|
||||
# - collapsible
|
||||
# - carousel
|
||||
"changeform_format": "horizontal_tabs",
|
||||
# override change forms on a per modeladmin basis
|
||||
"changeform_format_overrides": {
|
||||
"auth.user": "collapsible",
|
||||
"auth.group": "vertical_tabs",
|
||||
},
|
||||
# Add a language dropdown into the admin
|
||||
# "language_chooser": True,
|
||||
}
|
||||
|
||||
|
||||
JAZZMIN_UI_TWEAKS = {
|
||||
"navbar_small_text": False,
|
||||
"footer_small_text": False,
|
||||
@@ -284,14 +221,14 @@ JAZZMIN_UI_TWEAKS = {
|
||||
"sidebar_nav_compact_style": False,
|
||||
"sidebar_nav_legacy_style": False,
|
||||
"sidebar_nav_flat_style": False,
|
||||
"theme": "lumen",
|
||||
"theme": "default",
|
||||
"dark_mode_theme": None,
|
||||
"button_classes": {
|
||||
"primary": "btn-outline-primary",
|
||||
"secondary": "btn-outline-secondary",
|
||||
"info": "btn-outline-info",
|
||||
"warning": "btn-outline-warning",
|
||||
"danger": "btn-outline-danger",
|
||||
"success": "btn-outline-success",
|
||||
},
|
||||
"primary": "btn-primary",
|
||||
"secondary": "btn-secondary",
|
||||
"info": "btn-info",
|
||||
"warning": "btn-warning",
|
||||
"danger": "btn-danger",
|
||||
"success": "btn-success"
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,11 @@ Including another URLconf
|
||||
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from app.admin import custom_admin_site
|
||||
|
||||
urlpatterns = [
|
||||
# path("admin/", admin.site.urls),
|
||||
path("", custom_admin_site.urls, name="admin")
|
||||
]
|
||||
path("app/", include("app.urls")),
|
||||
path("", custom_admin_site.urls),
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
@@ -0,0 +1,225 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load static %}
|
||||
{% load app_filters %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<!-- Quick Stats Row -->
|
||||
<div class="row">
|
||||
<!-- Student Stats -->
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="small-box bg-info">
|
||||
<div class="inner">
|
||||
<h3>{{ total_students }}</h3>
|
||||
<p>Total Students</p>
|
||||
<div class="mt-2">
|
||||
<small>Active: {{ active_students }}</small> |
|
||||
<small>Completed: {{ completed_students }}</small> |
|
||||
<small>On Leave: {{ on_leave_students }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="fas fa-users"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Course Stats -->
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="small-box bg-success">
|
||||
<div class="inner">
|
||||
<h3>{{ total_courses }}</h3>
|
||||
<p>Course Statistics</p>
|
||||
<div class="mt-2">
|
||||
<small>Active: {{ active_courses }}</small> |
|
||||
<small>Completed: {{ completed_courses }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="fas fa-book"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Course Tracking Stats -->
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="small-box bg-warning">
|
||||
<div class="inner">
|
||||
<h3>{{ in_progress_trackings }}</h3>
|
||||
<p>Course Progress</p>
|
||||
<div class="mt-2">
|
||||
<small>In Progress</small> |
|
||||
<small>Completed: {{ completed_trackings }}</small> |
|
||||
<small>Dropped: {{ dropped_trackings }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Attendance Stats -->
|
||||
<div class="col-xl-3 col-md-6">
|
||||
<div class="small-box bg-danger">
|
||||
<div class="inner">
|
||||
<h3>{{ today_present }}/{{ today_attendance }}</h3>
|
||||
<p>Today's Attendance</p>
|
||||
<div class="mt-2">
|
||||
<small>Rate: {{ attendance_rate }}%</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="icon">
|
||||
<i class="fas fa-calendar-check"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content Row -->
|
||||
<div class="row">
|
||||
<!-- Course Progress Chart -->
|
||||
<div class="col-xl-8 col-lg-7">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Course Progress Overview</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Course</th>
|
||||
<th>Total Students</th>
|
||||
<th>In Progress</th>
|
||||
<th>Completed</th>
|
||||
<th>Avg Progress</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for progress in course_progress %}
|
||||
<tr>
|
||||
<td>{{ progress.course.name }}</td>
|
||||
<td>{{ progress.total_students }}</td>
|
||||
<td>{{ progress.in_progress }}</td>
|
||||
<td>{{ progress.completed }}</td>
|
||||
<td>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" style="width: {{ progress.average_progress }}%;"
|
||||
aria-valuenow="{{ progress.average_progress }}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100">
|
||||
{{ progress.average_progress }}%
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<div class="col-xl-4 col-lg-5">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Recent Activity</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Recent Leave Requests -->
|
||||
<h5>Recent Leave Requests</h5>
|
||||
<div class="mb-4">
|
||||
<h6 class="text-primary">Students</h6>
|
||||
{% for leave in recent_student_leaves %}
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">{{ leave.created_at|date:"M d, Y" }}</small>
|
||||
<div>{{ leave.student.name }} - {{ leave.get_status_display }}</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-muted">No recent student leave requests</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<h6 class="text-primary">Staff</h6>
|
||||
{% for leave in recent_staff_leaves %}
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">{{ leave.created_at|date:"M d, Y" }}</small>
|
||||
<div>{{ leave.staff.name }} - {{ leave.get_status_display }}</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-muted">No recent staff leave requests</p>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Recent Feedback -->
|
||||
<h5 class="mt-4">Recent Feedback</h5>
|
||||
{% for feedback in recent_feedback %}
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">{{ feedback.created_at|date:"M d, Y" }}</small>
|
||||
<div>{{ feedback.student.name }} - {{ feedback.subject.name }}</div>
|
||||
<div class="text-warning">
|
||||
{% for i in feedback.rating|get_range %}
|
||||
<i class="fas fa-star"></i>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-muted">No recent feedback</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Monthly Statistics -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Monthly Statistics</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="info-box">
|
||||
<span class="info-box-icon bg-info">
|
||||
<i class="fas fa-user-plus"></i>
|
||||
</span>
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">New Students</span>
|
||||
<span class="info-box-number">{{ monthly_stats.new_students }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="info-box">
|
||||
<span class="info-box-icon bg-success">
|
||||
<i class="fas fa-graduation-cap"></i>
|
||||
</span>
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">Completed Courses</span>
|
||||
<span class="info-box-number">{{ monthly_stats.completed_courses }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="info-box">
|
||||
<span class="info-box-icon bg-warning">
|
||||
<i class="fas fa-chart-bar"></i>
|
||||
</span>
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">Monthly Attendance Rate</span>
|
||||
<span class="info-box-number">{{ monthly_stats.attendance_rate }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,217 @@
|
||||
{% extends 'admin/base_site.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<!-- Welcome Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card bg-primary text-white">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
{% if student.image %}
|
||||
<img src="{{ student.image.url }}" alt="{{ student.name }}" class="rounded-circle me-3" style="width: 60px; height: 60px; object-fit: cover;">
|
||||
{% endif %}
|
||||
<div>
|
||||
<h2 class="mb-0">Welcome, {{ student.name }}!</h2>
|
||||
<p class="mb-0">Student ID: {{ student.id }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Stats Row -->
|
||||
<div class="row mb-4">
|
||||
<!-- Course Progress Card -->
|
||||
<div class="col-xl-4 col-md-6 mb-4">
|
||||
<div class="card border-left-primary h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">Course Progress</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ course_summary.completion_percentage|floatformat:1 }}%</div>
|
||||
<div class="mt-2">
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" style="width: {{ course_summary.completion_percentage }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 small">
|
||||
<span class="me-2">Completed: {{ course_summary.completed_courses }}/{{ course_summary.total_courses }}</span>
|
||||
<span>Ongoing: {{ course_summary.ongoing_courses }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-graduation-cap fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Attendance Card -->
|
||||
<div class="col-xl-4 col-md-6 mb-4">
|
||||
<div class="card border-left-success h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">Attendance Rate</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ attendance_summary.attendance_percentage|floatformat:1 }}%</div>
|
||||
<div class="mt-2">
|
||||
<div class="progress">
|
||||
<div class="progress-bar bg-success" role="progressbar" style="width: {{ attendance_summary.attendance_percentage }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 small">
|
||||
<span class="me-2">Present: {{ attendance_summary.classes_attended }}/{{ attendance_summary.total_classes }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-calendar-check fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Streak Card -->
|
||||
<div class="col-xl-4 col-md-6 mb-4">
|
||||
<div class="card border-left-info h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">Attendance Streaks</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">
|
||||
Current: {{ attendance_summary.current_streak }} days
|
||||
</div>
|
||||
<div class="mt-2 small">
|
||||
<span>Best Streak: {{ attendance_summary.longest_streak }} days</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-fire fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Course Details and Recent Activity -->
|
||||
<div class="row">
|
||||
<!-- Course Details -->
|
||||
<div class="col-xl-8 col-lg-7">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Course Details</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% for course_name, details in course_summary.details.items %}
|
||||
<div class="course-item mb-4">
|
||||
<h5>{{ course_name }}</h5>
|
||||
<div class="row mb-2">
|
||||
<div class="col-md-6">
|
||||
<p class="mb-1"><strong>Start Date:</strong> {{ details.start_date|default:'-' }}</p>
|
||||
<p class="mb-1"><strong>End Date:</strong> {{ details.end_date|default:'-' }}</p>
|
||||
<p class="mb-1"><strong>Status:</strong> <span class="badge {% if details.progress_status == 'Completed' %}bg-success{% elif details.progress_status == 'In Progress' %}bg-warning{% else %}bg-secondary{% endif %}">{{ details.progress_status }}</span></p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p class="mb-1"><strong>Progress:</strong></p>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" style="width: {{ details.completion_percentage }}%" aria-valuenow="{{ details.completion_percentage }}" aria-valuemin="0" aria-valuemax="100">{{ details.completion_percentage }}%</div>
|
||||
</div>
|
||||
{% if details.book %}
|
||||
<div class="mt-2">
|
||||
<a href="/media/{{ details.book }}" class="btn btn-sm btn-primary" target="_blank">
|
||||
<i class="fas fa-book me-1"></i> View Course Material
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-muted">No courses enrolled yet.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<div class="col-xl-4 col-lg-5">
|
||||
<!-- Recent Leave Requests -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Recent Leave Requests</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% for leave in recent_leaves %}
|
||||
<div class="leave-item mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="text-muted small">{{ leave.date }}</span>
|
||||
<span class="badge {% if leave.status == 1 %}bg-success{% elif leave.status == 2 %}bg-danger{% else %}bg-warning{% endif %}">
|
||||
{{ leave.get_status_display }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="mb-0 small">{{ leave.message|truncatechars:100 }}</p>
|
||||
<hr>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-muted">No recent leave requests.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Feedback -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Recent Feedback</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% for feedback in recent_feedback %}
|
||||
<div class="feedback-item mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="text-muted small">{{ feedback.created_at|date:"M d, Y" }}</span>
|
||||
<div class="rating">
|
||||
{% for i in "12345"|make_list %}
|
||||
<i class="fas fa-star {% if forloop.counter <= feedback.rating %}text-warning{% else %}text-muted{% endif %}"></i>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<p class="mb-1 small"><strong>{{ feedback.subject.name }}</strong> - {{ feedback.teacher.name }}</p>
|
||||
<p class="mb-0 small">{{ feedback.feedback_text|truncatechars:100 }}</p>
|
||||
<hr>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-muted">No recent feedback.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom styles -->
|
||||
<style>
|
||||
.border-left-primary {
|
||||
border-left: 4px solid #4e73df !important;
|
||||
}
|
||||
.border-left-success {
|
||||
border-left: 4px solid #1cc88a !important;
|
||||
}
|
||||
.border-left-info {
|
||||
border-left: 4px solid #36b9cc !important;
|
||||
}
|
||||
.progress {
|
||||
height: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.card {
|
||||
box-shadow: 0 0.15rem 1.75rem 0 rgba(58, 59, 69, 0.15);
|
||||
}
|
||||
.card-header {
|
||||
background-color: #f8f9fc;
|
||||
border-bottom: 1px solid #e3e6f0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,250 @@
|
||||
{% extends 'admin/base_site.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<!-- Welcome Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card bg-primary text-white">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
{% if teacher.image %}
|
||||
<img src="{{ teacher.image.url }}" alt="{{ teacher.name }}" class="rounded-circle me-3" style="width: 60px; height: 60px; object-fit: cover;">
|
||||
{% endif %}
|
||||
<div>
|
||||
<h2 class="mb-0">Welcome, {{ teacher.name }}!</h2>
|
||||
<p class="mb-0">{{ teacher.designation }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Stats Row -->
|
||||
<div class="row mb-4">
|
||||
<!-- Today's Classes Card -->
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-primary h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">Today's Classes</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ today_classes }}</div>
|
||||
<div class="mt-2 small">
|
||||
<span>Completed: {{ completed_classes }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-chalkboard-teacher fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Students Card -->
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-success h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">Total Students</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ total_students }}</div>
|
||||
<div class="mt-2 small">
|
||||
<span>Present Today: {{ present_students }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-users fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Feedback Card -->
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-info h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">Average Rating</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">
|
||||
{{ avg_rating|floatformat:1 }}/5.0
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<div class="rating">
|
||||
{% with ''|center:avg_rating_rounded as range %}
|
||||
{% for _ in range %}
|
||||
<i class="fas fa-star text-warning"></i>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
{% with ''|center:remaining_stars as range %}
|
||||
{% for _ in range %}
|
||||
<i class="fas fa-star text-muted"></i>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-star fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Leave Status Card -->
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-warning h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">Leave Requests</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{{ pending_leaves }}</div>
|
||||
<div class="mt-2 small">
|
||||
<span class="text-success">Approved: {{ approved_leaves }}</span> |
|
||||
<span class="text-danger">Rejected: {{ rejected_leaves }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-calendar-alt fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Today's Schedule and Recent Activity -->
|
||||
<div class="row">
|
||||
<!-- Today's Schedule -->
|
||||
<div class="col-xl-8 col-lg-7">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Today's Schedule</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if today_routines %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Course</th>
|
||||
<th>Status</th>
|
||||
<th>Attendance</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for routine in today_routines %}
|
||||
<tr>
|
||||
<td>{{ routine.start_time|time:"g:i A" }} - {{ routine.end_time|time:"g:i A" }}</td>
|
||||
<td>{{ routine.name }}</td>
|
||||
<td>
|
||||
{% if routine.is_completed %}
|
||||
<span class="badge bg-success">Completed</span>
|
||||
{% elif routine.is_ongoing %}
|
||||
<span class="badge bg-warning">Ongoing</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">Upcoming</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ routine.attendance_count }}/{{ routine.total_students }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-muted">No classes scheduled for today.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<div class="col-xl-4 col-lg-5">
|
||||
<!-- Recent Leave Requests -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Recent Leave Requests</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% for leave in recent_leaves %}
|
||||
<div class="leave-item mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="text-muted small">{{ leave.date }}</span>
|
||||
<span class="badge {% if leave.status == 1 %}bg-success{% elif leave.status == 2 %}bg-danger{% else %}bg-warning{% endif %}">
|
||||
{{ leave.get_status_display }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="mb-0 small">{{ leave.message|truncatechars:100 }}</p>
|
||||
<hr>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-muted">No recent leave requests.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Feedback -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Recent Feedback</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% for feedback in recent_feedback %}
|
||||
<div class="feedback-item mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="text-muted small">{{ feedback.created_at|date:"M d, Y" }}</span>
|
||||
<div class="rating">
|
||||
{% for i in "12345"|make_list %}
|
||||
<i class="fas fa-star {% if forloop.counter <= feedback.rating %}text-warning{% else %}text-muted{% endif %}"></i>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<p class="mb-1 small"><strong>{{ feedback.subject.name }}</strong> - {{ feedback.student.name }}</p>
|
||||
<p class="mb-0 small">{{ feedback.feedback_text|truncatechars:100 }}</p>
|
||||
<hr>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-muted">No recent feedback.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom styles -->
|
||||
<style>
|
||||
.border-left-primary {
|
||||
border-left: 4px solid #4e73df !important;
|
||||
}
|
||||
.border-left-success {
|
||||
border-left: 4px solid #1cc88a !important;
|
||||
}
|
||||
.border-left-info {
|
||||
border-left: 4px solid #36b9cc !important;
|
||||
}
|
||||
.border-left-warning {
|
||||
border-left: 4px solid #f6c23e !important;
|
||||
}
|
||||
.progress {
|
||||
height: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
.card {
|
||||
box-shadow: 0 0.15rem 1.75rem 0 rgba(58, 59, 69, 0.15);
|
||||
}
|
||||
.card-header {
|
||||
background-color: #f8f9fc;
|
||||
border-bottom: 1px solid #e3e6f0;
|
||||
}
|
||||
.rating {
|
||||
color: #f6c23e;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user