@bsol-oss/react-datatable5 12.0.0-beta.83 → 12.0.0-beta.85
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/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal, Dialog, Flex, Text, useDisclosure, DialogBackdrop, RadioGroup as RadioGroup$1, Grid, Box, Slider as Slider$1, HStack, For, Tag as Tag$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, CheckboxCard as CheckboxCard$1, Tooltip as Tooltip$1, Group, InputElement, Icon, EmptyState as EmptyState$2, VStack, List, Table as Table$1, Checkbox as Checkbox$1, Card, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Image, Alert, Field as Field$1, Popover, Tabs, NumberInput, Show, RadioCard, CheckboxGroup, Center, Heading, Skeleton } from '@chakra-ui/react';
|
|
2
|
+
import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal, Dialog, Flex, Text, useDisclosure, DialogBackdrop, RadioGroup as RadioGroup$1, Grid, Box, Slider as Slider$1, HStack, For, Tag as Tag$1, Input, Menu, createRecipeContext, createContext as createContext$1, Pagination as Pagination$1, usePaginationContext, CheckboxCard as CheckboxCard$1, Tooltip as Tooltip$1, Group, InputElement, Icon, EmptyState as EmptyState$2, VStack, List, Table as Table$1, Checkbox as Checkbox$1, Card, MenuRoot as MenuRoot$1, MenuTrigger as MenuTrigger$1, Image, Alert, Field as Field$1, Popover, useFilter, useListCollection, Combobox, Tabs, NumberInput, Show, RadioCard, CheckboxGroup, Center, Heading, Skeleton } from '@chakra-ui/react';
|
|
3
3
|
import { AiOutlineColumnWidth } from 'react-icons/ai';
|
|
4
4
|
import * as React from 'react';
|
|
5
|
-
import React__default, { createContext, useContext, useState, useEffect, useRef, forwardRef } from 'react';
|
|
5
|
+
import React__default, { createContext, useContext, useState, useEffect, useRef, useMemo, forwardRef } from 'react';
|
|
6
6
|
import { LuX, LuCheck, LuChevronRight, LuSearch, LuImage, LuFile } from 'react-icons/lu';
|
|
7
7
|
import { MdOutlineSort, MdFilterAlt, MdSearch, MdOutlineChecklist, MdClear, MdOutlineViewColumn, MdFilterListAlt, MdPushPin, MdCancel, MdDateRange } from 'react-icons/md';
|
|
8
8
|
import { FaUpDown, FaGripLinesVertical, FaTrash } from 'react-icons/fa6';
|
|
@@ -21,7 +21,7 @@ import { useQueryClient, useQuery } from '@tanstack/react-query';
|
|
|
21
21
|
import { IoReload } from 'react-icons/io5';
|
|
22
22
|
import { useDebounce } from '@uidotdev/usehooks';
|
|
23
23
|
import { BsExclamationCircleFill, BsClock } from 'react-icons/bs';
|
|
24
|
-
import { HiColorSwatch
|
|
24
|
+
import { HiColorSwatch } from 'react-icons/hi';
|
|
25
25
|
import { flexRender, createColumnHelper, makeStateUpdater, functionalUpdate, useReactTable, getCoreRowModel, getFilteredRowModel, getSortedRowModel, getPaginationRowModel } from '@tanstack/react-table';
|
|
26
26
|
import { GrAscend, GrDescend } from 'react-icons/gr';
|
|
27
27
|
import { useTranslation } from 'react-i18next';
|
|
@@ -555,7 +555,7 @@ const PaginationItems = (props) => {
|
|
|
555
555
|
return page.type === "ellipsis" ? (jsx(PaginationEllipsis, { index: index, ...props }, index)) : (jsx(PaginationItem, { type: "page", value: page.value, ...props }, index));
|
|
556
556
|
}) }));
|
|
557
557
|
};
|
|
558
|
-
|
|
558
|
+
React.forwardRef(function PaginationPageText(props, ref) {
|
|
559
559
|
const { format = "compact", ...rest } = props;
|
|
560
560
|
const { page, totalPages, pageRange, count } = usePaginationContext();
|
|
561
561
|
const content = React.useMemo(() => {
|
|
@@ -4156,16 +4156,6 @@ const DateRangePicker = ({ column, schema, prefix, }) => {
|
|
|
4156
4156
|
}, monthsToDisplay: 2 })] }) })] }) }));
|
|
4157
4157
|
};
|
|
4158
4158
|
|
|
4159
|
-
function filterArray(array, searchTerm) {
|
|
4160
|
-
// Convert the search term to lower case for case-insensitive comparison
|
|
4161
|
-
const lowerCaseSearchTerm = searchTerm.toLowerCase();
|
|
4162
|
-
// Use the filter method to return an array of matching items
|
|
4163
|
-
return array.filter((item) => {
|
|
4164
|
-
// Convert each item to a string and check if it includes the search term
|
|
4165
|
-
return item.toString().toLowerCase().includes(lowerCaseSearchTerm);
|
|
4166
|
-
});
|
|
4167
|
-
}
|
|
4168
|
-
|
|
4169
4159
|
const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLimit = false, }) => {
|
|
4170
4160
|
const { watch, formState: { errors }, setValue, } = useFormContext();
|
|
4171
4161
|
const { enumPickerLabels } = useSchemaContext();
|
|
@@ -4173,89 +4163,74 @@ const EnumPicker = ({ column, isMultiple = false, schema, prefix, showTotalAndLi
|
|
|
4173
4163
|
const { required, variant } = schema;
|
|
4174
4164
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
4175
4165
|
const { gridColumn = 'span 12', gridRow = 'span 1', renderDisplay } = schema;
|
|
4176
|
-
const [searchText, setSearchText] = useState();
|
|
4177
|
-
const [limit, setLimit] = useState(10);
|
|
4178
|
-
const [openSearchResult, setOpenSearchResult] = useState();
|
|
4179
|
-
const ref = useRef(null);
|
|
4180
4166
|
const colLabel = formI18n.colLabel;
|
|
4181
4167
|
const watchEnum = watch(colLabel);
|
|
4182
4168
|
const watchEnums = (watch(colLabel) ?? []);
|
|
4183
4169
|
const dataList = schema.enum ?? [];
|
|
4184
|
-
|
|
4185
|
-
const
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4170
|
+
// Current value for combobox (array format)
|
|
4171
|
+
const currentValue = isMultiple
|
|
4172
|
+
? watchEnums.filter((val) => val != null && val !== '')
|
|
4173
|
+
: watchEnum
|
|
4174
|
+
? [watchEnum]
|
|
4175
|
+
: [];
|
|
4176
|
+
// Transform enum data for combobox collection
|
|
4177
|
+
const comboboxItems = useMemo(() => {
|
|
4178
|
+
return dataList.map((item) => ({
|
|
4179
|
+
label: !!renderDisplay === true
|
|
4180
|
+
? String(renderDisplay(item))
|
|
4181
|
+
: formI18n.t(item),
|
|
4182
|
+
value: item,
|
|
4183
|
+
}));
|
|
4184
|
+
}, [dataList, renderDisplay, formI18n]);
|
|
4185
|
+
// Use filter hook for combobox
|
|
4186
|
+
const { contains } = useFilter({ sensitivity: 'base' });
|
|
4187
|
+
// Create collection for combobox
|
|
4188
|
+
const { collection, filter } = useListCollection({
|
|
4189
|
+
initialItems: comboboxItems,
|
|
4190
|
+
itemToString: (item) => item.label,
|
|
4191
|
+
itemToValue: (item) => item.value,
|
|
4192
|
+
filter: contains,
|
|
4193
|
+
});
|
|
4194
|
+
// Handle input value change (search)
|
|
4195
|
+
const handleInputValueChange = (details) => {
|
|
4196
|
+
filter(details.inputValue);
|
|
4197
|
+
};
|
|
4198
|
+
// Handle value change
|
|
4199
|
+
const handleValueChange = (details) => {
|
|
4200
|
+
if (isMultiple) {
|
|
4201
|
+
setValue(colLabel, details.value);
|
|
4202
|
+
}
|
|
4203
|
+
else {
|
|
4204
|
+
setValue(colLabel, details.value[0] || '');
|
|
4205
|
+
}
|
|
4189
4206
|
};
|
|
4190
4207
|
if (variant === 'radio') {
|
|
4191
4208
|
return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
4192
|
-
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsx(RadioGroup$1.Root, {
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
}
|
|
4199
|
-
const newSet = new Set([...(watchEnums ?? []), item]);
|
|
4200
|
-
setValue(colLabel, [...newSet]);
|
|
4201
|
-
}, value: item, children: [jsx(RadioGroup$1.ItemHiddenInput, {}), jsx(RadioGroup$1.ItemIndicator, {}), jsx(RadioGroup$1.ItemText, { children: !!renderDisplay === true
|
|
4209
|
+
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsx(RadioGroup$1.Root, { value: !isMultiple ? watchEnum : undefined, onValueChange: (details) => {
|
|
4210
|
+
if (!isMultiple) {
|
|
4211
|
+
setValue(colLabel, details.value);
|
|
4212
|
+
}
|
|
4213
|
+
}, children: jsx(HStack, { gap: "6", children: dataList.map((item) => {
|
|
4214
|
+
return (jsxs(RadioGroup$1.Item, { value: item, children: [jsx(RadioGroup$1.ItemHiddenInput, {}), jsx(RadioGroup$1.ItemIndicator, {}), jsx(RadioGroup$1.ItemText, { children: !!renderDisplay === true
|
|
4202
4215
|
? renderDisplay(item)
|
|
4203
4216
|
: formI18n.t(item) })] }, `${colLabel}-${item}`));
|
|
4204
4217
|
}) }) }) }));
|
|
4205
4218
|
}
|
|
4206
4219
|
return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
4207
|
-
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [isMultiple && (
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
}, justifyContent: 'start', children: !!watchEnum === false ? '' : formI18n.t(watchEnum ?? 'null') })), jsxs(PopoverRoot, { open: openSearchResult, onOpenChange: (e) => setOpenSearchResult(e.open), closeOnInteractOutside: true, initialFocusEl: () => ref.current, positioning: { placement: 'bottom-start' }, children: [jsx(PopoverTrigger, {}), jsx(PopoverContent, { portalled: false, children: jsxs(PopoverBody, { display: 'grid', gap: 1, children: [jsx(Input, { placeholder: enumPickerLabels?.typeToSearch ?? formI18n.t('type_to_search'), onChange: (event) => {
|
|
4222
|
-
onSearchChange(event);
|
|
4223
|
-
setOpenSearchResult(true);
|
|
4224
|
-
}, autoComplete: "off", ref: ref }), jsx(PopoverTitle, {}), showTotalAndLimit && (jsx(Text, { children: `${enumPickerLabels?.total ?? formI18n.t('total')}: ${count}, ${enumPickerLabels?.showing ?? formI18n.t('showing')} ${limit}` })), jsxs(Grid, { overflow: 'auto', maxHeight: '20rem', children: [jsx(Flex, { flexFlow: 'column wrap', children: dataList
|
|
4225
|
-
.filter((item) => {
|
|
4226
|
-
const searchTerm = (searchText || '').toLowerCase();
|
|
4227
|
-
if (!searchTerm)
|
|
4228
|
-
return true;
|
|
4229
|
-
// Check if the original enum value contains the search text
|
|
4230
|
-
const enumValueMatch = item
|
|
4231
|
-
.toLowerCase()
|
|
4232
|
-
.includes(searchTerm);
|
|
4233
|
-
// Check if the display value (translation) contains the search text
|
|
4234
|
-
const displayValue = !!renderDisplay === true
|
|
4235
|
-
? renderDisplay(item)
|
|
4236
|
-
: formI18n.t(item);
|
|
4237
|
-
// Convert to string and check if it includes the search term
|
|
4238
|
-
const displayValueString = String(displayValue).toLowerCase();
|
|
4239
|
-
const displayValueMatch = displayValueString.includes(searchTerm);
|
|
4240
|
-
return enumValueMatch || displayValueMatch;
|
|
4241
|
-
})
|
|
4242
|
-
.map((item) => {
|
|
4243
|
-
const selected = isMultiple
|
|
4244
|
-
? watchEnums.some((enumValue) => item === enumValue)
|
|
4245
|
-
: watchEnum == item;
|
|
4246
|
-
return (jsx(Box, { cursor: 'pointer', onClick: () => {
|
|
4247
|
-
if (!isMultiple) {
|
|
4248
|
-
setOpenSearchResult(false);
|
|
4249
|
-
setValue(colLabel, item);
|
|
4250
|
-
return;
|
|
4251
|
-
}
|
|
4252
|
-
const newSet = new Set([...(watchEnums ?? []), item]);
|
|
4253
|
-
setValue(colLabel, [...newSet]);
|
|
4254
|
-
}, ...(selected ? { color: 'colorPalette.400/50' } : {}), children: !!renderDisplay === true
|
|
4255
|
-
? renderDisplay(item)
|
|
4256
|
-
: formI18n.t(item) }, `${colLabel}-${item}`));
|
|
4257
|
-
}) }), isDirty && (jsx(Fragment, { children: dataList.length <= 0 && (jsx(Fragment, { children: enumPickerLabels?.emptySearchResult ??
|
|
4258
|
-
formI18n.t('empty_search_result') })) }))] })] }) })] })] }));
|
|
4220
|
+
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [isMultiple && currentValue.length > 0 && (jsx(Flex, { flexFlow: 'wrap', gap: 1, mb: 2, children: currentValue.map((enumValue) => {
|
|
4221
|
+
if (!enumValue) {
|
|
4222
|
+
return null;
|
|
4223
|
+
}
|
|
4224
|
+
return (jsx(Tag, { size: "lg", closable: true, onClick: () => {
|
|
4225
|
+
const newValue = currentValue.filter((val) => val !== enumValue);
|
|
4226
|
+
setValue(colLabel, newValue);
|
|
4227
|
+
}, children: !!renderDisplay === true
|
|
4228
|
+
? renderDisplay(enumValue)
|
|
4229
|
+
: formI18n.t(enumValue) }, enumValue));
|
|
4230
|
+
}) })), jsxs(Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: isMultiple, closeOnSelect: !isMultiple, openOnClick: true, invalid: !!errors[colLabel], width: "100%", children: [jsxs(Combobox.Control, { children: [jsx(Combobox.Input, { placeholder: enumPickerLabels?.typeToSearch ?? formI18n.t('type_to_search') }), jsxs(Combobox.IndicatorGroup, { children: [!isMultiple && currentValue.length > 0 && (jsx(Combobox.ClearTrigger, { onClick: () => {
|
|
4231
|
+
setValue(colLabel, '');
|
|
4232
|
+
} })), jsx(Combobox.Trigger, {})] })] }), jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsxs(Combobox.Content, { children: [showTotalAndLimit && (jsx(Text, { p: 2, fontSize: "sm", color: "fg.muted", children: `${enumPickerLabels?.total ?? formI18n.t('total')}: ${collection.items.length}` })), collection.items.length === 0 ? (jsx(Combobox.Empty, { children: enumPickerLabels?.emptySearchResult ??
|
|
4233
|
+
formI18n.t('empty_search_result') })) : (jsx(Fragment, { children: collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value))) }))] }) }) })] })] }));
|
|
4259
4234
|
};
|
|
4260
4235
|
|
|
4261
4236
|
function isEnteringWindow(_ref) {
|
|
@@ -4624,13 +4599,13 @@ function formatBytes(bytes) {
|
|
|
4624
4599
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
4625
4600
|
}
|
|
4626
4601
|
|
|
4627
|
-
const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, enabled = true, multiple = false, onFileSelect,
|
|
4602
|
+
const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, enabled = true, multiple = false, onFileSelect, selectedFile: controlledSelectedFile, onSelectedFileChange, }) => {
|
|
4628
4603
|
const [searchTerm, setSearchTerm] = useState('');
|
|
4629
|
-
const [
|
|
4604
|
+
const [internalSelectedFile, setInternalSelectedFile] = useState(multiple ? [] : undefined);
|
|
4630
4605
|
const [failedImageIds, setFailedImageIds] = useState(new Set());
|
|
4631
|
-
// Use controlled or internal state for
|
|
4632
|
-
const
|
|
4633
|
-
const
|
|
4606
|
+
// Use controlled or internal state for selectedFile
|
|
4607
|
+
const selectedFile = controlledSelectedFile ?? internalSelectedFile;
|
|
4608
|
+
const setSelectedFile = onSelectedFileChange ?? setInternalSelectedFile;
|
|
4634
4609
|
const { data: filesData, isLoading, isError, } = useQuery({
|
|
4635
4610
|
queryKey: ['file-picker-library', searchTerm],
|
|
4636
4611
|
queryFn: async () => {
|
|
@@ -4645,23 +4620,23 @@ const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, en
|
|
|
4645
4620
|
const filteredFiles = filterImageOnly
|
|
4646
4621
|
? files.filter((file) => /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name))
|
|
4647
4622
|
: files;
|
|
4648
|
-
const handleFileClick = (
|
|
4623
|
+
const handleFileClick = (file) => {
|
|
4649
4624
|
if (multiple) {
|
|
4650
|
-
const currentSelection = Array.isArray(
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
setSelectedFileId(newSelection);
|
|
4625
|
+
const currentSelection = Array.isArray(selectedFile) ? selectedFile : [];
|
|
4626
|
+
const isAlreadySelected = currentSelection.some((f) => f.id === file.id);
|
|
4627
|
+
const newSelection = isAlreadySelected
|
|
4628
|
+
? currentSelection.filter((f) => f.id !== file.id)
|
|
4629
|
+
: [...currentSelection, file];
|
|
4630
|
+
setSelectedFile(newSelection);
|
|
4657
4631
|
if (onFileSelect) {
|
|
4658
4632
|
onFileSelect(newSelection);
|
|
4659
4633
|
}
|
|
4660
4634
|
}
|
|
4661
4635
|
else {
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4636
|
+
const newFile = selectedFile === file ? undefined : file;
|
|
4637
|
+
setSelectedFile(newFile);
|
|
4638
|
+
if (onFileSelect && newFile) {
|
|
4639
|
+
onFileSelect(newFile);
|
|
4665
4640
|
}
|
|
4666
4641
|
}
|
|
4667
4642
|
};
|
|
@@ -4688,9 +4663,10 @@ const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, en
|
|
|
4688
4663
|
}, children: labels?.loadingFailed ?? 'Failed to load files' }) })), !isLoading && !isError && (jsx(Box, { maxHeight: "400px", overflowY: "auto", children: filteredFiles.length === 0 ? (jsx(Box, { textAlign: "center", py: 8, children: jsx(Text, { color: "fg.muted", children: labels?.noFilesFound ?? 'No files found' }) })) : (jsx(VStack, { align: "stretch", gap: 2, children: filteredFiles.map((file) => {
|
|
4689
4664
|
const isImage = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name);
|
|
4690
4665
|
const isSelected = multiple
|
|
4691
|
-
? Array.isArray(
|
|
4692
|
-
|
|
4693
|
-
:
|
|
4666
|
+
? Array.isArray(selectedFile) &&
|
|
4667
|
+
selectedFile.some((f) => f.id === file.id)
|
|
4668
|
+
: selectedFile?.id ===
|
|
4669
|
+
file.id;
|
|
4694
4670
|
const imageFailed = failedImageIds.has(file.id);
|
|
4695
4671
|
return (jsx(Box, { p: 3, border: "2px solid", borderColor: isSelected
|
|
4696
4672
|
? {
|
|
@@ -4702,7 +4678,7 @@ const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, en
|
|
|
4702
4678
|
base: 'colorPalette.50',
|
|
4703
4679
|
_dark: 'colorPalette.900/20',
|
|
4704
4680
|
}
|
|
4705
|
-
: 'bg.panel', colorPalette: "blue", cursor: "pointer", onClick: () => handleFileClick(file
|
|
4681
|
+
: 'bg.panel', colorPalette: "blue", cursor: "pointer", onClick: () => handleFileClick(file), _hover: {
|
|
4706
4682
|
borderColor: isSelected
|
|
4707
4683
|
? {
|
|
4708
4684
|
base: 'colorPalette.600',
|
|
@@ -4727,22 +4703,22 @@ const MediaLibraryBrowser = ({ onFetchFiles, filterImageOnly = false, labels, en
|
|
|
4727
4703
|
}) })) }))] }));
|
|
4728
4704
|
};
|
|
4729
4705
|
|
|
4730
|
-
function
|
|
4731
|
-
const [
|
|
4706
|
+
function MediaBrowserDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, onUploadFile, enableUpload = false, labels, translate, colLabel, }) {
|
|
4707
|
+
const [selectedFile, setSelectedFile] = useState(undefined);
|
|
4732
4708
|
const [activeTab, setActiveTab] = useState('browse');
|
|
4733
4709
|
const [uploadingFiles, setUploadingFiles] = useState(new Set());
|
|
4734
4710
|
const [uploadErrors, setUploadErrors] = useState(new Map());
|
|
4735
4711
|
const handleSelect = () => {
|
|
4736
|
-
if (
|
|
4737
|
-
onSelect(
|
|
4712
|
+
if (selectedFile) {
|
|
4713
|
+
onSelect(selectedFile);
|
|
4738
4714
|
onClose();
|
|
4739
|
-
|
|
4715
|
+
setSelectedFile(undefined);
|
|
4740
4716
|
setActiveTab('browse');
|
|
4741
4717
|
}
|
|
4742
4718
|
};
|
|
4743
4719
|
const handleClose = () => {
|
|
4744
4720
|
onClose();
|
|
4745
|
-
|
|
4721
|
+
setSelectedFile(undefined);
|
|
4746
4722
|
setActiveTab('browse');
|
|
4747
4723
|
setUploadingFiles(new Set());
|
|
4748
4724
|
setUploadErrors(new Map());
|
|
@@ -4760,16 +4736,23 @@ function FilePickerDialog({ open, onClose, onSelect, title, filterImageOnly = fa
|
|
|
4760
4736
|
});
|
|
4761
4737
|
try {
|
|
4762
4738
|
const fileId = await onUploadFile(file);
|
|
4763
|
-
|
|
4739
|
+
// Create a minimal FilePickerMediaFile object from the uploaded file
|
|
4740
|
+
const uploadedFile = {
|
|
4741
|
+
id: fileId,
|
|
4742
|
+
name: file.name,
|
|
4743
|
+
size: file.size,
|
|
4744
|
+
type: file.type,
|
|
4745
|
+
};
|
|
4746
|
+
setSelectedFile(uploadedFile);
|
|
4764
4747
|
setUploadingFiles((prev) => {
|
|
4765
4748
|
const newSet = new Set(prev);
|
|
4766
4749
|
newSet.delete(fileKey);
|
|
4767
4750
|
return newSet;
|
|
4768
4751
|
});
|
|
4769
4752
|
// Auto-select and close in single-select mode
|
|
4770
|
-
onSelect(
|
|
4753
|
+
onSelect(uploadedFile);
|
|
4771
4754
|
onClose();
|
|
4772
|
-
|
|
4755
|
+
setSelectedFile(undefined);
|
|
4773
4756
|
setActiveTab('browse');
|
|
4774
4757
|
}
|
|
4775
4758
|
catch (error) {
|
|
@@ -4793,7 +4776,7 @@ function FilePickerDialog({ open, onClose, onSelect, title, filterImageOnly = fa
|
|
|
4793
4776
|
translate(removeIndex(`${colLabel}.browse_tab`)) ??
|
|
4794
4777
|
'Browse Library' }), jsx(Tabs.Trigger, { value: "upload", children: labels?.uploadTab ??
|
|
4795
4778
|
translate(removeIndex(`${colLabel}.upload_tab`)) ??
|
|
4796
|
-
'Upload Files' })] }), jsx(Tabs.Content, { value: "browse", children: onFetchFiles && (jsx(MediaLibraryBrowser, { onFetchFiles: onFetchFiles, filterImageOnly: filterImageOnly, labels: labels, enabled: open && activeTab === 'browse',
|
|
4779
|
+
'Upload Files' })] }), jsx(Tabs.Content, { value: "browse", children: onFetchFiles && (jsx(MediaLibraryBrowser, { onFetchFiles: onFetchFiles, filterImageOnly: filterImageOnly, labels: labels, enabled: open && activeTab === 'browse', selectedFile: selectedFile, onFileSelect: setSelectedFile })) }), jsx(Tabs.Content, { value: "upload", children: jsxs(VStack, { align: "stretch", gap: 4, children: [jsx(FileDropzone, { onDrop: ({ files }) => handleFileUpload(files), placeholder: labels?.fileDropzone ??
|
|
4797
4780
|
translate(removeIndex(`${colLabel}.fileDropzone`)) ??
|
|
4798
4781
|
'Drop files here or click to upload' }), uploadingFiles.size > 0 && (jsx(Box, { children: Array.from(uploadingFiles).map((fileKey) => (jsx(Box, { py: 2, children: jsxs(HStack, { gap: 2, children: [jsx(Spinner, { size: "sm", colorPalette: "blue" }), jsxs(Text, { fontSize: "sm", color: "fg.muted", children: [labels?.uploading ??
|
|
4799
4782
|
translate(removeIndex(`${colLabel}.uploading`)) ??
|
|
@@ -4808,9 +4791,9 @@ function FilePickerDialog({ open, onClose, onSelect, title, filterImageOnly = fa
|
|
|
4808
4791
|
_dark: 'colorPalette.300',
|
|
4809
4792
|
}, children: [fileKey.split('-')[0], ":", ' ', labels?.uploadFailed ??
|
|
4810
4793
|
translate(removeIndex(`${colLabel}.upload_failed`)) ??
|
|
4811
|
-
'Upload failed', error && ` - ${error}`] }) }, fileKey))) }))] }) })] })) : onFetchFiles ? (jsx(MediaLibraryBrowser, { onFetchFiles: onFetchFiles, filterImageOnly: filterImageOnly, labels: labels, enabled: open,
|
|
4794
|
+
'Upload failed', error && ` - ${error}`] }) }, fileKey))) }))] }) })] })) : onFetchFiles ? (jsx(MediaLibraryBrowser, { onFetchFiles: onFetchFiles, filterImageOnly: filterImageOnly, labels: labels, enabled: open, selectedFile: selectedFile, onFileSelect: setSelectedFile })) : null }), jsx(DialogFooter, { children: jsxs(HStack, { gap: 3, justify: "end", children: [jsx(Button$1, { variant: "outline", onClick: handleClose, borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: labels?.cancel ??
|
|
4812
4795
|
translate(removeIndex(`${colLabel}.cancel`)) ??
|
|
4813
|
-
'Cancel' }), jsx(Button$1, { colorPalette: "blue", onClick: handleSelect, disabled: !
|
|
4796
|
+
'Cancel' }), jsx(Button$1, { colorPalette: "blue", onClick: handleSelect, disabled: !selectedFile, children: labels?.select ??
|
|
4814
4797
|
translate(removeIndex(`${colLabel}.select`)) ??
|
|
4815
4798
|
'Select' })] }) })] }) }));
|
|
4816
4799
|
}
|
|
@@ -4909,17 +4892,70 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
|
|
|
4909
4892
|
const colLabel = formI18n.colLabel;
|
|
4910
4893
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
4911
4894
|
const [failedImageIds, setFailedImageIds] = useState(new Set());
|
|
4895
|
+
// Map of file ID to FilePickerMediaFile for display
|
|
4896
|
+
const [fileMap, setFileMap] = useState(new Map());
|
|
4912
4897
|
const { onFetchFiles, filterImageOnly = false, enableUpload = false, onUploadFile, } = filePicker || {};
|
|
4898
|
+
// Fetch file details for existing file IDs
|
|
4899
|
+
useEffect(() => {
|
|
4900
|
+
if (!onFetchFiles || currentFileIds.length === 0)
|
|
4901
|
+
return;
|
|
4902
|
+
const fetchFileDetails = async () => {
|
|
4903
|
+
setFileMap((prevMap) => {
|
|
4904
|
+
const filesToFetch = currentFileIds.filter((id) => !prevMap.has(id));
|
|
4905
|
+
if (filesToFetch.length === 0)
|
|
4906
|
+
return prevMap;
|
|
4907
|
+
// Fetch all files and filter for the ones we need
|
|
4908
|
+
onFetchFiles('')
|
|
4909
|
+
.then((allFiles) => {
|
|
4910
|
+
setFileMap((currentMap) => {
|
|
4911
|
+
const newFileMap = new Map(currentMap);
|
|
4912
|
+
filesToFetch.forEach((id) => {
|
|
4913
|
+
const file = allFiles.find((f) => f.id === id);
|
|
4914
|
+
if (file) {
|
|
4915
|
+
newFileMap.set(id, file);
|
|
4916
|
+
}
|
|
4917
|
+
});
|
|
4918
|
+
return newFileMap;
|
|
4919
|
+
});
|
|
4920
|
+
})
|
|
4921
|
+
.catch((error) => {
|
|
4922
|
+
console.error('Failed to fetch file details:', error);
|
|
4923
|
+
});
|
|
4924
|
+
return prevMap;
|
|
4925
|
+
});
|
|
4926
|
+
};
|
|
4927
|
+
fetchFileDetails();
|
|
4928
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4929
|
+
}, [currentFileIds.join(','), onFetchFiles]);
|
|
4930
|
+
// Clean up fileMap when files are removed
|
|
4931
|
+
useEffect(() => {
|
|
4932
|
+
setFileMap((prevMap) => {
|
|
4933
|
+
const currentIds = new Set(currentFileIds);
|
|
4934
|
+
const newFileMap = new Map();
|
|
4935
|
+
prevMap.forEach((file, id) => {
|
|
4936
|
+
if (currentIds.has(id)) {
|
|
4937
|
+
newFileMap.set(id, file);
|
|
4938
|
+
}
|
|
4939
|
+
});
|
|
4940
|
+
return newFileMap.size !== prevMap.size ? newFileMap : prevMap;
|
|
4941
|
+
});
|
|
4942
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4943
|
+
}, [currentFileIds.join(',')]);
|
|
4913
4944
|
if (!onFetchFiles) {
|
|
4914
4945
|
return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
4915
4946
|
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsx(Text, { color: "fg.muted", children: "Media library browser requires onFetchFiles" }) }));
|
|
4916
4947
|
}
|
|
4917
|
-
const
|
|
4948
|
+
const handleImageError = (fileIdentifier) => {
|
|
4949
|
+
setFailedImageIds((prev) => new Set(prev).add(fileIdentifier));
|
|
4950
|
+
};
|
|
4951
|
+
const handleMediaLibrarySelect = (file) => {
|
|
4952
|
+
// Store the file in the map for display
|
|
4953
|
+
setFileMap((prev) => new Map(prev).set(file.id, file));
|
|
4918
4954
|
if (isSingleSelect) {
|
|
4919
|
-
setValue(colLabel,
|
|
4955
|
+
setValue(colLabel, file.id);
|
|
4920
4956
|
}
|
|
4921
4957
|
else {
|
|
4922
|
-
const newFileIds = [...currentFileIds,
|
|
4958
|
+
const newFileIds = [...currentFileIds, file.id];
|
|
4923
4959
|
setValue(colLabel, newFileIds);
|
|
4924
4960
|
}
|
|
4925
4961
|
};
|
|
@@ -4932,33 +4968,27 @@ const FormMediaLibraryBrowser = ({ column, schema, prefix, }) => {
|
|
|
4932
4968
|
setValue(colLabel, newFileIds);
|
|
4933
4969
|
}
|
|
4934
4970
|
};
|
|
4935
|
-
const isImageId = (fileId) => {
|
|
4936
|
-
return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(fileId);
|
|
4937
|
-
};
|
|
4938
4971
|
return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
4939
4972
|
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsx(VStack, { align: "stretch", gap: 2, children: jsx(Button$1, { variant: "outline", onClick: () => setDialogOpen(true), borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: filePickerLabels?.browseLibrary ??
|
|
4940
4973
|
formI18n.t('browse_library') ??
|
|
4941
|
-
'Browse from Library' }) }), jsx(
|
|
4974
|
+
'Browse from Library' }) }), jsx(MediaBrowserDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ??
|
|
4942
4975
|
formI18n.t('dialog_title') ??
|
|
4943
4976
|
'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, onUploadFile: onUploadFile, enableUpload: enableUpload, labels: filePickerLabels, translate: formI18n.t, colLabel: colLabel }), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFileIds.map((fileId, index) => {
|
|
4944
|
-
const
|
|
4977
|
+
const file = fileMap.get(fileId);
|
|
4978
|
+
const isImage = file
|
|
4979
|
+
? /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name)
|
|
4980
|
+
: /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(fileId);
|
|
4945
4981
|
const imageFailed = failedImageIds.has(fileId);
|
|
4982
|
+
const displayName = file?.name ?? fileId;
|
|
4946
4983
|
return (jsx(Card.Root, { variant: 'subtle', colorPalette: "blue", children: jsxs(Card.Body, { gap: "2", cursor: 'pointer', onClick: () => handleRemove(index), display: 'flex', flexFlow: 'row', alignItems: 'center', padding: '2', border: "2px solid", borderColor: "border.default", borderRadius: "md", _hover: {
|
|
4947
4984
|
borderColor: 'colorPalette.300',
|
|
4948
4985
|
bg: 'bg.muted',
|
|
4949
|
-
}, transition: "all 0.2s", children: [jsx(Box, { width: "60px", height: "60px", display: "flex", alignItems: "center", justifyContent: "center", bg: "bg.muted", borderRadius: "md", flexShrink: 0, marginRight: "2", children: isImage && !imageFailed ? (jsx(Icon, { as: LuImage, boxSize: 6, color: "fg.muted" })) : (jsx(Icon, { as: LuFile, boxSize: 6, color: "fg.muted" })) }),
|
|
4986
|
+
}, transition: "all 0.2s", children: [jsx(Box, { width: "60px", height: "60px", display: "flex", alignItems: "center", justifyContent: "center", bg: "bg.muted", borderRadius: "md", flexShrink: 0, marginRight: "2", overflow: "hidden", children: isImage && file?.url && !imageFailed ? (jsx(Image, { src: file.url, alt: displayName, boxSize: "60px", objectFit: "cover", onError: () => handleImageError(fileId) })) : isImage && !imageFailed ? (jsx(Icon, { as: LuImage, boxSize: 6, color: "fg.muted" })) : (jsx(Icon, { as: LuFile, boxSize: 6, color: "fg.muted" })) }), jsxs(VStack, { align: "start", flex: 1, gap: 1, children: [jsx(Text, { fontSize: "sm", fontWeight: "medium", color: "fg.default", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: displayName }), file?.size && (jsx(Text, { fontSize: "xs", color: "fg.muted", children: typeof file.size === 'number'
|
|
4987
|
+
? `${(file.size / 1024).toFixed(1)} KB`
|
|
4988
|
+
: file.size }))] }), jsx(Icon, { as: TiDeleteOutline, boxSize: 5, color: "fg.muted" })] }) }, `${fileId}-${index}`));
|
|
4950
4989
|
}) })] }));
|
|
4951
4990
|
};
|
|
4952
4991
|
|
|
4953
|
-
const ToggleTip = React.forwardRef(function ToggleTip(props, ref) {
|
|
4954
|
-
const { showArrow, children, portalled = true, content, portalRef, ...rest } = props;
|
|
4955
|
-
return (jsxs(Popover.Root, { ...rest, positioning: { ...rest.positioning, gutter: 4 }, children: [jsx(Popover.Trigger, { asChild: true, children: children }), jsx(Portal, { disabled: !portalled, container: portalRef, children: jsx(Popover.Positioner, { children: jsxs(Popover.Content, { width: "auto", px: "2", py: "1", textStyle: "xs", rounded: "sm", ref: ref, children: [showArrow && (jsx(Popover.Arrow, { children: jsx(Popover.ArrowTip, {}) })), content] }) }) })] }));
|
|
4956
|
-
});
|
|
4957
|
-
const InfoTip = React.forwardRef(function InfoTip(props, ref) {
|
|
4958
|
-
const { children, ...rest } = props;
|
|
4959
|
-
return (jsx(ToggleTip, { content: children, ...rest, ref: ref, children: jsx(IconButton, { variant: "ghost", "aria-label": "info", size: "2xs", colorPalette: "colorPalette", children: jsx(HiOutlineInformationCircle, {}) }) }));
|
|
4960
|
-
});
|
|
4961
|
-
|
|
4962
4992
|
const getTableData = async ({ serverUrl, in_table, searching = "", where = [], limit = 10, offset = 0, }) => {
|
|
4963
4993
|
if (serverUrl === undefined || serverUrl.length == 0) {
|
|
4964
4994
|
throw new Error("The serverUrl is missing");
|
|
@@ -4995,10 +5025,7 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
|
|
|
4995
5025
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
4996
5026
|
const { table, column: column_ref, display_column, customQueryFn, } = foreign_key;
|
|
4997
5027
|
const [searchText, setSearchText] = useState('');
|
|
4998
|
-
const [limit
|
|
4999
|
-
const [openSearchResult, setOpenSearchResult] = useState();
|
|
5000
|
-
const [page, setPage] = useState(0);
|
|
5001
|
-
const ref = useRef(null);
|
|
5028
|
+
const [limit] = useState(50); // Increased limit for combobox
|
|
5002
5029
|
const colLabel = formI18n.colLabel;
|
|
5003
5030
|
const watchedValue = watch(colLabel);
|
|
5004
5031
|
const watchId = !isMultiple ? watchedValue : undefined;
|
|
@@ -5013,22 +5040,25 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
|
|
|
5013
5040
|
: [];
|
|
5014
5041
|
// Use watched values if they exist (including empty string for single select),
|
|
5015
5042
|
// otherwise fall back to initial values from getValues()
|
|
5016
|
-
// This ensures the query can trigger on mount with initial values
|
|
5017
|
-
// For single: use watchId if it's not undefined/null, otherwise use initialId
|
|
5018
|
-
// For multiple: use watchIds if watchedValue is defined, otherwise use initialIds
|
|
5019
5043
|
const currentId = watchId !== undefined && watchId !== null ? watchId : initialId;
|
|
5020
5044
|
const currentIds = watchedValue !== undefined && watchedValue !== null && isMultiple
|
|
5021
5045
|
? watchIds
|
|
5022
5046
|
: initialIds;
|
|
5023
|
-
//
|
|
5047
|
+
// Current value for combobox (array format)
|
|
5048
|
+
const currentValue = isMultiple
|
|
5049
|
+
? currentIds.filter((id) => id != null && id !== '')
|
|
5050
|
+
: currentId
|
|
5051
|
+
? [currentId]
|
|
5052
|
+
: [];
|
|
5053
|
+
// Query for search results (async loading)
|
|
5024
5054
|
const query = useQuery({
|
|
5025
|
-
queryKey: [`idpicker`, { column, searchText, limit
|
|
5055
|
+
queryKey: [`idpicker`, { column, searchText, limit }],
|
|
5026
5056
|
queryFn: async () => {
|
|
5027
5057
|
if (customQueryFn) {
|
|
5028
5058
|
const { data, idMap } = await customQueryFn({
|
|
5029
5059
|
searching: searchText ?? '',
|
|
5030
5060
|
limit: limit,
|
|
5031
|
-
offset:
|
|
5061
|
+
offset: 0,
|
|
5032
5062
|
});
|
|
5033
5063
|
setIdMap((state) => {
|
|
5034
5064
|
return { ...state, ...idMap };
|
|
@@ -5040,7 +5070,7 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
|
|
|
5040
5070
|
searching: searchText ?? '',
|
|
5041
5071
|
in_table: table,
|
|
5042
5072
|
limit: limit,
|
|
5043
|
-
offset:
|
|
5073
|
+
offset: 0,
|
|
5044
5074
|
});
|
|
5045
5075
|
const newMap = Object.fromEntries((data ?? { data: [] }).data.map((item) => {
|
|
5046
5076
|
return [
|
|
@@ -5055,12 +5085,11 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
|
|
|
5055
5085
|
});
|
|
5056
5086
|
return data;
|
|
5057
5087
|
},
|
|
5058
|
-
enabled:
|
|
5088
|
+
enabled: true, // Always enabled for combobox
|
|
5059
5089
|
staleTime: 300000,
|
|
5060
5090
|
});
|
|
5061
5091
|
// Query for currently selected items (to display them properly)
|
|
5062
|
-
|
|
5063
|
-
const queryDefault = useQuery({
|
|
5092
|
+
useQuery({
|
|
5064
5093
|
queryKey: [
|
|
5065
5094
|
`idpicker-default`,
|
|
5066
5095
|
{
|
|
@@ -5070,11 +5099,9 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
|
|
|
5070
5099
|
},
|
|
5071
5100
|
],
|
|
5072
5101
|
queryFn: async () => {
|
|
5073
|
-
// Use current values (which include initial) for the query
|
|
5074
5102
|
const queryId = currentId;
|
|
5075
5103
|
const queryIds = currentIds;
|
|
5076
5104
|
if (customQueryFn) {
|
|
5077
|
-
// For customQueryFn, pass where clause to fetch specific IDs
|
|
5078
5105
|
const { data, idMap } = await customQueryFn({
|
|
5079
5106
|
searching: '',
|
|
5080
5107
|
limit: isMultiple ? queryIds.length : 1,
|
|
@@ -5089,10 +5116,9 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
|
|
|
5089
5116
|
if (!queryId && (!queryIds || queryIds.length === 0)) {
|
|
5090
5117
|
return { data: [] };
|
|
5091
5118
|
}
|
|
5092
|
-
const searchValue = isMultiple ? queryIds.join(',') : queryId;
|
|
5093
5119
|
const data = await getTableData({
|
|
5094
5120
|
serverUrl,
|
|
5095
|
-
searching:
|
|
5121
|
+
searching: '',
|
|
5096
5122
|
in_table: table,
|
|
5097
5123
|
where: [{ id: column_ref, value: isMultiple ? queryIds : queryId }],
|
|
5098
5124
|
limit: isMultiple ? queryIds.length : 1,
|
|
@@ -5115,101 +5141,80 @@ const IdPicker = ({ column, schema, prefix, isMultiple = false, }) => {
|
|
|
5115
5141
|
? Array.isArray(currentIds) && currentIds.length > 0
|
|
5116
5142
|
: !!currentId,
|
|
5117
5143
|
});
|
|
5118
|
-
// Effect to trigger initial data fetch when popover opens
|
|
5119
|
-
useEffect(() => {
|
|
5120
|
-
if (openSearchResult) {
|
|
5121
|
-
// Reset search text when opening the popover
|
|
5122
|
-
setSearchText('');
|
|
5123
|
-
// Reset page to first page
|
|
5124
|
-
setPage(0);
|
|
5125
|
-
// Fetch initial data
|
|
5126
|
-
query.refetch();
|
|
5127
|
-
}
|
|
5128
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5129
|
-
}, [openSearchResult]);
|
|
5130
|
-
const onSearchChange = async (event) => {
|
|
5131
|
-
setSearchText(event.target.value);
|
|
5132
|
-
setPage(0);
|
|
5133
|
-
query.refetch();
|
|
5134
|
-
};
|
|
5135
|
-
const handleLimitChange = (event) => {
|
|
5136
|
-
const newLimit = Number(event.target.value);
|
|
5137
|
-
setLimit(newLimit);
|
|
5138
|
-
// Reset to first page when changing limit
|
|
5139
|
-
setPage(0);
|
|
5140
|
-
// Trigger a new search with the updated limit
|
|
5141
|
-
query.refetch();
|
|
5142
|
-
};
|
|
5143
5144
|
const { isLoading, isFetching, data, isPending, isError } = query;
|
|
5144
5145
|
const dataList = data?.data ?? [];
|
|
5145
|
-
|
|
5146
|
-
const
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5146
|
+
// Transform data for combobox collection
|
|
5147
|
+
const comboboxItems = useMemo(() => {
|
|
5148
|
+
return dataList.map((item) => ({
|
|
5149
|
+
label: !!renderDisplay === true
|
|
5150
|
+
? String(renderDisplay(item))
|
|
5151
|
+
: String(item[display_column] ?? ''),
|
|
5152
|
+
value: String(item[column_ref]),
|
|
5153
|
+
raw: item,
|
|
5154
|
+
}));
|
|
5155
|
+
}, [dataList, display_column, column_ref, renderDisplay]);
|
|
5156
|
+
// Use filter hook for combobox
|
|
5157
|
+
const { contains } = useFilter({ sensitivity: 'base' });
|
|
5158
|
+
// Create collection for combobox
|
|
5159
|
+
const { collection, filter, set } = useListCollection({
|
|
5160
|
+
initialItems: comboboxItems,
|
|
5161
|
+
itemToString: (item) => item.label,
|
|
5162
|
+
itemToValue: (item) => item.value,
|
|
5163
|
+
filter: contains,
|
|
5164
|
+
});
|
|
5165
|
+
// Handle input value change (search)
|
|
5166
|
+
const handleInputValueChange = (details) => {
|
|
5167
|
+
setSearchText(details.inputValue);
|
|
5168
|
+
// Filter will be applied after data is fetched
|
|
5169
|
+
};
|
|
5170
|
+
// Handle value change
|
|
5171
|
+
const handleValueChange = (details) => {
|
|
5172
|
+
if (isMultiple) {
|
|
5173
|
+
setValue(colLabel, details.value);
|
|
5154
5174
|
}
|
|
5155
|
-
|
|
5156
|
-
|
|
5175
|
+
else {
|
|
5176
|
+
setValue(colLabel, details.value[0] || '');
|
|
5157
5177
|
}
|
|
5158
|
-
return record[display_column];
|
|
5159
5178
|
};
|
|
5179
|
+
// Debounce search to avoid too many API calls and update collection after data loads
|
|
5180
|
+
useEffect(() => {
|
|
5181
|
+
const timer = setTimeout(() => {
|
|
5182
|
+
if (searchText !== undefined) {
|
|
5183
|
+
query.refetch();
|
|
5184
|
+
}
|
|
5185
|
+
}, 300);
|
|
5186
|
+
return () => clearTimeout(timer);
|
|
5187
|
+
}, [searchText, query]);
|
|
5188
|
+
// Update collection and filter when data changes
|
|
5189
|
+
useEffect(() => {
|
|
5190
|
+
if (dataList.length > 0) {
|
|
5191
|
+
set(comboboxItems);
|
|
5192
|
+
// Apply filter to the collection
|
|
5193
|
+
if (searchText) {
|
|
5194
|
+
filter(searchText);
|
|
5195
|
+
}
|
|
5196
|
+
}
|
|
5197
|
+
}, [dataList, comboboxItems, set, filter, searchText]);
|
|
5160
5198
|
return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
5161
|
-
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [isMultiple && (
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
border: '1px solid #ccc',
|
|
5181
|
-
fontSize: '14px',
|
|
5182
|
-
}, children: [jsx("option", { value: "5", children: "5" }), jsx("option", { value: "10", children: "10" }), jsx("option", { value: "20", children: "20" }), jsx("option", { value: "30", children: "30" })] }) })] }), jsx(Grid, { overflowY: 'auto', children: dataList.length > 0 ? (jsx(Flex, { flexFlow: 'column wrap', gap: 1, children: dataList.map((item) => {
|
|
5183
|
-
const selected = isMultiple
|
|
5184
|
-
? watchIds.some((id) => item[column_ref] === id)
|
|
5185
|
-
: watchId === item[column_ref];
|
|
5186
|
-
return (jsx(Box, { cursor: 'pointer', onClick: () => {
|
|
5187
|
-
if (!isMultiple) {
|
|
5188
|
-
setOpenSearchResult(false);
|
|
5189
|
-
setValue(colLabel, item[column_ref]);
|
|
5190
|
-
return;
|
|
5191
|
-
}
|
|
5192
|
-
// For multiple selection, don't add if already selected
|
|
5193
|
-
if (selected)
|
|
5194
|
-
return;
|
|
5195
|
-
const newSet = new Set([
|
|
5196
|
-
...(watchIds ?? []),
|
|
5197
|
-
item[column_ref],
|
|
5198
|
-
]);
|
|
5199
|
-
setValue(colLabel, [...newSet]);
|
|
5200
|
-
}, opacity: 0.7, _hover: { opacity: 1 }, ...(selected
|
|
5201
|
-
? {
|
|
5202
|
-
color: 'colorPalette.400/50',
|
|
5203
|
-
fontWeight: 'bold',
|
|
5204
|
-
}
|
|
5205
|
-
: {}), children: !!renderDisplay === true
|
|
5206
|
-
? renderDisplay(item)
|
|
5207
|
-
: item[display_column] }, item[column_ref]));
|
|
5208
|
-
}) })) : (jsx(Text, { children: searchText
|
|
5209
|
-
? idPickerLabels?.emptySearchResult ??
|
|
5210
|
-
formI18n.t('empty_search_result')
|
|
5211
|
-
: idPickerLabels?.initialResults ??
|
|
5212
|
-
formI18n.t('initial_results') })) }), jsx(PaginationRoot, { justifySelf: 'center', count: count, pageSize: limit, defaultPage: 1, page: page + 1, onPageChange: (e) => setPage(e.page - 1), children: jsxs(HStack, { gap: "4", children: [jsx(PaginationPrevTrigger, {}), count > 0 && jsx(PaginationPageText, {}), jsx(PaginationNextTrigger, {})] }) })] }))] }) })] })] }));
|
|
5199
|
+
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [isMultiple && currentValue.length > 0 && (jsx(Flex, { flexFlow: 'wrap', gap: 1, mb: 2, children: currentValue.map((id) => {
|
|
5200
|
+
const item = idMap[id];
|
|
5201
|
+
if (item === undefined) {
|
|
5202
|
+
return (jsx(Text, { fontSize: "sm", children: idPickerLabels?.undefined ?? formI18n.t('undefined') }, id));
|
|
5203
|
+
}
|
|
5204
|
+
return (jsx(Tag, { closable: true, onClick: () => {
|
|
5205
|
+
const newValue = currentValue.filter((itemId) => itemId !== id);
|
|
5206
|
+
setValue(colLabel, newValue);
|
|
5207
|
+
}, children: !!renderDisplay === true
|
|
5208
|
+
? renderDisplay(item)
|
|
5209
|
+
: item[display_column] }, id));
|
|
5210
|
+
}) })), jsxs(Combobox.Root, { collection: collection, value: currentValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, multiple: isMultiple, closeOnSelect: !isMultiple, openOnClick: true, invalid: !!errors[colLabel], width: "100%", children: [jsxs(Combobox.Control, { children: [jsx(Combobox.Input, { placeholder: idPickerLabels?.typeToSearch ?? formI18n.t('type_to_search') }), jsxs(Combobox.IndicatorGroup, { children: [(isFetching || isLoading || isPending) && (jsx(Spinner, { size: "xs" })), isError && (jsx(Icon, { color: "fg.error", children: jsx(BiError, {}) })), !isMultiple && currentValue.length > 0 && (jsx(Combobox.ClearTrigger, { onClick: () => {
|
|
5211
|
+
setValue(colLabel, '');
|
|
5212
|
+
} })), jsx(Combobox.Trigger, {})] })] }), jsx(Portal, { children: jsx(Combobox.Positioner, { children: jsx(Combobox.Content, { children: isFetching || isLoading || isPending ? (jsxs(HStack, { p: 2, justify: "center", children: [jsx(Spinner, { size: "xs" }), jsx(Text, { fontSize: "sm", children: idPickerLabels?.loading ?? formI18n.t('loading') })] })) : isError ? (jsx(Text, { p: 2, color: "fg.error", fontSize: "sm", children: idPickerLabels?.loadingFailed ??
|
|
5213
|
+
formI18n.t('loading_failed') })) : collection.items.length === 0 ? (jsx(Combobox.Empty, { children: searchText
|
|
5214
|
+
? idPickerLabels?.emptySearchResult ??
|
|
5215
|
+
formI18n.t('empty_search_result')
|
|
5216
|
+
: idPickerLabels?.initialResults ??
|
|
5217
|
+
formI18n.t('initial_results') })) : (jsx(Fragment, { children: collection.items.map((item) => (jsxs(Combobox.Item, { item: item, children: [jsx(Combobox.ItemText, { children: item.label }), jsx(Combobox.ItemIndicator, {})] }, item.value))) })) }) }) })] })] }));
|
|
5213
5218
|
};
|
|
5214
5219
|
|
|
5215
5220
|
const NumberInputRoot = React.forwardRef(function NumberInput$1(props, ref) {
|