504 lines
28 KiB
PHP
504 lines
28 KiB
PHP
<?php
|
|
|
|
namespace App\Views;
|
|
|
|
use App\Services\CurriculumService;
|
|
|
|
class CurriculumStudio
|
|
{
|
|
public static function render(): string
|
|
{
|
|
$tree = CurriculumService::getCurriculumTree();
|
|
$treeJson = json_encode($tree, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
|
|
$serverStorageDir = realpath(__DIR__ . '/../../storage/curriculum') ?: (dirname(__DIR__, 2) . '/storage/curriculum');
|
|
|
|
ob_start();
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="ar" dir="rtl">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>استوديو إدارة وفهرسة المناهج الحية — صَقِل Enterprise</title>
|
|
<link rel="icon" type="image/jpeg" href="/assets/images/saqel_logo.jpg">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Alexandria:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
|
|
<script>
|
|
window.CURRICULUM_TREE = <?= $treeJson ?>;
|
|
window.SERVER_STORAGE_DIR = "<?= addslashes($serverStorageDir) ?>";
|
|
</script>
|
|
<style>
|
|
:root {
|
|
--bg-base: #0B0F19;
|
|
--bg-card: rgba(22, 27, 34, 0.85);
|
|
--border: rgba(255, 255, 255, 0.08);
|
|
--accent-cyan: #00F5D4;
|
|
--accent-gold: #FFD166;
|
|
--accent-purple: #7B2CBF;
|
|
--text-primary: #F8FAFC;
|
|
--text-secondary: #94A3B8;
|
|
--text-muted: #64748B;
|
|
}
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Alexandria', sans-serif; }
|
|
body { background-color: var(--bg-base); color: var(--text-primary); min-height: 100vh; }
|
|
|
|
header {
|
|
position: sticky; top: 0; z-index: 100;
|
|
background: rgba(11, 15, 25, 0.85); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
|
|
border-bottom: 1px solid var(--border); padding: 12px 24px;
|
|
}
|
|
.header-content { max-width: 1400px; margin: 0 auto; display: flex; justify-content: space-between; align-items: center; }
|
|
|
|
.container { max-width: 1400px; margin: 24px auto; padding: 0 20px; }
|
|
|
|
.main-layout { display: grid; grid-template-columns: 380px 1fr; gap: 24px; }
|
|
@media (max-width: 900px) { .main-layout { grid-template-columns: 1fr; } }
|
|
|
|
.tree-sidebar {
|
|
background: var(--bg-card); border: 1px solid var(--border); border-radius: 20px; padding: 20px;
|
|
max-height: calc(100vh - 120px); overflow-y: auto;
|
|
}
|
|
|
|
.tree-node { margin-bottom: 14px; }
|
|
.tree-header {
|
|
font-size: 13px; font-weight: 800; color: var(--accent-gold); display: flex; align-items: center; gap: 6px;
|
|
padding: 8px 12px; background: rgba(255, 209, 102, 0.08); border-radius: 10px; cursor: pointer;
|
|
}
|
|
.tree-subject {
|
|
font-size: 12.5px; font-weight: 700; color: var(--accent-cyan); margin: 6px 12px; padding: 4px 8px;
|
|
cursor: pointer; display: flex; align-items: center; gap: 6px;
|
|
}
|
|
.tree-unit {
|
|
font-size: 12px; font-weight: 600; color: var(--text-secondary); margin: 4px 20px; padding: 3px 6px;
|
|
}
|
|
.tree-lesson {
|
|
font-size: 11.5px; color: var(--text-muted); margin: 3px 30px; padding: 6px 10px; border-radius: 8px;
|
|
cursor: pointer; transition: all 0.2s;
|
|
}
|
|
.tree-lesson:hover, .tree-lesson.active {
|
|
background: rgba(0, 245, 212, 0.15); color: var(--accent-cyan); font-weight: 700;
|
|
}
|
|
|
|
.workspace-card {
|
|
background: var(--bg-card); border: 1px solid var(--border); border-radius: 20px; padding: 26px;
|
|
backdrop-filter: blur(16px);
|
|
}
|
|
|
|
.btn-primary {
|
|
background: linear-gradient(135deg, #0284C7, #0369A1); color: #FFF; border: none; padding: 10px 20px;
|
|
border-radius: 12px; font-size: 13px; font-weight: 700; cursor: pointer; transition: all 0.2s;
|
|
display: inline-flex; align-items: center; gap: 8px;
|
|
}
|
|
.btn-primary:hover { transform: translateY(-1px); box-shadow: 0 4px 16px rgba(2, 132, 199, 0.4); }
|
|
|
|
.search-box {
|
|
width: 100%; background: rgba(0,0,0,0.4); border: 1px solid var(--border); border-radius: 12px;
|
|
padding: 10px 16px; color: #FFF; font-size: 13px; margin-bottom: 16px; outline: none;
|
|
}
|
|
.search-box:focus { border-color: var(--accent-cyan); }
|
|
|
|
.md-editor {
|
|
width: 100%; height: 480px; background: rgba(0,0,0,0.5); border: 1px solid var(--border);
|
|
border-radius: 14px; padding: 16px; color: #F1F5F9; font-family: monospace; font-size: 13px;
|
|
line-height: 1.7; resize: vertical; outline: none;
|
|
}
|
|
|
|
.server-path-box {
|
|
background: rgba(0,0,0,0.4); border: 1px solid var(--border); border-radius: 8px; padding: 6px 12px;
|
|
font-size: 11.5px; font-family: monospace; color: var(--accent-cyan); margin-bottom: 14px;
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<header>
|
|
<div class="header-content">
|
|
<div style="display: flex; align-items: center; gap: 10px;">
|
|
<img src="/assets/images/saqel_logo.jpg" alt="صَقِل" style="width: 36px; height: 36px; border-radius: 10px;">
|
|
<span style="font-weight: 800; font-size: 18px; color: #FFF;">صَقِل Enterprise</span>
|
|
<span style="font-size: 11px; font-weight: 700; color: var(--accent-gold); background: rgba(255,209,102,0.12); padding: 2px 10px; border-radius: 980px;">استوديو تفريغ وفهرسة المناهج الحية 📚</span>
|
|
</div>
|
|
<div style="display: flex; gap: 10px;">
|
|
<a href="/teacher" style="font-size: 12px; color: var(--accent-cyan); text-decoration: none; border: 1px solid rgba(0,245,212,0.3); padding: 5px 14px; border-radius: 980px;">استوديو المعلم 👨🏫</a>
|
|
<a href="/student" style="font-size: 12px; color: var(--text-muted); text-decoration: none; border: 1px solid var(--border); padding: 5px 14px; border-radius: 980px;">بوابة الطالب 🎓</a>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="container">
|
|
<!-- Live PDF Ingestion Bar -->
|
|
<div class="workspace-card" style="margin-bottom: 24px; background: linear-gradient(135deg, rgba(30, 41, 59, 0.7), rgba(15, 23, 42, 0.9));">
|
|
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px;">
|
|
<div>
|
|
<h2 style="font-size: 20px; font-weight: 900; color: #FFF; margin-bottom: 4px;">رفع وتفريغ كتب المناهج الوزارية (PDF ⟵ Markdown Tree) 📑</h2>
|
|
<p style="font-size: 12.5px; color: var(--text-secondary);" id="upload_status_desc">اختر ملف كتاب المنهاج (PDF) ليقوم الخادم بتفريغه وفهرسة وحداته ودروسه الحقيقية على القرص وتوليد ملفات المارك داون.</p>
|
|
</div>
|
|
<div style="display: flex; gap: 10px; align-items: center;">
|
|
<input type="file" id="curriculum_pdf_input" accept=".pdf" style="display: none;" onchange="handleRealPdfUpload(this)">
|
|
<button type="button" id="btn_upload_pdf" onclick="document.getElementById('curriculum_pdf_input').click()" class="btn-primary">
|
|
<span>📤 رفع كتاب المنهاج PDF الحقيقي</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="main-layout">
|
|
<!-- Sidebar: Hierarchical Curriculum Tree Explorer -->
|
|
<div class="tree-sidebar">
|
|
<input type="text" class="search-box" id="curriculum_search_input" placeholder="🔍 ابحث في المناهج والوحدات ونتاجات التعلم..." oninput="handleCurriculumSearch(this.value)">
|
|
|
|
<div id="curriculum_tree_container">
|
|
<!-- Rendered Dynamically from Real Storage -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Workspace: Markdown Editor & Extraction Inspector -->
|
|
<div class="workspace-card" id="workspace_content_area">
|
|
<div id="empty_selection_view" style="display: none; text-align: center; padding: 60px 20px;">
|
|
<div style="font-size: 48px; margin-bottom: 12px;">📚</div>
|
|
<h3 style="font-size: 18px; font-weight: 800; color: #FFF; margin-bottom: 8px;">لا توجد مناهج مضافة حالياً</h3>
|
|
<p style="font-size: 13px; color: var(--text-muted); max-width: 480px; margin: 0 auto;">ارفع كتاب المنهاج PDF لبناء الشجرة وتفريغ الدروس بالمارك داون على السيرفر.</p>
|
|
</div>
|
|
|
|
<div id="active_lesson_view" style="display: none;">
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; flex-wrap: wrap; gap: 10px;">
|
|
<div>
|
|
<span style="font-size: 11px; font-weight: 800; color: var(--accent-cyan); background: rgba(0,245,212,0.1); padding: 2px 8px; border-radius: 6px;" id="current_breadcrumb"></span>
|
|
<h3 style="font-size: 18px; font-weight: 900; color: #FFF; margin-top: 6px;" id="current_lesson_title_display"></h3>
|
|
</div>
|
|
<button type="button" onclick="saveActiveMarkdown()" class="btn-primary" style="background: linear-gradient(135deg, #10B981, #059669);">
|
|
💾 حفظ التعديلات على السيرفر
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Exact Server Storage Path Readout -->
|
|
<div class="server-path-box">
|
|
<span>📁 مسار الملف على السيرفر: <strong id="server_file_path_display" style="color: #FFF;"></strong></span>
|
|
<span style="color: var(--accent-gold); font-size: 11px;">(ملف Markdown حي على القرص)</span>
|
|
</div>
|
|
|
|
<div style="margin-bottom: 14px;">
|
|
<span style="font-size: 12px; font-weight: 700; color: var(--text-muted); display: block; margin-bottom: 6px;">نتاجات التعلم والمفاهيم المستهدفة:</span>
|
|
<div id="current_outcomes_tags" style="display: flex; gap: 8px; flex-wrap: wrap;"></div>
|
|
</div>
|
|
|
|
<!-- Markdown Content Area -->
|
|
<textarea id="lesson_markdown_editor" class="md-editor"></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let currentFilePath = '';
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
renderCurriculumTree(window.CURRICULUM_TREE);
|
|
});
|
|
|
|
function renderCurriculumTree(tree) {
|
|
const container = document.getElementById('curriculum_tree_container');
|
|
const entries = Object.entries(tree || {});
|
|
|
|
if (entries.length === 0) {
|
|
container.innerHTML = `
|
|
<div style="padding: 40px 20px; text-align: center; color: var(--text-muted); font-size: 13px;">
|
|
<div style="font-size: 32px; margin-bottom: 8px;">📂</div>
|
|
<strong>لا توجد مناهج مفهرسة بعد</strong>
|
|
<p style="margin-top: 4px;">ارفع كتاب المنهاج الوزاري (PDF) عبر الزر بالأعلى ليتم بناء الشجرة وفهرستها تلقائياً.</p>
|
|
</div>
|
|
`;
|
|
document.getElementById('empty_selection_view').style.display = 'block';
|
|
document.getElementById('active_lesson_view').style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
for (const [gradeKey, grade] of entries) {
|
|
html += `
|
|
<div class="tree-node">
|
|
<div class="tree-header">📁 ${escapeHtml(grade.name)}</div>
|
|
`;
|
|
|
|
for (const [subKey, sub] of Object.entries(grade.subjects || {})) {
|
|
html += `<div class="tree-subject">📖 ${escapeHtml(sub.name)}</div>`;
|
|
|
|
for (const [semKey, sem] of Object.entries(sub.semesters || {})) {
|
|
for (const [unitKey, unit] of Object.entries(sem.units || {})) {
|
|
html += `<div class="tree-unit">🔹 ${escapeHtml(unit.name)}</div>`;
|
|
|
|
for (const les of (unit.lessons || [])) {
|
|
const activeClass = (les.file === currentFilePath) ? ' active' : '';
|
|
html += `
|
|
<div class="tree-lesson${activeClass}" onclick="selectLesson('${les.file}', '${escapeHtml(les.title)}', '${escapeHtml(grade.name)} ⟵ ${escapeHtml(sub.name)} ⟵ ${escapeHtml(unit.name)}', ${JSON.stringify(les.outcomes || []).replace(/"/g, '"')})">
|
|
📄 ${escapeHtml(les.title)}
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// NEW: Render Resources (Minhaji Style Categories)
|
|
if (sem.resources && Object.keys(sem.resources).length > 0) {
|
|
html += `<div style="margin-top: 15px; margin-bottom: 5px; font-weight: bold; color: var(--text-secondary); font-size: 11px; padding-right: 15px;">📂 مصادر المادة الإضافية:</div>`;
|
|
html += `<div style="display: flex; flex-wrap: wrap; gap: 6px; padding-right: 15px; margin-bottom: 10px;">`;
|
|
for (const [resKey, resourceGroup] of Object.entries(sem.resources)) {
|
|
if (!resourceGroup.items || resourceGroup.items.length === 0) continue;
|
|
|
|
// Hexagon / Badge Style
|
|
html += `<div style="flex: 1 1 45%; background: var(--bg-hover); border: 1px solid var(--border-color); border-radius: 6px; padding: 8px; text-align: center; cursor: pointer; transition: all 0.2s;" onmouseover="this.style.background='rgba(255,209,102,0.1)'" onmouseout="this.style.background='var(--bg-hover)'">
|
|
<div style="font-size: 14px; margin-bottom: 4px;">📌</div>
|
|
<div style="font-size: 10px; font-weight: bold; color: var(--text-primary);">${escapeHtml(resourceGroup.name)}</div>
|
|
</div>`;
|
|
|
|
// Render actual files below the badge (hidden or just list them)
|
|
for (const item of resourceGroup.items) {
|
|
const activeClass = (item.file === currentFilePath) ? ' active' : '';
|
|
html += `
|
|
<div style="width: 100%;" class="tree-lesson${activeClass}" onclick="selectLesson('${item.file}', '${escapeHtml(item.title)}', '${escapeHtml(grade.name)} ⟵ ${escapeHtml(sub.name)} ⟵ ${escapeHtml(resourceGroup.name)}', ['مرفق إضافي'])">
|
|
📎 ${escapeHtml(item.title)}
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
html += `</div>`;
|
|
}
|
|
}
|
|
}
|
|
|
|
html += `</div>`;
|
|
}
|
|
|
|
container.innerHTML = html;
|
|
|
|
// Auto-select first lesson if none selected
|
|
if (!currentFilePath) {
|
|
const firstGrade = Object.values(tree)[0];
|
|
const firstSub = firstGrade?.subjects ? Object.values(firstGrade.subjects)[0] : null;
|
|
const firstSem = firstSub?.semesters ? Object.values(firstSub.semesters)[0] : null;
|
|
const firstUnit = firstSem?.units ? Object.values(firstSem.units)[0] : null;
|
|
const firstLes = firstUnit?.lessons ? firstUnit.lessons[0] : null;
|
|
if (firstLes) {
|
|
selectLesson(firstLes.file, firstLes.title, `${firstGrade.name} ⟵ ${firstSub.name} ⟵ ${firstUnit.name}`, firstLes.outcomes || []);
|
|
}
|
|
}
|
|
}
|
|
async function selectLesson(filePath, title, breadcrumb, outcomes) {
|
|
currentFilePath = filePath;
|
|
document.getElementById('empty_selection_view').style.display = 'none';
|
|
document.getElementById('active_lesson_view').style.display = 'block';
|
|
|
|
document.getElementById('current_breadcrumb').textContent = breadcrumb;
|
|
document.getElementById('current_lesson_title_display').textContent = title;
|
|
document.getElementById('server_file_path_display').textContent = `backend/storage/curriculum/${filePath}`;
|
|
|
|
// Render Outcomes
|
|
const tags = document.getElementById('current_outcomes_tags');
|
|
tags.innerHTML = (outcomes || []).map(o => `
|
|
<span style="font-size: 11px; font-weight: 700; background: rgba(255,209,102,0.12); color: var(--accent-gold); border: 1px solid rgba(255,209,102,0.3); padding: 3px 10px; border-radius: 980px;">
|
|
🎯 ${escapeHtml(o)}
|
|
</span>
|
|
`).join('');
|
|
|
|
// Highlight Active in Sidebar
|
|
document.querySelectorAll('.tree-lesson').forEach(el => el.classList.remove('active'));
|
|
event?.currentTarget?.classList.add('active');
|
|
|
|
// Fetch Real Markdown from Server
|
|
try {
|
|
const res = await fetch(`/api/curriculum/lesson?file=${encodeURIComponent(filePath)}`);
|
|
const data = await res.json();
|
|
if (res.ok && data.status === 'success') {
|
|
document.getElementById('lesson_markdown_editor').value = data.content || '';
|
|
if (data.server_full_path) {
|
|
document.getElementById('server_file_path_display').textContent = data.server_full_path;
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error('Fetch markdown error:', e);
|
|
}
|
|
}
|
|
|
|
async function handleRealPdfUpload(input) {
|
|
if (!input.files || !input.files[0]) return;
|
|
const file = input.files[0];
|
|
|
|
const btn = document.getElementById('btn_upload_pdf');
|
|
const desc = document.getElementById('upload_status_desc');
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span>⏳ جارٍ فك تشفير وتفريغ الكتاب...</span>';
|
|
desc.textContent = `جارٍ تفريغ ملف [${file.name}] إلى شجرة ملفات Markdown حقيقية على السيرفر...`;
|
|
|
|
const formData = new FormData();
|
|
formData.append('pdf_file', file);
|
|
|
|
try {
|
|
const res = await fetch('/api/curriculum/upload-pdf', {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
const data = await res.json();
|
|
if (res.ok && data.status === 'success') {
|
|
window.CURRICULUM_TREE = data.tree;
|
|
currentFilePath = data.active_file || '';
|
|
renderCurriculumTree(data.tree);
|
|
|
|
if (data.active_file) {
|
|
document.getElementById('empty_selection_view').style.display = 'none';
|
|
document.getElementById('active_lesson_view').style.display = 'block';
|
|
document.getElementById('lesson_markdown_editor').value = data.active_md || '';
|
|
document.getElementById('current_lesson_title_display').textContent = data.active_title || 'الدرس المستخرج';
|
|
document.getElementById('current_breadcrumb').textContent = data.active_breadcrumb || 'المسار التعليمي';
|
|
document.getElementById('server_file_path_display').textContent = `${data.server_storage_dir}/${data.active_file}`;
|
|
}
|
|
|
|
alert(`✅ ${data.message}\n\nتم حفظ ملفات المنهاج على السيرفر في:\n${data.server_storage_dir}`);
|
|
desc.textContent = `تم اعتماد المنهاج [${file.name}] بنجاح في قاعدة المعرفة الشجرية!`;
|
|
} else {
|
|
alert('⚠️ ' + (data.message || 'فشلت معالجة الملف'));
|
|
}
|
|
} catch (err) {
|
|
console.error('PDF Upload error:', err);
|
|
alert('حدث خطأ في الاتصال بالخادم أثناء رفع الملف.');
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<span>📤 رفع كتاب المنهاج PDF الحقيقي</span>';
|
|
input.value = '';
|
|
}
|
|
}
|
|
|
|
async function saveActiveMarkdown() {
|
|
if (!currentFilePath) return;
|
|
const content = document.getElementById('lesson_markdown_editor').value;
|
|
|
|
try {
|
|
const res = await fetch('/api/curriculum/save-lesson', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ file: currentFilePath, content: content })
|
|
});
|
|
const data = await res.json();
|
|
if (res.ok && data.status === 'success') {
|
|
alert(`✅ تم حفظ واعتماد التعديلات بنجاح في الملف:\n${data.server_full_path || currentFilePath}`);
|
|
} else {
|
|
alert('⚠️ فشل حفظ الملف');
|
|
}
|
|
} catch (e) {
|
|
alert('تعذر الاتصال بالسيرفر');
|
|
}
|
|
}
|
|
|
|
function handleCurriculumSearch(q) {
|
|
if (!q || q.trim() === '') {
|
|
renderCurriculumTree(window.CURRICULUM_TREE);
|
|
return;
|
|
}
|
|
const qClean = q.toLowerCase().trim();
|
|
const filtered = {};
|
|
|
|
for (const [gKey, grade] of Object.entries(window.CURRICULUM_TREE || {})) {
|
|
let hasMatch = false;
|
|
const newSubs = {};
|
|
|
|
for (const [sKey, sub] of Object.entries(grade.subjects || {})) {
|
|
const newSems = {};
|
|
for (const [semKey, sem] of Object.entries(sub.semesters || {})) {
|
|
const newUnits = {};
|
|
for (const [uKey, unit] of Object.entries(sem.units || {})) {
|
|
const matchedLessons = (unit.lessons || []).filter(l =>
|
|
l.title.toLowerCase().includes(qClean) ||
|
|
(l.outcomes && l.outcomes.some(o => o.toLowerCase().includes(qClean)))
|
|
);
|
|
if (matchedLessons.length > 0) {
|
|
newUnits[uKey] = { ...unit, lessons: matchedLessons };
|
|
hasMatch = true;
|
|
}
|
|
}
|
|
if (Object.keys(newUnits).length > 0) {
|
|
newSems[semKey] = { ...sem, units: newUnits };
|
|
}
|
|
}
|
|
if (Object.keys(newSems).length > 0) {
|
|
newSubs[sKey] = { ...sub, semesters: newSems };
|
|
}
|
|
}
|
|
if (hasMatch) {
|
|
filtered[gKey] = { ...grade, subjects: newSubs };
|
|
}
|
|
}
|
|
|
|
renderCurriculumTree(filtered);
|
|
}
|
|
|
|
function escapeHtml(str) {
|
|
if (!str) return '';
|
|
const d = document.createElement('div');
|
|
d.textContent = str;
|
|
return d.innerHTML;
|
|
}
|
|
</script>
|
|
|
|
<!-- Upload Progress Modal -->
|
|
<div id="upload_progress_overlay" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(10, 15, 26, 0.85); backdrop-filter: blur(8px); z-index: 9999; flex-direction: column; align-items: center; justify-content: center;">
|
|
<div style="background: var(--bg-card); padding: 30px; border-radius: 16px; border: 1px solid var(--border-color); width: 400px; max-width: 90%; text-align: center; box-shadow: 0 10px 40px rgba(0,0,0,0.5);">
|
|
<div id="ai_spinner" style="font-size: 40px; margin-bottom: 15px; animation: pulse 1.5s infinite;">🧠</div>
|
|
<h3 style="color: var(--text-primary); margin-bottom: 10px; font-family: 'SF Arabic', sans-serif;">معالجة الذكاء الاصطناعي</h3>
|
|
<div style="width: 100%; background: var(--bg-hover); border-radius: 10px; height: 10px; margin-bottom: 15px; overflow: hidden; position: relative;">
|
|
<div id="upload_progress_bar" style="height: 100%; width: 0%; background: linear-gradient(90deg, var(--accent-gold), #ffed4a); transition: width 0.4s ease;"></div>
|
|
</div>
|
|
<p id="upload_progress_text" style="color: var(--text-secondary); font-size: 13px; font-weight: bold;">جاري التهيئة...</p>
|
|
<p id="upload_progress_percent" style="color: var(--accent-gold); font-size: 12px; margin-top: 5px;">0%</p>
|
|
|
|
<button id="btn_view_logs" type="button" style="margin-top: 15px; background: transparent; border: 1px solid var(--border-color); color: var(--text-muted); padding: 5px 12px; border-radius: 6px; font-size: 11px; cursor: pointer; transition: all 0.2s;" onmouseover="this.style.color='#fff'" onmouseout="this.style.color='var(--text-muted)'" onclick="toggleUploadLogs()">🔍 عرض سجل السيرفر (Logs)</button>
|
|
<div id="upload_logs_container" style="display: none; margin-top: 15px; width: 100%; text-align: left;">
|
|
<textarea id="upload_logs_textarea" readonly style="width: 100%; height: 120px; background: #000; color: #0f0; font-family: monospace; font-size: 11px; border: 1px solid #333; border-radius: 4px; padding: 8px; resize: none; direction: ltr;"></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let currentUploadTaskId = null;
|
|
|
|
function toggleUploadLogs() {
|
|
const container = document.getElementById('upload_logs_container');
|
|
if (container.style.display === 'none') {
|
|
container.style.display = 'block';
|
|
fetchUploadLogs();
|
|
} else {
|
|
container.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
async function fetchUploadLogs() {
|
|
if (!currentUploadTaskId) return;
|
|
try {
|
|
const res = await fetch(`/api/curriculum/upload-log?task_id=${currentUploadTaskId}`);
|
|
const data = await res.json();
|
|
const textarea = document.getElementById('upload_logs_textarea');
|
|
if (data.status === 'success') {
|
|
textarea.value = data.log || 'No logs generated yet...';
|
|
textarea.scrollTop = textarea.scrollHeight;
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
@keyframes pulse {
|
|
0% { transform: scale(1); opacity: 1; }
|
|
50% { transform: scale(1.1); opacity: 0.7; }
|
|
100% { transform: scale(1); opacity: 1; }
|
|
}
|
|
</style>
|
|
|
|
</body>
|
|
</html>
|
|
<?php
|
|
return ob_get_clean();
|
|
}
|
|
}
|