@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,478 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import React, { useCallback, useState, useEffect, useRef, useMemo } from 'react';
|
|
3
|
+
import { useFormContext } from 'react-hook-form';
|
|
4
|
+
import { getFieldError } from '../utils/fieldErrors';
|
|
5
|
+
import { useDropzone } from 'react-dropzone';
|
|
6
|
+
import { Check, Upload, File, Trash2, Loader2 } from 'lucide-react';
|
|
7
|
+
import { HintDisplay } from './utils/HintDisplay';
|
|
8
|
+
import { ViewFieldWrapper } from './utils/ViewFieldWrapper';
|
|
9
|
+
import { MediaPreviewModal } from './utils/MediaPreviewModal';
|
|
10
|
+
import { authenticatedFetch } from '../api/authenticatedFetch';
|
|
11
|
+
import { useValidation } from '../hooks/useValidation';
|
|
12
|
+
import { translate } from '../i18n/activeLocale';
|
|
13
|
+
/**
|
|
14
|
+
* Generate a unique ID for file tracking
|
|
15
|
+
*/
|
|
16
|
+
const generateId = () => `file-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
17
|
+
/**
|
|
18
|
+
* Convert File to base64 string
|
|
19
|
+
*/
|
|
20
|
+
const fileToBase64 = (file) => {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const reader = new FileReader();
|
|
23
|
+
reader.onload = () => {
|
|
24
|
+
const result = reader.result;
|
|
25
|
+
resolve(result.split(',')[1]);
|
|
26
|
+
};
|
|
27
|
+
reader.onerror = reject;
|
|
28
|
+
reader.readAsDataURL(file);
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Format bytes to human readable size
|
|
33
|
+
*/
|
|
34
|
+
const formatFileSize = (bytes) => {
|
|
35
|
+
if (bytes === 0)
|
|
36
|
+
return '0 Bytes';
|
|
37
|
+
const k = 1024;
|
|
38
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
39
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
40
|
+
return `${Math.round((bytes / Math.pow(k, i)) * 100) / 100} ${sizes[i]}`;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Check if a URL points to an image
|
|
44
|
+
*/
|
|
45
|
+
const isImageUrl = (url) => {
|
|
46
|
+
if (!url)
|
|
47
|
+
return false;
|
|
48
|
+
if (url.startsWith('blob:'))
|
|
49
|
+
return true;
|
|
50
|
+
return /\.(jpg|jpeg|png|gif|webp|svg|bmp)$/i.test(url);
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Check if a URL points to a video
|
|
54
|
+
*/
|
|
55
|
+
const isVideoUrl = (url) => {
|
|
56
|
+
if (!url)
|
|
57
|
+
return false;
|
|
58
|
+
return /\.(mp4|webm|ogg|mov|m4v)$/i.test(url);
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Check if a URL points to an audio file
|
|
62
|
+
*/
|
|
63
|
+
const isAudioUrl = (url) => {
|
|
64
|
+
if (!url)
|
|
65
|
+
return false;
|
|
66
|
+
return /\.(mp3|wav|ogg|m4a)$/i.test(url);
|
|
67
|
+
};
|
|
68
|
+
function FileUploadViewField({ label, value }) {
|
|
69
|
+
const [previewOpen, setPreviewOpen] = useState(false);
|
|
70
|
+
const [previewUrl, setPreviewUrl] = useState(null);
|
|
71
|
+
const [previewType, setPreviewType] = useState('image');
|
|
72
|
+
const displayValue = value || null;
|
|
73
|
+
const filesArray = Array.isArray(displayValue) ? displayValue : displayValue ? [displayValue] : [];
|
|
74
|
+
if (filesArray.length === 0) {
|
|
75
|
+
return _jsx(ViewFieldWrapper, { label: label, children: "-" });
|
|
76
|
+
}
|
|
77
|
+
const openPreview = (url, type) => {
|
|
78
|
+
setPreviewUrl(url);
|
|
79
|
+
setPreviewType(type);
|
|
80
|
+
setPreviewOpen(true);
|
|
81
|
+
};
|
|
82
|
+
return (_jsxs(ViewFieldWrapper, { label: label, children: [_jsx("div", { className: "flex flex-wrap gap-4", children: filesArray.map((file, index) => {
|
|
83
|
+
const fileUrl = typeof file === 'object' && file !== null ? file.url || file.key : file;
|
|
84
|
+
if (!fileUrl) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const image = isImageUrl(fileUrl);
|
|
88
|
+
const video = isVideoUrl(fileUrl);
|
|
89
|
+
if (video) {
|
|
90
|
+
return (_jsx("div", { className: "relative", children: _jsx("video", { src: fileUrl, controls: true, className: "w-72 h-44 rounded-lg border border-border object-cover bg-black", onClick: e => {
|
|
91
|
+
e.stopPropagation();
|
|
92
|
+
openPreview(fileUrl, 'video');
|
|
93
|
+
} }) }, index));
|
|
94
|
+
}
|
|
95
|
+
if (image) {
|
|
96
|
+
return (_jsx("div", { className: "relative", children: _jsx("img", { src: fileUrl, alt: `${label || translate('core:common.image')} ${index + 1}`, className: "w-40 h-40 object-cover rounded-lg border border-border cursor-pointer hover:opacity-80 transition-opacity", onClick: () => openPreview(fileUrl, 'image') }) }, index));
|
|
97
|
+
}
|
|
98
|
+
return (_jsxs("div", { className: "flex items-center gap-2 p-2 border border-border rounded-lg bg-surface", children: [_jsx(File, { size: 20, className: "text-fg-secondary" }), _jsx("span", { className: "text-sm text-fg", children: typeof file === 'object' && file !== null
|
|
99
|
+
? file.key || translate('core:common.file')
|
|
100
|
+
: translate('core:common.file') })] }, index));
|
|
101
|
+
}) }), previewUrl && (_jsx(MediaPreviewModal, { isOpen: previewOpen, onClose: () => setPreviewOpen(false), mediaUrl: previewUrl, mediaType: previewType, title: label, autoplay: previewType === 'video', controls: true }))] }));
|
|
102
|
+
}
|
|
103
|
+
export function FileUploadField({ name, label, helperText, hint, hintIcon, hintColor, acceptedFileTypes = [], maxSize, minSize, multiple = false, maxFiles, disabled, required, visibility, directory, bucket, apiBaseUrl, resource, mode, value, validation, operation, }) {
|
|
104
|
+
// View mode: render formatted display (with image/video preview)
|
|
105
|
+
if (mode === 'view') {
|
|
106
|
+
return _jsx(FileUploadViewField, { label: label, value: value });
|
|
107
|
+
}
|
|
108
|
+
const { setValue, watch, register, formState: { errors: formErrors }, } = useFormContext();
|
|
109
|
+
const [files, setFiles] = useState([]);
|
|
110
|
+
const [errors, setErrors] = useState([]);
|
|
111
|
+
const initializedRef = useRef(false);
|
|
112
|
+
// Preview state for edit mode
|
|
113
|
+
const [editPreviewOpen, setEditPreviewOpen] = useState(false);
|
|
114
|
+
const [editPreviewUrl, setEditPreviewUrl] = useState(null);
|
|
115
|
+
const [editPreviewType, setEditPreviewType] = useState('image');
|
|
116
|
+
const currentValue = watch(name);
|
|
117
|
+
// Evaluate validation conditions with form context
|
|
118
|
+
const validationResult = useValidation(validation?.rules || [], operation, name);
|
|
119
|
+
const isRequired = validationResult.required !== undefined;
|
|
120
|
+
const fieldError = getFieldError(formErrors, name);
|
|
121
|
+
// Register field with React Hook Form for validation
|
|
122
|
+
React.useEffect(() => {
|
|
123
|
+
register(name, validationResult);
|
|
124
|
+
}, [register, name, validationResult]);
|
|
125
|
+
// Compute endpoints
|
|
126
|
+
// Use generic media endpoint if no resource is provided, otherwise use resource-specific endpoint
|
|
127
|
+
const uploadEndpoint = useMemo(() => {
|
|
128
|
+
if (!apiBaseUrl)
|
|
129
|
+
return null;
|
|
130
|
+
if (resource) {
|
|
131
|
+
return `${apiBaseUrl}/${resource}/media/upload`;
|
|
132
|
+
}
|
|
133
|
+
return `${apiBaseUrl}/media/upload`;
|
|
134
|
+
}, [apiBaseUrl, resource]);
|
|
135
|
+
const deleteEndpoint = useMemo(() => {
|
|
136
|
+
if (!apiBaseUrl)
|
|
137
|
+
return null;
|
|
138
|
+
if (resource) {
|
|
139
|
+
return `${apiBaseUrl}/${resource}/media/delete`;
|
|
140
|
+
}
|
|
141
|
+
return `${apiBaseUrl}/media/delete`;
|
|
142
|
+
}, [apiBaseUrl, resource]);
|
|
143
|
+
/**
|
|
144
|
+
* Initialize from existing value (server sends { url, key } objects)
|
|
145
|
+
* Only runs once on mount if there's an initial value
|
|
146
|
+
*/
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
if (initializedRef.current)
|
|
149
|
+
return;
|
|
150
|
+
if (!currentValue)
|
|
151
|
+
return;
|
|
152
|
+
initializedRef.current = true;
|
|
153
|
+
const existingFiles = [];
|
|
154
|
+
const keys = [];
|
|
155
|
+
const processItem = (item) => {
|
|
156
|
+
if (!item)
|
|
157
|
+
return null;
|
|
158
|
+
// New format: { key, bucket, url }
|
|
159
|
+
if (typeof item === 'object' && item.key) {
|
|
160
|
+
keys.push(item.key);
|
|
161
|
+
return {
|
|
162
|
+
id: generateId(),
|
|
163
|
+
key: item.key,
|
|
164
|
+
bucket: item.bucket,
|
|
165
|
+
url: item.url || item.key, // Use url if available, fallback to key
|
|
166
|
+
status: 'existing',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
// String form: treat the value as a storage key
|
|
170
|
+
if (typeof item === 'string') {
|
|
171
|
+
keys.push(item);
|
|
172
|
+
return {
|
|
173
|
+
id: generateId(),
|
|
174
|
+
key: item,
|
|
175
|
+
url: item,
|
|
176
|
+
status: 'existing',
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
};
|
|
181
|
+
if (Array.isArray(currentValue)) {
|
|
182
|
+
currentValue.forEach(item => {
|
|
183
|
+
const fileItem = processItem(item);
|
|
184
|
+
if (fileItem)
|
|
185
|
+
existingFiles.push(fileItem);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
const fileItem = processItem(currentValue);
|
|
190
|
+
if (fileItem)
|
|
191
|
+
existingFiles.push(fileItem);
|
|
192
|
+
}
|
|
193
|
+
setFiles(existingFiles);
|
|
194
|
+
// Set form value to just keys (don't validate on initial load)
|
|
195
|
+
setValue(name, multiple ? keys : keys[0] || null, { shouldValidate: false });
|
|
196
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
197
|
+
}, []); // Only run once on mount
|
|
198
|
+
/**
|
|
199
|
+
* Upload a single file to the server
|
|
200
|
+
*/
|
|
201
|
+
const uploadFile = useCallback(async (file) => {
|
|
202
|
+
if (!uploadEndpoint) {
|
|
203
|
+
console.warn('No upload endpoint configured');
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
const base64 = await fileToBase64(file);
|
|
208
|
+
const response = await authenticatedFetch(uploadEndpoint, {
|
|
209
|
+
method: 'POST',
|
|
210
|
+
headers: { 'Content-Type': 'application/json' },
|
|
211
|
+
body: JSON.stringify({
|
|
212
|
+
file: base64,
|
|
213
|
+
filename: file.name,
|
|
214
|
+
contentType: file.type,
|
|
215
|
+
fieldName: name,
|
|
216
|
+
isArray: multiple,
|
|
217
|
+
path: directory,
|
|
218
|
+
visibility,
|
|
219
|
+
bucket, // Pass bucket adapter name
|
|
220
|
+
}),
|
|
221
|
+
}, apiBaseUrl);
|
|
222
|
+
if (!response.ok) {
|
|
223
|
+
const error = await response.json();
|
|
224
|
+
throw new Error(`Upload failed: ${error.message || response.statusText}`);
|
|
225
|
+
}
|
|
226
|
+
return await response.json();
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
console.error('Upload error:', error);
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}, [uploadEndpoint, name, multiple, directory, visibility]);
|
|
233
|
+
/**
|
|
234
|
+
* Delete a file from bucket
|
|
235
|
+
*/
|
|
236
|
+
const deleteFromStorage = useCallback(async (payload) => {
|
|
237
|
+
if (!deleteEndpoint)
|
|
238
|
+
return true;
|
|
239
|
+
try {
|
|
240
|
+
const body = typeof payload === 'string' ? { key: payload } : payload;
|
|
241
|
+
const response = await authenticatedFetch(deleteEndpoint, {
|
|
242
|
+
method: 'POST',
|
|
243
|
+
headers: { 'Content-Type': 'application/json' },
|
|
244
|
+
body: JSON.stringify(body),
|
|
245
|
+
}, apiBaseUrl);
|
|
246
|
+
return response.ok;
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
console.error('Delete error:', error);
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
}, [deleteEndpoint]);
|
|
253
|
+
/**
|
|
254
|
+
* Update form value with current keys
|
|
255
|
+
* Backend will automatically detect deletions by comparing with existing record
|
|
256
|
+
*/
|
|
257
|
+
const updateFormValue = useCallback((fileList) => {
|
|
258
|
+
// Get keys from uploaded/existing files
|
|
259
|
+
const keys = fileList
|
|
260
|
+
.filter(f => f.key && (f.status === 'uploaded' || f.status === 'existing'))
|
|
261
|
+
.map(f => f.key);
|
|
262
|
+
// Set main field value - backend will detect deletions automatically
|
|
263
|
+
// Trigger validation when value changes
|
|
264
|
+
if (multiple) {
|
|
265
|
+
setValue(name, keys.length > 0 ? keys : [], { shouldValidate: true });
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
setValue(name, keys[0] || null, { shouldValidate: true });
|
|
269
|
+
}
|
|
270
|
+
}, [multiple, name, setValue]);
|
|
271
|
+
/**
|
|
272
|
+
* Handle file drop/selection
|
|
273
|
+
*/
|
|
274
|
+
const onDrop = useCallback(async (acceptedFiles, rejectedFiles) => {
|
|
275
|
+
setErrors([]);
|
|
276
|
+
// Handle rejected files
|
|
277
|
+
const newErrors = [];
|
|
278
|
+
rejectedFiles.forEach(({ file, errors: fileErrors }) => {
|
|
279
|
+
fileErrors.forEach((error) => {
|
|
280
|
+
switch (error.code) {
|
|
281
|
+
case 'file-too-large':
|
|
282
|
+
newErrors.push(`${file.name}: File is too large`);
|
|
283
|
+
break;
|
|
284
|
+
case 'file-too-small':
|
|
285
|
+
newErrors.push(`${file.name}: File is too small`);
|
|
286
|
+
break;
|
|
287
|
+
case 'file-invalid-type':
|
|
288
|
+
newErrors.push(`${file.name}: Invalid file type`);
|
|
289
|
+
break;
|
|
290
|
+
case 'too-many-files':
|
|
291
|
+
newErrors.push(`Too many files. Maximum is ${maxFiles}`);
|
|
292
|
+
break;
|
|
293
|
+
default:
|
|
294
|
+
newErrors.push(`${file.name}: ${error.message}`);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
if (newErrors.length > 0) {
|
|
299
|
+
setErrors(newErrors);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (acceptedFiles.length === 0)
|
|
303
|
+
return;
|
|
304
|
+
// Create file items with pending status
|
|
305
|
+
const newFileItems = acceptedFiles.map(file => ({
|
|
306
|
+
id: generateId(),
|
|
307
|
+
file,
|
|
308
|
+
blobUrl: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined,
|
|
309
|
+
status: 'pending',
|
|
310
|
+
}));
|
|
311
|
+
// Handle single file replacement
|
|
312
|
+
let updatedFiles;
|
|
313
|
+
if (!multiple) {
|
|
314
|
+
// Clean up existing file
|
|
315
|
+
const existingFile = files[0];
|
|
316
|
+
if (existingFile) {
|
|
317
|
+
// Revoke blob URL
|
|
318
|
+
if (existingFile.blobUrl) {
|
|
319
|
+
URL.revokeObjectURL(existingFile.blobUrl);
|
|
320
|
+
}
|
|
321
|
+
if (existingFile.key && existingFile.status === 'uploaded') {
|
|
322
|
+
// Delete immediately (not saved yet) - backend will handle existing files
|
|
323
|
+
const deletePayload = existingFile.bucket
|
|
324
|
+
? { key: existingFile.key, bucket: existingFile.bucket }
|
|
325
|
+
: { key: existingFile.key };
|
|
326
|
+
deleteFromStorage(deletePayload);
|
|
327
|
+
}
|
|
328
|
+
// Note: For existing files, backend will detect deletion automatically
|
|
329
|
+
}
|
|
330
|
+
updatedFiles = newFileItems;
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
updatedFiles = [...files, ...newFileItems];
|
|
334
|
+
}
|
|
335
|
+
// Update state with pending files
|
|
336
|
+
setFiles(updatedFiles);
|
|
337
|
+
// Upload files
|
|
338
|
+
if (uploadEndpoint) {
|
|
339
|
+
// Mark files as uploading
|
|
340
|
+
setFiles(prev => prev.map(f => newFileItems.find(nf => nf.id === f.id) ? { ...f, status: 'uploading' } : f));
|
|
341
|
+
// Upload all new files and collect results
|
|
342
|
+
const uploadResults = [];
|
|
343
|
+
for (const fileItem of newFileItems) {
|
|
344
|
+
const result = await uploadFile(fileItem.file);
|
|
345
|
+
uploadResults.push({ id: fileItem.id, result });
|
|
346
|
+
}
|
|
347
|
+
// Build the final file list
|
|
348
|
+
const finalFiles = [];
|
|
349
|
+
// Start with existing/previously uploaded files (for multiple mode)
|
|
350
|
+
if (multiple) {
|
|
351
|
+
for (const f of updatedFiles) {
|
|
352
|
+
// Keep files that aren't part of this upload batch
|
|
353
|
+
if (!newFileItems.find(nf => nf.id === f.id)) {
|
|
354
|
+
finalFiles.push(f);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// Add newly uploaded files with their results
|
|
359
|
+
for (const fileItem of newFileItems) {
|
|
360
|
+
const uploadResult = uploadResults.find(r => r.id === fileItem.id);
|
|
361
|
+
if (uploadResult?.result?.data?.url && uploadResult?.result?.data?.key) {
|
|
362
|
+
// Revoke blob URL since we have server URL now
|
|
363
|
+
if (fileItem.blobUrl) {
|
|
364
|
+
URL.revokeObjectURL(fileItem.blobUrl);
|
|
365
|
+
}
|
|
366
|
+
// Create new object explicitly to avoid any issues with spread
|
|
367
|
+
// Extract bucket from formatted data if available
|
|
368
|
+
const bucketName = bucket;
|
|
369
|
+
finalFiles.push({
|
|
370
|
+
id: fileItem.id,
|
|
371
|
+
file: fileItem.file,
|
|
372
|
+
key: uploadResult.result.data.key,
|
|
373
|
+
bucket: bucketName,
|
|
374
|
+
url: uploadResult.result.data.url,
|
|
375
|
+
status: 'uploaded',
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
finalFiles.push({
|
|
380
|
+
id: fileItem.id,
|
|
381
|
+
file: fileItem.file,
|
|
382
|
+
blobUrl: fileItem.blobUrl,
|
|
383
|
+
status: 'error',
|
|
384
|
+
error: 'Upload failed',
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Update state and form value separately (not inside setFiles callback)
|
|
389
|
+
setFiles(finalFiles);
|
|
390
|
+
updateFormValue(finalFiles);
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
// No upload endpoint - just update state
|
|
394
|
+
setFiles(updatedFiles);
|
|
395
|
+
}
|
|
396
|
+
}, [files, multiple, maxFiles, uploadEndpoint, uploadFile, deleteFromStorage, updateFormValue]);
|
|
397
|
+
/**
|
|
398
|
+
* Remove a file
|
|
399
|
+
* Backend will automatically detect deletions by comparing with existing record
|
|
400
|
+
*/
|
|
401
|
+
const removeFile = useCallback(async (fileId) => {
|
|
402
|
+
const fileToRemove = files.find(f => f.id === fileId);
|
|
403
|
+
if (!fileToRemove)
|
|
404
|
+
return;
|
|
405
|
+
// Revoke blob URL
|
|
406
|
+
if (fileToRemove.blobUrl) {
|
|
407
|
+
URL.revokeObjectURL(fileToRemove.blobUrl);
|
|
408
|
+
}
|
|
409
|
+
if (fileToRemove.key && fileToRemove.status === 'uploaded') {
|
|
410
|
+
// Delete immediately (not saved to DB yet) - backend will handle existing files
|
|
411
|
+
const deletePayload = fileToRemove.bucket
|
|
412
|
+
? { key: fileToRemove.key, bucket: fileToRemove.bucket }
|
|
413
|
+
: { key: fileToRemove.key };
|
|
414
|
+
const deleted = await deleteFromStorage(deletePayload);
|
|
415
|
+
if (!deleted) {
|
|
416
|
+
setErrors(['Failed to delete file from bucket']);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// Note: For existing files, backend will detect deletion automatically
|
|
421
|
+
// Update files state
|
|
422
|
+
const updatedFiles = files.filter(f => f.id !== fileId);
|
|
423
|
+
setFiles(updatedFiles);
|
|
424
|
+
// Update form value - backend will detect deletions
|
|
425
|
+
updateFormValue(updatedFiles);
|
|
426
|
+
}, [files, deleteFromStorage, updateFormValue]);
|
|
427
|
+
// Calculate upload limits
|
|
428
|
+
const validFileCount = files.filter(f => f.status !== 'error').length;
|
|
429
|
+
const maxAllowed = multiple ? maxFiles || Infinity : 1;
|
|
430
|
+
const remainingSlots = Math.max(0, maxAllowed - validFileCount);
|
|
431
|
+
const isAtLimit = remainingSlots <= 0;
|
|
432
|
+
// Dropzone config
|
|
433
|
+
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
434
|
+
onDrop,
|
|
435
|
+
accept: acceptedFileTypes.length > 0
|
|
436
|
+
? acceptedFileTypes.reduce((acc, type) => ({ ...acc, [type]: [] }), {})
|
|
437
|
+
: undefined,
|
|
438
|
+
maxSize: maxSize ? maxSize * 1024 : undefined,
|
|
439
|
+
minSize: minSize ? minSize * 1024 : undefined,
|
|
440
|
+
multiple,
|
|
441
|
+
maxFiles: remainingSlots,
|
|
442
|
+
disabled: disabled || isAtLimit,
|
|
443
|
+
});
|
|
444
|
+
// Get display URL for a file
|
|
445
|
+
const getDisplayUrl = (file) => file.url || file.blobUrl;
|
|
446
|
+
// Get display name for a file
|
|
447
|
+
const getDisplayName = (file) => {
|
|
448
|
+
if (file.file?.name)
|
|
449
|
+
return file.file.name;
|
|
450
|
+
if (file.url)
|
|
451
|
+
return file.url.split('/').pop() || 'File';
|
|
452
|
+
if (file.key)
|
|
453
|
+
return file.key.split('/').pop() || 'File';
|
|
454
|
+
return 'File';
|
|
455
|
+
};
|
|
456
|
+
const openEditPreview = (url, type) => {
|
|
457
|
+
setEditPreviewUrl(url);
|
|
458
|
+
setEditPreviewType(type);
|
|
459
|
+
setEditPreviewOpen(true);
|
|
460
|
+
};
|
|
461
|
+
return (_jsxs("div", { className: "space-y-2", children: [label && (_jsxs("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300", children: [label, isRequired && _jsx("span", { className: "text-red-500 dark:text-red-400 ml-1", children: "*" })] })), helperText && _jsx("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: helperText }), _jsx(HintDisplay, { hint: hint, hintIcon: hintIcon, hintColor: hintColor }), isAtLimit ? (_jsxs("div", { className: "border-2 border-dashed rounded-lg p-6 text-center border-border bg-gray-50 dark:bg-gray-800/50", children: [_jsx(Check, { className: "mx-auto h-12 w-12 text-green-500 dark:text-green-400", strokeWidth: 1.5 }), _jsx("p", { className: "mt-2 text-sm text-gray-600 dark:text-gray-400", children: multiple ? (_jsxs(_Fragment, { children: ["Maximum of ", _jsx("span", { className: "font-semibold", children: maxFiles }), " files reached"] })) : (_jsx(_Fragment, { children: translate('core:file.uploaded') })) }), _jsxs("p", { className: "mt-1 text-xs text-gray-500", children: ["Remove ", multiple ? 'a file' : 'the file', " to upload a new one"] })] })) : (_jsxs("div", { ...getRootProps(), className: `
|
|
462
|
+
border-2 border-dashed rounded-lg p-6 text-center cursor-pointer transition-colors
|
|
463
|
+
${isDragActive ? 'border-accent bg-accent-soft dark:bg-accent-soft' : 'border-border hover:border-gray-400'}
|
|
464
|
+
${disabled ? 'opacity-50 cursor-not-allowed' : ''}
|
|
465
|
+
`, children: [_jsx("input", { ...getInputProps() }), _jsx(Upload, { className: "mx-auto h-12 w-12 text-gray-400 dark:text-gray-500", strokeWidth: 1.5 }), _jsx("p", { className: "mt-2 text-sm text-gray-600 dark:text-gray-400", children: isDragActive ? (_jsx("span", { className: "font-semibold text-accent", children: translate('core:file.drop_here') })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "font-semibold text-accent", children: translate('core:file.click_to_upload') }), ' ', "or drag and drop"] })) }), _jsxs("p", { className: "mt-1 text-xs text-gray-500", children: [acceptedFileTypes.length > 0 ? acceptedFileTypes.join(', ') : 'Any file type', maxSize && ` • Max ${maxSize}KB`, multiple && maxFiles && ` • ${validFileCount}/${maxFiles} files`] })] })), (errors.length > 0 || fieldError) && (_jsxs("div", { className: "space-y-1", children: [errors.map((error, index) => (_jsx("p", { className: "text-sm text-red-600 dark:text-red-400", children: error }, index))), fieldError && (_jsx("p", { className: "text-sm text-red-600 dark:text-red-400", children: fieldError.message }))] })), files.length > 0 && (_jsx("div", { className: "space-y-2 mt-4", children: files.map(file => {
|
|
466
|
+
const displayUrl = getDisplayUrl(file);
|
|
467
|
+
const displayName = getDisplayName(file);
|
|
468
|
+
const isImage = (displayUrl && isImageUrl(displayUrl)) || file.file?.type?.startsWith('image/');
|
|
469
|
+
const isVideo = (displayUrl && isVideoUrl(displayUrl)) || file.file?.type?.startsWith('video/');
|
|
470
|
+
const isAudio = (displayUrl && isAudioUrl(displayUrl)) || file.file?.type?.startsWith('audio/');
|
|
471
|
+
const isLoading = file.status === 'pending' || file.status === 'uploading';
|
|
472
|
+
const hasError = file.status === 'error';
|
|
473
|
+
return (_jsxs("div", { className: `flex items-center justify-between p-3 bg-muted rounded-lg border border-border
|
|
474
|
+
${isLoading ? 'opacity-60' : ''}
|
|
475
|
+
${hasError ? 'border-red-500 dark:border-red-400' : ''}
|
|
476
|
+
`, children: [_jsxs("div", { className: "flex items-center space-x-3 flex-1 min-w-0", children: [displayUrl && (isImage || isVideo || isAudio) ? (_jsx("button", { type: "button", onClick: () => openEditPreview(displayUrl, isVideo ? 'video' : isAudio ? 'audio' : 'image'), className: "h-14 w-14 rounded overflow-hidden flex items-center justify-center bg-muted hover:opacity-80 transition-opacity", children: isImage ? (_jsx("img", { src: displayUrl, alt: displayName, className: "h-full w-full object-cover" })) : isVideo ? (_jsx("div", { className: "h-full w-full flex items-center justify-center bg-black text-white text-xs", children: _jsx("span", { className: "px-1", children: translate('core:file.video') }) })) : (_jsx("div", { className: "h-full w-full flex items-center justify-center bg-linear-to-r from-blue-600 to-purple-600 text-white text-xs", children: _jsx("span", { className: "px-1", children: translate('core:file.audio') }) })) })) : (_jsx("div", { className: "h-10 w-10 bg-muted rounded flex items-center justify-center", children: _jsx(File, { className: "h-6 w-6 text-gray-400 dark:text-gray-500" }) })), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100 truncate", children: displayName }), file.status === 'pending' && (_jsxs("p", { className: "text-xs text-gray-500 flex items-center gap-1", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin" }), "Waiting..."] })), file.status === 'uploading' && (_jsxs("p", { className: "text-xs text-accent flex items-center gap-1", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin" }), "Uploading..."] })), file.status === 'error' && (_jsx("p", { className: "text-xs text-red-500 dark:text-red-400", children: file.error || translate('core:common.error') })), file.status === 'uploaded' && (_jsx("p", { className: "text-xs text-green-500 dark:text-green-400", children: translate('core:file.saved') })), file.status === 'existing' && (_jsx("p", { className: "text-xs text-green-500 dark:text-green-400", children: translate('core:file.saved') })), file.file && file.status !== 'error' && (_jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: formatFileSize(file.file.size) }))] })] }), _jsx("button", { type: "button", onClick: () => removeFile(file.id), className: "ml-3 text-red-500 dark:text-red-400 hover:text-red-700 dark:hover:text-red-300 focus:outline-none", disabled: disabled || isLoading, children: _jsx(Trash2, { className: "h-5 w-5" }) })] }, file.id));
|
|
477
|
+
}) })), editPreviewUrl && (_jsx(MediaPreviewModal, { isOpen: editPreviewOpen, onClose: () => setEditPreviewOpen(false), mediaUrl: editPreviewUrl, mediaType: editPreviewType, title: typeof label === 'string' ? label : undefined, autoplay: editPreviewType === 'video' || editPreviewType === 'audio', controls: true }))] }));
|
|
478
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface GlobalSearchResult {
|
|
3
|
+
_id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
featuredImage?: {
|
|
6
|
+
url: string;
|
|
7
|
+
key: string;
|
|
8
|
+
bucket: string;
|
|
9
|
+
};
|
|
10
|
+
record: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
export interface GlobalSearchResourceResults {
|
|
13
|
+
label: string;
|
|
14
|
+
pluralLabel: string;
|
|
15
|
+
icon?: string;
|
|
16
|
+
results: GlobalSearchResult[];
|
|
17
|
+
}
|
|
18
|
+
export interface GlobalSearchProps {
|
|
19
|
+
apiBaseUrl: string;
|
|
20
|
+
onResultClick?: (resourceSlug: string, recordId: string) => void;
|
|
21
|
+
}
|
|
22
|
+
export declare function GlobalSearch({ apiBaseUrl, onResultClick }: GlobalSearchProps): React.JSX.Element;
|