ui-soxo-bootstrap-core 2.6.55-dev.3 → 2.6.59
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 +41 -41
- package/.husky/pre-commit +11 -11
- package/.prettierrc.json +10 -10
- package/DEVELOPER_GUIDE.md +294 -294
- package/PUBLISHING.md +333 -333
- 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 +14 -14
- package/core/components/landing-api/landing-api.js +733 -728
- 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 +375 -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 +227 -227
- package/core/lib/components/consent/signature-pad.scss +23 -23
- 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 +293 -293
- package/core/lib/components/global-header/global-header.scss +399 -399
- 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 +632 -632
- 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 +341 -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 +29 -29
- 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 +667 -667
- 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 +508 -508
- package/core/lib/modules/generic/generic-list/fonts/NotoSansBold.js +8 -8
- package/core/lib/modules/generic/generic-list/fonts/NotoSansRegular.js +15 -15
- 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 +190 -190
- 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 +315 -315
- 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 +647 -647
- package/core/models/menus/components/menu-lists/menu-lists.scss +73 -73
- 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 +230 -230
- 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 +199 -199
- 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 +404 -404
- package/core/modules/reporting/components/reporting-dashboard/display-columns/display-cell-renderer.test.js +179 -179
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.js +652 -652
- package/core/modules/reporting/components/reporting-dashboard/reporting-dashboard.scss +215 -215
- package/core/modules/reporting/components/reporting-dashboard/reporting-table.js +537 -522
- 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 +1597 -1597
- package/core/modules/steps/steps.scss +1051 -1051
- 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 +125 -125
- package/tsconfig.json +26 -26
- package/webpack.config.js +173 -173
|
@@ -1,647 +1,647 @@
|
|
|
1
|
-
import React, { useState, useCallback, useEffect } from 'react';
|
|
2
|
-
import { Space, Popconfirm, Input, Drawer, Skeleton, Collapse, message, Tag, Empty } from 'antd';
|
|
3
|
-
import { ReloadOutlined, DeleteOutlined, EditOutlined, PlusCircleFilled, CopyOutlined } from '@ant-design/icons';
|
|
4
|
-
import { Button, Card, Switch, DraggableWrapper } from '../../../../lib';
|
|
5
|
-
// for draggable menu list import { DndProvider } from "react-dnd";
|
|
6
|
-
import { DndProvider } from 'react-dnd';
|
|
7
|
-
import { Link, useParams, useLocation } from 'react-router-dom';
|
|
8
|
-
import { HTML5Backend } from 'react-dnd-html5-backend';
|
|
9
|
-
|
|
10
|
-
import './menu-lists.scss';
|
|
11
|
-
|
|
12
|
-
const { Search } = Input;
|
|
13
|
-
const { Panel } = Collapse;
|
|
14
|
-
|
|
15
|
-
const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [], disableAddModal = model.disableAddModal }) => {
|
|
16
|
-
const actions = [];
|
|
17
|
-
|
|
18
|
-
match = useParams();
|
|
19
|
-
const id = match.id;
|
|
20
|
-
// const location = useLocation();
|
|
21
|
-
const step = parseInt(new URLSearchParams(location.search).get('step')) || 1;
|
|
22
|
-
|
|
23
|
-
const [records, setRecords] = useState([]);
|
|
24
|
-
const [originalRecords, setOriginalRecords] = useState([]);
|
|
25
|
-
const [loading, setLoading] = useState(false);
|
|
26
|
-
const [drawerVisible, setDrawerVisible] = useState(false);
|
|
27
|
-
const [single, setSingle] = useState({});
|
|
28
|
-
const [drawerTitle, setDrawerTitle] = useState('');
|
|
29
|
-
const [selectedRecord, setSelectedRecord] = useState(null);
|
|
30
|
-
const [query, setQuery] = useState('');
|
|
31
|
-
const [dragMode, setDragMode] = useState(false);
|
|
32
|
-
const [orderChanged, setOrderChanged] = useState(false);
|
|
33
|
-
// keys of currently-expanded top-level panels. Seeded from the search results
|
|
34
|
-
// when the query changes, but the user can freely toggle panels afterwards.
|
|
35
|
-
const [openKeys, setOpenKeys] = useState([]);
|
|
36
|
-
|
|
37
|
-
const [nextId, setNextId] = useState(10000);
|
|
38
|
-
|
|
39
|
-
useEffect(() => {
|
|
40
|
-
loadMenus();
|
|
41
|
-
}, [id]);
|
|
42
|
-
|
|
43
|
-
const loadMenus = () => {
|
|
44
|
-
setLoading(true);
|
|
45
|
-
|
|
46
|
-
model
|
|
47
|
-
.getCoreMenuLists
|
|
48
|
-
// {
|
|
49
|
-
// queries: [...additional_queries, { field: 'step', value: step }, { field: 'header_id', value: null }],
|
|
50
|
-
// }
|
|
51
|
-
()
|
|
52
|
-
.then((res) => {
|
|
53
|
-
const sorted = (res.result || []).sort((a, b) => a.order - b.order);
|
|
54
|
-
setRecords(sorted);
|
|
55
|
-
setOriginalRecords(sorted);
|
|
56
|
-
setLoading(false);
|
|
57
|
-
})
|
|
58
|
-
.catch(() => setLoading(false));
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
*
|
|
63
|
-
*/
|
|
64
|
-
const getRecords = () => {
|
|
65
|
-
let firstStepQueries = [
|
|
66
|
-
{
|
|
67
|
-
field: 'step',
|
|
68
|
-
value: step,
|
|
69
|
-
},
|
|
70
|
-
];
|
|
71
|
-
|
|
72
|
-
let config = {
|
|
73
|
-
queries: [...additional_queries, ...firstStepQueries],
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
if (!additional_queries.length) {
|
|
77
|
-
config.queries.push({
|
|
78
|
-
field: 'header_id',
|
|
79
|
-
value: null,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
setLoading(true);
|
|
84
|
-
|
|
85
|
-
// Get the records
|
|
86
|
-
return model
|
|
87
|
-
.get(config)
|
|
88
|
-
|
|
89
|
-
.then((res) => {
|
|
90
|
-
// setRecords(res.result);
|
|
91
|
-
var result = res.result;
|
|
92
|
-
|
|
93
|
-
if (Array.isArray(result)) {
|
|
94
|
-
setRecords(result.sort((a, b) => a.order - b.order));
|
|
95
|
-
// } else {
|
|
96
|
-
// setRecords([])
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
setLoading(false);
|
|
100
|
-
})
|
|
101
|
-
.catch((error) => {
|
|
102
|
-
setLoading(false);
|
|
103
|
-
|
|
104
|
-
console.log(error);
|
|
105
|
-
});
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const toggleDragMode = (checked) => {
|
|
109
|
-
setDragMode(checked);
|
|
110
|
-
if (checked) {
|
|
111
|
-
message.info('Drag mode enabled - Drag items anywhere, including the "Drop here" zones!');
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
// Function to find and remove an item from anywhere in the tree
|
|
116
|
-
const findAndRemoveItem = (items, itemId) => {
|
|
117
|
-
let foundItem = null;
|
|
118
|
-
|
|
119
|
-
const removeRecursive = (arr) => {
|
|
120
|
-
for (let i = 0; i < arr.length; i++) {
|
|
121
|
-
if (arr[i].id === itemId) {
|
|
122
|
-
foundItem = { ...arr[i] };
|
|
123
|
-
arr.splice(i, 1);
|
|
124
|
-
return true;
|
|
125
|
-
}
|
|
126
|
-
if (arr[i].sub_menus && arr[i].sub_menus.length > 0) {
|
|
127
|
-
if (removeRecursive(arr[i].sub_menus)) {
|
|
128
|
-
return true;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return false;
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const newItems = JSON.parse(JSON.stringify(items));
|
|
136
|
-
removeRecursive(newItems);
|
|
137
|
-
return { newItems, foundItem };
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
// Function to insert item at specific location
|
|
141
|
-
const insertItemAt = (items, item, targetLevel, targetParentId, targetIndex) => {
|
|
142
|
-
const newItems = JSON.parse(JSON.stringify(items));
|
|
143
|
-
|
|
144
|
-
// Update item properties for new location
|
|
145
|
-
const updatedItem = {
|
|
146
|
-
...item,
|
|
147
|
-
step: targetLevel,
|
|
148
|
-
header_id: targetParentId,
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
if (targetLevel === 1) {
|
|
152
|
-
// Insert at main level
|
|
153
|
-
newItems.splice(targetIndex, 0, updatedItem);
|
|
154
|
-
} else {
|
|
155
|
-
// Insert into nested level
|
|
156
|
-
const insertRecursive = (arr) => {
|
|
157
|
-
for (let i = 0; i < arr.length; i++) {
|
|
158
|
-
if (arr[i].id === targetParentId) {
|
|
159
|
-
if (!arr[i].sub_menus) arr[i].sub_menus = [];
|
|
160
|
-
arr[i].sub_menus.splice(targetIndex, 0, updatedItem);
|
|
161
|
-
return true;
|
|
162
|
-
}
|
|
163
|
-
if (arr[i].sub_menus && arr[i].sub_menus.length > 0) {
|
|
164
|
-
if (insertRecursive(arr[i].sub_menus)) {
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return false;
|
|
170
|
-
};
|
|
171
|
-
insertRecursive(newItems);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return newItems;
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
const handleCrossLevelMove = useCallback(
|
|
178
|
-
(draggedItem, dropTarget) => {
|
|
179
|
-
const { targetLevel, targetParentId, targetIndex } = dropTarget;
|
|
180
|
-
|
|
181
|
-
// Prevent moving item into itself or its children
|
|
182
|
-
if (draggedItem.id === targetParentId) {
|
|
183
|
-
message.warning('Cannot move item into itself');
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Prevent moving a menu into one of its own descendants
|
|
188
|
-
// (e.g. A > B, C — A cannot be dropped under B or C).
|
|
189
|
-
if (targetParentId) {
|
|
190
|
-
const draggedNode = findMenuById(records, draggedItem.id);
|
|
191
|
-
if (draggedNode && findMenuById(draggedNode.sub_menus || [], targetParentId)) {
|
|
192
|
-
message.warning('Cannot move a menu into its own sub menu');
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Remove item from original location
|
|
198
|
-
const { newItems: itemsAfterRemove, foundItem } = findAndRemoveItem(records, draggedItem.id);
|
|
199
|
-
|
|
200
|
-
if (!foundItem) {
|
|
201
|
-
message.error('Could not find item to move');
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Insert item at new location
|
|
206
|
-
const finalItems = insertItemAt(itemsAfterRemove, foundItem, targetLevel, targetParentId, targetIndex);
|
|
207
|
-
|
|
208
|
-
setRecords(finalItems);
|
|
209
|
-
setOrderChanged(true);
|
|
210
|
-
message.success(`Moved "${foundItem.caption}" to level ${targetLevel}`);
|
|
211
|
-
},
|
|
212
|
-
[records]
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
const deleteRecord = (rec) => {
|
|
216
|
-
model.delete(rec).then(loadMenus);
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
// Recursively filter the menu tree by caption. A menu is kept if its own
|
|
220
|
-
// caption matches OR any descendant matches. When the menu itself matches we
|
|
221
|
-
// keep its full subtree; when only a descendant matches we keep just the
|
|
222
|
-
// pruned path so the matching item stays reachable.
|
|
223
|
-
const filterMenus = (menus, q) => {
|
|
224
|
-
if (!q) return menus || [];
|
|
225
|
-
const lower = q.toLowerCase();
|
|
226
|
-
|
|
227
|
-
return (menus || []).reduce((acc, menu) => {
|
|
228
|
-
const selfMatch = (menu.caption || '').toLowerCase().includes(lower);
|
|
229
|
-
if (selfMatch) {
|
|
230
|
-
acc.push({ ...menu });
|
|
231
|
-
return acc;
|
|
232
|
-
}
|
|
233
|
-
const subFiltered = filterMenus(menu.sub_menus || [], q);
|
|
234
|
-
if (subFiltered.length) {
|
|
235
|
-
acc.push({ ...menu, sub_menus: subFiltered });
|
|
236
|
-
}
|
|
237
|
-
return acc;
|
|
238
|
-
}, []);
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
// Ids of items (at one level) that have sub_menus — used to auto-expand
|
|
242
|
-
// every branch leading to a search match.
|
|
243
|
-
const expandableKeys = (items) => (items || []).filter((m) => m.sub_menus && m.sub_menus.length > 0).map((m) => String(m.id));
|
|
244
|
-
|
|
245
|
-
const searchActive = !!query;
|
|
246
|
-
const visibleItems = filterMenus(records, query);
|
|
247
|
-
|
|
248
|
-
// When the query changes, seed the open panels from the search results (every
|
|
249
|
-
// branch on the path to a match) so the matched sub-menu is revealed. After
|
|
250
|
-
// that the user can collapse/expand freely via the Collapse onChange below.
|
|
251
|
-
useEffect(() => {
|
|
252
|
-
setOpenKeys(query ? expandableKeys(filterMenus(records, query)) : []);
|
|
253
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
254
|
-
}, [query]);
|
|
255
|
-
|
|
256
|
-
const onSearch = (event) => {
|
|
257
|
-
setQuery(event.target.value);
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
// ------------------------
|
|
261
|
-
// Recursive function to build payload
|
|
262
|
-
// ------------------------
|
|
263
|
-
const buildUpdatedOrderPayload = (menus, originalMenus) => {
|
|
264
|
-
const result = [];
|
|
265
|
-
|
|
266
|
-
menus.forEach((menu, index) => {
|
|
267
|
-
const original = findMenuById(originalMenus, menu.id);
|
|
268
|
-
|
|
269
|
-
const newOrder = index + 1;
|
|
270
|
-
const oldOrder = original ? original.order : null;
|
|
271
|
-
|
|
272
|
-
const orderChanged = oldOrder !== newOrder;
|
|
273
|
-
const parentChanged = original?.header_id !== menu.header_id;
|
|
274
|
-
|
|
275
|
-
// check sub menus
|
|
276
|
-
let updatedSubMenus = [];
|
|
277
|
-
if (menu.sub_menus?.length) {
|
|
278
|
-
updatedSubMenus = buildUpdatedOrderPayload(menu.sub_menus, originalMenus);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// include only if changed OR has changed children
|
|
282
|
-
if (orderChanged || parentChanged || updatedSubMenus.length) {
|
|
283
|
-
result.push({
|
|
284
|
-
id: menu.id,
|
|
285
|
-
order: newOrder,
|
|
286
|
-
header_id: menu.header_id,
|
|
287
|
-
name: menu.name,
|
|
288
|
-
...(updatedSubMenus.length && { sub_menus: updatedSubMenus }),
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
return result;
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
// ------------------------
|
|
297
|
-
// Find menu by id (recursive)
|
|
298
|
-
// ------------------------
|
|
299
|
-
const findMenuById = (menus, id) => {
|
|
300
|
-
for (const menu of menus || []) {
|
|
301
|
-
if (menu.id === id) return menu;
|
|
302
|
-
|
|
303
|
-
if (menu.sub_menus?.length) {
|
|
304
|
-
const found = findMenuById(menu.sub_menus, id);
|
|
305
|
-
if (found) return found;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
return null;
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
// ------------------------
|
|
312
|
-
// Save order function
|
|
313
|
-
// ------------------------
|
|
314
|
-
const saveOrder = () => {
|
|
315
|
-
const updatedMenus = buildUpdatedOrderPayload(records, originalRecords);
|
|
316
|
-
|
|
317
|
-
if (!updatedMenus.length) {
|
|
318
|
-
message.info('No order changes to save');
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const payload = { menus: updatedMenus };
|
|
323
|
-
|
|
324
|
-
model
|
|
325
|
-
.saveOrder({ formBody: payload })
|
|
326
|
-
.then(() => {
|
|
327
|
-
setOrderChanged(false);
|
|
328
|
-
loadMenus(); // refresh from backend
|
|
329
|
-
})
|
|
330
|
-
.catch(console.error);
|
|
331
|
-
};
|
|
332
|
-
|
|
333
|
-
const movePanel = useCallback(
|
|
334
|
-
(from, to) => {
|
|
335
|
-
if (!dragMode) return;
|
|
336
|
-
if (from === to) return;
|
|
337
|
-
|
|
338
|
-
setRecords((prevRecords) => {
|
|
339
|
-
const updated = [...prevRecords];
|
|
340
|
-
const [moved] = updated.splice(from, 1);
|
|
341
|
-
updated.splice(to, 0, moved);
|
|
342
|
-
return updated;
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
setOrderChanged(true);
|
|
346
|
-
},
|
|
347
|
-
[dragMode]
|
|
348
|
-
);
|
|
349
|
-
|
|
350
|
-
return (
|
|
351
|
-
<Card className="generic-list menu-lists">
|
|
352
|
-
<div className="menu-lists__header" style={{ marginBottom: 16 }}>
|
|
353
|
-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
|
354
|
-
<Search placeholder="Search menus & sub-menus…" allowClear style={{ width: 300, marginBottom: '0px' }} onChange={onSearch} />
|
|
355
|
-
|
|
356
|
-
<Space size="small">
|
|
357
|
-
{/* <Button onClick={getRecords} size={'small'} type="default">
|
|
358
|
-
<ReloadOutlined />
|
|
359
|
-
</Button> */}
|
|
360
|
-
|
|
361
|
-
<Switch checked={dragMode} onChange={toggleDragMode} checkedChildren="Order On" unCheckedChildren="Order Off" />
|
|
362
|
-
|
|
363
|
-
{dragMode && orderChanged && (
|
|
364
|
-
<Button onClick={saveOrder} type="primary" size="small">
|
|
365
|
-
Save Order
|
|
366
|
-
</Button>
|
|
367
|
-
)}
|
|
368
|
-
|
|
369
|
-
<Button
|
|
370
|
-
type="primary"
|
|
371
|
-
size="small"
|
|
372
|
-
onClick={() => {
|
|
373
|
-
setSelectedRecord({ step: 1, header_id: null });
|
|
374
|
-
setDrawerTitle('Create New Menu');
|
|
375
|
-
setDrawerVisible(true);
|
|
376
|
-
}}
|
|
377
|
-
>
|
|
378
|
-
Create New Menu
|
|
379
|
-
</Button>
|
|
380
|
-
</Space>
|
|
381
|
-
</div>
|
|
382
|
-
|
|
383
|
-
{dragMode && (
|
|
384
|
-
<div
|
|
385
|
-
style={{
|
|
386
|
-
padding: '8px 12px',
|
|
387
|
-
backgroundColor: '#e6f7ff',
|
|
388
|
-
borderRadius: 4,
|
|
389
|
-
border: '1px solid #91d5ff',
|
|
390
|
-
fontSize: 13,
|
|
391
|
-
}}
|
|
392
|
-
>
|
|
393
|
-
<strong>💡 Tips:</strong>
|
|
394
|
-
<ul style={{ margin: '4px 0', paddingLeft: 20 }}>
|
|
395
|
-
<li>Drag between items to reorder at the same level</li>
|
|
396
|
-
<li>Drop on the green "↳ Drop here" zone to make it a child of that item</li>
|
|
397
|
-
<li>Works at all levels - unlimited nesting supported!</li>
|
|
398
|
-
</ul>
|
|
399
|
-
</div>
|
|
400
|
-
)}
|
|
401
|
-
</div>
|
|
402
|
-
<div className="menu-lists__body">
|
|
403
|
-
{loading ? (
|
|
404
|
-
<Skeleton active />
|
|
405
|
-
) : (
|
|
406
|
-
<>
|
|
407
|
-
{/* {!view ? ( */}
|
|
408
|
-
<Card>
|
|
409
|
-
<DndProvider backend={HTML5Backend}>
|
|
410
|
-
<Collapse
|
|
411
|
-
accordion={!searchActive}
|
|
412
|
-
activeKey={openKeys}
|
|
413
|
-
onChange={(keys) => setOpenKeys(Array.isArray(keys) ? keys : keys ? [keys] : [])}
|
|
414
|
-
>
|
|
415
|
-
{visibleItems && visibleItems.length > 0 ? (
|
|
416
|
-
visibleItems.map((item, index) => (
|
|
417
|
-
<Panel
|
|
418
|
-
key={item.id}
|
|
419
|
-
header={
|
|
420
|
-
<DraggableWrapper
|
|
421
|
-
id={item.id}
|
|
422
|
-
index={index}
|
|
423
|
-
movePanel={movePanel}
|
|
424
|
-
item={item}
|
|
425
|
-
dragEnabled={dragMode}
|
|
426
|
-
level={1}
|
|
427
|
-
parentId={null}
|
|
428
|
-
onCrossLevelMove={handleCrossLevelMove}
|
|
429
|
-
canAcceptChildren={true}
|
|
430
|
-
/>
|
|
431
|
-
}
|
|
432
|
-
showArrow={item.sub_menus && item.sub_menus.length > 0}
|
|
433
|
-
extra={panelActions(item, model, setSelectedRecord, setDrawerTitle, setDrawerVisible, deleteRecord)}
|
|
434
|
-
>
|
|
435
|
-
{item.sub_menus && item.sub_menus.length > 0 && (
|
|
436
|
-
<NestedMenu
|
|
437
|
-
parentId={item.id}
|
|
438
|
-
step={item.step + 1}
|
|
439
|
-
items={item.sub_menus || []}
|
|
440
|
-
model={model}
|
|
441
|
-
dragMode={dragMode}
|
|
442
|
-
searchActive={searchActive}
|
|
443
|
-
query={query}
|
|
444
|
-
setSelectedRecord={setSelectedRecord}
|
|
445
|
-
setDrawerTitle={setDrawerTitle}
|
|
446
|
-
setDrawerVisible={setDrawerVisible}
|
|
447
|
-
deleteRecord={deleteRecord}
|
|
448
|
-
level={2}
|
|
449
|
-
onCrossLevelMove={handleCrossLevelMove}
|
|
450
|
-
onChange={(subMenus) => {
|
|
451
|
-
const updated = records.map((r) => (r.id === item.id ? { ...r, sub_menus: subMenus } : r));
|
|
452
|
-
setRecords(updated);
|
|
453
|
-
setOrderChanged(true);
|
|
454
|
-
}}
|
|
455
|
-
/>
|
|
456
|
-
)}
|
|
457
|
-
</Panel>
|
|
458
|
-
))
|
|
459
|
-
) : (
|
|
460
|
-
<div style={{ textAlign: 'center', padding: '40px 0' }}>
|
|
461
|
-
<Empty description="No Menu Items" />
|
|
462
|
-
</div>
|
|
463
|
-
)}
|
|
464
|
-
</Collapse>
|
|
465
|
-
</DndProvider>
|
|
466
|
-
</Card>
|
|
467
|
-
{/* ) : (
|
|
468
|
-
<CardList model={model} data={filtered ? filtered : records} />
|
|
469
|
-
)} */}
|
|
470
|
-
</>
|
|
471
|
-
)}
|
|
472
|
-
</div>
|
|
473
|
-
|
|
474
|
-
<Drawer title={drawerTitle} open={drawerVisible} width="80%" destroyOnClose onClose={() => setDrawerVisible(false)}>
|
|
475
|
-
{model.ModalAddComponent && (
|
|
476
|
-
<model.ModalAddComponent
|
|
477
|
-
formContent={selectedRecord}
|
|
478
|
-
match={match}
|
|
479
|
-
model={model}
|
|
480
|
-
additional_queries={additional_queries}
|
|
481
|
-
callback={() => {
|
|
482
|
-
setDrawerVisible(false);
|
|
483
|
-
loadMenus();
|
|
484
|
-
}}
|
|
485
|
-
/>
|
|
486
|
-
)}
|
|
487
|
-
</Drawer>
|
|
488
|
-
</Card>
|
|
489
|
-
);
|
|
490
|
-
};
|
|
491
|
-
/* -----------------------------------------------------------------------
|
|
492
|
-
PANEL ACTIONS
|
|
493
|
-
------------------------------------------------------------------------ */
|
|
494
|
-
function panelActions(item, model, setSelectedRecord, setDrawerTitle, setDrawerVisible, deleteRecord) {
|
|
495
|
-
return (
|
|
496
|
-
<Space onClick={(e) => e.stopPropagation()}>
|
|
497
|
-
<Tag color={item.is_visible === true ? 'green' : 'blue'}>{item.is_visible === true ? 'VISIBLE' : 'HIDDEN'}</Tag>{' '}
|
|
498
|
-
<Button
|
|
499
|
-
size="small"
|
|
500
|
-
type="dashed"
|
|
501
|
-
onClick={() => {
|
|
502
|
-
setSelectedRecord({
|
|
503
|
-
// parent menu id
|
|
504
|
-
header_id: item.id,
|
|
505
|
-
step: item.step + 1,
|
|
506
|
-
// new record
|
|
507
|
-
id: null,
|
|
508
|
-
});
|
|
509
|
-
setDrawerTitle(`Add Submenu to "${item.name}"`);
|
|
510
|
-
setDrawerVisible(true);
|
|
511
|
-
}}
|
|
512
|
-
>
|
|
513
|
-
<PlusCircleFilled />
|
|
514
|
-
Add Sub Menu
|
|
515
|
-
</Button>
|
|
516
|
-
{model.ModalAddComponent && (
|
|
517
|
-
<Button
|
|
518
|
-
size="small"
|
|
519
|
-
type="default"
|
|
520
|
-
onClick={() => {
|
|
521
|
-
setSelectedRecord(item);
|
|
522
|
-
setDrawerTitle('Edit Menu');
|
|
523
|
-
setDrawerVisible(true);
|
|
524
|
-
}}
|
|
525
|
-
>
|
|
526
|
-
<EditOutlined />
|
|
527
|
-
</Button>
|
|
528
|
-
)}
|
|
529
|
-
<Button
|
|
530
|
-
size="small"
|
|
531
|
-
type="default"
|
|
532
|
-
onClick={() => {
|
|
533
|
-
setSelectedRecord({ ...item, id: null, copy: true });
|
|
534
|
-
setDrawerTitle('Copy Menu');
|
|
535
|
-
setDrawerVisible(true);
|
|
536
|
-
}}
|
|
537
|
-
>
|
|
538
|
-
<CopyOutlined />
|
|
539
|
-
</Button>
|
|
540
|
-
<Popconfirm title="Are you sure?" onConfirm={() => deleteRecord(item)}>
|
|
541
|
-
<Button danger size="small" type="default">
|
|
542
|
-
<DeleteOutlined />
|
|
543
|
-
</Button>
|
|
544
|
-
</Popconfirm>
|
|
545
|
-
</Space>
|
|
546
|
-
);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
function NestedMenu({
|
|
550
|
-
parentId,
|
|
551
|
-
step,
|
|
552
|
-
items,
|
|
553
|
-
model,
|
|
554
|
-
dragMode,
|
|
555
|
-
searchActive,
|
|
556
|
-
query,
|
|
557
|
-
setSelectedRecord,
|
|
558
|
-
setDrawerTitle,
|
|
559
|
-
setDrawerVisible,
|
|
560
|
-
deleteRecord,
|
|
561
|
-
level,
|
|
562
|
-
onCrossLevelMove,
|
|
563
|
-
onChange,
|
|
564
|
-
}) {
|
|
565
|
-
const [localItems, setLocalItems] = useState(items);
|
|
566
|
-
// keys of currently-expanded sub-panels. Seeded from the search results when
|
|
567
|
-
// the query changes, but the user can freely toggle panels afterwards.
|
|
568
|
-
const [openKeys, setOpenKeys] = useState([]);
|
|
569
|
-
|
|
570
|
-
useEffect(() => {
|
|
571
|
-
setLocalItems(items);
|
|
572
|
-
}, [items]);
|
|
573
|
-
|
|
574
|
-
// Seed the open sub-panels from the search path when the query changes; keep
|
|
575
|
-
// the user's toggles otherwise so a search-opened panel can be closed.
|
|
576
|
-
useEffect(() => {
|
|
577
|
-
setOpenKeys(searchActive ? (items || []).filter((c) => c.sub_menus && c.sub_menus.length > 0).map((c) => String(c.id)) : []);
|
|
578
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
579
|
-
}, [query, searchActive]);
|
|
580
|
-
|
|
581
|
-
const moveSubMenu = useCallback(
|
|
582
|
-
(from, to) => {
|
|
583
|
-
if (!dragMode || from === to) return;
|
|
584
|
-
|
|
585
|
-
const updated = [...localItems];
|
|
586
|
-
const [moved] = updated.splice(from, 1);
|
|
587
|
-
updated.splice(to, 0, moved);
|
|
588
|
-
|
|
589
|
-
setLocalItems(updated);
|
|
590
|
-
onChange?.(updated);
|
|
591
|
-
},
|
|
592
|
-
[dragMode, localItems, onChange]
|
|
593
|
-
);
|
|
594
|
-
if (!localItems || localItems.length === 0) {
|
|
595
|
-
// <-- don’t render anything if no submenus
|
|
596
|
-
return null;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
return (
|
|
600
|
-
<Collapse accordion={!searchActive} activeKey={openKeys} onChange={(keys) => setOpenKeys(Array.isArray(keys) ? keys : keys ? [keys] : [])}>
|
|
601
|
-
{localItems.map((child, index) => (
|
|
602
|
-
<Panel
|
|
603
|
-
key={child.id}
|
|
604
|
-
header={
|
|
605
|
-
<DraggableWrapper
|
|
606
|
-
id={child.id}
|
|
607
|
-
index={index}
|
|
608
|
-
movePanel={moveSubMenu}
|
|
609
|
-
item={child}
|
|
610
|
-
dragEnabled={dragMode}
|
|
611
|
-
level={level}
|
|
612
|
-
parentId={parentId}
|
|
613
|
-
onCrossLevelMove={onCrossLevelMove}
|
|
614
|
-
canAcceptChildren={true}
|
|
615
|
-
/>
|
|
616
|
-
}
|
|
617
|
-
// only show arrow if sub_menus exist
|
|
618
|
-
showArrow={child.sub_menus && child.sub_menus.length > 0}
|
|
619
|
-
extra={panelActions(child, model, setSelectedRecord, setDrawerTitle, setDrawerVisible, deleteRecord)}
|
|
620
|
-
>
|
|
621
|
-
<NestedMenu
|
|
622
|
-
model={model}
|
|
623
|
-
parentId={child.id}
|
|
624
|
-
step={step + 1}
|
|
625
|
-
items={child.sub_menus || []}
|
|
626
|
-
dragMode={dragMode}
|
|
627
|
-
searchActive={searchActive}
|
|
628
|
-
query={query}
|
|
629
|
-
setSelectedRecord={setSelectedRecord}
|
|
630
|
-
setDrawerTitle={setDrawerTitle}
|
|
631
|
-
setDrawerVisible={setDrawerVisible}
|
|
632
|
-
deleteRecord={deleteRecord}
|
|
633
|
-
level={level + 1}
|
|
634
|
-
onCrossLevelMove={onCrossLevelMove}
|
|
635
|
-
onChange={(submenus) => {
|
|
636
|
-
const updated = localItems.map((item) => (item.id === child.id ? { ...item, sub_menus: submenus } : item));
|
|
637
|
-
setLocalItems(updated);
|
|
638
|
-
onChange?.(updated);
|
|
639
|
-
}}
|
|
640
|
-
/>
|
|
641
|
-
</Panel>
|
|
642
|
-
))}
|
|
643
|
-
</Collapse>
|
|
644
|
-
);
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
export default MenuLists;
|
|
1
|
+
import React, { useState, useCallback, useEffect } from 'react';
|
|
2
|
+
import { Space, Popconfirm, Input, Drawer, Skeleton, Collapse, message, Tag, Empty } from 'antd';
|
|
3
|
+
import { ReloadOutlined, DeleteOutlined, EditOutlined, PlusCircleFilled, CopyOutlined } from '@ant-design/icons';
|
|
4
|
+
import { Button, Card, Switch, DraggableWrapper } from '../../../../lib';
|
|
5
|
+
// for draggable menu list import { DndProvider } from "react-dnd";
|
|
6
|
+
import { DndProvider } from 'react-dnd';
|
|
7
|
+
import { Link, useParams, useLocation } from 'react-router-dom';
|
|
8
|
+
import { HTML5Backend } from 'react-dnd-html5-backend';
|
|
9
|
+
|
|
10
|
+
import './menu-lists.scss';
|
|
11
|
+
|
|
12
|
+
const { Search } = Input;
|
|
13
|
+
const { Panel } = Collapse;
|
|
14
|
+
|
|
15
|
+
const MenuLists = ({ model, match, relativeAdd = false, additional_queries = [], disableAddModal = model.disableAddModal }) => {
|
|
16
|
+
const actions = [];
|
|
17
|
+
|
|
18
|
+
match = useParams();
|
|
19
|
+
const id = match.id;
|
|
20
|
+
// const location = useLocation();
|
|
21
|
+
const step = parseInt(new URLSearchParams(location.search).get('step')) || 1;
|
|
22
|
+
|
|
23
|
+
const [records, setRecords] = useState([]);
|
|
24
|
+
const [originalRecords, setOriginalRecords] = useState([]);
|
|
25
|
+
const [loading, setLoading] = useState(false);
|
|
26
|
+
const [drawerVisible, setDrawerVisible] = useState(false);
|
|
27
|
+
const [single, setSingle] = useState({});
|
|
28
|
+
const [drawerTitle, setDrawerTitle] = useState('');
|
|
29
|
+
const [selectedRecord, setSelectedRecord] = useState(null);
|
|
30
|
+
const [query, setQuery] = useState('');
|
|
31
|
+
const [dragMode, setDragMode] = useState(false);
|
|
32
|
+
const [orderChanged, setOrderChanged] = useState(false);
|
|
33
|
+
// keys of currently-expanded top-level panels. Seeded from the search results
|
|
34
|
+
// when the query changes, but the user can freely toggle panels afterwards.
|
|
35
|
+
const [openKeys, setOpenKeys] = useState([]);
|
|
36
|
+
|
|
37
|
+
const [nextId, setNextId] = useState(10000);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
loadMenus();
|
|
41
|
+
}, [id]);
|
|
42
|
+
|
|
43
|
+
const loadMenus = () => {
|
|
44
|
+
setLoading(true);
|
|
45
|
+
|
|
46
|
+
model
|
|
47
|
+
.getCoreMenuLists
|
|
48
|
+
// {
|
|
49
|
+
// queries: [...additional_queries, { field: 'step', value: step }, { field: 'header_id', value: null }],
|
|
50
|
+
// }
|
|
51
|
+
()
|
|
52
|
+
.then((res) => {
|
|
53
|
+
const sorted = (res.result || []).sort((a, b) => a.order - b.order);
|
|
54
|
+
setRecords(sorted);
|
|
55
|
+
setOriginalRecords(sorted);
|
|
56
|
+
setLoading(false);
|
|
57
|
+
})
|
|
58
|
+
.catch(() => setLoading(false));
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
*
|
|
63
|
+
*/
|
|
64
|
+
const getRecords = () => {
|
|
65
|
+
let firstStepQueries = [
|
|
66
|
+
{
|
|
67
|
+
field: 'step',
|
|
68
|
+
value: step,
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
let config = {
|
|
73
|
+
queries: [...additional_queries, ...firstStepQueries],
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (!additional_queries.length) {
|
|
77
|
+
config.queries.push({
|
|
78
|
+
field: 'header_id',
|
|
79
|
+
value: null,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setLoading(true);
|
|
84
|
+
|
|
85
|
+
// Get the records
|
|
86
|
+
return model
|
|
87
|
+
.get(config)
|
|
88
|
+
|
|
89
|
+
.then((res) => {
|
|
90
|
+
// setRecords(res.result);
|
|
91
|
+
var result = res.result;
|
|
92
|
+
|
|
93
|
+
if (Array.isArray(result)) {
|
|
94
|
+
setRecords(result.sort((a, b) => a.order - b.order));
|
|
95
|
+
// } else {
|
|
96
|
+
// setRecords([])
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
setLoading(false);
|
|
100
|
+
})
|
|
101
|
+
.catch((error) => {
|
|
102
|
+
setLoading(false);
|
|
103
|
+
|
|
104
|
+
console.log(error);
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const toggleDragMode = (checked) => {
|
|
109
|
+
setDragMode(checked);
|
|
110
|
+
if (checked) {
|
|
111
|
+
message.info('Drag mode enabled - Drag items anywhere, including the "Drop here" zones!');
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Function to find and remove an item from anywhere in the tree
|
|
116
|
+
const findAndRemoveItem = (items, itemId) => {
|
|
117
|
+
let foundItem = null;
|
|
118
|
+
|
|
119
|
+
const removeRecursive = (arr) => {
|
|
120
|
+
for (let i = 0; i < arr.length; i++) {
|
|
121
|
+
if (arr[i].id === itemId) {
|
|
122
|
+
foundItem = { ...arr[i] };
|
|
123
|
+
arr.splice(i, 1);
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
if (arr[i].sub_menus && arr[i].sub_menus.length > 0) {
|
|
127
|
+
if (removeRecursive(arr[i].sub_menus)) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const newItems = JSON.parse(JSON.stringify(items));
|
|
136
|
+
removeRecursive(newItems);
|
|
137
|
+
return { newItems, foundItem };
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Function to insert item at specific location
|
|
141
|
+
const insertItemAt = (items, item, targetLevel, targetParentId, targetIndex) => {
|
|
142
|
+
const newItems = JSON.parse(JSON.stringify(items));
|
|
143
|
+
|
|
144
|
+
// Update item properties for new location
|
|
145
|
+
const updatedItem = {
|
|
146
|
+
...item,
|
|
147
|
+
step: targetLevel,
|
|
148
|
+
header_id: targetParentId,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
if (targetLevel === 1) {
|
|
152
|
+
// Insert at main level
|
|
153
|
+
newItems.splice(targetIndex, 0, updatedItem);
|
|
154
|
+
} else {
|
|
155
|
+
// Insert into nested level
|
|
156
|
+
const insertRecursive = (arr) => {
|
|
157
|
+
for (let i = 0; i < arr.length; i++) {
|
|
158
|
+
if (arr[i].id === targetParentId) {
|
|
159
|
+
if (!arr[i].sub_menus) arr[i].sub_menus = [];
|
|
160
|
+
arr[i].sub_menus.splice(targetIndex, 0, updatedItem);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
if (arr[i].sub_menus && arr[i].sub_menus.length > 0) {
|
|
164
|
+
if (insertRecursive(arr[i].sub_menus)) {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
};
|
|
171
|
+
insertRecursive(newItems);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return newItems;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const handleCrossLevelMove = useCallback(
|
|
178
|
+
(draggedItem, dropTarget) => {
|
|
179
|
+
const { targetLevel, targetParentId, targetIndex } = dropTarget;
|
|
180
|
+
|
|
181
|
+
// Prevent moving item into itself or its children
|
|
182
|
+
if (draggedItem.id === targetParentId) {
|
|
183
|
+
message.warning('Cannot move item into itself');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Prevent moving a menu into one of its own descendants
|
|
188
|
+
// (e.g. A > B, C — A cannot be dropped under B or C).
|
|
189
|
+
if (targetParentId) {
|
|
190
|
+
const draggedNode = findMenuById(records, draggedItem.id);
|
|
191
|
+
if (draggedNode && findMenuById(draggedNode.sub_menus || [], targetParentId)) {
|
|
192
|
+
message.warning('Cannot move a menu into its own sub menu');
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Remove item from original location
|
|
198
|
+
const { newItems: itemsAfterRemove, foundItem } = findAndRemoveItem(records, draggedItem.id);
|
|
199
|
+
|
|
200
|
+
if (!foundItem) {
|
|
201
|
+
message.error('Could not find item to move');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Insert item at new location
|
|
206
|
+
const finalItems = insertItemAt(itemsAfterRemove, foundItem, targetLevel, targetParentId, targetIndex);
|
|
207
|
+
|
|
208
|
+
setRecords(finalItems);
|
|
209
|
+
setOrderChanged(true);
|
|
210
|
+
message.success(`Moved "${foundItem.caption}" to level ${targetLevel}`);
|
|
211
|
+
},
|
|
212
|
+
[records]
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
const deleteRecord = (rec) => {
|
|
216
|
+
model.delete(rec).then(loadMenus);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// Recursively filter the menu tree by caption. A menu is kept if its own
|
|
220
|
+
// caption matches OR any descendant matches. When the menu itself matches we
|
|
221
|
+
// keep its full subtree; when only a descendant matches we keep just the
|
|
222
|
+
// pruned path so the matching item stays reachable.
|
|
223
|
+
const filterMenus = (menus, q) => {
|
|
224
|
+
if (!q) return menus || [];
|
|
225
|
+
const lower = q.toLowerCase();
|
|
226
|
+
|
|
227
|
+
return (menus || []).reduce((acc, menu) => {
|
|
228
|
+
const selfMatch = (menu.caption || '').toLowerCase().includes(lower);
|
|
229
|
+
if (selfMatch) {
|
|
230
|
+
acc.push({ ...menu });
|
|
231
|
+
return acc;
|
|
232
|
+
}
|
|
233
|
+
const subFiltered = filterMenus(menu.sub_menus || [], q);
|
|
234
|
+
if (subFiltered.length) {
|
|
235
|
+
acc.push({ ...menu, sub_menus: subFiltered });
|
|
236
|
+
}
|
|
237
|
+
return acc;
|
|
238
|
+
}, []);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// Ids of items (at one level) that have sub_menus — used to auto-expand
|
|
242
|
+
// every branch leading to a search match.
|
|
243
|
+
const expandableKeys = (items) => (items || []).filter((m) => m.sub_menus && m.sub_menus.length > 0).map((m) => String(m.id));
|
|
244
|
+
|
|
245
|
+
const searchActive = !!query;
|
|
246
|
+
const visibleItems = filterMenus(records, query);
|
|
247
|
+
|
|
248
|
+
// When the query changes, seed the open panels from the search results (every
|
|
249
|
+
// branch on the path to a match) so the matched sub-menu is revealed. After
|
|
250
|
+
// that the user can collapse/expand freely via the Collapse onChange below.
|
|
251
|
+
useEffect(() => {
|
|
252
|
+
setOpenKeys(query ? expandableKeys(filterMenus(records, query)) : []);
|
|
253
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
254
|
+
}, [query]);
|
|
255
|
+
|
|
256
|
+
const onSearch = (event) => {
|
|
257
|
+
setQuery(event.target.value);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// ------------------------
|
|
261
|
+
// Recursive function to build payload
|
|
262
|
+
// ------------------------
|
|
263
|
+
const buildUpdatedOrderPayload = (menus, originalMenus) => {
|
|
264
|
+
const result = [];
|
|
265
|
+
|
|
266
|
+
menus.forEach((menu, index) => {
|
|
267
|
+
const original = findMenuById(originalMenus, menu.id);
|
|
268
|
+
|
|
269
|
+
const newOrder = index + 1;
|
|
270
|
+
const oldOrder = original ? original.order : null;
|
|
271
|
+
|
|
272
|
+
const orderChanged = oldOrder !== newOrder;
|
|
273
|
+
const parentChanged = original?.header_id !== menu.header_id;
|
|
274
|
+
|
|
275
|
+
// check sub menus
|
|
276
|
+
let updatedSubMenus = [];
|
|
277
|
+
if (menu.sub_menus?.length) {
|
|
278
|
+
updatedSubMenus = buildUpdatedOrderPayload(menu.sub_menus, originalMenus);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// include only if changed OR has changed children
|
|
282
|
+
if (orderChanged || parentChanged || updatedSubMenus.length) {
|
|
283
|
+
result.push({
|
|
284
|
+
id: menu.id,
|
|
285
|
+
order: newOrder,
|
|
286
|
+
header_id: menu.header_id,
|
|
287
|
+
name: menu.name,
|
|
288
|
+
...(updatedSubMenus.length && { sub_menus: updatedSubMenus }),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
return result;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// ------------------------
|
|
297
|
+
// Find menu by id (recursive)
|
|
298
|
+
// ------------------------
|
|
299
|
+
const findMenuById = (menus, id) => {
|
|
300
|
+
for (const menu of menus || []) {
|
|
301
|
+
if (menu.id === id) return menu;
|
|
302
|
+
|
|
303
|
+
if (menu.sub_menus?.length) {
|
|
304
|
+
const found = findMenuById(menu.sub_menus, id);
|
|
305
|
+
if (found) return found;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return null;
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// ------------------------
|
|
312
|
+
// Save order function
|
|
313
|
+
// ------------------------
|
|
314
|
+
const saveOrder = () => {
|
|
315
|
+
const updatedMenus = buildUpdatedOrderPayload(records, originalRecords);
|
|
316
|
+
|
|
317
|
+
if (!updatedMenus.length) {
|
|
318
|
+
message.info('No order changes to save');
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const payload = { menus: updatedMenus };
|
|
323
|
+
|
|
324
|
+
model
|
|
325
|
+
.saveOrder({ formBody: payload })
|
|
326
|
+
.then(() => {
|
|
327
|
+
setOrderChanged(false);
|
|
328
|
+
loadMenus(); // refresh from backend
|
|
329
|
+
})
|
|
330
|
+
.catch(console.error);
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const movePanel = useCallback(
|
|
334
|
+
(from, to) => {
|
|
335
|
+
if (!dragMode) return;
|
|
336
|
+
if (from === to) return;
|
|
337
|
+
|
|
338
|
+
setRecords((prevRecords) => {
|
|
339
|
+
const updated = [...prevRecords];
|
|
340
|
+
const [moved] = updated.splice(from, 1);
|
|
341
|
+
updated.splice(to, 0, moved);
|
|
342
|
+
return updated;
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
setOrderChanged(true);
|
|
346
|
+
},
|
|
347
|
+
[dragMode]
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
<Card className="generic-list menu-lists">
|
|
352
|
+
<div className="menu-lists__header" style={{ marginBottom: 16 }}>
|
|
353
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
|
354
|
+
<Search placeholder="Search menus & sub-menus…" allowClear style={{ width: 300, marginBottom: '0px' }} onChange={onSearch} />
|
|
355
|
+
|
|
356
|
+
<Space size="small">
|
|
357
|
+
{/* <Button onClick={getRecords} size={'small'} type="default">
|
|
358
|
+
<ReloadOutlined />
|
|
359
|
+
</Button> */}
|
|
360
|
+
|
|
361
|
+
<Switch checked={dragMode} onChange={toggleDragMode} checkedChildren="Order On" unCheckedChildren="Order Off" />
|
|
362
|
+
|
|
363
|
+
{dragMode && orderChanged && (
|
|
364
|
+
<Button onClick={saveOrder} type="primary" size="small">
|
|
365
|
+
Save Order
|
|
366
|
+
</Button>
|
|
367
|
+
)}
|
|
368
|
+
|
|
369
|
+
<Button
|
|
370
|
+
type="primary"
|
|
371
|
+
size="small"
|
|
372
|
+
onClick={() => {
|
|
373
|
+
setSelectedRecord({ step: 1, header_id: null });
|
|
374
|
+
setDrawerTitle('Create New Menu');
|
|
375
|
+
setDrawerVisible(true);
|
|
376
|
+
}}
|
|
377
|
+
>
|
|
378
|
+
Create New Menu
|
|
379
|
+
</Button>
|
|
380
|
+
</Space>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
383
|
+
{dragMode && (
|
|
384
|
+
<div
|
|
385
|
+
style={{
|
|
386
|
+
padding: '8px 12px',
|
|
387
|
+
backgroundColor: '#e6f7ff',
|
|
388
|
+
borderRadius: 4,
|
|
389
|
+
border: '1px solid #91d5ff',
|
|
390
|
+
fontSize: 13,
|
|
391
|
+
}}
|
|
392
|
+
>
|
|
393
|
+
<strong>💡 Tips:</strong>
|
|
394
|
+
<ul style={{ margin: '4px 0', paddingLeft: 20 }}>
|
|
395
|
+
<li>Drag between items to reorder at the same level</li>
|
|
396
|
+
<li>Drop on the green "↳ Drop here" zone to make it a child of that item</li>
|
|
397
|
+
<li>Works at all levels - unlimited nesting supported!</li>
|
|
398
|
+
</ul>
|
|
399
|
+
</div>
|
|
400
|
+
)}
|
|
401
|
+
</div>
|
|
402
|
+
<div className="menu-lists__body">
|
|
403
|
+
{loading ? (
|
|
404
|
+
<Skeleton active />
|
|
405
|
+
) : (
|
|
406
|
+
<>
|
|
407
|
+
{/* {!view ? ( */}
|
|
408
|
+
<Card>
|
|
409
|
+
<DndProvider backend={HTML5Backend}>
|
|
410
|
+
<Collapse
|
|
411
|
+
accordion={!searchActive}
|
|
412
|
+
activeKey={openKeys}
|
|
413
|
+
onChange={(keys) => setOpenKeys(Array.isArray(keys) ? keys : keys ? [keys] : [])}
|
|
414
|
+
>
|
|
415
|
+
{visibleItems && visibleItems.length > 0 ? (
|
|
416
|
+
visibleItems.map((item, index) => (
|
|
417
|
+
<Panel
|
|
418
|
+
key={item.id}
|
|
419
|
+
header={
|
|
420
|
+
<DraggableWrapper
|
|
421
|
+
id={item.id}
|
|
422
|
+
index={index}
|
|
423
|
+
movePanel={movePanel}
|
|
424
|
+
item={item}
|
|
425
|
+
dragEnabled={dragMode}
|
|
426
|
+
level={1}
|
|
427
|
+
parentId={null}
|
|
428
|
+
onCrossLevelMove={handleCrossLevelMove}
|
|
429
|
+
canAcceptChildren={true}
|
|
430
|
+
/>
|
|
431
|
+
}
|
|
432
|
+
showArrow={item.sub_menus && item.sub_menus.length > 0}
|
|
433
|
+
extra={panelActions(item, model, setSelectedRecord, setDrawerTitle, setDrawerVisible, deleteRecord)}
|
|
434
|
+
>
|
|
435
|
+
{item.sub_menus && item.sub_menus.length > 0 && (
|
|
436
|
+
<NestedMenu
|
|
437
|
+
parentId={item.id}
|
|
438
|
+
step={item.step + 1}
|
|
439
|
+
items={item.sub_menus || []}
|
|
440
|
+
model={model}
|
|
441
|
+
dragMode={dragMode}
|
|
442
|
+
searchActive={searchActive}
|
|
443
|
+
query={query}
|
|
444
|
+
setSelectedRecord={setSelectedRecord}
|
|
445
|
+
setDrawerTitle={setDrawerTitle}
|
|
446
|
+
setDrawerVisible={setDrawerVisible}
|
|
447
|
+
deleteRecord={deleteRecord}
|
|
448
|
+
level={2}
|
|
449
|
+
onCrossLevelMove={handleCrossLevelMove}
|
|
450
|
+
onChange={(subMenus) => {
|
|
451
|
+
const updated = records.map((r) => (r.id === item.id ? { ...r, sub_menus: subMenus } : r));
|
|
452
|
+
setRecords(updated);
|
|
453
|
+
setOrderChanged(true);
|
|
454
|
+
}}
|
|
455
|
+
/>
|
|
456
|
+
)}
|
|
457
|
+
</Panel>
|
|
458
|
+
))
|
|
459
|
+
) : (
|
|
460
|
+
<div style={{ textAlign: 'center', padding: '40px 0' }}>
|
|
461
|
+
<Empty description="No Menu Items" />
|
|
462
|
+
</div>
|
|
463
|
+
)}
|
|
464
|
+
</Collapse>
|
|
465
|
+
</DndProvider>
|
|
466
|
+
</Card>
|
|
467
|
+
{/* ) : (
|
|
468
|
+
<CardList model={model} data={filtered ? filtered : records} />
|
|
469
|
+
)} */}
|
|
470
|
+
</>
|
|
471
|
+
)}
|
|
472
|
+
</div>
|
|
473
|
+
|
|
474
|
+
<Drawer title={drawerTitle} open={drawerVisible} width="80%" destroyOnClose onClose={() => setDrawerVisible(false)}>
|
|
475
|
+
{model.ModalAddComponent && (
|
|
476
|
+
<model.ModalAddComponent
|
|
477
|
+
formContent={selectedRecord}
|
|
478
|
+
match={match}
|
|
479
|
+
model={model}
|
|
480
|
+
additional_queries={additional_queries}
|
|
481
|
+
callback={() => {
|
|
482
|
+
setDrawerVisible(false);
|
|
483
|
+
loadMenus();
|
|
484
|
+
}}
|
|
485
|
+
/>
|
|
486
|
+
)}
|
|
487
|
+
</Drawer>
|
|
488
|
+
</Card>
|
|
489
|
+
);
|
|
490
|
+
};
|
|
491
|
+
/* -----------------------------------------------------------------------
|
|
492
|
+
PANEL ACTIONS
|
|
493
|
+
------------------------------------------------------------------------ */
|
|
494
|
+
function panelActions(item, model, setSelectedRecord, setDrawerTitle, setDrawerVisible, deleteRecord) {
|
|
495
|
+
return (
|
|
496
|
+
<Space onClick={(e) => e.stopPropagation()}>
|
|
497
|
+
<Tag color={item.is_visible === true ? 'green' : 'blue'}>{item.is_visible === true ? 'VISIBLE' : 'HIDDEN'}</Tag>{' '}
|
|
498
|
+
<Button
|
|
499
|
+
size="small"
|
|
500
|
+
type="dashed"
|
|
501
|
+
onClick={() => {
|
|
502
|
+
setSelectedRecord({
|
|
503
|
+
// parent menu id
|
|
504
|
+
header_id: item.id,
|
|
505
|
+
step: item.step + 1,
|
|
506
|
+
// new record
|
|
507
|
+
id: null,
|
|
508
|
+
});
|
|
509
|
+
setDrawerTitle(`Add Submenu to "${item.name}"`);
|
|
510
|
+
setDrawerVisible(true);
|
|
511
|
+
}}
|
|
512
|
+
>
|
|
513
|
+
<PlusCircleFilled />
|
|
514
|
+
Add Sub Menu
|
|
515
|
+
</Button>
|
|
516
|
+
{model.ModalAddComponent && (
|
|
517
|
+
<Button
|
|
518
|
+
size="small"
|
|
519
|
+
type="default"
|
|
520
|
+
onClick={() => {
|
|
521
|
+
setSelectedRecord(item);
|
|
522
|
+
setDrawerTitle('Edit Menu');
|
|
523
|
+
setDrawerVisible(true);
|
|
524
|
+
}}
|
|
525
|
+
>
|
|
526
|
+
<EditOutlined />
|
|
527
|
+
</Button>
|
|
528
|
+
)}
|
|
529
|
+
<Button
|
|
530
|
+
size="small"
|
|
531
|
+
type="default"
|
|
532
|
+
onClick={() => {
|
|
533
|
+
setSelectedRecord({ ...item, id: null, copy: true });
|
|
534
|
+
setDrawerTitle('Copy Menu');
|
|
535
|
+
setDrawerVisible(true);
|
|
536
|
+
}}
|
|
537
|
+
>
|
|
538
|
+
<CopyOutlined />
|
|
539
|
+
</Button>
|
|
540
|
+
<Popconfirm title="Are you sure?" onConfirm={() => deleteRecord(item)}>
|
|
541
|
+
<Button danger size="small" type="default">
|
|
542
|
+
<DeleteOutlined />
|
|
543
|
+
</Button>
|
|
544
|
+
</Popconfirm>
|
|
545
|
+
</Space>
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function NestedMenu({
|
|
550
|
+
parentId,
|
|
551
|
+
step,
|
|
552
|
+
items,
|
|
553
|
+
model,
|
|
554
|
+
dragMode,
|
|
555
|
+
searchActive,
|
|
556
|
+
query,
|
|
557
|
+
setSelectedRecord,
|
|
558
|
+
setDrawerTitle,
|
|
559
|
+
setDrawerVisible,
|
|
560
|
+
deleteRecord,
|
|
561
|
+
level,
|
|
562
|
+
onCrossLevelMove,
|
|
563
|
+
onChange,
|
|
564
|
+
}) {
|
|
565
|
+
const [localItems, setLocalItems] = useState(items);
|
|
566
|
+
// keys of currently-expanded sub-panels. Seeded from the search results when
|
|
567
|
+
// the query changes, but the user can freely toggle panels afterwards.
|
|
568
|
+
const [openKeys, setOpenKeys] = useState([]);
|
|
569
|
+
|
|
570
|
+
useEffect(() => {
|
|
571
|
+
setLocalItems(items);
|
|
572
|
+
}, [items]);
|
|
573
|
+
|
|
574
|
+
// Seed the open sub-panels from the search path when the query changes; keep
|
|
575
|
+
// the user's toggles otherwise so a search-opened panel can be closed.
|
|
576
|
+
useEffect(() => {
|
|
577
|
+
setOpenKeys(searchActive ? (items || []).filter((c) => c.sub_menus && c.sub_menus.length > 0).map((c) => String(c.id)) : []);
|
|
578
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
579
|
+
}, [query, searchActive]);
|
|
580
|
+
|
|
581
|
+
const moveSubMenu = useCallback(
|
|
582
|
+
(from, to) => {
|
|
583
|
+
if (!dragMode || from === to) return;
|
|
584
|
+
|
|
585
|
+
const updated = [...localItems];
|
|
586
|
+
const [moved] = updated.splice(from, 1);
|
|
587
|
+
updated.splice(to, 0, moved);
|
|
588
|
+
|
|
589
|
+
setLocalItems(updated);
|
|
590
|
+
onChange?.(updated);
|
|
591
|
+
},
|
|
592
|
+
[dragMode, localItems, onChange]
|
|
593
|
+
);
|
|
594
|
+
if (!localItems || localItems.length === 0) {
|
|
595
|
+
// <-- don’t render anything if no submenus
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return (
|
|
600
|
+
<Collapse accordion={!searchActive} activeKey={openKeys} onChange={(keys) => setOpenKeys(Array.isArray(keys) ? keys : keys ? [keys] : [])}>
|
|
601
|
+
{localItems.map((child, index) => (
|
|
602
|
+
<Panel
|
|
603
|
+
key={child.id}
|
|
604
|
+
header={
|
|
605
|
+
<DraggableWrapper
|
|
606
|
+
id={child.id}
|
|
607
|
+
index={index}
|
|
608
|
+
movePanel={moveSubMenu}
|
|
609
|
+
item={child}
|
|
610
|
+
dragEnabled={dragMode}
|
|
611
|
+
level={level}
|
|
612
|
+
parentId={parentId}
|
|
613
|
+
onCrossLevelMove={onCrossLevelMove}
|
|
614
|
+
canAcceptChildren={true}
|
|
615
|
+
/>
|
|
616
|
+
}
|
|
617
|
+
// only show arrow if sub_menus exist
|
|
618
|
+
showArrow={child.sub_menus && child.sub_menus.length > 0}
|
|
619
|
+
extra={panelActions(child, model, setSelectedRecord, setDrawerTitle, setDrawerVisible, deleteRecord)}
|
|
620
|
+
>
|
|
621
|
+
<NestedMenu
|
|
622
|
+
model={model}
|
|
623
|
+
parentId={child.id}
|
|
624
|
+
step={step + 1}
|
|
625
|
+
items={child.sub_menus || []}
|
|
626
|
+
dragMode={dragMode}
|
|
627
|
+
searchActive={searchActive}
|
|
628
|
+
query={query}
|
|
629
|
+
setSelectedRecord={setSelectedRecord}
|
|
630
|
+
setDrawerTitle={setDrawerTitle}
|
|
631
|
+
setDrawerVisible={setDrawerVisible}
|
|
632
|
+
deleteRecord={deleteRecord}
|
|
633
|
+
level={level + 1}
|
|
634
|
+
onCrossLevelMove={onCrossLevelMove}
|
|
635
|
+
onChange={(submenus) => {
|
|
636
|
+
const updated = localItems.map((item) => (item.id === child.id ? { ...item, sub_menus: submenus } : item));
|
|
637
|
+
setLocalItems(updated);
|
|
638
|
+
onChange?.(updated);
|
|
639
|
+
}}
|
|
640
|
+
/>
|
|
641
|
+
</Panel>
|
|
642
|
+
))}
|
|
643
|
+
</Collapse>
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
export default MenuLists;
|