feat: Connect Guardian portal to real student mastery tracking

- Add GuardianController API endpoint to fetch student progress, readiness, and weakness gap logs.
- Add dynamic JS logic to GuardianPortal.php to replace static mockups.
- Connect Exam AI diagnostics (weaknesses, socratic checks) directly to the parent's dashboard view.
This commit is contained in:
Hamza-Ayed
2026-08-28 23:17:14 +03:00
parent a18d19588d
commit 8aab91c77f
3 changed files with 222 additions and 4 deletions
+97 -4
View File
@@ -150,7 +150,7 @@ class GuardianPortal
<!-- Forensic Weakness Remediation Log -->
<div class="card" style="margin-bottom: 24px;">
<h3 style="font-size: 16px; font-weight: 800; margin-bottom: 16px; color: #FFF;">سجل رصد ومعالجة نقاط الضعف والفجوات (AI Diagnostic Log) 🔍</h3>
<div style="display: flex; flex-direction: column; gap: 12px;">
<div id="diagnostic_log_container" style="display: flex; flex-direction: column; gap: 12px;">
<div style="background: rgba(255,255,255,0.03); border: 1px solid var(--border); border-radius: 14px; padding: 16px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px;">
<div>
<span style="font-size: 13px; font-weight: 800; color: #FFF; display: block;">الرياضيات العلمي — الاشتقاق الضمني والمعدلات المرتبطة</span>
@@ -171,12 +171,105 @@ class GuardianPortal
</div>
<script>
function switchChildProfile(idx, name, natId, score) {
let dashboardData = [];
document.addEventListener('DOMContentLoaded', async () => {
// For demo/testing: If guardian token is missing but student token exists, use student token to simulate.
let token = localStorage.getItem('saqel_guardian_jwt') || localStorage.getItem('saqel_student_jwt') || localStorage.getItem('saqel_teacher_jwt');
if (!token) {
// Not authenticated, redirect or show message
document.querySelector('.container').innerHTML = `
<div style="text-align: center; margin-top: 50px;">
<h2>يرجى تسجيل الدخول كطالب أو ولي أمر لعرض البيانات</h2>
<a href="/student" style="color: var(--accent-cyan);">الذهاب لصفحة الدخول</a>
</div>
`;
return;
}
try {
const res = await fetch('/api/guardian/dashboard', {
headers: { 'Authorization': 'Bearer ' + token }
});
const json = await res.json();
if (json.status === 'success' && json.data.children.length > 0) {
dashboardData = json.data.children;
renderChildSelector();
switchChildProfile(0); // Load first child by default
} else {
document.querySelector('.container').innerHTML = `
<div style="text-align: center; margin-top: 50px;">
<h2>لا يوجد أبناء مرتبطين بحسابك حالياً.</h2>
</div>
`;
}
} catch (e) {
console.error("Failed to load guardian dashboard", e);
}
});
function renderChildSelector() {
const pill = document.querySelector('.child-selector-pill');
pill.innerHTML = dashboardData.map((childObj, idx) => `
<button class="child-btn ${idx === 0 ? 'active' : ''}" onclick="switchChildProfile(${idx})">
${childObj.student.name.split(' ')[0]}
</button>
`).join('');
}
function switchChildProfile(idx) {
// Update active button
document.querySelectorAll('.child-btn').forEach((b, i) => {
b.className = (i === idx) ? 'child-btn active' : 'child-btn';
});
document.getElementById('selected_child_name').textContent = `الابن: ${name}`;
document.getElementById('metric_readiness').textContent = score;
const data = dashboardData[idx];
if (!data) return;
// Update Header Info
document.getElementById('selected_child_name').textContent = `الابن: ${data.student.name}`;
// Note: Updated the subtitle to safely display grade and stream
const subtitle = document.querySelector('.hero-card p');
subtitle.textContent = `الصف: ${data.student.grade_stream} • الرقم الوطني: ${data.student.national_id}`;
// Update Metrics
document.getElementById('metric_readiness').textContent = data.metrics.readiness_score + '%';
document.getElementById('metric_lessons').textContent = data.metrics.checkpoints_passed + ' فحص سقراطي';
document.getElementById('metric_remediation').textContent = data.metrics.remediations_flagged + ' ثغرات متداركة';
// Render AI Diagnostic Logs (Weaknesses)
const logContainer = document.getElementById('diagnostic_log_container');
if (data.diagnostics && data.diagnostics.length > 0) {
logContainer.innerHTML = data.diagnostics.map(log => {
const isPassed = log.status === 'passed';
const badgeClass = isPassed ? 'remediation-badge' : 'remediation-badge" style="color: #F87171; background: rgba(248,113,113,0.1); border-color: rgba(248,113,113,0.3);';
const badgeText = isPassed ? 'تمت المعالجة والإتقان ✓' : 'قيد المعالجة (ثغرة مستمرة) ⚠️';
const weakTopicsStr = log.weak_topics.length > 0 ? log.weak_topics.join('، ') : 'لم تُرصد ثغرات';
return `
<div style="background: rgba(255,255,255,0.03); border: 1px solid var(--border); border-radius: 14px; padding: 16px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; margin-bottom: 12px;">
<div style="flex: 1; min-width: 250px;">
<span style="font-size: 13px; font-weight: 800; color: #FFF; display: block; margin-bottom: 4px;">
امتحان: ${log.exam_title} (${log.percentage}%)
</span>
<span style="font-size: 12px; color: var(--accent-gold); display: block; margin-bottom: 4px;">
فجوات الرصد الذكي: ${weakTopicsStr}
</span>
<span style="font-size: 11.5px; color: var(--text-muted); line-height: 1.5; display: block;">
تقرير المعلم الذكي: ${log.ai_diagnostic_report}
</span>
</div>
<span class="${badgeClass}">${badgeText}</span>
</div>
`;
}).join('');
} else {
logContainer.innerHTML = '<div style="font-size: 13px; color: var(--text-muted); text-align: center; padding: 20px;">لم يتم إجراء أي اختبارات تشخيصية حتى الآن.</div>';
}
}
</script>
</body>