Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d543c52cc | |||
| 253be573ac |
@@ -1 +1,2 @@
|
|||||||
*.log
|
*.log
|
||||||
|
writable/session/
|
||||||
|
|||||||
@@ -416,8 +416,10 @@ class AdministratorController extends BaseController
|
|||||||
$totalStudents = (int) (
|
$totalStudents = (int) (
|
||||||
$this->db->table('student_class')
|
$this->db->table('student_class')
|
||||||
->select('COUNT(DISTINCT student_class.student_id) AS cnt')
|
->select('COUNT(DISTINCT student_class.student_id) AS cnt')
|
||||||
|
->join('students', 'students.id = student_class.student_id', 'inner')
|
||||||
->where('student_class.school_year', $this->schoolYear)
|
->where('student_class.school_year', $this->schoolYear)
|
||||||
->where('student_class.class_section_id IS NOT NULL', null, false)
|
->where('student_class.class_section_id IS NOT NULL', null, false)
|
||||||
|
->where('students.is_active', 1)
|
||||||
->get()
|
->get()
|
||||||
->getRow('cnt')
|
->getRow('cnt')
|
||||||
?? 0
|
?? 0
|
||||||
|
|||||||
@@ -339,6 +339,37 @@ class ScoreController extends Controller
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$students = $this->getStudentScores($classSectionId, $semester, $schoolYear, $teacherId);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
log_message('error', 'ScoreController::submitScoresLock missing check failed: ' . $e->getMessage());
|
||||||
|
return redirect()->back()->with('error', 'Unable to validate missing scores. Please try again.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$missingRows = [];
|
||||||
|
foreach ($students as $student) {
|
||||||
|
$missingItems = $student['missing_items'] ?? [];
|
||||||
|
if (empty($missingItems)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$studentId = (int) ($student['student_id'] ?? 0);
|
||||||
|
$studentName = trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? ''));
|
||||||
|
if ($studentName === '') {
|
||||||
|
$studentName = $studentId > 0 ? ('Student #' . $studentId) : 'Student';
|
||||||
|
}
|
||||||
|
$missingRows[] = $studentName . ': ' . implode(', ', $missingItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($missingRows)) {
|
||||||
|
$sample = array_slice($missingRows, 0, 8);
|
||||||
|
$message = sprintf(
|
||||||
|
'Missing entries found for %d student(s). Fill them or mark "Missing ok" before submitting. %s',
|
||||||
|
count($missingRows),
|
||||||
|
$sample ? ('Examples: ' . implode(' | ', $sample)) : ''
|
||||||
|
);
|
||||||
|
return redirect()->back()->with('error', trim($message));
|
||||||
|
}
|
||||||
|
|
||||||
$existing = $this->gradingLockModel->getLock($classSectionId, $semester, $schoolYear);
|
$existing = $this->gradingLockModel->getLock($classSectionId, $semester, $schoolYear);
|
||||||
if (!empty($existing) && !empty($existing['is_locked'])) {
|
if (!empty($existing) && !empty($existing['is_locked'])) {
|
||||||
return redirect()->back()->with('status', 'Scores already submitted and locked.');
|
return redirect()->back()->with('status', 'Scores already submitted and locked.');
|
||||||
|
|||||||
@@ -517,37 +517,28 @@
|
|||||||
<section class="mb-5">
|
<section class="mb-5">
|
||||||
<h2 class="mb-4 text-center">Statistics</h2>
|
<h2 class="mb-4 text-center">Statistics</h2>
|
||||||
<div class="stats-row">
|
<div class="stats-row">
|
||||||
<!-- Students -->
|
|
||||||
<div class="stat-circle circle-primary">
|
<div class="stat-circle circle-primary">
|
||||||
<div class="stat-icon"><i class="bi bi-people-fill"></i></div>
|
<div class="stat-icon"><i class="fas fa-user-graduate"></i></div>
|
||||||
<div class="stat-value" data-stat="students">—</div>
|
<div class="stat-value" data-stat="students">—</div>
|
||||||
<div class="stat-title">Students</div>
|
<div class="stat-title">Students</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Teachers -->
|
|
||||||
<div class="stat-circle circle-info">
|
<div class="stat-circle circle-info">
|
||||||
<div class="stat-icon"><i class="bi bi-person-video2"></i></div>
|
<div class="stat-icon"><i class="fas fa-chalkboard-teacher"></i></div>
|
||||||
<div class="stat-value" data-stat="teachers">—</div>
|
<div class="stat-value" data-stat="teachers">—</div>
|
||||||
<div class="stat-title">Teachers</div>
|
<div class="stat-title">Teachers</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Teacher Assistants -->
|
|
||||||
<div class="stat-circle circle-success">
|
<div class="stat-circle circle-success">
|
||||||
<div class="stat-icon"><i class="bi bi-person-badge-fill"></i></div>
|
<div class="stat-icon"><i class="fas fa-user-cog"></i></div>
|
||||||
<div class="stat-value" data-stat="teacherAssistants">—</div>
|
<div class="stat-value" data-stat="teacherAssistants">—</div>
|
||||||
<div class="stat-title">Teachers' Assistants</div>
|
<div class="stat-title">TAs</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Admins -->
|
|
||||||
<div class="stat-circle circle-warning">
|
<div class="stat-circle circle-warning">
|
||||||
<div class="stat-icon"><i class="bi bi-shield-lock-fill"></i></div>
|
<div class="stat-icon"><i class="fas fa-user-shield"></i></div>
|
||||||
<div class="stat-value" data-stat="admins">—</div>
|
<div class="stat-value" data-stat="admins">—</div>
|
||||||
<div class="stat-title">Admins</div>
|
<div class="stat-title">Admins</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Parents -->
|
|
||||||
<div class="stat-circle circle-light">
|
<div class="stat-circle circle-light">
|
||||||
<div class="stat-icon"><i class="bi bi-people"></i></div>
|
<div class="stat-icon"><i class="fas fa-users"></i></div>
|
||||||
<div class="stat-value" data-stat="parents">—</div>
|
<div class="stat-value" data-stat="parents">—</div>
|
||||||
<div class="stat-title">Parents</div>
|
<div class="stat-title">Parents</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -71,9 +71,20 @@
|
|||||||
max-height: none;
|
max-height: none;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
.attn-pie-wrap {
|
||||||
|
width: 320px;
|
||||||
|
height: 320px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
.attn-analysis-half { grid-column: span 12; }
|
.attn-analysis-half { grid-column: span 12; }
|
||||||
}
|
}
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.attn-pie-wrap {
|
||||||
|
width: 180px;
|
||||||
|
height: 180px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<?= $this->endSection() ?>
|
<?= $this->endSection() ?>
|
||||||
|
|
||||||
@@ -291,7 +302,9 @@
|
|||||||
<div class="attn-analysis-grid">
|
<div class="attn-analysis-grid">
|
||||||
<div class="attn-analysis-card attn-analysis-half">
|
<div class="attn-analysis-card attn-analysis-half">
|
||||||
<h6>Overall Distribution</h6>
|
<h6>Overall Distribution</h6>
|
||||||
<canvas id="attnPieChart" height="50" aria-label="Overall attendance pie chart"></canvas>
|
<div class="attn-pie-wrap">
|
||||||
|
<canvas id="attnPieChart" aria-label="Overall attendance pie chart"></canvas>
|
||||||
|
</div>
|
||||||
<table class="attn-analysis-table mt-3 no-mgmt-sticky" data-no-mgmt-sticky>
|
<table class="attn-analysis-table mt-3 no-mgmt-sticky" data-no-mgmt-sticky>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -415,6 +428,8 @@
|
|||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
plugins: {
|
plugins: {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
callbacks: {
|
callbacks: {
|
||||||
|
|||||||
+173
-14
@@ -322,6 +322,72 @@
|
|||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.home-stats {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.08);
|
||||||
|
padding: 2.5rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-stats .stats-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1.5rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-stats .stat-circle {
|
||||||
|
width: 190px;
|
||||||
|
height: 190px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 700;
|
||||||
|
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-stats .stat-value {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-stats .stat-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: .2rem;
|
||||||
|
opacity: .9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-stats .stat-title {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-top: .25rem;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-stats .circle-primary {
|
||||||
|
background: radial-gradient(circle at 30% 30%, #0d6efd, #0044aa);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-stats .circle-success {
|
||||||
|
background: radial-gradient(circle at 30% 30%, #198754, #0c4f2c);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-stats .circle-warning {
|
||||||
|
background: radial-gradient(circle at 30% 30%, #daa544ff, #a66f00);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-stats .circle-info {
|
||||||
|
background: radial-gradient(circle at 30% 30%, #0dcaf0, #047e9c);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-stats .circle-light {
|
||||||
|
background: radial-gradient(circle at 30% 30%, #63af4cff, #00eb7dff);
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 992px) {
|
@media (max-width: 992px) {
|
||||||
.image-column {
|
.image-column {
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
@@ -332,6 +398,13 @@
|
|||||||
border-radius: 0 0 10px 10px !important;
|
border-radius: 0 0 10px 10px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.home-stats .stat-circle {
|
||||||
|
width: 160px;
|
||||||
|
height: 160px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -448,8 +521,43 @@
|
|||||||
<h4 class="fw-bold my-0"><u>Join Al Rahma family today! Click here for a quick guide on how to create an account.</u></h4>
|
<h4 class="fw-bold my-0"><u>Join Al Rahma family today! Click here for a quick guide on how to create an account.</u></h4>
|
||||||
</a>
|
</a>
|
||||||
</section>
|
</section>
|
||||||
|
<!-- Statistics Start -->
|
||||||
|
<div class="container-xxl py-2 content-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="home-stats" data-dashboard-endpoint="<?= site_url('api/administrator/dashboard') ?>">
|
||||||
|
<h1 class="d-flex justify-content-center p-md-1">Active Participants</h1>
|
||||||
|
<br>
|
||||||
|
<div class="stats-row">
|
||||||
|
<div class="stat-circle circle-primary">
|
||||||
|
<div class="stat-icon"><i class="fa-solid fa-user-graduate"></i></div>
|
||||||
|
<div class="stat-value" data-stat="students">—</div>
|
||||||
|
<div class="stat-title">Students</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-circle circle-info">
|
||||||
|
<div class="stat-icon"><i class="fa-solid fa-chalkboard-user"></i></div>
|
||||||
|
<div class="stat-value" data-stat="teachers">—</div>
|
||||||
|
<div class="stat-title">Teachers</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-circle circle-success">
|
||||||
|
<div class="stat-icon"><i class="fa-solid fa-user-gear"></i></div>
|
||||||
|
<div class="stat-value" data-stat="teacherAssistants">—</div>
|
||||||
|
<div class="stat-title">TAs</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-circle circle-warning">
|
||||||
|
<div class="stat-icon"><i class="fa-solid fa-user-shield"></i></div>
|
||||||
|
<div class="stat-value" data-stat="admins">—</div>
|
||||||
|
<div class="stat-title">Admins</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-circle circle-light">
|
||||||
|
<div class="stat-icon"><i class="fa-solid fa-people-group"></i></div>
|
||||||
|
<div class="stat-value" data-stat="parents">—</div>
|
||||||
|
<div class="stat-title">Parents</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Statistics End -->
|
||||||
<div class="container-xxl py-2 content-section">
|
<div class="container-xxl py-2 content-section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row bg-light squared align-items-center">
|
<div class="row bg-light squared align-items-center">
|
||||||
@@ -461,18 +569,18 @@
|
|||||||
<p class="mb-4">Under the umbrella of ISGL, Al Rahma Sunday School has been serving the community for over thirty years. Throughout the decades, it has provided students with a strong foundation in Islamic Studies and Quran, fostering both knowledge and character. With its dedicated teachers and well-rounded academic program, the school continues to guide generations of Muslim youth in their faith, values and practice of Islam.</p>
|
<p class="mb-4">Under the umbrella of ISGL, Al Rahma Sunday School has been serving the community for over thirty years. Throughout the decades, it has provided students with a strong foundation in Islamic Studies and Quran, fostering both knowledge and character. With its dedicated teachers and well-rounded academic program, the school continues to guide generations of Muslim youth in their faith, values and practice of Islam.</p>
|
||||||
<p>For more information, click below to visit ISGL official website.</p>
|
<p>For more information, click below to visit ISGL official website.</p>
|
||||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" bgcolor="#28a745" style="border-radius:6px;">
|
<td align="center" bgcolor="#28a745" style="border-radius:6px;">
|
||||||
<a href="https://isgl.org"
|
<a href="https://isgl.org"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
style="display:inline-block;padding:12px 18px;color:#ffffff;text-decoration:none;font-weight:600;">
|
style="display:inline-block;padding:12px 18px;color:#ffffff;text-decoration:none;font-weight:600;">
|
||||||
ISGL Official Website
|
ISGL Official Website
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Image Column - Fixed with correct path -->
|
<!-- Image Column - Fixed with correct path -->
|
||||||
@@ -677,6 +785,57 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const container = document.querySelector('.home-stats[data-dashboard-endpoint]');
|
||||||
|
if (!container) return;
|
||||||
|
const endpoint = container.dataset.dashboardEndpoint;
|
||||||
|
const statElements = container.querySelectorAll('[data-stat]');
|
||||||
|
const numberFormatter = new Intl.NumberFormat();
|
||||||
|
let isLoading = false;
|
||||||
|
|
||||||
|
const loadStats = function() {
|
||||||
|
if (isLoading) return;
|
||||||
|
isLoading = true;
|
||||||
|
fetch(endpoint, {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
|
credentials: 'same-origin',
|
||||||
|
})
|
||||||
|
.then(function(response) {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Request failed');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(function(payload) {
|
||||||
|
const counts = payload && typeof payload === 'object' && payload.counts ?
|
||||||
|
payload.counts :
|
||||||
|
{};
|
||||||
|
statElements.forEach(function(element) {
|
||||||
|
const key = element.dataset.stat;
|
||||||
|
const value = counts[key];
|
||||||
|
element.textContent = Number.isFinite(value) ?
|
||||||
|
numberFormatter.format(value) :
|
||||||
|
'—';
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function() {})
|
||||||
|
.finally(function() {
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
loadStats();
|
||||||
|
setInterval(loadStats, 60000);
|
||||||
|
document.addEventListener('visibilitychange', function() {
|
||||||
|
if (document.visibilityState === 'visible') {
|
||||||
|
loadStats();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -32,9 +32,20 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
toggleCommentCheckbox(wrapper);
|
toggleCommentCheckbox(wrapper);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let lastSubmitterId = null;
|
||||||
|
form.addEventListener('click', function (event) {
|
||||||
|
const target = event.target.closest('button, input[type="submit"]');
|
||||||
|
if (!target || target.form !== form) return;
|
||||||
|
lastSubmitterId = target.id || null;
|
||||||
|
});
|
||||||
|
|
||||||
form.addEventListener('submit', function (event) {
|
form.addEventListener('submit', function (event) {
|
||||||
const submitter = event.submitter;
|
const activeEl = document.activeElement;
|
||||||
if (!submitter || submitter.id !== 'submitScoresLockBtn') {
|
const submitterId = event.submitter
|
||||||
|
? event.submitter.id
|
||||||
|
: (activeEl && activeEl.form === form ? activeEl.id : lastSubmitterId);
|
||||||
|
lastSubmitterId = null;
|
||||||
|
if (submitterId !== 'submitScoresLockBtn') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user