@maxal_studio/kratosjs-react 1.0.0
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/README.md +44 -0
- package/dist/FieldRenderer.d.ts +13 -0
- package/dist/FieldRenderer.js +62 -0
- package/dist/FormRenderer.d.ts +7 -0
- package/dist/FormRenderer.js +78 -0
- package/dist/TableRenderer.d.ts +2 -0
- package/dist/TableRenderer.js +1 -0
- package/dist/api/actionsApi.d.ts +23 -0
- package/dist/api/actionsApi.js +46 -0
- package/dist/api/authenticatedFetch.d.ts +8 -0
- package/dist/api/authenticatedFetch.js +31 -0
- package/dist/api/exportApi.d.ts +18 -0
- package/dist/api/exportApi.js +50 -0
- package/dist/api/http.d.ts +24 -0
- package/dist/api/http.js +52 -0
- package/dist/api/resourceApi.d.ts +37 -0
- package/dist/api/resourceApi.js +52 -0
- package/dist/api/tableApi.d.ts +83 -0
- package/dist/api/tableApi.js +51 -0
- package/dist/api/urls.d.ts +19 -0
- package/dist/api/urls.js +46 -0
- package/dist/app.d.ts +101 -0
- package/dist/app.js +89 -0
- package/dist/auth/AuthContext.d.ts +22 -0
- package/dist/auth/AuthContext.js +147 -0
- package/dist/auth/LoginPage.d.ts +10 -0
- package/dist/auth/LoginPage.js +179 -0
- package/dist/auth/ProtectedRoute.d.ts +12 -0
- package/dist/auth/ProtectedRoute.js +22 -0
- package/dist/auth/authApiClient.d.ts +24 -0
- package/dist/auth/authApiClient.js +95 -0
- package/dist/auth/types.d.ts +103 -0
- package/dist/auth/types.js +1 -0
- package/dist/components/ActionFormModal.d.ts +22 -0
- package/dist/components/ActionFormModal.js +8 -0
- package/dist/components/AdminPanel.d.ts +11 -0
- package/dist/components/AdminPanel.js +194 -0
- package/dist/components/Checkbox.d.ts +10 -0
- package/dist/components/Checkbox.js +8 -0
- package/dist/components/CheckboxField.d.ts +7 -0
- package/dist/components/CheckboxField.js +26 -0
- package/dist/components/ColorPickerField.d.ts +7 -0
- package/dist/components/ColorPickerField.js +26 -0
- package/dist/components/DateTimePickerField.d.ts +7 -0
- package/dist/components/DateTimePickerField.js +64 -0
- package/dist/components/FileUploadField.d.ts +9 -0
- package/dist/components/FileUploadField.js +478 -0
- package/dist/components/GlobalSearch.d.ts +22 -0
- package/dist/components/GlobalSearch.js +181 -0
- package/dist/components/GroupField.d.ts +7 -0
- package/dist/components/GroupField.js +23 -0
- package/dist/components/HiddenField.d.ts +3 -0
- package/dist/components/HiddenField.js +10 -0
- package/dist/components/ModalBreadcrumb.d.ts +5 -0
- package/dist/components/ModalBreadcrumb.js +33 -0
- package/dist/components/ModalDrawer.d.ts +15 -0
- package/dist/components/ModalDrawer.js +40 -0
- package/dist/components/RadioField.d.ts +7 -0
- package/dist/components/RadioField.js +26 -0
- package/dist/components/RepeaterField.d.ts +3 -0
- package/dist/components/RepeaterField.js +191 -0
- package/dist/components/ResourceModalRenderer.d.ts +10 -0
- package/dist/components/ResourceModalRenderer.js +80 -0
- package/dist/components/RichEditorField.d.ts +3 -0
- package/dist/components/RichEditorField.js +655 -0
- package/dist/components/SectionField.d.ts +9 -0
- package/dist/components/SectionField.js +111 -0
- package/dist/components/SelectField.d.ts +8 -0
- package/dist/components/SelectField.js +523 -0
- package/dist/components/TabsField.d.ts +10 -0
- package/dist/components/TabsField.js +214 -0
- package/dist/components/TagsInputField.d.ts +7 -0
- package/dist/components/TagsInputField.js +172 -0
- package/dist/components/TextInputField.d.ts +7 -0
- package/dist/components/TextInputField.js +44 -0
- package/dist/components/TextareaField.d.ts +7 -0
- package/dist/components/TextareaField.js +31 -0
- package/dist/components/ToggleField.d.ts +7 -0
- package/dist/components/ToggleField.js +57 -0
- package/dist/components/ViewModal.d.ts +25 -0
- package/dist/components/ViewModal.js +159 -0
- package/dist/components/blocks/BlockRenderer.d.ts +7 -0
- package/dist/components/blocks/BlockRenderer.js +36 -0
- package/dist/components/blocks/FormBlockRenderer.d.ts +6 -0
- package/dist/components/blocks/FormBlockRenderer.js +110 -0
- package/dist/components/blocks/TableBlockRenderer.d.ts +6 -0
- package/dist/components/blocks/TableBlockRenderer.js +12 -0
- package/dist/components/blocks/TabsBlockRenderer.d.ts +7 -0
- package/dist/components/blocks/TabsBlockRenderer.js +11 -0
- package/dist/components/blocks/WidgetBlockRenderer.d.ts +6 -0
- package/dist/components/blocks/WidgetBlockRenderer.js +11 -0
- package/dist/components/columns/CheckboxColumnComponent.d.ts +6 -0
- package/dist/components/columns/CheckboxColumnComponent.js +21 -0
- package/dist/components/columns/ColorColumnComponent.d.ts +3 -0
- package/dist/components/columns/ColorColumnComponent.js +11 -0
- package/dist/components/columns/DeeplinkWrapper.d.ts +15 -0
- package/dist/components/columns/DeeplinkWrapper.js +85 -0
- package/dist/components/columns/IconColumnComponent.d.ts +3 -0
- package/dist/components/columns/IconColumnComponent.js +52 -0
- package/dist/components/columns/ImageColumnComponent.d.ts +3 -0
- package/dist/components/columns/ImageColumnComponent.js +98 -0
- package/dist/components/columns/MediaColumnComponent.d.ts +3 -0
- package/dist/components/columns/MediaColumnComponent.js +160 -0
- package/dist/components/columns/SelectColumnComponent.d.ts +6 -0
- package/dist/components/columns/SelectColumnComponent.js +26 -0
- package/dist/components/columns/TagsColumnComponent.d.ts +3 -0
- package/dist/components/columns/TagsColumnComponent.js +18 -0
- package/dist/components/columns/TextColumnComponent.d.ts +11 -0
- package/dist/components/columns/TextColumnComponent.js +107 -0
- package/dist/components/columns/TextInputColumnComponent.d.ts +6 -0
- package/dist/components/columns/TextInputColumnComponent.js +18 -0
- package/dist/components/columns/ToggleColumnComponent.d.ts +6 -0
- package/dist/components/columns/ToggleColumnComponent.js +25 -0
- package/dist/components/columns/VideoColumnComponent.d.ts +3 -0
- package/dist/components/columns/VideoColumnComponent.js +125 -0
- package/dist/components/columns/ViewColumnComponent.d.ts +3 -0
- package/dist/components/columns/ViewColumnComponent.js +7 -0
- package/dist/components/errors/ErrorBoundary.d.ts +23 -0
- package/dist/components/errors/ErrorBoundary.js +33 -0
- package/dist/components/filters/CustomFilterComponent.d.ts +10 -0
- package/dist/components/filters/CustomFilterComponent.js +33 -0
- package/dist/components/filters/DateFilterComponent.d.ts +15 -0
- package/dist/components/filters/DateFilterComponent.js +132 -0
- package/dist/components/filters/QueryBuilderFilterComponent.d.ts +11 -0
- package/dist/components/filters/QueryBuilderFilterComponent.js +200 -0
- package/dist/components/layout/Header.d.ts +10 -0
- package/dist/components/layout/Header.js +70 -0
- package/dist/components/layout/PanelBrandMark.d.ts +8 -0
- package/dist/components/layout/PanelBrandMark.js +28 -0
- package/dist/components/layout/Sidebar.d.ts +35 -0
- package/dist/components/layout/Sidebar.js +125 -0
- package/dist/components/modals/RelationCreateModal.d.ts +19 -0
- package/dist/components/modals/RelationCreateModal.js +57 -0
- package/dist/components/modals/ResourceFormModal.d.ts +37 -0
- package/dist/components/modals/ResourceFormModal.js +44 -0
- package/dist/components/modals/useResourceForm.d.ts +40 -0
- package/dist/components/modals/useResourceForm.js +138 -0
- package/dist/components/modals/view/RecordActions.d.ts +17 -0
- package/dist/components/modals/view/RecordActions.js +16 -0
- package/dist/components/modals/view/RecordDetails.d.ts +13 -0
- package/dist/components/modals/view/RecordDetails.js +29 -0
- package/dist/components/modals/view/RelationPanel.d.ts +18 -0
- package/dist/components/modals/view/RelationPanel.js +16 -0
- package/dist/components/modals/view/RelationTabs.d.ts +32 -0
- package/dist/components/modals/view/RelationTabs.js +42 -0
- package/dist/components/modals/view/useRecordView.d.ts +18 -0
- package/dist/components/modals/view/useRecordView.js +114 -0
- package/dist/components/pages/PageRenderer.d.ts +6 -0
- package/dist/components/pages/PageRenderer.js +107 -0
- package/dist/components/table/ColumnTogglePopup.d.ts +11 -0
- package/dist/components/table/ColumnTogglePopup.js +16 -0
- package/dist/components/table/GridCard.d.ts +21 -0
- package/dist/components/table/GridCard.js +30 -0
- package/dist/components/table/GridView.d.ts +23 -0
- package/dist/components/table/GridView.js +49 -0
- package/dist/components/table/LayoutToggle.d.ts +7 -0
- package/dist/components/table/LayoutToggle.js +9 -0
- package/dist/components/table/TableActionsDropdown.d.ts +13 -0
- package/dist/components/table/TableActionsDropdown.js +46 -0
- package/dist/components/table/TableBulkActions.d.ts +11 -0
- package/dist/components/table/TableBulkActions.js +21 -0
- package/dist/components/table/TableHeader.d.ts +14 -0
- package/dist/components/table/TableHeader.js +23 -0
- package/dist/components/table/TablePagination.d.ts +13 -0
- package/dist/components/table/TablePagination.js +55 -0
- package/dist/components/table/TableRow.d.ts +21 -0
- package/dist/components/table/TableRow.js +32 -0
- package/dist/components/table/TableSearchBar.d.ts +11 -0
- package/dist/components/table/TableSearchBar.js +12 -0
- package/dist/components/table/TableTabs.d.ts +14 -0
- package/dist/components/table/TableTabs.js +8 -0
- package/dist/components/ui/Badge.d.ts +6 -0
- package/dist/components/ui/Badge.js +12 -0
- package/dist/components/ui/Button.d.ts +22 -0
- package/dist/components/ui/Button.js +22 -0
- package/dist/components/ui/Card.d.ts +7 -0
- package/dist/components/ui/Card.js +5 -0
- package/dist/components/ui/ConfirmDialog.d.ts +19 -0
- package/dist/components/ui/ConfirmDialog.js +45 -0
- package/dist/components/ui/EmptyState.d.ts +9 -0
- package/dist/components/ui/EmptyState.js +6 -0
- package/dist/components/ui/ErrorAlert.d.ts +7 -0
- package/dist/components/ui/ErrorAlert.js +9 -0
- package/dist/components/ui/Input.d.ts +11 -0
- package/dist/components/ui/Input.js +10 -0
- package/dist/components/ui/Label.d.ts +5 -0
- package/dist/components/ui/Label.js +5 -0
- package/dist/components/ui/PillButton.d.ts +14 -0
- package/dist/components/ui/PillButton.js +19 -0
- package/dist/components/ui/Select.d.ts +7 -0
- package/dist/components/ui/Select.js +7 -0
- package/dist/components/ui/Spinner.d.ts +8 -0
- package/dist/components/ui/Spinner.js +14 -0
- package/dist/components/ui/Toast.d.ts +21 -0
- package/dist/components/ui/Toast.js +47 -0
- package/dist/components/ui/index.d.ts +24 -0
- package/dist/components/ui/index.js +12 -0
- package/dist/components/utils/HintDisplay.d.ts +11 -0
- package/dist/components/utils/HintDisplay.js +12 -0
- package/dist/components/utils/Icon.d.ts +22 -0
- package/dist/components/utils/Icon.js +22 -0
- package/dist/components/utils/MediaPreviewModal.d.ts +14 -0
- package/dist/components/utils/MediaPreviewModal.js +32 -0
- package/dist/components/utils/ViewFieldWrapper.d.ts +11 -0
- package/dist/components/utils/ViewFieldWrapper.js +9 -0
- package/dist/components/utils/layoutHelpers.d.ts +19 -0
- package/dist/components/utils/layoutHelpers.js +257 -0
- package/dist/components/widgets/ChartWidget.d.ts +16 -0
- package/dist/components/widgets/ChartWidget.js +192 -0
- package/dist/components/widgets/StatsWidget.d.ts +16 -0
- package/dist/components/widgets/StatsWidget.js +39 -0
- package/dist/components/widgets/WidgetRenderer.d.ts +10 -0
- package/dist/components/widgets/WidgetRenderer.js +50 -0
- package/dist/components/widgets/WidgetShell.d.ts +9 -0
- package/dist/components/widgets/WidgetShell.js +7 -0
- package/dist/contexts/AuthChallengeRegistryContext.d.ts +15 -0
- package/dist/contexts/AuthChallengeRegistryContext.js +15 -0
- package/dist/contexts/BlockRegistryContext.d.ts +18 -0
- package/dist/contexts/BlockRegistryContext.js +8 -0
- package/dist/contexts/ColumnRegistryContext.d.ts +8 -0
- package/dist/contexts/ColumnRegistryContext.js +30 -0
- package/dist/contexts/FieldRegistryContext.d.ts +13 -0
- package/dist/contexts/FieldRegistryContext.js +46 -0
- package/dist/contexts/PanelMetadataContext.d.ts +26 -0
- package/dist/contexts/PanelMetadataContext.js +26 -0
- package/dist/contexts/PanelProviders.d.ts +27 -0
- package/dist/contexts/PanelProviders.js +24 -0
- package/dist/contexts/ResourceModalContext.d.ts +26 -0
- package/dist/contexts/ResourceModalContext.js +76 -0
- package/dist/contexts/SlotRegistryContext.d.ts +19 -0
- package/dist/contexts/SlotRegistryContext.js +24 -0
- package/dist/contexts/TableRefreshContext.d.ts +10 -0
- package/dist/contexts/TableRefreshContext.js +30 -0
- package/dist/contexts/WidgetRegistryContext.d.ts +17 -0
- package/dist/contexts/WidgetRegistryContext.js +14 -0
- package/dist/contexts/createRegistryContext.d.ts +19 -0
- package/dist/contexts/createRegistryContext.js +20 -0
- package/dist/hooks/useAfterStateUpdated.d.ts +6 -0
- package/dist/hooks/useAfterStateUpdated.js +62 -0
- package/dist/hooks/useValidation.d.ts +26 -0
- package/dist/hooks/useValidation.js +76 -0
- package/dist/i18n/I18nProvider.d.ts +27 -0
- package/dist/i18n/I18nProvider.js +101 -0
- package/dist/i18n/LocaleSwitcher.d.ts +10 -0
- package/dist/i18n/LocaleSwitcher.js +30 -0
- package/dist/i18n/activeLocale.d.ts +11 -0
- package/dist/i18n/activeLocale.js +34 -0
- package/dist/i18n/buildClientI18n.d.ts +28 -0
- package/dist/i18n/buildClientI18n.js +67 -0
- package/dist/i18n/index.d.ts +11 -0
- package/dist/i18n/index.js +9 -0
- package/dist/i18n/locales/core/en.d.ts +225 -0
- package/dist/i18n/locales/core/en.js +252 -0
- package/dist/i18n/locales/core/index.d.ts +2 -0
- package/dist/i18n/locales/core/index.js +4 -0
- package/dist/i18n/locales/core/sq.d.ts +253 -0
- package/dist/i18n/locales/core/sq.js +255 -0
- package/dist/i18n/useFormatter.d.ts +18 -0
- package/dist/i18n/useFormatter.js +37 -0
- package/dist/i18n/useLocale.d.ts +11 -0
- package/dist/i18n/useLocale.js +11 -0
- package/dist/i18n/useTranslation.d.ts +12 -0
- package/dist/i18n/useTranslation.js +12 -0
- package/dist/index.d.ts +106 -0
- package/dist/index.js +101 -0
- package/dist/pages/ResourceListPage.d.ts +8 -0
- package/dist/pages/ResourceListPage.js +139 -0
- package/dist/plugin.d.ts +79 -0
- package/dist/plugin.js +34 -0
- package/dist/runtime/conditions.d.ts +35 -0
- package/dist/runtime/conditions.js +97 -0
- package/dist/runtime/formTraversal.d.ts +25 -0
- package/dist/runtime/formTraversal.js +37 -0
- package/dist/runtime/serializedFunctions.d.ts +41 -0
- package/dist/runtime/serializedFunctions.js +264 -0
- package/dist/slots/Slot.d.ts +24 -0
- package/dist/slots/Slot.js +29 -0
- package/dist/slots/SlotCluster.d.ts +22 -0
- package/dist/slots/SlotCluster.js +49 -0
- package/dist/slots/index.d.ts +7 -0
- package/dist/slots/index.js +4 -0
- package/dist/slots/mergeSlots.d.ts +18 -0
- package/dist/slots/mergeSlots.js +35 -0
- package/dist/slots/types.d.ts +87 -0
- package/dist/slots/types.js +30 -0
- package/dist/styles.css +1 -0
- package/dist/table/TableContext.d.ts +36 -0
- package/dist/table/TableContext.js +13 -0
- package/dist/table/TableRenderer.d.ts +29 -0
- package/dist/table/TableRenderer.js +159 -0
- package/dist/table/components/FiltersPanel.d.ts +11 -0
- package/dist/table/components/FiltersPanel.js +52 -0
- package/dist/table/components/TableToolbar.d.ts +28 -0
- package/dist/table/components/TableToolbar.js +27 -0
- package/dist/table/components/TableToolbarButton.d.ts +6 -0
- package/dist/table/components/TableToolbarButton.js +9 -0
- package/dist/table/components/TableView.d.ts +12 -0
- package/dist/table/components/TableView.js +21 -0
- package/dist/table/defaultRowActions.d.ts +21 -0
- package/dist/table/defaultRowActions.js +37 -0
- package/dist/table/hooks/useColumnVisibility.d.ts +13 -0
- package/dist/table/hooks/useColumnVisibility.js +59 -0
- package/dist/table/hooks/useEditableRows.d.ts +22 -0
- package/dist/table/hooks/useEditableRows.js +63 -0
- package/dist/table/hooks/useTableActions.d.ts +54 -0
- package/dist/table/hooks/useTableActions.js +313 -0
- package/dist/table/hooks/useTableData.d.ts +28 -0
- package/dist/table/hooks/useTableData.js +63 -0
- package/dist/table/hooks/useTableLayout.d.ts +12 -0
- package/dist/table/hooks/useTableLayout.js +31 -0
- package/dist/table/hooks/useTableQuery.d.ts +29 -0
- package/dist/table/hooks/useTableQuery.js +135 -0
- package/dist/types/index.d.ts +224 -0
- package/dist/types/index.js +6 -0
- package/dist/utils/classNames.d.ts +7 -0
- package/dist/utils/classNames.js +9 -0
- package/dist/utils/columnMediaDimensions.d.ts +13 -0
- package/dist/utils/columnMediaDimensions.js +29 -0
- package/dist/utils/columnVisibilityStorage.d.ts +22 -0
- package/dist/utils/columnVisibilityStorage.js +56 -0
- package/dist/utils/fieldErrors.d.ts +13 -0
- package/dist/utils/fieldErrors.js +25 -0
- package/dist/utils/formatValue.d.ts +28 -0
- package/dist/utils/formatValue.js +109 -0
- package/dist/utils/layoutStorage.d.ts +23 -0
- package/dist/utils/layoutStorage.js +53 -0
- package/dist/utils/redirectHandler.d.ts +7 -0
- package/dist/utils/redirectHandler.js +25 -0
- package/dist/utils/tableFormatters.d.ts +14 -0
- package/dist/utils/tableFormatters.js +93 -0
- package/dist/utils/widgetVisibilityStorage.d.ts +11 -0
- package/dist/utils/widgetVisibilityStorage.js +39 -0
- package/package.json +101 -0
- package/src/FieldRenderer.test.tsx +44 -0
- package/src/FieldRenderer.tsx +104 -0
- package/src/FormRenderer.containers.test.tsx +121 -0
- package/src/FormRenderer.test.tsx +174 -0
- package/src/FormRenderer.tsx +140 -0
- package/src/TableRenderer.tsx +2 -0
- package/src/api/actionsApi.ts +76 -0
- package/src/api/authenticatedFetch.ts +40 -0
- package/src/api/exportApi.ts +66 -0
- package/src/api/http.test.ts +58 -0
- package/src/api/http.ts +68 -0
- package/src/api/resourceApi.ts +88 -0
- package/src/api/tableApi.test.ts +108 -0
- package/src/api/tableApi.ts +107 -0
- package/src/api/urls.ts +50 -0
- package/src/app.test.tsx +67 -0
- package/src/app.tsx +181 -0
- package/src/auth/AuthContext.tsx +188 -0
- package/src/auth/LoginPage.tsx +380 -0
- package/src/auth/ProtectedRoute.tsx +39 -0
- package/src/auth/authApiClient.ts +109 -0
- package/src/auth/authFlow.test.tsx +168 -0
- package/src/auth/types.ts +104 -0
- package/src/components/ActionFormModal.tsx +45 -0
- package/src/components/AdminPanel.tsx +368 -0
- package/src/components/Checkbox.tsx +59 -0
- package/src/components/CheckboxField.tsx +88 -0
- package/src/components/ColorPickerField.tsx +93 -0
- package/src/components/DateTimePickerField.tsx +112 -0
- package/src/components/FileUploadField.tsx +841 -0
- package/src/components/GlobalSearch.tsx +436 -0
- package/src/components/GroupField.tsx +85 -0
- package/src/components/HiddenField.tsx +14 -0
- package/src/components/ModalBreadcrumb.tsx +74 -0
- package/src/components/ModalDrawer.tsx +137 -0
- package/src/components/RadioField.tsx +80 -0
- package/src/components/RepeaterField.tsx +546 -0
- package/src/components/ResourceModalRenderer.tsx +144 -0
- package/src/components/RichEditorField.tsx +942 -0
- package/src/components/SectionField.tsx +242 -0
- package/src/components/SelectField.tsx +843 -0
- package/src/components/TabsField.test.tsx +151 -0
- package/src/components/TabsField.tsx +386 -0
- package/src/components/TagsInputField.tsx +411 -0
- package/src/components/TextInputField.tsx +91 -0
- package/src/components/TextareaField.tsx +110 -0
- package/src/components/ToggleField.tsx +126 -0
- package/src/components/ViewModal.tsx +353 -0
- package/src/components/blocks/BlockRenderer.tsx +56 -0
- package/src/components/blocks/FormBlockRenderer.tsx +160 -0
- package/src/components/blocks/TableBlockRenderer.tsx +33 -0
- package/src/components/blocks/TabsBlockRenderer.tsx +49 -0
- package/src/components/blocks/WidgetBlockRenderer.tsx +19 -0
- package/src/components/columns/CheckboxColumnComponent.tsx +38 -0
- package/src/components/columns/ColorColumnComponent.tsx +23 -0
- package/src/components/columns/CustomColumn.test.tsx +55 -0
- package/src/components/columns/DeeplinkWrapper.tsx +103 -0
- package/src/components/columns/IconColumnComponent.tsx +55 -0
- package/src/components/columns/ImageColumnComponent.tsx +220 -0
- package/src/components/columns/MediaColumnComponent.tsx +294 -0
- package/src/components/columns/SelectColumnComponent.tsx +49 -0
- package/src/components/columns/TagsColumnComponent.tsx +46 -0
- package/src/components/columns/TextColumnComponent.tsx +191 -0
- package/src/components/columns/TextInputColumnComponent.tsx +35 -0
- package/src/components/columns/ToggleColumnComponent.tsx +56 -0
- package/src/components/columns/VideoColumnComponent.tsx +236 -0
- package/src/components/columns/ViewColumnComponent.tsx +9 -0
- package/src/components/errors/ErrorBoundary.tsx +58 -0
- package/src/components/filters/CustomFilterComponent.tsx +130 -0
- package/src/components/filters/DateFilterComponent.tsx +272 -0
- package/src/components/filters/QueryBuilderFilterComponent.tsx +502 -0
- package/src/components/layout/Header.tsx +212 -0
- package/src/components/layout/PanelBrandMark.tsx +61 -0
- package/src/components/layout/Sidebar.tsx +283 -0
- package/src/components/modals/RelationCreateModal.tsx +107 -0
- package/src/components/modals/ResourceFormModal.test.tsx +119 -0
- package/src/components/modals/ResourceFormModal.tsx +128 -0
- package/src/components/modals/useResourceForm.ts +207 -0
- package/src/components/modals/view/RecordActions.tsx +69 -0
- package/src/components/modals/view/RecordDetails.tsx +60 -0
- package/src/components/modals/view/RelationPanel.tsx +76 -0
- package/src/components/modals/view/RelationTabs.tsx +145 -0
- package/src/components/modals/view/useRecordView.ts +134 -0
- package/src/components/pages/PageRenderer.tsx +173 -0
- package/src/components/table/ColumnTogglePopup.tsx +85 -0
- package/src/components/table/GridCard.tsx +155 -0
- package/src/components/table/GridView.tsx +138 -0
- package/src/components/table/LayoutToggle.tsx +24 -0
- package/src/components/table/TableActionsDropdown.tsx +114 -0
- package/src/components/table/TableBulkActions.tsx +65 -0
- package/src/components/table/TableHeader.tsx +96 -0
- package/src/components/table/TablePagination.tsx +169 -0
- package/src/components/table/TableRow.tsx +155 -0
- package/src/components/table/TableSearchBar.tsx +66 -0
- package/src/components/table/TableTabs.tsx +49 -0
- package/src/components/ui/Badge.tsx +30 -0
- package/src/components/ui/Button.test.tsx +78 -0
- package/src/components/ui/Button.tsx +102 -0
- package/src/components/ui/Card.tsx +23 -0
- package/src/components/ui/ConfirmDialog.tsx +112 -0
- package/src/components/ui/EmptyState.tsx +24 -0
- package/src/components/ui/ErrorAlert.tsx +37 -0
- package/src/components/ui/Input.tsx +48 -0
- package/src/components/ui/Label.tsx +15 -0
- package/src/components/ui/PillButton.tsx +72 -0
- package/src/components/ui/Select.tsx +33 -0
- package/src/components/ui/Spinner.tsx +39 -0
- package/src/components/ui/Toast.tsx +105 -0
- package/src/components/ui/index.ts +24 -0
- package/src/components/utils/HintDisplay.tsx +26 -0
- package/src/components/utils/Icon.tsx +36 -0
- package/src/components/utils/MediaPreviewModal.tsx +114 -0
- package/src/components/utils/ViewFieldWrapper.tsx +23 -0
- package/src/components/utils/layoutHelpers.ts +267 -0
- package/src/components/widgets/ChartWidget.tsx +247 -0
- package/src/components/widgets/StatsWidget.tsx +72 -0
- package/src/components/widgets/WidgetRenderer.tsx +108 -0
- package/src/components/widgets/WidgetShell.tsx +37 -0
- package/src/contexts/AuthChallengeRegistryContext.tsx +29 -0
- package/src/contexts/BlockRegistryContext.tsx +28 -0
- package/src/contexts/ColumnRegistryContext.tsx +38 -0
- package/src/contexts/FieldRegistryContext.tsx +56 -0
- package/src/contexts/PanelMetadataContext.tsx +60 -0
- package/src/contexts/PanelProviders.tsx +85 -0
- package/src/contexts/ResourceModalContext.tsx +137 -0
- package/src/contexts/SlotRegistryContext.tsx +35 -0
- package/src/contexts/TableRefreshContext.tsx +44 -0
- package/src/contexts/WidgetRegistryContext.tsx +34 -0
- package/src/contexts/createRegistryContext.tsx +29 -0
- package/src/hooks/useAfterStateUpdated.ts +70 -0
- package/src/hooks/useValidation.test.ts +59 -0
- package/src/hooks/useValidation.ts +95 -0
- package/src/i18n/I18nProvider.tsx +128 -0
- package/src/i18n/LocaleSwitcher.tsx +50 -0
- package/src/i18n/activeLocale.ts +39 -0
- package/src/i18n/buildClientI18n.ts +101 -0
- package/src/i18n/i18n.test.tsx +140 -0
- package/src/i18n/index.ts +12 -0
- package/src/i18n/locales/core/en.ts +274 -0
- package/src/i18n/locales/core/index.ts +5 -0
- package/src/i18n/locales/core/sq.ts +275 -0
- package/src/i18n/useFormatter.ts +42 -0
- package/src/i18n/useLocale.ts +16 -0
- package/src/i18n/useTranslation.ts +17 -0
- package/src/index.ts +244 -0
- package/src/pages/ResourceListPage.tsx +205 -0
- package/src/plugin.ts +110 -0
- package/src/runtime/conditions.test.ts +99 -0
- package/src/runtime/conditions.ts +148 -0
- package/src/runtime/formTraversal.ts +41 -0
- package/src/runtime/serializedFunctions.test.ts +59 -0
- package/src/runtime/serializedFunctions.ts +284 -0
- package/src/slots/Slot.test.tsx +89 -0
- package/src/slots/Slot.tsx +47 -0
- package/src/slots/SlotCluster.test.tsx +95 -0
- package/src/slots/SlotCluster.tsx +107 -0
- package/src/slots/index.ts +15 -0
- package/src/slots/mergeSlots.test.ts +71 -0
- package/src/slots/mergeSlots.ts +40 -0
- package/src/slots/slotNames.test.ts +21 -0
- package/src/slots/types.ts +119 -0
- package/src/styles.css +437 -0
- package/src/table/TableContext.tsx +41 -0
- package/src/table/TableRenderer.test.tsx +197 -0
- package/src/table/TableRenderer.tsx +390 -0
- package/src/table/components/FiltersPanel.tsx +193 -0
- package/src/table/components/TableToolbar.tsx +153 -0
- package/src/table/components/TableToolbarButton.tsx +14 -0
- package/src/table/components/TableView.tsx +106 -0
- package/src/table/defaultRowActions.ts +43 -0
- package/src/table/hooks/useColumnVisibility.test.ts +51 -0
- package/src/table/hooks/useColumnVisibility.ts +71 -0
- package/src/table/hooks/useEditableRows.test.ts +69 -0
- package/src/table/hooks/useEditableRows.ts +89 -0
- package/src/table/hooks/useTableActions.ts +393 -0
- package/src/table/hooks/useTableData.ts +89 -0
- package/src/table/hooks/useTableLayout.ts +45 -0
- package/src/table/hooks/useTableQuery.test.ts +116 -0
- package/src/table/hooks/useTableQuery.ts +172 -0
- package/src/test/mockFetch.ts +67 -0
- package/src/test/setup.ts +25 -0
- package/src/types/index.ts +228 -0
- package/src/utils/classNames.ts +10 -0
- package/src/utils/columnMediaDimensions.ts +45 -0
- package/src/utils/columnVisibilityStorage.ts +55 -0
- package/src/utils/fieldErrors.test.ts +35 -0
- package/src/utils/fieldErrors.ts +27 -0
- package/src/utils/formatValue.test.tsx +65 -0
- package/src/utils/formatValue.tsx +117 -0
- package/src/utils/layoutStorage.ts +52 -0
- package/src/utils/redirectHandler.ts +29 -0
- package/src/utils/tableFormatters.test.ts +54 -0
- package/src/utils/tableFormatters.ts +104 -0
- package/src/utils/widgetVisibilityStorage.ts +38 -0
- package/tailwind.config.js +9 -0
- package/vite.config.ts +17 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { FieldRenderer } from '../FieldRenderer';
|
|
4
|
+
import { Icon } from './utils/Icon';
|
|
5
|
+
import { ChevronRight, ChevronDown } from 'lucide-react';
|
|
6
|
+
import { getFieldLayoutClasses } from './utils/layoutHelpers';
|
|
7
|
+
// Section background by depth; no bg-muted — direct opacity scale for light and dark.
|
|
8
|
+
const SECTION_BG_LIGHT = [
|
|
9
|
+
'bg-black/[0.04]',
|
|
10
|
+
'bg-black/[0.08]',
|
|
11
|
+
'bg-black/[0.12]',
|
|
12
|
+
'bg-black/[0.16]',
|
|
13
|
+
'bg-black/[0.20]',
|
|
14
|
+
'bg-black/[0.25]',
|
|
15
|
+
'bg-black/[0.30]',
|
|
16
|
+
'bg-black/[0.35]',
|
|
17
|
+
];
|
|
18
|
+
const SECTION_BG_DARK = [
|
|
19
|
+
'dark:bg-black/35',
|
|
20
|
+
'dark:bg-black/50',
|
|
21
|
+
'dark:bg-black/62',
|
|
22
|
+
'dark:bg-black/72',
|
|
23
|
+
'dark:bg-black/80',
|
|
24
|
+
'dark:bg-black/87',
|
|
25
|
+
'dark:bg-black/93',
|
|
26
|
+
'dark:bg-black',
|
|
27
|
+
];
|
|
28
|
+
export function SectionField({ heading, description, icon, collapsible, collapsed: initialCollapsed, compact, aside, mode, schema, columns, disabled, value, _recordData, operation, apiBaseUrl, resource, isLastSectionInParent, sectionDepth = 0, }) {
|
|
29
|
+
// Default to collapsed if not specified
|
|
30
|
+
const [isCollapsed, setIsCollapsed] = React.useState(initialCollapsed !== undefined ? initialCollapsed : true);
|
|
31
|
+
// In view mode, sections are always expanded and not collapsible
|
|
32
|
+
const isViewMode = mode === 'view';
|
|
33
|
+
const viewCollapsed = false;
|
|
34
|
+
if (!heading && !description) {
|
|
35
|
+
// Section without header - just a visual separator
|
|
36
|
+
return _jsx("div", { className: "my-6 border-t border-gray-200 dark:border-gray-700" });
|
|
37
|
+
}
|
|
38
|
+
const toggleCollapse = () => {
|
|
39
|
+
if (collapsible && !isViewMode) {
|
|
40
|
+
setIsCollapsed(!isCollapsed);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
// In view mode, use viewCollapsed (always false)
|
|
44
|
+
const displayCollapsed = isViewMode ? viewCollapsed : isCollapsed;
|
|
45
|
+
// In view mode, use _recordData if provided, otherwise use value
|
|
46
|
+
const recordData = mode === 'view' && _recordData ? _recordData : value || {};
|
|
47
|
+
// Get layout classes for nested fields
|
|
48
|
+
const fieldLayoutClasses = getFieldLayoutClasses(columns);
|
|
49
|
+
// At each level, the last section gets smaller padding and no border; others get full padding and border.
|
|
50
|
+
// Background gets darker for each nesting level; supports arbitrary depth (capped at array length).
|
|
51
|
+
const hasNestedSections = schema && Array.isArray(schema) && schema.some((f) => f.type === 'section');
|
|
52
|
+
const isLast = Boolean(isLastSectionInParent);
|
|
53
|
+
const depthIndex = Math.min(sectionDepth, SECTION_BG_LIGHT.length - 1);
|
|
54
|
+
const bgByDepth = `${SECTION_BG_LIGHT[depthIndex]} ${SECTION_BG_DARK[depthIndex]}`;
|
|
55
|
+
// Dark mode uses solid colors from styles.css (.dark .section-depth-bg[data-section-depth="1"] etc.)
|
|
56
|
+
const bodyWrapperClass = `${bgByDepth} section-depth-bg`;
|
|
57
|
+
const bodyWrapperData = { 'data-section-depth': depthIndex + 1 }; // CSS uses "1"–"8"
|
|
58
|
+
// Last section: same padding as others, only border-bottom removed.
|
|
59
|
+
const bodyClasses = isLast
|
|
60
|
+
? `rounded-lg ${bodyWrapperClass} p-4 border border-gray-100 dark:border-gray-700/50 border-b-0 mb-2`
|
|
61
|
+
: `rounded-lg ${bodyWrapperClass} p-4 border border-gray-100 dark:border-gray-700/50 mb-2`;
|
|
62
|
+
const outerClasses = hasNestedSections && !isLast ? (compact ? 'my-4' : 'my-6') : '';
|
|
63
|
+
return (_jsx("div", { className: outerClasses, children: _jsxs("div", { className: `${aside ? 'flex gap-6' : ''}`, children: [_jsxs("div", { className: `${aside ? 'w-1/3' : 'mb-4'}`, children: [_jsxs("div", { className: "flex items-center gap-2", children: [icon && _jsx(Icon, { name: icon, size: 20, className: "text-gray-500 dark:text-gray-400" }), heading && (_jsxs("h3", { className: `font-semibold ${aside ? 'text-lg' : 'text-xl'} text-gray-900 dark:text-gray-100 ${collapsible && !isViewMode
|
|
64
|
+
? 'cursor-pointer hover:text-gray-700 dark:hover:text-gray-300 flex items-center gap-2'
|
|
65
|
+
: ''}`, onClick: toggleCollapse, children: [heading, collapsible &&
|
|
66
|
+
!isViewMode &&
|
|
67
|
+
(displayCollapsed ? (_jsx(ChevronRight, { size: 16, className: "text-gray-500 dark:text-gray-400" })) : (_jsx(ChevronDown, { size: 16, className: "text-gray-500 dark:text-gray-400" })))] }))] }), description && _jsx("p", { className: "mt-1 text-sm text-gray-600 dark:text-gray-400", children: description })] }), aside ? (_jsx("div", { className: "flex-1", children: schema &&
|
|
68
|
+
Array.isArray(schema) &&
|
|
69
|
+
!displayCollapsed &&
|
|
70
|
+
(() => {
|
|
71
|
+
const lastSectionIndex = schema.reduce((last, f, i) => (f.type === 'section' ? i : last), -1);
|
|
72
|
+
return (_jsx("div", { className: bodyClasses, ...bodyWrapperData, children: _jsx("div", { className: fieldLayoutClasses, children: schema.map((field, index) => {
|
|
73
|
+
const isSection = field.type === 'section';
|
|
74
|
+
const nestedField = {
|
|
75
|
+
...field,
|
|
76
|
+
disabled: disabled || field.disabled,
|
|
77
|
+
mode: mode || field.mode,
|
|
78
|
+
// Only the single section at lastSectionIndex is "last"; others get false so border shows.
|
|
79
|
+
isLastSectionInParent: isSection
|
|
80
|
+
? lastSectionIndex >= 0 && index === lastSectionIndex
|
|
81
|
+
: undefined,
|
|
82
|
+
sectionDepth: isSection ? sectionDepth + 1 : undefined,
|
|
83
|
+
};
|
|
84
|
+
const nestedValue = mode === 'view' && field.name
|
|
85
|
+
? recordData[field.name]
|
|
86
|
+
: undefined;
|
|
87
|
+
return (_jsx(FieldRenderer, { field: nestedField, mode: mode, value: nestedValue, operation: operation, apiBaseUrl: apiBaseUrl, resource: resource }, index));
|
|
88
|
+
}) }) }));
|
|
89
|
+
})() })) : (_jsxs(_Fragment, { children: [schema &&
|
|
90
|
+
Array.isArray(schema) &&
|
|
91
|
+
!displayCollapsed &&
|
|
92
|
+
(() => {
|
|
93
|
+
const lastSectionIndex = schema.reduce((last, f, i) => (f.type === 'section' ? i : last), -1);
|
|
94
|
+
return (_jsx("div", { className: !isLast && hasNestedSections ? `mt-4 ${bodyClasses}` : bodyClasses, ...bodyWrapperData, children: _jsx("div", { className: fieldLayoutClasses, children: schema.map((field, index) => {
|
|
95
|
+
const isSection = field.type === 'section';
|
|
96
|
+
const nestedField = {
|
|
97
|
+
...field,
|
|
98
|
+
disabled: disabled || field.disabled,
|
|
99
|
+
mode: mode || field.mode,
|
|
100
|
+
isLastSectionInParent: isSection
|
|
101
|
+
? lastSectionIndex >= 0 && index === lastSectionIndex
|
|
102
|
+
: undefined,
|
|
103
|
+
sectionDepth: isSection ? sectionDepth + 1 : undefined,
|
|
104
|
+
};
|
|
105
|
+
const nestedValue = mode === 'view' && field.name
|
|
106
|
+
? recordData[field.name]
|
|
107
|
+
: undefined;
|
|
108
|
+
return (_jsx(FieldRenderer, { field: nestedField, mode: mode, value: nestedValue, operation: operation, apiBaseUrl: apiBaseUrl, resource: resource }, index));
|
|
109
|
+
}) }) }));
|
|
110
|
+
})(), hasNestedSections && !isLast && (_jsx("div", { className: "mt-4 border-t border-gray-200 dark:border-gray-700" }))] }))] }) }));
|
|
111
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FieldProps } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Select field component
|
|
5
|
+
* Renders single or multi-select dropdowns with optional creatable functionality
|
|
6
|
+
* Supports relationship-based data fetching from API
|
|
7
|
+
*/
|
|
8
|
+
export declare function SelectField(props: FieldProps): React.JSX.Element;
|
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useRef, useEffect } from 'react';
|
|
3
|
+
import { useFormContext, useWatch } from 'react-hook-form';
|
|
4
|
+
import { getFieldError } from '../utils/fieldErrors';
|
|
5
|
+
import { useValidation } from '../hooks/useValidation';
|
|
6
|
+
import { cn } from '../utils/classNames';
|
|
7
|
+
import { HintDisplay } from './utils/HintDisplay';
|
|
8
|
+
import { Icon } from './utils/Icon';
|
|
9
|
+
import { ResourceFormModal } from './modals/ResourceFormModal';
|
|
10
|
+
import { executeSerializedFunction } from '../runtime/serializedFunctions';
|
|
11
|
+
import { ViewFieldWrapper } from './utils/ViewFieldWrapper';
|
|
12
|
+
import { formatSelectLabel } from '../utils/formatValue';
|
|
13
|
+
import { authenticatedFetch } from '../api/authenticatedFetch';
|
|
14
|
+
import { translate } from '../i18n/activeLocale';
|
|
15
|
+
/**
|
|
16
|
+
* Component for rendering SelectField in view mode with relationship data fetching
|
|
17
|
+
*/
|
|
18
|
+
function SelectFieldViewMode({ value, isMultiple, relationship, label, optionLabelFormatter, apiBaseUrl, }) {
|
|
19
|
+
const [displayValue, setDisplayValue] = useState('-');
|
|
20
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const fetchRelationshipData = async () => {
|
|
23
|
+
if (!value || (isMultiple && (!Array.isArray(value) || value.length === 0))) {
|
|
24
|
+
setDisplayValue('-');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Extract IDs from value
|
|
28
|
+
const valueIds = isMultiple
|
|
29
|
+
? (Array.isArray(value) ? value : []).map((v) => {
|
|
30
|
+
if (typeof v === 'object' && v !== null) {
|
|
31
|
+
return v._id || v.id || String(v);
|
|
32
|
+
}
|
|
33
|
+
return String(v);
|
|
34
|
+
})
|
|
35
|
+
: typeof value === 'object' && value !== null
|
|
36
|
+
? [value._id || value.id || String(value)]
|
|
37
|
+
: [String(value)];
|
|
38
|
+
if (valueIds.length === 0) {
|
|
39
|
+
setDisplayValue('-');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// If all values are already objects with titleAttribute, use them directly
|
|
43
|
+
if (isMultiple && Array.isArray(value)) {
|
|
44
|
+
const allHaveTitle = value.every((v) => typeof v === 'object' && v !== null && v[relationship.titleAttribute]);
|
|
45
|
+
if (allHaveTitle) {
|
|
46
|
+
const formatted = value
|
|
47
|
+
.map((v) => {
|
|
48
|
+
const title = v[relationship.titleAttribute] || String(v);
|
|
49
|
+
if (optionLabelFormatter) {
|
|
50
|
+
const formattedTitle = executeSerializedFunction(optionLabelFormatter, v._id || v.id || String(v), v);
|
|
51
|
+
return formattedTitle !== null && formattedTitle !== undefined
|
|
52
|
+
? String(formattedTitle)
|
|
53
|
+
: title;
|
|
54
|
+
}
|
|
55
|
+
return title;
|
|
56
|
+
})
|
|
57
|
+
.join(', ');
|
|
58
|
+
setDisplayValue(formatted);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else if (!isMultiple &&
|
|
63
|
+
typeof value === 'object' &&
|
|
64
|
+
value !== null &&
|
|
65
|
+
value[relationship.titleAttribute]) {
|
|
66
|
+
const title = value[relationship.titleAttribute];
|
|
67
|
+
if (optionLabelFormatter) {
|
|
68
|
+
const formattedTitle = executeSerializedFunction(optionLabelFormatter, value._id || value.id || String(value), value);
|
|
69
|
+
setDisplayValue(formattedTitle !== null && formattedTitle !== undefined ? String(formattedTitle) : title);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
setDisplayValue(title);
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// Need to fetch records
|
|
77
|
+
setIsLoading(true);
|
|
78
|
+
try {
|
|
79
|
+
const resourceSlug = relationship.resource || relationship.name;
|
|
80
|
+
const apiUrl = apiBaseUrl;
|
|
81
|
+
const fetchPromises = valueIds.map(async (id) => {
|
|
82
|
+
try {
|
|
83
|
+
const response = await authenticatedFetch(`${apiUrl}/${resourceSlug}/${id}`, {
|
|
84
|
+
headers: {},
|
|
85
|
+
}, apiUrl.substring(0, apiUrl.lastIndexOf('/')));
|
|
86
|
+
if (response.ok) {
|
|
87
|
+
const result = await response.json();
|
|
88
|
+
const record = result.data || result;
|
|
89
|
+
return { id, record };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.error(`Error fetching record ${id}:`, error);
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
});
|
|
97
|
+
const results = await Promise.all(fetchPromises);
|
|
98
|
+
const validResults = results.filter((r) => r !== null);
|
|
99
|
+
if (validResults.length === 0) {
|
|
100
|
+
setDisplayValue(valueIds.join(', '));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// Format the display value
|
|
104
|
+
const formatted = validResults
|
|
105
|
+
.map(({ id, record }) => {
|
|
106
|
+
const title = record[relationship.titleAttribute] || id;
|
|
107
|
+
if (optionLabelFormatter) {
|
|
108
|
+
const formattedTitle = executeSerializedFunction(optionLabelFormatter, id, record);
|
|
109
|
+
return formattedTitle !== null && formattedTitle !== undefined
|
|
110
|
+
? String(formattedTitle)
|
|
111
|
+
: title;
|
|
112
|
+
}
|
|
113
|
+
return title;
|
|
114
|
+
})
|
|
115
|
+
.join(', ');
|
|
116
|
+
setDisplayValue(formatted);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
console.error('Error fetching relationship data:', error);
|
|
120
|
+
// Fallback to showing IDs
|
|
121
|
+
setDisplayValue(valueIds.join(', '));
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
setIsLoading(false);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
fetchRelationshipData();
|
|
128
|
+
}, [value, isMultiple, relationship, optionLabelFormatter, apiBaseUrl]);
|
|
129
|
+
if (isLoading) {
|
|
130
|
+
return (_jsx(ViewFieldWrapper, { label: label, children: _jsx("span", { className: "text-fg-secondary", children: translate('core:select.loading') }) }));
|
|
131
|
+
}
|
|
132
|
+
return (_jsx(ViewFieldWrapper, { label: label, children: _jsx("span", { dangerouslySetInnerHTML: { __html: displayValue || '-' } }) }));
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Select field component
|
|
136
|
+
* Renders single or multi-select dropdowns with optional creatable functionality
|
|
137
|
+
* Supports relationship-based data fetching from API
|
|
138
|
+
*/
|
|
139
|
+
export function SelectField(props) {
|
|
140
|
+
// View mode: render formatted display
|
|
141
|
+
if (props.mode === 'view') {
|
|
142
|
+
const value = props.value;
|
|
143
|
+
const isMultiple = props.isMultiple || props.multiple;
|
|
144
|
+
const hasRelationship = !!props.relationship;
|
|
145
|
+
// For relationship-based selects, we need to fetch the related records if value is just an ID
|
|
146
|
+
if (hasRelationship && props.relationship) {
|
|
147
|
+
// Use a component that fetches relationship data
|
|
148
|
+
return (_jsx(SelectFieldViewMode, { value: value, isMultiple: isMultiple, relationship: props.relationship, label: props.label, optionLabelFormatter: props.optionLabelFormatter, apiBaseUrl: props.apiBaseUrl }));
|
|
149
|
+
}
|
|
150
|
+
// For non-relationship selects, use static options
|
|
151
|
+
const displayValue = formatSelectLabel(value, props.options, isMultiple);
|
|
152
|
+
return _jsx(ViewFieldWrapper, { label: props.label, children: displayValue });
|
|
153
|
+
}
|
|
154
|
+
const { register, setValue, control, formState: { errors }, } = useFormContext();
|
|
155
|
+
const validation = useValidation(props.validation?.rules || [], props.operation, props.name);
|
|
156
|
+
const error = getFieldError(errors, props.name);
|
|
157
|
+
const hasError = !!error;
|
|
158
|
+
const isMultiple = props.isMultiple || props.multiple;
|
|
159
|
+
const isCreatable = props.creatable;
|
|
160
|
+
const isNative = props.native;
|
|
161
|
+
const hasRelationship = !!props.relationship;
|
|
162
|
+
// Watch current value for multi-select - let the form handle defaults
|
|
163
|
+
const rawValue = useWatch({ control, name: props.name });
|
|
164
|
+
// Normalize value: if it's an object (from edit mode), extract the ID
|
|
165
|
+
const normalizeValue = (value) => {
|
|
166
|
+
if (!value)
|
|
167
|
+
return isMultiple ? [] : '';
|
|
168
|
+
if (isMultiple) {
|
|
169
|
+
if (!Array.isArray(value))
|
|
170
|
+
return [];
|
|
171
|
+
return value.map((v) => {
|
|
172
|
+
if (typeof v === 'object' && v !== null) {
|
|
173
|
+
return v._id || v.id || String(v);
|
|
174
|
+
}
|
|
175
|
+
return String(v);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
if (typeof value === 'object' && value !== null) {
|
|
180
|
+
return value._id || value.id || String(value);
|
|
181
|
+
}
|
|
182
|
+
return String(value);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
const currentValue = normalizeValue(rawValue) || (isMultiple ? [] : '');
|
|
186
|
+
// Update form value if it was an object (normalize it to ID)
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
if (rawValue && typeof rawValue === 'object' && !Array.isArray(rawValue)) {
|
|
189
|
+
const normalized = normalizeValue(rawValue);
|
|
190
|
+
if (normalized !== rawValue) {
|
|
191
|
+
setValue(props.name, normalized, { shouldValidate: false });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else if (rawValue && Array.isArray(rawValue) && rawValue.some((v) => typeof v === 'object')) {
|
|
195
|
+
const normalized = normalizeValue(rawValue);
|
|
196
|
+
setValue(props.name, normalized, { shouldValidate: false });
|
|
197
|
+
}
|
|
198
|
+
}, [rawValue, props.name, setValue]);
|
|
199
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
200
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
201
|
+
const [inputValue, setInputValue] = useState('');
|
|
202
|
+
const [relationshipOptions, setRelationshipOptions] = useState({});
|
|
203
|
+
const [relationshipRecords, setRelationshipRecords] = useState({});
|
|
204
|
+
// Track which records were individually fetched (for edit mode) - these should be formatted
|
|
205
|
+
const [individuallyFetchedIds, setIndividuallyFetchedIds] = useState(new Set());
|
|
206
|
+
// Cache formatted labels for individually fetched records to prevent re-rendering
|
|
207
|
+
const [formattedLabels, setFormattedLabels] = useState({});
|
|
208
|
+
const [isLoadingRelationship, setIsLoadingRelationship] = useState(false);
|
|
209
|
+
const [showCreateModal, setShowCreateModal] = useState(false);
|
|
210
|
+
const dropdownRef = useRef(null);
|
|
211
|
+
// Format option label using custom formatter if provided
|
|
212
|
+
const formatOptionLabel = (value, label, record) => {
|
|
213
|
+
if (props.optionLabelFormatter && record) {
|
|
214
|
+
const result = executeSerializedFunction(props.optionLabelFormatter, value, record);
|
|
215
|
+
return result !== null && result !== undefined ? String(result) : label;
|
|
216
|
+
}
|
|
217
|
+
return label;
|
|
218
|
+
};
|
|
219
|
+
// Fetch relationship data when component mounts or search term changes
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
if (!hasRelationship || !props.relationship)
|
|
222
|
+
return;
|
|
223
|
+
const fetchRelationshipData = async () => {
|
|
224
|
+
setIsLoadingRelationship(true);
|
|
225
|
+
try {
|
|
226
|
+
const { resource, titleAttribute } = props.relationship;
|
|
227
|
+
const resourceSlug = resource || props.relationship.name;
|
|
228
|
+
if (!resourceSlug) {
|
|
229
|
+
throw new Error('Resource slug is required for relationship data fetching');
|
|
230
|
+
}
|
|
231
|
+
const apiBaseUrl = props.apiBaseUrl;
|
|
232
|
+
const url = `${apiBaseUrl}/${resourceSlug}/list`;
|
|
233
|
+
const body = { perPage: 50 };
|
|
234
|
+
if (searchTerm) {
|
|
235
|
+
body.search = searchTerm;
|
|
236
|
+
}
|
|
237
|
+
const response = await authenticatedFetch(url, {
|
|
238
|
+
method: 'POST',
|
|
239
|
+
headers: { 'Content-Type': 'application/json' },
|
|
240
|
+
body: JSON.stringify(body),
|
|
241
|
+
}, apiBaseUrl);
|
|
242
|
+
if (!response.ok) {
|
|
243
|
+
throw new Error(`Failed to fetch relationship data: ${response.status}`);
|
|
244
|
+
}
|
|
245
|
+
const result = await response.json();
|
|
246
|
+
const data = result.data || [];
|
|
247
|
+
const options = {};
|
|
248
|
+
const records = {};
|
|
249
|
+
data.forEach((item) => {
|
|
250
|
+
const id = item._id || item.id;
|
|
251
|
+
const title = item[titleAttribute] || id;
|
|
252
|
+
options[id] = title;
|
|
253
|
+
records[id] = item;
|
|
254
|
+
});
|
|
255
|
+
// Replace with API results so the list reflects current search; preserve labels for selected value(s) so chips still display
|
|
256
|
+
const valueIds = isMultiple
|
|
257
|
+
? Array.isArray(currentValue)
|
|
258
|
+
? currentValue
|
|
259
|
+
: []
|
|
260
|
+
: currentValue
|
|
261
|
+
? [currentValue]
|
|
262
|
+
: [];
|
|
263
|
+
setRelationshipOptions(prev => {
|
|
264
|
+
const next = { ...options };
|
|
265
|
+
valueIds.forEach((id) => {
|
|
266
|
+
if (prev[id])
|
|
267
|
+
next[id] = prev[id];
|
|
268
|
+
});
|
|
269
|
+
return next;
|
|
270
|
+
});
|
|
271
|
+
// Replace records with API results; preserve individually fetched ones so formatted labels are kept
|
|
272
|
+
setRelationshipRecords(prev => {
|
|
273
|
+
const next = { ...records };
|
|
274
|
+
valueIds.forEach((id) => {
|
|
275
|
+
if (individuallyFetchedIds.has(id) && prev[id])
|
|
276
|
+
next[id] = prev[id];
|
|
277
|
+
});
|
|
278
|
+
return next;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
console.error('Error fetching relationship data:', error);
|
|
283
|
+
setRelationshipOptions({});
|
|
284
|
+
}
|
|
285
|
+
finally {
|
|
286
|
+
setIsLoadingRelationship(false);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
const timeoutId = setTimeout(fetchRelationshipData, searchTerm ? 300 : 0);
|
|
290
|
+
return () => clearTimeout(timeoutId);
|
|
291
|
+
}, [hasRelationship, searchTerm, props.relationship, individuallyFetchedIds, currentValue, isMultiple]);
|
|
292
|
+
// Fetch related record(s) when editing (if we have a current value but no record data)
|
|
293
|
+
useEffect(() => {
|
|
294
|
+
if (!hasRelationship || !props.relationship || !currentValue)
|
|
295
|
+
return;
|
|
296
|
+
const valueIds = isMultiple ? (Array.isArray(currentValue) ? currentValue : []) : [currentValue];
|
|
297
|
+
const missingIds = valueIds.filter((id) => !relationshipRecords[id]);
|
|
298
|
+
if (missingIds.length > 0) {
|
|
299
|
+
const fetchRelatedRecords = async () => {
|
|
300
|
+
try {
|
|
301
|
+
const { resource, titleAttribute } = props.relationship;
|
|
302
|
+
const resourceSlug = resource || props.relationship.name;
|
|
303
|
+
const apiBaseUrl = props.apiBaseUrl;
|
|
304
|
+
const fetchPromises = missingIds.map(async (id) => {
|
|
305
|
+
try {
|
|
306
|
+
const response = await authenticatedFetch(`${apiBaseUrl}/${resourceSlug}/${id}`, {
|
|
307
|
+
headers: {},
|
|
308
|
+
}, apiBaseUrl);
|
|
309
|
+
if (response.ok) {
|
|
310
|
+
const result = await response.json();
|
|
311
|
+
// Handle new response structure: { data, metadata }
|
|
312
|
+
const record = result.data || result;
|
|
313
|
+
return { id, record };
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
console.error(`Error fetching record ${id}:`, error);
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
320
|
+
});
|
|
321
|
+
const results = await Promise.all(fetchPromises);
|
|
322
|
+
const newOptions = { ...relationshipOptions };
|
|
323
|
+
const newRecords = { ...relationshipRecords };
|
|
324
|
+
const newFormattedLabels = {};
|
|
325
|
+
results.forEach(result => {
|
|
326
|
+
if (result) {
|
|
327
|
+
const { id, record } = result;
|
|
328
|
+
const title = record[titleAttribute] || id;
|
|
329
|
+
newOptions[id] = title;
|
|
330
|
+
newRecords[id] = record;
|
|
331
|
+
// Pre-format and cache the label for this individually fetched record
|
|
332
|
+
const formatted = formatOptionLabel(id, title, record);
|
|
333
|
+
newFormattedLabels[id] = formatted;
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
setRelationshipOptions(newOptions);
|
|
337
|
+
setRelationshipRecords(newRecords);
|
|
338
|
+
// Mark these records as individually fetched and cache their formatted labels
|
|
339
|
+
setIndividuallyFetchedIds(prev => {
|
|
340
|
+
const newSet = new Set(prev);
|
|
341
|
+
results.forEach(result => {
|
|
342
|
+
if (result)
|
|
343
|
+
newSet.add(result.id);
|
|
344
|
+
});
|
|
345
|
+
return newSet;
|
|
346
|
+
});
|
|
347
|
+
setFormattedLabels(prev => ({ ...prev, ...newFormattedLabels }));
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
console.error('Error fetching related records:', error);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
fetchRelatedRecords();
|
|
354
|
+
}
|
|
355
|
+
}, [hasRelationship, currentValue, relationshipRecords, props.relationship, isMultiple]);
|
|
356
|
+
// Close dropdown when clicking outside
|
|
357
|
+
useEffect(() => {
|
|
358
|
+
const handleClickOutside = (event) => {
|
|
359
|
+
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
360
|
+
setIsOpen(false);
|
|
361
|
+
setSearchTerm('');
|
|
362
|
+
setInputValue('');
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
366
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
367
|
+
}, []);
|
|
368
|
+
// Use relationship options if available, otherwise use static options
|
|
369
|
+
const effectiveOptions = hasRelationship ? relationshipOptions : props.options || {};
|
|
370
|
+
// If native or not multiple and not creatable and not relationship, use native select
|
|
371
|
+
if (isNative || (!isMultiple && !isCreatable && !hasRelationship)) {
|
|
372
|
+
return (_jsxs("div", { className: "mb-4", children: [props.label && (_jsxs("label", { htmlFor: props.name, className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", children: [props.label, validation.required && _jsx("span", { className: "text-red-500 dark:text-red-400 ml-1", children: "*" })] })), _jsxs("select", { id: props.name, ...register(props.name, validation), disabled: props.disabled, multiple: isMultiple, className: cn('w-full px-3 py-2 border rounded-lg shadow-sm', 'k-input', 'focus:outline-none focus:ring-2 focus:ring-ring', 'transition duration-150 ease-in-out', hasError && 'border-red-500 dark:border-red-400 focus:ring-red-500 dark:focus:ring-red-400', props.disabled && 'opacity-60 cursor-not-allowed', isMultiple && 'min-h-[120px]'), children: [props.placeholder && !isMultiple && props.selectablePlaceholder !== false && (_jsx("option", { value: "", children: props.placeholder })), effectiveOptions &&
|
|
373
|
+
Object.entries(effectiveOptions).map(([value, label]) => (_jsx("option", { value: value, children: label }, value)))] }), hasError && _jsx("p", { className: "mt-1 text-sm text-red-600 dark:text-red-400", children: error?.message }), props.helperText && (_jsx("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: props.helperText })), !hasError && _jsx(HintDisplay, { hint: props.hint, hintIcon: props.hintIcon, hintColor: props.hintColor }), isMultiple && !hasError && (_jsx("p", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400", children: "Hold Ctrl/Cmd to select multiple options" }))] }));
|
|
374
|
+
}
|
|
375
|
+
// Enhanced multi-select with chips
|
|
376
|
+
const selectedValues = isMultiple
|
|
377
|
+
? Array.isArray(currentValue)
|
|
378
|
+
? currentValue.filter((v) => typeof v === 'string')
|
|
379
|
+
: []
|
|
380
|
+
: currentValue && typeof currentValue === 'string'
|
|
381
|
+
? [currentValue]
|
|
382
|
+
: [];
|
|
383
|
+
const optionEntries = Object.entries(effectiveOptions);
|
|
384
|
+
// Filter options based on search term
|
|
385
|
+
// For relationship-based selects, the API already filters the results, so skip client-side filtering
|
|
386
|
+
const filteredOptions = hasRelationship
|
|
387
|
+
? optionEntries
|
|
388
|
+
: optionEntries.filter(([value, label]) => {
|
|
389
|
+
const matchesSearch = !searchTerm || label.toLowerCase().includes(searchTerm.toLowerCase());
|
|
390
|
+
return matchesSearch;
|
|
391
|
+
});
|
|
392
|
+
// Check if we can create a new option
|
|
393
|
+
const canCreateNew = isCreatable &&
|
|
394
|
+
searchTerm &&
|
|
395
|
+
!optionEntries.some(([value, label]) => label.toLowerCase() === searchTerm.toLowerCase()) &&
|
|
396
|
+
!selectedValues.includes(searchTerm);
|
|
397
|
+
// Check if we can create a new relationship record
|
|
398
|
+
const canCreateRelationship = hasRelationship && props.createOptionForm;
|
|
399
|
+
const toggleOption = (value) => {
|
|
400
|
+
// When selecting a new option, format and cache its label if we have the record
|
|
401
|
+
const record = relationshipRecords[value];
|
|
402
|
+
if (record && !formattedLabels[value]) {
|
|
403
|
+
const label = relationshipOptions[value] || value;
|
|
404
|
+
const formatted = formatOptionLabel(value, label, record);
|
|
405
|
+
setFormattedLabels(prev => ({ ...prev, [value]: formatted }));
|
|
406
|
+
// Mark as individually fetched so it stays formatted
|
|
407
|
+
setIndividuallyFetchedIds(prev => new Set(prev).add(value));
|
|
408
|
+
}
|
|
409
|
+
if (isMultiple) {
|
|
410
|
+
const newValues = selectedValues.includes(value)
|
|
411
|
+
? selectedValues.filter((v) => v !== value)
|
|
412
|
+
: [...selectedValues, value];
|
|
413
|
+
setValue(props.name, newValues, { shouldValidate: true });
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
setValue(props.name, value, { shouldValidate: true });
|
|
417
|
+
setIsOpen(false);
|
|
418
|
+
setSearchTerm('');
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
const removeValue = (value) => {
|
|
422
|
+
if (isMultiple) {
|
|
423
|
+
const newValues = selectedValues.filter((v) => v !== value);
|
|
424
|
+
setValue(props.name, newValues, { shouldValidate: true });
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
setValue(props.name, '', { shouldValidate: true });
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
const createNewOption = () => {
|
|
431
|
+
if (!canCreateNew || !searchTerm)
|
|
432
|
+
return;
|
|
433
|
+
const trimmedValue = searchTerm.trim();
|
|
434
|
+
if (isMultiple) {
|
|
435
|
+
const newValues = [...selectedValues, trimmedValue];
|
|
436
|
+
setValue(props.name, newValues, { shouldValidate: true });
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
setValue(props.name, trimmedValue, { shouldValidate: true });
|
|
440
|
+
setIsOpen(false);
|
|
441
|
+
}
|
|
442
|
+
setSearchTerm('');
|
|
443
|
+
setInputValue('');
|
|
444
|
+
};
|
|
445
|
+
const handleCreateRelationshipSuccess = (newRecord) => {
|
|
446
|
+
const id = newRecord._id || newRecord.id;
|
|
447
|
+
const title = newRecord[props.relationship.titleAttribute] || id;
|
|
448
|
+
setRelationshipOptions(prev => ({ ...prev, [id]: title }));
|
|
449
|
+
setRelationshipRecords(prev => ({ ...prev, [id]: newRecord }));
|
|
450
|
+
// Format and cache the label for the newly created record
|
|
451
|
+
const formatted = formatOptionLabel(id, title, newRecord);
|
|
452
|
+
setFormattedLabels(prev => ({ ...prev, [id]: formatted }));
|
|
453
|
+
setIndividuallyFetchedIds(prev => new Set(prev).add(id));
|
|
454
|
+
if (isMultiple) {
|
|
455
|
+
const newValues = [...selectedValues, id];
|
|
456
|
+
setValue(props.name, newValues, { shouldValidate: true });
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
setValue(props.name, id, { shouldValidate: true });
|
|
460
|
+
}
|
|
461
|
+
setShowCreateModal(false);
|
|
462
|
+
setIsOpen(false);
|
|
463
|
+
setSearchTerm('');
|
|
464
|
+
};
|
|
465
|
+
const handleKeyDown = (e) => {
|
|
466
|
+
if (e.key === 'Enter') {
|
|
467
|
+
e.preventDefault();
|
|
468
|
+
if (canCreateNew) {
|
|
469
|
+
createNewOption();
|
|
470
|
+
}
|
|
471
|
+
else if (filteredOptions.length === 1) {
|
|
472
|
+
toggleOption(filteredOptions[0][0]);
|
|
473
|
+
setSearchTerm('');
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
else if (e.key === 'Backspace' && !searchTerm && selectedValues.length > 0 && isMultiple) {
|
|
477
|
+
removeValue(selectedValues[selectedValues.length - 1]);
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
const getDisplayLabel = (value) => {
|
|
481
|
+
// If we have a cached formatted label for this individually fetched record, use it
|
|
482
|
+
if (formattedLabels[value]) {
|
|
483
|
+
return formattedLabels[value];
|
|
484
|
+
}
|
|
485
|
+
// Otherwise, return the plain label
|
|
486
|
+
return effectiveOptions[value] || value;
|
|
487
|
+
};
|
|
488
|
+
return (_jsxs("div", { className: "mb-4", children: [props.label && (_jsxs("label", { htmlFor: props.name, className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1", children: [props.label, validation.required && _jsx("span", { className: "text-red-500 dark:text-red-400 ml-1", children: "*" })] })), _jsx("input", { type: "hidden", ...register(props.name, validation) }), _jsxs("div", { ref: dropdownRef, className: "relative", children: [_jsx("div", { onClick: () => !props.disabled && setIsOpen(!isOpen), className: cn('w-full min-h-[42px] px-3 py-2 border rounded-lg shadow-sm cursor-pointer', 'k-input', 'focus-within:outline-none focus-within:ring-2 focus-within:ring-ring ', 'transition duration-150 ease-in-out', hasError &&
|
|
489
|
+
'border-red-500 dark:border-red-400 focus-within:ring-red-500 dark:focus-within:ring-red-400', props.disabled && 'opacity-60 cursor-not-allowed bg-muted'), children: _jsxs("div", { className: "flex flex-wrap gap-2 items-center", children: [selectedValues.map((value) => {
|
|
490
|
+
const displayLabel = getDisplayLabel(value);
|
|
491
|
+
const hasHTML = /<[^>]+>/.test(displayLabel);
|
|
492
|
+
return (_jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-1 rounded text-sm font-medium bg-accent-soft dark:bg-accent-soft text-accent", children: [hasHTML ? (_jsx("span", { dangerouslySetInnerHTML: { __html: displayLabel } })) : (_jsx("span", { children: displayLabel })), !props.disabled && (_jsx("button", { type: "button", onClick: e => {
|
|
493
|
+
e.stopPropagation();
|
|
494
|
+
removeValue(value);
|
|
495
|
+
}, className: "hover:text-accent-hover transition-colors", children: _jsx(Icon, { name: "X", size: 14 }) }))] }, value));
|
|
496
|
+
}), !props.disabled && (_jsx("input", { type: "text", value: searchTerm, onChange: e => {
|
|
497
|
+
setSearchTerm(e.target.value);
|
|
498
|
+
if (!isOpen)
|
|
499
|
+
setIsOpen(true);
|
|
500
|
+
}, onKeyDown: handleKeyDown, onClick: e => {
|
|
501
|
+
e.stopPropagation();
|
|
502
|
+
setIsOpen(true);
|
|
503
|
+
}, onFocus: () => setIsOpen(true), placeholder: selectedValues.length === 0
|
|
504
|
+
? props.placeholder || translate('core:select.placeholder')
|
|
505
|
+
: '', className: "flex-1 min-w-[120px] outline-none bg-transparent text-fg", disabled: props.disabled })), _jsx(Icon, { name: isOpen ? 'ChevronUp' : 'ChevronDown', size: 16, className: "text-gray-400 shrink-0" })] }) }), isOpen && !props.disabled && (_jsxs("div", { className: "absolute z-50 w-full mt-1 bg-white dark:bg-gray-800 border border-border rounded-lg shadow-lg max-h-60 overflow-auto", children: [isLoadingRelationship && (_jsxs("div", { className: "px-3 py-2 text-sm text-fg-secondary flex items-center gap-2", children: [_jsx(Icon, { name: "Loader2", size: 16, className: "animate-spin" }), _jsx("span", { children: translate('core:select.loading') })] })), !isLoadingRelationship &&
|
|
506
|
+
filteredOptions.length === 0 &&
|
|
507
|
+
!canCreateNew &&
|
|
508
|
+
!canCreateRelationship && (_jsx("div", { className: "px-3 py-2 text-sm text-fg-secondary", children: searchTerm
|
|
509
|
+
? translate('core:select.no_matching')
|
|
510
|
+
: translate('core:filters.no_options') })), !isLoadingRelationship &&
|
|
511
|
+
filteredOptions.map(([value, label]) => {
|
|
512
|
+
const record = relationshipRecords[value];
|
|
513
|
+
const displayLabel = formatOptionLabel(value, label, record);
|
|
514
|
+
const hasHTML = /<[^>]+>/.test(displayLabel);
|
|
515
|
+
return (_jsx("div", { onClick: () => {
|
|
516
|
+
toggleOption(value);
|
|
517
|
+
setSearchTerm('');
|
|
518
|
+
}, className: cn('px-3 py-2 cursor-pointer text-sm transition-colors text-fg', 'hover:bg-gray-100 dark:hover:bg-gray-700', selectedValues.includes(value) && 'bg-accent-soft text-accent'), children: _jsxs("div", { className: "flex items-center justify-between", children: [hasHTML ? (_jsx("span", { dangerouslySetInnerHTML: { __html: displayLabel } })) : (_jsx("span", { children: displayLabel })), selectedValues.includes(value) && _jsx(Icon, { name: "Check", size: 16 })] }) }, value));
|
|
519
|
+
}), canCreateNew && !canCreateRelationship && (_jsx("div", { onClick: createNewOption, className: cn('px-3 py-2 cursor-pointer text-sm transition-colors hover:bg-gray-100 dark:hover:bg-gray-700 text-accent font-medium', filteredOptions.length > 0 && 'border-t border-border'), children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { name: "Plus", size: 16 }), _jsx("span", { children: translate('core:select.create', { term: searchTerm }) })] }) })), canCreateRelationship && (_jsx("div", { onClick: () => {
|
|
520
|
+
setShowCreateModal(true);
|
|
521
|
+
setIsOpen(false);
|
|
522
|
+
}, className: cn('px-3 py-2 cursor-pointer text-sm transition-colors hover:bg-gray-100 dark:hover:bg-gray-700 text-accent font-medium', filteredOptions.length > 0 && 'border-t border-border'), children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Icon, { name: "Plus", size: 16 }), _jsx("span", { children: props.createOptionModalHeading || translate('core:select.create_new') })] }) }))] }))] }), hasError && _jsx("p", { className: "mt-1 text-sm text-red-600 dark:text-red-400", children: error?.message }), props.helperText && _jsx("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: props.helperText }), !hasError && _jsx(HintDisplay, { hint: props.hint, hintIcon: props.hintIcon, hintColor: props.hintColor }), canCreateRelationship && showCreateModal && (_jsx(ResourceFormModal, { isOpen: showCreateModal, onClose: () => setShowCreateModal(false), mode: "create", resourceName: props.createOptionModalHeading || translate('core:common.record'), resourceSlug: props.relationship.resource || props.relationship.name, apiBaseUrl: props.apiBaseUrl, formSchema: { type: 'form', components: props.createOptionForm || [] }, onSuccess: handleCreateRelationshipSuccess, depth: 1 }))] }));
|
|
523
|
+
}
|