fix: Enable dynamic student/teacher contact discovery, dual REST/WebSocket persistence, and multi-path .env detection

This commit is contained in:
Hamza-Ayed
2026-08-27 03:47:45 +03:00
parent b11a4bd8c1
commit 7fd67756b4
4 changed files with 198 additions and 140 deletions
+83 -57
View File
@@ -960,6 +960,8 @@ class StudentPortal
}
}
let activeTeacherId = null;
function switchStudentTab(tab) {
document.getElementById('tab_lessons_content').style.display = (tab === 'lessons') ? 'block' : 'none';
document.getElementById('tab_chat_content').style.display = (tab === 'chat') ? 'block' : 'none';
@@ -968,51 +970,59 @@ class StudentPortal
document.getElementById('tab_btn_lessons').className = (tab === 'lessons') ? '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';
}
// Socratic In-Video Checkpoint Execution
function triggerCheckpointDemo() {
const video = document.getElementById('lesson_video_player');
if (video) video.pause();
document.getElementById('socratic_quiz_modal').style.display = 'flex';
}
function handleCheckpointAnswer(btn, isCorrect) {
const feedback = document.getElementById('checkpoint_feedback');
const btns = document.querySelectorAll('.quiz-option-btn');
btns.forEach(b => b.disabled = true);
if (isCorrect) {
btn.className = 'quiz-option-btn correct';
feedback.style.color = '#34D399';
feedback.textContent = '✓ إجابة ممتازة وصحيحة! تم تثبيت المفهوم المعرفي بنجاح.';
feedback.style.display = 'block';
setTimeout(() => {
document.getElementById('socratic_quiz_modal').style.display = 'none';
btns.forEach(b => { b.disabled = false; b.className = 'quiz-option-btn'; });
feedback.style.display = 'none';
const video = document.getElementById('lesson_video_player');
if (video) video.play();
}, 1800);
} else {
btn.className = 'quiz-option-btn wrong';
feedback.style.color = '#F87171';
feedback.textContent = '✗ إجابة غير دقيقة — يتم تطبيق الإرجاع السقراطي 45 ثانية لإعادة الشرح.';
feedback.style.display = 'block';
setTimeout(() => {
document.getElementById('socratic_quiz_modal').style.display = 'none';
btns.forEach(b => { b.disabled = false; b.className = 'quiz-option-btn'; });
feedback.style.display = 'none';
const video = document.getElementById('lesson_video_player');
if (video) {
video.currentTime = Math.max(0, video.currentTime - 45);
video.play();
}
}, 2200);
if (tab === 'chat') {
loadStudentChat();
}
}
// Real-time Chat
async function loadStudentChat() {
const token = localStorage.getItem('saqel_student_jwt');
if (!token) return;
try {
const res = await fetch('/api/chat/conversations', {
headers: { 'Authorization': 'Bearer ' + token }
});
const data = await res.json();
if (res.ok && data.data && data.data.length > 0) {
const teacher = data.data.find(c => c.role === 'teacher') || data.data[0];
if (teacher) {
activeTeacherId = teacher.user_id;
const badge = document.getElementById('chat_teacher_badge_name');
if (badge) {
badge.textContent = `${teacher.full_name} (${teacher.specialization || 'مدرس المادة'}) (متصل الآن 🟢)`;
}
loadStudentMessages(activeTeacherId);
}
}
} catch (e) {
console.error('Load student chat notice:', e);
}
}
async function loadStudentMessages(teacherId) {
const token = localStorage.getItem('saqel_student_jwt');
if (!token || !teacherId) return;
try {
const res = await fetch(`/api/chat/messages?other_user_id=${teacherId}`, {
headers: { 'Authorization': 'Bearer ' + token }
});
const data = await res.json();
if (res.ok && data.data?.messages) {
const messagesArea = document.getElementById('student_chat_messages');
messagesArea.innerHTML = '';
if (data.data.messages.length === 0) {
messagesArea.innerHTML = '<div style="text-align: center; color: var(--text-muted); font-size: 13px; margin-top: 40px;">مرحباً بك! اكتب سؤالك هنا وسيجيبك أستاذ المادة فوراً.</div>';
} else {
data.data.messages.forEach(msg => appendChatMessage(msg));
}
}
} catch (e) {
console.error('Fetch student messages notice:', e);
}
}
// Real-time Chat Rendering
function appendChatMessage(msg) {
const messagesArea = document.getElementById('student_chat_messages');
const isMine = msg.is_mine || false;
@@ -1032,31 +1042,47 @@ class StudentPortal
if (!message) return;
input.value = '';
const teacherId = 1;
const token = localStorage.getItem('saqel_student_jwt');
// If activeTeacherId is null, try to find teacher
if (!activeTeacherId) {
await loadStudentChat();
}
const targetId = activeTeacherId || 1;
if (wsSocket && wsSocket.readyState === WebSocket.OPEN) {
wsSocket.send(JSON.stringify({
event: 'chat_send',
data: {
receiver_id: teacherId,
message: message,
message_type: 'text'
}
}));
} else {
const token = localStorage.getItem('saqel_student_jwt');
await fetch('/api/chat/messages', {
// 1. Send via REST API to guarantee database persistence
try {
const res = await fetch('/api/chat/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
},
body: JSON.stringify({
receiver_id: teacherId,
message: message
receiver_id: targetId,
message: message,
message_type: 'text'
})
});
appendChatMessage({ message: message, is_mine: true });
const data = await res.json();
if (res.ok && data.data) {
appendChatMessage({ message: message, is_mine: true, created_at: 'الآن' });
}
} catch (err) {
console.error('REST chat send notice:', err);
appendChatMessage({ message: message, is_mine: true, created_at: 'الآن' });
}
// 2. Also broadcast over WebSocket
if (wsSocket && wsSocket.readyState === WebSocket.OPEN) {
wsSocket.send(JSON.stringify({
event: 'chat_send',
data: {
receiver_id: targetId,
message: message,
message_type: 'text'
}
}));
}
}