ui-soxo-bootstrap-core 2.6.40-dev.1 → 2.6.40-dev.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +8 -8
- package/.github/workflows/npm-publish.yml +5 -4
- package/.husky/pre-commit +11 -11
- package/.prettierrc.json +10 -10
- package/DEVELOPER_GUIDE.md +323 -323
- package/PUBLISHING.md +333 -0
- package/babel.config.js +2 -2
- package/core/components/component-loader/component-loader.js +125 -125
- package/core/components/component-wrapper/component-wrapper.js +121 -121
- package/core/components/external-window/DEVELOPER_GUIDE.md +705 -705
- package/core/components/external-window/external-window.js +236 -236
- package/core/components/external-window/external-window.test.js +80 -80
- package/core/components/extra-info/extra-info-details.js +155 -155
- package/core/components/extra-info/extra-info-details.scss +26 -26
- package/core/components/extra-info/extra-info.js +134 -134
- package/core/components/index.js +12 -12
- package/core/components/landing-api/landing-api.js +707 -707
- package/core/components/landing-api/landing-api.scss +41 -41
- package/core/components/license-management/license-alert.js +97 -97
- package/core/components/menu-template-api/menu-template-api.js +321 -321
- package/core/components/root-application-api/root-application-api.js +174 -174
- package/core/index.js +13 -13
- package/core/lib/Store.js +369 -369
- package/core/lib/components/application-bootstrap/application-bootstrap.js +115 -115
- package/core/lib/components/approval-form/approval-form.js +280 -280
- package/core/lib/components/approval-form/approval-form.scss +183 -183
- package/core/lib/components/approval-list/approval-list.js +143 -143
- package/core/lib/components/approval-list/approval-list.scss +2 -2
- package/core/lib/components/approval-list/components/request-card/request-card.js +42 -42
- package/core/lib/components/approval-list/components/request-card/request-card.scss +30 -30
- package/core/lib/components/camera/camera.js +230 -230
- package/core/lib/components/camera/camera.scss +86 -86
- package/core/lib/components/comment-block/comment-block.js +138 -138
- package/core/lib/components/comment-block/comment-block.scss +3 -3
- package/core/lib/components/confirm-modal/confirm-modal.js +82 -82
- package/core/lib/components/confirm-modal/confirm-modal.scss +2 -2
- package/core/lib/components/consent/consent.js +67 -67
- package/core/lib/components/consent/pdf-signature.js +299 -299
- package/core/lib/components/consent/signature-pad.js +90 -90
- package/core/lib/components/consent/signature-pad.scss +14 -14
- package/core/lib/components/file-upload/file-upload.js +133 -133
- package/core/lib/components/finger-print-reader/finger-print-reader.js +295 -295
- package/core/lib/components/finger-print-reader/finger-print-reader.scss +47 -47
- package/core/lib/components/finger-print-search/finger-print-search.js +200 -200
- package/core/lib/components/finger-print-search/finger-print-search.scss +47 -47
- package/core/lib/components/global-header/animations.js +18 -18
- package/core/lib/components/global-header/global-header.js +286 -286
- package/core/lib/components/global-header/global-header.scss +397 -397
- package/core/lib/components/header/generic-header.js +76 -76
- package/core/lib/components/header/generic-header.scss +99 -99
- package/core/lib/components/image-preview/image-preview.js +33 -33
- package/core/lib/components/image-wrapper/image-wrapper.js +108 -108
- package/core/lib/components/image-wrapper/image-wrapper.scss +12 -12
- package/core/lib/components/index.js +206 -206
- package/core/lib/components/landing/landing.js +403 -403
- package/core/lib/components/language-switcher/language-switcher.js +49 -49
- package/core/lib/components/menu-context/menu-context.js +69 -69
- package/core/lib/components/menu-template/menu-template.js +249 -249
- package/core/lib/components/menu-template/menu-template.scss +9 -9
- package/core/lib/components/modal-search/modal-search.js +153 -153
- package/core/lib/components/modal-search/modal-search.scss +78 -78
- package/core/lib/components/modal-wrapper/modal-manager.js +15 -15
- package/core/lib/components/modal-wrapper/modal-wrapper.js +108 -108
- package/core/lib/components/modal-wrapper/modal-wrapper.scss +13 -13
- package/core/lib/components/notice-board/notice-board.js +132 -132
- package/core/lib/components/notice-board/notice-board.scss +65 -65
- package/core/lib/components/page-container/page-container.js +55 -55
- package/core/lib/components/page-container/page-container.scss +8 -8
- package/core/lib/components/page-header/page-header.js +23 -23
- package/core/lib/components/page-header/page-header.scss +17 -17
- package/core/lib/components/pdf-viewer/pdf-viewer.js +56 -56
- package/core/lib/components/portlet-table/components/table-actions/table-actions.js +58 -58
- package/core/lib/components/portlet-table/components/table-actions/table-actions.scss +1 -1
- package/core/lib/components/portlet-table/components/table-data/table-data.js +106 -106
- package/core/lib/components/portlet-table/portlet-table.js +63 -63
- package/core/lib/components/portlet-table/portlet-table.scss +90 -90
- package/core/lib/components/progress-bar/progress-bar.js +58 -58
- package/core/lib/components/progress-bar/progress-bar.scss +15 -15
- package/core/lib/components/request-form/request-form.js +110 -110
- package/core/lib/components/root-application/root-application.js +70 -70
- package/core/lib/components/rupee/rupee.js +14 -14
- package/core/lib/components/script-input/script-input.js +169 -169
- package/core/lib/components/script-input/script-input.scss +8 -8
- package/core/lib/components/sidemenu/animations.js +51 -51
- package/core/lib/components/sidemenu/sidemenu.js +713 -713
- package/core/lib/components/sidemenu/sidemenu.scss +314 -314
- package/core/lib/components/spotlight-search/spotlight-search.component.js +635 -635
- package/core/lib/components/spotlight-search/spotlight-search.component.scss +78 -78
- package/core/lib/components/table-wrapper/table-wrapper.js +135 -135
- package/core/lib/components/table-wrapper/table-wrapper.scss +72 -72
- package/core/lib/components/ui_elements/Loader.js +12 -12
- package/core/lib/components/ui_elements/Notify.js +12 -12
- package/core/lib/components/ui_elements/PlaceHolder.js +33 -33
- package/core/lib/components/web-camera/web-camera.js +161 -161
- package/core/lib/components/web-camera/web-camera.scss +28 -28
- package/core/lib/core.md +9 -9
- package/core/lib/elements/Elements.md +2 -2
- package/core/lib/elements/basic/LoggedUserRedirect.js +21 -21
- package/core/lib/elements/basic/PrivateRoute.js +16 -16
- package/core/lib/elements/basic/button/Button.md +43 -43
- package/core/lib/elements/basic/button/button.js +170 -170
- package/core/lib/elements/basic/card/Card.md +15 -15
- package/core/lib/elements/basic/card/card.js +40 -40
- package/core/lib/elements/basic/card/card.scss +13 -13
- package/core/lib/elements/basic/checkbox/checkbox.js +23 -23
- package/core/lib/elements/basic/col/col.js +15 -15
- package/core/lib/elements/basic/copy-to-clipboard/Readme.md +40 -40
- package/core/lib/elements/basic/copy-to-clipboard/copy-to-clipboard.js +61 -61
- package/core/lib/elements/basic/country-phone-input/Readme.md +98 -98
- package/core/lib/elements/basic/country-phone-input/country-phone-input.js +81 -81
- package/core/lib/elements/basic/country-phone-input/phone-input.scss +75 -75
- package/core/lib/elements/basic/datepicker/datepicker.js +33 -33
- package/core/lib/elements/basic/dragabble-wrapper/draggable-wrapper.js +203 -203
- package/core/lib/elements/basic/empty/empty.js +14 -14
- package/core/lib/elements/basic/fingerprint-protrected/fingerprint-protected.js +118 -118
- package/core/lib/elements/basic/fingerprint-protrected/fingerprint-protected.scss +10 -10
- package/core/lib/elements/basic/form/form.js +70 -70
- package/core/lib/elements/basic/form/form.scss +3 -3
- package/core/lib/elements/basic/image/image.js +45 -45
- package/core/lib/elements/basic/image/image.scss +17 -17
- package/core/lib/elements/basic/image/readme.md +26 -26
- package/core/lib/elements/basic/image-viewer/image-viewer.js +108 -108
- package/core/lib/elements/basic/image-viewer/image-viewer.scss +7 -7
- package/core/lib/elements/basic/input/input.js +81 -81
- package/core/lib/elements/basic/input/readme.md +77 -77
- package/core/lib/elements/basic/json-input/json-input.js +51 -51
- package/core/lib/elements/basic/menu-dashboard/menu-dashboard.js +216 -216
- package/core/lib/elements/basic/menu-dashboard/menu-dashboard.scss +28 -28
- package/core/lib/elements/basic/menu-tree/menu-tree.js +127 -127
- package/core/lib/elements/basic/modal/modal.js +64 -64
- package/core/lib/elements/basic/modal/readme.md +62 -62
- package/core/lib/elements/basic/popconfirm/popconfirm.js +17 -17
- package/core/lib/elements/basic/popover/popover.js +12 -12
- package/core/lib/elements/basic/radio/radio.js +18 -18
- package/core/lib/elements/basic/rangepicker/rangepicker.js +141 -141
- package/core/lib/elements/basic/rangepicker/rangepicker.scss +24 -24
- package/core/lib/elements/basic/rangepicker/readme.md +81 -81
- package/core/lib/elements/basic/reference-select/readme.md +18 -18
- package/core/lib/elements/basic/reference-select/reference-select.js +337 -337
- package/core/lib/elements/basic/row/row.js +15 -15
- package/core/lib/elements/basic/select/select.js +46 -46
- package/core/lib/elements/basic/select-box/readme.md +52 -52
- package/core/lib/elements/basic/select-box/select-box.js +63 -63
- package/core/lib/elements/basic/skeleton/readme.md +35 -35
- package/core/lib/elements/basic/skeleton/skeleton.js +35 -35
- package/core/lib/elements/basic/skeleton/skeleton.scss +53 -53
- package/core/lib/elements/basic/space/space.js +12 -12
- package/core/lib/elements/basic/switch/readme.md +29 -29
- package/core/lib/elements/basic/switch/switch.js +67 -67
- package/core/lib/elements/basic/tab/tab.js +14 -14
- package/core/lib/elements/basic/table/readme.md +8 -8
- package/core/lib/elements/basic/table/table.js +95 -95
- package/core/lib/elements/basic/tag/tag.js +63 -63
- package/core/lib/elements/basic/tag/tag.scss +2 -2
- package/core/lib/elements/basic/timeline/timeline.js +13 -13
- package/core/lib/elements/basic/title/readme.md +20 -20
- package/core/lib/elements/basic/title/title.js +37 -37
- package/core/lib/elements/basic/user-search/user-search.js +192 -192
- package/core/lib/elements/complex/barcode/barcode.js +27 -27
- package/core/lib/elements/complex/bargraph/bar-graph.js +262 -262
- package/core/lib/elements/complex/basic-table/basic-table.js +110 -110
- package/core/lib/elements/complex/basic-table/basic-table.scss +4 -4
- package/core/lib/elements/complex/date-display/date-display.js +37 -37
- package/core/lib/elements/complex/error-boundary/error-boundary.js +29 -29
- package/core/lib/elements/complex/google-location-input/map-container-library-load.js +92 -92
- package/core/lib/elements/complex/google-map/google-map.js +230 -230
- package/core/lib/elements/complex/google-map/google-map.scss +13 -13
- package/core/lib/elements/complex/line-graph/line-graph.js +108 -108
- package/core/lib/elements/complex/location-search-input/location-search-input.js +100 -100
- package/core/lib/elements/complex/pie-chart/pie-chart.js +202 -202
- package/core/lib/elements/complex/qr-code/qr-code.js +27 -27
- package/core/lib/elements/complex/qrscanner/qrscanner.js +57 -57
- package/core/lib/elements/complex/search-debounce/search-debounce.js +37 -37
- package/core/lib/elements/complex/statistic-card/dashboard-statistic-card.js +75 -75
- package/core/lib/elements/complex/statistic-card/statistic-card.js +28 -28
- package/core/lib/elements/index.js +226 -226
- package/core/lib/hooks/device-detect.js +25 -25
- package/core/lib/hooks/index.js +9 -9
- package/core/lib/hooks/use-location.js +33 -33
- package/core/lib/hooks/use-otp-timer.js +80 -80
- package/core/lib/hooks/use-window-size.js +34 -34
- package/core/lib/i18n.js +69 -69
- package/core/lib/index.js +106 -106
- package/core/lib/introduction.md +73 -73
- package/core/lib/js-styleguide.md +4112 -4112
- package/core/lib/models/actions/actions.js +127 -127
- package/core/lib/models/actions/components/action-detail/action-detail.js +190 -190
- package/core/lib/models/actions/components/custom-actions/custom-actions.js +185 -185
- package/core/lib/models/attachments/attachments.js +231 -231
- package/core/lib/models/base-loader.js +99 -99
- package/core/lib/models/base.js +716 -716
- package/core/lib/models/branches/branches.js +125 -125
- package/core/lib/models/checklists/checklists.js +114 -114
- package/core/lib/models/columns/columns.js +169 -169
- package/core/lib/models/columns/components/columns-add/columns-add.js +171 -171
- package/core/lib/models/comments/comments.js +213 -213
- package/core/lib/models/departments/departments.js +107 -107
- package/core/lib/models/financial-years/financial_years.js +127 -127
- package/core/lib/models/forms/components/form-creator/form-creator.js +665 -665
- package/core/lib/models/forms/components/form-creator/form-creator.scss +39 -39
- package/core/lib/models/forms/components/form-detail/form-detail.js +224 -224
- package/core/lib/models/forms/forms.js +121 -121
- package/core/lib/models/index.js +203 -203
- package/core/lib/models/invoice-numbers/invoice_numbers.js +204 -204
- package/core/lib/models/lookup-types/components/lookup-detail/lookup-detail.js +145 -145
- package/core/lib/models/lookup-types/lookup-types.js +113 -113
- package/core/lib/models/lookup-values/components/lookup-values-add/lookup-values-add.js +126 -126
- package/core/lib/models/lookup-values/lookup-values.js +107 -107
- package/core/lib/models/menu-roles/menu-roles.js +127 -127
- package/core/lib/models/menus/components/menu-add/menu-add.js +228 -228
- package/core/lib/models/menus/components/menu-detail/menu-detail.js +170 -170
- package/core/lib/models/menus/components/menu-list/menu-list.js +550 -550
- package/core/lib/models/menus/components/menu-list/menu-list.scss +5 -5
- package/core/lib/models/menus/components/menu-roles-add/menu-roles-add.js +183 -183
- package/core/lib/models/menus/menus.js +499 -499
- package/core/lib/models/models/components/model-detail/model-detail.js +137 -137
- package/core/lib/models/models/components/models.js +128 -128
- package/core/lib/models/modules/modules.js +204 -204
- package/core/lib/models/outbox/outbox.js +73 -73
- package/core/lib/models/pages/pages.js +107 -107
- package/core/lib/models/permissions/permissions.js +71 -71
- package/core/lib/models/process/components/process-add/process-add.js +181 -181
- package/core/lib/models/process/components/process-dashboard/process-dashboard.js +1068 -1068
- package/core/lib/models/process/components/process-dashboard/process-dashboard.scss +66 -66
- package/core/lib/models/process/components/process-detail/process-detail.js +140 -140
- package/core/lib/models/process/components/process-timeline/process-timeline.js +139 -139
- package/core/lib/models/process/components/task-detail/task-detail.js +240 -240
- package/core/lib/models/process/components/task-detail/task-detail.scss +27 -27
- package/core/lib/models/process/components/task-form/task-form.js +528 -528
- package/core/lib/models/process/components/task-form/task-form.scss +7 -7
- package/core/lib/models/process/components/task-list/task-list.js +221 -221
- package/core/lib/models/process/components/task-list/task-list.scss +14 -14
- package/core/lib/models/process/components/task-overview/task-overview.js +299 -299
- package/core/lib/models/process/components/task-overview-legacy/task-overview-legacy.js +192 -192
- package/core/lib/models/process/components/task-routes/task-routes.js +45 -45
- package/core/lib/models/process/components/task-status/task-status.js +175 -175
- package/core/lib/models/process/components/task-status/task-status.scss +11 -11
- package/core/lib/models/process/process.js +780 -780
- package/core/lib/models/process-transactions/process-transactions.js +123 -123
- package/core/lib/models/roles/roles.js +106 -106
- package/core/lib/models/scripts/scripts.js +111 -111
- package/core/lib/models/step-transactions/step-transcations.js +147 -147
- package/core/lib/models/steps/components/step-add/step-add.js +261 -261
- package/core/lib/models/steps/components/step-detail/step-detail.js +157 -157
- package/core/lib/models/steps/steps.js +356 -356
- package/core/lib/models/user-preferences/user-preferences.js +83 -83
- package/core/lib/models/users/components/user-add/user-add.js +226 -226
- package/core/lib/models/users/users.js +119 -119
- package/core/lib/modules/business/launch-page/launch-page.js +29 -29
- package/core/lib/modules/business/launch-page/launch-page.scss +5 -5
- package/core/lib/modules/business/slots/slots.js +231 -231
- package/core/lib/modules/business/slots/slots.scss +108 -108
- package/core/lib/modules/forms/components/field-customizer/field-customizer.js +138 -138
- package/core/lib/modules/forms/components/field-selector/field-selector.js +157 -157
- package/core/lib/modules/forms/components/field-selector/field-selector.scss +25 -25
- package/core/lib/modules/forms/components/form-display/form-display.js +203 -203
- package/core/lib/modules/forms/components/form-display/form-display.scss +9 -9
- package/core/lib/modules/forms/components/tab-customizer/tab-customizer.js +124 -124
- package/core/lib/modules/generic/generic-add/generic-add.js +213 -213
- package/core/lib/modules/generic/generic-detail/generic-detail.js +199 -199
- package/core/lib/modules/generic/generic-edit/generic-edit.js +120 -120
- package/core/lib/modules/generic/generic-list/ExportReactCSV.js +414 -414
- package/core/lib/modules/generic/generic-list/generic-list.js +705 -705
- package/core/lib/modules/generic/generic-list/generic-list.scss +68 -68
- package/core/lib/modules/generic/generic-upload/generic-upload.js +483 -483
- package/core/lib/modules/generic/table-settings/table-settings.js +226 -226
- package/core/lib/modules/generic/table-settings/table-settings.scss +37 -37
- package/core/lib/modules/index.js +52 -52
- package/core/lib/modules/modules-routes/module-routes.js +35 -35
- package/core/lib/pages/change-password/change-password.js +204 -204
- package/core/lib/pages/change-password/change-password.scss +73 -73
- package/core/lib/pages/homepage/homepage.js +53 -53
- package/core/lib/pages/index.js +19 -19
- package/core/lib/pages/login/commnication-mode-selection.js +46 -46
- package/core/lib/pages/login/communication-mode-selection.scss +60 -60
- package/core/lib/pages/login/login.js +872 -872
- package/core/lib/pages/login/login.scss +353 -353
- package/core/lib/pages/login/reset-password.js +124 -124
- package/core/lib/pages/login/reset-password.scss +31 -31
- package/core/lib/pages/manage-users/manage-users.js +429 -429
- package/core/lib/pages/manage-users/manage-users.scss +25 -25
- package/core/lib/pages/profile/profile.js +247 -247
- package/core/lib/pages/profile/profile.scss +107 -107
- package/core/lib/pages/profile/theme-config.js +18 -18
- package/core/lib/pages/profile/themes-backup.json +310 -310
- package/core/lib/pages/profile/themes.json +254 -254
- package/core/lib/pages/register/register.js +176 -176
- package/core/lib/pages/register/register.scss +128 -128
- package/core/lib/react-styleguide.md +756 -756
- package/core/lib/utils/api/api.utils.js +207 -207
- package/core/lib/utils/api/readme.md +426 -426
- package/core/lib/utils/async.js +35 -35
- package/core/lib/utils/common/common.utils.js +237 -237
- package/core/lib/utils/common/readme.md +30 -30
- package/core/lib/utils/date/date.utils.js +295 -295
- package/core/lib/utils/date/readme.md +2 -2
- package/core/lib/utils/firebase.support.utils.js +98 -98
- package/core/lib/utils/firebase.utils.js +808 -808
- package/core/lib/utils/font-awesome.utils.js +168 -168
- package/core/lib/utils/form/form.utils.js +255 -255
- package/core/lib/utils/generic/generic.utils.js +70 -70
- package/core/lib/utils/http/auth.helper.js +95 -95
- package/core/lib/utils/http/http.utils.js +186 -186
- package/core/lib/utils/http/readme.md +14 -14
- package/core/lib/utils/index.js +43 -43
- package/core/lib/utils/location/location.utils.js +137 -137
- package/core/lib/utils/location/readme.md +18 -18
- package/core/lib/utils/modal.utils.js +15 -15
- package/core/lib/utils/notification.utils.js +34 -34
- package/core/lib/utils/pwa/pwa.utils.js +88 -88
- package/core/lib/utils/script.utils.js +235 -235
- package/core/lib/utils/setting.utils.js +68 -68
- package/core/lib/utils/upload.utils.js +29 -29
- package/core/models/Preference/Preferences.js +46 -46
- package/core/models/base/base.js +403 -403
- package/core/models/base-clone-loader.js +107 -107
- package/core/models/base-clone.js +187 -187
- package/core/models/base-loader.js +97 -97
- package/core/models/core-scripts/core-scripts.js +179 -179
- package/core/models/dashboard/dashboard.js +201 -201
- package/core/models/detail-loader.js +88 -88
- package/core/models/doctor/components/doctor-add/doctor-add.js +432 -432
- package/core/models/doctor/components/doctor-add/doctor-add.scss +32 -32
- package/core/models/groups.js +82 -82
- package/core/models/index.js +100 -100
- package/core/models/lookup-types/components/lookup-detail/lookup-detail.js +129 -129
- package/core/models/lookup-types/lookup-types.js +96 -96
- package/core/models/lookup-values/components/lookup-values-modal/lookup-values-modal.js +95 -95
- package/core/models/lookup-values/lookup-values.js +92 -92
- package/core/models/menu-roles/components/menu-roles-add/menu-roles-add.js +153 -153
- package/core/models/menu-roles/menu-roles.js +158 -158
- package/core/models/menus/components/menu-add/menu-add.js +288 -288
- package/core/models/menus/components/menu-add/menu-add.scss +31 -31
- package/core/models/menus/components/menu-detail/menu-detail.js +263 -263
- package/core/models/menus/components/menu-list/menu-list.js +392 -392
- package/core/models/menus/components/menu-lists/menu-lists.js +635 -584
- package/core/models/menus/components/menu-lists/menu-lists.scss +46 -46
- package/core/models/menus/menus.js +338 -338
- package/core/models/model-columns.js +121 -121
- package/core/models/models/components/model-detail/model-add.js +120 -120
- package/core/models/models/components/model-detail/model-detail.js +133 -133
- package/core/models/models/models.js +154 -154
- package/core/models/pages/components/page-add/page-add.js +163 -163
- package/core/models/pages/components/page-add/page-add.scss +30 -30
- package/core/models/pages/components/page-details/page-details.js +209 -209
- package/core/models/pages/components/page-list/page-list.js +248 -248
- package/core/models/pages/pages.js +142 -142
- package/core/models/pages.js +142 -142
- package/core/models/roles/components/role-add/menu-label.js +14 -14
- package/core/models/roles/components/role-add/menu-tree.js +127 -127
- package/core/models/roles/components/role-add/role-add.js +222 -222
- package/core/models/roles/components/role-add/role-add.scss +4 -4
- package/core/models/roles/components/role-list/role-list.js +406 -406
- package/core/models/roles/roles.js +196 -196
- package/core/models/staff/components/staff-add/staff-add.js +455 -455
- package/core/models/user-roles/components/user-roles-add/user-roles-add.js +149 -149
- package/core/models/user-roles/user-roles.js +113 -113
- package/core/models/users/components/assign-role/assign-role.js +428 -428
- package/core/models/users/components/assign-role/assign-role.scss +281 -281
- package/core/models/users/components/assign-role/avatar-props.js +45 -45
- package/core/models/users/components/user-add/user-add.js +847 -847
- package/core/models/users/components/user-add/user-edit.js +110 -110
- package/core/models/users/components/user-detail/user-detail.js +236 -236
- package/core/models/users/components/user-list/user-list.js +397 -397
- package/core/models/users/users.js +379 -379
- package/core/modules/Informations/change-info/change-info.js +618 -618
- package/core/modules/Informations/change-info/change-info.scss +134 -134
- package/core/modules/dashboard/components/dashboard-card/animations.js +64 -64
- package/core/modules/dashboard/components/dashboard-card/dashboard-card.js +197 -197
- package/core/modules/dashboard/components/dashboard-card/menu-dashboard-card.js +430 -430
- package/core/modules/dashboard/components/dashboard-card/menu-dashboard-card.scss +59 -59
- package/core/modules/dashboard/components/pop-query-dashboard/pop-query-dashboard.js +66 -66
- package/core/modules/generic/components/generic-add/generic-add.js +121 -121
- package/core/modules/generic/components/generic-add/generic-add.scss +13 -13
- package/core/modules/generic/components/generic-add-modal/generic-add-modal.js +125 -125
- package/core/modules/generic/components/generic-add-modal/generic-add-modal.scss +13 -13
- package/core/modules/generic/components/generic-detail/generic-detail.js +184 -184
- package/core/modules/generic/components/generic-detail/generic-detail.scss +25 -25
- package/core/modules/generic/components/generic-edit/generic-edit.js +123 -123
- package/core/modules/generic/components/generic-list/generic-list.js +335 -335
- package/core/modules/generic/components/generic-list/generic-list.scss +35 -35
- package/core/modules/index.js +42 -42
- package/core/modules/module-routes/module-routes.js +37 -37
- package/core/modules/reporting/components/index.js +6 -6
- package/core/modules/reporting/components/reporting-dashboard/README.md +316 -316
- package/core/modules/reporting/components/reporting-dashboard/adavance-search/advance-search.js +271 -271
- package/core/modules/reporting/components/reporting-dashboard/adavance-search/advance-search.scss +76 -76
- package/core/modules/reporting/components/reporting-dashboard/display-columns/build-display-columns.js +90 -90
- package/core/modules/reporting/components/reporting-dashboard/display-columns/build-display-columns.test.js +74 -74
- package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.js +449 -449
- package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.test.js +199 -199
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +1116 -1116
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.scss +215 -215
- package/core/modules/reporting/components/reporting-dashboard/reporting-table.js +519 -519
- package/core/modules/steps/action-buttons.js +92 -92
- package/core/modules/steps/action-buttons.scss +62 -62
- package/core/modules/steps/chat-assistant.js +141 -141
- package/core/modules/steps/narration.js +192 -192
- package/core/modules/steps/openai-realtime.js +275 -275
- package/core/modules/steps/progress-storage.js +140 -140
- package/core/modules/steps/readme.md +167 -167
- package/core/modules/steps/steps.js +1567 -1567
- package/core/modules/steps/steps.scss +907 -907
- package/core/modules/steps/timeline.js +56 -56
- package/core/modules/steps/voice-navigation.js +709 -709
- package/core/pages/homepage-api/homepage-api.js +106 -106
- package/core/pages/homepage-api/homepage-api.scss +233 -233
- package/core/pages/homepage-api/menu-dashboard.js +169 -169
- package/core/pages/homepage-api/menu-dashboard.scss +11 -11
- package/core/translation.json +53 -53
- package/core/translations.json +19 -19
- package/core/utils/script.utils.js +129 -129
- package/core/utils/settings.utils.js +25 -25
- package/eslint.config.mjs +79 -79
- package/index.js +35 -35
- package/jest.config.js +7 -7
- package/jest.setup.js +1 -1
- package/package.json +124 -124
- package/tsconfig.json +26 -26
- package/webpack.config.js +173 -173
|
@@ -1,713 +1,713 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* Sidemenu component
|
|
4
|
-
*
|
|
5
|
-
* @author Ashique Mohammed
|
|
6
|
-
* @co-author Sneha
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import React, { useState, useEffect, useContext, useCallback, useRef } from 'react';
|
|
12
|
-
|
|
13
|
-
import { animationControls, motion, useAnimation } from 'framer-motion';
|
|
14
|
-
|
|
15
|
-
import { GlobalContext } from './../../Store';
|
|
16
|
-
|
|
17
|
-
import { Menu, message, Skeleton, Space, Popover } from 'antd';
|
|
18
|
-
|
|
19
|
-
import FirebaseUtils from './../../utils/firebase.utils';
|
|
20
|
-
|
|
21
|
-
import { useHistory } from 'react-router-dom';
|
|
22
|
-
|
|
23
|
-
import { useTranslation, Trans } from 'react-i18next';
|
|
24
|
-
|
|
25
|
-
import { Menus } from '../../models';
|
|
26
|
-
|
|
27
|
-
import { expandFaAliases, ensureFontAwesomeAvailable } from '../../utils/font-awesome.utils';
|
|
28
|
-
|
|
29
|
-
import './sidemenu.scss';
|
|
30
|
-
|
|
31
|
-
const { SubMenu } = Menu;
|
|
32
|
-
|
|
33
|
-
const SIDEMENU_WIDTH_KEY = 'soxo:sidemenu-width';
|
|
34
|
-
const DEFAULT_SIDEMENU_WIDTH = 211;
|
|
35
|
-
const MIN_SIDEMENU_WIDTH = 180;
|
|
36
|
-
const MAX_SIDEMENU_WIDTH = 420;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Renders the menu's image if present, falling back to the Font Awesome icon.
|
|
40
|
-
* The FA icon is rendered immediately so something is always visible during
|
|
41
|
-
* a slow image fetch (e.g. cold reload, route change before the image is
|
|
42
|
-
* cached). The image takes over only after it successfully loads; if it
|
|
43
|
-
* fails, the FA icon stays.
|
|
44
|
-
*/
|
|
45
|
-
function MenuIcon({ menu, icon, size = 25 }) {
|
|
46
|
-
const src = menu && typeof menu.image_path === 'string' ? menu.image_path.trim() : '';
|
|
47
|
-
const [imageStatus, setImageStatus] = useState(src ? 'loading' : 'none');
|
|
48
|
-
const imgRef = useRef(null);
|
|
49
|
-
|
|
50
|
-
// Reset and probe whenever src changes. This covers two cases that were
|
|
51
|
-
// breaking icon rendering on direct URL navigation / reload:
|
|
52
|
-
// 1. Cached images: the browser may fire `load` synchronously before
|
|
53
|
-
// React attaches the onLoad handler, leaving status stuck at
|
|
54
|
-
// 'loading'. We re-check `img.complete` after mount.
|
|
55
|
-
// 2. Broken responses (200 with empty/corrupt body, CSP blocked, etc.)
|
|
56
|
-
// that fire `load` but with naturalWidth=0 — treat as failed so the
|
|
57
|
-
// Font Awesome fallback shows instead of an invisible <img>.
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
if (!src) {
|
|
60
|
-
setImageStatus('none');
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
const img = imgRef.current;
|
|
64
|
-
if (img && img.complete) {
|
|
65
|
-
setImageStatus(img.naturalWidth > 0 ? 'loaded' : 'failed');
|
|
66
|
-
} else {
|
|
67
|
-
setImageStatus('loading');
|
|
68
|
-
}
|
|
69
|
-
}, [src]);
|
|
70
|
-
|
|
71
|
-
const handleLoad = () => {
|
|
72
|
-
const img = imgRef.current;
|
|
73
|
-
setImageStatus(img && img.naturalWidth > 0 ? 'loaded' : 'failed');
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const showImage = src && imageStatus !== 'failed';
|
|
77
|
-
const showIcon = !showImage || imageStatus !== 'loaded';
|
|
78
|
-
|
|
79
|
-
return (
|
|
80
|
-
<>
|
|
81
|
-
{showIcon && <i className={`fa-solid fas ${expandFaAliases(icon)}`} />}
|
|
82
|
-
{showImage && (
|
|
83
|
-
<img
|
|
84
|
-
ref={imgRef}
|
|
85
|
-
style={{ width: size, display: imageStatus === 'loaded' ? 'inline-block' : 'none' }}
|
|
86
|
-
src={src}
|
|
87
|
-
alt=""
|
|
88
|
-
onLoad={handleLoad}
|
|
89
|
-
onError={() => setImageStatus('failed')}
|
|
90
|
-
/>
|
|
91
|
-
)}
|
|
92
|
-
</>
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
*
|
|
98
|
-
* @param {*} collapsed
|
|
99
|
-
* @param {*} icon
|
|
100
|
-
* @param {*} caption
|
|
101
|
-
* @returns
|
|
102
|
-
*/
|
|
103
|
-
function CollapsedIconMenu({ menu, collapsed, icon, caption }) {
|
|
104
|
-
// Import t and i18n from useTranslation
|
|
105
|
-
const { t, i18n } = useTranslation();
|
|
106
|
-
const { state } = useContext(GlobalContext);
|
|
107
|
-
const [isMobile, setIsMobile] = useState(false);
|
|
108
|
-
|
|
109
|
-
useEffect(() => {
|
|
110
|
-
const handleResize = () => {
|
|
111
|
-
setIsMobile(window.innerWidth < 768); // Common breakpoint for mobile
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
handleResize(); // Set initial value
|
|
115
|
-
window.addEventListener('resize', handleResize);
|
|
116
|
-
|
|
117
|
-
return () => window.removeEventListener('resize', handleResize);
|
|
118
|
-
}, []);
|
|
119
|
-
|
|
120
|
-
const hasCaption = typeof caption === 'string' && caption.length > 0;
|
|
121
|
-
const menuText = hasCaption ? t(caption) : '';
|
|
122
|
-
const menuContent = (
|
|
123
|
-
<>
|
|
124
|
-
{/* If value of collapsed is false show caption & icon else hiding caption and showing only icon*/}
|
|
125
|
-
{!collapsed ? (
|
|
126
|
-
<div className="menu-collapsed">
|
|
127
|
-
<div>
|
|
128
|
-
<MenuIcon menu={menu} icon={icon} />
|
|
129
|
-
</div>
|
|
130
|
-
|
|
131
|
-
<div style={{ color: state.theme.colors.leftSectionColor }}>
|
|
132
|
-
<span className="caption">
|
|
133
|
-
{menuText}
|
|
134
|
-
</span>
|
|
135
|
-
</div>
|
|
136
|
-
</div>
|
|
137
|
-
) : (
|
|
138
|
-
<div className="menu-collapsed">
|
|
139
|
-
<span className="anticon">
|
|
140
|
-
<MenuIcon menu={menu} icon={icon} />
|
|
141
|
-
</span>
|
|
142
|
-
|
|
143
|
-
<span style={{ color: state.theme.colors.colorPrimaryText, paddingLeft: '6px' }}>
|
|
144
|
-
{menuText}
|
|
145
|
-
</span>
|
|
146
|
-
</div>
|
|
147
|
-
)}
|
|
148
|
-
</>
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
// On mobile, or when the menu is collapsed (based on original logic), don't show the popover tooltip.
|
|
152
|
-
return isMobile || collapsed || !hasCaption ? (
|
|
153
|
-
menuContent
|
|
154
|
-
) : (
|
|
155
|
-
<Popover content={menuText} placement="right">
|
|
156
|
-
{menuContent}
|
|
157
|
-
</Popover>
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export default function SideMenu({ loading, modules = [], callback, appSettings, collapsed }) {
|
|
162
|
-
const { brandLogo, footerLogo, renderCustomHeader = () => {} } = appSettings;
|
|
163
|
-
|
|
164
|
-
let history = useHistory();
|
|
165
|
-
|
|
166
|
-
// const [selected, setSelected] = useState([1]);
|
|
167
|
-
|
|
168
|
-
const [selectedKeys, setSelectedKeys] = useState([]);
|
|
169
|
-
const [openKeys, setOpenKeys] = useState([]);
|
|
170
|
-
const [menu, setMenu] = useState({});
|
|
171
|
-
|
|
172
|
-
const [sidebarWidth, setSidebarWidth] = useState(() => {
|
|
173
|
-
const saved = parseInt(typeof window !== 'undefined' ? localStorage.getItem(SIDEMENU_WIDTH_KEY) : null, 10);
|
|
174
|
-
return Number.isFinite(saved) && saved >= MIN_SIDEMENU_WIDTH && saved <= MAX_SIDEMENU_WIDTH ? saved : DEFAULT_SIDEMENU_WIDTH;
|
|
175
|
-
});
|
|
176
|
-
const [isDragging, setIsDragging] = useState(false);
|
|
177
|
-
|
|
178
|
-
const { user = { locations: [] }, dispatch, state } = useContext(GlobalContext);
|
|
179
|
-
|
|
180
|
-
// Expose the current expanded sidebar width as a CSS variable so the
|
|
181
|
-
// surrounding layout (right-section, page-wrapper) can follow the drag.
|
|
182
|
-
useEffect(() => {
|
|
183
|
-
if (typeof document === 'undefined') return;
|
|
184
|
-
document.documentElement.style.setProperty('--sidemenu-width', `${sidebarWidth}px`);
|
|
185
|
-
}, [sidebarWidth]);
|
|
186
|
-
|
|
187
|
-
// Make sure Font Awesome is actually rendering glyphs in this app; if the
|
|
188
|
-
// host hasn't loaded it (or shipped a broken build), inject FA6 from a CDN
|
|
189
|
-
// so menu icons appear.
|
|
190
|
-
useEffect(() => {
|
|
191
|
-
ensureFontAwesomeAvailable();
|
|
192
|
-
}, []);
|
|
193
|
-
|
|
194
|
-
useEffect(() => {
|
|
195
|
-
if (typeof window === 'undefined') return;
|
|
196
|
-
localStorage.setItem(SIDEMENU_WIDTH_KEY, String(sidebarWidth));
|
|
197
|
-
}, [sidebarWidth]);
|
|
198
|
-
|
|
199
|
-
useEffect(() => {
|
|
200
|
-
if (!isDragging) return;
|
|
201
|
-
|
|
202
|
-
const handleMouseMove = (e) => {
|
|
203
|
-
const next = Math.min(Math.max(e.clientX, MIN_SIDEMENU_WIDTH), MAX_SIDEMENU_WIDTH);
|
|
204
|
-
setSidebarWidth(next);
|
|
205
|
-
};
|
|
206
|
-
const handleMouseUp = () => {
|
|
207
|
-
setIsDragging(false);
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
document.body.style.cursor = 'col-resize';
|
|
211
|
-
document.body.style.userSelect = 'none';
|
|
212
|
-
document.addEventListener('mousemove', handleMouseMove);
|
|
213
|
-
document.addEventListener('mouseup', handleMouseUp);
|
|
214
|
-
|
|
215
|
-
return () => {
|
|
216
|
-
document.body.style.cursor = '';
|
|
217
|
-
document.body.style.userSelect = '';
|
|
218
|
-
document.removeEventListener('mousemove', handleMouseMove);
|
|
219
|
-
document.removeEventListener('mouseup', handleMouseUp);
|
|
220
|
-
};
|
|
221
|
-
}, [isDragging]);
|
|
222
|
-
|
|
223
|
-
const handleResizeStart = useCallback((e) => {
|
|
224
|
-
e.preventDefault();
|
|
225
|
-
setIsDragging(true);
|
|
226
|
-
}, []);
|
|
227
|
-
|
|
228
|
-
useEffect(() => {
|
|
229
|
-
// Here we have to consider three cases now ,
|
|
230
|
-
// One is firebase for which it is primarly designed for
|
|
231
|
-
// Then Nura Old Schema
|
|
232
|
-
// Saudi New Schema
|
|
233
|
-
// Bringing all to one structure is the ideal
|
|
234
|
-
// if (user.role || user.ID) {
|
|
235
|
-
// setLoading(false);
|
|
236
|
-
// }
|
|
237
|
-
}, [user]);
|
|
238
|
-
|
|
239
|
-
// Keep current menu item selected after reload or navigation
|
|
240
|
-
useEffect(() => {
|
|
241
|
-
const currentPath = history.location.pathname;
|
|
242
|
-
setSelectedKeys([currentPath]);
|
|
243
|
-
}, [history.location.pathname]);
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Logout Function
|
|
247
|
-
*/
|
|
248
|
-
async function handleLogout() {
|
|
249
|
-
// let dbPtrValue = appSettings.headers.db_ptr;
|
|
250
|
-
const isEnvThemeTrue = process.env.REACT_APP_THEME === 'true';
|
|
251
|
-
|
|
252
|
-
// Only clear localStorage if theme is not 'true'
|
|
253
|
-
if (!isEnvThemeTrue) {
|
|
254
|
-
localStorage.clear();
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// localStorage.clear();
|
|
258
|
-
|
|
259
|
-
if (process.env.REACT_APP_PRIMARY_BACKEND === 'SQL') {
|
|
260
|
-
// const result = Users.logout()
|
|
261
|
-
localStorage.clear();
|
|
262
|
-
|
|
263
|
-
// localStorage.setItem('db_ptr', dbPtrValue);
|
|
264
|
-
|
|
265
|
-
let userInfo = {
|
|
266
|
-
dms: {},
|
|
267
|
-
locations: [],
|
|
268
|
-
...{ loggedCheckDone: true },
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
dispatch({ type: 'user', payload: userInfo });
|
|
272
|
-
|
|
273
|
-
history.push('/login');
|
|
274
|
-
|
|
275
|
-
message.success('You have logged out successfully.');
|
|
276
|
-
} else {
|
|
277
|
-
FirebaseUtils.logout().then(() => {
|
|
278
|
-
history.push('/login');
|
|
279
|
-
|
|
280
|
-
message.success('You have logged out successfully.');
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Menu Click Function
|
|
287
|
-
*/
|
|
288
|
-
|
|
289
|
-
// const onMenuClick = (menu, index) => {
|
|
290
|
-
// setSelected([index]);
|
|
291
|
-
|
|
292
|
-
// history.push(menu.path);
|
|
293
|
-
|
|
294
|
-
// setMenu(menu);
|
|
295
|
-
|
|
296
|
-
// callback();
|
|
297
|
-
// };
|
|
298
|
-
|
|
299
|
-
const onMenuClick = (menu, index) => {
|
|
300
|
-
const key = menu.path || `menu-${menu.id || index}`;
|
|
301
|
-
|
|
302
|
-
if (menu.isRoot) {
|
|
303
|
-
setOpenKeys([]);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
setSelectedKeys([key]);
|
|
307
|
-
|
|
308
|
-
if (menu.path) {
|
|
309
|
-
history.push(menu.path);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
setMenu(menu);
|
|
313
|
-
if (callback) callback();
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Controls submenu open/close behavior.
|
|
318
|
-
*
|
|
319
|
-
* - Detects the most recently opened menu key.
|
|
320
|
-
* - When opening a submenu, keeps only its full parent path expanded
|
|
321
|
-
* (accordion behavior).
|
|
322
|
-
* - When closing a submenu, updates state without recalculating paths.
|
|
323
|
-
*
|
|
324
|
-
* @param {string[]} keys - Currently open menu keys from the Menu component.
|
|
325
|
-
*
|
|
326
|
-
* Assumptions:
|
|
327
|
-
* - Menu keys are stable and unique.
|
|
328
|
-
* - Only one menu branch should be open at a time.
|
|
329
|
-
*/
|
|
330
|
-
const onOpenChange = (keys) => {
|
|
331
|
-
const latestOpenKey = keys.find((k) => !openKeys.includes(k));
|
|
332
|
-
|
|
333
|
-
if (!latestOpenKey) {
|
|
334
|
-
setOpenKeys(keys);
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const findPath = (items) => {
|
|
339
|
-
const sortedItems = Menus.screenMenus(items, 'order');
|
|
340
|
-
for (const item of sortedItems) {
|
|
341
|
-
const key = String(item.id || item.path || item.caption);
|
|
342
|
-
if (key === latestOpenKey) {
|
|
343
|
-
return [key];
|
|
344
|
-
}
|
|
345
|
-
if (item.sub_menus) {
|
|
346
|
-
const childPath = findPath(item.sub_menus);
|
|
347
|
-
if (childPath) {
|
|
348
|
-
return [key, ...childPath];
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
return null;
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
const path = findPath(modules);
|
|
356
|
-
if (path) {
|
|
357
|
-
setOpenKeys(path);
|
|
358
|
-
} else {
|
|
359
|
-
setOpenKeys(keys);
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Function renders the footer logo
|
|
365
|
-
*
|
|
366
|
-
* @param {*} footerLogo
|
|
367
|
-
*/
|
|
368
|
-
function renderFooter(footerLogo) {
|
|
369
|
-
return (
|
|
370
|
-
<div className={`sidebar-footer ${!collapsed ? 'open' : 'close'}`}>
|
|
371
|
-
<img className="footer-logo" src={footerLogo} alt={'footer-logo'} />
|
|
372
|
-
</div>
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
let icon;
|
|
377
|
-
|
|
378
|
-
let index = 0;
|
|
379
|
-
|
|
380
|
-
useEffect(() => {
|
|
381
|
-
// Dynamically set the CSS variables based on the current theme
|
|
382
|
-
// document.documentElement.style.setProperty('--custom-bg-color', state.theme.colors.leftSectionBackground);
|
|
383
|
-
// document.documentElement.style.setProperty('--custom-text-color', state.theme.colors.colorText);
|
|
384
|
-
}, [state.theme]);
|
|
385
|
-
|
|
386
|
-
const rootSubmenuKeys = Menus.screenMenus(modules, 'order').map((m) => m.id || m.path || m.caption);
|
|
387
|
-
|
|
388
|
-
return (
|
|
389
|
-
<div className="sidemenu">
|
|
390
|
-
{loading ? (
|
|
391
|
-
<Space className="side-loader">
|
|
392
|
-
<Skeleton.Avatar />
|
|
393
|
-
<Skeleton.Button />
|
|
394
|
-
{/* <Skeleton.Input /> */}
|
|
395
|
-
</Space>
|
|
396
|
-
) : (
|
|
397
|
-
<div
|
|
398
|
-
className="intro"
|
|
399
|
-
style={{ backgroundColor: state.theme.colors.leftSectionBackground, borderBottom: `2px solid ${state.theme.colors.borderColor}` }}
|
|
400
|
-
>
|
|
401
|
-
{/* Logo Bar */}
|
|
402
|
-
<div className="logo-wrapper">
|
|
403
|
-
{/* Changing the size of logo bar according to the value of collapsed */}
|
|
404
|
-
<img
|
|
405
|
-
className={`sidemenu-brand-logo ${!collapsed ? 'open' : 'close'}`}
|
|
406
|
-
onClick={() => {
|
|
407
|
-
history.push('/');
|
|
408
|
-
}}
|
|
409
|
-
src={brandLogo}
|
|
410
|
-
alt="Logo"
|
|
411
|
-
/>
|
|
412
|
-
|
|
413
|
-
{/* <button class="openbtn" onclick={()=>{openNav()}}>☰</button> */}
|
|
414
|
-
|
|
415
|
-
{/* If value of collapsed is true show version else not showing */}
|
|
416
|
-
|
|
417
|
-
{!collapsed && process.env.REACT_APP_package_version && (
|
|
418
|
-
<small
|
|
419
|
-
style={{
|
|
420
|
-
color: state.theme.colors.leftSectionColor,
|
|
421
|
-
}}
|
|
422
|
-
>
|
|
423
|
-
{process.env.REACT_APP_package_version}
|
|
424
|
-
</small>
|
|
425
|
-
)}
|
|
426
|
-
</div>
|
|
427
|
-
{/* Logo Bar Ends */}
|
|
428
|
-
|
|
429
|
-
{/* If value of collapsed is true render header else not rendering */}
|
|
430
|
-
|
|
431
|
-
{!collapsed ? renderCustomHeader() : ''}
|
|
432
|
-
</div>
|
|
433
|
-
)}
|
|
434
|
-
|
|
435
|
-
{/* Intro Component */}
|
|
436
|
-
{/* Intro Component Ends */}
|
|
437
|
-
|
|
438
|
-
{/* Search for Queries */}
|
|
439
|
-
|
|
440
|
-
{/* <div className="menu-search">
|
|
441
|
-
|
|
442
|
-
<small level={5}>Search</small>
|
|
443
|
-
|
|
444
|
-
<Input placeholder="Search" />
|
|
445
|
-
</div> */}
|
|
446
|
-
|
|
447
|
-
{/* Search for Queries Ends */}
|
|
448
|
-
|
|
449
|
-
<div className={`menu-item ${!collapsed ? 'open' : 'close'}`} style={{ backgroundColor: state.theme.colors.leftSectionBackground }}>
|
|
450
|
-
{loading ? (
|
|
451
|
-
<></>
|
|
452
|
-
) : (
|
|
453
|
-
<Menu
|
|
454
|
-
// selectedKeys={[selected]}
|
|
455
|
-
// style={{ width: 256 }}
|
|
456
|
-
// defaultSelectedKeys={selected}
|
|
457
|
-
// defaultOpenKeys={['']}
|
|
458
|
-
inlineCollapsed={collapsed}
|
|
459
|
-
mode="inline"
|
|
460
|
-
theme={process.env.REACT_APP_THEME}
|
|
461
|
-
selectedKeys={selectedKeys}
|
|
462
|
-
openKeys={openKeys}
|
|
463
|
-
onOpenChange={onOpenChange}
|
|
464
|
-
style={{ backgroundColor: state.theme.colors.leftSectionBackground, color: state.theme.colors.leftSectionColor }}
|
|
465
|
-
// theme={''}
|
|
466
|
-
>
|
|
467
|
-
{/* <Menu.Item
|
|
468
|
-
onClick={() => {
|
|
469
|
-
setSelected([1]);
|
|
470
|
-
history.push("/");
|
|
471
|
-
setMenu({ caption: "Home" });
|
|
472
|
-
callback();
|
|
473
|
-
}}
|
|
474
|
-
key="home-menu"
|
|
475
|
-
>
|
|
476
|
-
If value of collapsed is true show caption & icon else hiding caption and showing only icon
|
|
477
|
-
|
|
478
|
-
<CollapsedIconMenu
|
|
479
|
-
menu={menu}
|
|
480
|
-
caption="Home"
|
|
481
|
-
icon="fa-solid fa-house"
|
|
482
|
-
collapsed={collapsed}
|
|
483
|
-
/>
|
|
484
|
-
</Menu.Item> */}
|
|
485
|
-
|
|
486
|
-
{/* {
|
|
487
|
-
Menus.screenMenus(modules).map((menu, index) => {
|
|
488
|
-
|
|
489
|
-
return <MenuItem history={history} menu={menu} index={index} />
|
|
490
|
-
})
|
|
491
|
-
} */}
|
|
492
|
-
|
|
493
|
-
{Menus.screenMenus(modules, 'order')
|
|
494
|
-
|
|
495
|
-
.filter((record) => {
|
|
496
|
-
icon = record;
|
|
497
|
-
|
|
498
|
-
// Drop entries without a caption — they have no permission/label
|
|
499
|
-
// to render, so showing just an icon is misleading.
|
|
500
|
-
const hasCaption = typeof record.caption === 'string' && record.caption.trim().length > 0;
|
|
501
|
-
if (!hasCaption) return false;
|
|
502
|
-
|
|
503
|
-
if (record.id) {
|
|
504
|
-
if (record.is_visible) {
|
|
505
|
-
return true;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
if (record.icon_name) {
|
|
509
|
-
return true;
|
|
510
|
-
} else {
|
|
511
|
-
return false;
|
|
512
|
-
}
|
|
513
|
-
} else {
|
|
514
|
-
return true;
|
|
515
|
-
}
|
|
516
|
-
})
|
|
517
|
-
.map((menu, index) => {
|
|
518
|
-
// return <MenuItem menu={menu} index={index} />
|
|
519
|
-
|
|
520
|
-
let sub_menus = menu && menu.sub_menus
|
|
521
|
-
? Menus.screenMenus(menu.sub_menus).filter((s) => typeof s.caption === 'string' && s.caption.trim().length > 0)
|
|
522
|
-
: [];
|
|
523
|
-
|
|
524
|
-
if (menu && sub_menus && sub_menus.length) {
|
|
525
|
-
// let randomIndex = parseInt(Math.random() * 10000000000);
|
|
526
|
-
|
|
527
|
-
return (
|
|
528
|
-
<SubMenu
|
|
529
|
-
className="popup"
|
|
530
|
-
style={{ color: state.theme.colors.leftSectionColor }}
|
|
531
|
-
// key={`first-level-${randomIndex}-${menu.caption}`}
|
|
532
|
-
|
|
533
|
-
key={menu.id || menu.path || menu.caption}
|
|
534
|
-
title={
|
|
535
|
-
<>
|
|
536
|
-
<CollapsedIconMenu
|
|
537
|
-
menu={menu}
|
|
538
|
-
caption={menu.caption}
|
|
539
|
-
icon={menu.icon_name || 'fa-solid fas fa-user'}
|
|
540
|
-
collapsed={collapsed}
|
|
541
|
-
/>
|
|
542
|
-
</>
|
|
543
|
-
}
|
|
544
|
-
>
|
|
545
|
-
{sub_menus.map((submenu, innerIndex) => {
|
|
546
|
-
// let randomIndex = parseInt(Math.random() * 10000000000);
|
|
547
|
-
|
|
548
|
-
let third_menus = submenu && submenu.sub_menus ? Menus.screenMenus(submenu.sub_menus) : [];
|
|
549
|
-
|
|
550
|
-
if (third_menus && third_menus.length) {
|
|
551
|
-
return (
|
|
552
|
-
<SubMenu
|
|
553
|
-
className="popup"
|
|
554
|
-
// key={`second-level-${randomIndex}-${submenu.id}`}
|
|
555
|
-
|
|
556
|
-
key={submenu.id || submenu.path || submenu.caption}
|
|
557
|
-
title={
|
|
558
|
-
<span>
|
|
559
|
-
<CollapsedIconMenu
|
|
560
|
-
menu={menu}
|
|
561
|
-
caption={submenu.caption}
|
|
562
|
-
icon={submenu.icon_name || 'fa-solid fas fa-user'}
|
|
563
|
-
collapsed={collapsed}
|
|
564
|
-
/>
|
|
565
|
-
</span>
|
|
566
|
-
}
|
|
567
|
-
>
|
|
568
|
-
{third_menus.map((menu) => {
|
|
569
|
-
// let randomIndex = parseInt(Math.random() * 10000000000);
|
|
570
|
-
|
|
571
|
-
return (
|
|
572
|
-
<Menu.Item
|
|
573
|
-
// onClick={() => {
|
|
574
|
-
// onMenuClick(menu, index);
|
|
575
|
-
// }}
|
|
576
|
-
onClick={() => {
|
|
577
|
-
onMenuClick({ ...menu, parentKey: submenu.path || submenu.caption }, index);
|
|
578
|
-
}}
|
|
579
|
-
// key={`second-level-${randomIndex}-${index}`}
|
|
580
|
-
|
|
581
|
-
key={menu.path || menu.caption}
|
|
582
|
-
>
|
|
583
|
-
<CollapsedIconMenu
|
|
584
|
-
menu={menu}
|
|
585
|
-
caption={menu.caption}
|
|
586
|
-
icon={menu.icon_name || 'fa-solid fas fa-user'}
|
|
587
|
-
collapsed={collapsed}
|
|
588
|
-
/>
|
|
589
|
-
</Menu.Item>
|
|
590
|
-
);
|
|
591
|
-
})}
|
|
592
|
-
</SubMenu>
|
|
593
|
-
);
|
|
594
|
-
} else {
|
|
595
|
-
// let randomIndex = parseInt(Math.random() * 10000000000);
|
|
596
|
-
|
|
597
|
-
return (
|
|
598
|
-
<Menu.Item
|
|
599
|
-
// onClick={() => {
|
|
600
|
-
// onMenuClick(submenu, index);
|
|
601
|
-
// }}
|
|
602
|
-
onClick={() => {
|
|
603
|
-
onMenuClick({ ...submenu, parentKey: menu.path || menu.caption }, index);
|
|
604
|
-
}}
|
|
605
|
-
// key={`first-level-${randomIndex}-${innerIndex}`}
|
|
606
|
-
key={submenu.path || submenu.caption}
|
|
607
|
-
>
|
|
608
|
-
<CollapsedIconMenu
|
|
609
|
-
menu={menu}
|
|
610
|
-
caption={submenu.caption}
|
|
611
|
-
icon={submenu.icon_name || 'fa-solid fas fa-user'}
|
|
612
|
-
collapsed={collapsed}
|
|
613
|
-
/>
|
|
614
|
-
</Menu.Item>
|
|
615
|
-
);
|
|
616
|
-
}
|
|
617
|
-
})}
|
|
618
|
-
</SubMenu>
|
|
619
|
-
);
|
|
620
|
-
} else {
|
|
621
|
-
// let randomIndex = parseInt(Math.random() * 10000000000);
|
|
622
|
-
|
|
623
|
-
return (
|
|
624
|
-
<Menu.Item
|
|
625
|
-
// onClick={() => {
|
|
626
|
-
// onMenuClick(menu, index);
|
|
627
|
-
// }}
|
|
628
|
-
|
|
629
|
-
onClick={() => {
|
|
630
|
-
onMenuClick({ ...menu, parentKey: menu.path || menu.caption, isRoot: true }, index);
|
|
631
|
-
}}
|
|
632
|
-
// key={`${menu.id}-${randomIndex}`}
|
|
633
|
-
key={menu.path || menu.caption}
|
|
634
|
-
>
|
|
635
|
-
<CollapsedIconMenu menu={menu} caption={menu.caption} icon={menu.icon_name || 'fa-solid fas fa-user'} collapsed={collapsed} />
|
|
636
|
-
</Menu.Item>
|
|
637
|
-
);
|
|
638
|
-
}
|
|
639
|
-
})}
|
|
640
|
-
|
|
641
|
-
{loading ? (
|
|
642
|
-
<div class="skeleton-wrapper"></div>
|
|
643
|
-
) : (
|
|
644
|
-
<Menu.Item onClick={handleLogout} key="logout-button">
|
|
645
|
-
<CollapsedIconMenu caption="Logout" icon="fa-solid fas fa-user" collapsed={collapsed} />
|
|
646
|
-
</Menu.Item>
|
|
647
|
-
)}
|
|
648
|
-
</Menu>
|
|
649
|
-
)}
|
|
650
|
-
|
|
651
|
-
{/* Footer Logo */}
|
|
652
|
-
{/* {renderFooter(footerLogo)} */}
|
|
653
|
-
{/* Footer Logo Ends */}
|
|
654
|
-
</div>
|
|
655
|
-
|
|
656
|
-
{!collapsed && (
|
|
657
|
-
<div
|
|
658
|
-
className={`sidemenu-resize-handle${isDragging ? ' dragging' : ''}`}
|
|
659
|
-
onMouseDown={handleResizeStart}
|
|
660
|
-
role="separator"
|
|
661
|
-
aria-orientation="vertical"
|
|
662
|
-
aria-label="Resize sidebar"
|
|
663
|
-
title="Drag to resize"
|
|
664
|
-
/>
|
|
665
|
-
)}
|
|
666
|
-
</div>
|
|
667
|
-
);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
/**
|
|
671
|
-
* The Menu Item Takes a menu and creates more
|
|
672
|
-
* sub menus
|
|
673
|
-
*/
|
|
674
|
-
function MenuItem({ menu, index, history }) {
|
|
675
|
-
function renderMenus({ menu, index }) {
|
|
676
|
-
let sub_menus = [];
|
|
677
|
-
|
|
678
|
-
if (menu.sub_menus) {
|
|
679
|
-
sub_menus = Menus.screenMenus(menu.sub_menus);
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
if (menu && sub_menus && sub_menus.length) {
|
|
683
|
-
// return 'hello';
|
|
684
|
-
return (
|
|
685
|
-
<SubMenu
|
|
686
|
-
key={menu.id}
|
|
687
|
-
title={
|
|
688
|
-
<span>
|
|
689
|
-
<span>{menu.caption}</span>
|
|
690
|
-
</span>
|
|
691
|
-
}
|
|
692
|
-
>
|
|
693
|
-
{menu.sub_menus.map((menu, index) => {
|
|
694
|
-
return <MenuItem menu={menu} index={menu.id} />;
|
|
695
|
-
})}
|
|
696
|
-
</SubMenu>
|
|
697
|
-
);
|
|
698
|
-
} else {
|
|
699
|
-
return (
|
|
700
|
-
<Menu.Item
|
|
701
|
-
onClick={() => {
|
|
702
|
-
history.push(menu.path);
|
|
703
|
-
}}
|
|
704
|
-
key={`${menu.id}`}
|
|
705
|
-
>
|
|
706
|
-
{menu.caption} {menu.id}
|
|
707
|
-
</Menu.Item>
|
|
708
|
-
);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
return renderMenus({ menu, index });
|
|
713
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* Sidemenu component
|
|
4
|
+
*
|
|
5
|
+
* @author Ashique Mohammed
|
|
6
|
+
* @co-author Sneha
|
|
7
|
+
*
|
|
8
|
+
*
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React, { useState, useEffect, useContext, useCallback, useRef } from 'react';
|
|
12
|
+
|
|
13
|
+
import { animationControls, motion, useAnimation } from 'framer-motion';
|
|
14
|
+
|
|
15
|
+
import { GlobalContext } from './../../Store';
|
|
16
|
+
|
|
17
|
+
import { Menu, message, Skeleton, Space, Popover } from 'antd';
|
|
18
|
+
|
|
19
|
+
import FirebaseUtils from './../../utils/firebase.utils';
|
|
20
|
+
|
|
21
|
+
import { useHistory } from 'react-router-dom';
|
|
22
|
+
|
|
23
|
+
import { useTranslation, Trans } from 'react-i18next';
|
|
24
|
+
|
|
25
|
+
import { Menus } from '../../models';
|
|
26
|
+
|
|
27
|
+
import { expandFaAliases, ensureFontAwesomeAvailable } from '../../utils/font-awesome.utils';
|
|
28
|
+
|
|
29
|
+
import './sidemenu.scss';
|
|
30
|
+
|
|
31
|
+
const { SubMenu } = Menu;
|
|
32
|
+
|
|
33
|
+
const SIDEMENU_WIDTH_KEY = 'soxo:sidemenu-width';
|
|
34
|
+
const DEFAULT_SIDEMENU_WIDTH = 211;
|
|
35
|
+
const MIN_SIDEMENU_WIDTH = 180;
|
|
36
|
+
const MAX_SIDEMENU_WIDTH = 420;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Renders the menu's image if present, falling back to the Font Awesome icon.
|
|
40
|
+
* The FA icon is rendered immediately so something is always visible during
|
|
41
|
+
* a slow image fetch (e.g. cold reload, route change before the image is
|
|
42
|
+
* cached). The image takes over only after it successfully loads; if it
|
|
43
|
+
* fails, the FA icon stays.
|
|
44
|
+
*/
|
|
45
|
+
function MenuIcon({ menu, icon, size = 25 }) {
|
|
46
|
+
const src = menu && typeof menu.image_path === 'string' ? menu.image_path.trim() : '';
|
|
47
|
+
const [imageStatus, setImageStatus] = useState(src ? 'loading' : 'none');
|
|
48
|
+
const imgRef = useRef(null);
|
|
49
|
+
|
|
50
|
+
// Reset and probe whenever src changes. This covers two cases that were
|
|
51
|
+
// breaking icon rendering on direct URL navigation / reload:
|
|
52
|
+
// 1. Cached images: the browser may fire `load` synchronously before
|
|
53
|
+
// React attaches the onLoad handler, leaving status stuck at
|
|
54
|
+
// 'loading'. We re-check `img.complete` after mount.
|
|
55
|
+
// 2. Broken responses (200 with empty/corrupt body, CSP blocked, etc.)
|
|
56
|
+
// that fire `load` but with naturalWidth=0 — treat as failed so the
|
|
57
|
+
// Font Awesome fallback shows instead of an invisible <img>.
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!src) {
|
|
60
|
+
setImageStatus('none');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const img = imgRef.current;
|
|
64
|
+
if (img && img.complete) {
|
|
65
|
+
setImageStatus(img.naturalWidth > 0 ? 'loaded' : 'failed');
|
|
66
|
+
} else {
|
|
67
|
+
setImageStatus('loading');
|
|
68
|
+
}
|
|
69
|
+
}, [src]);
|
|
70
|
+
|
|
71
|
+
const handleLoad = () => {
|
|
72
|
+
const img = imgRef.current;
|
|
73
|
+
setImageStatus(img && img.naturalWidth > 0 ? 'loaded' : 'failed');
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const showImage = src && imageStatus !== 'failed';
|
|
77
|
+
const showIcon = !showImage || imageStatus !== 'loaded';
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<>
|
|
81
|
+
{showIcon && <i className={`fa-solid fas ${expandFaAliases(icon)}`} />}
|
|
82
|
+
{showImage && (
|
|
83
|
+
<img
|
|
84
|
+
ref={imgRef}
|
|
85
|
+
style={{ width: size, display: imageStatus === 'loaded' ? 'inline-block' : 'none' }}
|
|
86
|
+
src={src}
|
|
87
|
+
alt=""
|
|
88
|
+
onLoad={handleLoad}
|
|
89
|
+
onError={() => setImageStatus('failed')}
|
|
90
|
+
/>
|
|
91
|
+
)}
|
|
92
|
+
</>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
*
|
|
98
|
+
* @param {*} collapsed
|
|
99
|
+
* @param {*} icon
|
|
100
|
+
* @param {*} caption
|
|
101
|
+
* @returns
|
|
102
|
+
*/
|
|
103
|
+
function CollapsedIconMenu({ menu, collapsed, icon, caption }) {
|
|
104
|
+
// Import t and i18n from useTranslation
|
|
105
|
+
const { t, i18n } = useTranslation();
|
|
106
|
+
const { state } = useContext(GlobalContext);
|
|
107
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
108
|
+
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
const handleResize = () => {
|
|
111
|
+
setIsMobile(window.innerWidth < 768); // Common breakpoint for mobile
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
handleResize(); // Set initial value
|
|
115
|
+
window.addEventListener('resize', handleResize);
|
|
116
|
+
|
|
117
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
118
|
+
}, []);
|
|
119
|
+
|
|
120
|
+
const hasCaption = typeof caption === 'string' && caption.length > 0;
|
|
121
|
+
const menuText = hasCaption ? t(caption) : '';
|
|
122
|
+
const menuContent = (
|
|
123
|
+
<>
|
|
124
|
+
{/* If value of collapsed is false show caption & icon else hiding caption and showing only icon*/}
|
|
125
|
+
{!collapsed ? (
|
|
126
|
+
<div className="menu-collapsed">
|
|
127
|
+
<div>
|
|
128
|
+
<MenuIcon menu={menu} icon={icon} />
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<div style={{ color: state.theme.colors.leftSectionColor }}>
|
|
132
|
+
<span className="caption">
|
|
133
|
+
{menuText}
|
|
134
|
+
</span>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
) : (
|
|
138
|
+
<div className="menu-collapsed">
|
|
139
|
+
<span className="anticon">
|
|
140
|
+
<MenuIcon menu={menu} icon={icon} />
|
|
141
|
+
</span>
|
|
142
|
+
|
|
143
|
+
<span style={{ color: state.theme.colors.colorPrimaryText, paddingLeft: '6px' }}>
|
|
144
|
+
{menuText}
|
|
145
|
+
</span>
|
|
146
|
+
</div>
|
|
147
|
+
)}
|
|
148
|
+
</>
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// On mobile, or when the menu is collapsed (based on original logic), don't show the popover tooltip.
|
|
152
|
+
return isMobile || collapsed || !hasCaption ? (
|
|
153
|
+
menuContent
|
|
154
|
+
) : (
|
|
155
|
+
<Popover content={menuText} placement="right">
|
|
156
|
+
{menuContent}
|
|
157
|
+
</Popover>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export default function SideMenu({ loading, modules = [], callback, appSettings, collapsed }) {
|
|
162
|
+
const { brandLogo, footerLogo, renderCustomHeader = () => {} } = appSettings;
|
|
163
|
+
|
|
164
|
+
let history = useHistory();
|
|
165
|
+
|
|
166
|
+
// const [selected, setSelected] = useState([1]);
|
|
167
|
+
|
|
168
|
+
const [selectedKeys, setSelectedKeys] = useState([]);
|
|
169
|
+
const [openKeys, setOpenKeys] = useState([]);
|
|
170
|
+
const [menu, setMenu] = useState({});
|
|
171
|
+
|
|
172
|
+
const [sidebarWidth, setSidebarWidth] = useState(() => {
|
|
173
|
+
const saved = parseInt(typeof window !== 'undefined' ? localStorage.getItem(SIDEMENU_WIDTH_KEY) : null, 10);
|
|
174
|
+
return Number.isFinite(saved) && saved >= MIN_SIDEMENU_WIDTH && saved <= MAX_SIDEMENU_WIDTH ? saved : DEFAULT_SIDEMENU_WIDTH;
|
|
175
|
+
});
|
|
176
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
177
|
+
|
|
178
|
+
const { user = { locations: [] }, dispatch, state } = useContext(GlobalContext);
|
|
179
|
+
|
|
180
|
+
// Expose the current expanded sidebar width as a CSS variable so the
|
|
181
|
+
// surrounding layout (right-section, page-wrapper) can follow the drag.
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
if (typeof document === 'undefined') return;
|
|
184
|
+
document.documentElement.style.setProperty('--sidemenu-width', `${sidebarWidth}px`);
|
|
185
|
+
}, [sidebarWidth]);
|
|
186
|
+
|
|
187
|
+
// Make sure Font Awesome is actually rendering glyphs in this app; if the
|
|
188
|
+
// host hasn't loaded it (or shipped a broken build), inject FA6 from a CDN
|
|
189
|
+
// so menu icons appear.
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
ensureFontAwesomeAvailable();
|
|
192
|
+
}, []);
|
|
193
|
+
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
if (typeof window === 'undefined') return;
|
|
196
|
+
localStorage.setItem(SIDEMENU_WIDTH_KEY, String(sidebarWidth));
|
|
197
|
+
}, [sidebarWidth]);
|
|
198
|
+
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
if (!isDragging) return;
|
|
201
|
+
|
|
202
|
+
const handleMouseMove = (e) => {
|
|
203
|
+
const next = Math.min(Math.max(e.clientX, MIN_SIDEMENU_WIDTH), MAX_SIDEMENU_WIDTH);
|
|
204
|
+
setSidebarWidth(next);
|
|
205
|
+
};
|
|
206
|
+
const handleMouseUp = () => {
|
|
207
|
+
setIsDragging(false);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
document.body.style.cursor = 'col-resize';
|
|
211
|
+
document.body.style.userSelect = 'none';
|
|
212
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
213
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
214
|
+
|
|
215
|
+
return () => {
|
|
216
|
+
document.body.style.cursor = '';
|
|
217
|
+
document.body.style.userSelect = '';
|
|
218
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
219
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
220
|
+
};
|
|
221
|
+
}, [isDragging]);
|
|
222
|
+
|
|
223
|
+
const handleResizeStart = useCallback((e) => {
|
|
224
|
+
e.preventDefault();
|
|
225
|
+
setIsDragging(true);
|
|
226
|
+
}, []);
|
|
227
|
+
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
// Here we have to consider three cases now ,
|
|
230
|
+
// One is firebase for which it is primarly designed for
|
|
231
|
+
// Then Nura Old Schema
|
|
232
|
+
// Saudi New Schema
|
|
233
|
+
// Bringing all to one structure is the ideal
|
|
234
|
+
// if (user.role || user.ID) {
|
|
235
|
+
// setLoading(false);
|
|
236
|
+
// }
|
|
237
|
+
}, [user]);
|
|
238
|
+
|
|
239
|
+
// Keep current menu item selected after reload or navigation
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
const currentPath = history.location.pathname;
|
|
242
|
+
setSelectedKeys([currentPath]);
|
|
243
|
+
}, [history.location.pathname]);
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Logout Function
|
|
247
|
+
*/
|
|
248
|
+
async function handleLogout() {
|
|
249
|
+
// let dbPtrValue = appSettings.headers.db_ptr;
|
|
250
|
+
const isEnvThemeTrue = process.env.REACT_APP_THEME === 'true';
|
|
251
|
+
|
|
252
|
+
// Only clear localStorage if theme is not 'true'
|
|
253
|
+
if (!isEnvThemeTrue) {
|
|
254
|
+
localStorage.clear();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// localStorage.clear();
|
|
258
|
+
|
|
259
|
+
if (process.env.REACT_APP_PRIMARY_BACKEND === 'SQL') {
|
|
260
|
+
// const result = Users.logout()
|
|
261
|
+
localStorage.clear();
|
|
262
|
+
|
|
263
|
+
// localStorage.setItem('db_ptr', dbPtrValue);
|
|
264
|
+
|
|
265
|
+
let userInfo = {
|
|
266
|
+
dms: {},
|
|
267
|
+
locations: [],
|
|
268
|
+
...{ loggedCheckDone: true },
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
dispatch({ type: 'user', payload: userInfo });
|
|
272
|
+
|
|
273
|
+
history.push('/login');
|
|
274
|
+
|
|
275
|
+
message.success('You have logged out successfully.');
|
|
276
|
+
} else {
|
|
277
|
+
FirebaseUtils.logout().then(() => {
|
|
278
|
+
history.push('/login');
|
|
279
|
+
|
|
280
|
+
message.success('You have logged out successfully.');
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Menu Click Function
|
|
287
|
+
*/
|
|
288
|
+
|
|
289
|
+
// const onMenuClick = (menu, index) => {
|
|
290
|
+
// setSelected([index]);
|
|
291
|
+
|
|
292
|
+
// history.push(menu.path);
|
|
293
|
+
|
|
294
|
+
// setMenu(menu);
|
|
295
|
+
|
|
296
|
+
// callback();
|
|
297
|
+
// };
|
|
298
|
+
|
|
299
|
+
const onMenuClick = (menu, index) => {
|
|
300
|
+
const key = menu.path || `menu-${menu.id || index}`;
|
|
301
|
+
|
|
302
|
+
if (menu.isRoot) {
|
|
303
|
+
setOpenKeys([]);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
setSelectedKeys([key]);
|
|
307
|
+
|
|
308
|
+
if (menu.path) {
|
|
309
|
+
history.push(menu.path);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
setMenu(menu);
|
|
313
|
+
if (callback) callback();
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Controls submenu open/close behavior.
|
|
318
|
+
*
|
|
319
|
+
* - Detects the most recently opened menu key.
|
|
320
|
+
* - When opening a submenu, keeps only its full parent path expanded
|
|
321
|
+
* (accordion behavior).
|
|
322
|
+
* - When closing a submenu, updates state without recalculating paths.
|
|
323
|
+
*
|
|
324
|
+
* @param {string[]} keys - Currently open menu keys from the Menu component.
|
|
325
|
+
*
|
|
326
|
+
* Assumptions:
|
|
327
|
+
* - Menu keys are stable and unique.
|
|
328
|
+
* - Only one menu branch should be open at a time.
|
|
329
|
+
*/
|
|
330
|
+
const onOpenChange = (keys) => {
|
|
331
|
+
const latestOpenKey = keys.find((k) => !openKeys.includes(k));
|
|
332
|
+
|
|
333
|
+
if (!latestOpenKey) {
|
|
334
|
+
setOpenKeys(keys);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const findPath = (items) => {
|
|
339
|
+
const sortedItems = Menus.screenMenus(items, 'order');
|
|
340
|
+
for (const item of sortedItems) {
|
|
341
|
+
const key = String(item.id || item.path || item.caption);
|
|
342
|
+
if (key === latestOpenKey) {
|
|
343
|
+
return [key];
|
|
344
|
+
}
|
|
345
|
+
if (item.sub_menus) {
|
|
346
|
+
const childPath = findPath(item.sub_menus);
|
|
347
|
+
if (childPath) {
|
|
348
|
+
return [key, ...childPath];
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return null;
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const path = findPath(modules);
|
|
356
|
+
if (path) {
|
|
357
|
+
setOpenKeys(path);
|
|
358
|
+
} else {
|
|
359
|
+
setOpenKeys(keys);
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Function renders the footer logo
|
|
365
|
+
*
|
|
366
|
+
* @param {*} footerLogo
|
|
367
|
+
*/
|
|
368
|
+
function renderFooter(footerLogo) {
|
|
369
|
+
return (
|
|
370
|
+
<div className={`sidebar-footer ${!collapsed ? 'open' : 'close'}`}>
|
|
371
|
+
<img className="footer-logo" src={footerLogo} alt={'footer-logo'} />
|
|
372
|
+
</div>
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
let icon;
|
|
377
|
+
|
|
378
|
+
let index = 0;
|
|
379
|
+
|
|
380
|
+
useEffect(() => {
|
|
381
|
+
// Dynamically set the CSS variables based on the current theme
|
|
382
|
+
// document.documentElement.style.setProperty('--custom-bg-color', state.theme.colors.leftSectionBackground);
|
|
383
|
+
// document.documentElement.style.setProperty('--custom-text-color', state.theme.colors.colorText);
|
|
384
|
+
}, [state.theme]);
|
|
385
|
+
|
|
386
|
+
const rootSubmenuKeys = Menus.screenMenus(modules, 'order').map((m) => m.id || m.path || m.caption);
|
|
387
|
+
|
|
388
|
+
return (
|
|
389
|
+
<div className="sidemenu">
|
|
390
|
+
{loading ? (
|
|
391
|
+
<Space className="side-loader">
|
|
392
|
+
<Skeleton.Avatar />
|
|
393
|
+
<Skeleton.Button />
|
|
394
|
+
{/* <Skeleton.Input /> */}
|
|
395
|
+
</Space>
|
|
396
|
+
) : (
|
|
397
|
+
<div
|
|
398
|
+
className="intro"
|
|
399
|
+
style={{ backgroundColor: state.theme.colors.leftSectionBackground, borderBottom: `2px solid ${state.theme.colors.borderColor}` }}
|
|
400
|
+
>
|
|
401
|
+
{/* Logo Bar */}
|
|
402
|
+
<div className="logo-wrapper">
|
|
403
|
+
{/* Changing the size of logo bar according to the value of collapsed */}
|
|
404
|
+
<img
|
|
405
|
+
className={`sidemenu-brand-logo ${!collapsed ? 'open' : 'close'}`}
|
|
406
|
+
onClick={() => {
|
|
407
|
+
history.push('/');
|
|
408
|
+
}}
|
|
409
|
+
src={brandLogo}
|
|
410
|
+
alt="Logo"
|
|
411
|
+
/>
|
|
412
|
+
|
|
413
|
+
{/* <button class="openbtn" onclick={()=>{openNav()}}>☰</button> */}
|
|
414
|
+
|
|
415
|
+
{/* If value of collapsed is true show version else not showing */}
|
|
416
|
+
|
|
417
|
+
{!collapsed && process.env.REACT_APP_package_version && (
|
|
418
|
+
<small
|
|
419
|
+
style={{
|
|
420
|
+
color: state.theme.colors.leftSectionColor,
|
|
421
|
+
}}
|
|
422
|
+
>
|
|
423
|
+
{process.env.REACT_APP_package_version}
|
|
424
|
+
</small>
|
|
425
|
+
)}
|
|
426
|
+
</div>
|
|
427
|
+
{/* Logo Bar Ends */}
|
|
428
|
+
|
|
429
|
+
{/* If value of collapsed is true render header else not rendering */}
|
|
430
|
+
|
|
431
|
+
{!collapsed ? renderCustomHeader() : ''}
|
|
432
|
+
</div>
|
|
433
|
+
)}
|
|
434
|
+
|
|
435
|
+
{/* Intro Component */}
|
|
436
|
+
{/* Intro Component Ends */}
|
|
437
|
+
|
|
438
|
+
{/* Search for Queries */}
|
|
439
|
+
|
|
440
|
+
{/* <div className="menu-search">
|
|
441
|
+
|
|
442
|
+
<small level={5}>Search</small>
|
|
443
|
+
|
|
444
|
+
<Input placeholder="Search" />
|
|
445
|
+
</div> */}
|
|
446
|
+
|
|
447
|
+
{/* Search for Queries Ends */}
|
|
448
|
+
|
|
449
|
+
<div className={`menu-item ${!collapsed ? 'open' : 'close'}`} style={{ backgroundColor: state.theme.colors.leftSectionBackground }}>
|
|
450
|
+
{loading ? (
|
|
451
|
+
<></>
|
|
452
|
+
) : (
|
|
453
|
+
<Menu
|
|
454
|
+
// selectedKeys={[selected]}
|
|
455
|
+
// style={{ width: 256 }}
|
|
456
|
+
// defaultSelectedKeys={selected}
|
|
457
|
+
// defaultOpenKeys={['']}
|
|
458
|
+
inlineCollapsed={collapsed}
|
|
459
|
+
mode="inline"
|
|
460
|
+
theme={process.env.REACT_APP_THEME}
|
|
461
|
+
selectedKeys={selectedKeys}
|
|
462
|
+
openKeys={openKeys}
|
|
463
|
+
onOpenChange={onOpenChange}
|
|
464
|
+
style={{ backgroundColor: state.theme.colors.leftSectionBackground, color: state.theme.colors.leftSectionColor }}
|
|
465
|
+
// theme={''}
|
|
466
|
+
>
|
|
467
|
+
{/* <Menu.Item
|
|
468
|
+
onClick={() => {
|
|
469
|
+
setSelected([1]);
|
|
470
|
+
history.push("/");
|
|
471
|
+
setMenu({ caption: "Home" });
|
|
472
|
+
callback();
|
|
473
|
+
}}
|
|
474
|
+
key="home-menu"
|
|
475
|
+
>
|
|
476
|
+
If value of collapsed is true show caption & icon else hiding caption and showing only icon
|
|
477
|
+
|
|
478
|
+
<CollapsedIconMenu
|
|
479
|
+
menu={menu}
|
|
480
|
+
caption="Home"
|
|
481
|
+
icon="fa-solid fa-house"
|
|
482
|
+
collapsed={collapsed}
|
|
483
|
+
/>
|
|
484
|
+
</Menu.Item> */}
|
|
485
|
+
|
|
486
|
+
{/* {
|
|
487
|
+
Menus.screenMenus(modules).map((menu, index) => {
|
|
488
|
+
|
|
489
|
+
return <MenuItem history={history} menu={menu} index={index} />
|
|
490
|
+
})
|
|
491
|
+
} */}
|
|
492
|
+
|
|
493
|
+
{Menus.screenMenus(modules, 'order')
|
|
494
|
+
|
|
495
|
+
.filter((record) => {
|
|
496
|
+
icon = record;
|
|
497
|
+
|
|
498
|
+
// Drop entries without a caption — they have no permission/label
|
|
499
|
+
// to render, so showing just an icon is misleading.
|
|
500
|
+
const hasCaption = typeof record.caption === 'string' && record.caption.trim().length > 0;
|
|
501
|
+
if (!hasCaption) return false;
|
|
502
|
+
|
|
503
|
+
if (record.id) {
|
|
504
|
+
if (record.is_visible) {
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (record.icon_name) {
|
|
509
|
+
return true;
|
|
510
|
+
} else {
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
})
|
|
517
|
+
.map((menu, index) => {
|
|
518
|
+
// return <MenuItem menu={menu} index={index} />
|
|
519
|
+
|
|
520
|
+
let sub_menus = menu && menu.sub_menus
|
|
521
|
+
? Menus.screenMenus(menu.sub_menus).filter((s) => typeof s.caption === 'string' && s.caption.trim().length > 0)
|
|
522
|
+
: [];
|
|
523
|
+
|
|
524
|
+
if (menu && sub_menus && sub_menus.length) {
|
|
525
|
+
// let randomIndex = parseInt(Math.random() * 10000000000);
|
|
526
|
+
|
|
527
|
+
return (
|
|
528
|
+
<SubMenu
|
|
529
|
+
className="popup"
|
|
530
|
+
style={{ color: state.theme.colors.leftSectionColor }}
|
|
531
|
+
// key={`first-level-${randomIndex}-${menu.caption}`}
|
|
532
|
+
|
|
533
|
+
key={menu.id || menu.path || menu.caption}
|
|
534
|
+
title={
|
|
535
|
+
<>
|
|
536
|
+
<CollapsedIconMenu
|
|
537
|
+
menu={menu}
|
|
538
|
+
caption={menu.caption}
|
|
539
|
+
icon={menu.icon_name || 'fa-solid fas fa-user'}
|
|
540
|
+
collapsed={collapsed}
|
|
541
|
+
/>
|
|
542
|
+
</>
|
|
543
|
+
}
|
|
544
|
+
>
|
|
545
|
+
{sub_menus.map((submenu, innerIndex) => {
|
|
546
|
+
// let randomIndex = parseInt(Math.random() * 10000000000);
|
|
547
|
+
|
|
548
|
+
let third_menus = submenu && submenu.sub_menus ? Menus.screenMenus(submenu.sub_menus) : [];
|
|
549
|
+
|
|
550
|
+
if (third_menus && third_menus.length) {
|
|
551
|
+
return (
|
|
552
|
+
<SubMenu
|
|
553
|
+
className="popup"
|
|
554
|
+
// key={`second-level-${randomIndex}-${submenu.id}`}
|
|
555
|
+
|
|
556
|
+
key={submenu.id || submenu.path || submenu.caption}
|
|
557
|
+
title={
|
|
558
|
+
<span>
|
|
559
|
+
<CollapsedIconMenu
|
|
560
|
+
menu={menu}
|
|
561
|
+
caption={submenu.caption}
|
|
562
|
+
icon={submenu.icon_name || 'fa-solid fas fa-user'}
|
|
563
|
+
collapsed={collapsed}
|
|
564
|
+
/>
|
|
565
|
+
</span>
|
|
566
|
+
}
|
|
567
|
+
>
|
|
568
|
+
{third_menus.map((menu) => {
|
|
569
|
+
// let randomIndex = parseInt(Math.random() * 10000000000);
|
|
570
|
+
|
|
571
|
+
return (
|
|
572
|
+
<Menu.Item
|
|
573
|
+
// onClick={() => {
|
|
574
|
+
// onMenuClick(menu, index);
|
|
575
|
+
// }}
|
|
576
|
+
onClick={() => {
|
|
577
|
+
onMenuClick({ ...menu, parentKey: submenu.path || submenu.caption }, index);
|
|
578
|
+
}}
|
|
579
|
+
// key={`second-level-${randomIndex}-${index}`}
|
|
580
|
+
|
|
581
|
+
key={menu.path || menu.caption}
|
|
582
|
+
>
|
|
583
|
+
<CollapsedIconMenu
|
|
584
|
+
menu={menu}
|
|
585
|
+
caption={menu.caption}
|
|
586
|
+
icon={menu.icon_name || 'fa-solid fas fa-user'}
|
|
587
|
+
collapsed={collapsed}
|
|
588
|
+
/>
|
|
589
|
+
</Menu.Item>
|
|
590
|
+
);
|
|
591
|
+
})}
|
|
592
|
+
</SubMenu>
|
|
593
|
+
);
|
|
594
|
+
} else {
|
|
595
|
+
// let randomIndex = parseInt(Math.random() * 10000000000);
|
|
596
|
+
|
|
597
|
+
return (
|
|
598
|
+
<Menu.Item
|
|
599
|
+
// onClick={() => {
|
|
600
|
+
// onMenuClick(submenu, index);
|
|
601
|
+
// }}
|
|
602
|
+
onClick={() => {
|
|
603
|
+
onMenuClick({ ...submenu, parentKey: menu.path || menu.caption }, index);
|
|
604
|
+
}}
|
|
605
|
+
// key={`first-level-${randomIndex}-${innerIndex}`}
|
|
606
|
+
key={submenu.path || submenu.caption}
|
|
607
|
+
>
|
|
608
|
+
<CollapsedIconMenu
|
|
609
|
+
menu={menu}
|
|
610
|
+
caption={submenu.caption}
|
|
611
|
+
icon={submenu.icon_name || 'fa-solid fas fa-user'}
|
|
612
|
+
collapsed={collapsed}
|
|
613
|
+
/>
|
|
614
|
+
</Menu.Item>
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
})}
|
|
618
|
+
</SubMenu>
|
|
619
|
+
);
|
|
620
|
+
} else {
|
|
621
|
+
// let randomIndex = parseInt(Math.random() * 10000000000);
|
|
622
|
+
|
|
623
|
+
return (
|
|
624
|
+
<Menu.Item
|
|
625
|
+
// onClick={() => {
|
|
626
|
+
// onMenuClick(menu, index);
|
|
627
|
+
// }}
|
|
628
|
+
|
|
629
|
+
onClick={() => {
|
|
630
|
+
onMenuClick({ ...menu, parentKey: menu.path || menu.caption, isRoot: true }, index);
|
|
631
|
+
}}
|
|
632
|
+
// key={`${menu.id}-${randomIndex}`}
|
|
633
|
+
key={menu.path || menu.caption}
|
|
634
|
+
>
|
|
635
|
+
<CollapsedIconMenu menu={menu} caption={menu.caption} icon={menu.icon_name || 'fa-solid fas fa-user'} collapsed={collapsed} />
|
|
636
|
+
</Menu.Item>
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
})}
|
|
640
|
+
|
|
641
|
+
{loading ? (
|
|
642
|
+
<div class="skeleton-wrapper"></div>
|
|
643
|
+
) : (
|
|
644
|
+
<Menu.Item onClick={handleLogout} key="logout-button">
|
|
645
|
+
<CollapsedIconMenu caption="Logout" icon="fa-solid fas fa-user" collapsed={collapsed} />
|
|
646
|
+
</Menu.Item>
|
|
647
|
+
)}
|
|
648
|
+
</Menu>
|
|
649
|
+
)}
|
|
650
|
+
|
|
651
|
+
{/* Footer Logo */}
|
|
652
|
+
{/* {renderFooter(footerLogo)} */}
|
|
653
|
+
{/* Footer Logo Ends */}
|
|
654
|
+
</div>
|
|
655
|
+
|
|
656
|
+
{!collapsed && (
|
|
657
|
+
<div
|
|
658
|
+
className={`sidemenu-resize-handle${isDragging ? ' dragging' : ''}`}
|
|
659
|
+
onMouseDown={handleResizeStart}
|
|
660
|
+
role="separator"
|
|
661
|
+
aria-orientation="vertical"
|
|
662
|
+
aria-label="Resize sidebar"
|
|
663
|
+
title="Drag to resize"
|
|
664
|
+
/>
|
|
665
|
+
)}
|
|
666
|
+
</div>
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* The Menu Item Takes a menu and creates more
|
|
672
|
+
* sub menus
|
|
673
|
+
*/
|
|
674
|
+
function MenuItem({ menu, index, history }) {
|
|
675
|
+
function renderMenus({ menu, index }) {
|
|
676
|
+
let sub_menus = [];
|
|
677
|
+
|
|
678
|
+
if (menu.sub_menus) {
|
|
679
|
+
sub_menus = Menus.screenMenus(menu.sub_menus);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (menu && sub_menus && sub_menus.length) {
|
|
683
|
+
// return 'hello';
|
|
684
|
+
return (
|
|
685
|
+
<SubMenu
|
|
686
|
+
key={menu.id}
|
|
687
|
+
title={
|
|
688
|
+
<span>
|
|
689
|
+
<span>{menu.caption}</span>
|
|
690
|
+
</span>
|
|
691
|
+
}
|
|
692
|
+
>
|
|
693
|
+
{menu.sub_menus.map((menu, index) => {
|
|
694
|
+
return <MenuItem menu={menu} index={menu.id} />;
|
|
695
|
+
})}
|
|
696
|
+
</SubMenu>
|
|
697
|
+
);
|
|
698
|
+
} else {
|
|
699
|
+
return (
|
|
700
|
+
<Menu.Item
|
|
701
|
+
onClick={() => {
|
|
702
|
+
history.push(menu.path);
|
|
703
|
+
}}
|
|
704
|
+
key={`${menu.id}`}
|
|
705
|
+
>
|
|
706
|
+
{menu.caption} {menu.id}
|
|
707
|
+
</Menu.Item>
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
return renderMenus({ menu, index });
|
|
713
|
+
}
|