Compare commits

..

153 Commits

Author SHA1 Message Date
Mazdak Gibran 447d15cef6 logging.html style improvement
Wrapped date input fields in a new container and added FontAwesome calendar icons for better UI clarity. Updated SCSS to style the date input wrappers and position the icons appropriately.
1 week ago
Naeem Ullah 6f5b56b61e Merge pull request 'Update logging.component.html' (#54) from mazdak/UX-2361 into dev-pending-20-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/54
1 week ago
Mazdak Gibran 0356224023 Update logging.component.html 1 week ago
Naeem Ullah d03c117008 Merge pull request 'removed superadmin from role options' (#53) from mazdak/UX-2026 into dev-pending-20-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/53
1 week ago
Mazdak Gibran 9ff7b72b1d removed superadmin from role options
removed superadmin from role options
1 week ago
Naeem Ullah 510d7ecc71 Merge pull request 'permission manager dropdown fixed' (#52) from mazdak/UX-2349 into dev-pending-20-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/52
1 week ago
Mazdak Gibran 705523a09f permission manager dropdown fixed
permission manager dropdown fixed
1 week ago
Naeem Ullah 4df1c52b39 Merge pull request 'Enhance reset password flow and add permission control' (#51) from mazdak/UX-2338 into dev-pending-20-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/51
1 week ago
Mazdak Gibran 79d258b0cf Enhance reset password flow and add permission control
Replaced user ID input with a dropdown in the reset password form and loaded users dynamically. Added 'resetPasswordButton' permission to side menu and conditionally displayed the reset password button
1 week ago
Naeem Ullah 50f1005fd5 Merge pull request 'Add reset password modal to user management' (#50) from mazdak/UX-2303 into dev-pending-20-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/50
1 week ago
Mazdak Gibran 3b70e23da0 Add reset password modal to user management
Introduced a new ResetPasswordModalComponent for resetting user passwords from the setup user page. Updated the setup-user component and template to include a reset password button and modal, and refactored password-hide-show to support resetting its state. Also adjusted reset-password component to enable userId input.
1 week ago
Naeem Ullah 1ddb35bbac Add SuperAdmin role support to user management
Introduces the SUPER_ADMIN role in enums, updates admin check logic to include SuperAdmin, and adds SuperAdmin to the role options in the setup user component.
1 week ago
Naeem Ullah 356a159df5 Merge pull request 'show activity log for user' (#49) from mazdak/UX-2250 into dev-pending-20-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/49
1 week ago
Mazdak Gibran b532a882a0 show activity log for user
show activity log for user after permission
1 week ago
Naeem Ullah 3b4881ba32 Merge pull request 'transaction logs' (#48) from mazdak/UX-2136 into dev-pending-20-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/48
2 weeks ago
Mazdak Gibran c78b500d61 transaction logs
added transactionCode and transactionUri to the transaaction logs  table
2 weeks ago
Naeem Ullah 6bc21d59a5 Merge pull request 'arabic right to left alignment' (#47) from mazdak/UX-2135 into dev-pending-20-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/47
2 weeks ago
Mazdak Gibran 05c8508d30 arabic right to left alignment
arabic right to left alignment,
2 weeks ago
Naeem Ullah 3be9099daa Merge pull request 'logger manager to activity logs' (#46) from mazdak/UX-2219 into dev-pending-20-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/46
2 weeks ago
Mazdak Gibran 258f1d3e8c logger manager to activity logs
changed logger manager to activity logs, disabled future dates, fixed pagination buttons
2 weeks ago
Naeem Ullah fadc5990f6 Merge branch 'dev-pending-20-01-2026' of https://ct.mfsys.com.pk/aConnect/aConnect-UX into dev-pending-20-01-2026 2 weeks ago
Naeem Ullah 0329da50a3 Merge pull request 'user permission fix' (#45) from mazdak/UX-2123 into dev-pending-20-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/45
2 weeks ago
Naeem Ullah 879ed17c49 Enhance app initialization and storage service
Refactored AppComponent initialization to set default language and direction, handle user authentication state, and redirect accordingly. Added removeItem method to StorageService for item removal. Also removed an unused import from authentication.guard.ts.
2 weeks ago
Naeem Ullah 6df0195ed8 Refactor logging component UI and logic
Simplified and modernized the logging.component.html layout, replacing nested cards and redundant markup with a cleaner structure. Refactored logging.component.ts to improve data flow: search, filtering, and pagination are now handled in the component instead of the template pipe. Updated page size options in app.constants.ts for better usability. Minor cleanup in setup-user.component.ts.
2 weeks ago
Mazdak Gibran 67b7341f50 user permission fix
user permission fix internal server error
2 weeks ago
Naeem Ullah efbf56adbc Update password change flow and date validator logic
Changed the change password API endpoint to use FIRST_LOGIN_URI instead of CHANGE_PASSWORD_URI. Added FIRST_LOGIN_URI to URI enums and app.uri.json. Improved the toDateAfterFromDateValidator to normalize dates, enforce fromDate < toDate, and disallow future dates.
2 weeks ago
Naeem Ullah 772653b51c Merge branch 'dev-pending-20-01-2026-naeem' into dev-pending-20-01-2026 2 weeks ago
Naeem Ullah 69cb52694e Enhance transaction logs and user permissions UI
Added new fields (crPcaglacode, drPcaGlacode, amount, paymentMode) to the TransactionLog model and updated the transaction logs table to display these fields. Improved user permissions UI with better styling, scrollable tables, and enhanced ng-select options. Updated user setup to use a more descriptive placeholder and added a confirmation dialog for user deletion. Also improved i18n translations for new fields and cleaned up validation and error messages.
2 weeks ago
Naeem Ullah 8876b9fce7 Merge pull request 'first time login' (#44) from mazdak/UX-2114 into dev-pending-20-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/44
2 weeks ago
Mazdak Gibran d94e715957 first time login
first time login logic and validations,
2 weeks ago
Naeem Ullah fd1b5b1607 Merge pull request 'user management bugs fixed' (#43) from mazdak/UX-2147 into dev-pending-20-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/43
2 weeks ago
Mazdak Gibran f3685ba942 user management bugs fixed
bugs resolved:
Setup User: No validation applied; the form saves with empty inputs. User ID length validation is not implemented. The role column is not displayed in the table.

Reset Password: No success message is shown after password update. Strong password validation is not applied.

Change Password: No success message is shown after password update. Strong password validation is not applied. Validation to prevent using the same value for old and new passwords is not implemented.
2 weeks ago
Naeem Ullah 45b34eef3f Merge pull request 'mazdak/UX-2151' (#42) from mazdak/UX-2151 into dev-pending-20-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/42
2 weeks ago
Mazdak Gibran 2cb37101de show error message for wrong password and Id
Error message shown on wrong password and ID,
Change the browser Title from ACONNECTUX TO aConnect
2 weeks ago
Naeem Ullah ab3b9f581a Merge pull request 'hide buttons' (#41) from mazdak/UX-2141 into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/41
2 weeks ago
Naeem Ullah 972f3cd2de Merge pull request 'add success message' (#40) from mazdak/UX-2073 into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/40
2 weeks ago
Mazdak Gibran 04adbef474 hide buttons
hide third party registration, IB support and Sms banking
2 weeks ago
Mazdak Gibran 150fb0f88b add success message
add success message to change password and reset password, and fixed the routing in change password.
2 weeks ago
Naeem Ullah 44e012b1aa 1mb
1mb
3 weeks ago
Naeem Ullah d5fd9888ee 2026-01-16
2026-01-16
3 weeks ago
Naeem Ullah e122f8bf9e Merge pull request 'live test' (#39) from mazdak/UX-live-test into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/39
3 weeks ago
Mazdak Gibran 0b013393d5 Update side-nav.component.html 3 weeks ago
Naeem Ullah d0b8d7f374 Update environment.test.ts 3 weeks ago
Naeem Ullah 6e459179ce Merge pull request 'mazdak/UX-fix-changePassword' (#38) from mazdak/UX-fix-changePassword into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/38
3 weeks ago
Naeem Ullah 819e11b3d3 added profiler 3 weeks ago
Mazdak Gibran 3ac1eb2bd9 show username in header 3 weeks ago
Mazdak Gibran aa3b22c65f Update change-password.component.html 3 weeks ago
Naeem Ullah cbe3b93043 Merge pull request 'fixed small bugs and adjusted styling' (#37) from mazdak/UX-styling into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/37
3 weeks ago
Mazdak Gibran 6252fed4f4 delete pipes 3 weeks ago
Mazdak Gibran 9130b2db6b fixed small bugs and adjusted styling
fixed small bugs and adjusted styling
3 weeks ago
Naeem Ullah 39635ada54 Merge pull request 'transaction-logs updated' (#36) from mazdak/UX-2025 into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/36
3 weeks ago
Mazdak Gibran 8dd969fbf6 transaction-logs updated
transaction-logs updated, removed extra service file and matched architecture with other components
3 weeks ago
Naeem Ullah 66c51a9e70 Merge pull request 'setup-user updated' (#35) from mazdak/UX-1985 into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/35
3 weeks ago
Mazdak Gibran 30641db59e setup-user updated
setup-user updated to match architecture of other components.
3 weeks ago
Naeem Ullah bcf4b208cc Merge pull request 'setup user email' (#34) from mazdak/setup-user-email into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/34
3 weeks ago
Mazdak Gibran 314e899394 setup user email
setup user email, fixing bugs, pagination and added search filter for transaction logs
3 weeks ago
Naeem Ullah c7c1a10dbb Merge pull request 'added export to excel functionality in transaction logs' (#33) from aconnect-UX/1962 into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/33
3 weeks ago
atif118-mfsys 2dd260fc51 added export to excel functionality in transaction logs 3 weeks ago
Naeem Ullah 60b0ceddf9 Merge pull request 'added export data in excel and added som validations in logger manager screen' (#32) from aconnect-UX/1942 into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/32
3 weeks ago
atif118-mfsys 58cc256169 added export data in excel and added som validations in logger manager screen 3 weeks ago
Naeem Ullah 273d3e98be Merge pull request 'aconnect-UX/1576' (#31) from aconnect-UX/1576 into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/31
3 weeks ago
atif118-mfsys bf8ac0b2ed Merge branch 'dev-pending-01-01-2026' into aconnect-UX/1576 3 weeks ago
atif118-mfsys e3bd2193b8 integrated GET call in logger manager to get logging details 3 weeks ago
Naeem Ullah 6aed9f6b42 Merge pull request 'adding fields to user setup' (#30) from mazdak/UX-1903 into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/30
3 weeks ago
Mazdak Gibran 1aacd37b80 styling adjustments 3 weeks ago
Mazdak Gibran 52c51c9ed1 css adjustment
styling sms banking
4 weeks ago
Mazdak Gibran 0573a880ff adjust styling 4 weeks ago
Mazdak Gibran 82a032affd adding fields to user setup
adding fields to user setup  and error handling
4 weeks ago
Naeem Ullah a57de088ff Merge pull request 'aconnect-UX/1876' (#29) from aconnect-UX/1876 into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/29
4 weeks ago
atif118-mfsys 82d9060b4d Merge branch 'dev-pending-01-01-2026' into aconnect-UX/1876 4 weeks ago
atif118-mfsys 274659a429 added button level permissions on edit and delete buttons 4 weeks ago
Naeem Ullah 162662f81f Merge pull request 'transaction log Api integration' (#28) from mazdak/UX-1887 into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/28
4 weeks ago
Mazdak Gibran 921b28f27b transaction log Api integration
transaction log Api integration and showing the data in tabular form.
4 weeks ago
Naeem Ullah c6b660a80e Merge pull request 'reset user password api' (#27) from mazdak/UX-1861 into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/27
4 weeks ago
Mazdak Gibran 37f0489f05 Update setup-user.component.ts 4 weeks ago
Mazdak Gibran 0163c1c469 fixing setup-user component
added porOrgacode to payload for createUser
4 weeks ago
Mazdak Gibran 5955ad90a5 reset user password api
reset user password api integration
4 weeks ago
Naeem Ullah 2756a3faa5 Merge pull request 'third part registration' (#24) from mazdak/UX-1814 into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/24
4 weeks ago
Mazdak Gibran 1126264d7a Merge branch 'dev-pending-01-01-2026' of https://ct.mfsys.com.pk/aConnect/aConnect-UX into mazdak/UX-1814 4 weeks ago
Naeem Ullah 38830228c2 Merge pull request 'change password api' (#26) from mazdak/UX-1852 into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/26
4 weeks ago
Mazdak Gibran 50e3dfec72 change password api
change password api integration
4 weeks ago
Naeem Ullah 24d3c810c3 Merge pull request 'revamped the code and design for permission management screen' (#25) from aconnect-UX/1826 into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/25
4 weeks ago
atif118-mfsys d872c5fd65 revamped the code and design for permission management screen 4 weeks ago
Mazdak Gibran 0efdedca6e third part registration
third part registration reactive form with validations and send payload
4 weeks ago
Naeem Ullah ee6138d555 Merge pull request 'aconnect-UX/1765' (#23) from aconnect-UX/1765 into dev-pending-01-01-2026
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/23
4 weeks ago
atif118-mfsys 9d48e0acf2 pushing missing code 4 weeks ago
Mazdak Gibran 33e0c86a1f change password screen fix
change password screen fix for first time login
4 weeks ago
atif118-mfsys db1eba9c54 Merge branch 'dev-pending-09-12-2025' into aconnect-UX/1765 4 weeks ago
atif118-mfsys c278b0f89c implemented permissions checks to prevent users accessing the content they do not have permission for. 4 weeks ago
Naeem Ullah 822f4860d9 Merge pull request 'creating payload for reset and change password' (#22) from mazdak/UX-1570 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/22
1 month ago
Mazdak Gibran c57bc708d4 Merge branch 'dev-pending-09-12-2025' of https://ct.mfsys.com.pk/aConnect/aConnect-UX into mazdak/UX-1570 1 month ago
Naeem Ullah 321e157033 Merge pull request 'integrated the API to save permissions for a specific user' (#21) from aconnect-UX/1717 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/21
1 month ago
Mazdak Gibran ba5ca98da9 removed unused imports 1 month ago
Mazdak Gibran 61df1a4ac2 creating payload for reset and change password
creating payload for reset and change password before the apis are ready
1 month ago
Naeem Ullah aacb190702 Merge pull request 'mazdak/UX-1729' (#20) from mazdak/UX-1729 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/20
1 month ago
atif118-mfsys 7ba6a8b65f integrated the API to save permissions for a specific user 1 month ago
Mazdak Gibran 25d1e49475 add missing translation
add missing translation
1 month ago
Mazdak Gibran 35c23badcc updated the form with Reactive Forms
updated the form with Reactive Forms
1 month ago
Naeem Ullah 1867e11502 Merge pull request 'integrated get permission for a specific user API' (#19) from aconnect-UX/1716 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/19
1 month ago
atif118-mfsys 23d65b82d2 integrated get permission for a specific user API 1 month ago
Naeem Ullah 7788668973 Merge pull request 'aconnect-UX/1715' (#18) from aconnect-UX/1715 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/18
1 month ago
atif118-mfsys 180ecd38a1 Merge branch 'dev-pending-09-12-2025' into aconnect-UX/1715 1 month ago
atif118-mfsys dee23848ea integrated get all users API to provide options for user dropdown in permission management 1 month ago
Naeem Ullah 4d090dfff7 Merge pull request 'mazdak/UX-1709' (#17) from mazdak/UX-1709 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/17
1 month ago
Mazdak Gibran 80a1e62411 onDelete form reset
onDelete form reset
1 month ago
Mazdak Gibran 207e55cfe3 Merge branch 'dev-pending-09-12-2025' of https://ct.mfsys.com.pk/aConnect/aConnect-UX into mazdak/UX-1709 1 month ago
Naeem Ullah c93f231dfd Merge pull request 'aconnect-UX/1653' (#14) from aconnect-UX/1653 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/14
1 month ago
Mazdak Gibran 1976648c08 Integrate deleteUser Api
Integrate deleteUser Api
1 month ago
Mazdak Gibran d703494c3a Implemented getUser Api
implemented getUser Api to show the view and edit of the selected Id in the form. later removed the edit function
1 month ago
Mazdak Gibran 2b98437f15 change password for first login fixed
change password for first login fixed
1 month ago
Mazdak Gibran bc18bcf946 Setup User API Integration
createUser and getAllUsers API Integration
1 month ago
atif118-mfsys 9ccda5bc86 applied optional chaining to avoid errors. 1 month ago
atif118-mfsys db1e57a008 completed design for permission management screen. 1 month ago
Naeem Ullah 6f2843b805 Merge pull request 'GBRSP/UX-1599' (#13) from GBRSP/UX-1599 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/13
1 month ago
atif118-mfsys 966f0d59bc fixed and revamped first time change password screen logic 1 month ago
atif118-mfsys 72d5d08828 Merge branch 'dev-pending-09-12-2025' into GBRSP/UX-1599 1 month ago
atif118-mfsys 1c0b19e33c added activity guard and added some translations 1 month ago
atif118-mfsys 8340f14035 working on setting up http services as per standard 1 month ago
Naeem Ullah a06a2b6004 Merge pull request 'Change Password Screen view for first time login' (#12) from mazdak/UX-1461 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/12
1 month ago
Mazdak Gibran 7fa0ee6a76 Change Password Screen view for first time login
Show different change password screen view for first time login
1 month ago
Naeem Ullah 9a10b8d37f Merge pull request 'login Api integration' (#11) from mazdak/UX-1381 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/11
2 months ago
Mazdak Gibran 77282eeca7 login api integration
integrating login API with aConnect backend
2 months ago
Naeem Ullah f93372a5ca Merge pull request 'mazdak/UX-1387' (#10) from mazdak/UX-1387 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/10
2 months ago
Mazdak Gibran a6a38d068e sms gateway screen
sms gateway screen dynamically renders different forms
2 months ago
Mazdak Gibran c257ed7e6f SMS Gateway
design implementation of SMS Gateway
2 months ago
Naeem Ullah f7be4f6c7b Merge pull request 'Configure Transaction Purpose screen' (#9) from mazdak/UX-1373 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/9
2 months ago
Mazdak Gibran 7dacf6f9c8 Configure Transaction Purpose screen
Configure Transaction Purpose screen
2 months ago
Naeem Ullah 0cc25afccd Merge pull request 'mazdak/UX-1350' (#8) from mazdak/UX-1350 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/8
2 months ago
Naeem Ullah 3c3f1b5ee0 Merge pull request 'mazdak/UX-1320' (#7) from mazdak/UX-1320 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/7
2 months ago
Mazdak Gibran b0f8311a2c feedback setup update
feedback setup update
2 months ago
Mazdak Gibran 7228790cc8 Update feedback-setup.component.html 2 months ago
Mazdak Gibran 83dc8b5c7f feedback setup screen
feedback setup screen
2 months ago
Mazdak Gibran ebde11ad65 Update ib-unblock-user.component.ts 2 months ago
Mazdak Gibran e1ec631421 Update ib-unblock-user.component.html 2 months ago
Mazdak Gibran bf3fe98e63 UnBlock IB User screen
design implementation of UnBlock IB User
2 months ago
Naeem Ullah cda611495b Merge pull request 'SMS Logger screen' (#6) from mazdak/UX-1310 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/6
2 months ago
Mazdak Gibran c4ccfaf24a SMS Logger screen
SMS Logger screen
2 months ago
Naeem Ullah 6d88ae8767 Merge pull request 'Logger Manager Screen design' (#5) from mazdak/UX-1272 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/5
2 months ago
Mazdak Gibran 88ffd7a35b Logger Manager Screen design
Logger Manager Screen design
2 months ago
Mubashar Hussain 084d17e6ad Merge pull request 'mazdak/UX-1213' (#4) from mazdak/UX-1213 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/4
2 months ago
Mazdak Gibran 71c767c684 setup user design
setup user design: fixed camel case
2 months ago
Mazdak Gibran d0f3d28458 Merge branch 'dev-pending-09-12-2025' into mazdak/UX-1213 2 months ago
Mazdak Gibran bf3df61c9d Setup user
setup user
2 months ago
Naeem Ullah 65aded50be Merge pull request 'reset user password design' (#3) from mazdak/UX-1202 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/3
2 months ago
Mazdak Gibran 9ee1c0f4e2 update reset-passwords
update reset-passwords.ts
2 months ago
Mazdak Gibran b6fe4e0398 reset user password design
reset user password design
2 months ago
Naeem Ullah 5e38a86049 Merge pull request 'mazdak/UX-1133' (#2) from mazdak/UX-1133 into dev-pending-09-12-2025
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/2
2 months ago
Mazdak Gibran 36904b68fd update change password
update change password
2 months ago
Mazdak Gibran 8eaf5948ab Merge branch 'FMFI-PRE-PRODUCTION' into mazdak/UX-1133 2 months ago
Mazdak Gibran edfea86870 change password
change password design
2 months ago
Naeem Ullah 3fbe09a4ee Merge pull request 'added design for third party registration screen' (#1) from atif/UX-1125 into FMFI-PRE-PRODUCTION
Reviewed-on: https://ct.mfsys.com.pk/aConnect/aConnect-UX/pulls/1
2 months ago
atif118-mfsys 383772db87 added design for third party registration screen 2 months ago
atif118-mfsys d59d64987c initial aConnect commit. 2 months ago

@ -0,0 +1,17 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
ij_typescript_use_double_quotes = false
[*.md]
max_line_length = off
trim_trailing_whitespace = false

43
.gitignore vendored

@ -0,0 +1,43 @@
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
package-lock.json
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db

@ -0,0 +1,4 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": ["angular.ng-template"]
}

@ -0,0 +1,20 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ng serve",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: start",
"url": "http://localhost:4200/"
},
{
"name": "ng test",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: test",
"url": "http://localhost:9876/debug.html"
}
]
}

42
.vscode/tasks.json vendored

@ -0,0 +1,42 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
},
{
"type": "npm",
"script": "test",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
}
]
}

@ -0,0 +1,59 @@
# ACONNECTUX
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 19.2.19.
## Development server
To start a local development server, run:
```bash
ng serve
```
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
## Code scaffolding
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
```bash
ng generate component component-name
```
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
```bash
ng generate --help
```
## Building
To build the project run:
```bash
ng build
```
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
## Running unit tests
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
```bash
ng test
```
## Running end-to-end tests
For end-to-end (e2e) testing, run:
```bash
ng e2e
```
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
## Additional Resources
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.

@ -0,0 +1,141 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"ACONNECT-UX": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/aconnect-ux/browser",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets"],
"styles": [
"src/styles.scss",
"node_modules/@ng-select/ng-select/themes/default.theme.css",
"node_modules/ngx-toastr/toastr.css",
"src/assets/css/owl.carousel.min.css",
"src/assets/css/bootstrap.min.css",
"src/assets/css/icons.min.css",
"src/assets/css/app.min.css"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{ "type": "initial", "maximumWarning": "1mb", "maximumError": "5MB" },
{ "type": "anyComponentStyle", "maximumWarning": "4kB", "maximumError": "8kB" }
],
"outputHashing": "all",
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dev.ts"
}
]
},
"test": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.test.ts"
}
]
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": { "buildTarget": "ACONNECT-UX:build:production" },
"development": { "buildTarget": "ACONNECT-UX:build:development" },
"test": { "buildTarget": "ACONNECT-UX:build:test" }
},
"defaultConfiguration": "development"
},
"server": {
"builder": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist/aconnect-ux/server",
"main": "src/main.server.ts",
"tsConfig": "tsconfig.server.json"
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
},
"development": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dev.ts"
}
]
},
"test": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.test.ts"
}
]
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": ["zone.js", "zone.js/testing"],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [{ "glob": "**/*", "input": "public" }],
"styles": ["src/styles.scss"],
"scripts": []
}
}
}
}
}
}

@ -0,0 +1,64 @@
{
"name": "aconnect-ux",
"version": "0.0.0",
"releaseDate": "2026-01-16",
"scripts": {
"ng": "ng",
"start": "ng serve --configuration=development",
"start:dev": "ng serve --configuration=development",
"start:test": "ng serve --configuration=test",
"start:prod": "ng serve --configuration=production",
"build": "ng build --configuration=development",
"build:dev": "ng build --configuration=development",
"build:test": "ng build --configuration=test",
"build:prod": "ng build --configuration=production",
"watch:dev": "ng build --watch --configuration=development",
"watch:test": "ng build --watch --configuration=test",
"test": "ng test",
"serve:ssr": "node dist/aconnect-ux/server/main.js"
},
"private": true,
"dependencies": {
"@angular/animations": "^19.1.0",
"@angular/cdk": "^19.1.0",
"@angular/common": "^19.1.0",
"@angular/compiler": "^19.1.0",
"@angular/core": "^19.1.0",
"@angular/forms": "^19.1.0",
"@angular/platform-browser": "^19.1.0",
"@angular/platform-browser-dynamic": "^19.1.0",
"@angular/platform-server": "^19.1.0",
"@angular/router": "^19.1.0",
"@angular/ssr": "^19.1.6",
"@ng-select/ng-select": "^14.8.0",
"@ngx-translate/core": "^17.0.0",
"@ngx-translate/http-loader": "^16.0.1",
"@types/crypto-js": "^4.2.2",
"@types/file-saver": "^2.0.7",
"bootstrap": "^5.3.8",
"crypto-js": "^4.2.0",
"express": "^4.18.2",
"file-saver": "^2.0.5",
"ngx-spinner": "^19.0.0",
"ngx-toastr": "^19.1.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"xlsx": "^0.18.5",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^19.1.0",
"@angular/cli": "^19.1.0",
"@angular/compiler-cli": "^19.1.0",
"@types/express": "^4.17.17",
"@types/jasmine": "~5.1.0",
"@types/node": "^18.18.0",
"jasmine-core": "~5.5.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.7.2"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,3 @@
<app-loader></app-loader>
<app-notifications></app-notifications>
<router-outlet></router-outlet>

@ -0,0 +1,29 @@
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AppComponent],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have the 'ACONNECT-UX' title`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('ACONNECT-UX');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, ACONNECT-UX');
});
});

@ -0,0 +1,63 @@
import { Component, Inject, PLATFORM_ID } from '@angular/core';
import { Router, RouterOutlet } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { StorageService } from './shared/services/storage.service';
import { isPlatformBrowser } from '@angular/common';
import { directions, supportedLanguages } from './utils/enums';
import { LoaderComponent } from './shared/components/loader/loader.component';
import { NotificationsComponent } from './shared/components/notifications/notifications.component';
@Component({
selector: 'app-root',
imports: [RouterOutlet, LoaderComponent, NotificationsComponent],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
direction: any;
title = 'aConnect';
constructor(
private translateService: TranslateService,
private storageService: StorageService,
private router: Router,
@Inject(PLATFORM_ID) private platformId: object
) { }
ngOnInit() {
if (!isPlatformBrowser(this.platformId)) return;
const currentLanguage = this.storageService.getItem('language') || supportedLanguages.ENGLISH;
this.storageService.setItem('language', currentLanguage);
this.translateService.setDefaultLang(currentLanguage);
this.translateService.use(currentLanguage);
this.direction = this.storageService.getItem('direction') || directions.LTR;
this.storageService.setItem('direction', this.direction);
const userStr = this.storageService.getItem('user');
if (userStr) {
try {
const data = JSON.parse(userStr);
if (data?.token) {
if (this.router.url === '/' || this.router.url === '/login') {
this.router.navigate(['/home/dashboard']);
}
} else {
if (this.router.url === '/') {
this.router.navigate(['/login']);
}
}
} catch {
this.storageService.removeItem('user');
if (this.router.url === '/') {
this.router.navigate(['/login']);
}
}
} else {
if (this.router.url === '/') {
this.router.navigate(['/login']);
}
}
}
}

@ -0,0 +1,14 @@
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { provideServerRouting } from '@angular/ssr';
import { appConfig } from './app.config';
import { serverRoutes } from './app.routes.server';
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(),
provideServerRouting(serverRoutes)
]
};
export const config = mergeApplicationConfig(appConfig, serverConfig);

@ -0,0 +1,51 @@
import { ApplicationConfig, importProvidersFrom, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { HTTP_INTERCEPTORS, HttpClient, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { AuthInterceptor } from './shared/interceptors/auth.interceptor';
import { ToastrModule } from 'ngx-toastr';
import { provideAnimations } from '@angular/platform-browser/animations';
import { LoadingInterceptor } from './shared/interceptors/loading.interceptor';
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, '/assets/i18n/', '.json');
}
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideClientHydration(withEventReplay()),
provideHttpClient(withInterceptorsFromDi()),
provideAnimations(),
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
},
{
provide: HTTP_INTERCEPTORS,
useClass: LoadingInterceptor,
multi: true
},
importProvidersFrom(
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient],
},
}),
ToastrModule.forRoot({
timeOut: 4000,
positionClass: 'toast-top-right',
newestOnTop: true,
closeButton: true
}),
)
]
};

@ -0,0 +1,125 @@
import { HttpHeaders, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { filter, Observable, Observer } from 'rxjs';
import { HttpService } from './shared/services/http.service';
import { URIService } from './app.uri';
import { URIKey } from '../app/utils/uri-enums';
@Injectable(
{ providedIn: 'root' }
)
export class HttpURIService {
constructor(private http: HttpService, private uriService: URIService) {
}
requestGET<T>(uriKey: URIKey | string, params?: HttpParams): Observable<T> {
return new Observable((observer: Observer<any>) => {
this.uriService.canSubscribe.pipe(filter(val => val)).subscribe(val => {
let uri: string = this.uriService.getURIForRequest(uriKey as URIKey)
this.http.requestGET<T>(uri, params).subscribe(t => {
observer.next(t),
observer.complete()
},
error => {
console.error(error);
observer.next(error),
observer.complete()
});
});
});
}
requestPOST<T>(uriKey: URIKey | string, body: any, headers?: HttpHeaders, params?: HttpParams): Observable<T> {
return new Observable((observer: Observer<any>) => {
this.uriService.canSubscribe.pipe(filter(val => val)).subscribe(val => {
let uri: string = this.uriService.getURIForRequest(uriKey as URIKey)
this.http.requestPOST<T>(uri, body, headers, params).subscribe(
t => {
observer.next(t),
observer.complete()
},
error => {
console.error(error);
observer.next(error),
observer.complete()
}
);
});
});
}
requestPOSTMultipart<T>(uriKey: URIKey | string, body: any, headers?: HttpHeaders, params?: HttpParams): Observable<T> {
return new Observable((observer: Observer<any>) => {
this.uriService.canSubscribe.pipe(filter(val => val)).subscribe(val => {
let uri: string = this.uriService.getURIForRequest(uriKey as URIKey)
this.http.requestPOSTMultipart<T>(uri, body, headers, params).subscribe(
t => {
observer.next(t),
observer.complete()
},
error => {
console.error(error);
observer.next(error),
observer.complete()
}
);
});
});
}
requestDELETE<T>(uriKey: URIKey | string, params: HttpParams): Observable<T> {
return new Observable((observer: Observer<any>) => {
this.uriService.canSubscribe.pipe(filter(val => val)).subscribe(val => {
let uri: string = this.uriService.getURIForRequest(uriKey as URIKey)
this.http.requestDELETE<T>(uri, params).subscribe(t => {
observer.next(t),
observer.complete()
},
error => {
console.error(error);
observer.next(error),
observer.complete()
}
);
});
});
}
requestPATCH<T>(uriKey: URIKey | string, body: any, headers?: HttpHeaders, params?: HttpParams): Observable<T> {
return new Observable((observer: Observer<any>) => {
this.uriService.canSubscribe.pipe(filter(val => val)).subscribe(val => {
let uri: string = this.uriService.getURIForRequest(uriKey as URIKey)
this.http.requestPATCH<T>(uri, body, headers, params).subscribe(t => {
observer.next(t),
observer.complete()
},
error => {
console.error(error);
observer.next(error),
observer.complete()
}
);
});
});
}
requestPUT<T>(uriKey: URIKey | string, body: any, headers?: HttpHeaders, params?: HttpParams): Observable<T> {
return new Observable((observer: Observer<any>) => {
this.uriService.canSubscribe.pipe(filter(val => val)).subscribe(val => {
let uri: string = this.uriService.getURIForRequest(uriKey as URIKey)
this.http.requestPUT<T>(uri, body, headers, params).subscribe(t => {
observer.next(t),
observer.complete()
},
error => {
console.error(error);
observer.next(error),
observer.complete()
}
);
});
});
}
}

@ -0,0 +1,8 @@
import { RenderMode, ServerRoute } from '@angular/ssr';
export const serverRoutes: ServerRoute[] = [
{
path: '**',
renderMode: RenderMode.Prerender
}
];

@ -0,0 +1,149 @@
import { Routes } from '@angular/router';
import { LoginComponent } from './authenticate/login/login.component';
import { ChangePasswordComponent } from './user-management/change-password/change-password.component';
import { FullLayoutComponent } from './full-layout/full-layout.component';
import { AuthenticationGuard } from './shared/guards/authentication.guard';
import { ActivityGuard } from './shared/guards/activity.guard';
export const routes: Routes = [
{
path: 'login',
component: LoginComponent
},
{
path: 'first-login-change-password',
component: ChangePasswordComponent
},
{
path: '',
redirectTo: 'login',
pathMatch: 'full'
},
{
path: 'home',
component: FullLayoutComponent,
canActivate: [AuthenticationGuard],
children: [
{
path: '',
redirectTo: 'dashboard',
pathMatch: 'full'
},
{
path: 'dashboard',
loadComponent: () =>
import('./dashboard/dashboard.component').then(
m => m.DashboardComponent
)
},
{
path: 'permissions',
canActivate: [ActivityGuard],
loadComponent: () =>
import('./user-permissions/user-permissions.component').then(
m => m.UserPermissionsComponent
)
},
{
path: 'smsLogger',
canActivate: [ActivityGuard],
loadComponent: () =>
import('./sms-banking/sms-banking.component').then(
m => m.SmsBankingComponent
)
},
{
path: 'smsGateway',
canActivate: [ActivityGuard],
loadComponent: () =>
import('./sms-gateway/sms-gateway.component').then(
m => m.SmsGatewayComponent
)
},
{
path: 'activityLogs',
canActivate: [ActivityGuard],
loadComponent: () =>
import('./logging/logging.component').then(
m => m.LoggingComponent
)
},
{
path: 'transactionLogs',
canActivate: [ActivityGuard],
loadComponent: () =>
import('./transaction-logs/transaction-logs.component').then(
m => m.TransactionLogsComponent
)
},
{
path: 'analysis',
canActivate: [ActivityGuard],
loadComponent: () =>
import('./data-analysis/data-analysis.component').then(
m => m.DataAnalysisComponent
)
},
{
path: 'ibUnblockUser',
canActivate: [ActivityGuard],
loadComponent: () =>
import('./ib-support/ib-unblock-user/ib-unblock-user.component').then(
m => m.IbUnblockUserComponent
)
},
{
path: 'feedbackSetup',
canActivate: [ActivityGuard],
loadComponent: () =>
import('./ib-support/feedback-setup/feedback-setup.component').then(
m => m.FeedbackSetupComponent
)
},
{
path: 'purposeSetup',
canActivate: [ActivityGuard],
loadComponent: () =>
import('./ib-support/tran-purpose-setup/tran-purpose-setup.component').then(
m => m.TranPurposeSetupComponent
)
},
{
path: 'thirdPartyRegistration',
canActivate: [ActivityGuard],
loadComponent: () =>
import('./user-management/third-party-registration/third-party-registration.component').then(
m => m.ThirdPartyRegistrationComponent
)
},
{
path: 'setupUser',
canActivate: [ActivityGuard],
loadComponent: () =>
import('./user-management/setup-user/setup-user.component').then(
m => m.SetupUserComponent
)
},
{
path: 'resetPassword',
canActivate: [ActivityGuard],
loadComponent: () =>
import('./user-management/reset-password/reset-password.component').then(
m => m.ResetPasswordComponent
)
},
{
path: 'changePassword',
canActivate: [ActivityGuard],
loadComponent: () =>
import('./user-management/change-password/change-password.component').then(
m => m.ChangePasswordComponent
)
}
]
},
{
path: '**',
redirectTo: 'home/dashboard'
}
];

@ -0,0 +1,84 @@
import { HttpClient } from '@angular/common/http';
import { Injectable, OnInit } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { environment } from '../environments/environment';
import { URIKey } from './utils/uri-enums';
@Injectable({
providedIn: 'root',
})
export class URIService {
canSubscribe: BehaviorSubject<boolean>;
uriMap: Map<URIKey, string>;
constructor(private http: HttpClient) {
this.canSubscribe = new BehaviorSubject(<boolean>false);
this.uriMap = new Map<URIKey, string>();
this.loadURIs();
}
loadURIs(): void {
this.http.get<URIInfo[]>('assets/data/app.uri.json')
.subscribe(data => {
for (const item of data) {
const baseURI = environment.moduleHost.get(item.Id) as string;
if (baseURI) {
for (const module of item.Modules) {
for (const page of module.Pages) {
const uri = `${baseURI}${module.URI}${page.URI}`;
const key = URIKey[page.UUID as keyof typeof URIKey];
if (key !== undefined) {
this.uriMap.set(key, uri);
}
}
}
}
}
this.canSubscribe.next(true);
});
}
getURI(key: URIKey): string | undefined {
return this.uriMap.get(key);
}
getURIForRequest(key: URIKey): string {
let uri = this.getURI(key as URIKey);
if (uri != undefined) {
return uri;
}
else {
let arr = key.split("/");
if (arr.length) {
let db = arr[0];
let baseurl = environment.moduleHost.get(db.toUpperCase() + "_DOMAIN_URI");
if (baseurl != undefined) {
uri = (baseurl).concat("/").concat(key);
return uri;
}
}
}
return key;
}
}
export interface URIInfo {
Id: string;
URI: string;
Modules: URIModule[];
}
interface URIModule {
Id: string;
URI: string;
Pages: URIPage[];
}
interface URIPage {
Id: string;
URI: string;
UUID: string;
}

@ -0,0 +1,23 @@
export interface AuthenticationToken {
token: string;
}
export interface AuthenticationResponse extends AuthenticationToken {
authenticated: boolean
porOrgacode: string;
userId: string;
password: string;
userHomevac: string;
user: any
}
export class UserCredentials {
porOrgacode!: string;
userId!: string;
password!: string;
token!: string;
}
export class AuthenticationRequest {
isInProgress: boolean = false;
}

@ -0,0 +1,66 @@
<div>
<div class="col-md-11 mx-auto">
<div class="row">
<div class="page-title-box d-sm-flex align-items-center pt-2 justify-content-between sticky-top">
<div class="col-xl-11"></div>
<div class="col-xl-1 mt-2">
<div class="page-title-right float-end mx-3">
<ng-select class="form-select" [formControl]="currentLanguage" [items]="languageOptions" bindLabel="label"
bindValue="value" [clearable]="false" [searchable]="false" style="width: 100px;" (change)="onLangChange()">
</ng-select>
</div>
</div>
</div>
</div>
</div>
<div class="auth-page d-flex align-items-center">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6 col-xl-3-4">
<div class="card">
<div class="bg-primary bg-soft text-center py-3 mt-2">
<img src="assets/images/logo.png" class="img-fluid mb-2" height="120" width="150" alt="Logo">
</div>
<!-- <h2 class="text-center fw-semibold my-3">
aConnect
</h2> -->
<div class="card-body">
<form [formGroup]="loginForm" class="form-horizontal">
<div class="mb-3">
<label for="USER_ID" class="form-label">{{"userName" | translate}}</label>
<div class="input-group auth-pass-inputgroup">
<input type="text" class="form-control" id="USER_ID" formControlName="USER_ID">
</div>
<div *ngIf="loginForm.get('USER_ID')?.invalid && loginForm.get('USER_ID')?.touched" class="text-danger">
<small *ngIf="loginForm.get('USER_ID')?.errors?.['required']">{{"userNameRequired" | translate}}</small>
</div>
</div>
<div class="mb-3">
<label for="PASSWORD" class="form-label">{{"password" | translate}}</label>
<div class="input-group auth-pass-inputgroup">
<input type="{{passwordType}}" class="form-control" id="PASSWORD" formControlName="PASSWORD" aria-label="Password" autocomplete="current-password">
<app-password-hide-show [showPassword]="true" (onEyeClick)="togglePasswordType()"></app-password-hide-show>
</div>
<div *ngIf="loginForm.get('PASSWORD')?.invalid && loginForm.get('PASSWORD')?.touched" class="text-danger">
<small *ngIf="loginForm.get('PASSWORD')?.errors?.['required']">{{"PasswordRequired" | translate}}</small>
</div>
</div>
<div class="mt-3 d-grid">
<button class="btn btn-primary waves-effect waves-light" type="button" (click)="login()" [disabled]="loginForm.invalid">{{"login" | translate}}</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row fixed-bottom">
<div class="col-md-12 float-left">
<p class="Copyright-text"><span class="VersionNumber">{{ versionNumber }} {{ buildDate }}</span></p>
</div>
</div>
</div>

@ -0,0 +1,5 @@
@media (max-width: 768px) {
.VersionNumber{
text-align: center;
}
}

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component';
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [LoginComponent]
})
.compileComponents();
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,143 @@
import { Component, Inject, inject, PLATFORM_ID, ViewChild } from '@angular/core';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { CONSTANTS } from '../../utils/app.constants';
import { PasswordHideShowComponent } from '../../shared/components/password-hide-show/password-hide-show.component';
import { Router } from '@angular/router';
import { MiscService } from '../../shared/services/misc.service';
import { User } from '../../models/user';
import { CommonModule, isPlatformBrowser } from '@angular/common';
import { StorageService } from '../../shared/services/storage.service';
import { directions, FormConstants, HiddenValues, supportedLanguages } from '../../utils/enums';
import { environment } from '../../../environments/environment';
import { AuthenticationService } from '../../services/authenticate.service';
import { UserCredentials } from '../authenticate';
import { NgSelectModule } from '@ng-select/ng-select';
@Component({
selector: 'app-login',
imports: [TranslateModule, ReactiveFormsModule, CommonModule, PasswordHideShowComponent, NgSelectModule],
templateUrl: './login.component.html',
styleUrl: './login.component.scss'
})
export class LoginComponent {
versionNumber: string = '';
buildDate: string = '';
buildNumber: string = '';
loginForm!: FormGroup;
currentLanguage = new FormControl();
passwordType: string = 'password';
direction: string = '';
languageOptions = [
{ label: 'English', value: supportedLanguages.ENGLISH },
{ label: 'Arabic', value: supportedLanguages.ARABIC }
];
ucred: UserCredentials = new UserCredentials();
@ViewChild(PasswordHideShowComponent) passwordHideShow?: PasswordHideShowComponent;
constructor(
private authService: AuthenticationService,
private translateService: TranslateService,
private router: Router,
private miscService: MiscService,
private storageService: StorageService,
@Inject(PLATFORM_ID) private platformId: object
) {
this.initializeLanguage();
}
ngOnInit() {
this.initializeLoginForm();
}
setVersionNumberAndBuildDate(){
this.translateService.get('versionAndBuildNumber', {
versionNumber: environment.versionNumber,
buildNumber: environment.buildNumber
}).subscribe((res: string) => {
this.versionNumber = res;
});
this.translateService.get('versionBuildDate', {
date: environment.buildDate
}).subscribe((res: string) => {
this.buildDate = res;
})
}
initializeLanguage(): void {
if (isPlatformBrowser(this.platformId)) {
const savedLanguage = this.storageService.getItem('language') || supportedLanguages.ENGLISH;;
this.storageService.setItem('language', savedLanguage);
this.currentLanguage.setValue(savedLanguage)
this.translateService.setDefaultLang(savedLanguage);
this.translateService.use(savedLanguage).subscribe(() => {
this.setVersionNumberAndBuildDate();
});
this.setDirection();
}
}
setDirection() {
let selectedLang = this.currentLanguage.value;
if (selectedLang === supportedLanguages.ENGLISH) {
this.direction = directions.LTR;
this.storageService.setItem('direction', this.direction);
}
else {
this.direction = directions.RTL;
this.storageService.setItem('direction', this.direction);
}
if (typeof document !== 'undefined') {
document.documentElement.setAttribute('dir', this.direction);
document.documentElement.setAttribute('lang',
this.direction === directions.RTL ? 'ar' : 'en');
}
}
initializeLoginForm() {
this.loginForm = new FormGroup({
USER_ID: new FormControl('', [Validators.required, Validators.pattern('^[a-z0-9]*$')]),
PASSWORD: new FormControl('', [Validators.required])
})
}
login() {
if (this.loginForm.valid) {
this.ucred.password = this.loginForm.get(FormConstants.PASSWORD)?.value;
this.ucred.userId = this.loginForm.get(FormConstants.USER_ID)?.value;
this.authService.authenticate(this.ucred).subscribe( (res: any) => {
const user = res?.user;
this.storageService.setItem('user', JSON.stringify(res));
if (res?.requiresPasswordChange || user?.firstLogin) {
console.log('First time login - redirecting to first-login-change-password');
this.router.navigate(['/first-login-change-password']);
} else {
console.log('Regular login - redirecting to dashboard');
this.router.navigate(['/home/dashboard']);
}
});
}
}
onLangChange() {
const selectedLang = this.currentLanguage.value;
this.translateService.setDefaultLang(selectedLang);
this.translateService.use(selectedLang).subscribe(() => {
this.setVersionNumberAndBuildDate();
});
this.storageService.setItem('language', selectedLang);
this.setDirection();
// document.documentElement.setAttribute('dir', this.direction);
}
togglePasswordType() {
this.passwordType = this.passwordHideShow?.showPassword ? 'password' : 'text';
}
}

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DashboardComponent } from './dashboard.component';
describe('DashboardComponent', () => {
let component: DashboardComponent;
let fixture: ComponentFixture<DashboardComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DashboardComponent]
})
.compileComponents();
fixture = TestBed.createComponent(DashboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,11 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-dashboard',
imports: [],
templateUrl: './dashboard.component.html',
styleUrl: './dashboard.component.scss'
})
export class DashboardComponent {
}

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DataAnalysisComponent } from './data-analysis.component';
describe('DataAnalysisComponent', () => {
let component: DataAnalysisComponent;
let fixture: ComponentFixture<DataAnalysisComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DataAnalysisComponent]
})
.compileComponents();
fixture = TestBed.createComponent(DataAnalysisComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,11 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-data-analysis',
imports: [],
templateUrl: './data-analysis.component.html',
styleUrl: './data-analysis.component.scss'
})
export class DataAnalysisComponent {
}

@ -0,0 +1,18 @@
<div id="layout-wrapper" data-sidebar="dark" data-keep-enlarged="true">
<app-header></app-header>
<app-side-nav></app-side-nav>
<div class="main-content">
<router-outlet></router-outlet>
<footer class="footer" [dir]="direction">
<div class="container-fluid">
<div class="row">
<div class="col-sm-12">
<div class="text-sm-end d-none text-sm-center d-sm-block">
{{ footerText }}
</div>
</div>
</div>
</div>
</footer>
</div>
</div>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FullLayoutComponent } from './full-layout.component';
describe('FullLayoutComponent', () => {
let component: FullLayoutComponent;
let fixture: ComponentFixture<FullLayoutComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FullLayoutComponent]
})
.compileComponents();
fixture = TestBed.createComponent(FullLayoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,42 @@
import { Component } from '@angular/core';
import { SideNavComponent } from '../shared/components/side-nav/side-nav.component';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { RouterOutlet } from '@angular/router';
import { StorageService } from '../shared/services/storage.service';
import { HeaderComponent } from '../shared/components/header/header.component';
@Component({
selector: 'app-full-layout',
imports: [SideNavComponent, RouterOutlet, HeaderComponent, TranslateModule],
templateUrl: './full-layout.component.html',
styleUrl: './full-layout.component.scss'
})
export class FullLayoutComponent {
direction: any;
footerText: string = '';
constructor(private translateService: TranslateService,
private stoargeService: StorageService
){
}
ngOnInit(){
this.translateService.setDefaultLang(this.stoargeService.getItem('language')!);
this.translateService.use(this.stoargeService.getItem('language')!);
this.direction = this.stoargeService.getItem('direction');
this.setFooterText();
}
setFooterText(){
this.footerText = this.translateService.instant('copyRightsReserved', {
currentYearLong: this.currentYearLong()
})
}
currentYearLong(): number {
return new Date().getFullYear();
}
}

@ -0,0 +1,126 @@
<div id="layout-wrapper">
<div class="inner-pg-sp">
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-sm-flex align-items-center justify-content-between navbar-header p-0">
</div>
</div>
</div>
<div class="container-fluid">
<div class="col-xl-12 mt-4">
<div class="card border">
<div class="card-body">
<div class="table-section">
<div class="row">
<div class="col-lg-12">
<div class="card-body mt-2 p-0">
<div class="card mb-0 mt-2">
<div
class="card-header font-edit-13-child d-flex justify-content-between align-items-center">
{{'credentialsTitle' | translate}}
</div>
<div class="card-body">
<form>
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="userID" class="text-nowrap">
{{ 'Email' | translate }}<span
class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<div class="d-flex flex-row align-items-stretch">
<input type="text" id="userID"
class="form-control"
placeholder="{{ 'Email' | translate }}" appNoWhitespaces
/>
</div>
<!-- <div class="text-danger">
{{ 'requiredField' | translate }}
</div> -->
</div>
</div>
<div class="mt-3">
<p class="text-info h5">{{'2-stepAppPassword' | translate}}</p>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-start gap-2">
<label for="password"
class="text-nowrap mt-2">
{{ 'password' | translate }}<span
class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<input id="password" class="form-control" autocomplete="new-password" type="{{passwordType}}" maxlength="500"
placeholder="{{ 'password' | translate }}" appNoWhitespaces rows="3" />
<app-password-hide-show #psh class="password-eye align-items-stretch" [showPassword]="true"
(onEyeClick)="togglePasswordType()"></app-password-hide-show>
</div>
</div>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="confirmPassword" class="text-nowrap">
{{ 'confirmPassword' | translate }}<span
class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<input id="confirmPassword" class="form-control" type="{{confirmPasswordType}}"
placeholder="{{ 'confirmPassword' | translate }}" appNoWhitespaces />
<app-password-hide-show #cpsh class="password-eye align-items-stretch" [showPassword]="true"
(onEyeClick)="toggleConfirmPasswordType()"></app-password-hide-show>
<!-- <div class="text-danger">
<div>
{{ 'requiredField' | translate }}
</div>
</div>
<div>
{{ 'expiryBeforeRenewal' | translate }}
</div> -->
</div>
</div>
</div>
<div class="col-md-6">
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6 ms-auto text-end">
<button
class="btn btn-primary waves-effect waves-light"
>{{'save' | translate}}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FeedbackSetupComponent } from './feedback-setup.component';
describe('FeedbackSetupComponent', () => {
let component: FeedbackSetupComponent;
let fixture: ComponentFixture<FeedbackSetupComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FeedbackSetupComponent]
})
.compileComponents();
fixture = TestBed.createComponent(FeedbackSetupComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,26 @@
import { CommonModule } from '@angular/common';
import { Component, ViewChild } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TranslateModule } from '@ngx-translate/core';
import { PasswordHideShowComponent } from '../../shared/components/password-hide-show/password-hide-show.component';
@Component({
selector: 'app-feedback-setup',
imports: [TranslateModule, ReactiveFormsModule, FormsModule, CommonModule, PasswordHideShowComponent],
templateUrl: './feedback-setup.component.html',
styleUrl: './feedback-setup.component.scss'
})
export class FeedbackSetupComponent {
@ViewChild('psh') passwordHideShow ?: PasswordHideShowComponent
@ViewChild('cpsh') confirmPasswordHideShow ?: PasswordHideShowComponent
passwordType: string = 'password';
confirmPasswordType: string = 'password';
togglePasswordType(){
this.passwordType = this.passwordHideShow?.showPassword ? 'password' : 'text';
}
toggleConfirmPasswordType(){
this.confirmPasswordType = this.confirmPasswordHideShow?.showPassword ? 'password' : 'text';
}
}

@ -0,0 +1,236 @@
<div id="layout-wrapper">
<div class="inner-pg-sp">
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-sm-flex align-items-center justify-content-between navbar-header p-0">
</div>
</div>
</div>
<div class="container-fluid">
<div class="col-xl-12 mt-4">
<div class="card border">
<div class="card-body">
<div class="table-section">
<div class="row">
<div class="col-lg-12">
<div class="card-body mt-2 p-0">
<div class="card mb-0 mt-2">
<div
class="card-header font-edit-13-child d-flex justify-content-between align-items-center">
{{'IBChildTitle' | translate}}
</div>
<div class="card-body">
<form>
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="selectIdentValueType" class="text-nowrap">
{{ 'selectIdentValueType' | translate }}<span
class="mandatory">*</span>
</label>
<div
class="password-wrapper position-relative w-100">
<div class="d-flex flex-row align-items-stretch">
<ng-select class="custom-select col-md-12 custom-select-sm ms-2 seleted-edit" [(ngModel)]="optionValue"
placeholder="{{ 'selectIdentValueType' | translate }}"
name="optionValue" [clearable]="false" [searchable]="false">
<!-- Select Type (CLICKABLE) -->
<ng-option value="select">
{{ 'selectIdentValueType' | translate }}
</ng-option>
<ng-option value="cnic">
{{ 'cnic_scnic' | translate }}
</ng-option>
<ng-option value="passport">
{{ 'passport' | translate }}
</ng-option>
<ng-option value="nicop">
{{ 'nicop' | translate }}
</ng-option>
<ng-option value="poc">
{{ 'poc' | translate }}
</ng-option>
</ng-select>
</div>
<!-- <div class="text-danger">
{{ 'requiredField' | translate }}
</div> -->
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="custId" class="text-nowrap">
{{ 'custId' | translate }}<span
class="mandatory">*</span>
</label>
<div
class="password-wrapper position-relative w-100">
<input id="custId" type="text" class="form-control"
placeholder="{{ 'enterIdentityValue' | translate }}"
maxlength="500"
appNoWhitespaces rows="3" />
</div>
</div>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6 ms-auto text-end">
<button
class="btn btn-primary waves-effect waves-light">{{'fetchCustomer'
| translate}}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="col-xl-12 mt-4">
<div class="card border">
<div class="card-body">
<div class="table-section">
<div class="row">
<div class="col-lg-12">
<div class="card-body mt-2 p-0">
<div class="card mb-0 mt-2">
<div
class="card-header font-edit-13-child d-flex justify-content-between align-items-center">
{{'unblockUserDetails' | translate}}
<div class="d-flex align-items-center gap-2">
<div class="search-box">
<input type="text" class="form-control form-control-sm"
placeholder="{{ 'search' | translate }}">
<i class="fas fa-search search-icon"></i>
</div>
<i class="materialdesignicons">
<ng-container *ngIf="renewalDataExpanded; else collapsedIcon">
<i class="dripicons-chevron-up float-end"></i>
</ng-container>
<ng-template #collapsedIcon>
<i class="dripicons-chevron-down float-end"></i>
</ng-template>
</i>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table mb-0 border">
<thead class="table-light">
<tr>
<th>{{'firstName' | translate}}</th>
<th>{{'lastName' | translate}}</th>
<th>{{'cmpCuststatus' | translate}}</th>
<th>{{'cmpCustlastlogin' | translate}}</th>
<th>{{'accountNonLocked' | translate}}</th>
<th>{{'lockTime' | translate}}</th>
<th>{{'accountno' | translate}}</th>
<th>{{'phoneno' | translate}}</th>
<th>{{'action' | translate}}</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>
<div
class="d-flex justify-content-center gap-2">
<button class="btn btn-info btn-sm"
title="View">
<i class="mdi mdi-eye-outline"></i>
</button>
<button *ngIf="buttonPermissions?.edit" class="btn btn-secondary btn-sm"
title="Edit">
<i class="fas fa-pen"></i>
</button>
<button *ngIf="buttonPermissions?.delete" class="btn btn-danger btn-sm"
title="Delete">
<i class="fas fa-trash-alt"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
<div
class="d-flex justify-content-between align-items-center mt-3">
<div class="form-group mb-0">
<ng-select class="form-select-sm"
[items]="pageSizeOptions" bindLabel="label"
bindValue="value" [(ngModel)]="itemsPerPage"
[searchable]="false" [clearable]="false"
[dropdownPosition]="'top'">
</ng-select>
</div>
<div class="text-muted">
{{ 'page' | translate }} {{ 'of' | translate }} ({{
'totalItems' | translate }})
</div>
<div class="btn-group">
<button
class="btn btn-primary waves-effect waves-light">
{{ 'previous' | translate }}
</button>
<button
class="btn btn-primary waves-effect waves-light">
{{ 'next' | translate }}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IbUnblockUserComponent } from './ib-unblock-user.component';
describe('IbUnblockUserComponent', () => {
let component: IbUnblockUserComponent;
let fixture: ComponentFixture<IbUnblockUserComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [IbUnblockUserComponent]
})
.compileComponents();
fixture = TestBed.createComponent(IbUnblockUserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,37 @@
import { Component } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NgSelectModule } from '@ng-select/ng-select';
import { TranslateModule } from '@ngx-translate/core';
import { pageSizeOptions } from '../../utils/app.constants';
import { CommonModule } from '@angular/common';
import { ButtonManagementService } from '../../services/button-management.service';
@Component({
selector: 'app-ib-unblock-user',
imports: [TranslateModule, FormsModule, NgSelectModule, CommonModule, ReactiveFormsModule],
templateUrl: './ib-unblock-user.component.html',
styleUrl: './ib-unblock-user.component.scss'
})
export class IbUnblockUserComponent {
renewalDataExpanded: boolean = true
itemsPerPage: number = 5;
pageSizeOptions = pageSizeOptions
optionValue: any;
buttonPermissions: any;
constructor(
private buttonManagementService: ButtonManagementService
){}
ngOnInit(){
this.getButtonPermissions();
}
itemsPerPageChanged() {}
getButtonPermissions() {
this.buttonPermissions = this.buttonManagementService.buttonPermissions["ibUnblockUser"];
}
}

@ -0,0 +1,199 @@
<div id="layout-wrapper">
<div class="inner-pg-sp">
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-sm-flex align-items-center justify-content-between navbar-header p-0">
</div>
</div>
</div>
<div class="container-fluid">
<div class="col-xl-12 mt-4">
<div class="card border">
<div class="card-body">
<div class="table-section">
<div class="row">
<div class="col-lg-12">
<div class="card-body mt-2 p-0">
<div class="card mb-0 mt-2">
<div
class="card-header font-edit-13-child d-flex justify-content-between align-items-center">
{{'purposeSetup' | translate}}
</div>
<div class="card-body">
<form>
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="purpcodeLabel" class="text-nowrap">
{{ 'purpcodeLabel' | translate }}<span
class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<div class="d-flex flex-row align-items-stretch">
<input type="text" id="purpcodeLabel"
class="form-control"
placeholder="{{ 'purpcodePlaceholder' | translate }}" appNoWhitespaces
/>
</div>
<!-- <div class="text-danger">
{{ 'requiredField' | translate }}
</div> -->
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-start gap-2">
<label for="purpdescLabel"
class="text-nowrap mt-2">
{{ 'purpdescLabel' | translate }}<span
class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<input id="purpdescLabel"
class="form-control"
maxlength="500"
placeholder="{{ 'purpdescPlaceholder' | translate }}" appNoWhitespaces
rows="3" />
</div>
</div>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6 ms-auto text-end">
<button
class="btn btn-primary waves-effect waves-light"
>{{'save' | translate}}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="col-xl-12 mt-4">
<div class="card border">
<div class="card-body">
<div class="table-section">
<div class="row">
<div class="col-lg-12">
<div class="card-body mt-2 p-0">
<div class="card mb-0 mt-2">
<div class="card-header font-edit-13-child d-flex justify-content-between align-items-center">
{{'transactionDetails' | translate}}
<div class="d-flex align-items-center gap-2">
<div class="search-box">
<input type="text" class="form-control form-control-sm"
placeholder="{{ 'search' | translate }}">
<i class="fas fa-search search-icon"></i>
</div>
<i class="materialdesignicons">
<ng-container *ngIf="renewalDataExpanded; else collapsedIcon">
<i class="dripicons-chevron-up float-end"></i>
</ng-container>
<ng-template #collapsedIcon>
<i class="dripicons-chevron-down float-end"></i>
</ng-template>
</i>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table mb-0 border">
<thead class="table-light">
<tr>
<th>{{'smsOrgaCode' | translate}}</th>
<th>{{'purpcodeLabel' | translate}}</th>
<th>{{'purpdescLabel' | translate}}</th>
<th>{{'action' | translate}}</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td>
<div class="d-flex justify-content-center gap-2">
<button class="btn btn-info btn-sm" title="View">
<i class="mdi mdi-eye-outline"></i>
</button>
<button *ngIf="buttonPermissions?.edit" class="btn btn-secondary btn-sm" title="Edit">
<i class="fas fa-pen"></i>
</button>
<button *ngIf="buttonPermissions?.delete" class="btn btn-danger btn-sm" title="Delete">
<i class="fas fa-trash-alt"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>
<div class="d-flex justify-content-between align-items-center mt-3">
<div class="form-group mb-0">
<ng-select class="form-select-sm"
[items]="pageSizeOptions"
bindLabel="label"
bindValue="value"
[(ngModel)]="itemsPerPage"
[searchable]="false"
[clearable]="false"
[dropdownPosition]="'top'">
</ng-select>
</div>
<div class="text-muted">
{{ 'page' | translate }} {{ 'of' | translate }} ({{ 'totalItems' | translate }})
</div>
<div class="btn-group">
<button class="btn btn-primary waves-effect waves-light">
{{ 'previous' | translate }}
</button>
<button class="btn btn-primary waves-effect waves-light">
{{ 'next' | translate }}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TranPurposeSetupComponent } from './tran-purpose-setup.component';
describe('TranPurposeSetupComponent', () => {
let component: TranPurposeSetupComponent;
let fixture: ComponentFixture<TranPurposeSetupComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TranPurposeSetupComponent]
})
.compileComponents();
fixture = TestBed.createComponent(TranPurposeSetupComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,32 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NgSelectModule } from '@ng-select/ng-select';
import { TranslateModule } from '@ngx-translate/core';
import { pageSizeOptions } from '../../utils/app.constants';
import { ButtonManagementService } from '../../services/button-management.service';
@Component({
selector: 'app-tran-purpose-setup',
imports: [TranslateModule, CommonModule, ReactiveFormsModule, FormsModule, NgSelectModule],
templateUrl: './tran-purpose-setup.component.html',
styleUrl: './tran-purpose-setup.component.scss'
})
export class TranPurposeSetupComponent {
pageSizeOptions = pageSizeOptions
renewalDataExpanded: any;
itemsPerPage: number = 5;
buttonPermissions: any;
constructor(
private buttonManagementService: ButtonManagementService
){}
ngOnInit(){
this.getButtonPermissions();
}
getButtonPermissions() {
this.buttonPermissions = this.buttonManagementService.buttonPermissions["thirdPartyRegistration"];
}
}

@ -0,0 +1,186 @@
<div id="layout-wrapper">
<div class="inner-pg-sp">
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div
class="d-sm-flex align-items-center justify-content-between navbar-header p-0"
></div>
</div>
</div>
</div>
<div class="container-fluid">
<!-- SEARCH FORM -->
<div class="col-xl-12 mt-4">
<div class="card border">
<div class="card-body">
<div class="card-header font-edit-13-child mb-3">
{{ "activityLogs" | translate }}
</div>
<form [formGroup]="logsSearchForm">
<div class="row g-3 mb-3 ">
<div class="col-md-6">
<label>
{{ "fromDate" | translate }}
<span class="mandatory">*</span>
</label>
<div class="date-input-wrapper">
<input
type="date"
formControlName="fromDate"
class="form-control"
[max]="maxDate"
/>
<i class="fas fa-calendar calendar-icon"></i>
</div>
</div>
<div class="col-md-6">
<label>
{{ "toDate" | translate }} <span class="mandatory">*</span>
</label>
<div class="date-input-wrapper">
<input
type="date"
formControlName="toDate"
class="form-control"
[max]="maxDate"
/>
<i class="fas fa-calendar calendar-icon"></i>
</div>
</div>
</div>
<div class="text-end">
<button
class="btn btn-primary"
[disabled]="logsSearchForm.invalid"
(click)="getlogsData()"
>
{{ "findLogs" | translate }}
</button>
</div>
</form>
</div>
</div>
</div>
<!-- TABLE -->
<div class="col-xl-12 mt-4">
<div class="card border">
<div class="card-body">
<div
class="card-header d-flex justify-content-between align-items-center font-edit-13-child"
>
{{ "activityLogDetails" | translate }}
<div class="d-flex gap-2 align-items-center" *ngIf="allItems.length">
<div class="search-box">
<input
type="text"
class="form-control form-control-sm"
placeholder="{{ 'search' | translate }}"
[(ngModel)]="searchText"
(ngModelChange)="applySearch()"
/>
<i class="fas fa-search search-icon"></i>
</div>
<div class="d-flex align-items-center gap-2">
<i (click)="exportDataInExcel()" id="downloadReport" class="fa fa-download"></i>
</div>
<i class="materialdesignicons" (click)="toggleTableCard()">
<ng-container *ngIf="logsDataExpanded; else down">
<i class="dripicons-chevron-up float-end"></i>
</ng-container>
<ng-template #down>
<i class="dripicons-chevron-down float-end"></i>
</ng-template>
</i>
</div>
</div>
</div>
<div class="card-body" *ngIf="logsDataExpanded">
<!-- NO RECORDS -->
<div *ngIf="!filteredItems.length" class="text-center text-muted">
{{ "noLoggingDetailsFound" | translate }}
</div>
<!-- TABLE -->
<div *ngIf="filteredItems.length" class="table-responsive">
<table class="table table-bordered mb-0">
<thead class="table-light">
<tr>
<th>{{ "loggingID" | translate }}</th>
<th>{{ "loggingRequestUri" | translate }}</th>
<th>{{ "loggingResponseCode" | translate }}</th>
<th>{{ "loggingRemoteIP" | translate }}</th>
<th>{{ "loggingDateTime" | translate }}</th>
<th>{{ "loggingMethod" | translate }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let logs of pagedItems">
<td>{{ logs.id }}</td>
<td>{{ logs.requestUri }}</td>
<td>{{ logs.responseCode }}</td>
<td>{{ logs.remoteIp }}</td>
<td>{{ logs.dateTime | date: "dd-MM-yyyy, hh:mm a" }}</td>
<td>{{ logs.method }}</td>
</tr>
</tbody>
</table>
<!-- FOOTER -->
<div
class="d-flex justify-content-between align-items-center mt-3"
>
<ng-select
[items]="pageSizeOptions"
bindLabel="label"
bindValue="value"
[(ngModel)]="itemsPerPage"
(change)="itemsPerPageChanged()"
[searchable]="false"
[clearable]="false">
</ng-select>
<div class="text-muted">
{{ "page" | translate }} {{ currentPage }}
{{ "of" | translate }} {{ totalPages() }} ({{
filteredItems.length
}}
{{ "totalItems" | translate }})
</div>
<div class="btn-group">
<button
class="btn btn-primary"
(click)="previousPage()"
[disabled]="currentPage === 1"
>
{{ "previous" | translate }}
</button>
<button
class="btn btn-primary"
(click)="nextPage()"
[disabled]="currentPage >= totalPages()"
>
{{ "next" | translate }}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

@ -0,0 +1,29 @@
.date-input-wrapper {
position: relative;
input[type="date"] {
cursor: pointer;
padding-right: 35px;
&::-webkit-calendar-picker-indicator {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
cursor: pointer;
opacity: 0;
}
}
.calendar-icon {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
color: #6c757d;
}
}

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoggingComponent } from './logging.component';
describe('LoggingComponent', () => {
let component: LoggingComponent;
let fixture: ComponentFixture<LoggingComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [LoggingComponent]
})
.compileComponents();
fixture = TestBed.createComponent(LoggingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,170 @@
import { Component, OnInit } from '@angular/core';
import {
FormBuilder,
FormGroup,
FormsModule,
ReactiveFormsModule,
Validators,
} from '@angular/forms';
import { NgSelectModule } from '@ng-select/ng-select';
import { TranslateModule } from '@ngx-translate/core';
import {
LOGGING_DETAILS_FILE_NAME,
pageSizeOptions,
toDateAfterFromDateValidator,
} from '../utils/app.constants';
import { CommonModule, DatePipe } from '@angular/common';
import { HttpParams } from '@angular/common/http';
import { URIKey } from '../utils/uri-enums';
import { HttpURIService } from '../app.http.uri.service';
import { LogsManagementResponse } from '../utils/app.interfaces';
import { ExcelExportService } from '../shared/services/excel-export.service';
@Component({
selector: 'app-logging',
standalone: true,
imports: [
TranslateModule,
FormsModule,
NgSelectModule,
CommonModule,
ReactiveFormsModule,
],
providers: [DatePipe],
templateUrl: './logging.component.html',
styleUrl: './logging.component.scss',
})
export class LoggingComponent implements OnInit {
logsSearchForm!: FormGroup;
pageSizeOptions = pageSizeOptions;
itemsPerPage = 10;
currentPage = 1;
maxDate: string;
searchText = '';
logsDataExpanded = true;
isLoading = false;
/** DATA LAYERS (do not mix these) */
allItems: LogsManagementResponse[] = []; // raw API data
filteredItems: LogsManagementResponse[] = []; // after search
pagedItems: LogsManagementResponse[] = []; // table view
constructor(
private fb: FormBuilder,
private httpService: HttpURIService,
private datePipe: DatePipe,
private excelExportService: ExcelExportService,
) {
this.maxDate = new Date().toISOString().split('T')[0];
}
ngOnInit(): void {
this.logsSearchForm = this.fb.group(
{
fromDate: ['', Validators.required],
toDate: ['', Validators.required],
},
{ validators: toDateAfterFromDateValidator },
);
}
getlogsData(): void {
if (this.logsSearchForm.invalid) return;
this.isLoading = true;
const { fromDate, toDate } = this.logsSearchForm.value;
const params = new HttpParams()
.set('fromDate', this.datePipe.transform(fromDate, 'dd-MM-yyyy')!)
.set('toDate', this.datePipe.transform(toDate, 'dd-MM-yyyy')!);
this.httpService
.requestGET<LogsManagementResponse[]>(URIKey.LOGGER_MANAGER_URI, params)
.subscribe({
next: (response) => {
this.allItems = response ?? [];
this.filteredItems = [...this.allItems];
this.currentPage = 1;
this.updatePagedItems();
this.isLoading = false;
},
error: () => {
this.allItems = [];
this.filteredItems = [];
this.pagedItems = [];
this.isLoading = false;
},
});
}
/** SEARCH — filters DATA, not DOM */
applySearch(): void {
const value = this.searchText.toLowerCase().trim();
if (!value) {
this.filteredItems = [...this.allItems];
} else {
this.filteredItems = this.allItems.filter((item) =>
[
'id',
'method',
'remoteIp',
'requestUri',
'responseCode',
'userId',
'dateTime',
].some((key) =>
String((item as any)[key] ?? '')
.toLowerCase()
.includes(value),
),
);
}
this.currentPage = 1;
this.updatePagedItems();
}
updatePagedItems(): void {
const start = (this.currentPage - 1) * this.itemsPerPage;
const end = start + this.itemsPerPage;
this.pagedItems = this.filteredItems.slice(start, end);
}
totalPages(): number {
return Math.ceil(this.filteredItems.length / this.itemsPerPage);
}
previousPage(): void {
if (this.currentPage > 1) {
this.currentPage--;
this.updatePagedItems();
}
}
nextPage(): void {
if (this.currentPage < this.totalPages()) {
this.currentPage++;
this.updatePagedItems();
}
}
itemsPerPageChanged(): void {
this.currentPage = 1;
this.updatePagedItems();
}
toggleTableCard(): void {
this.logsDataExpanded = !this.logsDataExpanded;
}
exportDataInExcel(): void {
this.excelExportService.exportExcel(
this.filteredItems,
LOGGING_DETAILS_FILE_NAME,
);
}
}

@ -0,0 +1,14 @@
export interface ServerException {
error: HttpError;
}
export interface HttpError {
errorCode: string;
arguments: Array<any>;
}
export interface FunctionReturn {
returnCode: number;
messageCode: string;
}
export interface FunctionReturnDetail extends FunctionReturn {
arguments: Array<any>;
}

@ -0,0 +1,34 @@
export class User {
Username: string="";
Email?: string="";
Password: string="";
}
export interface SetupUser {
userId: string;
userFullname: string;
password: string;
porOrgacode: string | null;
email: string;
role: string;
}
export interface TransactionLog {
logId: number;
porOrgacode: string;
transactionID: string;
drMbmbkmsnumber: string;
crMbmbkmsnumber: string;
crPcaglacode: string;
drPcaGlacode: string;
amount: number;
paymentMode: string;
ppmPymdcode: string;
sgtGntrdate: string;
channelCode: string;
createdAt: string;
transactionUri: string;
transactionCode: string
}

@ -0,0 +1,14 @@
export interface ServerException {
error: HttpError;
}
export interface HttpError {
errorCode: string;
arguments: Array<any>;
}
export interface FunctionReturn {
returnCode: number;
messageCode: string;
}
export interface FunctionReturnDetail extends FunctionReturn {
arguments: Array<any>;
}

@ -0,0 +1,110 @@
import { HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { BehaviorSubject, Observable, Observer } from 'rxjs';
import { tap } from 'rxjs/operators';
import { ErrorMessages, FormConstants, HiddenValues, SuccessMessages } from '../utils/enums';
import { CredentialService } from './credential.service';
import { AuthenticationToken, UserCredentials } from '../authenticate/authenticate';
import { HttpURIService } from '../app.http.uri.service';
import { URIKey } from '../utils/uri-enums';
import { I18NService } from './i18n.service';
import { StorageService } from '../shared/services/storage.service';
import { ButtonManagementService } from './button-management.service';
@Injectable(
{ providedIn: 'root' }
)
export class AuthenticationService {
showLicenseInfo: boolean = false;
reset: boolean = false;
public onAuthenticationComplete: BehaviorSubject<boolean> = new BehaviorSubject(<boolean>false);
constructor(private buttonManagementService: ButtonManagementService, private httpService: HttpURIService, private router: Router, private credentialService: CredentialService, private i18nService: I18NService, private storageService: StorageService) {
}
authenticate(uCreds: UserCredentials): Observable<any> {
const userJson = this.storageService.getItem('user');
if (this.storageService.getItem('user') != null) {
this.i18nService.error(ErrorMessages.ALREADY_LOGGED_IN, []);
return new Observable(); // empty
}
this.credentialService.setPorOrgacode(HiddenValues.POR_ORGACODE);
this.credentialService.setUserId(uCreds.userId);
this.credentialService.setPassword(uCreds.password);
this.storageService.setItem(FormConstants.POR_ORGACODE, HiddenValues.POR_ORGACODE);
this.storageService.setItem(FormConstants.USER_ID, uCreds.userId);
this.storageService.setItem(FormConstants.PASSWORD, uCreds.password);
return this.httpService.requestPOST(URIKey.USER_LOGIN_URI, uCreds).pipe(
tap((data: any) => {
if (!(data instanceof HttpErrorResponse)) {
data.authenticated = true;
this.storageService.setItem('user', JSON.stringify(data));
this.credentialService.setToken(data.token);
this.credentialService.setUserType(data.role);
if (data.user.permissions) {
this.storageService.setItem('permission', data.user.permissions);
this.credentialService.setPermission(JSON.parse(data.user.permissions));
} else {
this.storageService.setItem('permission', '[]');
this.credentialService.setPermission([]);
}
this.buttonManagementService.setButtonPermissions(this.credentialService.getPermission(), this.isAdminUser());
}
})
);
}
updateCredentialsAfterPasswordChange(newPassword: string) {
this.storageService.setItem(FormConstants.PASSWORD, newPassword);
this.credentialService.setPassword(newPassword);
const userStr = this.storageService.getItem('user');
if (userStr) {
const user = JSON.parse(userStr);
user.authenticated = true;
this.storageService.setItem('user', JSON.stringify(user));
}
}
isAuthenticated(): boolean {
if (this.storageService && this.storageService.getItem('user') != null) {
let cachedUser = JSON.parse(this.storageService.getItem('user') || '{}');
return cachedUser.authenticated;
}
return false;
}
isAdminUser(){
if (this.storageService && this.storageService.getItem('user') != null) {
let cachedUser = JSON.parse(this.storageService.getItem('user') || '{}');
return cachedUser.user.role === HiddenValues.ADMIN_USER || cachedUser.user.role === HiddenValues.SUPER_ADMIN;
}
return false;
}
refreshToken() {
let uCreds: UserCredentials = { porOrgacode: this.credentialService.getPorOrgacode(), userId: this.credentialService.getUserId(), password: this.credentialService.getPassword(), token: this.credentialService.getToken() };
return this.httpService.requestPOST<AuthenticationToken>(URIKey.USER_REFRESH_TOKEN, uCreds).pipe(
tap(response => {
this.credentialService.setToken(response.token);
let cachedUser = JSON.parse(this.storageService.getItem('user') || '{}');
cachedUser.token = response.token;
this.storageService.setItem('user', JSON.stringify(cachedUser));
})
);
}
logout() {
let defaultPermission: string = this.storageService.getItem("defaultPermission") || "{}";
this.storageService.clear();
this.storageService.setItem("defaultPermission", defaultPermission)
this.credentialService.resetService();
this.router.navigate(['/login']);
}
}

@ -0,0 +1,56 @@
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { PermissionNode } from "../utils/app.interfaces";
import { HttpURIService } from "../app.http.uri.service";
import { StorageService } from "../shared/services/storage.service";
@Injectable({
providedIn: 'root'
})
export class ButtonManagementService {
buttonPermissions: any = {};
defaultPermission: any = {};
constructor(private httpService: HttpURIService, private storageService: StorageService){
this.defaultPermissions().subscribe((data: PermissionNode[]) => {
this.defaultPermission = data;
this.storageService.setItem("defaultPermission", JSON.stringify(data));
});
}
traverse(nodes: any, isSuperAdmin: boolean) {
if (!Array.isArray(nodes)) return;
nodes.forEach(node => {
// Check if the node has buttons
if (node.buttons && Array.isArray(node.buttons) && node.buttons.length > 0) {
// Create an object for this node's buttons
this.buttonPermissions[node.name] = {};
// Map each button's name to its checked value
node.buttons.forEach((button:any) => {
this.buttonPermissions[node.name][button.name] = isSuperAdmin? true : button.checked;
});
}
// Recursively traverse children
if (node.children && Array.isArray(node.children)) {
this.traverse(node.children, isSuperAdmin);
}
});
}
setButtonPermissions(permission: any, isSuperAdmin: boolean){
if (isSuperAdmin){
if (Object.keys(this.defaultPermission).length === 0){
this.defaultPermission = JSON.parse(this.storageService.getItem("defaultPermission") || '{}');
}
this.traverse(this.defaultPermission, isSuperAdmin);
} else{
this.traverse(permission, isSuperAdmin);
}
}
defaultPermissions(): Observable<PermissionNode[]> {
return this.httpService.requestGET<PermissionNode[]>('assets/data/sideMenu.json');
}
}

@ -0,0 +1,79 @@
import { Injectable } from '@angular/core';
import { StorageService } from '../shared/services/storage.service';
@Injectable(
{ providedIn: 'root' }
)
export class CredentialService {
private porOrgacode!: string;
private userId!: string;
private userType!: string;
private password!: string;
private token!: string;
private userHomevac!: string;
private permission: any[] = [];
constructor(private storageService: StorageService){
}
getToken(): string {
return this.token;
}
setToken(token: string) {
this.token = token;
}
getPorOrgacode(): string {
return this.porOrgacode;
}
setPorOrgacode(porOrgacode: string) {
this.porOrgacode = porOrgacode
}
getUserId(): string {
return this.userId
}
setUserId(userId: string) {
this.userId = userId;
}
getUserType(): string {
return this.userType
}
setUserType(userType: string) {
this.userType = userType;
}
getPassword(): string {
return this.password;
}
setPassword(password: string) {
this.password = password;
}
getPermission(): any[] {
return this.permission;
}
setPermission(permission: any[]) {
this.permission = permission;
}
resetService() {
this.setPorOrgacode("");
this.setUserId("");
this.setUserType("");
this.setPassword("");
this.setToken("");
this.setPermission([]);
}
}

@ -0,0 +1,54 @@
import { Injectable } from '@angular/core';
import { AES, enc, pad} from 'crypto-js';
@Injectable({
providedIn: 'root'
})
export class EncryptionService {
constructor() { }
encryptData(request: any): object {
const fromKey1: number = Math.floor(Math.random() * 5) + 1;
const fromKey2: number = Math.floor(Math.random() * 5) + 1;
const key1: string = this.generateRandomKey(16);
const key2: string = this.generateRandomKey(16);
try {
let input: string;
if (typeof request === 'string') {
input = request;
} else {
input = JSON.stringify(request);
}
// Encryption
const encrypted: string = AES.encrypt(input, enc.Utf8.parse(key1), {
iv: enc.Utf8.parse(key2),
padding: pad.Pkcs7
}).toString();
const dataBeforeKey1 = encrypted.substring(0, fromKey1);
let encryptedDataSubstring = encrypted.substring(fromKey1);
const dataBeforeAfterKey2 = encryptedDataSubstring.substring(encryptedDataSubstring.length - fromKey2);
encryptedDataSubstring = encryptedDataSubstring.substring(0, encryptedDataSubstring.length - fromKey2);
const encryptedRequestData = `1${fromKey1}${dataBeforeKey1}${key1}${encryptedDataSubstring}${key2}${dataBeforeAfterKey2}${fromKey2}1`;
const encryptedDataObject = { data: encryptedRequestData };
return encryptedDataObject;
} catch (error) {
console.error(error);
return {};
}
}
generateRandomKey(length: number): string {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let key = '';
for (let i = 0; i < length; i++) {
key += characters.charAt(Math.floor(Math.random() * characters.length));
}
return key;
}
}

@ -0,0 +1,87 @@
import { Injectable } from "@angular/core";
import { TranslateService } from "@ngx-translate/core";
import { BehaviorSubject, Observable } from "rxjs";
import { MESSAGEKEY } from "../utils/enums";
import { NotificationService } from "../shared/services/notification.service";
@Injectable(
{ providedIn: 'root' }
)
export class I18NService {
public translationsMerge: BehaviorSubject<boolean> = new BehaviorSubject(<boolean>false);
constructor(private translate: TranslateService, private notificationService: NotificationService) {
}
success(code: string, customMessage: any[], defaultText?: string): any {
let params = this.keyValueParamter(customMessage);
this.getMessageTranslate(MESSAGEKEY.SUCCESS, code, params).subscribe((res) => {
this.notificationService.success((res[code] == code && defaultText != undefined) ? defaultText : res[code]);
});
}
error(code: string, customMessage: any[], defaultText?: string): any {
let params = this.keyValueParamter(customMessage);
this.getMessageTranslate(MESSAGEKEY.ERROR, code, params).subscribe((res) => {
this.notificationService.error((res[code] == code && defaultText != undefined) ? defaultText : res[code]);
});
}
info(code: string, customMessage: any[]): any {
let params = this.keyValueParamter(customMessage);
this.getMessageTranslate(MESSAGEKEY.INFO, code, params).subscribe((res) => {
this.notificationService.info(res[code]);
});
}
warn(code: string, customMessage: any[]): any {
let params = this.keyValueParamter(customMessage);
this.getMessageTranslate(MESSAGEKEY.WARN, code, params).subscribe((res) => {
this.notificationService.warning(res[code]);
});
}
notification(code: string, customMessage: any[]): string {
let params = this.keyValueParamter(customMessage);
let notification: string = "";
this.getMessageTranslate(MESSAGEKEY.NOTIFICATION, code, params).subscribe((res) => {
notification = res[code] as string;
});
return notification;
}
keyValueParamter(params: object[]): Object {
// Create an object to hold the key-value pairs
const keyValueObject: { [key: string]: string } = {};
// Populate the object
for (let i = 0; i < params?.length; i++) {
keyValueObject[`value${i + 1}`] = String(params[i]);
}
return keyValueObject;
}
getMessageTranslate(type: string, code: string, params: any): Observable<string | any> {
if (type == MESSAGEKEY.SUCCESS) {
return this.translate.get([code, "SUC_APP_F_SUM"], params)
} else if (type == MESSAGEKEY.ERROR) {
return this.translate.get([code, "ERR_APP_F_SUM"], params)
} else if (type == MESSAGEKEY.WARN) {
return this.translate.get([code, "WRN_APP_F_SUM"], params)
} else if (type == MESSAGEKEY.INFO) {
return this.translate.get([code, "INF_APP_F_SUM"], params)
} else if (type == MESSAGEKEY.NOTIFICATION) {
return this.translate.get([code, "NTF_APP_F_SUM"], params)
} else {
return this.translate.get([code, code], params)
}
}
}

@ -0,0 +1,20 @@
import { Injectable } from '@angular/core';
import { ToastrService } from 'ngx-toastr';
@Injectable({
providedIn: 'root'
})
export class MessageService {
constructor(private toastr: ToastrService) { }
Success(Message: string,) {
this.toastr.success(Message, "Success", { tapToDismiss: true, progressBar: true, progressAnimation: 'increasing' });
}
Show(Message: string, Title: string) {
this.toastr.success(Message, Title, { tapToDismiss: true, progressBar: true, progressAnimation: 'increasing' });
}
Error(Message: string) {
this.toastr.error(Message, "Error", { tapToDismiss: true, progressBar: true, progressAnimation: 'increasing' });
}
}

@ -0,0 +1,18 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class SidebarService {
private isSidebarOpen = new BehaviorSubject<boolean>(false);
public sidebarState$ = this.isSidebarOpen.asObservable();
currentSubModule:string;
constructor() {
this.currentSubModule = '';
}
toggleSidebar(): void {
this.isSidebarOpen.next(!this.isSidebarOpen.value);
}
}

@ -0,0 +1,16 @@
import { Injectable } from '@angular/core';
import { NgxSpinnerService } from 'ngx-spinner';
@Injectable({
providedIn: 'root'
})
export class SpinnerService {
constructor(private spinner: NgxSpinnerService) { }
IsBusy(state:boolean) {
if (state == true)
this.spinner.show();
else
this.spinner.hide();
}}

@ -0,0 +1,76 @@
<div class="col-md-10 mx-auto" id="page-topbar">
<div class="p-0">
<div class="navbar-header shadow-lg d-flex justify-content-between align-items-center w-100">
<!-- Left Section -->
<div class="d-flex align-items-center">
<!-- Logo Section -->
<div class="navbar-brand-box bg-primary me-2">
<a routerLink="/home/dashboard" class="logo logo-dark">
<span class="logo-sm">
<img src="assets/images/mfsys-logo.png" alt="Logo" height="30" />
</span>
<span class="logo-lg">
<img src="assets/images/mfsys-logoo.png" alt="Logo" height="60" />
</span>
</a>
<a routerLink="/home/dashboard" class="logo logo-light">
<span class="logo-sm">
<img src="assets/images/mfsys-logo.png" alt="Logo" height="30" />
</span>
<span class="logo-lg">
<img src="assets/images/mfsys-logoo.png" alt="Logo" height="60" />
</span>
</a>
</div>
<!-- Vertical Menu Button -->
<button type="button"
class="btn btn-sm px-3 text-muted header-item waves-effect"
id="vertical-menu-btn">
<img src="assets/images/data-transfers.png" alt="Logo" style="width: 16px; margin: -4px;" />
</button>
<!-- Submodule Text -->
<div class="d-none d-lg-block ms-3">
<p class="text-muted mt-3 fw-normal mb-0">
{{ (sidebarService.currentSubModule | translate) }}
</p>
</div>
</div>
<!-- Right Section -->
<div class="d-flex align-items-center">
<div class="d-none d-lg-inline-block me-3">
<label class="text-muted fw-normal mb-0 "
[title]="mismatchedDates"
[style]="{'color': dateColor}"
style="font-size: 14px;" >
{{'date' | translate}}: {{date}}
</label>
</div>
<div class="dropdown d-inline-block profile-dropdown">
<button type="button"
class="btn header-item waves-effect p-0 d-flex align-items-center"
id="page-header-user-dropdown"
(click)="toggleDropdown()"
aria-haspopup="true"
[attr.aria-expanded]="isDropdownVisible ? 'true' : 'false'">
<img class="rounded-circle header-profile-user" src="assets/images/user-icon.png" alt="user-icon" />
<span class="d-none d-xl-inline-block ms-2 fw-sm">{{username}}</span>
<i class="mdi mdi-chevron-down text-muted d-xl-inline-block font-size-22"></i>
</button>
<div class="dropdown-menu dropdown-menu-end" [ngClass]="{'show': isDropdownVisible}">
<a class="dropdown-item text-danger" (click)="logout()">
<i class="bx bx-power-off font-size-16 align-middle me-1 text-danger"></i> {{ 'logout' | translate }}
</a>
</div>
</div>
</div>
</div>
</div>
</div>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HeaderComponent } from './header.component';
describe('HeaderComponent', () => {
let component: HeaderComponent;
let fixture: ComponentFixture<HeaderComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HeaderComponent]
})
.compileComponents();
fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,178 @@
import { Component, HostListener, Inject, OnInit, PLATFORM_ID } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { SidebarService } from '../../../services/sidebar.service';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { StorageService } from '../../services/storage.service';
import { isPlatformBrowser, formatDate, CommonModule } from '@angular/common';
import { AuthenticationService } from '../../../services/authenticate.service';
@Component({
selector: 'app-header',
imports: [TranslateModule, CommonModule],
templateUrl: './header.component.html',
styleUrl: './header.component.scss'
})
export class HeaderComponent implements OnInit{
isDropdownVisible: boolean;
isVacDropdownVisible: boolean;
isNotificationsVisible: boolean;
notifications = [
{
imgSrc: '',
title: 'Salena Layfield',
message: 'As a skeptical Cambridge friend of mine occidental.',
timeAgo: '1 hour ago'
},
];
direction: string = 'ltr';
user: any;
userObj: any;
username : string = '';
mismatchedDates: string = "";
dateColor = "black";
date: any;
vacName: any;
allVacs: any;
constructor(
public sidebarService: SidebarService,
@Inject(PLATFORM_ID) private platformId: Object,
private storageService: StorageService,
public authService: AuthenticationService
) {
this.isDropdownVisible = false;
this.isVacDropdownVisible = false;
this.isNotificationsVisible = false;
this.date = new Date().toISOString().split('T')[0];
}
ngOnInit(): void {
if (typeof document !== 'undefined' && typeof window !== 'undefined') {
this.initializeVerticalMenuToggle();
this.initializeFullscreenToggle();
const body = document.body;
if (window.innerWidth >= 992) {
const isCollapsed = body.classList.toggle('sidebar-enable');
this.storageService.setItem('sidebarState', isCollapsed ? 'expanded' : 'collapsed');
}
}
this.userObj = JSON.parse(this.storageService.getItem('user') || '{}');
this.username = this.userObj?.user?.userFullname ?? '';
}
ngAfterViewInit(): void {
}
initializeVerticalMenuToggle(): void {
if (isPlatformBrowser(this.platformId)) {
const verticalMenuBtn = document.getElementById('vertical-menu-btn');
if (window.innerWidth <= 992) {
const body = document.body;
body.classList.add('vertical-collpsed');
}
if (verticalMenuBtn) {
verticalMenuBtn.addEventListener('click', this.toggleSidebar.bind(this));
}
}
}
initializeFullscreenToggle(): void {
if (isPlatformBrowser(this.platformId)) {
const fullscreenButton = document.querySelector<HTMLButtonElement>('[data-bs-toggle="fullscreen"]');
if (fullscreenButton) {
fullscreenButton.addEventListener("click", () => {
if (!document.fullscreenElement) {
this.toggleFullscreen(true);
} else {
this.toggleFullscreen(false);
}
});
}
}
}
toggleFullscreen(enter: boolean): void {
if (isPlatformBrowser(this.platformId)) {
if (enter) {
document.documentElement.requestFullscreen();
} else {
document.exitFullscreen();
}
document.body.classList.toggle('fullscreen-enable', enter);
}
}
toggleSidebar(): void {
if (isPlatformBrowser(this.platformId)) {
const body = document.body;
const isSidebarEnabled = body.classList.toggle('sidebar-enable');
if (window.innerWidth >= 992) {
const isCollapsed = body.classList.toggle('vertical-collpsed');
this.storageService.setItem('sidebarState', isCollapsed ? 'collapsed' : 'expanded');
if (isCollapsed) {
const subMenus = document.querySelectorAll('.sub-menu');
subMenus.forEach(menu => {
(menu as HTMLElement).style.display = '';
menu.setAttribute('aria-expanded', 'false');
});
} else {
const subMenus = document.querySelectorAll('.sub-menu');
subMenus.forEach(menu => {
(menu as HTMLElement).style.display = 'none';
menu.setAttribute('aria-expanded', 'false');
});
}
} else {
body.classList.remove('vertical-collpsed');
this.storageService.setItem('sidebarState', 'expanded');
const subMenus = document.querySelectorAll('.sub-menu');
subMenus.forEach(menu => {
(menu as HTMLElement).style.display = 'none';
menu.setAttribute('aria-expanded', 'false');
});
}
}
}
toggleDropdown(): void {
this.isDropdownVisible = !this.isDropdownVisible;
this.isNotificationsVisible = false;
}
toggleVacDropdown(): void {
this.isVacDropdownVisible = !this.isVacDropdownVisible;
this.isNotificationsVisible = false;
}
toggleNotifications(): void {
this.isNotificationsVisible = !this.isNotificationsVisible;
this.isDropdownVisible = false;
}
// toggleSidebar() {
// this.sidebarService.toggleSidebar();
// }
logout() {
this.authService.logout();
}
@HostListener('document:click', ['$event'])
handleClickOutside(event: MouseEvent) {
const targetElement = event.target as HTMLElement;
const isClickInsideProfileDropdown = targetElement.closest('.profile-dropdown');
const isClickInsideVacDropdown = targetElement.closest('.vac-dropdown');
if (!isClickInsideProfileDropdown) {
this.isDropdownVisible = false;
}
if (!isClickInsideVacDropdown) {
this.isVacDropdownVisible = false;
}
}
formatDate(date?: Date) {
if ((date && !isNaN(date.getTime())) || (date != null || date != undefined)) {
// Adil 5152 - Changing the Date Locale based on the language selected
return formatDate(date, 'EEEE, d MMMM yyyy', 'en');
}
return null;
}
}

@ -0,0 +1,5 @@
@if (loading$ | async) {
<div class="loader-overlay">
<div class="loader"></div>
</div>
}

@ -0,0 +1,27 @@
.loader-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.loader {
border: 16px solid #f3f3f3;
border-radius: 50%;
border-top: 16px solid #3498db;
width: 120px;
height: 120px;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoaderComponent } from './loader.component';
describe('LoaderComponent', () => {
let component: LoaderComponent;
let fixture: ComponentFixture<LoaderComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [LoaderComponent]
})
.compileComponents();
fixture = TestBed.createComponent(LoaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,18 @@
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { LoadingService } from '../../services/loading.service';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-loader',
imports: [CommonModule],
templateUrl: './loader.component.html',
styleUrl: './loader.component.scss'
})
export class LoaderComponent {
loading$: Observable<boolean>;
constructor(private loadingService: LoadingService) {
this.loading$ = this.loadingService.loading$;
}
}

@ -0,0 +1,4 @@
<div *ngIf="notifications$ | async as notification" class="notification" [ngClass]="notification.type">
{{ notification.message | translate }}
<button class="close-btn" (click)="clearNotification()">×</button>
</div>

@ -0,0 +1,88 @@
.notification {
position: fixed;
top: 20px;
right: 20px;
max-width: 400px;
padding: 16px 48px 16px 20px;
border-radius: 6px;
z-index: 10000;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
background-color: #ffffff;
color: #333333;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 14px;
line-height: 1.4;
transition: opacity 0.4s ease, transform 0.3s ease;
animation: fadeIn 0.3s ease forwards;
border-left: 8px solid #c9c9c9;
display: flex;
align-items: center;
justify-content: space-between;
&.success {
background-color: #e8f5e9;
border-color: #4caf50;
color: #2e7d32;
}
&.error {
background-color: #ffebee;
border-color: #f44336;
color: #c62828;
}
&.warning {
background-color: #fffde7;
border-color: #ffeb3b;
color: #fbc02d;
}
&.info {
background-color: #e3f2fd;
border-color: #2196f3;
color: #1976d2;
}
&.fade-out {
animation: fadeOut 0.4s ease forwards;
}
}
.close-btn {
position: absolute;
top: 12px;
right: 16px;
width: 20px;
height: 20px;
background: none;
border: none;
font-size: 20px;
color: inherit;
cursor: pointer;
transition: color 0.3s;
line-height: 1;
font-weight: bold;
&:hover {
color: #000000;
}
}
/* Animations */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(100px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes fadeOut {
to {
opacity: 0;
transform: translateX(100px);
}
}

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NotificationsComponent } from './notifications.component';
describe('NotificationsComponent', () => {
let component: NotificationsComponent;
let fixture: ComponentFixture<NotificationsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [NotificationsComponent]
})
.compileComponents();
fixture = TestBed.createComponent(NotificationsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,22 @@
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { NotificationService } from '../../services/notification.service';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
@Component({
selector: 'app-notifications',
imports: [CommonModule, TranslateModule],
templateUrl: './notifications.component.html',
styleUrl: './notifications.component.scss'
})
export class NotificationsComponent {
notifications$: Observable<{ type: string; message: string; } | null>
constructor(private notificationService: NotificationService) {
this.notifications$ = this.notificationService.notifications$;
}
clearNotification() {
this.notificationService.clearNotification();
}
}

@ -0,0 +1 @@
<button class="btn btn-light lh-sm" type="button" (click)="togglePassword()"><i [hidden]="!this.showPassword" class="mdi mdi-eye-off-outline"></i><i class="mdi mdi-eye-outline" [hidden]="this.showPassword"></i></button>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PasswordHideShowComponent } from './password-hide-show.component';
describe('PasswordHideShowComponent', () => {
let component: PasswordHideShowComponent;
let fixture: ComponentFixture<PasswordHideShowComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [PasswordHideShowComponent]
})
.compileComponents();
fixture = TestBed.createComponent(PasswordHideShowComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,32 @@
import { Component, EventEmitter, forwardRef, Input, Output } from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'app-password-hide-show',
imports: [],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => PasswordHideShowComponent),
multi: true
}],
templateUrl: './password-hide-show.component.html',
styleUrl: './password-hide-show.component.scss'
})
export class PasswordHideShowComponent {
@Output() onEyeClick = new EventEmitter();
@Input() showPassword : boolean = false;
inputType : String = '';
constructor() { }
ngOnInit(): void {
}
togglePassword(){
this.showPassword = !this.showPassword;
this.onEyeClick.emit();
}
reset() {
this.showPassword = true;
}
}

@ -0,0 +1,107 @@
<div class="vertical-menu bg-primary" (click)="handleMenuClick($event)">
<div id="sidebar-menu" class="hidden-scroll">
<ul class="metismenu list-unstyled" id="side-menu">
<li>
<a href="javascript: void(0);" routerLink="/home/dashboard" routerLinkActive="mm-active">
<i class="fa fa-home"></i>
<span>{{ 'dashboard' | translate }}</span>
</a>
</li>
<li *ngIf="permissions.UserManagement || authService.isAdminUser()">
<a href="javascript:void(0);" class="has-arrow waves-effect" (click)="toggleMenu($event)">
<i class="fa fa-user-secret"></i>
<span>{{ 'UserManagement' | translate }}</span>
</a>
<ul class="sub-menu" aria-expanded="false">
<!-- <li *ngIf="permissions.thirdPartyRegistration || authService.isAdminUser()">
<a routerLink="/home/thirdPartyRegistration" routerLinkActive="mm-active">
<span> {{ 'thirdPartyRegistration' | translate }}</span>
</a>
</li> -->
<li *ngIf="permissions.setupUser || authService.isAdminUser()">
<a routerLink="/home/setupUser" routerLinkActive="mm-active">
<span>{{ 'setupUser' | translate }}</span>
</a>
</li>
<li *ngIf="permissions.resetPassword || authService.isAdminUser()">
<a routerLink="/home/resetPassword" routerLinkActive="mm-active">
<span> {{ 'resetPassword' | translate }}</span>
</a>
</li>
<li *ngIf="permissions.changePassword || authService.isAdminUser()">
<a routerLink="/home/changePassword" routerLinkActive="mm-active" style="cursor: pointer">
<span> {{ 'changePassword' | translate }}</span>
</a>
</li>
</ul>
</li>
<li *ngIf="permissions.Logging || authService.isAdminUser()">
<a href="javascript:void(0);" class="has-arrow waves-effect" (click)="toggleMenu($event)">
<i class="fa fa-history"></i>
<span>{{ 'Logging' | translate }}</span>
</a>
<ul class="sub-menu" aria-expanded="false">
<li *ngIf="permissions.activityLogs || authService.isAdminUser()">
<a routerLink="/home/activityLogs" routerLinkActive="mm-active">
<span> {{ 'activityLogs' | translate }}</span>
</a>
</li>
<li *ngIf="permissions.transactionLogs || authService.isAdminUser()">
<a routerLink="/home/transactionLogs" routerLinkActive="mm-active">
<span>{{ 'transactionLogs' | translate }}</span>
</a>
</li>
</ul>
</li>
<!-- <li *ngIf="permissions.SMSBanking || authService.isAdminUser()">
<a href="javascript:void(0);" class="has-arrow waves-effect" (click)="toggleMenu($event)">
<i class="mdi mdi-comment-outline"></i>
<span>{{ 'SMSBanking' | translate }}</span>
</a>
<ul class="sub-menu" aria-expanded="false">
<li *ngIf="permissions.smsLogger || authService.isAdminUser()">
<a routerLink="/home/smsLogger" routerLinkActive="mm-active">
<span> {{ 'smsLogger' | translate }}</span>
</a>
</li>
<li *ngIf="permissions.smsGateway || authService.isAdminUser()">
<a routerLink="/home/smsGateway" routerLinkActive="mm-active">
<span> {{ 'smsGateway' | translate }}</span>
</a>
</li>
</ul>
</li> -->
<!-- <li *ngIf="permissions.ibSupport || authService.isAdminUser()">
<a href="javascript:void(0);" class="has-arrow waves-effect" (click)="toggleMenu($event)">
<i class="mdi mdi-comment-outline"></i>
<span>{{ 'ibSupport' | translate }}</span>
</a>
<ul class="sub-menu" aria-expanded="false">
<li *ngIf="permissions.ibUnblockUser || authService.isAdminUser()">
<a routerLink="/home/ibUnblockUser" routerLinkActive="mm-active">
<span> {{ 'ibUnblockUser' | translate }}</span>
</a>
</li>
<li *ngIf="permissions.feedbackSetup || authService.isAdminUser()">
<a routerLink="/home/feedbackSetup" routerLinkActive="mm-active">
<span> {{ 'feedbackSetup' | translate }}</span>
</a>
</li>
<li *ngIf="permissions.purposeSetup || authService.isAdminUser()">
<a routerLink="/home/purposeSetup" routerLinkActive="mm-active">
<span> {{ 'purposeSetup' | translate }}</span>
</a>
</li>
</ul>
</li> -->
<li *ngIf="permissions.permissions || authService.isAdminUser()">
<a routerLink="/home/permissions" routerLinkActive="mm-active">
<i class='fa fa-lock'></i>
<span> {{ 'permissions' | translate }}</span>
</a>
</li>
</ul>
</div>
</div>

@ -0,0 +1,30 @@
.active-submenu {
font-weight: bold;
}
.hidden-scroll {
overflow-y: auto;
-ms-overflow-style: none;
scrollbar-width: none;
}
.hidden-scroll::-webkit-scrollbar {
display: none;
}
#sidebar-menu {
height: calc(100vh - 60px);
}
@media (max-width: 768px) {
#sidebar-menu {
height: calc(100vh - 100px);
}
}
.sub-menu {
display: none;
}
.sub-menu[aria-expanded="true"] {
display:block;
}

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SideNavComponent } from './side-nav.component';
describe('SideNavComponent', () => {
let component: SideNavComponent;
let fixture: ComponentFixture<SideNavComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SideNavComponent]
})
.compileComponents();
fixture = TestBed.createComponent(SideNavComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,111 @@
import { Component, Inject, PLATFORM_ID } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { SidebarService } from '../../../services/sidebar.service';
import { StorageService } from '../../services/storage.service';
import { CommonModule, isPlatformBrowser } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { RouterModule } from '@angular/router';
import { Router } from '@angular/router';
import { CredentialService } from '../../../services/credential.service';
import { AuthenticationService } from '../../../services/authenticate.service';
@Component({
selector: 'app-side-nav',
imports: [TranslateModule, RouterModule, CommonModule],
templateUrl: './side-nav.component.html',
styleUrl: './side-nav.component.scss',
})
export class SideNavComponent {
isDropdownVisible = false;
isNotificationsVisible = false;
searchForm!: FormGroup;
permissions: any = {};
activeMenu: string | null = null;
direction: string = 'ltr';
constructor(
private sidebarService: SidebarService,
@Inject(PLATFORM_ID) private platformId: Object,
private storageService: StorageService,
private router: Router,
private credentialService: CredentialService,
public authService: AuthenticationService
) {
}
ngOnInit(): void {
this.credentialService.getPermission().forEach((permission: any) => {
this.permissions[permission.name] = permission.checked;
if(permission.children.length>0){
permission.children.forEach((child: any)=>{
this.permissions[child.name] = child.checked;
})
}
});
this.sidebarService.currentSubModule = this.storageService.getItem('currentSubModule') ?? 'dashboard';
this.closeSidebarMenu();
}
closeSidebarMenu(): void {
if (isPlatformBrowser(this.platformId)) {
const subMenus = document.querySelectorAll('#sidebar-menu .sub-menu');
subMenus.forEach(menu => {
(menu as HTMLElement).style.display = 'none';
menu.setAttribute('aria-expanded', 'false');
});
}
}
toggleMenu(event: Event): void {
const target = event.currentTarget as HTMLElement;
const submenu = target.nextElementSibling as HTMLElement;
if (submenu && submenu.classList.contains('sub-menu')) {
const isExpanded = submenu.getAttribute('aria-expanded') === 'true';
submenu.style.display = isExpanded ? 'none' : '';
submenu.setAttribute('aria-expanded', isExpanded ? 'false' : 'true');
this.storageService.setItem('menuState' + submenu.id, isExpanded ? 'false' : 'true'); // Saving state per submenu
if (window.innerWidth <= 992) {
const links = submenu.querySelectorAll('a');
links.forEach(link => {
link.addEventListener('click', () => {
const body = document.body;
body.classList.remove('sidebar-enable'); // Hide the sidebar
this.storageService.setItem('sidebarState', 'collapsed'); // Store collapsed state
});
});
}
}
}
toggleDropdown(): void {
this.isDropdownVisible = !this.isDropdownVisible;
this.isNotificationsVisible = false;
}
toggleNotifications(): void {
this.isNotificationsVisible = !this.isNotificationsVisible;
this.isDropdownVisible = false;
}
handleMenuClick(event: Event) {
const target = event.target as HTMLElement;
const linkElement = target.closest('a[routerLink]');
if (linkElement) {
const routerLink = linkElement.getAttribute('routerLink');
if (routerLink) {
this.onModuleClick(routerLink);
}
}
}
onModuleClick(route: string) {
const routeParts = route.split('/').filter(part => part.length > 0);
const lastRoutePart = routeParts[routeParts.length - 1];
this.sidebarService.currentSubModule = lastRoutePart;
if (isPlatformBrowser(this.platformId)) {
this.storageService.setItem('currentSubModule', lastRoutePart);
}
}
}

@ -0,0 +1,59 @@
import { LocationStrategy } from '@angular/common';
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { AuthenticationService } from '../../services/authenticate.service';
import { I18NService } from '../../services/i18n.service';
import { ErrorMessages, FormConstants } from '../../utils/enums';
import { CredentialService } from '../../services/credential.service';
@Injectable(
{ providedIn: 'root' }
)
export class ActivityGuard implements CanActivate {
constructor(private router: Router, private authService: AuthenticationService, private i18nService: I18NService, private credentialService: CredentialService) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (typeof window !== 'undefined' && window.localStorage) {
let permissions = JSON.parse(window.localStorage.getItem('permission') || '[]');
if (this.authService.isAuthenticated()) {
if (this.authService.isAdminUser()){
return true;
}
let routeLink = (state.url.split('?'))[0];
if (this.isRouteAuthorized(routeLink, route.queryParams, permissions)) {
return true;
}
this.i18nService.error(ErrorMessages.ACCESS_DENIED, []);
window.localStorage.setItem('currentSubModule','dashboard');
this.router.navigate(["/home/dashboard"]);
return false;
} else {
this.authService.logout();
return false;
}
}
return false;
}
isRouteAuthorized(routerLink: string, queryParams: any, permissions: any): boolean {
let routePermissions : any = {}
let permissionName : any = {}
permissions.forEach((permission: any) => {
routePermissions[permission.route] = permission.checked;
permissionName[permission.name] = permission.checked;
if(permission.children.length>0){
permission.children.forEach((child: any)=>{
routePermissions[child.route] = child.checked;
permissionName[child.name] = child.checked;
})
}
});
if(routePermissions[routerLink]){
return true;
}
return false;
}
}

@ -0,0 +1,61 @@
import { LocationStrategy } from '@angular/common';
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { AuthenticationService } from '../../services/authenticate.service';
import { CredentialService } from '../../services/credential.service';
import { FormConstants } from '../../utils/enums';
import { ButtonManagementService } from '../../services/button-management.service';
import { StorageService } from '../services/storage.service';
@Injectable(
{ providedIn: 'root' }
)
export class AuthenticationGuard implements CanActivate {
constructor(private router: Router, private authService: AuthenticationService, private location: LocationStrategy, private credentialService: CredentialService,private buttonManagementService: ButtonManagementService, private storageService: StorageService) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (state.url.includes('first-login-change-password')) {
return true;
}
if (typeof window !== 'undefined' && window.localStorage) {
const userStr = this.storageService.getItem('user');
if (!userStr) {
this.authService.logout();
return false;
}
const data = JSON.parse(userStr);
if ((data?.requiresPasswordChange || data?.user?.firstLogin) &&
!state.url.includes('changePassword')) {
this.router.navigate(['/first-login-change-password']);
return false;
}
if (this.authService.isAuthenticated()) {
this.credentialService.setPorOrgacode(window.localStorage.getItem(FormConstants.POR_ORGACODE) || '');
this.credentialService.setUserId(window.localStorage.getItem(FormConstants.USER_ID) || '');
this.credentialService.setPassword(window.localStorage.getItem(FormConstants.PASSWORD) || '');
this.credentialService.setToken(data.token);
this.credentialService.setUserType(data.user.role);
let permission = JSON.parse(window.localStorage.getItem('permission') || '[]');
this.credentialService.setPermission(permission);
this.buttonManagementService.setButtonPermissions(this.credentialService.getPermission(), this.authService.isAdminUser());
this.authService.onAuthenticationComplete.next(true);
return true;
}
else {
this.authService.logout();
return false;
}
}
return false;
}
}

@ -0,0 +1,133 @@
import { Injectable, Injector } from '@angular/core';
import {HttpRequest,HttpHandler,HttpEvent,HttpInterceptor,HttpErrorResponse} from '@angular/common/http';
import { BehaviorSubject, Observable, throwError } from 'rxjs';
import { catchError, filter, switchMap, take } from 'rxjs/operators';
import { ErrorMessages } from '../../utils/enums';
import { environment } from '../../../environments/environment';
import { CredentialService } from '../../services/credential.service';
import { EncryptionService } from '../../services/encryption.service';
import { NotificationService } from '../services/notification.service';
import { I18NService } from '../../services/i18n.service';
import { AuthenticationService } from '../../services/authenticate.service';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private isRefreshing = false;
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
private enableEncryption = environment.enableEncryption;
constructor(private injector: Injector, private credentialService: CredentialService, private encryptionService: EncryptionService, private notificationService: NotificationService, private i18nService: I18NService) {}
intercept(request: HttpRequest<any>, handler: HttpHandler): Observable<HttpEvent<any>> {
if (this.credentialService.getPorOrgacode()!= undefined){
request = this.setDefaultHeaders(request);
}
// FOR BIOMETRIC SECUGEN WE BYPASS THESE URIS AS SECUGEN DRIVERS IS USING LOCAL ENDPOINTS.
if (this.credentialService.getToken()&& !request.url.endsWith("/SGIFPCapture")&& !request.url.endsWith("/CreateTemplate")&& !request.url.endsWith("/verifyUserBiometric")) {
request = this.addToken(request, this.credentialService.getToken());
}
if(this.enableEncryption && (request.method === "POST" || request.method === "PATCH" ) && !request.url.endsWith("/SGIFPCapture")&& !request.url.endsWith("/createTemplate")&& !request.url.endsWith("/verifyUserBiometric"))
{
request = this.setEncryptionHeader(request);
request = this.encryptRequestBody(request);
}
return handler.handle(request).pipe(catchError(error => {
if (error instanceof HttpErrorResponse && error.status === 401) {
return this.handleAuthError(request, handler);
} else {
this.handleServerError(error);
return throwError(error);
}
}));
}
private encryptRequestBody(request: HttpRequest<any>): HttpRequest<any> {
if (Object.keys(request.body).length > 0) {
const encryptedData: object = this.encryptionService.encryptData(request.body);
const encryptedRequest: any = request.clone({ body: encryptedData });
return encryptedRequest;
}
return request;
}
private handleAuthError(request: HttpRequest<any>, handler: HttpHandler) {
if (!this.isRefreshing) {
this.isRefreshing = true;
this.refreshTokenSubject.next(null);
let authService: AuthenticationService = this.injector.get(AuthenticationService);
return authService.refreshToken().pipe(
switchMap((response: any) => {
this.isRefreshing = false;
this.refreshTokenSubject.next(response.token);
return handler.handle(this.addToken(request, response.token)).pipe(catchError(error => {
this.handleServerError(error);
return throwError(error);
}));
}));
} else {
return this.refreshTokenSubject.pipe(
filter(token => token != null),
take(1),
switchMap(token => {
return handler.handle(this.addToken(request, token));
}));
}
}
private handleServerError(error: HttpErrorResponse) {
let url: string = error.url as string;
let moduleName: string = "";
if (url != null && url != undefined) {
moduleName = url.split(':').length>2 ?
url.split(':')[2].split('/')[1]:
url.split('/')[3];
}
let authService: AuthenticationService = this.injector.get(AuthenticationService);
switch (error.status) {
case 400:
let errorResponse:any = error ;
if (errorResponse.error && errorResponse.error.errorCode != null) {
this.i18nService.error(errorResponse.error.errorCode, errorResponse.error.arguments);
} else {
this.i18nService.error(ErrorMessages.BAD_REQUEST,[moduleName.toUpperCase()]);
}
break;
case 401:
this.i18nService.error(ErrorMessages.UNAUTHORIZED_REQUEST,[]);
authService.logout();
break;
case 403:
this.i18nService.error(ErrorMessages.FORBIDDEN_REQUEST,[]);
authService.logout();
break;
case 500:
this.i18nService.error(ErrorMessages.INTERNAL_SERVER_ERROR,[moduleName.toUpperCase()]);
break;
case 0:
this.i18nService.error(ErrorMessages.CONNECTION_ERROR,[moduleName.toUpperCase()]);
break;
}
}
private addToken(request: HttpRequest<any>, token: string) {
return request.clone({
setHeaders: {
'Authorization': `Bearer ${token}`
}
});
}
private setDefaultHeaders(request: HttpRequest<any>): HttpRequest<any> {
const modifiedHeaders = request.headers.set('userId', this.credentialService.getUserId())
.append('porOrgacode', this.credentialService.getPorOrgacode())
return request.clone({ headers: modifiedHeaders });
}
private setEncryptionHeader(request: HttpRequest<any>): HttpRequest<any> {
const modifiedHeaders = request.headers.set('X-Encrypted', this.enableEncryption.toString());
return request.clone({ headers: modifiedHeaders });
}
}

@ -0,0 +1,18 @@
import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { LoadingService } from '../services/loading.service';
@Injectable()
export class LoadingInterceptor implements HttpInterceptor {
constructor(private loadingService: LoadingService) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.loadingService.showLoader();
return next.handle(request).pipe(
finalize(() => this.loadingService.hideLoader())
);
}
}

@ -0,0 +1,25 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'tableFilter',
standalone: true
})
export class TableFilterPipe implements PipeTransform {
transform(items: any[], searchText: string, filterProperties: string[]): any[] {
if (!items) return [];
if (!searchText.trim()) return items;
searchText = searchText.toLowerCase().trim();
return items.filter(item => {
return filterProperties.some(prop => {
const value = this.getNestedProperty(item, prop)?.toString().toLowerCase();
return value?.includes(searchText);
});
});
}
private getNestedProperty(obj: any, path: string): any {
return path.split('.').reduce((o, p) => o?.[p], obj);
}
}

@ -0,0 +1,28 @@
import { Injectable } from '@angular/core';
import { saveAs } from 'file-saver';
import * as XLSX from 'xlsx';
import { EXCEL_FILE_EXTENSION, EXCEL_FILE_TYPE } from '../../utils/app.constants';
@Injectable({
providedIn: 'root'
})
export class ExcelExportService {
constructor() { }
private fileType = EXCEL_FILE_TYPE;
private fileExtension = EXCEL_FILE_EXTENSION;
public exportExcel(jsonData: any[], fileName: string): void {
const ws: XLSX.WorkSheet = XLSX.utils.json_to_sheet(jsonData);
const wb: XLSX.WorkBook = { Sheets: { 'data': ws }, SheetNames: ['data'] };
const excelBuffer: any = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
this.saveExcelFile(excelBuffer, fileName);
}
private saveExcelFile(buffer: any, fileName: string): void {
const data: Blob = new Blob([buffer], {type: this.fileType});
saveAs.saveAs(data, fileName + this.fileExtension);
}
}

@ -0,0 +1,108 @@
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { FormConstants, HiddenValues } from '../../utils/enums';
@Injectable(
{ providedIn: 'root' }
)
export class HttpService {
private loadingSubject = new BehaviorSubject<boolean>(false);
loading$ = this.loadingSubject.asObservable();
private setLoading(loading: boolean) {
this.loadingSubject.next(loading);
}
constructor(private http: HttpClient) {
}
requestPOST<T>(url: string, body: any, headers?: HttpHeaders, params?: HttpParams): Observable<T> {
this.setLoading(true);
if (headers == undefined) {
headers = new HttpHeaders().set('Content-Type', 'application/json');
}
headers = headers.set(FormConstants.POR_ORGACODE, HiddenValues.POR_ORGACODE).set(FormConstants.CHANNEL_CODE, HiddenValues.CHANNEL_CODE)
if (params == undefined) {
return this.http.post<T>(url, body, { headers: headers })
} else {
url = this.substituePathVariables(url, params);
return this.http.post<T>(url, body, { params: params, headers: headers });
}
}
requestPOSTMultipart<T>(url: string, body: any, headers?: HttpHeaders, params?: HttpParams): Observable<T> {
this.setLoading(true);
if (headers == undefined) {
headers = new HttpHeaders();
}
headers = headers.set(FormConstants.POR_ORGACODE, HiddenValues.POR_ORGACODE).set(FormConstants.CHANNEL_CODE, HiddenValues.CHANNEL_CODE)
if (params == undefined) {
return this.http.post<T>(url, body, { headers: headers })
} else {
url = this.substituePathVariables(url, params);
return this.http.post<T>(url, body, { params: params, headers: headers });
}
}
requestGET<T>(url: string, reqParams?: HttpParams): Observable<T> {
this.setLoading(true);
let httpHeaders: HttpHeaders = new HttpHeaders();
httpHeaders = httpHeaders.set(FormConstants.POR_ORGACODE,HiddenValues.POR_ORGACODE).set(FormConstants.CHANNEL_CODE,HiddenValues.CHANNEL_CODE);
if (reqParams == undefined) {
return this.http.get<T>(url, { headers: httpHeaders })
} else {
url = this.substituePathVariables(url, reqParams);
return this.http.get<T>(url, { params: reqParams, headers: httpHeaders });
}
}
requestDELETE<T>(url: string, reqParams: HttpParams): Observable<T> {
this.setLoading(true);
url = this.substituePathVariables(url, reqParams);
let httpHeaders: HttpHeaders = new HttpHeaders().set(FormConstants.POR_ORGACODE,HiddenValues.POR_ORGACODE).set(FormConstants.CHANNEL_CODE,HiddenValues.CHANNEL_CODE);
if (reqParams.keys().length > 0) {
return this.http.delete<T>(url, { params: reqParams, headers: httpHeaders });
}
else {
return this.http.delete<T>(url, { headers: httpHeaders });
}
}
requestPATCH<T>(url: string, body: any, headers?: HttpHeaders, params?: HttpParams): Observable<T> {
this.setLoading(true);
if (headers == undefined) {
headers = new HttpHeaders().set('Content-Type', 'application/json');
}
if (params != undefined) {
url = this.substituePathVariables(url, params);
}
headers = headers.set(FormConstants.POR_ORGACODE,HiddenValues.POR_ORGACODE).set(FormConstants.CHANNEL_CODE, HiddenValues.CHANNEL_CODE);
return this.http.patch<T>(url, body, { headers: headers, params: params });
}
requestPUT<T>(url: string, body: any, headers?: HttpHeaders, params?: HttpParams): Observable<T> {
this.setLoading(true);
if (headers == undefined) {
headers = new HttpHeaders().set('Content-Type', 'application/json');
}
if (params != undefined) {
url = this.substituePathVariables(url, params);
}
headers = headers.set(FormConstants.POR_ORGACODE,HiddenValues.POR_ORGACODE).set(FormConstants.CHANNEL_CODE, HiddenValues.CHANNEL_CODE);
return this.http.put<T>(url, body, { headers: headers, params: params });
}
private substituePathVariables(url: string, params: HttpParams): string {
params.keys().forEach(param => {
let pathVariable: string = `{${param}}`;
let pathValue = params.get(param);
if (url.includes(pathVariable) && pathValue != null) {
url = url.replace(pathVariable, pathValue);
params.delete(param);
}
});
return url;
}
}

@ -0,0 +1,19 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class LoadingService {
private loadingSubject = new BehaviorSubject<boolean>(false);
loading$ = this.loadingSubject.asObservable();
showLoader() {
this.loadingSubject.next(true);
}
hideLoader() {
this.loadingSubject.next(false);
}
}

@ -0,0 +1,48 @@
import { Injectable } from '@angular/core';
import { MessageService } from '../../services/message.service';
import { SpinnerService } from '../../services/spinner.service';
import { TranslateService } from '@ngx-translate/core';
@Injectable({
providedIn: 'root'
})
export class MiscService {
constructor(private message: MessageService, private spinnerService: SpinnerService,private translateService: TranslateService,) { }
showLoader(): void {
this.spinnerService.IsBusy(true);
}
hideLoader(): void {
this.spinnerService.IsBusy(false);
}
handleSuccess(message: string): void {
this.message.Success(message);
}
handleError(errorMessage: string, argumentValues: string[] = []): void {
const translatedErrorMessage = this.translateService.instant(errorMessage, { value1: argumentValues[0], value2: argumentValues[1], value3: argumentValues[2] });
this.message.Error(translatedErrorMessage);
}
getErrorMessageTranslation(key: string) {
return this.getTranslation(`${key}`);
}
getTranslation(key: string, defaultLabel?: string): string {
if (key == null || key === "" || key == undefined) {
return defaultLabel ? defaultLabel : "";
}
let translated = this.translateService.instant(String(key));
if (translated == key || translated == "") {
if (defaultLabel && defaultLabel.length > 0) {
return defaultLabel;
}
else {
return key;
}
} else {
return translated;
}
}
}

@ -0,0 +1,40 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, timer } from 'rxjs';
import { switchMap, startWith } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class NotificationService {
private notificationSubject = new BehaviorSubject<{ type: string, message: string } | null>(null);
notifications$ = this.notificationSubject.asObservable();
success(message: string) {
this.notify('success', 'Success: ' + message);
}
error(message: string) {
this.notify('error', 'Error: ' + message);
}
warning(message: string) {
this.notify('warning', 'Warning: ' + message);
}
info(message: string) {
this.notify('info', 'Info: ' + message);
}
private notify(type: string, message: string) {
this.notificationSubject.next({ type, message });
// Automatically clear notification after 3 seconds using RxJS timer
this.notifications$.pipe(
startWith(null),
switchMap(() => timer(5000))
).subscribe(() => this.notificationSubject.next(null));
}
clearNotification() {
this.notificationSubject.next(null);
}
}

@ -0,0 +1,37 @@
import { isPlatformBrowser } from '@angular/common';
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class StorageService {
private isBrowser: boolean = false;
constructor(@Inject(PLATFORM_ID) platformId: object) {
this.isBrowser = isPlatformBrowser(platformId);
}
getItem(key: string): string | null {
if (this.isBrowser) {
return localStorage.getItem(key);
}
return null;
}
setItem(key: string, value: string) {
if (this.isBrowser) {
localStorage.setItem(key, value);
}
}
clear() {
if (this.isBrowser) {
localStorage.clear();
}
}
removeItem(key: string) {
localStorage.removeItem(key);
}
}

@ -0,0 +1,186 @@
<div id="layout-wrapper">
<div class="inner-pg-sp">
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-sm-flex align-items-center justify-content-between navbar-header p-0">
</div>
</div>
</div>
<div class="container-fluid">
<div class="col-xl-12 mt-4">
<div class="card border">
<div class="card-body">
<div class="table-section">
<div class="row">
<div class="col-lg-12">
<div class="card-body mt-2 p-0">
<div class="card mb-0 mt-2">
<div
class="card-header font-edit-13-child d-flex justify-content-between align-items-center">
{{'smsLogger' | translate}}
</div>
<div class="card-body">
<form>
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="fromDate" class="text-nowrap">
{{ 'fromDate' | translate }}<span
class="mandatory">*</span>
</label>
<div
class="password-wrapper position-relative w-100">
<div
class="d-flex flex-row align-items-stretch">
<input type="date" id="fromDate"
class="form-control"
appNoWhitespaces />
</div>
<!-- <div class="text-danger">
{{ 'requiredField' | translate }}
</div> -->
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-start gap-2">
<label for="toDate" class="text-nowrap mt-2">
{{ 'toDate' | translate }}<span
class="mandatory">*</span>
</label>
<div
class="password-wrapper position-relative w-100">
<input id="toDate" type="date" class="form-control"
maxlength="500"
appNoWhitespaces rows="3" />
</div>
</div>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6 ms-auto text-end">
<button
class="btn btn-primary waves-effect waves-light">{{'findLogs'
| translate}}</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="col-xl-12 mt-4">
<div class="card border">
<div class="card-body">
<div class="table-section">
<div class="row">
<div class="col-lg-12">
<div class="card-body mt-2 p-0">
<div class="card mb-0 mt-2">
<div
class="card-header font-edit-13-child d-flex justify-content-between align-items-center">
{{'smsLoggerDetails' | translate}}
<div class="d-flex align-items-center gap-2">
<div class="search-box">
<input type="text" class="form-control form-control-sm"
placeholder="{{ 'search' | translate }}">
<i class="fas fa-search search-icon"></i>
</div>
<i class="materialdesignicons">
<ng-container *ngIf="renewalDataExpanded; else collapsedIcon">
<i class="dripicons-chevron-up float-end"></i>
</ng-container>
<ng-template #collapsedIcon>
<i class="dripicons-chevron-down float-end"></i>
</ng-template>
</i>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table mb-0 border">
<thead class="table-light">
<tr>
<th>{{'smsTrackingID' | translate}}</th>
<th>{{'smsMessage' | translate}}</th>
<th>{{'smsNo' | translate}}</th>
<th>{{'smsOrgaCode' | translate}}</th>
<th>{{'smsDate' | translate}}</th>
<th>{{'smsStatus' | translate}}</th>
<!-- <th>{{'action' | translate}}</th> -->
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<div
class="d-flex justify-content-between align-items-center mt-3">
<div class="form-group mb-0">
<ng-select class="form-select-sm"
[items]="pageSizeOptions" bindLabel="label"
bindValue="value" [(ngModel)]="itemsPerPage"
[searchable]="false" [clearable]="false"
[dropdownPosition]="'top'">
</ng-select>
</div>
<div class="text-muted">
{{ 'page' | translate }} {{ 'of' | translate }} ({{
'totalItems' | translate }})
</div>
<div class="btn-group">
<button
class="btn btn-primary waves-effect waves-light">
{{ 'previous' | translate }}
</button>
<button
class="btn btn-primary waves-effect waves-light">
{{ 'next' | translate }}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SmsBankingComponent } from './sms-banking.component';
describe('SmsBankingComponent', () => {
let component: SmsBankingComponent;
let fixture: ComponentFixture<SmsBankingComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SmsBankingComponent]
})
.compileComponents();
fixture = TestBed.createComponent(SmsBankingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,21 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { NgSelectModule } from '@ng-select/ng-select';
import { TranslateModule } from '@ngx-translate/core';
import { pageSizeOptions } from '../utils/app.constants';
@Component({
selector: 'app-sms-banking',
imports: [TranslateModule, ReactiveFormsModule, NgSelectModule, CommonModule, FormsModule],
templateUrl: './sms-banking.component.html',
styleUrl: './sms-banking.component.scss'
})
export class SmsBankingComponent {
currentPage: number = 1;
pageSizeOptions = pageSizeOptions
renewalDataExpanded: boolean = true
itemsPerPage: number = 5;
searchText: any;
}

@ -0,0 +1,280 @@
<div id="layout-wrapper">
<div class="inner-pg-sp">
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-sm-flex align-items-center justify-content-between navbar-header p-0">
</div>
</div>
</div>
<div class="container-fluid">
<div class="col-xl-12 mt-4">
<div class="card border">
<div class="card-body">
<div class="table-section">
<div class="row">
<div class="col-lg-12">
<div class="card-body mt-2 p-0">
<div class="card mb-0 mt-2">
<div
class="card-header font-edit-13-child d-flex justify-content-between align-items-center">
<span *ngIf="!selectedGateway"></span>
<span *ngIf="selectedGateway">{{(selectedGateway === selectedGatewayType.SYRIATEL ? 'syriatelCredentials' : (selectedGateway === selectedGatewayType.TWILIO ? 'twilioCredentials' : (selectedGateway === selectedGatewayType.JAZZ ? 'jazzCredentials' : ''))) | translate}}</span>
<select class="form-select-sms-gateway"style="min-width: 200px; width: auto;" [(ngModel)]="selectedGateway">
<option value="">{{'SMSGatewaySelect' | translate}}</option>
<option [value]="selectedGatewayType.SYRIATEL">{{'SMSGatewaySyriatel' | translate}}</option>
<option [value]="selectedGatewayType.TWILIO">{{'SMSGatewayTwillio' | translate}}</option>
<option [value]="selectedGatewayType.JAZZ">{{'SMSGatewayJazz' | translate}}</option>
</select>
</div>
<div class="card-body">
<form *ngIf="selectedGateway==='Jazz' || selectedGateway==='Twilio'">
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="accountSID" class="text-nowrap">
{{ 'accountSID' | translate }}<span
class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<div class="d-flex flex-row align-items-stretch">
<input type="text" id="accountSID"
class="form-control"
placeholder="{{ 'accountSID' | translate }}" appNoWhitespaces
/>
</div>
<!-- <div class="text-danger">
{{ 'requiredField' | translate }}
</div> -->
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-start gap-2">
<label for="authToken"
class="text-nowrap mt-2">
{{ 'authToken' | translate }}<span
class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<input id="authToken" class="form-control" autocomplete="new-password" maxlength="500"
placeholder="{{ 'authToken' | translate }}" appNoWhitespaces rows="3" />
</div>
</div>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="fromNumber" class="text-nowrap">
{{ 'fromNumber' | translate }}<span
class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<input id="fromNumber" class="form-control"
placeholder="{{ 'fromNumber' | translate }}" appNoWhitespaces />
<!-- <div class="text-danger">
<div>
{{ 'requiredField' | translate }}
</div>
</div>
<div>
{{ 'expiryBeforeRenewal' | translate }}
</div> -->
</div>
</div>
</div>
<div class="col-md-6">
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label class="text-nowrap">
{{ 'notificationType' | translate }}<span
class="mandatory">*</span>
</label>
<div class="w-100 d-flex gap-3">
<div class="form-check">
<input class="form-check-input" type="radio" name="notificationType" id="message" value="Message">
<label class="form-check-label" for="message">
{{ 'message' | translate }}
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="notificationType" id="template" value="Template">
<label class="form-check-label" for="template">
{{ 'template' | translate }}
</label>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label class="text-nowrap">
{{ 'language' | translate }}<span
class="mandatory">*</span>
</label>
<div class="w-100 d-flex gap-3">
<div class="form-check">
<input class="form-check-input" type="radio" name="language" id="languageArabic" value="Arabic">
<label class="form-check-label" for="languageArabic">
{{ 'arabic' | translate }}
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="language" id="languageEnglish" value="English">
<label class="form-check-label" for="languageEnglish">
{{ 'english' | translate }}
</label>
</div>
</div>
</div>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6 ms-auto text-end">
<button
class="btn btn-primary waves-effect waves-light"
>{{'save' | translate}}</button>
</div>
</div>
</form>
<form *ngIf="selectedGateway==='Syriatel'">
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="userName" class="text-nowrap">
{{ 'userName' | translate }}<span class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<div class="d-flex flex-row align-items-stretch">
<input type="text" id="userName" class="form-control" placeholder="{{ 'userNamePlaceHolder' | translate }}"
appNoWhitespaces />
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label for="senderName" class="text-nowrap">
{{ 'senderName' | translate }}<span class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<input type="text" id="senderName" class="form-control" placeholder="{{ 'senderNamePlaceHolder' | translate }}"
appNoWhitespaces />
</div>
</div>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="d-flex align-items-start gap-2">
<label for="password" class="text-nowrap mt-2">
{{ 'password' | translate }}<span class="mandatory">*</span>
</label>
<div class="password-wrapper position-relative w-100">
<input type="password" id="password" class="form-control" autocomplete="new-password"
maxlength="500" placeholder="{{ 'passwordPlaceholder' | translate }}" appNoWhitespaces />
</div>
</div>
</div>
<div class="col-md-6">
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label class="text-nowrap">
{{ 'notificationType' | translate }}<span class="mandatory">*</span>
</label>
<div class="w-100 d-flex gap-3">
<div class="form-check">
<input class="form-check-input" type="radio" name="notificationType" id="message"
value="Message">
<label class="form-check-label" for="message">
{{ 'message' | translate }}
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="notificationType" id="template"
value="Template">
<label class="form-check-label" for="template">
{{ 'template' | translate }}
</label>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-center gap-2">
<label class="text-nowrap">
{{ 'language' | translate }}<span class="mandatory">*</span>
</label>
<div class="w-100 d-flex gap-3">
<div class="form-check">
<input class="form-check-input" type="radio" name="language" id="languageArabic" value="Arabic">
<label class="form-check-label" for="languageArabic">
{{ 'arabic' | translate }}
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="language" id="languageEnglish"
value="English">
<label class="form-check-label" for="languageEnglish">
{{ 'english' | translate }}
</label>
</div>
</div>
</div>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6 ms-auto text-end">
<button class="btn btn-primary waves-effect waves-light">
{{'save' | translate}}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save