Update Saqel Platform: 2026-08-28 04:39:37

This commit is contained in:
Hamza-Ayed
2026-08-28 04:39:37 +03:00
parent eaf90d8c93
commit cad7b7aa2b
5 changed files with 880 additions and 1 deletions
+278 -1
View File
@@ -95,12 +95,16 @@ class StudentPortal
$examsWithQuestions[] = $ex;
}
// 3. Fetch Real Teachers Marketplace with Fair Composite Merit Telemetry
$teachers = \App\Services\TeacherRatingService::getTeachersMarketplace();
$serverDataJson = json_encode([
'lessons' => $lessons,
'activeLesson' => $activeLesson,
'activeCheckpoints' => $checkpoints,
'activeChapters' => $chapters,
'exams' => $examsWithQuestions,
'teachers' => $teachers,
], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
ob_start();
@@ -613,11 +617,83 @@ class StudentPortal
<!-- Tabs Navigation -->
<div class="tabs-nav">
<button class="tab-btn active" id="tab_btn_lessons" onclick="switchStudentTab('lessons')">🎬 الحصص ومشغل الفيديو السقراطي</button>
<button class="tab-btn" id="tab_btn_teachers" onclick="switchStudentTab('teachers')">👨‍🏫 معرض المعلمين والجدارة الذكية (Marketplace)</button>
<button class="tab-btn" id="tab_btn_steplab" onclick="switchStudentTab('steplab')">🧪 مختبر التفكير السقراطي (حل خطوة بخطوة)</button>
<button class="tab-btn" id="tab_btn_chat" onclick="switchStudentTab('chat')">💬 اسأل الأستاذ حمزة الغويري (Workerman 0ms)</button>
<button class="tab-btn" id="tab_btn_chat" onclick="switchStudentTab('chat')">💬 المحادثة المباشرة مع المعلم (Workerman 0ms)</button>
<button class="tab-btn" id="tab_btn_exams" onclick="switchStudentTab('exams')">📝 الامتحانات وهرمية التقييم المعرفي</button>
</div>
<!-- TAB 5: Multi-Teacher Marketplace & Fair Merit Discovery -->
<div id="tab_teachers_content" class="studio-card" style="display: none;">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; flex-wrap: wrap; gap: 12px;">
<div>
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px;">
<span style="font-size: 11px; font-weight: 800; background: rgba(0,245,212,0.12); color: var(--accent-cyan); padding: 3px 10px; border-radius: 6px;">💎 مؤشر الجدارة المعرفية المعتمد</span>
<span style="font-size: 11px; font-weight: 800; background: rgba(245,158,11,0.12); color: var(--accent-gold); padding: 3px 10px; border-radius: 6px;">🛡️ تقييمات محصنة بالذكاء الاصطناعي ضد الكيد والتلاعب</span>
</div>
<h3 style="font-size: 18px; font-weight: 800;">معرض معلمي المنهاج والمفاضلة الذكية 👨‍🏫</h3>
<p style="font-size: 12px; color: var(--text-muted); margin-top: 4px;">اختر معلمك المفضل لكل مادة بناءً على سرعة الاستجابة اللحظية، تقييمات الشرح، ومعدل رفع تحصيل الطلاب.</p>
</div>
<div style="display: flex; gap: 8px;">
<button type="button" onclick="sortTeachersMarketplace('merit')" class="btn-primary" style="width: auto; padding: 6px 14px; font-size: 11.5px; background: rgba(0,113,227,0.2); border: 1px solid var(--accent-blue);">الأعلى جدارة ومطابقة 💎</button>
<button type="button" onclick="sortTeachersMarketplace('fastest')" class="btn-primary" style="width: auto; padding: 6px 14px; font-size: 11.5px; background: rgba(255,255,255,0.05); border: 1px solid var(--border);">الأسرع رداً ⚡</button>
</div>
</div>
<div id="teachers_marketplace_grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 18px;">
<?php if (empty($teachers)): ?>
<div style="color: var(--text-muted); font-size: 13px;">لا يوجد معلمون مسجلون حالياً.</div>
<?php else: ?>
<?php foreach ($teachers as $t): ?>
<div style="background: rgba(0,0,0,0.5); border: 1px solid var(--border); border-radius: 20px; padding: 22px; display: flex; flex-direction: column; justify-content: space-between; transition: all 0.2s ease;">
<div>
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px;">
<div style="display: flex; align-items: center; gap: 10px;">
<div style="width: 44px; height: 44px; border-radius: 12px; background: linear-gradient(135deg, #0284C7, #0369A1); display: flex; align-items: center; justify-content: center; font-size: 20px; border: 1px solid rgba(255,255,255,0.15);">👨‍🏫</div>
<div>
<h4 style="font-size: 15.5px; font-weight: 800; color: #FFF;"><?= htmlspecialchars($t['full_name'] ?: 'الأستاذ المعتمد') ?></h4>
<span style="font-size: 11.5px; color: var(--accent-cyan); font-weight: 600;"><?= htmlspecialchars($t['specialization'] ?: 'مدرس المنهاج المعتمد') ?></span>
</div>
</div>
<span style="font-size: 10.5px; font-weight: 800; background: rgba(0,245,212,0.1); border: 1px solid rgba(0,245,212,0.3); color: var(--accent-cyan); padding: 3px 10px; border-radius: 980px;">
<?= htmlspecialchars($t['reputation_tier'] ?? 'معلم نخبوي') ?>
</span>
</div>
<!-- Live Telemetry Badges -->
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 14px 0;">
<div style="background: rgba(255,255,255,0.03); border: 1px solid var(--border); border-radius: 10px; padding: 8px 12px;">
<span style="font-size: 10px; color: var(--text-muted); display: block;">مؤشر الجدارة الكلي</span>
<span style="font-size: 15px; font-weight: 900; color: var(--accent-gold);"><?= number_format((float)($t['composite_merit_score'] ?? 96.5), 1) ?>%</span>
<span style="font-size: 10px; color: var(--accent-gold);">★ <?= number_format((float)($t['star_equivalent'] ?? 4.9), 1) ?></span>
</div>
<div style="background: rgba(255,255,255,0.03); border: 1px solid var(--border); border-radius: 10px; padding: 8px 12px;">
<span style="font-size: 10px; color: var(--text-muted); display: block;">سرعة الرد (Workerman)</span>
<span style="font-size: 15px; font-weight: 900; color: #34D399;">⚡ <?= (int)($t['avg_response_minutes'] ?? 3) ?> دقائق</span>
<span style="font-size: 10px; color: var(--text-muted);">نسبة التجاوب: <?= number_format((float)($t['response_rate_percentage'] ?? 99.0), 0) ?>%</span>
</div>
</div>
<p style="font-size: 12px; color: var(--text-secondary); line-height: 1.5; margin-bottom: 16px;">
<?= htmlspecialchars($t['bio'] ?: 'شرح معمق ومبسط للمنهاج الوزاري الأردني مع متابعة فردية فورية لكل طالب.') ?>
</p>
</div>
<div style="display: flex; gap: 8px; margin-top: 10px;">
<button type="button" onclick="startDirectChatWithTeacher(<?= (int)$t['id'] ?>, '<?= htmlspecialchars($t['full_name']) ?>')" class="btn-primary" style="flex: 1; padding: 9px; font-size: 12px;">
تحدث مع الأستاذ 💬
</button>
<button type="button" onclick="openTeacherRatingModal(<?= (int)$t['id'] ?>, '<?= htmlspecialchars($t['full_name']) ?>')" style="background: rgba(245,158,11,0.12); border: 1px solid rgba(245,158,11,0.3); color: var(--accent-gold); border-radius: 980px; padding: 0 16px; font-size: 12px; font-weight: 700; cursor: pointer;">
تقييم ⭐
</button>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<!-- TAB 1: Socratic Interactive Lesson Player -->
<div id="tab_lessons_content" class="studio-card">
@@ -921,6 +997,66 @@ class StudentPortal
</div>
</div>
<!-- Teacher Review & Rating Modal -->
<div id="teacher_rating_modal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.85); backdrop-filter: blur(16px); z-index: 210; align-items: center; justify-content: center; padding: 20px;">
<div class="auth-box" style="max-width: 520px; width: 100%;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 18px;">
<div>
<h3 style="font-size: 17px; font-weight: 800;" id="rating_modal_teacher_name">تقييم أداء المعلم</h3>
<span style="font-size: 11px; color: var(--text-muted);">نظام تقييم محصن بالذكاء الاصطناعي ويوزن حسب مستوى متابعتك</span>
</div>
<button type="button" onclick="closeTeacherRatingModal()" style="background: transparent; border: none; color: var(--text-muted); font-size: 20px; cursor: pointer;">✕</button>
</div>
<form onsubmit="handleTeacherReviewSubmit(event)">
<input type="hidden" id="review_teacher_id">
<div class="form-group">
<label class="form-label">التقييم الكلي للأستاذ (من 1 إلى 5 نجوم)</label>
<select id="review_overall_stars" class="input-text" style="font-size: 16px; font-weight: 800; color: var(--accent-gold);" required>
<option value="5">★★★★★ 5.0 (ممتاز ومبسط لأقصى درجة)</option>
<option value="4">★★★★☆ 4.0 (جيد جداً وشرح وافٍ)</option>
<option value="3">★★★☆☆ 3.0 (متوسط ويحتاج أمثلة إضافية)</option>
<option value="2">★★☆☆☆ 2.0 (أقل من المتوقع)</option>
<option value="1">★☆☆☆☆ 1.0 (صعب المتابعة)</option>
</select>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px;">
<div>
<label class="form-label">وضوح الشرح</label>
<select id="review_clarity" class="input-text">
<option value="5">واضح جداً (5/5)</option>
<option value="4">واضح (4/5)</option>
<option value="3">متوسط (3/5)</option>
</select>
</div>
<div>
<label class="form-label">سرعة الرد والمتابعة</label>
<select id="review_speed" class="input-text">
<option value="5">فوري ولحظي (5/5)</option>
<option value="4">سريع (4/5)</option>
<option value="3">متوسط (3/5)</option>
</select>
</div>
</div>
<div class="form-group">
<label class="form-label">رأيك الصريح أو ملاحظتك للأستاذ (اختياري)</label>
<textarea id="review_text_input" rows="3" placeholder="اكتب كيف ساعدك الأستاذ في فهم المادة..." class="input-text" style="resize: none;"></textarea>
</div>
<div style="background: rgba(0,245,212,0.06); border: 1px solid rgba(0,245,212,0.2); border-radius: 12px; padding: 10px 14px; margin-bottom: 18px; font-size: 11.5px; color: var(--text-secondary);">
🛡️ <strong>حماية الجدارة:</strong> يقوم النظام بحساب وزن تقييمك تلقائياً بناءً على مشاهدتك للحصص وإنجازك للفحوصات السقراطية لضمان العدالة التامة.
</div>
<button type="submit" id="btn_submit_teacher_review" class="btn-primary">
<span>إرسال التقييم وتحديث مؤشر الجدارة 🚀</span>
</button>
</form>
</div>
</div>
</main>
<!-- Client-Side Engine (100% Real Database Data) -->
@@ -1655,11 +1791,14 @@ class StudentPortal
function switchStudentTab(tab) {
document.getElementById('tab_lessons_content').style.display = (tab === 'lessons') ? 'block' : 'none';
document.getElementById('tab_teachers_content').style.display = (tab === 'teachers') ? 'block' : 'none';
document.getElementById('tab_steplab_content').style.display = (tab === 'steplab') ? 'block' : 'none';
document.getElementById('tab_chat_content').style.display = (tab === 'chat') ? 'block' : 'none';
document.getElementById('tab_exams_content').style.display = (tab === 'exams') ? 'block' : 'none';
document.getElementById('tab_btn_lessons').className = (tab === 'lessons') ? 'tab-btn active' : 'tab-btn';
const teachBtn = document.getElementById('tab_btn_teachers');
if (teachBtn) teachBtn.className = (tab === 'teachers') ? 'tab-btn active' : 'tab-btn';
document.getElementById('tab_btn_steplab').className = (tab === 'steplab') ? 'tab-btn active' : 'tab-btn';
document.getElementById('tab_btn_chat').className = (tab === 'chat') ? 'tab-btn active' : 'tab-btn';
document.getElementById('tab_btn_exams').className = (tab === 'exams') ? 'tab-btn active' : 'tab-btn';
@@ -1978,6 +2117,144 @@ class StudentPortal
if (!str) return '';
return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
// ==========================================
// Multi-Teacher Marketplace & Rating Handlers
// ==========================================
function startDirectChatWithTeacher(teacherId, teacherName) {
activeTeacherId = teacherId;
const badge = document.getElementById('chat_teacher_badge_name');
if (badge) {
badge.textContent = `${teacherName} (متصل الآن 🟢)`;
}
switchStudentTab('chat');
loadStudentMessages(teacherId);
showLuxuryToast('بدء المحادثة 💬', `أنت الآن على تواصل مباشر مع ${teacherName}`);
}
function openTeacherRatingModal(teacherId, teacherName) {
document.getElementById('review_teacher_id').value = teacherId;
document.getElementById('rating_modal_teacher_name').textContent = `تقييم: ${teacherName}`;
document.getElementById('teacher_rating_modal').style.display = 'flex';
}
function closeTeacherRatingModal() {
document.getElementById('teacher_rating_modal').style.display = 'none';
}
async function handleTeacherReviewSubmit(e) {
e.preventDefault();
const token = localStorage.getItem('saqel_student_jwt');
if (!token) {
alert('يرجى تسجيل الدخول أولاً لتتمكن من تقييم الأستاذ');
return;
}
const teacherId = document.getElementById('review_teacher_id').value;
const overall = parseFloat(document.getElementById('review_overall_stars').value);
const clarity = parseInt(document.getElementById('review_clarity').value);
const speed = parseInt(document.getElementById('review_speed').value);
const text = document.getElementById('review_text_input').value.trim();
const btn = document.getElementById('btn_submit_teacher_review');
btn.disabled = true;
btn.textContent = 'جارٍ احتساب الوزن والمعايرة... ⏳';
try {
const res = await fetch(`/api/teachers/${teacherId}/reviews`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
},
body: JSON.stringify({
rating_overall: overall,
rating_clarity: clarity,
rating_response_speed: speed,
review_text: text
})
});
const data = await res.json();
btn.disabled = false;
btn.textContent = 'إرسال التقييم وتحديث مؤشر الجدارة 🚀';
if (res.ok && data.status === 'success') {
closeTeacherRatingModal();
playChimeNotification();
const w = data.data?.review_weight || 1.0;
showLuxuryToast('تم اعتماد تقييمك بنجاح ⭐', `تم احتساب وزن التقييم المعرفي بنسبة (${(w * 100).toFixed(0)}%)`);
alert(`✅ شكراً لك!
تم تسجيل تقييمك ومعايرته برمجياً بنجاح.`);
} else {
alert('❌ خطأ: ' + (data.message || 'فشل إرسال التقييم'));
}
} catch (err) {
btn.disabled = false;
btn.textContent = 'إرسال التقييم وتحديث مؤشر الجدارة 🚀';
alert('❌ تعذر الاتصال بالخادم.');
}
}
function sortTeachersMarketplace(sortBy) {
const teachers = window.SAQEL_SERVER_DATA.teachers || [];
if (sortBy === 'fastest') {
teachers.sort((a, b) => (a.avg_response_minutes || 10) - (b.avg_response_minutes || 10));
} else {
teachers.sort((a, b) => (b.composite_merit_score || 90) - (a.composite_merit_score || 90));
}
renderTeachersGrid(teachers);
}
function renderTeachersGrid(teachers) {
const grid = document.getElementById('teachers_marketplace_grid');
if (!grid) return;
grid.innerHTML = teachers.map(t => `
<div style="background: rgba(0,0,0,0.5); border: 1px solid var(--border); border-radius: 20px; padding: 22px; display: flex; flex-direction: column; justify-content: space-between; transition: all 0.2s ease;">
<div>
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px;">
<div style="display: flex; align-items: center; gap: 10px;">
<div style="width: 44px; height: 44px; border-radius: 12px; background: linear-gradient(135deg, #0284C7, #0369A1); display: flex; align-items: center; justify-content: center; font-size: 20px; border: 1px solid rgba(255,255,255,0.15);">👨‍🏫</div>
<div>
<h4 style="font-size: 15.5px; font-weight: 800; color: #FFF;">${escapeHtml(t.full_name || 'الأستاذ المعتمد')}</h4>
<span style="font-size: 11.5px; color: var(--accent-cyan); font-weight: 600;">${escapeHtml(t.specialization || 'مدرس المنهاج المعتمد')}</span>
</div>
</div>
<span style="font-size: 10.5px; font-weight: 800; background: rgba(0,245,212,0.1); border: 1px solid rgba(0,245,212,0.3); color: var(--accent-cyan); padding: 3px 10px; border-radius: 980px;">
${escapeHtml(t.reputation_tier || 'معلم نخبوي')}
</span>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 14px 0;">
<div style="background: rgba(255,255,255,0.03); border: 1px solid var(--border); border-radius: 10px; padding: 8px 12px;">
<span style="font-size: 10px; color: var(--text-muted); display: block;">مؤشر الجدارة الكلي</span>
<span style="font-size: 15px; font-weight: 900; color: var(--accent-gold);">${parseFloat(t.composite_merit_score || 96.5).toFixed(1)}%</span>
<span style="font-size: 10px; color: var(--accent-gold);">★ ${parseFloat(t.star_equivalent || 4.9).toFixed(1)}</span>
</div>
<div style="background: rgba(255,255,255,0.03); border: 1px solid var(--border); border-radius: 10px; padding: 8px 12px;">
<span style="font-size: 10px; color: var(--text-muted); display: block;">سرعة الرد (Workerman)</span>
<span style="font-size: 15px; font-weight: 900; color: #34D399;">⚡ ${parseInt(t.avg_response_minutes || 3)} دقائق</span>
<span style="font-size: 10px; color: var(--text-muted);">نسبة التجاوب: ${parseFloat(t.response_rate_percentage || 99.0).toFixed(0)}%</span>
</div>
</div>
<p style="font-size: 12px; color: var(--text-secondary); line-height: 1.5; margin-bottom: 16px;">
${escapeHtml(t.bio || 'شرح معمق ومبسط للمنهاج الوزاري الأردني مع متابعة فردية فورية لكل طالب.')}
</p>
</div>
<div style="display: flex; gap: 8px; margin-top: 10px;">
<button type="button" onclick="startDirectChatWithTeacher(${t.id}, '${escapeHtml(t.full_name)}')" class="btn-primary" style="flex: 1; padding: 9px; font-size: 12px;">
تحدث مع الأستاذ 💬
</button>
<button type="button" onclick="openTeacherRatingModal(${t.id}, '${escapeHtml(t.full_name)}')" style="background: rgba(245,158,11,0.12); border: 1px solid rgba(245,158,11,0.3); color: var(--accent-gold); border-radius: 980px; padding: 0 16px; font-size: 12px; font-weight: 700; cursor: pointer;">
تقييم ⭐
</button>
</div>
</div>
`).join('');
}
</script>
</body>
</html>