Commit bcd98b65 authored by Mahmoud Aglan's avatar Mahmoud Aglan

pdf

parent e1b6511d
...@@ -7,6 +7,7 @@ use App\Core\Controller; ...@@ -7,6 +7,7 @@ use App\Core\Controller;
use App\Core\Request; use App\Core\Request;
use App\Core\Response; use App\Core\Response;
use App\Modules\Tutorials\TutorialRegistry; use App\Modules\Tutorials\TutorialRegistry;
use App\Shared\Services\PdfExportService;
class TutorialController extends Controller class TutorialController extends Controller
{ {
...@@ -540,4 +541,144 @@ class TutorialController extends Controller ...@@ -540,4 +541,144 @@ class TutorialController extends Controller
'nextTitle' => $next ? $tutorials[$next]['title'] : null, 'nextTitle' => $next ? $tutorials[$next]['title'] : null,
]); ]);
} }
public function bookPage(Request $request): Response
{
$sections = TutorialRegistry::getSections();
$totalTutorials = 0;
foreach (array_keys($sections) as $key) {
$totalTutorials += count(TutorialRegistry::getTutorials($key));
}
$totalTutorials += count(self::MEMBERSHIP_TUTORIALS) + count(self::SA_TUTORIALS) + count(self::TREASURY_TUTORIALS);
return $this->view('Tutorials.Views.book', [
'totalSections' => count($sections) + 3,
'totalTutorials' => $totalTutorials,
]);
}
public function exportPdf(Request $request): Response
{
$screenshotsPath = realpath(__DIR__ . '/../../../../public/assets/tutorials/screenshots');
$viewsPath = realpath(__DIR__ . '/../Views');
$bookData = $this->collectBookData($screenshotsPath, $viewsPath);
ob_start();
$data = $bookData;
include __DIR__ . '/../Views/export_pdf.php';
$html = ob_get_clean();
return PdfExportService::renderToPdf($html, 'Book-of-the-ERP.pdf');
}
private function collectBookData(string $screenshotsPath, string $viewsPath): array
{
$book = [
'generatedAt' => date('Y-m-d H:i'),
'sections' => [],
];
$detailedSections = [
'membership' => [
'title' => 'شئون العضوية',
'tutorials' => self::MEMBERSHIP_TUTORIALS,
'categories' => self::MEMBERSHIP_CATEGORIES,
'viewDir' => $viewsPath . '/membership',
],
'sports-activity' => [
'title' => 'الأنشطة الرياضية',
'tutorials' => self::SA_TUTORIALS,
'categories' => self::CATEGORIES,
'viewDir' => $viewsPath . '/sports_activity',
],
'treasury' => [
'title' => 'الخزنة الفرعية',
'tutorials' => self::TREASURY_TUTORIALS,
'categories' => self::TREASURY_CATEGORIES,
'viewDir' => $viewsPath . '/treasury',
],
];
foreach ($detailedSections as $key => $section) {
$tutorials = [];
foreach ($section['tutorials'] as $slug => $tutorial) {
$viewFile = $section['viewDir'] . '/' . str_replace('-', '_', $slug) . '.php';
$content = '';
if (file_exists($viewFile)) {
$content = file_get_contents($viewFile);
$content = $this->extractTutorialContent($content);
}
$tutorials[$slug] = array_merge($tutorial, ['htmlContent' => $content]);
}
$book['sections'][$key] = [
'title' => $section['title'],
'tutorials' => $tutorials,
'categories' => $section['categories'],
];
}
$registrySections = TutorialRegistry::getSections();
foreach ($registrySections as $sectionKey => $sectionMeta) {
if (isset($book['sections'][$sectionKey])) {
continue;
}
$tutorials = TutorialRegistry::getTutorials($sectionKey);
$categories = TutorialRegistry::getCategories($sectionKey);
$screenshot = null;
if ($screenshotsPath) {
$possibleFiles = [
$screenshotsPath . '/' . $sectionKey . '.png',
$screenshotsPath . '/' . str_replace('-', '_', $sectionKey) . '.png',
];
foreach ($possibleFiles as $f) {
if (file_exists($f)) {
$screenshot = $f;
break;
}
}
}
$book['sections'][$sectionKey] = [
'title' => $sectionMeta['title'],
'subtitle' => $sectionMeta['subtitle'] ?? '',
'tutorials' => $tutorials,
'categories' => $categories,
'screenshot' => $screenshot,
];
}
return $book;
}
private function extractTutorialContent(string $raw): string
{
$start = strpos($raw, '<div class="tut-page">');
if ($start === false) {
$start = strpos($raw, '<div class="tut-header">');
}
if ($start === false) {
return '';
}
$content = substr($raw, $start);
$navPos = strpos($content, '<div class="tut-nav">');
if ($navPos !== false) {
$content = substr($content, 0, $navPos);
}
$content = preg_replace('/<div class="tut-breadcrumb">.*?<\/div>/s', '', $content);
$content = str_replace('loading="lazy"', '', $content);
$publicPath = realpath(__DIR__ . '/../../../../public');
$content = preg_replace(
'#src="(/assets/[^"]+)"#',
'src="file://' . $publicPath . '$1"',
$content
);
$content = preg_replace('/<i\s+data-lucide="[^"]*"[^>]*><\/i>/', '', $content);
return $content;
}
} }
...@@ -3,6 +3,8 @@ declare(strict_types=1); ...@@ -3,6 +3,8 @@ declare(strict_types=1);
return [ return [
['GET', '/tutorials', 'Tutorials\Controllers\TutorialController@index', ['auth'], 'tutorials.view'], ['GET', '/tutorials', 'Tutorials\Controllers\TutorialController@index', ['auth'], 'tutorials.view'],
['GET', '/tutorials/book', 'Tutorials\Controllers\TutorialController@bookPage', ['auth'], 'tutorials.view'],
['GET', '/tutorials/export-pdf', 'Tutorials\Controllers\TutorialController@exportPdf', ['auth'], 'tutorials.view'],
['GET', '/tutorials/sports-activity', 'Tutorials\Controllers\TutorialController@sportsActivity', ['auth'], 'tutorials.view'], ['GET', '/tutorials/sports-activity', 'Tutorials\Controllers\TutorialController@sportsActivity', ['auth'], 'tutorials.view'],
['GET', '/tutorials/sports-activity/{slug}', 'Tutorials\Controllers\TutorialController@show', ['auth'], 'tutorials.view'], ['GET', '/tutorials/sports-activity/{slug}', 'Tutorials\Controllers\TutorialController@show', ['auth'], 'tutorials.view'],
['GET', '/tutorials/membership', 'Tutorials\Controllers\TutorialController@membership', ['auth'], 'tutorials.view'], ['GET', '/tutorials/membership', 'Tutorials\Controllers\TutorialController@membership', ['auth'], 'tutorials.view'],
......
<?php $__template->layout('Layout.main'); ?>
<?php $__template->section('title'); ?>كتاب النظام<?php $__template->endSection(); ?>
<?php $__template->section('content'); ?>
<div style="max-width:700px;margin:0 auto;padding:40px 0;">
<!-- Header -->
<div style="text-align:center;margin-bottom:48px;">
<div style="width:100px;height:100px;background:linear-gradient(135deg,#1A1A2E,#4C1D95);border-radius:24px;display:flex;align-items:center;justify-content:center;margin:0 auto 20px;box-shadow:0 8px 32px rgba(76,29,149,0.3);">
<i data-lucide="book-open" style="width:48px;height:48px;color:#fff;"></i>
</div>
<h1 style="font-size:32px;font-weight:900;color:#1A1A2E;margin:0 0 8px;">Book of the ERP</h1>
<h2 style="font-size:20px;font-weight:600;color:#4C1D95;margin:0 0 12px;">كتاب النظام الشامل</h2>
<p style="font-size:14px;color:#6B7280;margin:0;line-height:1.8;">
تصدير جميع الشروحات والخطوات ولقطات الشاشة في ملف PDF واحد<br>
يمكن طباعته أو مشاركته كدليل تدريبي للموظفين
</p>
</div>
<!-- Stats -->
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-bottom:40px;">
<div class="card" style="padding:20px;text-align:center;">
<div style="font-size:28px;font-weight:900;color:#4C1D95;"><?= (int) $totalSections ?></div>
<div style="font-size:12px;color:#6B7280;margin-top:4px;">قسم</div>
</div>
<div class="card" style="padding:20px;text-align:center;">
<div style="font-size:28px;font-weight:900;color:#059669;"><?= (int) $totalTutorials ?></div>
<div style="font-size:12px;color:#6B7280;margin-top:4px;">شرح</div>
</div>
<div class="card" style="padding:20px;text-align:center;">
<div style="font-size:28px;font-weight:900;color:#3B82F6;">105</div>
<div style="font-size:12px;color:#6B7280;margin-top:4px;">لقطة شاشة</div>
</div>
</div>
<!-- Export Card -->
<div class="card" style="padding:32px;" id="exportCard">
<!-- Before Export -->
<div id="beforeExport">
<div style="display:flex;align-items:center;gap:14px;margin-bottom:20px;">
<div style="width:44px;height:44px;background:#EDE9FE;border-radius:12px;display:flex;align-items:center;justify-content:center;flex-shrink:0;">
<i data-lucide="file-down" style="width:22px;height:22px;color:#7C3AED;"></i>
</div>
<div>
<h3 style="font-size:16px;font-weight:700;color:#1A1A2E;margin:0 0 2px;">تصدير الكتاب كاملاً</h3>
<p style="font-size:12px;color:#6B7280;margin:0;">سيتم تجميع كل الشروحات وتحويلها إلى PDF</p>
</div>
</div>
<div style="background:#F9FAFB;border-radius:10px;padding:14px 16px;margin-bottom:20px;">
<div style="font-size:12px;color:#374151;line-height:2;">
<span style="display:inline-flex;align-items:center;gap:4px;"><i data-lucide="check-circle" style="width:12px;height:12px;color:#059669;"></i> غلاف الكتاب</span><br>
<span style="display:inline-flex;align-items:center;gap:4px;"><i data-lucide="check-circle" style="width:12px;height:12px;color:#059669;"></i> فهرس المحتويات</span><br>
<span style="display:inline-flex;align-items:center;gap:4px;"><i data-lucide="check-circle" style="width:12px;height:12px;color:#059669;"></i> جميع الشروحات مع الخطوات التفصيلية</span><br>
<span style="display:inline-flex;align-items:center;gap:4px;"><i data-lucide="check-circle" style="width:12px;height:12px;color:#059669;"></i> لقطات الشاشة المدمجة</span><br>
<span style="display:inline-flex;align-items:center;gap:4px;"><i data-lucide="check-circle" style="width:12px;height:12px;color:#059669;"></i> تنسيق A4 جاهز للطباعة</span>
</div>
</div>
<button type="button" id="exportBtn" onclick="startExport()" style="width:100%;padding:14px;background:linear-gradient(135deg,#1A1A2E,#4C1D95);color:#fff;border:none;border-radius:12px;font-size:15px;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px;transition:all .2s;box-shadow:0 4px 12px rgba(76,29,149,0.3);">
<i data-lucide="download" style="width:18px;height:18px;"></i>
بدء التصدير
</button>
</div>
<!-- During Export -->
<div id="duringExport" style="display:none;">
<div style="text-align:center;margin-bottom:20px;">
<div style="width:56px;height:56px;margin:0 auto 14px;position:relative;">
<svg viewBox="0 0 56 56" style="width:56px;height:56px;animation:spin 1.5s linear infinite;">
<circle cx="28" cy="28" r="24" fill="none" stroke="#EDE9FE" stroke-width="4"></circle>
<circle cx="28" cy="28" r="24" fill="none" stroke="#7C3AED" stroke-width="4" stroke-dasharray="120 150" stroke-linecap="round"></circle>
</svg>
</div>
<h3 style="font-size:16px;font-weight:700;color:#1A1A2E;margin:0 0 6px;" id="exportTitle">جاري التصدير...</h3>
<p style="font-size:12px;color:#6B7280;margin:0;" id="exportSubtitle">يتم الآن تجميع الشروحات ولقطات الشاشة</p>
</div>
<!-- Progress Bar -->
<div style="background:#F3F4F6;border-radius:8px;height:12px;overflow:hidden;margin-bottom:12px;">
<div id="progressBar" style="height:100%;background:linear-gradient(90deg,#7C3AED,#8B5CF6);border-radius:8px;transition:width 0.4s ease;width:0%;"></div>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;">
<span style="font-size:11px;color:#6B7280;" id="progressLabel">0%</span>
<span style="font-size:11px;color:#6B7280;" id="progressStep">تحضير البيانات...</span>
</div>
</div>
<!-- After Export (Success) -->
<div id="afterExport" style="display:none;">
<div style="text-align:center;">
<div style="width:64px;height:64px;background:#ECFDF5;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 16px;">
<i data-lucide="check-circle-2" style="width:32px;height:32px;color:#059669;"></i>
</div>
<h3 style="font-size:16px;font-weight:700;color:#059669;margin:0 0 6px;">تم التصدير بنجاح!</h3>
<p style="font-size:12px;color:#6B7280;margin:0 0 20px;">تم تحميل الملف — تحقق من مجلد التنزيلات</p>
<button type="button" onclick="resetExport()" style="padding:10px 24px;background:#F9FAFB;border:1px solid #E5E7EB;border-radius:10px;font-size:13px;font-weight:600;color:#374151;cursor:pointer;transition:all .15s;" onmouseover="this.style.background='#EDE9FE';this.style.borderColor='#8B5CF6'" onmouseout="this.style.background='#F9FAFB';this.style.borderColor='#E5E7EB'">
<i data-lucide="refresh-cw" style="width:13px;height:13px;display:inline;vertical-align:-2px;"></i>
تصدير مرة أخرى
</button>
</div>
</div>
<!-- Error State -->
<div id="errorExport" style="display:none;">
<div style="text-align:center;">
<div style="width:64px;height:64px;background:#FEF2F2;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 16px;">
<i data-lucide="alert-circle" style="width:32px;height:32px;color:#DC2626;"></i>
</div>
<h3 style="font-size:16px;font-weight:700;color:#DC2626;margin:0 0 6px;">حدث خطأ</h3>
<p style="font-size:12px;color:#6B7280;margin:0 0 20px;" id="errorMessage">فشل التصدير. حاول مرة أخرى.</p>
<button type="button" onclick="resetExport()" style="padding:10px 24px;background:#F9FAFB;border:1px solid #E5E7EB;border-radius:10px;font-size:13px;font-weight:600;color:#374151;cursor:pointer;">
إعادة المحاولة
</button>
</div>
</div>
</div>
<!-- Info Note -->
<div style="margin-top:24px;padding:14px 16px;background:#DBEAFE;border:1px solid #3B82F630;border-radius:10px;font-size:12px;color:#1E40AF;line-height:1.8;">
<strong>ملاحظة:</strong> حجم الملف قد يكون كبيراً بسبب لقطات الشاشة المدمجة. التصدير قد يستغرق بضع ثوانٍ حسب سرعة السيرفر.
</div>
</div>
<style>
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
</style>
<script>
var exportSteps = [
{ pct: 5, label: 'تحضير البيانات...' },
{ pct: 15, label: 'تجميع شروحات العضوية...' },
{ pct: 30, label: 'تجميع شروحات الأنشطة الرياضية...' },
{ pct: 45, label: 'تجميع شروحات الخزنة...' },
{ pct: 55, label: 'تجميع باقي الأقسام...' },
{ pct: 70, label: 'تضمين لقطات الشاشة...' },
{ pct: 85, label: 'توليد ملف PDF...' },
{ pct: 95, label: 'تجهيز التحميل...' },
];
function startExport() {
document.getElementById('beforeExport').style.display = 'none';
document.getElementById('duringExport').style.display = 'block';
document.getElementById('afterExport').style.display = 'none';
document.getElementById('errorExport').style.display = 'none';
var stepIndex = 0;
var progressBar = document.getElementById('progressBar');
var progressLabel = document.getElementById('progressLabel');
var progressStep = document.getElementById('progressStep');
function animateStep() {
if (stepIndex >= exportSteps.length) return;
var step = exportSteps[stepIndex];
progressBar.style.width = step.pct + '%';
progressLabel.textContent = step.pct + '%';
progressStep.textContent = step.label;
stepIndex++;
if (stepIndex < exportSteps.length) {
setTimeout(animateStep, 600);
}
}
animateStep();
fetch('/tutorials/export-pdf')
.then(function(response) {
if (!response.ok) throw new Error('HTTP ' + response.status);
var contentType = response.headers.get('Content-Type') || '';
return response.blob().then(function(blob) {
return { blob: blob, contentType: contentType };
});
})
.then(function(result) {
progressBar.style.width = '100%';
progressLabel.textContent = '100%';
progressStep.textContent = 'اكتمل!';
var ext = result.contentType.indexOf('pdf') !== -1 ? '.pdf' : '.html';
var filename = 'Book-of-the-ERP' + ext;
var url = window.URL.createObjectURL(result.blob);
var a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(function() {
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}, 100);
setTimeout(function() { finishExport(true); }, 600);
})
.catch(function(err) {
document.getElementById('errorMessage').textContent = 'فشل التصدير: ' + err.message;
finishExport(false);
});
}
function finishExport(success) {
document.getElementById('duringExport').style.display = 'none';
if (success) {
document.getElementById('afterExport').style.display = 'block';
} else {
document.getElementById('errorExport').style.display = 'block';
}
}
function resetExport() {
document.getElementById('beforeExport').style.display = 'block';
document.getElementById('duringExport').style.display = 'none';
document.getElementById('afterExport').style.display = 'none';
document.getElementById('errorExport').style.display = 'none';
document.getElementById('progressBar').style.width = '0%';
}
document.addEventListener('DOMContentLoaded', function() {
if (typeof lucide !== 'undefined') lucide.createIcons();
});
</script>
<?php $__template->endSection(); ?>
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<title>Book of the ERP - كتاب النظام</title>
<style>
@page {
size: A4;
margin: 20mm 15mm 25mm 15mm;
}
@page :first {
margin: 0;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Segoe UI', Tahoma, Arial, sans-serif;
font-size: 13px;
line-height: 1.7;
color: #1A1A2E;
direction: rtl;
}
/* Cover Page */
.cover-page {
page-break-after: always;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #1A1A2E 0%, #2D1B69 50%, #4C1D95 100%);
color: #fff;
text-align: center;
padding: 40px;
}
.cover-logo {
width: 120px;
height: 120px;
background: rgba(255,255,255,0.1);
border-radius: 30px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40px;
border: 2px solid rgba(255,255,255,0.2);
}
.cover-logo svg {
width: 60px;
height: 60px;
fill: none;
stroke: #fff;
stroke-width: 1.5;
}
.cover-title {
font-size: 48px;
font-weight: 900;
margin-bottom: 12px;
letter-spacing: -1px;
}
.cover-subtitle {
font-size: 22px;
font-weight: 300;
opacity: 0.85;
margin-bottom: 8px;
}
.cover-desc {
font-size: 16px;
opacity: 0.6;
margin-bottom: 60px;
}
.cover-meta {
font-size: 12px;
opacity: 0.5;
border-top: 1px solid rgba(255,255,255,0.15);
padding-top: 20px;
}
/* Table of Contents */
.toc-page {
page-break-after: always;
padding: 40px 0;
}
.toc-title {
font-size: 28px;
font-weight: 800;
color: #1A1A2E;
margin-bottom: 30px;
padding-bottom: 12px;
border-bottom: 3px solid #8B5CF6;
}
.toc-section {
margin-bottom: 20px;
}
.toc-section-title {
font-size: 16px;
font-weight: 700;
color: #4C1D95;
margin-bottom: 6px;
display: flex;
align-items: center;
gap: 8px;
}
.toc-section-title::before {
content: '';
width: 8px;
height: 8px;
background: #8B5CF6;
border-radius: 50%;
flex-shrink: 0;
}
.toc-items {
padding-right: 24px;
list-style: none;
}
.toc-items li {
font-size: 12px;
color: #374151;
padding: 2px 0;
display: flex;
align-items: center;
gap: 6px;
}
.toc-items li::before {
content: '\2022';
color: #9CA3AF;
}
.toc-count {
display: inline-block;
background: #F3F4F6;
color: #6B7280;
font-size: 10px;
padding: 1px 6px;
border-radius: 8px;
margin-right: 8px;
}
/* Section Headers */
.section-header {
page-break-before: always;
page-break-after: avoid;
padding: 60px 0 30px;
text-align: center;
border-bottom: 3px solid #8B5CF6;
margin-bottom: 30px;
}
.section-header h2 {
font-size: 30px;
font-weight: 900;
color: #1A1A2E;
margin-bottom: 8px;
}
.section-header p {
font-size: 14px;
color: #6B7280;
}
.section-header .section-count {
display: inline-block;
background: #EDE9FE;
color: #7C3AED;
font-size: 12px;
font-weight: 600;
padding: 4px 12px;
border-radius: 12px;
margin-top: 12px;
}
/* Tutorial Blocks */
.tutorial-block {
page-break-inside: avoid;
margin-bottom: 30px;
border: 1px solid #E5E7EB;
border-radius: 12px;
overflow: hidden;
}
.tutorial-block-header {
background: #F9FAFB;
padding: 16px 20px;
border-bottom: 1px solid #E5E7EB;
display: flex;
align-items: center;
gap: 12px;
}
.tutorial-block-header h3 {
font-size: 16px;
font-weight: 700;
color: #1A1A2E;
margin: 0;
}
.tutorial-block-header .tutorial-subtitle {
font-size: 12px;
color: #6B7280;
margin: 2px 0 0;
}
.tutorial-block-header .tutorial-num {
width: 28px;
height: 28px;
background: #8B5CF6;
color: #fff;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 700;
flex-shrink: 0;
}
.tutorial-block-body {
padding: 20px;
}
/* Screenshot in tutorial */
.tutorial-screenshot {
width: 100%;
max-height: 400px;
object-fit: contain;
border: 1px solid #E5E7EB;
border-radius: 8px;
margin-bottom: 16px;
}
/* Steps (for detailed tutorials) */
.tut-page { max-width: 100%; }
.tut-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 20px;
padding: 16px;
background: #F9FAFB;
border-radius: 10px;
border: 1px solid #E5E7EB;
}
.tut-header-icon {
width: 44px;
height: 44px;
background: #8B5CF6;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.tut-header h1 {
font-size: 18px;
font-weight: 800;
color: #1A1A2E;
margin: 0 0 2px;
}
.tut-header p {
font-size: 12px;
color: #6B7280;
margin: 0;
}
.tut-step {
position: relative;
padding: 14px 50px 14px 14px;
margin-bottom: 10px;
background: #fff;
border: 1px solid #E5E7EB;
border-radius: 8px;
page-break-inside: avoid;
}
.tut-step-num {
position: absolute;
right: 12px;
top: 14px;
width: 28px;
height: 28px;
background: #EDE9FE;
color: #7C3AED;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 800;
}
.tut-step-title {
font-size: 13px;
font-weight: 700;
color: #1A1A2E;
margin: 0 0 4px;
}
.tut-step-body {
font-size: 12px;
color: #374151;
line-height: 1.7;
}
.tut-step-body ul { margin: 6px 0; padding-right: 16px; }
.tut-step-body li { margin-bottom: 3px; }
.tut-step-body .field {
display: inline-block;
background: #F3F4F6;
color: #1A1A2E;
padding: 0 6px;
border-radius: 3px;
font-size: 11px;
font-weight: 600;
}
.tut-step-body .warn {
display: block;
background: #FEF3C7;
border: 1px solid #F59E0B40;
border-radius: 6px;
padding: 8px 10px;
margin: 6px 0;
font-size: 11px;
color: #92400E;
}
.tut-step-body .info {
display: block;
background: #DBEAFE;
border: 1px solid #3B82F640;
border-radius: 6px;
padding: 8px 10px;
margin: 6px 0;
font-size: 11px;
color: #1E40AF;
}
.tut-step-body .success {
display: block;
background: #ECFDF5;
border: 1px solid #05966940;
border-radius: 6px;
padding: 8px 10px;
margin: 6px 0;
font-size: 11px;
color: #065F46;
}
.tut-diagram {
background: #F8FAFC;
border: 1px solid #E2E8F0;
border-radius: 8px;
padding: 12px;
margin: 8px 0;
font-family: monospace;
font-size: 10px;
direction: ltr;
text-align: left;
line-height: 1.5;
overflow: hidden;
}
/* Category Separator */
.category-header {
margin: 24px 0 12px;
padding: 8px 14px;
background: #F3F4F6;
border-radius: 8px;
font-size: 14px;
font-weight: 700;
color: #374151;
page-break-after: avoid;
}
/* Footer */
.page-footer {
position: fixed;
bottom: 10mm;
left: 15mm;
right: 15mm;
font-size: 9px;
color: #9CA3AF;
text-align: center;
border-top: 1px solid #F3F4F6;
padding-top: 6px;
}
/* Screenshot image within content */
.tutorial-block-body img {
max-width: 100%;
height: auto;
border-radius: 6px;
border: 1px solid #E5E7EB;
margin: 8px 0;
}
/* Print optimizations */
@media print {
body { font-size: 11px; }
.cover-page { height: 297mm; }
.no-print { display: none; }
}
</style>
</head>
<body>
<!-- Cover Page -->
<div class="cover-page">
<div class="cover-logo">
<svg viewBox="0 0 24 24"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
<div class="cover-title">Book of the ERP</div>
<div class="cover-subtitle">كتاب النظام الشامل</div>
<div class="cover-desc">الدليل التفصيلي لجميع عمليات نظام إدارة النادي</div>
<div class="cover-meta">
تم التوليد في: <?= $data['generatedAt'] ?> &nbsp;|&nbsp;
عدد الأقسام: <?= count($data['sections']) ?> &nbsp;|&nbsp;
إجمالي الشروحات: <?php
$totalTutorials = 0;
foreach ($data['sections'] as $s) {
$totalTutorials += count($s['tutorials']);
}
echo $totalTutorials;
?>
</div>
</div>
<!-- Table of Contents -->
<div class="toc-page">
<h1 class="toc-title">فهرس المحتويات</h1>
<?php $sectionNum = 0; foreach ($data['sections'] as $sectionKey => $section): $sectionNum++; ?>
<div class="toc-section">
<div class="toc-section-title">
<?= htmlspecialchars($section['title'], ENT_QUOTES, 'UTF-8') ?>
<span class="toc-count"><?= count($section['tutorials']) ?> شرح</span>
</div>
<ul class="toc-items">
<?php foreach ($section['tutorials'] as $slug => $tutorial): ?>
<li><?= htmlspecialchars($tutorial['title'], ENT_QUOTES, 'UTF-8') ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endforeach; ?>
</div>
<!-- Sections & Tutorials -->
<?php $sectionNum = 0; foreach ($data['sections'] as $sectionKey => $section): $sectionNum++; ?>
<div class="section-header">
<h2>القسم <?= $sectionNum ?>: <?= htmlspecialchars($section['title'], ENT_QUOTES, 'UTF-8') ?></h2>
<?php if (!empty($section['subtitle'])): ?>
<p><?= htmlspecialchars($section['subtitle'], ENT_QUOTES, 'UTF-8') ?></p>
<?php endif; ?>
<div class="section-count"><?= count($section['tutorials']) ?> شرح في هذا القسم</div>
</div>
<?php if (!empty($section['screenshot']) && file_exists($section['screenshot'])): ?>
<div style="text-align:center;margin-bottom:24px;">
<img src="file://<?= $section['screenshot'] ?>" class="tutorial-screenshot" alt="<?= htmlspecialchars($section['title'], ENT_QUOTES, 'UTF-8') ?>">
</div>
<?php endif; ?>
<?php
$tutorialNum = 0;
$currentCategory = '';
foreach ($section['tutorials'] as $slug => $tutorial):
$tutorialNum++;
$cat = $tutorial['category'] ?? '';
if ($cat !== $currentCategory && !empty($section['categories'][$cat])):
$currentCategory = $cat;
?>
<div class="category-header"><?= htmlspecialchars($section['categories'][$cat]['label'], ENT_QUOTES, 'UTF-8') ?></div>
<?php endif; ?>
<div class="tutorial-block">
<div class="tutorial-block-header">
<div class="tutorial-num"><?= $tutorialNum ?></div>
<div>
<h3><?= htmlspecialchars($tutorial['title'], ENT_QUOTES, 'UTF-8') ?></h3>
<?php if (!empty($tutorial['subtitle'])): ?>
<div class="tutorial-subtitle"><?= htmlspecialchars($tutorial['subtitle'], ENT_QUOTES, 'UTF-8') ?></div>
<?php endif; ?>
</div>
</div>
<div class="tutorial-block-body">
<?php if (!empty($tutorial['htmlContent'])): ?>
<?= $tutorial['htmlContent'] ?>
<?php else: ?>
<p style="color:#6B7280;font-style:italic;font-size:12px;">
<?= htmlspecialchars($tutorial['subtitle'] ?? $tutorial['title'], ENT_QUOTES, 'UTF-8') ?>
</p>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php endforeach; ?>
</body>
</html>
...@@ -37,6 +37,8 @@ MenuRegistry::register('tutorials', [ ...@@ -37,6 +37,8 @@ MenuRegistry::register('tutorials', [
['label_ar' => 'المستخدمون والصلاحيات', 'label_en' => 'Users & Permissions', 'route' => '/tutorials/roles', 'permission' => 'tutorials.view', 'order' => 41], ['label_ar' => 'المستخدمون والصلاحيات', 'label_en' => 'Users & Permissions', 'route' => '/tutorials/roles', 'permission' => 'tutorials.view', 'order' => 41],
['label_ar' => 'مصفوفة الصلاحيات', 'label_en' => 'Access Matrix', 'route' => '/tutorials/access-matrix', 'permission' => 'tutorials.view', 'order' => 42], ['label_ar' => 'مصفوفة الصلاحيات', 'label_en' => 'Access Matrix', 'route' => '/tutorials/access-matrix', 'permission' => 'tutorials.view', 'order' => 42],
['label_ar' => 'القواعد والتسعير', 'label_en' => 'Rules & Pricing', 'route' => '/tutorials/rules', 'permission' => 'tutorials.view', 'order' => 43], ['label_ar' => 'القواعد والتسعير', 'label_en' => 'Rules & Pricing', 'route' => '/tutorials/rules', 'permission' => 'tutorials.view', 'order' => 43],
// ── كتاب النظام (Export) ───────────────────────────
['label_ar' => 'كتاب النظام (PDF)', 'label_en' => 'Book of the ERP', 'route' => '/tutorials/book', 'permission' => 'tutorials.view', 'order' => 99, 'icon' => 'book-open'],
], ],
]); ]);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment