Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
Clubphp
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
Clubphp
Commits
b98c1c14
Commit
b98c1c14
authored
May 24, 2026
by
Mahmoud Aglan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
GO THERE
parent
7f5b59a3
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
682 additions
and
210 deletions
+682
-210
TutorialController.php
app/Modules/Tutorials/Controllers/TutorialController.php
+2
-2
export_pdf.php
app/Modules/Tutorials/Views/export_pdf.php
+677
-203
PdfExportService.php
app/Shared/Services/PdfExportService.php
+3
-5
No files found.
app/Modules/Tutorials/Controllers/TutorialController.php
View file @
b98c1c14
...
...
@@ -606,7 +606,7 @@ class TutorialController extends Controller
@
unlink
(
$tmpOutput
);
$response
=
new
Response
();
return
$response
->
html
(
$pdfContent
,
200
,
[
return
$response
->
html
(
$pdfContent
,
200
)
->
withHeaders
(
[
'Content-Type'
=>
'application/pdf'
,
'Content-Disposition'
=>
'attachment; filename="Book-of-the-ERP.pdf"'
,
'Content-Length'
=>
(
string
)
strlen
(
$pdfContent
),
...
...
@@ -616,7 +616,7 @@ class TutorialController extends Controller
}
$response
=
new
Response
();
return
$response
->
html
(
$html
,
200
,
[
return
$response
->
html
(
$html
,
200
)
->
withHeaders
(
[
'Content-Type'
=>
'text/html; charset=utf-8'
,
'Content-Disposition'
=>
'attachment; filename="Book-of-the-ERP.html"'
,
]);
...
...
app/Modules/Tutorials/Views/export_pdf.php
View file @
b98c1c14
...
...
@@ -4,236 +4,518 @@
<meta
charset=
"UTF-8"
>
<title>
Book of the ERP - كتاب النظام
</title>
<style>
/* ═══════════════════════════════════════════════════════════════════
DESIGN FEATURES:
1. Full-page module divider pages with gradient backgrounds
2. page-break-inside:avoid on all tutorial blocks (no split)
3. Orphan/widow control (min 3 lines before/after break)
4. Professional TOC with numbered sections & dot leaders
5. Color-coded section accents (unique color per module)
6. Running footer with page numbers
7. Cover page with geometric SVG pattern
8. Category sub-dividers with thick colored left border
9. Tutorial numbering with gradient badges
10. Screenshot frames with drop shadow effect
11. Step cards with colored side accent bar
12. Styled callout boxes (info/warning/success/tip)
13. Professional typography hierarchy (8pt grid)
14. Decorative corner elements on divider pages
15. Tutorial metadata tags (colored chips)
16. Subtle diagonal stripe background on divider pages
17. Proper print margins with gutters
18. Back cover page with branding
19. Section summary stats on divider pages
20. Consistent vertical rhythm (8px base unit)
═══════════════════════════════════════════════════════════════════ */
@page
{
size
:
A4
;
margin
:
2
0mm
15mm
25mm
15
mm
;
margin
:
2
2mm
18mm
28mm
18
mm
;
}
@page
:
first
{
margin
:
0
;
}
*
{
box-sizing
:
border-box
;
margin
:
0
;
padding
:
0
;
}
/* ── Feature 13: Typography hierarchy ── */
body
{
font-family
:
'Noto Sans Arabic'
,
'Noto Sans'
,
'Segoe UI'
,
Tahoma
,
Arial
,
sans-serif
;
font-size
:
1
3
px
;
line-height
:
1.
7
;
color
:
#1
A1A2E
;
font-size
:
1
2
px
;
line-height
:
1.
8
;
color
:
#1
F2937
;
direction
:
rtl
;
-webkit-print-color-adjust
:
exact
;
print-color-adjust
:
exact
;
}
/* ── Feature 3: Orphan/Widow control ── */
p
,
li
,
.tut-step-body
{
orphans
:
3
;
widows
:
3
;
}
/* Cover Page */
/* ══════════════════════════════════════
Feature 7: COVER PAGE
══════════════════════════════════════ */
.cover-page
{
page-break-after
:
always
;
height
:
100vh
;
width
:
210mm
;
height
:
297mm
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
background
:
linear-gradient
(
1
35deg
,
#1A1A2E
0%
,
#2D1B69
50%
,
#4C1D95
100%
);
background
:
linear-gradient
(
1
60deg
,
#0F0A2A
0%
,
#1A1145
25%
,
#2D1B69
50%
,
#4C1D95
75%
,
#6D28D9
100%
);
color
:
#fff
;
text-align
:
center
;
padding
:
40px
;
padding
:
60px
50px
;
position
:
relative
;
overflow
:
hidden
;
}
.cover-page
::before
{
content
:
''
;
position
:
absolute
;
top
:
-50%
;
right
:
-30%
;
width
:
120%
;
height
:
120%
;
background
:
radial-gradient
(
ellipse
at
center
,
rgba
(
139
,
92
,
246
,
0.15
)
0%
,
transparent
60%
);
}
.cover-page
::after
{
content
:
''
;
position
:
absolute
;
bottom
:
0
;
left
:
0
;
right
:
0
;
height
:
4px
;
background
:
linear-gradient
(
90deg
,
#8B5CF6
,
#EC4899
,
#F59E0B
,
#10B981
,
#3B82F6
);
}
.cover-geometric
{
position
:
absolute
;
top
:
40px
;
left
:
40px
;
right
:
40px
;
bottom
:
40px
;
border
:
1px
solid
rgba
(
255
,
255
,
255
,
0.06
);
border-radius
:
24px
;
}
.cover-geometric
::before
{
content
:
''
;
position
:
absolute
;
top
:
20px
;
left
:
20px
;
right
:
20px
;
bottom
:
20px
;
border
:
1px
solid
rgba
(
255
,
255
,
255
,
0.04
);
border-radius
:
20px
;
}
.cover-logo
{
width
:
1
2
0px
;
height
:
1
2
0px
;
background
:
rgba
(
255
,
255
,
255
,
0.
1
);
border-radius
:
3
0
px
;
width
:
1
4
0px
;
height
:
1
4
0px
;
background
:
rgba
(
255
,
255
,
255
,
0.
08
);
border-radius
:
3
6
px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
margin-bottom
:
40px
;
border
:
2px
solid
rgba
(
255
,
255
,
255
,
0.2
);
margin-bottom
:
48px
;
border
:
2px
solid
rgba
(
255
,
255
,
255
,
0.15
);
position
:
relative
;
z-index
:
1
;
box-shadow
:
0
20px
60px
rgba
(
0
,
0
,
0
,
0.3
);
}
.cover-logo
svg
{
width
:
6
0px
;
height
:
6
0px
;
width
:
7
0px
;
height
:
7
0px
;
fill
:
none
;
stroke
:
#fff
;
stroke-width
:
1.5
;
}
.cover-title
{
font-size
:
48
px
;
font-size
:
56
px
;
font-weight
:
900
;
margin-bottom
:
12px
;
letter-spacing
:
-1px
;
margin-bottom
:
8px
;
letter-spacing
:
-2px
;
position
:
relative
;
z-index
:
1
;
text-shadow
:
0
4px
20px
rgba
(
0
,
0
,
0
,
0.3
);
}
.cover-subtitle
{
font-size
:
22px
;
font-weight
:
300
;
opacity
:
0.85
;
margin-bottom
:
8px
;
font-size
:
26px
;
font-weight
:
600
;
opacity
:
0.9
;
margin-bottom
:
12px
;
position
:
relative
;
z-index
:
1
;
}
.cover-desc
{
font-size
:
1
6
px
;
font-size
:
1
5
px
;
opacity
:
0.6
;
margin-bottom
:
80px
;
max-width
:
400px
;
line-height
:
2
;
position
:
relative
;
z-index
:
1
;
}
.cover-stats
{
display
:
flex
;
gap
:
40px
;
margin-bottom
:
60px
;
position
:
relative
;
z-index
:
1
;
}
.cover-meta
{
font-size
:
12px
;
.cover-stat
{
text-align
:
center
;
}
.cover-stat-num
{
font-size
:
36px
;
font-weight
:
900
;
display
:
block
;
background
:
linear-gradient
(
135deg
,
#C4B5FD
,
#fff
);
-webkit-background-clip
:
text
;
-webkit-text-fill-color
:
transparent
;
}
.cover-stat-label
{
font-size
:
11px
;
opacity
:
0.5
;
border-top
:
1px
solid
rgba
(
255
,
255
,
255
,
0.15
);
text-transform
:
uppercase
;
letter-spacing
:
1px
;
}
.cover-meta
{
font-size
:
11px
;
opacity
:
0.4
;
position
:
relative
;
z-index
:
1
;
border-top
:
1px
solid
rgba
(
255
,
255
,
255
,
0.1
);
padding-top
:
20px
;
width
:
100%
;
max-width
:
400px
;
}
/* Table of Contents */
/* ══════════════════════════════════════
Feature 4: TABLE OF CONTENTS
══════════════════════════════════════ */
.toc-page
{
page-break-after
:
always
;
padding
:
40px
0
;
padding
:
0
;
}
.toc-title
{
font-size
:
28px
;
font-weight
:
800
;
.toc-header
{
text-align
:
center
;
margin-bottom
:
40px
;
padding-bottom
:
24px
;
border-bottom
:
2px
solid
#E5E7EB
;
}
.toc-header
h1
{
font-size
:
32px
;
font-weight
:
900
;
color
:
#1A1A2E
;
margin-bottom
:
30px
;
padding-bottom
:
12px
;
border-bottom
:
3px
solid
#8B5CF6
;
margin-bottom
:
8px
;
}
.toc-header
p
{
font-size
:
13px
;
color
:
#6B7280
;
}
.toc-grid
{
display
:
grid
;
grid-template-columns
:
1
fr
1
fr
;
gap
:
20px
32px
;
}
.toc-section
{
margin-bottom
:
20px
;
margin-bottom
:
8px
;
padding
:
12px
16px
;
background
:
#F9FAFB
;
border-radius
:
10px
;
border-right
:
4px
solid
#8B5CF6
;
}
.toc-section-title
{
font-size
:
1
6
px
;
font-size
:
1
3
px
;
font-weight
:
700
;
color
:
#
4C1D95
;
margin-bottom
:
6
px
;
color
:
#
1A1A2E
;
margin-bottom
:
4
px
;
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
justify-content
:
space-between
;
}
.toc-section-title
::before
{
content
:
''
;
width
:
8px
;
height
:
8px
;
.toc-section-num
{
width
:
22px
;
height
:
22px
;
background
:
#8B5CF6
;
border-radius
:
50%
;
flex-shrink
:
0
;
color
:
#fff
;
border-radius
:
6px
;
display
:
inline-flex
;
align-items
:
center
;
justify-content
:
center
;
font-size
:
10px
;
font-weight
:
800
;
margin-left
:
8px
;
}
.toc-badge
{
display
:
inline-block
;
background
:
#EDE9FE
;
color
:
#7C3AED
;
font-size
:
9px
;
font-weight
:
700
;
padding
:
2px
8px
;
border-radius
:
10px
;
}
.toc-items
{
padding-right
:
24px
;
list-style
:
none
;
padding
:
0
;
margin
:
6px
0
0
;
}
.toc-items
li
{
font-size
:
12px
;
color
:
#374151
;
padding
:
2px
0
;
display
:
flex
;
align-items
:
center
;
gap
:
6px
;
font-size
:
10px
;
color
:
#6B7280
;
padding
:
1px
0
;
padding-right
:
12px
;
position
:
relative
;
}
.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
;
content
:
'—'
;
position
:
absolute
;
right
:
0
;
color
:
#D1D5DB
;
font-size
:
9px
;
}
/* Section Headers */
.section-header
{
/* ══════════════════════════════════════
Feature 1 & 16: MODULE DIVIDER PAGES
══════════════════════════════════════ */
.module-divider
{
page-break-before
:
always
;
page-break-after
:
avoid
;
padding
:
60px
0
30px
;
page-break-after
:
always
;
width
:
100%
;
min-height
:
250mm
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
text-align
:
center
;
border-bottom
:
3px
solid
#8B5CF6
;
margin-bottom
:
30px
;
position
:
relative
;
padding
:
80px
40px
;
background
:
#FAFBFF
;
}
.module-divider
::before
{
content
:
''
;
position
:
absolute
;
top
:
0
;
left
:
0
;
right
:
0
;
height
:
6px
;
background
:
var
(
--section-color
,
#8B5CF6
);
}
.module-divider
::after
{
content
:
''
;
position
:
absolute
;
bottom
:
0
;
left
:
0
;
right
:
0
;
height
:
2px
;
background
:
var
(
--section-color
,
#8B5CF6
);
opacity
:
0.3
;
}
/* Feature 14: Decorative corners */
.module-divider-corners
{
position
:
absolute
;
top
:
30px
;
left
:
30px
;
right
:
30px
;
bottom
:
30px
;
pointer-events
:
none
;
}
.module-divider-corners
::before
,
.module-divider-corners
::after
{
content
:
''
;
position
:
absolute
;
width
:
40px
;
height
:
40px
;
border-color
:
var
(
--section-color
,
#8B5CF6
);
opacity
:
0.2
;
}
.module-divider-corners
::before
{
top
:
0
;
right
:
0
;
border-top
:
3px
solid
;
border-right
:
3px
solid
;
}
.module-divider-corners
::after
{
bottom
:
0
;
left
:
0
;
border-bottom
:
3px
solid
;
border-left
:
3px
solid
;
}
.section-header
h2
{
font-size
:
30px
;
.module-divider-num
{
font-size
:
80px
;
font-weight
:
900
;
color
:
var
(
--section-color
,
#8B5CF6
);
opacity
:
0.1
;
position
:
absolute
;
top
:
60px
;
left
:
50%
;
transform
:
translateX
(
-50%
);
line-height
:
1
;
}
.module-divider-icon
{
width
:
80px
;
height
:
80px
;
background
:
var
(
--section-color
,
#8B5CF6
);
border-radius
:
20px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
margin-bottom
:
32px
;
box-shadow
:
0
8px
32px
rgba
(
0
,
0
,
0
,
0.08
);
}
.module-divider-icon
svg
{
width
:
40px
;
height
:
40px
;
fill
:
none
;
stroke
:
#fff
;
stroke-width
:
2
;
stroke-linecap
:
round
;
stroke-linejoin
:
round
;
}
.module-divider
h2
{
font-size
:
36px
;
font-weight
:
900
;
color
:
#1A1A2E
;
margin-bottom
:
8
px
;
margin-bottom
:
12
px
;
}
.
section-header
p
{
font-size
:
1
4
px
;
.
module-divider-subtitle
{
font-size
:
1
5
px
;
color
:
#6B7280
;
margin-bottom
:
32px
;
max-width
:
350px
;
line-height
:
2
;
}
.section-header
.section-count
{
display
:
inline-block
;
background
:
#EDE9FE
;
color
:
#7C3AED
;
font-size
:
12
px
;
font-weight
:
600
;
padding
:
4px
1
2px
;
/* Feature 19: Section summary stats */
.module-divider-stats
{
display
:
flex
;
gap
:
24
px
;
background
:
#fff
;
padding
:
16px
3
2px
;
border-radius
:
12px
;
margin-top
:
12px
;
border
:
1px
solid
#E5E7EB
;
box-shadow
:
0
2px
8px
rgba
(
0
,
0
,
0
,
0.04
);
}
.module-divider-stat
{
text-align
:
center
;
}
.module-divider-stat-num
{
font-size
:
24px
;
font-weight
:
900
;
color
:
var
(
--section-color
,
#8B5CF6
);
display
:
block
;
}
.module-divider-stat-label
{
font-size
:
10px
;
color
:
#9CA3AF
;
font-weight
:
600
;
}
/* Tutorial Blocks */
/* ══════════════════════════════════════
Feature 2: TUTORIAL BLOCKS (no split)
══════════════════════════════════════ */
.tutorial-block
{
page-break-inside
:
avoid
;
margin-bottom
:
30px
;
page-break-before
:
auto
;
margin-bottom
:
24px
;
border
:
1px
solid
#E5E7EB
;
border-radius
:
12px
;
overflow
:
hidden
;
box-shadow
:
0
1px
4px
rgba
(
0
,
0
,
0
,
0.04
);
}
/* Feature 9: Gradient badge numbering */
.tutorial-block-header
{
background
:
#F9FAFB
;
background
:
linear-gradient
(
135deg
,
#FAFBFF
,
#F3F4F6
)
;
padding
:
16px
20px
;
border-bottom
:
1px
solid
#E5E7EB
;
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
gap
:
14px
;
}
.tutorial-num
{
width
:
32px
;
height
:
32px
;
background
:
linear-gradient
(
135deg
,
#7C3AED
,
#8B5CF6
);
color
:
#fff
;
border-radius
:
10px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
font-size
:
13px
;
font-weight
:
800
;
flex-shrink
:
0
;
box-shadow
:
0
2px
8px
rgba
(
124
,
58
,
237
,
0.3
);
}
.tutorial-block-header
h3
{
font-size
:
1
6
px
;
font-size
:
1
5
px
;
font-weight
:
700
;
color
:
#1A1A2E
;
margin
:
0
;
margin
:
0
0
2px
;
}
.tutorial-block-header
.tutorial-subtitle
{
font-size
:
1
2
px
;
font-size
:
1
1
px
;
color
:
#6B7280
;
margin
:
2px
0
0
;
margin
:
0
;
}
.tutorial-block-header
.tutorial-num
{
width
:
28px
;
height
:
28px
;
background
:
#8B5CF6
;
color
:
#fff
;
border-radius
:
8px
;
/* Feature 15: Tutorial metadata tags */
.tutorial-tags
{
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
font-size
:
12px
;
font-weight
:
700
;
flex-shrink
:
0
;
gap
:
6px
;
margin-top
:
6px
;
flex-wrap
:
wrap
;
}
.tutorial-tag
{
display
:
inline-block
;
padding
:
2px
8px
;
border-radius
:
8px
;
font-size
:
9px
;
font-weight
:
600
;
}
.tutorial-tag-setup
{
background
:
#DBEAFE
;
color
:
#1D4ED8
;
}
.tutorial-tag-operation
{
background
:
#D1FAE5
;
color
:
#065F46
;
}
.tutorial-tag-report
{
background
:
#FEF3C7
;
color
:
#92400E
;
}
.tutorial-tag-management
{
background
:
#EDE9FE
;
color
:
#6D28D9
;
}
.tutorial-block-body
{
padding
:
20px
;
padding
:
20px
24px
;
}
/*
Screenshot in tutorial
*/
/*
── Feature 10: Screenshot frames ──
*/
.tutorial-screenshot
{
width
:
100%
;
max-height
:
40
0px
;
max-height
:
35
0px
;
object-fit
:
contain
;
border
:
1px
solid
#E5E7EB
;
border-radius
:
8px
;
margin-bottom
:
16px
;
margin
:
12px
0
;
box-shadow
:
0
4px
16px
rgba
(
0
,
0
,
0
,
0.08
);
}
/* Steps (for detailed tutorials) */
/* ══════════════════════════════════════
Feature 11: STEP CARDS
══════════════════════════════════════ */
.tut-page
{
max-width
:
100%
;
}
.tut-header
{
display
:
flex
;
align-items
:
center
;
gap
:
14px
;
margin-bottom
:
20px
;
padding
:
16px
;
background
:
#F9FAFB
;
padding
:
1
4px
1
6px
;
background
:
linear-gradient
(
135deg
,
#F9FAFB
,
#F3F4F6
)
;
border-radius
:
10px
;
border
:
1px
solid
#E5E7EB
;
}
.tut-header-icon
{
width
:
4
4
px
;
height
:
4
4
px
;
background
:
#8B5CF6
;
width
:
4
0
px
;
height
:
4
0
px
;
background
:
linear-gradient
(
135deg
,
#7C3AED
,
#8B5CF6
)
;
border-radius
:
10px
;
display
:
flex
;
align-items
:
center
;
...
...
@@ -241,202 +523,373 @@ body {
flex-shrink
:
0
;
}
.tut-header
h1
{
font-size
:
1
8
px
;
font-size
:
1
6
px
;
font-weight
:
800
;
color
:
#1A1A2E
;
margin
:
0
0
2px
;
}
.tut-header
p
{
font-size
:
1
2
px
;
font-size
:
1
1
px
;
color
:
#6B7280
;
margin
:
0
;
}
.tut-step
{
position
:
relative
;
padding
:
14px
50px
14px
14
px
;
padding
:
14px
16px
14px
48
px
;
margin-bottom
:
10px
;
background
:
#fff
;
border
:
1px
solid
#E5E7EB
;
border-radius
:
8px
;
border-radius
:
10px
;
border-right
:
4px
solid
#8B5CF6
;
page-break-inside
:
avoid
;
}
.tut-step-num
{
position
:
absolute
;
right
:
12px
;
right
:
auto
;
left
:
12px
;
top
:
14px
;
width
:
2
8
px
;
height
:
2
8
px
;
background
:
#EDE9FE
;
width
:
2
6
px
;
height
:
2
6
px
;
background
:
linear-gradient
(
135deg
,
#EDE9FE
,
#DDD6FE
)
;
color
:
#7C3AED
;
border-radius
:
6
px
;
border-radius
:
8
px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
font-size
:
1
2
px
;
font-size
:
1
1
px
;
font-weight
:
800
;
}
.tut-step-title
{
font-size
:
1
3
px
;
font-size
:
1
2
px
;
font-weight
:
700
;
color
:
#1A1A2E
;
margin
:
0
0
4px
;
}
.tut-step-body
{
font-size
:
1
2
px
;
font-size
:
1
1
px
;
color
:
#374151
;
line-height
:
1.
7
;
line-height
:
1.
8
;
}
.tut-step-body
ul
{
margin
:
6px
0
;
padding-right
:
16px
;
}
.tut-step-body
li
{
margin-bottom
:
3
px
;
}
.tut-step-body
li
{
margin-bottom
:
4
px
;
}
.tut-step-body
.field
{
display
:
inline-block
;
background
:
#F3F4F6
;
color
:
#1A1A2E
;
padding
:
0
6
px
;
border-radius
:
3
px
;
font-size
:
1
1
px
;
padding
:
1px
8
px
;
border-radius
:
4
px
;
font-size
:
1
0
px
;
font-weight
:
600
;
border
:
1px
solid
#E5E7EB
;
}
/* ── Feature 12: Callout boxes ── */
.tut-step-body
.warn
{
display
:
block
;
background
:
#FEF3C7
;
border
:
1px
solid
#F59E0B40
;
border-radius
:
6px
;
padding
:
8px
10px
;
margin
:
6px
0
;
font-size
:
11px
;
background
:
#FFFBEB
;
border
:
1px
solid
#FDE68A
;
border-right
:
4px
solid
#F59E0B
;
border-radius
:
8px
;
padding
:
10px
14px
;
margin
:
8px
0
;
font-size
:
10px
;
color
:
#92400E
;
line-height
:
1.8
;
}
.tut-step-body
.info
{
display
:
block
;
background
:
#DBEAFE
;
border
:
1px
solid
#3B82F640
;
border-radius
:
6px
;
padding
:
8px
10px
;
margin
:
6px
0
;
font-size
:
11px
;
background
:
#EFF6FF
;
border
:
1px
solid
#BFDBFE
;
border-right
:
4px
solid
#3B82F6
;
border-radius
:
8px
;
padding
:
10px
14px
;
margin
:
8px
0
;
font-size
:
10px
;
color
:
#1E40AF
;
line-height
:
1.8
;
}
.tut-step-body
.success
{
display
:
block
;
background
:
#ECFDF5
;
border
:
1px
solid
#05966940
;
border-radius
:
6px
;
padding
:
8px
10px
;
margin
:
6px
0
;
font-size
:
11px
;
border
:
1px
solid
#A7F3D0
;
border-right
:
4px
solid
#10B981
;
border-radius
:
8px
;
padding
:
10px
14px
;
margin
:
8px
0
;
font-size
:
10px
;
color
:
#065F46
;
line-height
:
1.8
;
}
.tut-step-body
.tip
{
display
:
block
;
background
:
#F5F3FF
;
border
:
1px
solid
#DDD6FE
;
border-right
:
4px
solid
#8B5CF6
;
border-radius
:
8px
;
padding
:
10px
14px
;
margin
:
8px
0
;
font-size
:
10px
;
color
:
#5B21B6
;
line-height
:
1.8
;
}
.tut-diagram
{
background
:
#F8FAFC
;
border
:
1px
solid
#E2E8F0
;
border-radius
:
8px
;
padding
:
1
2
px
;
margin
:
8
px
0
;
font-family
:
monospace
;
font-size
:
10
px
;
padding
:
1
4
px
;
margin
:
10
px
0
;
font-family
:
'Courier New'
,
monospace
;
font-size
:
9
px
;
direction
:
ltr
;
text-align
:
left
;
line-height
:
1.
5
;
line-height
:
1.
6
;
overflow
:
hidden
;
}
/*
Category Separator
*/
/*
── Feature 8: Category sub-dividers ──
*/
.category-header
{
margin
:
24px
0
12px
;
padding
:
8px
14px
;
background
:
#F3F4F6
;
border-radius
:
8px
;
margin
:
28px
0
16px
;
padding
:
10px
18px
;
background
:
linear-gradient
(
135deg
,
#F9FAFB
,
#F3F4F6
);
border-radius
:
10px
;
border-right
:
5px
solid
var
(
--section-color
,
#8B5CF6
);
font-size
:
14px
;
font-weight
:
7
00
;
color
:
#
374151
;
font-weight
:
8
00
;
color
:
#
1A1A2E
;
page-break-after
:
avoid
;
display
:
flex
;
align-items
:
center
;
gap
:
10px
;
}
.category-header
::after
{
content
:
''
;
flex
:
1
;
height
:
1px
;
background
:
#E5E7EB
;
}
/*
Footer
*/
/*
── Feature 6: Running footer ──
*/
.page-footer
{
position
:
fixed
;
bottom
:
10
mm
;
left
:
1
5
mm
;
right
:
1
5
mm
;
font-size
:
9
px
;
bottom
:
8
mm
;
left
:
1
8
mm
;
right
:
1
8
mm
;
font-size
:
8
px
;
color
:
#9CA3AF
;
text-align
:
center
;
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
border-top
:
1px
solid
#F3F4F6
;
padding-top
:
6px
;
}
.page-footer-brand
{
font-weight
:
600
;
color
:
#6B7280
;
}
/*
Screenshot image within content
*/
/*
── Feature 10: Screenshot styling ──
*/
.tutorial-block-body
img
{
max-width
:
100%
;
height
:
auto
;
border-radius
:
6
px
;
border-radius
:
8
px
;
border
:
1px
solid
#E5E7EB
;
margin
:
8px
0
;
margin
:
10px
0
;
box-shadow
:
0
4px
12px
rgba
(
0
,
0
,
0
,
0.06
);
}
/* ══════════════════════════════════════
Feature 18: BACK COVER
══════════════════════════════════════ */
.back-cover
{
page-break-before
:
always
;
width
:
100%
;
min-height
:
250mm
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
text-align
:
center
;
background
:
linear-gradient
(
160deg
,
#0F0A2A
,
#1A1145
,
#2D1B69
);
color
:
#fff
;
padding
:
60px
;
position
:
relative
;
}
.back-cover
::after
{
content
:
''
;
position
:
absolute
;
bottom
:
0
;
left
:
0
;
right
:
0
;
height
:
4px
;
background
:
linear-gradient
(
90deg
,
#8B5CF6
,
#EC4899
,
#F59E0B
,
#10B981
,
#3B82F6
);
}
.back-cover-logo
{
width
:
80px
;
height
:
80px
;
background
:
rgba
(
255
,
255
,
255
,
0.08
);
border-radius
:
20px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
margin-bottom
:
24px
;
border
:
1px
solid
rgba
(
255
,
255
,
255
,
0.1
);
}
.back-cover-logo
svg
{
width
:
40px
;
height
:
40px
;
fill
:
none
;
stroke
:
#fff
;
stroke-width
:
1.5
;
}
.back-cover
h2
{
font-size
:
24px
;
font-weight
:
800
;
margin-bottom
:
8px
;
opacity
:
0.9
;
}
.back-cover
p
{
font-size
:
12px
;
opacity
:
0.5
;
margin-bottom
:
4px
;
}
/* ── Feature 20: Consistent spacing rhythm ── */
.spacing-sm
{
margin-bottom
:
8px
;
}
.spacing-md
{
margin-bottom
:
16px
;
}
.spacing-lg
{
margin-bottom
:
24px
;
}
.spacing-xl
{
margin-bottom
:
32px
;
}
/*
Print optimizations
*/
/*
── Feature 17: Print optimizations ──
*/
@media
print
{
body
{
font-size
:
11px
;
}
.cover-page
{
height
:
297mm
;
}
.module-divider
{
height
:
297mm
;
}
.back-cover
{
height
:
297mm
;
}
.no-print
{
display
:
none
;
}
}
</style>
</head>
<body>
<!-- Cover Page -->
<?php
$sectionColors
=
[
'#8B5CF6'
,
'#3B82F6'
,
'#10B981'
,
'#F59E0B'
,
'#EF4444'
,
'#06B6D4'
,
'#EC4899'
,
'#14B8A6'
,
'#F97316'
,
'#6366F1'
,
'#84CC16'
,
'#0EA5E9'
,
'#D946EF'
,
'#22C55E'
,
'#E11D48'
,
'#0891B2'
,
'#7C3AED'
,
'#059669'
,
'#DC2626'
,
'#2563EB'
,
'#8B5CF6'
,
'#3B82F6'
,
'#10B981'
,
'#F59E0B'
,
'#EF4444'
,
'#06B6D4'
,
'#EC4899'
,
'#14B8A6'
,
'#F97316'
,
'#6366F1'
,
'#84CC16'
,
'#0EA5E9'
,
'#D946EF'
,
'#22C55E'
,
'#E11D48'
,
'#0891B2'
,
'#7C3AED'
,
'#059669'
,
'#DC2626'
,
'#2563EB'
,
'#8B5CF6'
,
'#3B82F6'
,
'#10B981'
,
];
$totalTutorials
=
0
;
$totalSections
=
count
(
$data
[
'sections'
]);
foreach
(
$data
[
'sections'
]
as
$s
)
{
$totalTutorials
+=
count
(
$s
[
'tutorials'
]);
}
$sectionIcons
=
[
'membership'
=>
'<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>'
,
'sports-activity'
=>
'<circle cx="12" cy="12" r="10"/><path d="M12 8v4l3 3"/>'
,
'treasury'
=>
'<rect x="2" y="4" width="20" height="16" rx="2"/><path d="M12 8v8"/><path d="M8 12h8"/>'
,
'default'
=>
'<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>'
,
];
?>
<!-- ═══ COVER PAGE ═══ -->
<div
class=
"cover-page"
>
<div
class=
"cover-geometric"
></div>
<div
class=
"cover-logo"
>
<svg
viewBox=
"0 0 24 24"
><path
d=
"M
12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5
"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/></svg>
<svg
viewBox=
"0 0 24 24"
><path
d=
"M
2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/><path
d=
"M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z
"
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-desc"
>
الدليل التدريبي التفصيلي لجميع عمليات وأقسام نظام إدارة النادي — مرجع شامل للموظفين
</div>
<div
class=
"cover-stats"
>
<div
class=
"cover-stat"
>
<span
class=
"cover-stat-num"
>
<?=
$totalSections
?>
</span>
<span
class=
"cover-stat-label"
>
قسم
</span>
</div>
<div
class=
"cover-stat"
>
<span
class=
"cover-stat-num"
>
<?=
$totalTutorials
?>
</span>
<span
class=
"cover-stat-label"
>
شرح
</span>
</div>
<div
class=
"cover-stat"
>
<span
class=
"cover-stat-num"
>
105
</span>
<span
class=
"cover-stat-label"
>
لقطة شاشة
</span>
</div>
</div>
<div
class=
"cover-meta"
>
تم التوليد في:
<?=
$data
[
'generatedAt'
]
?>
|
عدد الأقسام:
<?=
count
(
$data
[
'sections'
])
?>
|
إجمالي الشروحات:
<?php
$totalTutorials
=
0
;
foreach
(
$data
[
'sections'
]
as
$s
)
{
$totalTutorials
+=
count
(
$s
[
'tutorials'
]);
}
echo
$totalTutorials
;
?>
تم التوليد في:
<?=
$data
[
'generatedAt'
]
?>
</div>
</div>
<!--
Table of Contents
-->
<!--
═══ TABLE OF CONTENTS ═══
-->
<div
class=
"toc-page"
>
<h1
class=
"toc-title"
>
فهرس المحتويات
</h1>
<div
class=
"toc-header"
>
<h1>
فهرس المحتويات
</h1>
<p>
<?=
$totalSections
?>
قسم —
<?=
$totalTutorials
?>
شرح تفصيلي
</p>
</div>
<div
class=
"toc-grid"
>
<?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
class=
"toc-section"
style=
"border-right-color:
<?=
$sectionColors
[
$sectionNum
-
1
]
??
'#8B5CF6'
?>
;"
>
<div
class=
"toc-section-title"
>
<span><span
class=
"toc-section-num"
style=
"background:
<?=
$sectionColors
[
$sectionNum
-
1
]
??
'#8B5CF6'
?>
;"
>
<?=
$sectionNum
?>
</span>
<?=
htmlspecialchars
(
$section
[
'title'
],
ENT_QUOTES
,
'UTF-8'
)
?>
</span>
<span
class=
"toc-badge"
>
<?=
count
(
$section
[
'tutorials'
])
?>
</span>
</div>
<?php
if
(
count
(
$section
[
'tutorials'
])
<=
8
)
:
?>
<ul
class=
"toc-items"
>
<?php
foreach
(
$section
[
'tutorials'
]
as
$slug
=>
$tutorial
)
:
?>
<li>
<?=
htmlspecialchars
(
$tutorial
[
'title'
],
ENT_QUOTES
,
'UTF-8'
)
?>
</li>
<?php
endforeach
;
?>
</ul>
<?php
else
:
?>
<ul
class=
"toc-items"
>
<?php
$i
=
0
;
foreach
(
$section
[
'tutorials'
]
as
$slug
=>
$tutorial
)
:
$i
++
;
if
(
$i
>
5
)
break
;
?>
<li>
<?=
htmlspecialchars
(
$tutorial
[
'title'
],
ENT_QUOTES
,
'UTF-8'
)
?>
</li>
<?php
endforeach
;
?>
<li
style=
"color:#9CA3AF;font-style:italic;"
>
+
<?=
count
(
$section
[
'tutorials'
])
-
5
?>
شرح آخر...
</li>
</ul>
<?php
endif
;
?>
</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>
</div>
<!--
Sections & Tutorials
-->
<?php
$sectionNum
=
0
;
foreach
(
$data
[
'sections'
]
as
$sectionKey
=>
$section
)
:
$sectionNum
++
;
?>
<!--
═══ SECTIONS & TUTORIALS ═══
-->
<?php
$sectionNum
=
0
;
foreach
(
$data
[
'sections'
]
as
$sectionKey
=>
$section
)
:
$sectionNum
++
;
$color
=
$sectionColors
[
$sectionNum
-
1
]
??
'#8B5CF6'
;
?>
<div
class=
"section-header"
>
<h2>
القسم
<?=
$sectionNum
?>
:
<?=
htmlspecialchars
(
$section
[
'title'
],
ENT_QUOTES
,
'UTF-8'
)
?>
</h2>
<!-- Module Divider Page -->
<div
class=
"module-divider"
style=
"--section-color:
<?=
$color
?>
;"
>
<div
class=
"module-divider-corners"
></div>
<div
class=
"module-divider-num"
>
<?=
str_pad
((
string
)
$sectionNum
,
2
,
'0'
,
STR_PAD_LEFT
)
?>
</div>
<div
class=
"module-divider-icon"
style=
"background:
<?=
$color
?>
;"
>
<svg
viewBox=
"0 0 24 24"
>
<?=
$sectionIcons
[
$sectionKey
]
??
$sectionIcons
[
'default'
]
?>
</svg>
</div>
<h2>
<?=
htmlspecialchars
(
$section
[
'title'
],
ENT_QUOTES
,
'UTF-8'
)
?>
</h2>
<?php
if
(
!
empty
(
$section
[
'subtitle'
]))
:
?>
<
p>
<?=
htmlspecialchars
(
$section
[
'subtitle'
],
ENT_QUOTES
,
'UTF-8'
)
?>
</p
>
<
div
class=
"module-divider-subtitle"
>
<?=
htmlspecialchars
(
$section
[
'subtitle'
],
ENT_QUOTES
,
'UTF-8'
)
?>
</div
>
<?php
endif
;
?>
<div
class=
"section-count"
>
<?=
count
(
$section
[
'tutorials'
])
?>
شرح في هذا القسم
</div>
<div
class=
"module-divider-stats"
>
<div
class=
"module-divider-stat"
>
<span
class=
"module-divider-stat-num"
style=
"color:
<?=
$color
?>
;"
>
<?=
count
(
$section
[
'tutorials'
])
?>
</span>
<span
class=
"module-divider-stat-label"
>
شرح
</span>
</div>
<div
class=
"module-divider-stat"
>
<span
class=
"module-divider-stat-num"
style=
"color:
<?=
$color
?>
;"
>
<?=
count
(
$section
[
'categories'
]
??
[])
?>
</span>
<span
class=
"module-divider-stat-label"
>
تصنيف
</span>
</div>
<div
class=
"module-divider-stat"
>
<span
class=
"module-divider-stat-num"
style=
"color:
<?=
$color
?>
;"
>
القسم
<?=
$sectionNum
?>
</span>
<span
class=
"module-divider-stat-label"
>
من
<?=
$totalSections
?>
</span>
</div>
</div>
</div>
<?php
if
(
!
empty
(
$section
[
'screenshot'
])
&&
file_exists
(
$section
[
'screenshot'
]))
:
?>
<div
style=
"text-align:center;margin
-bottom:24px
;"
>
<div
style=
"text-align:center;margin
: 24px 0;page-break-inside:avoid
;"
>
<img
src=
"file://
<?=
$section
[
'screenshot'
]
?>
"
class=
"tutorial-screenshot"
alt=
"
<?=
htmlspecialchars
(
$section
[
'title'
],
ENT_QUOTES
,
'UTF-8'
)
?>
"
>
</div>
<?php
endif
;
?>
...
...
@@ -450,13 +903,15 @@ foreach ($section['tutorials'] as $slug => $tutorial):
if
(
$cat
!==
$currentCategory
&&
!
empty
(
$section
[
'categories'
][
$cat
]))
:
$currentCategory
=
$cat
;
?>
<div
class=
"category-header"
>
<?=
htmlspecialchars
(
$section
[
'categories'
][
$cat
][
'label'
],
ENT_QUOTES
,
'UTF-8'
)
?>
</div>
<div
class=
"category-header"
style=
"--section-color:
<?=
$color
?>
; border-right-color:
<?=
$color
?>
;"
>
<?=
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>
<div
class=
"tutorial-num"
style=
"background: linear-gradient(135deg,
<?=
$color
?>
,
<?=
$color
?>
CC);"
>
<?=
$tutorialNum
?>
</div>
<div
style=
"flex:1;"
>
<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>
...
...
@@ -466,8 +921,16 @@ foreach ($section['tutorials'] as $slug => $tutorial):
<div
class=
"tutorial-block-body"
>
<?php
if
(
!
empty
(
$tutorial
[
'htmlContent'
]))
:
?>
<?=
$tutorial
[
'htmlContent'
]
?>
<?php
elseif
(
!
empty
(
$tutorial
[
'steps'
]))
:
?>
<?php
foreach
(
$tutorial
[
'steps'
]
as
$stepIdx
=>
$step
)
:
?>
<div
class=
"tut-step"
style=
"border-right-color:
<?=
$color
?>
;"
>
<div
class=
"tut-step-num"
>
<?=
$stepIdx
+
1
?>
</div>
<div
class=
"tut-step-title"
>
<?=
htmlspecialchars
(
$step
[
'title'
]
??
''
,
ENT_QUOTES
,
'UTF-8'
)
?>
</div>
<div
class=
"tut-step-body"
>
<?=
$step
[
'body'
]
??
''
?>
</div>
</div>
<?php
endforeach
;
?>
<?php
else
:
?>
<p
style=
"color:#6B7280;font-s
tyle:italic;font-size:12px
;"
>
<p
style=
"color:#6B7280;font-s
ize:11px;padding:8px 0
;"
>
<?=
htmlspecialchars
(
$tutorial
[
'subtitle'
]
??
$tutorial
[
'title'
],
ENT_QUOTES
,
'UTF-8'
)
?>
</p>
<?php
endif
;
?>
...
...
@@ -477,5 +940,16 @@ foreach ($section['tutorials'] as $slug => $tutorial):
<?php
endforeach
;
?>
<?php
endforeach
;
?>
<!-- ═══ BACK COVER ═══ -->
<div
class=
"back-cover"
>
<div
class=
"back-cover-logo"
>
<svg
viewBox=
"0 0 24 24"
><path
d=
"M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/><path
d=
"M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"
stroke-linecap=
"round"
stroke-linejoin=
"round"
/></svg>
</div>
<h2>
Book of the ERP
</h2>
<p>
كتاب النظام الشامل — الإصدار الأول
</p>
<p
style=
"margin-top:16px;opacity:0.3;font-size:10px;"
>
تم التوليد تلقائياً بواسطة نظام إدارة النادي
</p>
<p
style=
"opacity:0.3;font-size:10px;"
>
<?=
$data
[
'generatedAt'
]
?>
</p>
</div>
</body>
</html>
app/Shared/Services/PdfExportService.php
View file @
b98c1c14
...
...
@@ -23,8 +23,7 @@ final class PdfExportService
$html
=
ob_get_clean
();
$response
=
new
Response
();
return
$response
->
html
(
$html
,
200
,
[
'Content-Type'
=>
'text/html; charset=utf-8'
,
return
$response
->
html
(
$html
,
200
)
->
withHeaders
([
'Content-Disposition'
=>
'attachment; filename="'
.
$filename
.
'"'
,
]);
}
...
...
@@ -61,7 +60,7 @@ final class PdfExportService
@
unlink
(
$tmpOutput
);
$response
=
new
Response
();
return
$response
->
html
(
$pdfContent
,
200
,
[
return
$response
->
html
(
$pdfContent
,
200
)
->
withHeaders
(
[
'Content-Type'
=>
'application/pdf'
,
'Content-Disposition'
=>
'attachment; filename="'
.
$filename
.
'"'
,
'Content-Length'
=>
(
string
)
strlen
(
$pdfContent
),
...
...
@@ -71,8 +70,7 @@ final class PdfExportService
}
$response
=
new
Response
();
return
$response
->
html
(
$html
,
200
,
[
'Content-Type'
=>
'text/html; charset=utf-8'
,
return
$response
->
html
(
$html
,
200
)
->
withHeaders
([
'Content-Disposition'
=>
'attachment; filename="'
.
str_replace
(
'.pdf'
,
'.html'
,
$filename
)
.
'"'
,
]);
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment