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
Expand all
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
This diff is collapsed.
Click to expand it.
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
This diff is collapsed.
Click to expand it.
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
This diff is collapsed.
Click to expand it.
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