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
ffa74159
Commit
ffa74159
authored
Apr 11, 2026
by
Mahmoud Aglan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
go
parent
4372c4e4
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
1870 additions
and
94 deletions
+1870
-94
bootstrap.php
app/Modules/Branches/bootstrap.php
+2
-1
print.php
app/Modules/Carnets/Views/print.php
+101
-28
print.php
app/Modules/Receipts/Views/print.php
+86
-57
BrandingController.php
app/Modules/Settings/Controllers/BrandingController.php
+142
-0
Routes.php
app/Modules/Settings/Routes.php
+6
-0
BrandingService.php
app/Modules/Settings/Services/BrandingService.php
+147
-0
branding.php
app/Modules/Settings/Views/branding.php
+551
-0
sidebar.php
app/Shared/Components/sidebar.php
+7
-1
auth.php
app/Shared/Layout/auth.php
+16
-5
main.php
app/Shared/Layout/main.php
+2
-1
print.php
app/Shared/Layout/print.php
+6
-1
Phase_16_001_seed_branding_config.php
database/seeds/Phase_16_001_seed_branding_config.php
+98
-0
branding.css
public/assets/css/branding.css
+695
-0
main.css
public/assets/css/main.css
+11
-0
No files found.
app/Modules/Branches/bootstrap.php
View file @
ffa74159
...
...
@@ -14,7 +14,8 @@ MenuRegistry::register('branches_settings', [
'children'
=>
[
[
'label_ar'
=>
'الفروع'
,
'label_en'
=>
'Branches'
,
'route'
=>
'/branches'
,
'permission'
=>
'settings.view'
,
'order'
=>
1
],
[
'label_ar'
=>
'إعدادات النظام'
,
'label_en'
=>
'Settings'
,
'route'
=>
'/settings'
,
'permission'
=>
'settings.view'
,
'order'
=>
2
],
[
'label_ar'
=>
'سجل المراجعة'
,
'label_en'
=>
'Audit Log'
,
'route'
=>
'/audit'
,
'permission'
=>
'report.view_audit'
,
'order'
=>
3
],
[
'label_ar'
=>
'العلامة التجارية'
,
'label_en'
=>
'Branding'
,
'route'
=>
'/settings/branding'
,
'permission'
=>
'settings.edit'
,
'order'
=>
3
],
[
'label_ar'
=>
'سجل المراجعة'
,
'label_en'
=>
'Audit Log'
,
'route'
=>
'/audit'
,
'permission'
=>
'report.view_audit'
,
'order'
=>
4
],
],
]);
...
...
app/Modules/Carnets/Views/print.php
View file @
ffa74159
<?php
use
App\Modules\Settings\Services\BrandingService
;
$design
=
BrandingService
::
carnetDesign
();
$clubNameAr
=
BrandingService
::
clubNameAr
();
$clubNameEn
=
BrandingService
::
clubNameEn
();
$clubSubtitle
=
BrandingService
::
subtitle
();
$logoUrl
=
BrandingService
::
logo
();
$bgColor
=
$design
[
'front_bg_color'
];
$textColor
=
$design
[
'front_text_color'
];
$showSub
=
$design
[
'front_show_subtitle'
];
$showEn
=
$design
[
'front_show_english_name'
];
$showType
=
$design
[
'front_show_member_type'
];
$showQr
=
$design
[
'front_show_qr'
];
$qrPos
=
$design
[
'front_qr_position'
];
$logoPos
=
$design
[
'front_logo_position'
];
$showStrip
=
$design
[
'back_show_branch_strip'
];
$stripColor
=
$design
[
'back_strip_color'
];
$showInstr
=
$design
[
'back_show_instructions'
];
$instrText
=
$design
[
'back_instructions_text'
];
$backFields
=
$design
[
'back_fields'
]
??
[];
// QR position CSS
$qrStyle
=
'position:absolute;'
;
if
(
str_contains
(
$qrPos
,
'bottom'
))
$qrStyle
.=
'bottom:15px;'
;
else
$qrStyle
.=
'top:15px;'
;
if
(
str_contains
(
$qrPos
,
'left'
))
$qrStyle
.=
'left:15px;'
;
else
$qrStyle
.=
'right:15px;'
;
// Logo position CSS
$logoAlign
=
'text-align:center;'
;
if
(
$logoPos
===
'top-right'
)
$logoAlign
=
'text-align:right;'
;
elseif
(
$logoPos
===
'top-left'
)
$logoAlign
=
'text-align:left;'
;
// Back field data map
$fieldLabels
=
[
'full_name_ar'
=>
'الاسم'
,
'full_name_en'
=>
'Name'
,
'membership_number'
=>
'رقم العضوية'
,
'branch_name'
=>
'الفرع'
,
'issued_at'
=>
'تاريخ الإصدار'
,
'national_id'
=>
'الرقم القومي'
,
'phone'
=>
'الهاتف'
,
];
$fieldValues
=
[
'full_name_ar'
=>
$carnet
[
'full_name_ar'
]
??
'—'
,
'full_name_en'
=>
$carnet
[
'full_name_en'
]
??
'—'
,
'membership_number'
=>
$carnet
[
'membership_number'
]
??
'—'
,
'branch_name'
=>
$carnet
[
'branch_name'
]
??
'—'
,
'issued_at'
=>
isset
(
$carnet
[
'issued_at'
])
?
substr
(
$carnet
[
'issued_at'
],
0
,
10
)
:
'—'
,
'national_id'
=>
$carnet
[
'national_id'
]
??
'—'
,
'phone'
=>
$carnet
[
'phone'
]
??
'—'
,
];
?>
<?php
$__template
->
layout
(
'Layout.print'
);
?>
<?php
$__template
->
section
(
'title'
);
?>
كارنيه العضوية —
<?=
e
(
$carnet
[
'membership_number'
]
??
$carnet
[
'carnet_number'
])
?><?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'content'
);
?>
<style>
@media
print
{
body
{
margin
:
0
;
}
}
@media
print
{
body
{
margin
:
0
;
}
.print-header
{
display
:
none
;
}
}
.carnet-container
{
width
:
340px
;
margin
:
20px
auto
;
font-family
:
'Cairo'
,
Arial
,
sans-serif
;
}
.carnet-front
{
width
:
340px
;
height
:
215px
;
background
:
#0D7377
;
border-radius
:
12px
;
color
:
#fff
;
padding
:
20px
;
position
:
relative
;
overflow
:
hidden
;
margin-bottom
:
15px
;
width
:
340px
;
height
:
215px
;
background
:
<?=
e
(
$bgColor
)
?>
;
border-radius
:
12px
;
color
:
<?=
e
(
$textColor
)
?>
;
padding
:
20px
;
position
:
relative
;
overflow
:
hidden
;
margin-bottom
:
15px
;
box-shadow
:
0
4px
12px
rgba
(
0
,
0
,
0
,
0.15
);
}
.carnet-back
{
width
:
340px
;
height
:
215px
;
background
:
#fff
;
border-radius
:
12px
;
border
:
2px
solid
#0D7377
;
padding
:
20px
;
position
:
relative
;
border
:
2px
solid
<?=
e
(
$stripColor
)
?>
;
padding
:
20px
;
position
:
relative
;
box-shadow
:
0
4px
12px
rgba
(
0
,
0
,
0
,
0.1
);
}
.carnet-front
.club-name
{
text-align
:
center
;
font-size
:
16px
;
font-weight
:
700
;
margin-bottom
:
4px
;
}
.carnet-front
.club-subtitle
{
text-align
:
center
;
font-size
:
11px
;
opacity
:
0.8
;
margin-bottom
:
15px
;
}
.carnet-front
.club-name
{
<?=
$logoAlign
?>
font-size
:
16px
;
font-weight
:
700
;
margin-bottom
:
4px
;
}
.carnet-front
.club-subtitle
{
<?=
$logoAlign
?>
font-size
:
11px
;
opacity
:
0.8
;
margin-bottom
:
15px
;
}
.carnet-front
.front-logo
{
<?=
$logoAlign
?>
margin-bottom
:
8px
;
}
.carnet-front
.front-logo
img
{
max-height
:
30px
;
}
.carnet-front
.member-name
{
font-size
:
14px
;
font-weight
:
700
;
margin-bottom
:
5px
;
}
.carnet-front
.member-name-en
{
font-size
:
11px
;
opacity
:
0.8
;
margin-bottom
:
8px
;
}
.carnet-front
.member-number
{
font-size
:
20px
;
font-weight
:
700
;
letter-spacing
:
2px
;
direction
:
ltr
;
text-align
:
right
;
}
.carnet-front
.qr-area
{
position
:
absolute
;
bottom
:
15px
;
left
:
15px
;
width
:
70px
;
height
:
70px
;
background
:
#fff
;
border-radius
:
6px
;
padding
:
5px
;
}
.carnet-front
.member-type
{
font-size
:
11px
;
margin-top
:
5px
;
opacity
:
0.8
;
}
.carnet-front
.qr-area
{
<?=
$qrStyle
?>
width
:
70px
;
height
:
70px
;
background
:
#fff
;
border-radius
:
6px
;
padding
:
5px
;
}
.carnet-front
.qr-area
svg
{
width
:
60px
;
height
:
60px
;
}
.carnet-front
.carnet-num
{
position
:
absolute
;
bottom
:
15px
;
right
:
15px
;
font-size
:
10px
;
opacity
:
0.7
;
}
.carnet-back
.back-header
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
margin-bottom
:
15px
;
border-bottom
:
2px
solid
#0D7377
;
padding-bottom
:
10px
;
}
.carnet-back
.back-header
.logo-text
{
font-size
:
12px
;
font-weight
:
700
;
color
:
#0D7377
;
}
.carnet-back
.branch-strip
{
position
:
absolute
;
left
:
0
;
top
:
0
;
bottom
:
0
;
width
:
30px
;
background
:
#0D7377
;
border-radius
:
12px
0
0
12px
;
writing-mode
:
vertical-rl
;
text-orientation
:
mixed
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
color
:
#fff
;
font-size
:
11px
;
font-weight
:
600
;
}
.carnet-back
.back-header
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
margin-bottom
:
15px
;
border-bottom
:
2px
solid
<?=
e
(
$stripColor
)
?>
;
padding-bottom
:
10px
;
}
.carnet-back
.back-header
.logo-text
{
font-size
:
12px
;
font-weight
:
700
;
color
:
<?=
e
(
$stripColor
)
?>
;
}
.carnet-back
.back-header
.back-logo
img
{
max-height
:
25px
;
}
.carnet-back
.branch-strip
{
position
:
absolute
;
left
:
0
;
top
:
0
;
bottom
:
0
;
width
:
30px
;
background
:
<?=
e
(
$stripColor
)
?>
;
border-radius
:
12px
0
0
12px
;
writing-mode
:
vertical-rl
;
text-orientation
:
mixed
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
color
:
#fff
;
font-size
:
11px
;
font-weight
:
600
;
}
.carnet-back
.instructions
{
font-size
:
10px
;
color
:
#6B7280
;
text-align
:
center
;
position
:
absolute
;
bottom
:
10px
;
left
:
40px
;
right
:
10px
;
}
.carnet-back
.member-info
{
margin-right
:
35px
;
font-size
:
12px
;
color
:
#1A1A2E
;
}
.carnet-back
.member-info
div
{
margin-bottom
:
4px
;
}
.carnet-back
.member-info
strong
{
color
:
#0D7377
;
}
.carnet-back
.member-info
strong
{
color
:
<?=
e
(
$stripColor
)
?>
;
}
</style>
<div
class=
"carnet-container"
>
<!-- Front -->
<div
class=
"carnet-front"
>
<div
class=
"club-name"
>
THE CLUB — نادي النادي شيراتون
</div>
<div
class=
"club-subtitle"
>
SPORTS CITY
</div>
<?php
if
(
$logoUrl
)
:
?>
<div
class=
"front-logo"
><img
src=
"
<?=
e
(
$logoUrl
)
?>
"
alt=
"Logo"
></div>
<?php
endif
;
?>
<div
class=
"club-name"
>
<?=
e
(
$clubNameEn
)
?>
—
<?=
e
(
$clubNameAr
)
?>
</div>
<?php
if
(
$showSub
)
:
?>
<div
class=
"club-subtitle"
>
<?=
e
(
$clubSubtitle
)
?>
</div>
<?php
endif
;
?>
<div
class=
"member-name"
>
<?=
e
(
$carnet
[
'full_name_ar'
])
?>
</div>
<?php
if
(
$
carnet
[
'full_name_en'
]
)
:
?>
<div
style=
"font-size:11px;opacity:0.8;margin-bottom:8px;
"
>
<?=
e
(
$carnet
[
'full_name_en'
])
?>
</div>
<?php
if
(
$
showEn
&&
!
empty
(
$carnet
[
'full_name_en'
])
)
:
?>
<div
class=
"member-name-en
"
>
<?=
e
(
$carnet
[
'full_name_en'
])
?>
</div>
<?php
endif
;
?>
<div
class=
"member-number"
>
<?=
e
(
$carnet
[
'membership_number'
]
??
'—'
)
?>
</div>
<div
style=
"font-size:11px;margin-top:5px;opacity:0.8;"
>
<?=
$carnet
[
'carnet_type'
]
===
'seasonal'
?
'عضوية موسمية'
:
'عضو عامل'
?>
</div>
<div
class=
"qr-area"
>
<?=
$qrSvg
?>
</div>
<?php
if
(
$showType
)
:
?>
<div
class=
"member-type"
>
<?=
$carnet
[
'carnet_type'
]
===
'seasonal'
?
'عضوية موسمية'
:
'عضو عامل'
?>
</div>
<?php
endif
;
?>
<?php
if
(
$showQr
)
:
?>
<div
class=
"qr-area"
>
<?=
$qrSvg
?>
</div>
<?php
endif
;
?>
<div
class=
"carnet-num"
>
<?=
e
(
$carnet
[
'carnet_number'
])
?>
</div>
</div>
<!-- Back -->
<div
class=
"carnet-back"
>
<div
class=
"branch-strip"
>
<?=
e
(
$carnet
[
'branch_name'
]
??
'فرع شيراتون'
)
?>
</div>
<?php
if
(
$showStrip
)
:
?>
<div
class=
"branch-strip"
>
<?=
e
(
$carnet
[
'branch_name'
]
??
'فرع شيراتون'
)
?>
</div>
<?php
endif
;
?>
<div
class=
"back-header"
>
<div
class=
"logo-text"
>
THE CLUB
<br>
نادي النادي
</div>
<div
style=
"font-size:20px;"
>
🇪🇬
</div>
<?php
if
(
$logoUrl
)
:
?>
<div
class=
"back-logo"
><img
src=
"
<?=
e
(
$logoUrl
)
?>
"
alt=
"Logo"
></div>
<?php
else
:
?>
<div
class=
"logo-text"
>
<?=
e
(
$clubNameEn
)
?>
<br>
<?=
e
(
$clubNameAr
)
?>
</div>
<?php
endif
;
?>
</div>
<div
class=
"member-info"
>
<div><strong>
الاسم:
</strong>
<?=
e
(
$carnet
[
'full_name_ar'
])
?>
</div>
<div><strong>
رقم العضوية:
</strong>
<?=
e
(
$carnet
[
'membership_number'
]
??
'—'
)
?>
</div>
<div><strong>
الفرع:
</strong>
<?=
e
(
$carnet
[
'branch_name'
]
??
'—'
)
?>
</div>
<div><strong>
تاريخ الإصدار:
</strong>
<?=
e
(
substr
(
$carnet
[
'issued_at'
],
0
,
10
))
?>
</div>
<?php
foreach
(
$backFields
as
$field
)
:
?>
<?php
if
(
isset
(
$fieldLabels
[
$field
]))
:
?>
<div><strong>
<?=
e
(
$fieldLabels
[
$field
])
?>
:
</strong>
<?=
e
(
$fieldValues
[
$field
]
??
'—'
)
?>
</div>
<?php
endif
;
?>
<?php
endforeach
;
?>
</div>
<div
class=
"instructions"
>
برجاء حمل هذه البطاقة أثناء التواجد بالنادي وتقديمها عند الطلب
</div>
<?php
if
(
$showInstr
)
:
?>
<div
class=
"instructions"
>
<?=
e
(
$instrText
)
?>
</div>
<?php
endif
;
?>
</div>
</div>
<?php
$__template
->
endSection
();
?>
\ No newline at end of file
<?php
$__template
->
endSection
();
?>
app/Modules/Receipts/Views/print.php
View file @
ffa74159
<?php
use
App\Modules\Settings\Services\BrandingService
;
$rd
=
BrandingService
::
receiptDesign
();
$clubNameAr
=
BrandingService
::
clubNameAr
();
$clubNameEn
=
BrandingService
::
clubNameEn
();
$logoUrl
=
BrandingService
::
logo
();
$showLogo
=
$rd
[
'show_logo'
];
$showWatermark
=
$rd
[
'show_watermark'
];
$watermarkText
=
$rd
[
'watermark_text'
];
$watermarkOp
=
$rd
[
'watermark_opacity'
];
$headerColor
=
$rd
[
'header_color'
];
$showFooter
=
$rd
[
'show_footer_print_info'
];
?>
<?php
$__template
->
layout
(
'Layout.print'
);
?>
<?php
$__template
->
section
(
'title'
);
?>
إيصال رقم
<?=
e
(
$receipt
[
'receipt_number'
])
?><?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'content'
);
?>
<div
style=
"max-width:700px;margin:0 auto;font-family:'Cairo',sans-serif;direction:rtl;"
>
<?php
if
(
$receipt
[
'is_voided'
])
:
?>
<div
style=
"text-align:center;color:#DC2626;font-size:24px;font-weight:700;border:3px solid #DC2626;padding:10px;margin-bottom:20px;"
>
ملغى
</div>
<?php
endif
;
?>
<div
style=
"max-width:700px;margin:0 auto;font-family:'Cairo',sans-serif;direction:rtl;position:relative;overflow:hidden;"
>
<div
style=
"text-align:center;margin-bottom:30px;"
>
<h2
style=
"margin:0;color:#0D7377;"
>
نادي النادي شيراتون
</h2>
<p
style=
"margin:5px 0;color:#6B7280;"
>
THE CLUB Sheraton
</p>
<h3
style=
"margin:15px 0 5px;color:#1A1A2E;"
>
إيصال تحصيل
</h3>
<?php
if
(
$showWatermark
&&
$watermarkText
)
:
?>
<div
style=
"position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) rotate(-35deg);font-size:52px;font-weight:900;color:
<?=
e
(
$headerColor
)
?>
;opacity:
<?=
e
(
$watermarkOp
)
?>
;white-space:nowrap;pointer-events:none;z-index:0;"
>
<?=
e
(
$watermarkText
)
?>
</div>
<?php
endif
;
?>
<table
style=
"width:100%;border-collapse:collapse;margin-bottom:20px;font-size:14px;"
>
<tr>
<td
style=
"padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;width:30%;"
>
رقم الإيصال
</td>
<td
style=
"padding:8px;border:1px solid #E5E7EB;direction:ltr;text-align:right;font-weight:700;font-size:16px;"
>
<?=
e
(
$receipt
[
'receipt_number'
])
?>
</td>
</tr>
<tr>
<td
style=
"padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;"
>
التاريخ
</td>
<td
style=
"padding:8px;border:1px solid #E5E7EB;"
>
<?=
e
(
substr
(
$receipt
[
'issued_at'
],
0
,
10
))
?>
</td>
</tr>
<tr>
<td
style=
"padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;"
>
اسم العضو
</td>
<td
style=
"padding:8px;border:1px solid #E5E7EB;font-weight:600;"
>
<?=
e
(
$receipt
[
'member_name'
])
?>
</td>
</tr>
<tr>
<td
style=
"padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;"
>
رقم العضوية
</td>
<td
style=
"padding:8px;border:1px solid #E5E7EB;"
>
<?=
e
(
$receipt
[
'membership_number'
]
??
'—'
)
?>
</td>
</tr>
<tr>
<td
style=
"padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;"
>
البيان
</td>
<td
style=
"padding:8px;border:1px solid #E5E7EB;"
>
<?=
e
(
$receipt
[
'description_ar'
]
??
'—'
)
?>
</td>
</tr>
<tr>
<td
style=
"padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;"
>
المبلغ (رقماً)
</td>
<td
style=
"padding:8px;border:1px solid #E5E7EB;font-size:22px;font-weight:700;color:#0D7377;direction:ltr;text-align:right;"
>
<?=
money
(
$receipt
[
'amount'
])
?>
</td>
</tr>
<tr>
<td
style=
"padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;"
>
المبلغ (كتابةً)
</td>
<td
style=
"padding:8px;border:1px solid #E5E7EB;font-size:13px;"
>
<?=
e
(
$receipt
[
'amount_in_words_ar'
]
??
''
)
?>
</td>
</tr>
<?php
if
(
$receipt
[
'payment_type'
]
??
''
)
:
?>
<tr>
<td
style=
"padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;"
>
نوع الدفعة
</td>
<td
style=
"padding:8px;border:1px solid #E5E7EB;"
>
<?=
e
(
\App\Modules\Payments\Models\Payment
::
getPaymentTypeLabel
(
$receipt
[
'payment_type'
]))
?>
</td>
</tr>
<?php
endif
;
?>
<?php
if
(
$receipt
[
'payment_method'
]
??
''
)
:
?>
<tr>
<td
style=
"padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;"
>
طريقة الدفع
</td>
<td
style=
"padding:8px;border:1px solid #E5E7EB;"
>
<?=
e
(
\App\Modules\Payments\Models\Payment
::
getPaymentMethodLabel
(
$receipt
[
'payment_method'
]))
?>
</td>
</tr>
<div
style=
"position:relative;z-index:1;"
>
<?php
if
(
$receipt
[
'is_voided'
])
:
?>
<div
style=
"text-align:center;color:#DC2626;font-size:24px;font-weight:700;border:3px solid #DC2626;padding:10px;margin-bottom:20px;"
>
ملغى
</div>
<?php
endif
;
?>
</table>
<div
style=
"display:grid;grid-template-columns:1fr 1fr 1fr;gap:30px;margin-top:50px;text-align:center;font-size:13px;"
>
<div
style=
"border-top:1px solid #000;padding-top:10px;"
>
توقيع المستلم
</div>
<div
style=
"border-top:1px solid #000;padding-top:10px;"
>
أمين الخزينة
<br><small>
<?=
e
(
$receipt
[
'issued_by_name'
]
??
''
)
?>
</small></div>
<div
style=
"border-top:1px solid #000;padding-top:10px;"
>
المدير المالي
</div>
</div>
<div
style=
"text-align:center;margin-bottom:30px;padding-bottom:16px;border-bottom:2px solid
<?=
e
(
$headerColor
)
?>
;"
>
<?php
if
(
$showLogo
&&
$logoUrl
)
:
?>
<div
style=
"margin-bottom:10px;"
><img
src=
"
<?=
e
(
$logoUrl
)
?>
"
alt=
"Logo"
style=
"max-height:45px;"
></div>
<?php
endif
;
?>
<h2
style=
"margin:0;color:
<?=
e
(
$headerColor
)
?>
;"
>
<?=
e
(
$clubNameAr
)
?>
</h2>
<p
style=
"margin:5px 0;color:#6B7280;"
>
<?=
e
(
$clubNameEn
)
?>
</p>
<h3
style=
"margin:15px 0 5px;color:#1A1A2E;"
>
إيصال تحصيل
</h3>
</div>
<table
style=
"width:100%;border-collapse:collapse;margin-bottom:20px;font-size:14px;"
>
<tr>
<td
style=
"padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;width:30%;"
>
رقم الإيصال
</td>
<td
style=
"padding:8px;border:1px solid #E5E7EB;direction:ltr;text-align:right;font-weight:700;font-size:16px;"
>
<?=
e
(
$receipt
[
'receipt_number'
])
?>
</td>
</tr>
<tr>
<td
style=
"padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;"
>
التاريخ
</td>
<td
style=
"padding:8px;border:1px solid #E5E7EB;"
>
<?=
e
(
substr
(
$receipt
[
'issued_at'
],
0
,
10
))
?>
</td>
</tr>
<tr>
<td
style=
"padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;"
>
اسم العضو
</td>
<td
style=
"padding:8px;border:1px solid #E5E7EB;font-weight:600;"
>
<?=
e
(
$receipt
[
'member_name'
])
?>
</td>
</tr>
<tr>
<td
style=
"padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;"
>
رقم العضوية
</td>
<td
style=
"padding:8px;border:1px solid #E5E7EB;"
>
<?=
e
(
$receipt
[
'membership_number'
]
??
'—'
)
?>
</td>
</tr>
<tr>
<td
style=
"padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;"
>
البيان
</td>
<td
style=
"padding:8px;border:1px solid #E5E7EB;"
>
<?=
e
(
$receipt
[
'description_ar'
]
??
'—'
)
?>
</td>
</tr>
<tr>
<td
style=
"padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;"
>
المبلغ (رقماً)
</td>
<td
style=
"padding:8px;border:1px solid #E5E7EB;font-size:22px;font-weight:700;color:
<?=
e
(
$headerColor
)
?>
;direction:ltr;text-align:right;"
>
<?=
money
(
$receipt
[
'amount'
])
?>
</td>
</tr>
<tr>
<td
style=
"padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;"
>
المبلغ (كتابةً)
</td>
<td
style=
"padding:8px;border:1px solid #E5E7EB;font-size:13px;"
>
<?=
e
(
$receipt
[
'amount_in_words_ar'
]
??
''
)
?>
</td>
</tr>
<?php
if
(
$receipt
[
'payment_type'
]
??
''
)
:
?>
<tr>
<td
style=
"padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;"
>
نوع الدفعة
</td>
<td
style=
"padding:8px;border:1px solid #E5E7EB;"
>
<?=
e
(
\App\Modules\Payments\Models\Payment
::
getPaymentTypeLabel
(
$receipt
[
'payment_type'
]))
?>
</td>
</tr>
<?php
endif
;
?>
<?php
if
(
$receipt
[
'payment_method'
]
??
''
)
:
?>
<tr>
<td
style=
"padding:8px;border:1px solid #E5E7EB;background:#F9FAFB;font-weight:600;"
>
طريقة الدفع
</td>
<td
style=
"padding:8px;border:1px solid #E5E7EB;"
>
<?=
e
(
\App\Modules\Payments\Models\Payment
::
getPaymentMethodLabel
(
$receipt
[
'payment_method'
]))
?>
</td>
</tr>
<?php
endif
;
?>
</table>
<div
style=
"margin-top:30px;text-align:center;font-size:11px;color:#9CA3AF;"
>
طبع بتاريخ:
<?=
date
(
'Y-m-d H:i:s'
)
?>
— عدد مرات الطباعة:
<?=
(
int
)
(
$receipt
[
'print_count'
]
??
0
)
+
1
?>
<div
style=
"display:grid;grid-template-columns:1fr 1fr 1fr;gap:30px;margin-top:50px;text-align:center;font-size:13px;"
>
<div
style=
"border-top:1px solid #000;padding-top:10px;"
>
توقيع المستلم
</div>
<div
style=
"border-top:1px solid #000;padding-top:10px;"
>
أمين الخزينة
<br><small>
<?=
e
(
$receipt
[
'issued_by_name'
]
??
''
)
?>
</small></div>
<div
style=
"border-top:1px solid #000;padding-top:10px;"
>
المدير المالي
</div>
</div>
<?php
if
(
$showFooter
)
:
?>
<div
style=
"margin-top:30px;text-align:center;font-size:11px;color:#9CA3AF;"
>
طبع بتاريخ:
<?=
date
(
'Y-m-d H:i:s'
)
?>
— عدد مرات الطباعة:
<?=
(
int
)
(
$receipt
[
'print_count'
]
??
0
)
+
1
?>
</div>
<?php
endif
;
?>
</div>
</div>
<?php
$__template
->
endSection
();
?>
\ No newline at end of file
<?php
$__template
->
endSection
();
?>
app/Modules/Settings/Controllers/BrandingController.php
0 → 100644
View file @
ffa74159
<?php
declare
(
strict_types
=
1
);
namespace
App\Modules\Settings\Controllers
;
use
App\Core\Controller
;
use
App\Core\Request
;
use
App\Core\Response
;
use
App\Core\App
;
use
App\Modules\Settings\Models\SystemConfig
;
use
App\Modules\Settings\Services\BrandingService
;
class
BrandingController
extends
Controller
{
public
function
index
(
Request
$request
)
:
Response
{
return
$this
->
view
(
'Settings.Views.branding'
,
[
'logo'
=>
BrandingService
::
logo
(),
'clubNameAr'
=>
BrandingService
::
clubNameAr
(),
'clubNameEn'
=>
BrandingService
::
clubNameEn
(),
'subtitle'
=>
BrandingService
::
subtitle
(),
'carnetDesign'
=>
BrandingService
::
carnetDesign
(),
'receiptDesign'
=>
BrandingService
::
receiptDesign
(),
]);
}
public
function
updateLogo
(
Request
$request
)
:
Response
{
// Handle club name / subtitle fields
$clubNameAr
=
trim
(
$request
->
post
(
'club_name_ar'
,
''
));
$clubNameEn
=
trim
(
$request
->
post
(
'club_name_en'
,
''
));
$subtitle
=
trim
(
$request
->
post
(
'club_subtitle'
,
''
));
if
(
$clubNameAr
!==
''
)
{
SystemConfig
::
set
(
'branding.club_name_ar'
,
$clubNameAr
);
}
if
(
$clubNameEn
!==
''
)
{
SystemConfig
::
set
(
'branding.club_name_en'
,
$clubNameEn
);
}
SystemConfig
::
set
(
'branding.club_subtitle'
,
$subtitle
);
// Handle logo upload
if
(
$request
->
hasFile
(
'logo_file'
))
{
$file
=
$request
->
file
(
'logo_file'
);
if
(
$file
&&
$file
[
'error'
]
===
UPLOAD_ERR_OK
)
{
// Validate size (max 2MB)
if
(
$file
[
'size'
]
>
2
*
1024
*
1024
)
{
return
$this
->
redirect
(
'/settings/branding'
)
->
withError
(
'حجم الشعار يجب أن لا يتجاوز 2 ميجا'
);
}
// Validate MIME type
$finfo
=
new
\finfo
(
FILEINFO_MIME_TYPE
);
$mimeType
=
$finfo
->
file
(
$file
[
'tmp_name'
]);
$allowedTypes
=
[
'image/png'
,
'image/jpeg'
,
'image/jpg'
,
'image/svg+xml'
,
'image/webp'
];
if
(
!
in_array
(
$mimeType
,
$allowedTypes
))
{
return
$this
->
redirect
(
'/settings/branding'
)
->
withError
(
'نوع الملف غير مسموح (PNG, JPG, SVG, WebP فقط)'
);
}
// Generate filename
$ext
=
strtolower
(
pathinfo
(
$file
[
'name'
],
PATHINFO_EXTENSION
));
if
(
$ext
===
'svg'
)
$ext
=
'svg'
;
$storedFilename
=
'logo_'
.
date
(
'Ymd_His'
)
.
'_'
.
bin2hex
(
random_bytes
(
4
))
.
'.'
.
$ext
;
// Ensure upload directory exists
$uploadDir
=
App
::
getInstance
()
->
basePath
()
.
'/storage/uploads/branding/'
;
if
(
!
is_dir
(
$uploadDir
))
{
mkdir
(
$uploadDir
,
0755
,
true
);
}
$filePath
=
$uploadDir
.
$storedFilename
;
if
(
!
move_uploaded_file
(
$file
[
'tmp_name'
],
$filePath
))
{
return
$this
->
redirect
(
'/settings/branding'
)
->
withError
(
'فشل في حفظ الشعار'
);
}
// Delete old logo file if exists
$oldPath
=
BrandingService
::
logoFilePath
();
if
(
$oldPath
&&
file_exists
(
$oldPath
))
{
@
unlink
(
$oldPath
);
}
// Save path to config
SystemConfig
::
set
(
'branding.logo_path'
,
'storage/uploads/branding/'
.
$storedFilename
);
BrandingService
::
clearCache
();
}
}
// Handle logo removal
if
(
$request
->
post
(
'remove_logo'
)
===
'1'
)
{
$oldPath
=
BrandingService
::
logoFilePath
();
if
(
$oldPath
&&
file_exists
(
$oldPath
))
{
@
unlink
(
$oldPath
);
}
SystemConfig
::
set
(
'branding.logo_path'
,
''
);
}
BrandingService
::
clearCache
();
return
$this
->
redirect
(
'/settings/branding'
)
->
withSuccess
(
'تم تحديث الهوية البصرية بنجاح'
);
}
public
function
updateCarnetDesign
(
Request
$request
)
:
Response
{
$design
=
[
'front_bg_color'
=>
$request
->
post
(
'front_bg_color'
,
'#0D7377'
),
'front_text_color'
=>
$request
->
post
(
'front_text_color'
,
'#ffffff'
),
'front_show_subtitle'
=>
$request
->
post
(
'front_show_subtitle'
)
===
'1'
,
'front_show_english_name'
=>
$request
->
post
(
'front_show_english_name'
)
===
'1'
,
'front_show_member_type'
=>
$request
->
post
(
'front_show_member_type'
)
===
'1'
,
'front_show_qr'
=>
$request
->
post
(
'front_show_qr'
)
===
'1'
,
'front_qr_position'
=>
$request
->
post
(
'front_qr_position'
,
'bottom-left'
),
'front_logo_position'
=>
$request
->
post
(
'front_logo_position'
,
'top-center'
),
'back_show_branch_strip'
=>
$request
->
post
(
'back_show_branch_strip'
)
===
'1'
,
'back_strip_color'
=>
$request
->
post
(
'back_strip_color'
,
'#0D7377'
),
'back_show_instructions'
=>
$request
->
post
(
'back_show_instructions'
)
===
'1'
,
'back_instructions_text'
=>
trim
(
$request
->
post
(
'back_instructions_text'
,
''
)),
'back_fields'
=>
$request
->
post
(
'back_fields'
)
?
(
array
)
$request
->
post
(
'back_fields'
)
:
[
'full_name_ar'
,
'membership_number'
],
];
$json
=
json_encode
(
$design
,
JSON_UNESCAPED_UNICODE
);
SystemConfig
::
set
(
'branding.carnet_design'
,
$json
);
BrandingService
::
clearCache
();
return
$this
->
redirect
(
'/settings/branding'
)
->
withSuccess
(
'تم تحديث تصميم الكارنيه بنجاح'
);
}
public
function
updateReceiptDesign
(
Request
$request
)
:
Response
{
$design
=
[
'show_logo'
=>
$request
->
post
(
'show_logo'
)
===
'1'
,
'show_watermark'
=>
$request
->
post
(
'show_watermark'
)
===
'1'
,
'watermark_text'
=>
trim
(
$request
->
post
(
'watermark_text'
,
''
)),
'watermark_opacity'
=>
max
(
0.01
,
min
(
0.2
,
(
float
)
$request
->
post
(
'watermark_opacity'
,
'0.06'
))),
'header_color'
=>
$request
->
post
(
'header_color'
,
'#0D7377'
),
'show_footer_print_info'
=>
$request
->
post
(
'show_footer_print_info'
)
===
'1'
,
];
$json
=
json_encode
(
$design
,
JSON_UNESCAPED_UNICODE
);
SystemConfig
::
set
(
'branding.receipt_design'
,
$json
);
BrandingService
::
clearCache
();
return
$this
->
redirect
(
'/settings/branding'
)
->
withSuccess
(
'تم تحديث تصميم الإيصال بنجاح'
);
}
}
app/Modules/Settings/Routes.php
View file @
ffa74159
...
...
@@ -5,4 +5,10 @@ return [
[
'GET'
,
'/settings'
,
'Settings\Controllers\SettingsController@index'
,
[
'auth'
],
'settings.view'
],
[
'GET'
,
'/settings/group/{group}'
,
'Settings\Controllers\SettingsController@editGroup'
,
[
'auth'
],
'settings.edit'
],
[
'POST'
,
'/settings/group/{group}'
,
'Settings\Controllers\SettingsController@updateGroup'
,
[
'auth'
,
'csrf'
],
'settings.edit'
],
// Branding
[
'GET'
,
'/settings/branding'
,
'Settings\Controllers\BrandingController@index'
,
[
'auth'
],
'settings.edit'
],
[
'POST'
,
'/settings/branding/logo'
,
'Settings\Controllers\BrandingController@updateLogo'
,
[
'auth'
,
'csrf'
],
'settings.edit'
],
[
'POST'
,
'/settings/branding/carnet'
,
'Settings\Controllers\BrandingController@updateCarnetDesign'
,
[
'auth'
,
'csrf'
],
'settings.edit'
],
[
'POST'
,
'/settings/branding/receipt'
,
'Settings\Controllers\BrandingController@updateReceiptDesign'
,[
'auth'
,
'csrf'
],
'settings.edit'
],
];
\ No newline at end of file
app/Modules/Settings/Services/BrandingService.php
0 → 100644
View file @
ffa74159
<?php
declare
(
strict_types
=
1
);
namespace
App\Modules\Settings\Services
;
use
App\Core\App
;
/**
* BrandingService — Centralized access to branding configuration.
* Reads from system_config (group: branding) and caches per-request.
*/
class
BrandingService
{
private
static
?
array
$cache
=
null
;
private
static
function
load
()
:
array
{
if
(
self
::
$cache
!==
null
)
{
return
self
::
$cache
;
}
try
{
$db
=
App
::
getInstance
()
->
db
();
$rows
=
$db
->
select
(
"SELECT config_key, config_value, config_type FROM system_config WHERE group_name = 'branding'"
);
}
catch
(
\Throwable
$e
)
{
$rows
=
[];
}
$data
=
[];
foreach
(
$rows
as
$row
)
{
$key
=
str_replace
(
'branding.'
,
''
,
$row
[
'config_key'
]);
$value
=
$row
[
'config_value'
];
if
(
$row
[
'config_type'
]
===
'json'
&&
$value
)
{
$decoded
=
json_decode
(
$value
,
true
);
$data
[
$key
]
=
is_array
(
$decoded
)
?
$decoded
:
[];
}
else
{
$data
[
$key
]
=
$value
;
}
}
self
::
$cache
=
$data
;
return
$data
;
}
/**
* Clear the cache (after updating branding settings).
*/
public
static
function
clearCache
()
:
void
{
self
::
$cache
=
null
;
}
/**
* Get URL path to the logo image, or null if not set.
*/
public
static
function
logo
()
:
?
string
{
$data
=
self
::
load
();
$path
=
$data
[
'logo_path'
]
??
null
;
if
(
$path
&&
$path
!==
''
)
{
// Return as web-accessible URL relative to public/
// Logo is stored in storage/uploads/branding/ — we need to serve it
// via a relative path from the app root
$basePath
=
rtrim
(
parse_url
(
config
(
'app.url'
,
''
),
PHP_URL_PATH
)
?:
''
,
'/'
);
return
$basePath
.
'/'
.
ltrim
(
$path
,
'/'
);
}
return
null
;
}
/**
* Get the absolute file path to the logo, or null.
*/
public
static
function
logoFilePath
()
:
?
string
{
$data
=
self
::
load
();
$path
=
$data
[
'logo_path'
]
??
null
;
if
(
$path
&&
$path
!==
''
)
{
$full
=
App
::
getInstance
()
->
basePath
()
.
'/'
.
ltrim
(
$path
,
'/'
);
return
file_exists
(
$full
)
?
$full
:
null
;
}
return
null
;
}
public
static
function
clubNameAr
()
:
string
{
$data
=
self
::
load
();
return
$data
[
'club_name_ar'
]
??
'نادي النادي شيراتون'
;
}
public
static
function
clubNameEn
()
:
string
{
$data
=
self
::
load
();
return
$data
[
'club_name_en'
]
??
'THE CLUB Sheraton'
;
}
public
static
function
subtitle
()
:
string
{
$data
=
self
::
load
();
return
$data
[
'club_subtitle'
]
??
'SPORTS CITY'
;
}
/**
* Get carnet design configuration as an associative array.
*/
public
static
function
carnetDesign
()
:
array
{
$data
=
self
::
load
();
$design
=
$data
[
'carnet_design'
]
??
[];
// Merge with defaults so views always have every key
return
array_merge
([
'front_bg_color'
=>
'#0D7377'
,
'front_text_color'
=>
'#ffffff'
,
'front_show_subtitle'
=>
true
,
'front_show_english_name'
=>
true
,
'front_show_member_type'
=>
true
,
'front_show_qr'
=>
true
,
'front_qr_position'
=>
'bottom-left'
,
'front_logo_position'
=>
'top-center'
,
'back_show_branch_strip'
=>
true
,
'back_strip_color'
=>
'#0D7377'
,
'back_show_instructions'
=>
true
,
'back_instructions_text'
=>
'برجاء حمل هذه البطاقة أثناء التواجد بالنادي وتقديمها عند الطلب'
,
'back_fields'
=>
[
'full_name_ar'
,
'membership_number'
,
'branch_name'
,
'issued_at'
],
],
$design
);
}
/**
* Get receipt design configuration as an associative array.
*/
public
static
function
receiptDesign
()
:
array
{
$data
=
self
::
load
();
$design
=
$data
[
'receipt_design'
]
??
[];
return
array_merge
([
'show_logo'
=>
true
,
'show_watermark'
=>
true
,
'watermark_text'
=>
'نادي النادي شيراتون'
,
'watermark_opacity'
=>
0.06
,
'header_color'
=>
'#0D7377'
,
'show_footer_print_info'
=>
true
,
],
$design
);
}
}
app/Modules/Settings/Views/branding.php
0 → 100644
View file @
ffa74159
<?php
$__template
->
layout
(
'Layout.main'
);
?>
<?php
$__template
->
section
(
'title'
);
?>
العلامة التجارية
<?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'styles'
);
?>
<link
rel=
"stylesheet"
href=
"
<?=
url
(
'assets/css/branding.css'
)
?>
"
>
<?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'content'
);
?>
<?php
use
App\Core\CSRF
;
$cd
=
$carnetDesign
;
$rd
=
$receiptDesign
;
?>
<!-- Tabs -->
<div
class=
"branding-tabs"
>
<button
class=
"branding-tab active"
data-tab=
"identity"
onclick=
"switchTab(this)"
>
<i
data-lucide=
"image"
></i>
<span>
الشعار والهوية
</span>
</button>
<button
class=
"branding-tab"
data-tab=
"carnet"
onclick=
"switchTab(this)"
>
<i
data-lucide=
"id-card"
></i>
<span>
تصميم الكارنيه
</span>
</button>
<button
class=
"branding-tab"
data-tab=
"receipt"
onclick=
"switchTab(this)"
>
<i
data-lucide=
"receipt"
></i>
<span>
تصميم الإيصال
</span>
</button>
</div>
<!-- ============================================ -->
<!-- TAB 1: Logo & Identity -->
<!-- ============================================ -->
<div
class=
"tab-panel active"
id=
"tab-identity"
>
<form
method=
"POST"
action=
"/settings/branding/logo"
enctype=
"multipart/form-data"
>
<input
type=
"hidden"
name=
"csrf_token"
value=
"
<?=
e
(
CSRF
::
token
())
?>
"
>
<div
class=
"branding-grid"
>
<!-- Logo Upload Card -->
<div
class=
"card branding-card"
>
<div
class=
"card-header"
>
<h3><i
data-lucide=
"upload"
></i>
شعار النادي
</h3>
</div>
<div
class=
"card-body"
>
<div
class=
"logo-upload-area"
id=
"logo-upload-area"
>
<?php
if
(
$logo
)
:
?>
<div
class=
"logo-preview"
id=
"logo-preview"
>
<img
src=
"
<?=
e
(
$logo
)
?>
"
alt=
"شعار النادي"
id=
"logo-preview-img"
>
<div
class=
"logo-preview-overlay"
>
<button
type=
"button"
class=
"btn btn-sm btn-danger"
onclick=
"removeLogo()"
>
<i
data-lucide=
"trash-2"
></i>
حذف
</button>
</div>
</div>
<?php
else
:
?>
<div
class=
"logo-placeholder"
id=
"logo-placeholder"
onclick=
"document.getElementById('logo_file').click()"
>
<i
data-lucide=
"image-plus"
></i>
<p>
اضغط لرفع الشعار
</p>
<small>
PNG, JPG, SVG, WebP — أقصى حجم 2MB
</small>
</div>
<?php
endif
;
?>
<input
type=
"file"
name=
"logo_file"
id=
"logo_file"
accept=
"image/png,image/jpeg,image/svg+xml,image/webp"
style=
"display:none;"
onchange=
"previewLogo(this)"
>
<input
type=
"hidden"
name=
"remove_logo"
id=
"remove_logo"
value=
"0"
>
</div>
</div>
</div>
<!-- Club Name Card -->
<div
class=
"card branding-card"
>
<div
class=
"card-header"
>
<h3><i
data-lucide=
"type"
></i>
بيانات النادي
</h3>
</div>
<div
class=
"card-body"
>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
اسم النادي بالعربية
</label>
<input
type=
"text"
name=
"club_name_ar"
class=
"form-input"
value=
"
<?=
e
(
$clubNameAr
)
?>
"
placeholder=
"نادي النادي شيراتون"
dir=
"rtl"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
اسم النادي بالإنجليزية
</label>
<input
type=
"text"
name=
"club_name_en"
class=
"form-input"
value=
"
<?=
e
(
$clubNameEn
)
?>
"
placeholder=
"THE CLUB Sheraton"
dir=
"ltr"
style=
"text-align:left;"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
العنوان الفرعي
</label>
<input
type=
"text"
name=
"club_subtitle"
class=
"form-input"
value=
"
<?=
e
(
$subtitle
)
?>
"
placeholder=
"SPORTS CITY"
dir=
"ltr"
style=
"text-align:left;"
>
</div>
</div>
</div>
</div>
<div
class=
"form-actions"
>
<button
type=
"submit"
class=
"btn btn-primary"
>
<i
data-lucide=
"save"
></i>
حفظ الهوية البصرية
</button>
</div>
</form>
</div>
<!-- ============================================ -->
<!-- TAB 2: Carnet Designer -->
<!-- ============================================ -->
<div
class=
"tab-panel"
id=
"tab-carnet"
>
<form
method=
"POST"
action=
"/settings/branding/carnet"
>
<input
type=
"hidden"
name=
"csrf_token"
value=
"
<?=
e
(
CSRF
::
token
())
?>
"
>
<div
class=
"designer-layout"
>
<!-- Settings Panel -->
<div
class=
"designer-settings"
>
<!-- Front Card Settings -->
<div
class=
"card branding-card"
>
<div
class=
"card-header"
>
<h3><i
data-lucide=
"credit-card"
></i>
الوجه الأمامي
</h3>
</div>
<div
class=
"card-body"
>
<div
class=
"form-row"
>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
لون الخلفية
</label>
<div
class=
"color-input-wrap"
>
<input
type=
"color"
name=
"front_bg_color"
value=
"
<?=
e
(
$cd
[
'front_bg_color'
])
?>
"
id=
"front_bg_color"
onchange=
"updateCarnetPreview()"
>
<input
type=
"text"
class=
"form-input form-input-sm color-hex"
value=
"
<?=
e
(
$cd
[
'front_bg_color'
])
?>
"
onchange=
"this.previousElementSibling.value=this.value;updateCarnetPreview()"
dir=
"ltr"
>
</div>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
لون النص
</label>
<div
class=
"color-input-wrap"
>
<input
type=
"color"
name=
"front_text_color"
value=
"
<?=
e
(
$cd
[
'front_text_color'
])
?>
"
id=
"front_text_color"
onchange=
"updateCarnetPreview()"
>
<input
type=
"text"
class=
"form-input form-input-sm color-hex"
value=
"
<?=
e
(
$cd
[
'front_text_color'
])
?>
"
onchange=
"this.previousElementSibling.value=this.value;updateCarnetPreview()"
dir=
"ltr"
>
</div>
</div>
</div>
<div
class=
"toggle-group"
>
<label
class=
"toggle-label"
>
<input
type=
"hidden"
name=
"front_show_subtitle"
value=
"0"
>
<input
type=
"checkbox"
name=
"front_show_subtitle"
value=
"1"
<?=
$cd
[
'front_show_subtitle'
]
?
'checked'
:
''
?>
onchange=
"updateCarnetPreview()"
>
<span
class=
"toggle-switch"
></span>
<span>
إظهار العنوان الفرعي
</span>
</label>
<label
class=
"toggle-label"
>
<input
type=
"hidden"
name=
"front_show_english_name"
value=
"0"
>
<input
type=
"checkbox"
name=
"front_show_english_name"
value=
"1"
<?=
$cd
[
'front_show_english_name'
]
?
'checked'
:
''
?>
onchange=
"updateCarnetPreview()"
>
<span
class=
"toggle-switch"
></span>
<span>
إظهار الاسم الإنجليزي
</span>
</label>
<label
class=
"toggle-label"
>
<input
type=
"hidden"
name=
"front_show_member_type"
value=
"0"
>
<input
type=
"checkbox"
name=
"front_show_member_type"
value=
"1"
<?=
$cd
[
'front_show_member_type'
]
?
'checked'
:
''
?>
onchange=
"updateCarnetPreview()"
>
<span
class=
"toggle-switch"
></span>
<span>
إظهار نوع العضوية
</span>
</label>
<label
class=
"toggle-label"
>
<input
type=
"hidden"
name=
"front_show_qr"
value=
"0"
>
<input
type=
"checkbox"
name=
"front_show_qr"
value=
"1"
<?=
$cd
[
'front_show_qr'
]
?
'checked'
:
''
?>
onchange=
"updateCarnetPreview()"
>
<span
class=
"toggle-switch"
></span>
<span>
إظهار QR Code
</span>
</label>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
موضع QR Code
</label>
<select
name=
"front_qr_position"
class=
"form-input"
id=
"front_qr_position"
onchange=
"updateCarnetPreview()"
>
<option
value=
"bottom-left"
<?=
$cd
[
'front_qr_position'
]
===
'bottom-left'
?
'selected'
:
''
?>
>
أسفل يسار
</option>
<option
value=
"bottom-right"
<?=
$cd
[
'front_qr_position'
]
===
'bottom-right'
?
'selected'
:
''
?>
>
أسفل يمين
</option>
<option
value=
"top-left"
<?=
$cd
[
'front_qr_position'
]
===
'top-left'
?
'selected'
:
''
?>
>
أعلى يسار
</option>
<option
value=
"top-right"
<?=
$cd
[
'front_qr_position'
]
===
'top-right'
?
'selected'
:
''
?>
>
أعلى يمين
</option>
</select>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
موضع الشعار
</label>
<select
name=
"front_logo_position"
class=
"form-input"
id=
"front_logo_position"
onchange=
"updateCarnetPreview()"
>
<option
value=
"top-center"
<?=
$cd
[
'front_logo_position'
]
===
'top-center'
?
'selected'
:
''
?>
>
أعلى الوسط
</option>
<option
value=
"top-right"
<?=
$cd
[
'front_logo_position'
]
===
'top-right'
?
'selected'
:
''
?>
>
أعلى يمين
</option>
<option
value=
"top-left"
<?=
$cd
[
'front_logo_position'
]
===
'top-left'
?
'selected'
:
''
?>
>
أعلى يسار
</option>
</select>
</div>
</div>
</div>
<!-- Back Card Settings -->
<div
class=
"card branding-card"
>
<div
class=
"card-header"
>
<h3><i
data-lucide=
"flip-horizontal"
></i>
الوجه الخلفي
</h3>
</div>
<div
class=
"card-body"
>
<div
class=
"toggle-group"
>
<label
class=
"toggle-label"
>
<input
type=
"hidden"
name=
"back_show_branch_strip"
value=
"0"
>
<input
type=
"checkbox"
name=
"back_show_branch_strip"
value=
"1"
<?=
$cd
[
'back_show_branch_strip'
]
?
'checked'
:
''
?>
onchange=
"updateCarnetPreview()"
>
<span
class=
"toggle-switch"
></span>
<span>
إظهار شريط الفرع
</span>
</label>
<label
class=
"toggle-label"
>
<input
type=
"hidden"
name=
"back_show_instructions"
value=
"0"
>
<input
type=
"checkbox"
name=
"back_show_instructions"
value=
"1"
<?=
$cd
[
'back_show_instructions'
]
?
'checked'
:
''
?>
onchange=
"updateCarnetPreview()"
>
<span
class=
"toggle-switch"
></span>
<span>
إظهار التعليمات
</span>
</label>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
لون شريط الفرع
</label>
<div
class=
"color-input-wrap"
>
<input
type=
"color"
name=
"back_strip_color"
value=
"
<?=
e
(
$cd
[
'back_strip_color'
])
?>
"
id=
"back_strip_color"
onchange=
"updateCarnetPreview()"
>
<input
type=
"text"
class=
"form-input form-input-sm color-hex"
value=
"
<?=
e
(
$cd
[
'back_strip_color'
])
?>
"
onchange=
"this.previousElementSibling.value=this.value;updateCarnetPreview()"
dir=
"ltr"
>
</div>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
نص التعليمات
</label>
<textarea
name=
"back_instructions_text"
class=
"form-input"
rows=
"2"
onchange=
"updateCarnetPreview()"
id=
"back_instructions_text"
>
<?=
e
(
$cd
[
'back_instructions_text'
])
?>
</textarea>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
حقول الوجه الخلفي
</label>
<?php
$allFields
=
[
'full_name_ar'
=>
'الاسم بالعربية'
,
'full_name_en'
=>
'الاسم بالإنجليزية'
,
'membership_number'
=>
'رقم العضوية'
,
'branch_name'
=>
'الفرع'
,
'issued_at'
=>
'تاريخ الإصدار'
,
'national_id'
=>
'الرقم القومي'
,
'phone'
=>
'الهاتف'
,
];
$selectedFields
=
$cd
[
'back_fields'
]
??
[];
?>
<div
class=
"checkbox-grid"
>
<?php
foreach
(
$allFields
as
$fieldKey
=>
$fieldLabel
)
:
?>
<label
class=
"checkbox-label"
>
<input
type=
"checkbox"
name=
"back_fields[]"
value=
"
<?=
e
(
$fieldKey
)
?>
"
<?=
in_array
(
$fieldKey
,
$selectedFields
)
?
'checked'
:
''
?>
onchange=
"updateCarnetPreview()"
>
<span
class=
"checkbox-box"
></span>
<span>
<?=
e
(
$fieldLabel
)
?>
</span>
</label>
<?php
endforeach
;
?>
</div>
</div>
</div>
</div>
</div>
<!-- Live Preview -->
<div
class=
"designer-preview"
>
<div
class=
"preview-sticky"
>
<h3
class=
"preview-title"
><i
data-lucide=
"eye"
></i>
معاينة حية
</h3>
<!-- Front Preview -->
<div
class=
"preview-label"
>
الوجه الأمامي
</div>
<div
class=
"carnet-preview-front"
id=
"carnet-front-preview"
>
<div
class=
"cpf-club-name"
>
<?=
e
(
$clubNameAr
)
?>
—
<?=
e
(
$clubNameEn
)
?>
</div>
<div
class=
"cpf-subtitle"
id=
"cpf-subtitle"
>
<?=
e
(
$subtitle
)
?>
</div>
<div
class=
"cpf-member-name"
>
أحمد محمد عبدالله
</div>
<div
class=
"cpf-member-name-en"
id=
"cpf-name-en"
>
Ahmed Mohamed Abdullah
</div>
<div
class=
"cpf-member-number"
>
2024-001234
</div>
<div
class=
"cpf-member-type"
id=
"cpf-member-type"
>
عضو عامل
</div>
<div
class=
"cpf-qr"
id=
"cpf-qr"
>
<svg
viewBox=
"0 0 40 40"
fill=
"#000"
><rect
x=
"2"
y=
"2"
width=
"8"
height=
"8"
/><rect
x=
"12"
y=
"2"
width=
"4"
height=
"4"
/><rect
x=
"18"
y=
"2"
width=
"4"
height=
"8"
/><rect
x=
"30"
y=
"2"
width=
"8"
height=
"8"
/><rect
x=
"2"
y=
"12"
width=
"4"
height=
"4"
/><rect
x=
"10"
y=
"14"
width=
"4"
height=
"4"
/><rect
x=
"18"
y=
"12"
width=
"8"
height=
"4"
/><rect
x=
"30"
y=
"12"
width=
"4"
height=
"4"
/><rect
x=
"2"
y=
"18"
width=
"8"
height=
"4"
/><rect
x=
"14"
y=
"18"
width=
"4"
height=
"4"
/><rect
x=
"22"
y=
"18"
width=
"4"
height=
"4"
/><rect
x=
"30"
y=
"18"
width=
"8"
height=
"4"
/><rect
x=
"2"
y=
"30"
width=
"8"
height=
"8"
/><rect
x=
"14"
y=
"30"
width=
"4"
height=
"4"
/><rect
x=
"22"
y=
"28"
width=
"4"
height=
"6"
/><rect
x=
"30"
y=
"30"
width=
"8"
height=
"8"
/></svg>
</div>
<div
class=
"cpf-carnet-num"
>
CRN-2024-0001
</div>
</div>
<!-- Back Preview -->
<div
class=
"preview-label"
style=
"margin-top:16px;"
>
الوجه الخلفي
</div>
<div
class=
"carnet-preview-back"
id=
"carnet-back-preview"
>
<div
class=
"cpb-branch-strip"
id=
"cpb-branch-strip"
>
فرع شيراتون
</div>
<div
class=
"cpb-header"
>
<div
class=
"cpb-logo-text"
>
<?=
e
(
$clubNameEn
)
?>
<br>
<?=
e
(
$clubNameAr
)
?>
</div>
</div>
<div
class=
"cpb-info"
id=
"cpb-info"
>
<div><strong>
الاسم:
</strong>
أحمد محمد عبدالله
</div>
<div><strong>
رقم العضوية:
</strong>
2024-001234
</div>
<div><strong>
الفرع:
</strong>
فرع شيراتون
</div>
<div><strong>
تاريخ الإصدار:
</strong>
2024-01-15
</div>
</div>
<div
class=
"cpb-instructions"
id=
"cpb-instructions"
>
<?=
e
(
$cd
[
'back_instructions_text'
])
?>
</div>
</div>
</div>
</div>
</div>
<div
class=
"form-actions"
>
<button
type=
"submit"
class=
"btn btn-primary"
>
<i
data-lucide=
"save"
></i>
حفظ تصميم الكارنيه
</button>
</div>
</form>
</div>
<!-- ============================================ -->
<!-- TAB 3: Receipt Designer -->
<!-- ============================================ -->
<div
class=
"tab-panel"
id=
"tab-receipt"
>
<form
method=
"POST"
action=
"/settings/branding/receipt"
>
<input
type=
"hidden"
name=
"csrf_token"
value=
"
<?=
e
(
CSRF
::
token
())
?>
"
>
<div
class=
"designer-layout"
>
<!-- Settings Panel -->
<div
class=
"designer-settings"
>
<div
class=
"card branding-card"
>
<div
class=
"card-header"
>
<h3><i
data-lucide=
"receipt"
></i>
إعدادات الإيصال
</h3>
</div>
<div
class=
"card-body"
>
<div
class=
"toggle-group"
>
<label
class=
"toggle-label"
>
<input
type=
"hidden"
name=
"show_logo"
value=
"0"
>
<input
type=
"checkbox"
name=
"show_logo"
value=
"1"
<?=
$rd
[
'show_logo'
]
?
'checked'
:
''
?>
onchange=
"updateReceiptPreview()"
>
<span
class=
"toggle-switch"
></span>
<span>
إظهار الشعار في الرأس
</span>
</label>
<label
class=
"toggle-label"
>
<input
type=
"hidden"
name=
"show_watermark"
value=
"0"
>
<input
type=
"checkbox"
name=
"show_watermark"
value=
"1"
<?=
$rd
[
'show_watermark'
]
?
'checked'
:
''
?>
onchange=
"updateReceiptPreview()"
>
<span
class=
"toggle-switch"
></span>
<span>
إظهار العلامة المائية
</span>
</label>
<label
class=
"toggle-label"
>
<input
type=
"hidden"
name=
"show_footer_print_info"
value=
"0"
>
<input
type=
"checkbox"
name=
"show_footer_print_info"
value=
"1"
<?=
$rd
[
'show_footer_print_info'
]
?
'checked'
:
''
?>
onchange=
"updateReceiptPreview()"
>
<span
class=
"toggle-switch"
></span>
<span>
إظهار معلومات الطباعة
</span>
</label>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
نص العلامة المائية
</label>
<input
type=
"text"
name=
"watermark_text"
class=
"form-input"
value=
"
<?=
e
(
$rd
[
'watermark_text'
])
?>
"
id=
"watermark_text"
onchange=
"updateReceiptPreview()"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
شفافية العلامة المائية:
<span
id=
"opacity-value"
>
<?=
number_format
(
$rd
[
'watermark_opacity'
],
2
)
?>
</span></label>
<input
type=
"range"
name=
"watermark_opacity"
class=
"form-range"
min=
"0.01"
max=
"0.20"
step=
"0.01"
value=
"
<?=
e
(
$rd
[
'watermark_opacity'
])
?>
"
id=
"watermark_opacity"
oninput=
"document.getElementById('opacity-value').textContent=parseFloat(this.value).toFixed(2);updateReceiptPreview()"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
لون الرأس
</label>
<div
class=
"color-input-wrap"
>
<input
type=
"color"
name=
"header_color"
value=
"
<?=
e
(
$rd
[
'header_color'
])
?>
"
id=
"receipt_header_color"
onchange=
"updateReceiptPreview()"
>
<input
type=
"text"
class=
"form-input form-input-sm color-hex"
value=
"
<?=
e
(
$rd
[
'header_color'
])
?>
"
onchange=
"this.previousElementSibling.value=this.value;updateReceiptPreview()"
dir=
"ltr"
>
</div>
</div>
</div>
</div>
</div>
<!-- Receipt Preview -->
<div
class=
"designer-preview"
>
<div
class=
"preview-sticky"
>
<h3
class=
"preview-title"
><i
data-lucide=
"eye"
></i>
معاينة حية
</h3>
<div
class=
"receipt-preview"
id=
"receipt-preview"
>
<div
class=
"rp-watermark"
id=
"rp-watermark"
>
<?=
e
(
$rd
[
'watermark_text'
])
?>
</div>
<?php
if
(
$logo
)
:
?>
<div
class=
"rp-logo"
id=
"rp-logo"
><img
src=
"
<?=
e
(
$logo
)
?>
"
alt=
"Logo"
></div>
<?php
else
:
?>
<div
class=
"rp-logo"
id=
"rp-logo"
style=
"display:none;"
></div>
<?php
endif
;
?>
<div
class=
"rp-header"
id=
"rp-header"
>
<h2>
<?=
e
(
$clubNameAr
)
?>
</h2>
<p>
<?=
e
(
$clubNameEn
)
?>
</p>
<h3>
إيصال تحصيل
</h3>
</div>
<div
class=
"rp-body"
>
<table>
<tr><td
class=
"rp-label"
>
رقم الإيصال
</td><td
class=
"rp-value"
>
RCP-2024-0001
</td></tr>
<tr><td
class=
"rp-label"
>
التاريخ
</td><td
class=
"rp-value"
>
2024-01-15
</td></tr>
<tr><td
class=
"rp-label"
>
اسم العضو
</td><td
class=
"rp-value"
>
أحمد محمد عبدالله
</td></tr>
<tr><td
class=
"rp-label"
>
المبلغ
</td><td
class=
"rp-value rp-amount"
>
1,500.00 ج.م
</td></tr>
</table>
</div>
<div
class=
"rp-signatures"
>
<div>
توقيع المستلم
</div>
<div>
أمين الخزينة
</div>
<div>
المدير المالي
</div>
</div>
<div
class=
"rp-footer"
id=
"rp-footer"
>
طبع بتاريخ:
<?=
date
(
'Y-m-d H:i'
)
?>
</div>
</div>
</div>
</div>
</div>
<div
class=
"form-actions"
>
<button
type=
"submit"
class=
"btn btn-primary"
>
<i
data-lucide=
"save"
></i>
حفظ تصميم الإيصال
</button>
</div>
</form>
</div>
<?php
$__template
->
endSection
();
?>
<?php
$__template
->
section
(
'scripts'
);
?>
<script>
// Tab switching
function
switchTab
(
btn
)
{
document
.
querySelectorAll
(
'.branding-tab'
).
forEach
(
function
(
t
)
{
t
.
classList
.
remove
(
'active'
);
});
document
.
querySelectorAll
(
'.tab-panel'
).
forEach
(
function
(
p
)
{
p
.
classList
.
remove
(
'active'
);
});
btn
.
classList
.
add
(
'active'
);
document
.
getElementById
(
'tab-'
+
btn
.
dataset
.
tab
).
classList
.
add
(
'active'
);
lucide
.
createIcons
();
}
// Logo preview
function
previewLogo
(
input
)
{
if
(
!
input
.
files
||
!
input
.
files
[
0
])
return
;
var
file
=
input
.
files
[
0
];
if
(
file
.
size
>
2
*
1024
*
1024
)
{
alert
(
'حجم الملف يتجاوز 2 ميجا'
);
input
.
value
=
''
;
return
;
}
var
reader
=
new
FileReader
();
reader
.
onload
=
function
(
e
)
{
var
area
=
document
.
getElementById
(
'logo-upload-area'
);
area
.
innerHTML
=
'<div class="logo-preview" id="logo-preview">'
+
'<img src="'
+
e
.
target
.
result
+
'" alt="معاينة" id="logo-preview-img">'
+
'<div class="logo-preview-overlay">'
+
'<button type="button" class="btn btn-sm btn-danger" onclick="removeLogo()">'
+
'<i data-lucide="trash-2"></i> حذف</button></div></div>'
;
// Re-append the hidden inputs
var
fileInput
=
document
.
getElementById
(
'logo_file'
);
var
removeInput
=
document
.
getElementById
(
'remove_logo'
);
if
(
!
area
.
contains
(
fileInput
))
area
.
appendChild
(
fileInput
);
if
(
!
area
.
contains
(
removeInput
))
area
.
appendChild
(
removeInput
);
removeInput
.
value
=
'0'
;
lucide
.
createIcons
();
};
reader
.
readAsDataURL
(
file
);
}
function
removeLogo
()
{
var
area
=
document
.
getElementById
(
'logo-upload-area'
);
area
.
innerHTML
=
'<div class="logo-placeholder" id="logo-placeholder" onclick="document.getElementById(
\'
logo_file
\'
).click()">'
+
'<i data-lucide="image-plus"></i>'
+
'<p>اضغط لرفع الشعار</p>'
+
'<small>PNG, JPG, SVG, WebP — أقصى حجم 2MB</small></div>'
+
'<input type="file" name="logo_file" id="logo_file" accept="image/png,image/jpeg,image/svg+xml,image/webp" style="display:none;" onchange="previewLogo(this)">'
+
'<input type="hidden" name="remove_logo" id="remove_logo" value="1">'
;
lucide
.
createIcons
();
}
// Carnet live preview
function
updateCarnetPreview
()
{
var
front
=
document
.
getElementById
(
'carnet-front-preview'
);
var
bgColor
=
document
.
getElementById
(
'front_bg_color'
).
value
;
var
textColor
=
document
.
getElementById
(
'front_text_color'
).
value
;
front
.
style
.
background
=
bgColor
;
front
.
style
.
color
=
textColor
;
var
showSub
=
document
.
querySelector
(
'[name="front_show_subtitle"][type="checkbox"]'
).
checked
;
var
showEn
=
document
.
querySelector
(
'[name="front_show_english_name"][type="checkbox"]'
).
checked
;
var
showType
=
document
.
querySelector
(
'[name="front_show_member_type"][type="checkbox"]'
).
checked
;
var
showQr
=
document
.
querySelector
(
'[name="front_show_qr"][type="checkbox"]'
).
checked
;
document
.
getElementById
(
'cpf-subtitle'
).
style
.
display
=
showSub
?
''
:
'none'
;
document
.
getElementById
(
'cpf-name-en'
).
style
.
display
=
showEn
?
''
:
'none'
;
document
.
getElementById
(
'cpf-member-type'
).
style
.
display
=
showType
?
''
:
'none'
;
document
.
getElementById
(
'cpf-qr'
).
style
.
display
=
showQr
?
''
:
'none'
;
// QR position
var
qrPos
=
document
.
getElementById
(
'front_qr_position'
).
value
;
var
qr
=
document
.
getElementById
(
'cpf-qr'
);
qr
.
style
.
top
=
''
;
qr
.
style
.
bottom
=
''
;
qr
.
style
.
left
=
''
;
qr
.
style
.
right
=
''
;
if
(
qrPos
===
'bottom-left'
)
{
qr
.
style
.
bottom
=
'10px'
;
qr
.
style
.
left
=
'10px'
;
}
else
if
(
qrPos
===
'bottom-right'
)
{
qr
.
style
.
bottom
=
'10px'
;
qr
.
style
.
right
=
'10px'
;
}
else
if
(
qrPos
===
'top-left'
)
{
qr
.
style
.
top
=
'10px'
;
qr
.
style
.
left
=
'10px'
;
}
else
if
(
qrPos
===
'top-right'
)
{
qr
.
style
.
top
=
'10px'
;
qr
.
style
.
right
=
'10px'
;
}
// Back
var
stripColor
=
document
.
getElementById
(
'back_strip_color'
).
value
;
var
showStrip
=
document
.
querySelector
(
'[name="back_show_branch_strip"][type="checkbox"]'
).
checked
;
var
showInstr
=
document
.
querySelector
(
'[name="back_show_instructions"][type="checkbox"]'
).
checked
;
var
instrText
=
document
.
getElementById
(
'back_instructions_text'
).
value
;
var
strip
=
document
.
getElementById
(
'cpb-branch-strip'
);
strip
.
style
.
display
=
showStrip
?
''
:
'none'
;
strip
.
style
.
background
=
stripColor
;
document
.
getElementById
(
'cpb-instructions'
).
style
.
display
=
showInstr
?
''
:
'none'
;
document
.
getElementById
(
'cpb-instructions'
).
textContent
=
instrText
;
// Back fields
var
checked
=
[];
document
.
querySelectorAll
(
'[name="back_fields[]"]:checked'
).
forEach
(
function
(
cb
)
{
checked
.
push
(
cb
.
value
);
});
var
fieldLabels
=
{
'full_name_ar'
:
'الاسم'
,
'full_name_en'
:
'Name'
,
'membership_number'
:
'رقم العضوية'
,
'branch_name'
:
'الفرع'
,
'issued_at'
:
'تاريخ الإصدار'
,
'national_id'
:
'الرقم القومي'
,
'phone'
:
'الهاتف'
};
var
fieldValues
=
{
'full_name_ar'
:
'أحمد محمد عبدالله'
,
'full_name_en'
:
'Ahmed M. Abdullah'
,
'membership_number'
:
'2024-001234'
,
'branch_name'
:
'فرع شيراتون'
,
'issued_at'
:
'2024-01-15'
,
'national_id'
:
'29001010123456'
,
'phone'
:
'01012345678'
};
var
infoHtml
=
''
;
for
(
var
i
=
0
;
i
<
checked
.
length
;
i
++
)
{
var
k
=
checked
[
i
];
infoHtml
+=
'<div><strong>'
+
(
fieldLabels
[
k
]
||
k
)
+
':</strong> '
+
(
fieldValues
[
k
]
||
'—'
)
+
'</div>'
;
}
document
.
getElementById
(
'cpb-info'
).
innerHTML
=
infoHtml
;
}
// Receipt live preview
function
updateReceiptPreview
()
{
var
showWatermark
=
document
.
querySelector
(
'[name="show_watermark"][type="checkbox"]'
).
checked
;
var
showLogo
=
document
.
querySelector
(
'[name="show_logo"][type="checkbox"]'
).
checked
;
var
showFooter
=
document
.
querySelector
(
'[name="show_footer_print_info"][type="checkbox"]'
).
checked
;
var
watermarkText
=
document
.
getElementById
(
'watermark_text'
).
value
;
var
opacity
=
parseFloat
(
document
.
getElementById
(
'watermark_opacity'
).
value
);
var
headerColor
=
document
.
getElementById
(
'receipt_header_color'
).
value
;
var
wm
=
document
.
getElementById
(
'rp-watermark'
);
wm
.
style
.
display
=
showWatermark
?
''
:
'none'
;
wm
.
textContent
=
watermarkText
;
wm
.
style
.
opacity
=
opacity
;
var
logo
=
document
.
getElementById
(
'rp-logo'
);
logo
.
style
.
display
=
showLogo
?
''
:
'none'
;
var
footer
=
document
.
getElementById
(
'rp-footer'
);
footer
.
style
.
display
=
showFooter
?
''
:
'none'
;
var
header
=
document
.
getElementById
(
'rp-header'
);
header
.
style
.
borderBottomColor
=
headerColor
;
header
.
querySelector
(
'h2'
).
style
.
color
=
headerColor
;
}
// Sync color hex inputs
document
.
querySelectorAll
(
'.color-input-wrap input[type="color"]'
).
forEach
(
function
(
colorInput
)
{
colorInput
.
addEventListener
(
'input'
,
function
()
{
var
hex
=
this
.
nextElementSibling
;
if
(
hex
)
hex
.
value
=
this
.
value
;
});
});
// Initialize previews on page load
document
.
addEventListener
(
'DOMContentLoaded'
,
function
()
{
updateCarnetPreview
();
updateReceiptPreview
();
});
</script>
<?php
$__template
->
endSection
();
?>
app/Shared/Components/sidebar.php
View file @
ffa74159
...
...
@@ -6,6 +6,7 @@
use
App\Core\App
;
use
App\Core\Registries\MenuRegistry
;
use
App\Modules\Settings\Services\BrandingService
;
$currentPath
=
parse_url
(
$_SERVER
[
'REQUEST_URI'
]
??
'/'
,
PHP_URL_PATH
);
$employee
=
App
::
getInstance
()
->
currentEmployee
();
...
...
@@ -150,7 +151,12 @@ if (empty($menuItems)) {
?>
<div
class=
"sidebar-header"
>
<div
class=
"sidebar-brand"
>
THE CLUB
</div>
<?php
$brandLogo
=
BrandingService
::
logo
();
?>
<?php
if
(
$brandLogo
)
:
?>
<img
src=
"
<?=
e
(
$brandLogo
)
?>
"
alt=
"
<?=
e
(
BrandingService
::
clubNameEn
())
?>
"
class=
"sidebar-logo"
>
<?php
else
:
?>
<div
class=
"sidebar-brand"
>
<?=
e
(
BrandingService
::
clubNameEn
())
?>
</div>
<?php
endif
;
?>
</div>
<nav
class=
"sidebar-nav"
>
...
...
app/Shared/Layout/auth.php
View file @
ffa74159
<?php
use
App\Core\CSRF
;
use
App\Modules\Settings\Services\BrandingService
;
$brandLogo
=
BrandingService
::
logo
();
$brandNameAr
=
BrandingService
::
clubNameAr
();
$brandNameEn
=
BrandingService
::
clubNameEn
();
?>
<!DOCTYPE html>
<html
lang=
"ar"
dir=
"rtl"
>
...
...
@@ -269,11 +274,17 @@ use App\Core\CSRF;
<div
class=
"auth-card"
>
<div
class=
"auth-header"
>
<div
class=
"auth-logo"
>
<svg
xmlns=
"http://www.w3.org/2000/svg"
viewBox=
"0 0 24 24"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"2"
stroke-linecap=
"round"
stroke-linejoin=
"round"
><path
d=
"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"
/></svg>
</div>
<h1>
نادي النادي شيراتون
</h1>
<p>
THE CLUB Sheraton
</p>
<?php
if
(
$brandLogo
)
:
?>
<div
class=
"auth-logo"
style=
"background:transparent;box-shadow:none;"
>
<img
src=
"
<?=
e
(
$brandLogo
)
?>
"
alt=
"
<?=
e
(
$brandNameEn
)
?>
"
style=
"max-width:48px;max-height:48px;object-fit:contain;"
>
</div>
<?php
else
:
?>
<div
class=
"auth-logo"
>
<svg
xmlns=
"http://www.w3.org/2000/svg"
viewBox=
"0 0 24 24"
fill=
"none"
stroke=
"currentColor"
stroke-width=
"2"
stroke-linecap=
"round"
stroke-linejoin=
"round"
><path
d=
"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"
/></svg>
</div>
<?php
endif
;
?>
<h1>
<?=
e
(
$brandNameAr
)
?>
</h1>
<p>
<?=
e
(
$brandNameEn
)
?>
</p>
</div>
<?php
...
...
app/Shared/Layout/main.php
View file @
ffa74159
...
...
@@ -6,6 +6,7 @@
use
App\Core\App
;
use
App\Core\CSRF
;
use
App\Modules\Settings\Services\BrandingService
;
$app
=
App
::
getInstance
();
$employee
=
$app
->
currentEmployee
();
...
...
@@ -90,7 +91,7 @@ $currentPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
<!-- Footer -->
<footer
class=
"page-footer"
>
<span>
THE CLUB Sheraton
©
<?=
date
(
'Y'
)
?>
</span>
<span>
<?=
e
(
BrandingService
::
clubNameEn
())
?>
©
<?=
date
(
'Y'
)
?>
</span>
<span>
v1.0.0
</span>
<span>
<?=
arabic_date
(
date
(
'Y-m-d'
))
?>
</span>
</footer>
...
...
app/Shared/Layout/print.php
View file @
ffa74159
<?php
use
App\Modules\Settings\Services\BrandingService
;
?>
<!DOCTYPE html>
<html
lang=
"ar"
dir=
"rtl"
>
<head>
...
...
@@ -44,7 +45,11 @@
</head>
<body>
<div
class=
"print-header"
>
<h2>
نادي النادي شيراتون
</h2>
<?php
$printLogo
=
BrandingService
::
logo
();
?>
<?php
if
(
$printLogo
)
:
?>
<img
src=
"
<?=
e
(
$printLogo
)
?>
"
alt=
"Logo"
style=
"max-height:40px;margin-bottom:8px;"
>
<?php
endif
;
?>
<h2>
<?=
e
(
BrandingService
::
clubNameAr
())
?>
</h2>
<p>
<?=
arabic_date
(
today
())
?>
</p>
</div>
...
...
database/seeds/Phase_16_001_seed_branding_config.php
0 → 100644
View file @
ffa74159
<?php
declare
(
strict_types
=
1
);
use
App\Core\Database
;
return
function
(
Database
$db
)
:
void
{
$configs
=
[
[
'config_key'
=>
'branding.logo_path'
,
'config_value'
=>
''
,
'config_type'
=>
'string'
,
'group_name'
=>
'branding'
,
'description_ar'
=>
'مسار شعار النادي'
,
'description_en'
=>
'Club logo file path'
,
'is_editable'
=>
1
,
],
[
'config_key'
=>
'branding.club_name_ar'
,
'config_value'
=>
'نادي النادي شيراتون'
,
'config_type'
=>
'string'
,
'group_name'
=>
'branding'
,
'description_ar'
=>
'اسم النادي بالعربية'
,
'description_en'
=>
'Club name in Arabic'
,
'is_editable'
=>
1
,
],
[
'config_key'
=>
'branding.club_name_en'
,
'config_value'
=>
'THE CLUB Sheraton'
,
'config_type'
=>
'string'
,
'group_name'
=>
'branding'
,
'description_ar'
=>
'اسم النادي بالإنجليزية'
,
'description_en'
=>
'Club name in English'
,
'is_editable'
=>
1
,
],
[
'config_key'
=>
'branding.club_subtitle'
,
'config_value'
=>
'SPORTS CITY'
,
'config_type'
=>
'string'
,
'group_name'
=>
'branding'
,
'description_ar'
=>
'العنوان الفرعي للنادي'
,
'description_en'
=>
'Club subtitle'
,
'is_editable'
=>
1
,
],
[
'config_key'
=>
'branding.carnet_design'
,
'config_value'
=>
json_encode
([
'front_bg_color'
=>
'#0D7377'
,
'front_text_color'
=>
'#ffffff'
,
'front_show_subtitle'
=>
true
,
'front_show_english_name'
=>
true
,
'front_show_member_type'
=>
true
,
'front_show_qr'
=>
true
,
'front_qr_position'
=>
'bottom-left'
,
'front_logo_position'
=>
'top-center'
,
'back_show_branch_strip'
=>
true
,
'back_strip_color'
=>
'#0D7377'
,
'back_show_instructions'
=>
true
,
'back_instructions_text'
=>
'برجاء حمل هذه البطاقة أثناء التواجد بالنادي وتقديمها عند الطلب'
,
'back_fields'
=>
[
'full_name_ar'
,
'membership_number'
,
'branch_name'
,
'issued_at'
],
],
JSON_UNESCAPED_UNICODE
),
'config_type'
=>
'json'
,
'group_name'
=>
'branding'
,
'description_ar'
=>
'تصميم الكارنيه (وجه وخلفية)'
,
'description_en'
=>
'Carnet card design (front & back)'
,
'is_editable'
=>
1
,
],
[
'config_key'
=>
'branding.receipt_design'
,
'config_value'
=>
json_encode
([
'show_logo'
=>
true
,
'show_watermark'
=>
true
,
'watermark_text'
=>
'نادي النادي شيراتون'
,
'watermark_opacity'
=>
0.06
,
'header_color'
=>
'#0D7377'
,
'show_footer_print_info'
=>
true
,
],
JSON_UNESCAPED_UNICODE
),
'config_type'
=>
'json'
,
'group_name'
=>
'branding'
,
'description_ar'
=>
'تصميم الإيصال (شعار وعلامة مائية)'
,
'description_en'
=>
'Receipt design (logo & watermark)'
,
'is_editable'
=>
1
,
],
];
foreach
(
$configs
as
$config
)
{
$existing
=
$db
->
selectOne
(
"SELECT id FROM system_config WHERE config_key = ?"
,
[
$config
[
'config_key'
]]
);
if
(
$existing
)
{
continue
;
}
$db
->
insert
(
'system_config'
,
array_merge
(
$config
,
[
'created_at'
=>
date
(
'Y-m-d H:i:s'
),
'updated_at'
=>
date
(
'Y-m-d H:i:s'
),
]));
}
};
public/assets/css/branding.css
0 → 100644
View file @
ffa74159
/* ================================================
Branding Settings — Tabs, Designer, Previews
================================================ */
/* Tabs */
.branding-tabs
{
display
:
flex
;
gap
:
4px
;
background
:
rgba
(
255
,
255
,
255
,
0.6
);
border-radius
:
14px
;
padding
:
5px
;
margin-bottom
:
24px
;
backdrop-filter
:
blur
(
8px
);
border
:
1px
solid
#e2e8f0
;
}
.branding-tab
{
flex
:
1
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
gap
:
8px
;
padding
:
12px
16px
;
background
:
transparent
;
border
:
none
;
border-radius
:
10px
;
font-family
:
inherit
;
font-size
:
14px
;
font-weight
:
600
;
color
:
#64748b
;
cursor
:
pointer
;
transition
:
all
0.3s
cubic-bezier
(
0.16
,
1
,
0.3
,
1
);
}
.branding-tab
:hover
{
color
:
#0D7377
;
background
:
rgba
(
13
,
115
,
119
,
0.05
);
}
.branding-tab.active
{
background
:
#fff
;
color
:
#0D7377
;
box-shadow
:
0
2px
8px
rgba
(
0
,
0
,
0
,
0.08
);
}
.branding-tab
i
,
.branding-tab
svg
{
width
:
18px
;
height
:
18px
;
}
/* Tab panels */
.tab-panel
{
display
:
none
;
animation
:
tabFadeIn
0.4s
cubic-bezier
(
0.16
,
1
,
0.3
,
1
);
}
.tab-panel.active
{
display
:
block
;
}
@keyframes
tabFadeIn
{
from
{
opacity
:
0
;
transform
:
translateY
(
8px
);
}
to
{
opacity
:
1
;
transform
:
translateY
(
0
);
}
}
/* Branding grid (identity tab) */
.branding-grid
{
display
:
grid
;
grid-template-columns
:
1
fr
1
fr
;
gap
:
24px
;
}
@media
(
max-width
:
768px
)
{
.branding-grid
{
grid-template-columns
:
1
fr
;
}
}
/* Branding card */
.branding-card
{
border-radius
:
16px
;
overflow
:
hidden
;
}
.branding-card
.card-header
{
padding
:
16px
20px
;
border-bottom
:
1px
solid
#e2e8f0
;
background
:
#f8fafc
;
}
.branding-card
.card-header
h3
{
margin
:
0
;
font-size
:
15px
;
font-weight
:
700
;
color
:
#0f172a
;
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
}
.branding-card
.card-header
h3
i
,
.branding-card
.card-header
h3
svg
{
width
:
18px
;
height
:
18px
;
color
:
#0D7377
;
}
.branding-card
.card-body
{
padding
:
20px
;
}
/* Logo upload area */
.logo-upload-area
{
min-height
:
200px
;
}
.logo-placeholder
{
border
:
2px
dashed
#cbd5e1
;
border-radius
:
12px
;
padding
:
40px
20px
;
text-align
:
center
;
cursor
:
pointer
;
transition
:
all
0.3s
ease
;
color
:
#94a3b8
;
}
.logo-placeholder
:hover
{
border-color
:
#0D7377
;
background
:
rgba
(
13
,
115
,
119
,
0.03
);
color
:
#0D7377
;
}
.logo-placeholder
i
,
.logo-placeholder
svg
{
width
:
48px
;
height
:
48px
;
margin-bottom
:
12px
;
opacity
:
0.5
;
}
.logo-placeholder
p
{
margin
:
0
0
4px
;
font-size
:
14px
;
font-weight
:
600
;
}
.logo-placeholder
small
{
font-size
:
12px
;
opacity
:
0.7
;
}
.logo-preview
{
position
:
relative
;
text-align
:
center
;
border
:
2px
solid
#e2e8f0
;
border-radius
:
12px
;
padding
:
24px
;
background
:
#f8fafc
;
min-height
:
200px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
.logo-preview
img
{
max-width
:
200px
;
max-height
:
150px
;
object-fit
:
contain
;
}
.logo-preview-overlay
{
position
:
absolute
;
inset
:
0
;
background
:
rgba
(
0
,
0
,
0
,
0.5
);
border-radius
:
10px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
opacity
:
0
;
transition
:
opacity
0.25s
ease
;
}
.logo-preview
:hover
.logo-preview-overlay
{
opacity
:
1
;
}
/* Form actions */
.form-actions
{
margin-top
:
24px
;
display
:
flex
;
justify-content
:
flex-start
;
}
/* Color input */
.color-input-wrap
{
display
:
flex
;
align-items
:
center
;
gap
:
10px
;
}
.color-input-wrap
input
[
type
=
"color"
]
{
width
:
42px
;
height
:
42px
;
border
:
2px
solid
#e2e8f0
;
border-radius
:
10px
;
padding
:
3px
;
cursor
:
pointer
;
background
:
#fff
;
transition
:
border-color
0.2s
;
}
.color-input-wrap
input
[
type
=
"color"
]
:hover
{
border-color
:
#0D7377
;
}
.color-hex
{
width
:
100px
;
font-family
:
'Courier New'
,
monospace
;
font-size
:
13px
;
text-align
:
center
;
}
/* Toggle switches */
.toggle-group
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
margin-bottom
:
16px
;
}
.toggle-label
{
display
:
flex
;
align-items
:
center
;
gap
:
10px
;
cursor
:
pointer
;
font-size
:
13px
;
font-weight
:
500
;
color
:
#334155
;
}
.toggle-label
input
[
type
=
"checkbox"
]
{
display
:
none
;
}
.toggle-switch
{
position
:
relative
;
width
:
40px
;
height
:
22px
;
background
:
#cbd5e1
;
border-radius
:
11px
;
flex-shrink
:
0
;
transition
:
background
0.3s
ease
;
}
.toggle-switch
::after
{
content
:
''
;
position
:
absolute
;
top
:
2px
;
left
:
2px
;
width
:
18px
;
height
:
18px
;
background
:
#fff
;
border-radius
:
50%
;
transition
:
transform
0.3s
cubic-bezier
(
0.16
,
1
,
0.3
,
1
);
box-shadow
:
0
1px
3px
rgba
(
0
,
0
,
0
,
0.15
);
}
.toggle-label
input
[
type
=
"checkbox"
]
:checked
+
.toggle-switch
{
background
:
#0D7377
;
}
.toggle-label
input
[
type
=
"checkbox"
]
:checked
+
.toggle-switch
::after
{
transform
:
translateX
(
18px
);
}
/* Checkbox grid */
.checkbox-grid
{
display
:
grid
;
grid-template-columns
:
1
fr
1
fr
;
gap
:
8px
;
}
.checkbox-label
{
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
cursor
:
pointer
;
font-size
:
13px
;
color
:
#334155
;
padding
:
6px
8px
;
border-radius
:
8px
;
transition
:
background
0.2s
;
}
.checkbox-label
:hover
{
background
:
#f1f5f9
;
}
.checkbox-label
input
[
type
=
"checkbox"
]
{
display
:
none
;
}
.checkbox-box
{
width
:
18px
;
height
:
18px
;
border
:
2px
solid
#cbd5e1
;
border-radius
:
5px
;
flex-shrink
:
0
;
position
:
relative
;
transition
:
all
0.2s
;
}
.checkbox-label
input
[
type
=
"checkbox"
]
:checked
+
.checkbox-box
{
background
:
#0D7377
;
border-color
:
#0D7377
;
}
.checkbox-label
input
[
type
=
"checkbox"
]
:checked
+
.checkbox-box
::after
{
content
:
''
;
position
:
absolute
;
top
:
1px
;
left
:
5px
;
width
:
5px
;
height
:
9px
;
border
:
solid
#fff
;
border-width
:
0
2px
2px
0
;
transform
:
rotate
(
45deg
);
}
/* Form row */
.form-row
{
display
:
grid
;
grid-template-columns
:
1
fr
1
fr
;
gap
:
16px
;
}
/* Form range */
.form-range
{
width
:
100%
;
height
:
6px
;
border-radius
:
3px
;
background
:
#e2e8f0
;
outline
:
none
;
-webkit-appearance
:
none
;
appearance
:
none
;
margin-top
:
8px
;
}
.form-range
::-webkit-slider-thumb
{
-webkit-appearance
:
none
;
width
:
20px
;
height
:
20px
;
border-radius
:
50%
;
background
:
#0D7377
;
cursor
:
pointer
;
box-shadow
:
0
2px
6px
rgba
(
13
,
115
,
119
,
0.3
);
transition
:
transform
0.2s
;
}
.form-range
::-webkit-slider-thumb:hover
{
transform
:
scale
(
1.15
);
}
.form-range
::-moz-range-thumb
{
width
:
20px
;
height
:
20px
;
border-radius
:
50%
;
background
:
#0D7377
;
border
:
none
;
cursor
:
pointer
;
}
/* ================================================
Designer Layout (side-by-side)
================================================ */
.designer-layout
{
display
:
grid
;
grid-template-columns
:
1
fr
380px
;
gap
:
24px
;
align-items
:
start
;
}
@media
(
max-width
:
1024px
)
{
.designer-layout
{
grid-template-columns
:
1
fr
;
}
}
.designer-settings
{
display
:
flex
;
flex-direction
:
column
;
gap
:
20px
;
}
.designer-preview
{
position
:
relative
;
}
.preview-sticky
{
position
:
sticky
;
top
:
80px
;
}
.preview-title
{
font-size
:
14px
;
font-weight
:
700
;
color
:
#475569
;
margin
:
0
0
12px
;
display
:
flex
;
align-items
:
center
;
gap
:
6px
;
}
.preview-title
i
,
.preview-title
svg
{
width
:
16px
;
height
:
16px
;
color
:
#0D7377
;
}
.preview-label
{
font-size
:
12px
;
font-weight
:
600
;
color
:
#94a3b8
;
text-transform
:
uppercase
;
letter-spacing
:
1px
;
margin-bottom
:
8px
;
}
/* ================================================
Carnet Preview (front & back)
================================================ */
.carnet-preview-front
{
width
:
340px
;
height
:
215px
;
background
:
<?=
e
(
$
cd
[
'front_bg_color'
])
?>
;
color
:
<?=
e
(
$
cd
[
'front_text_color'
])
?>
;
border-radius
:
12px
;
padding
:
16px
18px
;
position
:
relative
;
overflow
:
hidden
;
font-family
:
'Cairo'
,
sans-serif
;
box-shadow
:
0
8px
24px
rgba
(
0
,
0
,
0
,
0.15
);
transition
:
background
0.3s
,
color
0.3s
;
}
.cpf-club-name
{
text-align
:
center
;
font-size
:
13px
;
font-weight
:
700
;
margin-bottom
:
2px
;
}
.cpf-subtitle
{
text-align
:
center
;
font-size
:
10px
;
opacity
:
0.75
;
margin-bottom
:
12px
;
}
.cpf-member-name
{
font-size
:
13px
;
font-weight
:
700
;
margin-bottom
:
3px
;
}
.cpf-member-name-en
{
font-size
:
10px
;
opacity
:
0.8
;
margin-bottom
:
6px
;
}
.cpf-member-number
{
font-size
:
18px
;
font-weight
:
700
;
letter-spacing
:
2px
;
direction
:
ltr
;
text-align
:
right
;
}
.cpf-member-type
{
font-size
:
10px
;
margin-top
:
4px
;
opacity
:
0.8
;
}
.cpf-qr
{
position
:
absolute
;
bottom
:
10px
;
left
:
10px
;
width
:
55px
;
height
:
55px
;
background
:
#fff
;
border-radius
:
6px
;
padding
:
5px
;
transition
:
all
0.3s
;
}
.cpf-qr
svg
{
width
:
100%
;
height
:
100%
;
}
.cpf-carnet-num
{
position
:
absolute
;
bottom
:
10px
;
right
:
14px
;
font-size
:
9px
;
opacity
:
0.6
;
}
/* Carnet back */
.carnet-preview-back
{
width
:
340px
;
height
:
215px
;
background
:
#fff
;
border-radius
:
12px
;
border
:
2px
solid
#e2e8f0
;
padding
:
14px
14px
14px
14px
;
position
:
relative
;
font-family
:
'Cairo'
,
sans-serif
;
box-shadow
:
0
8px
24px
rgba
(
0
,
0
,
0
,
0.08
);
overflow
:
hidden
;
}
.cpb-branch-strip
{
position
:
absolute
;
left
:
0
;
top
:
0
;
bottom
:
0
;
width
:
28px
;
background
:
<?=
e
(
$
cd
[
'back_strip_color'
])
?>
;
border-radius
:
12px
0
0
12px
;
writing-mode
:
vertical-rl
;
text-orientation
:
mixed
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
color
:
#fff
;
font-size
:
10px
;
font-weight
:
600
;
transition
:
background
0.3s
;
}
.cpb-header
{
margin-right
:
20px
;
margin-bottom
:
10px
;
padding-bottom
:
8px
;
border-bottom
:
1.5px
solid
#e2e8f0
;
}
.cpb-logo-text
{
font-size
:
10px
;
font-weight
:
700
;
color
:
#0D7377
;
line-height
:
1.4
;
}
.cpb-info
{
margin-right
:
20px
;
font-size
:
11px
;
color
:
#1a1a2e
;
}
.cpb-info
div
{
margin-bottom
:
2px
;
}
.cpb-info
strong
{
color
:
#0D7377
;
}
.cpb-instructions
{
position
:
absolute
;
bottom
:
8px
;
left
:
35px
;
right
:
10px
;
font-size
:
9px
;
color
:
#94a3b8
;
text-align
:
center
;
}
/* ================================================
Receipt Preview
================================================ */
.receipt-preview
{
background
:
#fff
;
border
:
1px
solid
#e2e8f0
;
border-radius
:
12px
;
padding
:
24px
20px
;
position
:
relative
;
overflow
:
hidden
;
font-family
:
'Cairo'
,
sans-serif
;
box-shadow
:
0
8px
24px
rgba
(
0
,
0
,
0
,
0.08
);
direction
:
rtl
;
min-height
:
400px
;
}
.rp-watermark
{
position
:
absolute
;
top
:
50%
;
left
:
50%
;
transform
:
translate
(
-50%
,
-50%
)
rotate
(
-35deg
);
font-size
:
48px
;
font-weight
:
900
;
color
:
#0D7377
;
opacity
:
<?=
e
(
$
rd
[
'watermark_opacity'
])
?>
;
white-space
:
nowrap
;
pointer-events
:
none
;
z-index
:
0
;
transition
:
opacity
0.3s
;
}
.rp-logo
{
text-align
:
center
;
margin-bottom
:
12px
;
position
:
relative
;
z-index
:
1
;
}
.rp-logo
img
{
max-height
:
40px
;
object-fit
:
contain
;
}
.rp-header
{
text-align
:
center
;
margin-bottom
:
20px
;
padding-bottom
:
12px
;
border-bottom
:
2px
solid
<?=
e
(
$
rd
[
'header_color'
])
?>
;
position
:
relative
;
z-index
:
1
;
transition
:
border-color
0.3s
;
}
.rp-header
h2
{
margin
:
0
;
font-size
:
16px
;
font-weight
:
800
;
color
:
<?=
e
(
$
rd
[
'header_color'
])
?>
;
transition
:
color
0.3s
;
}
.rp-header
p
{
margin
:
2px
0
0
;
font-size
:
11px
;
color
:
#94a3b8
;
}
.rp-header
h3
{
margin
:
10px
0
0
;
font-size
:
14px
;
color
:
#0f172a
;
}
.rp-body
{
position
:
relative
;
z-index
:
1
;
}
.rp-body
table
{
width
:
100%
;
border-collapse
:
collapse
;
font-size
:
12px
;
}
.rp-body
td
{
padding
:
8px
10px
;
border
:
1px
solid
#e2e8f0
;
}
.rp-label
{
background
:
#f8fafc
;
font-weight
:
600
;
width
:
35%
;
color
:
#475569
;
}
.rp-value
{
color
:
#0f172a
;
}
.rp-amount
{
font-size
:
16px
;
font-weight
:
700
;
color
:
#0D7377
;
direction
:
ltr
;
text-align
:
right
;
}
.rp-signatures
{
display
:
grid
;
grid-template-columns
:
1
fr
1
fr
1
fr
;
gap
:
20px
;
margin-top
:
30px
;
text-align
:
center
;
font-size
:
11px
;
color
:
#64748b
;
position
:
relative
;
z-index
:
1
;
}
.rp-signatures
>
div
{
border-top
:
1px
solid
#000
;
padding-top
:
8px
;
}
.rp-footer
{
margin-top
:
20px
;
text-align
:
center
;
font-size
:
10px
;
color
:
#94a3b8
;
position
:
relative
;
z-index
:
1
;
}
/* ================================================
Responsive
================================================ */
@media
(
max-width
:
640px
)
{
.branding-tab
span
{
display
:
none
;
}
.branding-tabs
{
justify-content
:
center
;
}
.branding-tab
{
flex
:
0
;
padding
:
12px
20px
;
}
.form-row
{
grid-template-columns
:
1
fr
;
}
.checkbox-grid
{
grid-template-columns
:
1
fr
;
}
.carnet-preview-front
,
.carnet-preview-back
{
width
:
100%
;
max-width
:
340px
;
}
}
public/assets/css/main.css
View file @
ffa74159
...
...
@@ -202,6 +202,17 @@ code {
background-clip
:
text
;
}
.sidebar-logo
{
max-height
:
36px
;
max-width
:
180px
;
object-fit
:
contain
;
transition
:
opacity
var
(
--duration-normal
)
ease
;
}
.sidebar-logo
:hover
{
opacity
:
0.85
;
}
.sidebar-toggle
{
background
:
none
;
border
:
none
;
...
...
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