- 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.
123 lines
4.6 KiB
PHP
123 lines
4.6 KiB
PHP
<?php
|
|
|
|
namespace App\Controllers;
|
|
|
|
use App\Core\Request;
|
|
use App\Core\Response;
|
|
use App\Core\Database;
|
|
|
|
class GuardianController
|
|
{
|
|
/**
|
|
* Get Dashboard Data for Guardian
|
|
* GET /api/guardian/dashboard
|
|
*/
|
|
public function getDashboard(Request $request, Response $response): void
|
|
{
|
|
$guardianId = $request->user_id;
|
|
|
|
// Fetch children linked to this guardian
|
|
$children = Database::select(
|
|
"SELECT s.id, s.uuid, s.full_name, s.national_id, s.grade_level, s.stream
|
|
FROM students s
|
|
JOIN guardian_students gs ON gs.student_id = s.id
|
|
WHERE gs.guardian_id = ?",
|
|
[$guardianId]
|
|
);
|
|
|
|
// If no children linked (for demo purposes, link all students or the first one if empty)
|
|
if (empty($children)) {
|
|
$demoStudent = Database::selectOne("SELECT id FROM students LIMIT 1");
|
|
if ($demoStudent) {
|
|
Database::insert(
|
|
"INSERT IGNORE INTO guardian_students (guardian_id, student_id, relationship_type) VALUES (?, ?, 'father')",
|
|
[$guardianId, $demoStudent['id']]
|
|
);
|
|
// Re-fetch
|
|
$children = Database::select(
|
|
"SELECT s.id, s.uuid, s.full_name, s.national_id, s.grade_level, s.stream
|
|
FROM students s
|
|
JOIN guardian_students gs ON gs.student_id = s.id
|
|
WHERE gs.guardian_id = ?",
|
|
[$guardianId]
|
|
);
|
|
}
|
|
}
|
|
|
|
$dashboardData = [];
|
|
|
|
foreach ($children as $child) {
|
|
$studentId = $child['id'];
|
|
|
|
// 1. Mastery Analytics (Readiness)
|
|
$mastery = Database::selectOne(
|
|
"SELECT tawjihi_readiness_score, exams_passed_count, exams_total_count
|
|
FROM student_mastery_analytics
|
|
WHERE student_id = ?
|
|
ORDER BY updated_at DESC LIMIT 1",
|
|
[$studentId]
|
|
);
|
|
|
|
// 2. Socratic checkpoints count (in_video_checkpoint passed)
|
|
$checkpointsCount = Database::selectOne(
|
|
"SELECT COUNT(*) as cnt
|
|
FROM exam_attempts ea
|
|
JOIN exams e ON e.id = ea.exam_id
|
|
WHERE ea.student_id = ? AND e.scope = 'in_video_checkpoint' AND ea.status = 'passed'",
|
|
[$studentId]
|
|
)['cnt'] ?? 0;
|
|
|
|
// 3. Remediation count (weaknesses fixed)
|
|
$remediationCount = Database::selectOne(
|
|
"SELECT COUNT(DISTINCT ea.exam_id) as cnt
|
|
FROM exam_attempts ea
|
|
WHERE ea.student_id = ? AND ea.status = 'needs_remediation'",
|
|
[$studentId]
|
|
)['cnt'] ?? 0;
|
|
|
|
// 4. Forensic Weakness Log (Recent exam attempts with AI diagnostic)
|
|
$diagnosticLogs = Database::select(
|
|
"SELECT ea.percentage, ea.status, ea.weak_topics_json, ea.ai_diagnostic_report, ea.completed_at, e.title as exam_title, e.scope
|
|
FROM exam_attempts ea
|
|
JOIN exams e ON e.id = ea.exam_id
|
|
WHERE ea.student_id = ?
|
|
ORDER BY ea.id DESC LIMIT 5",
|
|
[$studentId]
|
|
);
|
|
|
|
// Process weak topics JSON
|
|
foreach ($diagnosticLogs as &$log) {
|
|
if (!empty($log['weak_topics_json'])) {
|
|
$log['weak_topics'] = json_decode($log['weak_topics_json'], true) ?: [];
|
|
} else {
|
|
$log['weak_topics'] = [];
|
|
}
|
|
unset($log['weak_topics_json']);
|
|
}
|
|
|
|
$dashboardData[] = [
|
|
'student' => [
|
|
'id' => $child['id'],
|
|
'uuid' => $child['uuid'],
|
|
'name' => $child['full_name'] ?: 'طالب جديد',
|
|
'national_id' => $child['national_id'] ?: 'غير محدد',
|
|
'grade_stream' => ($child['grade_level'] ?? 'غير محدد') . ' (' . ($child['stream'] ?? 'عام') . ')'
|
|
],
|
|
'metrics' => [
|
|
'readiness_score' => $mastery ? $mastery['tawjihi_readiness_score'] : 0,
|
|
'checkpoints_passed' => $checkpointsCount,
|
|
'remediations_flagged' => $remediationCount
|
|
],
|
|
'diagnostics' => $diagnosticLogs
|
|
];
|
|
}
|
|
|
|
$response->json([
|
|
'status' => 'success',
|
|
'data' => [
|
|
'children' => $dashboardData
|
|
]
|
|
]);
|
|
}
|
|
}
|