@bsol-oss/react-datatable5 12.0.0-beta.75 → 12.0.0-beta.77
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.d.ts +28 -2
- package/dist/index.js +312 -38
- package/dist/index.mjs +313 -39
- package/dist/types/components/Form/SchemaFormContext.d.ts +2 -1
- package/dist/types/components/Form/components/FileDropzone.d.ts +2 -2
- package/dist/types/components/Form/components/core/FormRoot.d.ts +3 -2
- package/dist/types/components/Form/components/fields/DateRangePicker.d.ts +2 -0
- package/dist/types/components/Form/components/fields/SchemaRenderer.d.ts +1 -1
- package/dist/types/components/Form/components/types/CustomJSONSchema7.d.ts +25 -0
- package/dist/types/components/Form/utils/formatBytes.d.ts +6 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -823,6 +823,17 @@ interface EnumPickerLabels {
|
|
|
823
823
|
emptySearchResult?: string;
|
|
824
824
|
initialResults?: string;
|
|
825
825
|
}
|
|
826
|
+
interface FilePickerLabels {
|
|
827
|
+
fileDropzone?: string;
|
|
828
|
+
browseLibrary?: string;
|
|
829
|
+
dialogTitle?: string;
|
|
830
|
+
searchPlaceholder?: string;
|
|
831
|
+
loading?: string;
|
|
832
|
+
loadingFailed?: string;
|
|
833
|
+
noFilesFound?: string;
|
|
834
|
+
cancel?: string;
|
|
835
|
+
select?: string;
|
|
836
|
+
}
|
|
826
837
|
interface CustomJSONSchema7 extends JSONSchema7 {
|
|
827
838
|
gridColumn?: string;
|
|
828
839
|
gridRow?: string;
|
|
@@ -849,12 +860,26 @@ interface CustomJSONSchema7 extends JSONSchema7 {
|
|
|
849
860
|
formatOptions?: Intl.NumberFormatOptions;
|
|
850
861
|
numberStorageType?: 'string' | 'number';
|
|
851
862
|
errorMessages?: Partial<Record<ValidationErrorType | string, string>>;
|
|
863
|
+
filePicker?: FilePickerProps;
|
|
852
864
|
}
|
|
853
865
|
interface TagPickerProps {
|
|
854
866
|
column: string;
|
|
855
867
|
schema: CustomJSONSchema7;
|
|
856
868
|
prefix: string;
|
|
857
869
|
}
|
|
870
|
+
interface FilePickerMediaFile {
|
|
871
|
+
id: string;
|
|
872
|
+
name: string;
|
|
873
|
+
url?: string;
|
|
874
|
+
size?: string | number;
|
|
875
|
+
comment?: string;
|
|
876
|
+
type?: string;
|
|
877
|
+
}
|
|
878
|
+
interface FilePickerProps {
|
|
879
|
+
onFetchFiles?: (search: string) => Promise<FilePickerMediaFile[]>;
|
|
880
|
+
enableMediaLibrary?: boolean;
|
|
881
|
+
filterImageOnly?: boolean;
|
|
882
|
+
}
|
|
858
883
|
|
|
859
884
|
interface FormRootProps<TData extends FieldValues> {
|
|
860
885
|
schema: CustomJSONSchema7;
|
|
@@ -883,6 +908,7 @@ interface FormRootProps<TData extends FieldValues> {
|
|
|
883
908
|
dateTimePickerLabels?: DateTimePickerLabels;
|
|
884
909
|
idPickerLabels?: IdPickerLabels;
|
|
885
910
|
enumPickerLabels?: EnumPickerLabels;
|
|
911
|
+
filePickerLabels?: FilePickerLabels;
|
|
886
912
|
}
|
|
887
913
|
interface CustomJSONSchema7Definition extends JSONSchema7 {
|
|
888
914
|
variant: string;
|
|
@@ -899,7 +925,7 @@ declare const idPickerSanityCheck: (column: string, foreign_key?: {
|
|
|
899
925
|
column?: string | undefined;
|
|
900
926
|
display_column?: string | undefined;
|
|
901
927
|
} | undefined) => void;
|
|
902
|
-
declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, serverUrl, translate, children, order, ignore, include, onSubmit, rowNumber, requestOptions, getUpdatedData, customErrorRenderer, customSuccessRenderer, displayConfig, requireConfirmation, dateTimePickerLabels, idPickerLabels, enumPickerLabels, }: FormRootProps<TData>) => react_jsx_runtime.JSX.Element;
|
|
928
|
+
declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, serverUrl, translate, children, order, ignore, include, onSubmit, rowNumber, requestOptions, getUpdatedData, customErrorRenderer, customSuccessRenderer, displayConfig, requireConfirmation, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, }: FormRootProps<TData>) => react_jsx_runtime.JSX.Element;
|
|
903
929
|
|
|
904
930
|
interface DefaultFormProps<TData extends FieldValues> {
|
|
905
931
|
formConfig: Omit<FormRootProps<TData>, "children">;
|
|
@@ -1071,4 +1097,4 @@ declare module "@tanstack/react-table" {
|
|
|
1071
1097
|
}
|
|
1072
1098
|
}
|
|
1073
1099
|
|
|
1074
|
-
export { type CalendarProps, CardHeader, type CardHeaderProps, type CustomJSONSchema7, type CustomJSONSchema7Definition, DataDisplay, type DataDisplayProps, type DataResponse, DataTable, type DataTableDefaultState, type DataTableProps, DataTableServer, type DataTableServerProps, type DatePickerLabels, type DatePickerProps, type DateTimePickerLabels, DefaultCardTitle, DefaultForm, type DefaultFormProps, DefaultTable, type DefaultTableProps, DensityToggleButton, type DensityToggleButtonProps, type EditFilterButtonProps, EditSortingButton, type EditSortingButtonProps, type EditViewButtonProps, EmptyState, type EmptyStateProps, type EnumPickerLabels, ErrorAlert, type ErrorAlertProps, type ErrorMessageConfig, type ErrorMessageResult, type FieldErrorConfig, FilterDialog, FormBody, FormRoot, type FormRootProps, FormTitle, type GetColumnsConfigs, type GetDateColorProps, type GetMultiDatesProps, type GetRangeDatesProps, type GetStyleProps, type GetVariantProps, GlobalFilter, type IdPickerLabels, PageSizeControl, type PageSizeControlProps, Pagination, type QueryParams, type RangeCalendarProps, type RangeDatePickerProps, RecordDisplay, type RecordDisplayProps, ReloadButton, type ReloadButtonProps, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, type Result, RowCountText, Table, TableBody, type TableBodyProps, TableCardContainer, type TableCardContainerProps, TableCards, type TableCardsProps, TableComponent, TableControls, type TableControlsProps, TableDataDisplay, type TableDataDisplayProps, TableFilter, TableFilterTags, TableFooter, type TableFooterProps, TableHeader, type TableHeaderProps, type TableHeaderTexts, TableLoadingComponent, type TableLoadingComponentProps, type TableProps, type TableRendererProps, type TableRowSelectorProps, TableSelector, TableSorter, TableViewer, type TagPickerProps, TextCell, type TextCellProps, type UseDataTableProps, type UseDataTableReturn, type UseDataTableServerProps, type UseDataTableServerReturn, type UseFormProps, type ValidationErrorType, ViewDialog, buildErrorMessages, buildFieldErrors, buildRequiredErrors, convertToAjvErrorsFormat, createErrorMessage, getColumns, getMultiDates, getRangeDates, idPickerSanityCheck, useDataTable, useDataTableContext, useDataTableServer, useForm, widthSanityCheck };
|
|
1100
|
+
export { type CalendarProps, CardHeader, type CardHeaderProps, type CustomJSONSchema7, type CustomJSONSchema7Definition, DataDisplay, type DataDisplayProps, type DataResponse, DataTable, type DataTableDefaultState, type DataTableProps, DataTableServer, type DataTableServerProps, type DatePickerLabels, type DatePickerProps, type DateTimePickerLabels, DefaultCardTitle, DefaultForm, type DefaultFormProps, DefaultTable, type DefaultTableProps, DensityToggleButton, type DensityToggleButtonProps, type EditFilterButtonProps, EditSortingButton, type EditSortingButtonProps, type EditViewButtonProps, EmptyState, type EmptyStateProps, type EnumPickerLabels, ErrorAlert, type ErrorAlertProps, type ErrorMessageConfig, type ErrorMessageResult, type FieldErrorConfig, type FilePickerLabels, type FilePickerMediaFile, type FilePickerProps, FilterDialog, FormBody, FormRoot, type FormRootProps, FormTitle, type GetColumnsConfigs, type GetDateColorProps, type GetMultiDatesProps, type GetRangeDatesProps, type GetStyleProps, type GetVariantProps, GlobalFilter, type IdPickerLabels, PageSizeControl, type PageSizeControlProps, Pagination, type QueryParams, type RangeCalendarProps, type RangeDatePickerProps, RecordDisplay, type RecordDisplayProps, ReloadButton, type ReloadButtonProps, ResetFilteringButton, ResetSelectionButton, ResetSortingButton, type Result, RowCountText, Table, TableBody, type TableBodyProps, TableCardContainer, type TableCardContainerProps, TableCards, type TableCardsProps, TableComponent, TableControls, type TableControlsProps, TableDataDisplay, type TableDataDisplayProps, TableFilter, TableFilterTags, TableFooter, type TableFooterProps, TableHeader, type TableHeaderProps, type TableHeaderTexts, TableLoadingComponent, type TableLoadingComponentProps, type TableProps, type TableRendererProps, type TableRowSelectorProps, TableSelector, TableSorter, TableViewer, type TagPickerProps, TextCell, type TextCellProps, type UseDataTableProps, type UseDataTableReturn, type UseDataTableServerProps, type UseDataTableServerReturn, type UseFormProps, type ValidationErrorType, ViewDialog, buildErrorMessages, buildFieldErrors, buildRequiredErrors, convertToAjvErrorsFormat, createErrorMessage, getColumns, getMultiDates, getRangeDates, idPickerSanityCheck, useDataTable, useDataTableContext, useDataTableServer, useForm, widthSanityCheck };
|
package/dist/index.js
CHANGED
|
@@ -3908,7 +3908,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
|
|
|
3908
3908
|
showSubmitButton: true,
|
|
3909
3909
|
showResetButton: true,
|
|
3910
3910
|
showTitle: true,
|
|
3911
|
-
}, requireConfirmation = false, dateTimePickerLabels, idPickerLabels, enumPickerLabels, }) => {
|
|
3911
|
+
}, requireConfirmation = false, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, }) => {
|
|
3912
3912
|
const [isSuccess, setIsSuccess] = React.useState(false);
|
|
3913
3913
|
const [isError, setIsError] = React.useState(false);
|
|
3914
3914
|
const [isSubmiting, setIsSubmiting] = React.useState(false);
|
|
@@ -3997,6 +3997,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
|
|
|
3997
3997
|
dateTimePickerLabels,
|
|
3998
3998
|
idPickerLabels,
|
|
3999
3999
|
enumPickerLabels,
|
|
4000
|
+
filePickerLabels,
|
|
4000
4001
|
ajvResolver: ajvResolver(schema),
|
|
4001
4002
|
}, children: jsxRuntime.jsx(reactHookForm.FormProvider, { ...form, children: children }) }));
|
|
4002
4003
|
};
|
|
@@ -4370,6 +4371,92 @@ const DatePicker = ({ column, schema, prefix }) => {
|
|
|
4370
4371
|
} })] }) })] }) }));
|
|
4371
4372
|
};
|
|
4372
4373
|
|
|
4374
|
+
dayjs.extend(utc);
|
|
4375
|
+
dayjs.extend(timezone);
|
|
4376
|
+
const DateRangePicker = ({ column, schema, prefix, }) => {
|
|
4377
|
+
const { watch, formState: { errors }, setValue, } = reactHookForm.useFormContext();
|
|
4378
|
+
const { timezone, dateTimePickerLabels } = useSchemaContext();
|
|
4379
|
+
const formI18n = useFormI18n(column, prefix);
|
|
4380
|
+
const { required, gridColumn = 'span 12', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD', dateFormat = 'YYYY-MM-DD', } = schema;
|
|
4381
|
+
const isRequired = required?.some((columnId) => columnId === column);
|
|
4382
|
+
const colLabel = formI18n.colLabel;
|
|
4383
|
+
const [open, setOpen] = React.useState(false);
|
|
4384
|
+
const selectedDateRange = watch(colLabel);
|
|
4385
|
+
// Convert string[] to Date[] for the picker
|
|
4386
|
+
const selectedDates = (selectedDateRange ?? [])
|
|
4387
|
+
.map((dateStr) => {
|
|
4388
|
+
if (!dateStr)
|
|
4389
|
+
return null;
|
|
4390
|
+
const parsed = dayjs(dateStr).tz(timezone);
|
|
4391
|
+
return parsed.isValid() ? parsed.toDate() : null;
|
|
4392
|
+
})
|
|
4393
|
+
.filter((date) => date !== null);
|
|
4394
|
+
// Format display string
|
|
4395
|
+
const getDisplayText = () => {
|
|
4396
|
+
if (!selectedDateRange || selectedDateRange.length === 0) {
|
|
4397
|
+
return '';
|
|
4398
|
+
}
|
|
4399
|
+
if (selectedDateRange.length === 1) {
|
|
4400
|
+
const date = dayjs(selectedDateRange[0]).tz(timezone);
|
|
4401
|
+
return date.isValid() ? date.format(displayDateFormat) : '';
|
|
4402
|
+
}
|
|
4403
|
+
if (selectedDateRange.length === 2) {
|
|
4404
|
+
const startDate = dayjs(selectedDateRange[0]).tz(timezone);
|
|
4405
|
+
const endDate = dayjs(selectedDateRange[1]).tz(timezone);
|
|
4406
|
+
if (startDate.isValid() && endDate.isValid()) {
|
|
4407
|
+
return `${startDate.format(displayDateFormat)} - ${endDate.format(displayDateFormat)}`;
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4410
|
+
return '';
|
|
4411
|
+
};
|
|
4412
|
+
React.useEffect(() => {
|
|
4413
|
+
try {
|
|
4414
|
+
if (selectedDateRange && selectedDateRange.length > 0) {
|
|
4415
|
+
// Format dates according to dateFormat from schema
|
|
4416
|
+
const formatted = selectedDateRange
|
|
4417
|
+
.map((dateStr) => {
|
|
4418
|
+
if (!dateStr)
|
|
4419
|
+
return null;
|
|
4420
|
+
const parsed = dayjs(dateStr).tz(timezone);
|
|
4421
|
+
return parsed.isValid() ? parsed.format(dateFormat) : null;
|
|
4422
|
+
})
|
|
4423
|
+
.filter((date) => date !== null);
|
|
4424
|
+
// Update the form value only if different to avoid loops
|
|
4425
|
+
// Compare arrays element by element
|
|
4426
|
+
const needsUpdate = formatted.length !== selectedDateRange.length ||
|
|
4427
|
+
formatted.some((val, idx) => val !== selectedDateRange[idx]);
|
|
4428
|
+
if (needsUpdate && formatted.length > 0) {
|
|
4429
|
+
setValue(colLabel, formatted, {
|
|
4430
|
+
shouldValidate: true,
|
|
4431
|
+
shouldDirty: true,
|
|
4432
|
+
});
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
}
|
|
4436
|
+
catch (e) {
|
|
4437
|
+
console.error(e);
|
|
4438
|
+
}
|
|
4439
|
+
}, [selectedDateRange, dateFormat, colLabel, setValue, timezone]);
|
|
4440
|
+
return (jsxRuntime.jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
4441
|
+
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxRuntime.jsxs(PopoverRoot, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: jsxRuntime.jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
|
|
4442
|
+
setOpen(true);
|
|
4443
|
+
}, justifyContent: 'start', children: [jsxRuntime.jsx(md.MdDateRange, {}), getDisplayText()] }) }), jsxRuntime.jsx(PopoverContent, { minW: '600px', children: jsxRuntime.jsxs(PopoverBody, { children: [jsxRuntime.jsx(PopoverTitle, {}), jsxRuntime.jsx(RangeDatePicker, { selected: selectedDates, onDateSelected: ({ selected, selectable, date }) => {
|
|
4444
|
+
const newDates = getRangeDates({
|
|
4445
|
+
selectable,
|
|
4446
|
+
date,
|
|
4447
|
+
selectedDates,
|
|
4448
|
+
}) ?? [];
|
|
4449
|
+
// Convert Date[] to string[]
|
|
4450
|
+
const formattedDates = newDates
|
|
4451
|
+
.map((dateObj) => dayjs(dateObj).tz(timezone).format(dateFormat))
|
|
4452
|
+
.filter((dateStr) => dateStr);
|
|
4453
|
+
setValue(colLabel, formattedDates, {
|
|
4454
|
+
shouldValidate: true,
|
|
4455
|
+
shouldDirty: true,
|
|
4456
|
+
});
|
|
4457
|
+
}, monthsToDisplay: 2 })] }) })] }) }));
|
|
4458
|
+
};
|
|
4459
|
+
|
|
4373
4460
|
function filterArray(array, searchTerm) {
|
|
4374
4461
|
// Convert the search term to lower case for case-insensitive comparison
|
|
4375
4462
|
const lowerCaseSearchTerm = searchTerm.toLowerCase();
|
|
@@ -4782,7 +4869,7 @@ function getText(_ref2) {
|
|
|
4782
4869
|
return source.getStringData(textMediaType);
|
|
4783
4870
|
}
|
|
4784
4871
|
|
|
4785
|
-
const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }, placeholder =
|
|
4872
|
+
const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }, placeholder = 'Drop files here or click to upload', }) => {
|
|
4786
4873
|
const ref = React.useRef(null);
|
|
4787
4874
|
const [isDraggedOver, setIsDraggedOver] = React.useState(false);
|
|
4788
4875
|
React.useEffect(() => {
|
|
@@ -4796,7 +4883,7 @@ const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }
|
|
|
4796
4883
|
onDrop: ({ source }) => {
|
|
4797
4884
|
const files = getFiles({ source });
|
|
4798
4885
|
const text = getText({ source });
|
|
4799
|
-
console.log(files, text,
|
|
4886
|
+
console.log(files, text, 'dfposa');
|
|
4800
4887
|
onDrop({ files, text });
|
|
4801
4888
|
},
|
|
4802
4889
|
});
|
|
@@ -4805,9 +4892,9 @@ const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }
|
|
|
4805
4892
|
function getColor(isDraggedOver) {
|
|
4806
4893
|
if (isDraggedOver) {
|
|
4807
4894
|
return {
|
|
4808
|
-
backgroundColor:
|
|
4895
|
+
backgroundColor: 'blue.400',
|
|
4809
4896
|
_dark: {
|
|
4810
|
-
backgroundColor:
|
|
4897
|
+
backgroundColor: 'blue.400',
|
|
4811
4898
|
},
|
|
4812
4899
|
};
|
|
4813
4900
|
}
|
|
@@ -4828,27 +4915,211 @@ const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }
|
|
|
4828
4915
|
const filesArray = [...event.target.files];
|
|
4829
4916
|
onDrop({ files: filesArray });
|
|
4830
4917
|
};
|
|
4831
|
-
return (jsxRuntime.jsxs(react.Grid, { ...getColor(isDraggedOver), ref: ref, cursor:
|
|
4918
|
+
return (jsxRuntime.jsxs(react.Grid, { ...getColor(isDraggedOver), ref: ref, cursor: 'pointer', onClick: handleClick, borderStyle: 'dashed', borderColor: 'colorPalette.400', alignContent: 'center', justifyContent: 'center', borderWidth: 1, borderRadius: 4, minH: "120px", ...gridProps, children: [children, !!children === false && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(react.Flex, { children: placeholder }), jsxRuntime.jsx(react.Input, { type: "file", multiple: true, style: { display: 'none' }, ref: fileInput, onChange: handleChange })] }))] }));
|
|
4832
4919
|
};
|
|
4833
4920
|
|
|
4921
|
+
/**
|
|
4922
|
+
* Format bytes to human-readable string
|
|
4923
|
+
* @param bytes - The number of bytes to format
|
|
4924
|
+
* @returns Formatted string (e.g., "1.5 KB", "2.3 MB")
|
|
4925
|
+
*/
|
|
4926
|
+
function formatBytes(bytes) {
|
|
4927
|
+
if (bytes === 0)
|
|
4928
|
+
return '0 Bytes';
|
|
4929
|
+
const k = 1024;
|
|
4930
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
4931
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
4932
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
4933
|
+
}
|
|
4934
|
+
|
|
4935
|
+
function FilePickerDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, labels, translate, colLabel, }) {
|
|
4936
|
+
const [searchTerm, setSearchTerm] = React.useState('');
|
|
4937
|
+
const [selectedFileId, setSelectedFileId] = React.useState('');
|
|
4938
|
+
const [failedImageIds, setFailedImageIds] = React.useState(new Set());
|
|
4939
|
+
const { data: filesData, isLoading, isError, } = reactQuery.useQuery({
|
|
4940
|
+
queryKey: ['file-picker-library', searchTerm],
|
|
4941
|
+
queryFn: async () => {
|
|
4942
|
+
if (!onFetchFiles)
|
|
4943
|
+
return { data: [] };
|
|
4944
|
+
const files = await onFetchFiles(searchTerm.trim() || '');
|
|
4945
|
+
return { data: files };
|
|
4946
|
+
},
|
|
4947
|
+
enabled: open && !!onFetchFiles,
|
|
4948
|
+
});
|
|
4949
|
+
const files = (filesData?.data || []);
|
|
4950
|
+
const filteredFiles = filterImageOnly
|
|
4951
|
+
? files.filter((file) => /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name))
|
|
4952
|
+
: files;
|
|
4953
|
+
const handleSelect = () => {
|
|
4954
|
+
if (selectedFileId) {
|
|
4955
|
+
onSelect(selectedFileId);
|
|
4956
|
+
onClose();
|
|
4957
|
+
setSelectedFileId('');
|
|
4958
|
+
setSearchTerm('');
|
|
4959
|
+
}
|
|
4960
|
+
};
|
|
4961
|
+
const handleClose = () => {
|
|
4962
|
+
onClose();
|
|
4963
|
+
setSelectedFileId('');
|
|
4964
|
+
setSearchTerm('');
|
|
4965
|
+
setFailedImageIds(new Set());
|
|
4966
|
+
};
|
|
4967
|
+
const handleImageError = (fileId) => {
|
|
4968
|
+
setFailedImageIds((prev) => new Set(prev).add(fileId));
|
|
4969
|
+
};
|
|
4970
|
+
if (!onFetchFiles)
|
|
4971
|
+
return null;
|
|
4972
|
+
return (jsxRuntime.jsx(DialogRoot, { open: open, onOpenChange: (e) => !e.open && handleClose(), children: jsxRuntime.jsxs(DialogContent, { maxWidth: "800px", maxHeight: "90vh", children: [jsxRuntime.jsxs(DialogHeader, { children: [jsxRuntime.jsx(DialogTitle, { fontSize: "lg", fontWeight: "bold", children: title }), jsxRuntime.jsx(DialogCloseTrigger, {})] }), jsxRuntime.jsx(DialogBody, { children: jsxRuntime.jsxs(react.VStack, { align: "stretch", gap: 4, children: [jsxRuntime.jsxs(react.Box, { position: "relative", children: [jsxRuntime.jsx(react.Input, { placeholder: labels?.searchPlaceholder ??
|
|
4973
|
+
translate(removeIndex(`${colLabel}.search_placeholder`)) ??
|
|
4974
|
+
'Search files...', value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), bg: "bg.panel", border: "1px solid", borderColor: "border.default", colorPalette: "blue", _focus: {
|
|
4975
|
+
borderColor: 'colorPalette.500',
|
|
4976
|
+
_dark: {
|
|
4977
|
+
borderColor: 'colorPalette.400',
|
|
4978
|
+
},
|
|
4979
|
+
boxShadow: {
|
|
4980
|
+
base: '0 0 0 1px var(--chakra-colors-blue-500)',
|
|
4981
|
+
_dark: '0 0 0 1px var(--chakra-colors-blue-400)',
|
|
4982
|
+
},
|
|
4983
|
+
}, pl: 10 }), jsxRuntime.jsx(react.Icon, { as: lu.LuSearch, position: "absolute", left: 3, top: "50%", transform: "translateY(-50%)", color: "fg.muted", boxSize: 4 })] }), isLoading && (jsxRuntime.jsxs(react.Box, { textAlign: "center", py: 8, children: [jsxRuntime.jsx(react.Spinner, { size: "lg", colorPalette: "blue" }), jsxRuntime.jsx(react.Text, { mt: 4, color: "fg.muted", children: labels?.loading ??
|
|
4984
|
+
translate(removeIndex(`${colLabel}.loading`)) ??
|
|
4985
|
+
'Loading files...' })] })), isError && (jsxRuntime.jsx(react.Box, { bg: { base: 'colorPalette.50', _dark: 'colorPalette.900/20' }, border: "1px solid", borderColor: {
|
|
4986
|
+
base: 'colorPalette.200',
|
|
4987
|
+
_dark: 'colorPalette.800',
|
|
4988
|
+
}, colorPalette: "red", borderRadius: "md", p: 4, children: jsxRuntime.jsx(react.Text, { color: {
|
|
4989
|
+
base: 'colorPalette.600',
|
|
4990
|
+
_dark: 'colorPalette.300',
|
|
4991
|
+
}, children: labels?.loadingFailed ??
|
|
4992
|
+
translate(removeIndex(`${colLabel}.error.loading_failed`)) ??
|
|
4993
|
+
'Failed to load files' }) })), !isLoading && !isError && (jsxRuntime.jsx(react.Box, { maxHeight: "400px", overflowY: "auto", children: filteredFiles.length === 0 ? (jsxRuntime.jsx(react.Box, { textAlign: "center", py: 8, children: jsxRuntime.jsx(react.Text, { color: "fg.muted", children: labels?.noFilesFound ??
|
|
4994
|
+
translate(removeIndex(`${colLabel}.no_files_found`)) ??
|
|
4995
|
+
'No files found' }) })) : (jsxRuntime.jsx(react.VStack, { align: "stretch", gap: 2, children: filteredFiles.map((file) => {
|
|
4996
|
+
const isImage = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name);
|
|
4997
|
+
const isSelected = selectedFileId === file.id;
|
|
4998
|
+
const imageFailed = failedImageIds.has(file.id);
|
|
4999
|
+
return (jsxRuntime.jsx(react.Box, { p: 3, border: "2px solid", borderColor: isSelected
|
|
5000
|
+
? {
|
|
5001
|
+
base: 'colorPalette.500',
|
|
5002
|
+
_dark: 'colorPalette.400',
|
|
5003
|
+
}
|
|
5004
|
+
: 'border.default', borderRadius: "md", bg: isSelected
|
|
5005
|
+
? {
|
|
5006
|
+
base: 'colorPalette.50',
|
|
5007
|
+
_dark: 'colorPalette.900/20',
|
|
5008
|
+
}
|
|
5009
|
+
: 'bg.panel', colorPalette: "blue", cursor: "pointer", onClick: () => setSelectedFileId(file.id), _hover: {
|
|
5010
|
+
borderColor: isSelected
|
|
5011
|
+
? {
|
|
5012
|
+
base: 'colorPalette.600',
|
|
5013
|
+
_dark: 'colorPalette.400',
|
|
5014
|
+
}
|
|
5015
|
+
: {
|
|
5016
|
+
base: 'colorPalette.300',
|
|
5017
|
+
_dark: 'colorPalette.400',
|
|
5018
|
+
},
|
|
5019
|
+
bg: isSelected
|
|
5020
|
+
? {
|
|
5021
|
+
base: 'colorPalette.100',
|
|
5022
|
+
_dark: 'colorPalette.800/30',
|
|
5023
|
+
}
|
|
5024
|
+
: 'bg.muted',
|
|
5025
|
+
}, transition: "all 0.2s", children: jsxRuntime.jsxs(react.HStack, { gap: 3, children: [jsxRuntime.jsx(react.Box, { width: "60px", height: "60px", display: "flex", alignItems: "center", justifyContent: "center", bg: "bg.muted", borderRadius: "md", flexShrink: 0, children: isImage && file.url && !imageFailed ? (jsxRuntime.jsx(react.Image, { src: file.url, alt: file.name, boxSize: "60px", objectFit: "cover", borderRadius: "md", onError: () => handleImageError(file.id) })) : isImage && (imageFailed || !file.url) ? (jsxRuntime.jsx(react.Icon, { as: lu.LuImage, boxSize: 6, color: "fg.muted" })) : (jsxRuntime.jsx(react.Icon, { as: lu.LuFile, boxSize: 6, color: "fg.muted" })) }), jsxRuntime.jsxs(react.VStack, { align: "start", flex: 1, gap: 1, children: [jsxRuntime.jsx(react.Text, { fontSize: "sm", fontWeight: "medium", color: "fg.default", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: file.name }), jsxRuntime.jsxs(react.HStack, { gap: 2, children: [file.size && (jsxRuntime.jsx(jsxRuntime.Fragment, { children: jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", children: typeof file.size === 'number'
|
|
5026
|
+
? formatBytes(file.size)
|
|
5027
|
+
: file.size }) })), file.comment && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [file.size && (jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", children: "\u2022" })), jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: file.comment })] }))] })] }), isSelected && (jsxRuntime.jsx(react.Box, { width: "24px", height: "24px", borderRadius: "full", bg: {
|
|
5028
|
+
base: 'colorPalette.500',
|
|
5029
|
+
_dark: 'colorPalette.400',
|
|
5030
|
+
}, colorPalette: "blue", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, children: jsxRuntime.jsx(react.Text, { color: "white", fontSize: "xs", fontWeight: "bold", children: "\u2713" }) }))] }) }, file.id));
|
|
5031
|
+
}) })) }))] }) }), jsxRuntime.jsx(DialogFooter, { children: jsxRuntime.jsxs(react.HStack, { gap: 3, justify: "end", children: [jsxRuntime.jsx(react.Button, { variant: "outline", onClick: handleClose, borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: labels?.cancel ??
|
|
5032
|
+
translate(removeIndex(`${colLabel}.cancel`)) ??
|
|
5033
|
+
'Cancel' }), jsxRuntime.jsx(react.Button, { colorPalette: "blue", onClick: handleSelect, disabled: !selectedFileId, children: labels?.select ??
|
|
5034
|
+
translate(removeIndex(`${colLabel}.select`)) ??
|
|
5035
|
+
'Select' })] }) })] }) }));
|
|
5036
|
+
}
|
|
4834
5037
|
const FilePicker = ({ column, schema, prefix }) => {
|
|
4835
5038
|
const { setValue, formState: { errors }, watch, } = reactHookForm.useFormContext();
|
|
4836
|
-
const {
|
|
4837
|
-
const
|
|
5039
|
+
const { filePickerLabels } = useSchemaContext();
|
|
5040
|
+
const formI18n = useFormI18n(column, prefix);
|
|
5041
|
+
const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, } = schema;
|
|
4838
5042
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
4839
|
-
const
|
|
4840
|
-
const
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
5043
|
+
const currentValue = watch(column) ?? [];
|
|
5044
|
+
const currentFiles = Array.isArray(currentValue)
|
|
5045
|
+
? currentValue
|
|
5046
|
+
: [];
|
|
5047
|
+
const colLabel = formI18n.colLabel;
|
|
5048
|
+
const [dialogOpen, setDialogOpen] = React.useState(false);
|
|
5049
|
+
const [failedImageIds, setFailedImageIds] = React.useState(new Set());
|
|
5050
|
+
const { onFetchFiles, enableMediaLibrary = false, filterImageOnly = false, } = filePicker || {};
|
|
5051
|
+
const showMediaLibrary = enableMediaLibrary && !!onFetchFiles;
|
|
5052
|
+
const handleImageError = (fileIdentifier) => {
|
|
5053
|
+
setFailedImageIds((prev) => new Set(prev).add(fileIdentifier));
|
|
5054
|
+
};
|
|
5055
|
+
const handleMediaLibrarySelect = (fileId) => {
|
|
5056
|
+
const newFiles = [...currentFiles, fileId];
|
|
5057
|
+
setValue(colLabel, newFiles);
|
|
5058
|
+
};
|
|
5059
|
+
const handleRemove = (index) => {
|
|
5060
|
+
const newFiles = currentFiles.filter((_, i) => i !== index);
|
|
5061
|
+
setValue(colLabel, newFiles);
|
|
5062
|
+
};
|
|
5063
|
+
const isFileObject = (value) => {
|
|
5064
|
+
return value instanceof File;
|
|
5065
|
+
};
|
|
5066
|
+
const getFileIdentifier = (file, index) => {
|
|
5067
|
+
if (isFileObject(file)) {
|
|
5068
|
+
return `${file.name}-${file.size}-${index}`;
|
|
5069
|
+
}
|
|
5070
|
+
return file;
|
|
5071
|
+
};
|
|
5072
|
+
const getFileName = (file) => {
|
|
5073
|
+
if (isFileObject(file)) {
|
|
5074
|
+
return file.name;
|
|
5075
|
+
}
|
|
5076
|
+
return typeof file === 'string' ? file : 'Unknown file';
|
|
5077
|
+
};
|
|
5078
|
+
const getFileSize = (file) => {
|
|
5079
|
+
if (isFileObject(file)) {
|
|
5080
|
+
return file.size;
|
|
5081
|
+
}
|
|
5082
|
+
return undefined;
|
|
5083
|
+
};
|
|
5084
|
+
const isImageFile = (file) => {
|
|
5085
|
+
if (isFileObject(file)) {
|
|
5086
|
+
return file.type.startsWith('image/');
|
|
5087
|
+
}
|
|
5088
|
+
if (typeof file === 'string') {
|
|
5089
|
+
return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file);
|
|
5090
|
+
}
|
|
5091
|
+
return false;
|
|
5092
|
+
};
|
|
5093
|
+
const getImageUrl = (file) => {
|
|
5094
|
+
if (isFileObject(file)) {
|
|
5095
|
+
return URL.createObjectURL(file);
|
|
5096
|
+
}
|
|
5097
|
+
return undefined;
|
|
5098
|
+
};
|
|
5099
|
+
return (jsxRuntime.jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
5100
|
+
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsxRuntime.jsxs(react.VStack, { align: "stretch", gap: 2, children: [jsxRuntime.jsx(FileDropzone, { onDrop: ({ files }) => {
|
|
5101
|
+
const newFiles = files.filter(({ name }) => !currentFiles.some((cur) => {
|
|
5102
|
+
if (isFileObject(cur)) {
|
|
5103
|
+
return cur.name === name;
|
|
5104
|
+
}
|
|
5105
|
+
return false;
|
|
5106
|
+
}));
|
|
5107
|
+
setValue(colLabel, [...currentFiles, ...newFiles]);
|
|
5108
|
+
}, placeholder: filePickerLabels?.fileDropzone ?? formI18n.t('fileDropzone') }), showMediaLibrary && (jsxRuntime.jsx(react.Button, { variant: "outline", onClick: () => setDialogOpen(true), borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: filePickerLabels?.browseLibrary ??
|
|
5109
|
+
formI18n.t('browse_library') ??
|
|
5110
|
+
'Browse from Library' }))] }), showMediaLibrary && (jsxRuntime.jsx(FilePickerDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ??
|
|
5111
|
+
formI18n.t('dialog_title') ??
|
|
5112
|
+
'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, labels: filePickerLabels, translate: formI18n.t, colLabel: colLabel })), jsxRuntime.jsx(react.Flex, { flexFlow: 'column', gap: 1, children: currentFiles.map((file, index) => {
|
|
5113
|
+
const fileIdentifier = getFileIdentifier(file, index);
|
|
5114
|
+
const fileName = getFileName(file);
|
|
5115
|
+
const fileSize = getFileSize(file);
|
|
5116
|
+
const isImage = isImageFile(file);
|
|
5117
|
+
const imageUrl = getImageUrl(file);
|
|
5118
|
+
const imageFailed = failedImageIds.has(fileIdentifier);
|
|
5119
|
+
return (jsxRuntime.jsx(react.Card.Root, { variant: 'subtle', colorPalette: "blue", children: jsxRuntime.jsxs(react.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: {
|
|
5120
|
+
borderColor: 'colorPalette.300',
|
|
5121
|
+
bg: 'bg.muted',
|
|
5122
|
+
}, transition: "all 0.2s", children: [jsxRuntime.jsx(react.Box, { width: "60px", height: "60px", display: "flex", alignItems: "center", justifyContent: "center", bg: "bg.muted", borderRadius: "md", flexShrink: 0, marginRight: "2", children: isImage && imageUrl && !imageFailed ? (jsxRuntime.jsx(react.Image, { src: imageUrl, alt: fileName, boxSize: "60px", objectFit: "cover", borderRadius: "md", onError: () => handleImageError(fileIdentifier) })) : isImage && (imageFailed || !imageUrl) ? (jsxRuntime.jsx(react.Icon, { as: lu.LuImage, boxSize: 6, color: "fg.muted" })) : (jsxRuntime.jsx(react.Icon, { as: lu.LuFile, boxSize: 6, color: "fg.muted" })) }), jsxRuntime.jsxs(react.VStack, { align: "start", flex: 1, gap: 1, children: [jsxRuntime.jsx(react.Text, { fontSize: "sm", fontWeight: "medium", color: "fg.default", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: fileName }), fileSize !== undefined && (jsxRuntime.jsx(react.Text, { fontSize: "xs", color: "fg.muted", children: formatBytes(fileSize) }))] }), jsxRuntime.jsx(react.Icon, { as: ti.TiDeleteOutline, boxSize: 5, color: "fg.muted" })] }) }, fileIdentifier));
|
|
4852
5123
|
}) })] }));
|
|
4853
5124
|
};
|
|
4854
5125
|
|
|
@@ -5972,59 +6243,62 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
|
|
|
5972
6243
|
const SchemaRenderer = ({ schema, prefix, column, }) => {
|
|
5973
6244
|
const colSchema = schema;
|
|
5974
6245
|
const { type, variant, properties: innerProperties, foreign_key, format, items, } = schema;
|
|
5975
|
-
if (variant ===
|
|
6246
|
+
if (variant === 'custom-input') {
|
|
5976
6247
|
return jsxRuntime.jsx(CustomInput, { schema: colSchema, prefix, column });
|
|
5977
6248
|
}
|
|
5978
|
-
if (type ===
|
|
6249
|
+
if (type === 'string') {
|
|
5979
6250
|
if ((schema.enum ?? []).length > 0) {
|
|
5980
6251
|
return jsxRuntime.jsx(EnumPicker, { schema: colSchema, prefix, column });
|
|
5981
6252
|
}
|
|
5982
|
-
if (variant ===
|
|
6253
|
+
if (variant === 'id-picker') {
|
|
5983
6254
|
idPickerSanityCheck(column, foreign_key);
|
|
5984
6255
|
return jsxRuntime.jsx(IdPicker, { schema: colSchema, prefix, column });
|
|
5985
6256
|
}
|
|
5986
|
-
if (format ===
|
|
6257
|
+
if (format === 'date') {
|
|
5987
6258
|
return jsxRuntime.jsx(DatePicker, { schema: colSchema, prefix, column });
|
|
5988
6259
|
}
|
|
5989
|
-
if (format ===
|
|
6260
|
+
if (format === 'time') {
|
|
5990
6261
|
return jsxRuntime.jsx(TimePicker, { schema: colSchema, prefix, column });
|
|
5991
6262
|
}
|
|
5992
|
-
if (format ===
|
|
6263
|
+
if (format === 'date-time') {
|
|
5993
6264
|
return jsxRuntime.jsx(DateTimePicker, { schema: colSchema, prefix, column });
|
|
5994
6265
|
}
|
|
5995
|
-
if (variant ===
|
|
6266
|
+
if (variant === 'text-area') {
|
|
5996
6267
|
return jsxRuntime.jsx(TextAreaInput, { schema: colSchema, prefix, column });
|
|
5997
6268
|
}
|
|
5998
6269
|
return jsxRuntime.jsx(StringInputField, { schema: colSchema, prefix, column });
|
|
5999
6270
|
}
|
|
6000
|
-
if (type ===
|
|
6271
|
+
if (type === 'number' || type === 'integer') {
|
|
6001
6272
|
return jsxRuntime.jsx(NumberInputField, { schema: colSchema, prefix, column });
|
|
6002
6273
|
}
|
|
6003
|
-
if (type ===
|
|
6274
|
+
if (type === 'boolean') {
|
|
6004
6275
|
return jsxRuntime.jsx(BooleanPicker, { schema: colSchema, prefix, column });
|
|
6005
6276
|
}
|
|
6006
|
-
if (type ===
|
|
6277
|
+
if (type === 'object') {
|
|
6007
6278
|
if (innerProperties) {
|
|
6008
6279
|
return jsxRuntime.jsx(ObjectInput, { schema: colSchema, prefix, column });
|
|
6009
6280
|
}
|
|
6010
6281
|
return jsxRuntime.jsx(RecordInput$1, { schema: colSchema, prefix, column });
|
|
6011
6282
|
}
|
|
6012
|
-
if (type ===
|
|
6013
|
-
if (variant ===
|
|
6283
|
+
if (type === 'array') {
|
|
6284
|
+
if (variant === 'id-picker') {
|
|
6014
6285
|
idPickerSanityCheck(column, foreign_key);
|
|
6015
6286
|
return (jsxRuntime.jsx(IdPicker, { schema: colSchema, prefix, column, isMultiple: true }));
|
|
6016
6287
|
}
|
|
6017
|
-
if (variant ===
|
|
6288
|
+
if (variant === 'tag-picker') {
|
|
6018
6289
|
return jsxRuntime.jsx(TagPicker, { schema: colSchema, prefix, column });
|
|
6019
6290
|
}
|
|
6020
|
-
if (variant ===
|
|
6291
|
+
if (variant === 'file-picker') {
|
|
6021
6292
|
return jsxRuntime.jsx(FilePicker, { schema: colSchema, prefix, column });
|
|
6022
6293
|
}
|
|
6023
|
-
if (variant ===
|
|
6294
|
+
if (variant === 'date-range') {
|
|
6295
|
+
return jsxRuntime.jsx(DateRangePicker, { schema: colSchema, prefix, column });
|
|
6296
|
+
}
|
|
6297
|
+
if (variant === 'enum-picker') {
|
|
6024
6298
|
const { items } = colSchema;
|
|
6025
6299
|
const { enum: enumItems } = items;
|
|
6026
6300
|
const enumSchema = {
|
|
6027
|
-
type:
|
|
6301
|
+
type: 'string',
|
|
6028
6302
|
enum: enumItems,
|
|
6029
6303
|
};
|
|
6030
6304
|
return (jsxRuntime.jsx(EnumPicker, { isMultiple: true, schema: enumSchema, prefix, column }));
|
|
@@ -6034,7 +6308,7 @@ const SchemaRenderer = ({ schema, prefix, column, }) => {
|
|
|
6034
6308
|
}
|
|
6035
6309
|
return jsxRuntime.jsx(react.Text, { children: `array ${column}` });
|
|
6036
6310
|
}
|
|
6037
|
-
if (type ===
|
|
6311
|
+
if (type === 'null') {
|
|
6038
6312
|
return jsxRuntime.jsx(react.Text, { children: `null ${column}` });
|
|
6039
6313
|
}
|
|
6040
6314
|
return jsxRuntime.jsx(react.Text, { children: "missing type" });
|
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { Button as Button$1, AbsoluteCenter, Spinner, Span, IconButton, Portal,
|
|
|
3
3
|
import { AiOutlineColumnWidth } from 'react-icons/ai';
|
|
4
4
|
import * as React from 'react';
|
|
5
5
|
import React__default, { createContext, useContext, useState, useEffect, useRef, forwardRef } from 'react';
|
|
6
|
-
import { LuX, LuCheck, LuChevronRight } from 'react-icons/lu';
|
|
6
|
+
import { LuX, LuCheck, LuChevronRight, LuImage, LuFile, LuSearch } from 'react-icons/lu';
|
|
7
7
|
import { MdOutlineSort, MdFilterAlt, MdSearch, MdOutlineViewColumn, MdFilterListAlt, MdPushPin, MdCancel, MdClear, MdOutlineChecklist, MdDateRange } from 'react-icons/md';
|
|
8
8
|
import { FaUpDown, FaGripLinesVertical, FaTrash } from 'react-icons/fa6';
|
|
9
9
|
import { BiDownArrow, BiUpArrow, BiError } from 'react-icons/bi';
|
|
@@ -3888,7 +3888,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
|
|
|
3888
3888
|
showSubmitButton: true,
|
|
3889
3889
|
showResetButton: true,
|
|
3890
3890
|
showTitle: true,
|
|
3891
|
-
}, requireConfirmation = false, dateTimePickerLabels, idPickerLabels, enumPickerLabels, }) => {
|
|
3891
|
+
}, requireConfirmation = false, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, }) => {
|
|
3892
3892
|
const [isSuccess, setIsSuccess] = useState(false);
|
|
3893
3893
|
const [isError, setIsError] = useState(false);
|
|
3894
3894
|
const [isSubmiting, setIsSubmiting] = useState(false);
|
|
@@ -3977,6 +3977,7 @@ const FormRoot = ({ schema, idMap, setIdMap, form, serverUrl, translate, childre
|
|
|
3977
3977
|
dateTimePickerLabels,
|
|
3978
3978
|
idPickerLabels,
|
|
3979
3979
|
enumPickerLabels,
|
|
3980
|
+
filePickerLabels,
|
|
3980
3981
|
ajvResolver: ajvResolver(schema),
|
|
3981
3982
|
}, children: jsx(FormProvider, { ...form, children: children }) }));
|
|
3982
3983
|
};
|
|
@@ -4350,6 +4351,92 @@ const DatePicker = ({ column, schema, prefix }) => {
|
|
|
4350
4351
|
} })] }) })] }) }));
|
|
4351
4352
|
};
|
|
4352
4353
|
|
|
4354
|
+
dayjs.extend(utc);
|
|
4355
|
+
dayjs.extend(timezone);
|
|
4356
|
+
const DateRangePicker = ({ column, schema, prefix, }) => {
|
|
4357
|
+
const { watch, formState: { errors }, setValue, } = useFormContext();
|
|
4358
|
+
const { timezone, dateTimePickerLabels } = useSchemaContext();
|
|
4359
|
+
const formI18n = useFormI18n(column, prefix);
|
|
4360
|
+
const { required, gridColumn = 'span 12', gridRow = 'span 1', displayDateFormat = 'YYYY-MM-DD', dateFormat = 'YYYY-MM-DD', } = schema;
|
|
4361
|
+
const isRequired = required?.some((columnId) => columnId === column);
|
|
4362
|
+
const colLabel = formI18n.colLabel;
|
|
4363
|
+
const [open, setOpen] = useState(false);
|
|
4364
|
+
const selectedDateRange = watch(colLabel);
|
|
4365
|
+
// Convert string[] to Date[] for the picker
|
|
4366
|
+
const selectedDates = (selectedDateRange ?? [])
|
|
4367
|
+
.map((dateStr) => {
|
|
4368
|
+
if (!dateStr)
|
|
4369
|
+
return null;
|
|
4370
|
+
const parsed = dayjs(dateStr).tz(timezone);
|
|
4371
|
+
return parsed.isValid() ? parsed.toDate() : null;
|
|
4372
|
+
})
|
|
4373
|
+
.filter((date) => date !== null);
|
|
4374
|
+
// Format display string
|
|
4375
|
+
const getDisplayText = () => {
|
|
4376
|
+
if (!selectedDateRange || selectedDateRange.length === 0) {
|
|
4377
|
+
return '';
|
|
4378
|
+
}
|
|
4379
|
+
if (selectedDateRange.length === 1) {
|
|
4380
|
+
const date = dayjs(selectedDateRange[0]).tz(timezone);
|
|
4381
|
+
return date.isValid() ? date.format(displayDateFormat) : '';
|
|
4382
|
+
}
|
|
4383
|
+
if (selectedDateRange.length === 2) {
|
|
4384
|
+
const startDate = dayjs(selectedDateRange[0]).tz(timezone);
|
|
4385
|
+
const endDate = dayjs(selectedDateRange[1]).tz(timezone);
|
|
4386
|
+
if (startDate.isValid() && endDate.isValid()) {
|
|
4387
|
+
return `${startDate.format(displayDateFormat)} - ${endDate.format(displayDateFormat)}`;
|
|
4388
|
+
}
|
|
4389
|
+
}
|
|
4390
|
+
return '';
|
|
4391
|
+
};
|
|
4392
|
+
useEffect(() => {
|
|
4393
|
+
try {
|
|
4394
|
+
if (selectedDateRange && selectedDateRange.length > 0) {
|
|
4395
|
+
// Format dates according to dateFormat from schema
|
|
4396
|
+
const formatted = selectedDateRange
|
|
4397
|
+
.map((dateStr) => {
|
|
4398
|
+
if (!dateStr)
|
|
4399
|
+
return null;
|
|
4400
|
+
const parsed = dayjs(dateStr).tz(timezone);
|
|
4401
|
+
return parsed.isValid() ? parsed.format(dateFormat) : null;
|
|
4402
|
+
})
|
|
4403
|
+
.filter((date) => date !== null);
|
|
4404
|
+
// Update the form value only if different to avoid loops
|
|
4405
|
+
// Compare arrays element by element
|
|
4406
|
+
const needsUpdate = formatted.length !== selectedDateRange.length ||
|
|
4407
|
+
formatted.some((val, idx) => val !== selectedDateRange[idx]);
|
|
4408
|
+
if (needsUpdate && formatted.length > 0) {
|
|
4409
|
+
setValue(colLabel, formatted, {
|
|
4410
|
+
shouldValidate: true,
|
|
4411
|
+
shouldDirty: true,
|
|
4412
|
+
});
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4415
|
+
}
|
|
4416
|
+
catch (e) {
|
|
4417
|
+
console.error(e);
|
|
4418
|
+
}
|
|
4419
|
+
}, [selectedDateRange, dateFormat, colLabel, setValue, timezone]);
|
|
4420
|
+
return (jsx(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
4421
|
+
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: jsxs(PopoverRoot, { open: open, onOpenChange: (e) => setOpen(e.open), closeOnInteractOutside: true, children: [jsx(PopoverTrigger, { asChild: true, children: jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
|
|
4422
|
+
setOpen(true);
|
|
4423
|
+
}, justifyContent: 'start', children: [jsx(MdDateRange, {}), getDisplayText()] }) }), jsx(PopoverContent, { minW: '600px', children: jsxs(PopoverBody, { children: [jsx(PopoverTitle, {}), jsx(RangeDatePicker, { selected: selectedDates, onDateSelected: ({ selected, selectable, date }) => {
|
|
4424
|
+
const newDates = getRangeDates({
|
|
4425
|
+
selectable,
|
|
4426
|
+
date,
|
|
4427
|
+
selectedDates,
|
|
4428
|
+
}) ?? [];
|
|
4429
|
+
// Convert Date[] to string[]
|
|
4430
|
+
const formattedDates = newDates
|
|
4431
|
+
.map((dateObj) => dayjs(dateObj).tz(timezone).format(dateFormat))
|
|
4432
|
+
.filter((dateStr) => dateStr);
|
|
4433
|
+
setValue(colLabel, formattedDates, {
|
|
4434
|
+
shouldValidate: true,
|
|
4435
|
+
shouldDirty: true,
|
|
4436
|
+
});
|
|
4437
|
+
}, monthsToDisplay: 2 })] }) })] }) }));
|
|
4438
|
+
};
|
|
4439
|
+
|
|
4353
4440
|
function filterArray(array, searchTerm) {
|
|
4354
4441
|
// Convert the search term to lower case for case-insensitive comparison
|
|
4355
4442
|
const lowerCaseSearchTerm = searchTerm.toLowerCase();
|
|
@@ -4762,7 +4849,7 @@ function getText(_ref2) {
|
|
|
4762
4849
|
return source.getStringData(textMediaType);
|
|
4763
4850
|
}
|
|
4764
4851
|
|
|
4765
|
-
const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }, placeholder =
|
|
4852
|
+
const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }, placeholder = 'Drop files here or click to upload', }) => {
|
|
4766
4853
|
const ref = useRef(null);
|
|
4767
4854
|
const [isDraggedOver, setIsDraggedOver] = useState(false);
|
|
4768
4855
|
useEffect(() => {
|
|
@@ -4776,7 +4863,7 @@ const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }
|
|
|
4776
4863
|
onDrop: ({ source }) => {
|
|
4777
4864
|
const files = getFiles({ source });
|
|
4778
4865
|
const text = getText({ source });
|
|
4779
|
-
console.log(files, text,
|
|
4866
|
+
console.log(files, text, 'dfposa');
|
|
4780
4867
|
onDrop({ files, text });
|
|
4781
4868
|
},
|
|
4782
4869
|
});
|
|
@@ -4785,9 +4872,9 @@ const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }
|
|
|
4785
4872
|
function getColor(isDraggedOver) {
|
|
4786
4873
|
if (isDraggedOver) {
|
|
4787
4874
|
return {
|
|
4788
|
-
backgroundColor:
|
|
4875
|
+
backgroundColor: 'blue.400',
|
|
4789
4876
|
_dark: {
|
|
4790
|
-
backgroundColor:
|
|
4877
|
+
backgroundColor: 'blue.400',
|
|
4791
4878
|
},
|
|
4792
4879
|
};
|
|
4793
4880
|
}
|
|
@@ -4808,27 +4895,211 @@ const FileDropzone = ({ children = undefined, gridProps = {}, onDrop = () => { }
|
|
|
4808
4895
|
const filesArray = [...event.target.files];
|
|
4809
4896
|
onDrop({ files: filesArray });
|
|
4810
4897
|
};
|
|
4811
|
-
return (jsxs(Grid, { ...getColor(isDraggedOver), ref: ref, cursor:
|
|
4898
|
+
return (jsxs(Grid, { ...getColor(isDraggedOver), ref: ref, cursor: 'pointer', onClick: handleClick, borderStyle: 'dashed', borderColor: 'colorPalette.400', alignContent: 'center', justifyContent: 'center', borderWidth: 1, borderRadius: 4, minH: "120px", ...gridProps, children: [children, !!children === false && (jsxs(Fragment, { children: [jsx(Flex, { children: placeholder }), jsx(Input, { type: "file", multiple: true, style: { display: 'none' }, ref: fileInput, onChange: handleChange })] }))] }));
|
|
4812
4899
|
};
|
|
4813
4900
|
|
|
4901
|
+
/**
|
|
4902
|
+
* Format bytes to human-readable string
|
|
4903
|
+
* @param bytes - The number of bytes to format
|
|
4904
|
+
* @returns Formatted string (e.g., "1.5 KB", "2.3 MB")
|
|
4905
|
+
*/
|
|
4906
|
+
function formatBytes(bytes) {
|
|
4907
|
+
if (bytes === 0)
|
|
4908
|
+
return '0 Bytes';
|
|
4909
|
+
const k = 1024;
|
|
4910
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
4911
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
4912
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
4913
|
+
}
|
|
4914
|
+
|
|
4915
|
+
function FilePickerDialog({ open, onClose, onSelect, title, filterImageOnly = false, onFetchFiles, labels, translate, colLabel, }) {
|
|
4916
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
4917
|
+
const [selectedFileId, setSelectedFileId] = useState('');
|
|
4918
|
+
const [failedImageIds, setFailedImageIds] = useState(new Set());
|
|
4919
|
+
const { data: filesData, isLoading, isError, } = useQuery({
|
|
4920
|
+
queryKey: ['file-picker-library', searchTerm],
|
|
4921
|
+
queryFn: async () => {
|
|
4922
|
+
if (!onFetchFiles)
|
|
4923
|
+
return { data: [] };
|
|
4924
|
+
const files = await onFetchFiles(searchTerm.trim() || '');
|
|
4925
|
+
return { data: files };
|
|
4926
|
+
},
|
|
4927
|
+
enabled: open && !!onFetchFiles,
|
|
4928
|
+
});
|
|
4929
|
+
const files = (filesData?.data || []);
|
|
4930
|
+
const filteredFiles = filterImageOnly
|
|
4931
|
+
? files.filter((file) => /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name))
|
|
4932
|
+
: files;
|
|
4933
|
+
const handleSelect = () => {
|
|
4934
|
+
if (selectedFileId) {
|
|
4935
|
+
onSelect(selectedFileId);
|
|
4936
|
+
onClose();
|
|
4937
|
+
setSelectedFileId('');
|
|
4938
|
+
setSearchTerm('');
|
|
4939
|
+
}
|
|
4940
|
+
};
|
|
4941
|
+
const handleClose = () => {
|
|
4942
|
+
onClose();
|
|
4943
|
+
setSelectedFileId('');
|
|
4944
|
+
setSearchTerm('');
|
|
4945
|
+
setFailedImageIds(new Set());
|
|
4946
|
+
};
|
|
4947
|
+
const handleImageError = (fileId) => {
|
|
4948
|
+
setFailedImageIds((prev) => new Set(prev).add(fileId));
|
|
4949
|
+
};
|
|
4950
|
+
if (!onFetchFiles)
|
|
4951
|
+
return null;
|
|
4952
|
+
return (jsx(DialogRoot, { open: open, onOpenChange: (e) => !e.open && handleClose(), children: jsxs(DialogContent, { maxWidth: "800px", maxHeight: "90vh", children: [jsxs(DialogHeader, { children: [jsx(DialogTitle, { fontSize: "lg", fontWeight: "bold", children: title }), jsx(DialogCloseTrigger, {})] }), jsx(DialogBody, { children: jsxs(VStack, { align: "stretch", gap: 4, children: [jsxs(Box, { position: "relative", children: [jsx(Input, { placeholder: labels?.searchPlaceholder ??
|
|
4953
|
+
translate(removeIndex(`${colLabel}.search_placeholder`)) ??
|
|
4954
|
+
'Search files...', value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), bg: "bg.panel", border: "1px solid", borderColor: "border.default", colorPalette: "blue", _focus: {
|
|
4955
|
+
borderColor: 'colorPalette.500',
|
|
4956
|
+
_dark: {
|
|
4957
|
+
borderColor: 'colorPalette.400',
|
|
4958
|
+
},
|
|
4959
|
+
boxShadow: {
|
|
4960
|
+
base: '0 0 0 1px var(--chakra-colors-blue-500)',
|
|
4961
|
+
_dark: '0 0 0 1px var(--chakra-colors-blue-400)',
|
|
4962
|
+
},
|
|
4963
|
+
}, pl: 10 }), jsx(Icon, { as: LuSearch, position: "absolute", left: 3, top: "50%", transform: "translateY(-50%)", color: "fg.muted", boxSize: 4 })] }), isLoading && (jsxs(Box, { textAlign: "center", py: 8, children: [jsx(Spinner, { size: "lg", colorPalette: "blue" }), jsx(Text, { mt: 4, color: "fg.muted", children: labels?.loading ??
|
|
4964
|
+
translate(removeIndex(`${colLabel}.loading`)) ??
|
|
4965
|
+
'Loading files...' })] })), isError && (jsx(Box, { bg: { base: 'colorPalette.50', _dark: 'colorPalette.900/20' }, border: "1px solid", borderColor: {
|
|
4966
|
+
base: 'colorPalette.200',
|
|
4967
|
+
_dark: 'colorPalette.800',
|
|
4968
|
+
}, colorPalette: "red", borderRadius: "md", p: 4, children: jsx(Text, { color: {
|
|
4969
|
+
base: 'colorPalette.600',
|
|
4970
|
+
_dark: 'colorPalette.300',
|
|
4971
|
+
}, children: labels?.loadingFailed ??
|
|
4972
|
+
translate(removeIndex(`${colLabel}.error.loading_failed`)) ??
|
|
4973
|
+
'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 ??
|
|
4974
|
+
translate(removeIndex(`${colLabel}.no_files_found`)) ??
|
|
4975
|
+
'No files found' }) })) : (jsx(VStack, { align: "stretch", gap: 2, children: filteredFiles.map((file) => {
|
|
4976
|
+
const isImage = /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file.name);
|
|
4977
|
+
const isSelected = selectedFileId === file.id;
|
|
4978
|
+
const imageFailed = failedImageIds.has(file.id);
|
|
4979
|
+
return (jsx(Box, { p: 3, border: "2px solid", borderColor: isSelected
|
|
4980
|
+
? {
|
|
4981
|
+
base: 'colorPalette.500',
|
|
4982
|
+
_dark: 'colorPalette.400',
|
|
4983
|
+
}
|
|
4984
|
+
: 'border.default', borderRadius: "md", bg: isSelected
|
|
4985
|
+
? {
|
|
4986
|
+
base: 'colorPalette.50',
|
|
4987
|
+
_dark: 'colorPalette.900/20',
|
|
4988
|
+
}
|
|
4989
|
+
: 'bg.panel', colorPalette: "blue", cursor: "pointer", onClick: () => setSelectedFileId(file.id), _hover: {
|
|
4990
|
+
borderColor: isSelected
|
|
4991
|
+
? {
|
|
4992
|
+
base: 'colorPalette.600',
|
|
4993
|
+
_dark: 'colorPalette.400',
|
|
4994
|
+
}
|
|
4995
|
+
: {
|
|
4996
|
+
base: 'colorPalette.300',
|
|
4997
|
+
_dark: 'colorPalette.400',
|
|
4998
|
+
},
|
|
4999
|
+
bg: isSelected
|
|
5000
|
+
? {
|
|
5001
|
+
base: 'colorPalette.100',
|
|
5002
|
+
_dark: 'colorPalette.800/30',
|
|
5003
|
+
}
|
|
5004
|
+
: 'bg.muted',
|
|
5005
|
+
}, transition: "all 0.2s", children: jsxs(HStack, { gap: 3, children: [jsx(Box, { width: "60px", height: "60px", display: "flex", alignItems: "center", justifyContent: "center", bg: "bg.muted", borderRadius: "md", flexShrink: 0, children: isImage && file.url && !imageFailed ? (jsx(Image, { src: file.url, alt: file.name, boxSize: "60px", objectFit: "cover", borderRadius: "md", onError: () => handleImageError(file.id) })) : isImage && (imageFailed || !file.url) ? (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: file.name }), jsxs(HStack, { gap: 2, children: [file.size && (jsx(Fragment, { children: jsx(Text, { fontSize: "xs", color: "fg.muted", children: typeof file.size === 'number'
|
|
5006
|
+
? formatBytes(file.size)
|
|
5007
|
+
: file.size }) })), file.comment && (jsxs(Fragment, { children: [file.size && (jsx(Text, { fontSize: "xs", color: "fg.muted", children: "\u2022" })), jsx(Text, { fontSize: "xs", color: "fg.muted", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", children: file.comment })] }))] })] }), isSelected && (jsx(Box, { width: "24px", height: "24px", borderRadius: "full", bg: {
|
|
5008
|
+
base: 'colorPalette.500',
|
|
5009
|
+
_dark: 'colorPalette.400',
|
|
5010
|
+
}, colorPalette: "blue", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, children: jsx(Text, { color: "white", fontSize: "xs", fontWeight: "bold", children: "\u2713" }) }))] }) }, file.id));
|
|
5011
|
+
}) })) }))] }) }), 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 ??
|
|
5012
|
+
translate(removeIndex(`${colLabel}.cancel`)) ??
|
|
5013
|
+
'Cancel' }), jsx(Button$1, { colorPalette: "blue", onClick: handleSelect, disabled: !selectedFileId, children: labels?.select ??
|
|
5014
|
+
translate(removeIndex(`${colLabel}.select`)) ??
|
|
5015
|
+
'Select' })] }) })] }) }));
|
|
5016
|
+
}
|
|
4814
5017
|
const FilePicker = ({ column, schema, prefix }) => {
|
|
4815
5018
|
const { setValue, formState: { errors }, watch, } = useFormContext();
|
|
4816
|
-
const {
|
|
4817
|
-
const
|
|
5019
|
+
const { filePickerLabels } = useSchemaContext();
|
|
5020
|
+
const formI18n = useFormI18n(column, prefix);
|
|
5021
|
+
const { required, gridColumn = 'span 12', gridRow = 'span 1', filePicker, } = schema;
|
|
4818
5022
|
const isRequired = required?.some((columnId) => columnId === column);
|
|
4819
|
-
const
|
|
4820
|
-
const
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
5023
|
+
const currentValue = watch(column) ?? [];
|
|
5024
|
+
const currentFiles = Array.isArray(currentValue)
|
|
5025
|
+
? currentValue
|
|
5026
|
+
: [];
|
|
5027
|
+
const colLabel = formI18n.colLabel;
|
|
5028
|
+
const [dialogOpen, setDialogOpen] = useState(false);
|
|
5029
|
+
const [failedImageIds, setFailedImageIds] = useState(new Set());
|
|
5030
|
+
const { onFetchFiles, enableMediaLibrary = false, filterImageOnly = false, } = filePicker || {};
|
|
5031
|
+
const showMediaLibrary = enableMediaLibrary && !!onFetchFiles;
|
|
5032
|
+
const handleImageError = (fileIdentifier) => {
|
|
5033
|
+
setFailedImageIds((prev) => new Set(prev).add(fileIdentifier));
|
|
5034
|
+
};
|
|
5035
|
+
const handleMediaLibrarySelect = (fileId) => {
|
|
5036
|
+
const newFiles = [...currentFiles, fileId];
|
|
5037
|
+
setValue(colLabel, newFiles);
|
|
5038
|
+
};
|
|
5039
|
+
const handleRemove = (index) => {
|
|
5040
|
+
const newFiles = currentFiles.filter((_, i) => i !== index);
|
|
5041
|
+
setValue(colLabel, newFiles);
|
|
5042
|
+
};
|
|
5043
|
+
const isFileObject = (value) => {
|
|
5044
|
+
return value instanceof File;
|
|
5045
|
+
};
|
|
5046
|
+
const getFileIdentifier = (file, index) => {
|
|
5047
|
+
if (isFileObject(file)) {
|
|
5048
|
+
return `${file.name}-${file.size}-${index}`;
|
|
5049
|
+
}
|
|
5050
|
+
return file;
|
|
5051
|
+
};
|
|
5052
|
+
const getFileName = (file) => {
|
|
5053
|
+
if (isFileObject(file)) {
|
|
5054
|
+
return file.name;
|
|
5055
|
+
}
|
|
5056
|
+
return typeof file === 'string' ? file : 'Unknown file';
|
|
5057
|
+
};
|
|
5058
|
+
const getFileSize = (file) => {
|
|
5059
|
+
if (isFileObject(file)) {
|
|
5060
|
+
return file.size;
|
|
5061
|
+
}
|
|
5062
|
+
return undefined;
|
|
5063
|
+
};
|
|
5064
|
+
const isImageFile = (file) => {
|
|
5065
|
+
if (isFileObject(file)) {
|
|
5066
|
+
return file.type.startsWith('image/');
|
|
5067
|
+
}
|
|
5068
|
+
if (typeof file === 'string') {
|
|
5069
|
+
return /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/i.test(file);
|
|
5070
|
+
}
|
|
5071
|
+
return false;
|
|
5072
|
+
};
|
|
5073
|
+
const getImageUrl = (file) => {
|
|
5074
|
+
if (isFileObject(file)) {
|
|
5075
|
+
return URL.createObjectURL(file);
|
|
5076
|
+
}
|
|
5077
|
+
return undefined;
|
|
5078
|
+
};
|
|
5079
|
+
return (jsxs(Field, { label: formI18n.label(), required: isRequired, alignItems: 'stretch', gridColumn,
|
|
5080
|
+
gridRow, errorText: errors[`${colLabel}`] ? formI18n.required() : undefined, invalid: !!errors[colLabel], children: [jsxs(VStack, { align: "stretch", gap: 2, children: [jsx(FileDropzone, { onDrop: ({ files }) => {
|
|
5081
|
+
const newFiles = files.filter(({ name }) => !currentFiles.some((cur) => {
|
|
5082
|
+
if (isFileObject(cur)) {
|
|
5083
|
+
return cur.name === name;
|
|
5084
|
+
}
|
|
5085
|
+
return false;
|
|
5086
|
+
}));
|
|
5087
|
+
setValue(colLabel, [...currentFiles, ...newFiles]);
|
|
5088
|
+
}, placeholder: filePickerLabels?.fileDropzone ?? formI18n.t('fileDropzone') }), showMediaLibrary && (jsx(Button$1, { variant: "outline", onClick: () => setDialogOpen(true), borderColor: "border.default", bg: "bg.panel", _hover: { bg: 'bg.muted' }, children: filePickerLabels?.browseLibrary ??
|
|
5089
|
+
formI18n.t('browse_library') ??
|
|
5090
|
+
'Browse from Library' }))] }), showMediaLibrary && (jsx(FilePickerDialog, { open: dialogOpen, onClose: () => setDialogOpen(false), onSelect: handleMediaLibrarySelect, title: filePickerLabels?.dialogTitle ??
|
|
5091
|
+
formI18n.t('dialog_title') ??
|
|
5092
|
+
'Select File', filterImageOnly: filterImageOnly, onFetchFiles: onFetchFiles, labels: filePickerLabels, translate: formI18n.t, colLabel: colLabel })), jsx(Flex, { flexFlow: 'column', gap: 1, children: currentFiles.map((file, index) => {
|
|
5093
|
+
const fileIdentifier = getFileIdentifier(file, index);
|
|
5094
|
+
const fileName = getFileName(file);
|
|
5095
|
+
const fileSize = getFileSize(file);
|
|
5096
|
+
const isImage = isImageFile(file);
|
|
5097
|
+
const imageUrl = getImageUrl(file);
|
|
5098
|
+
const imageFailed = failedImageIds.has(fileIdentifier);
|
|
5099
|
+
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: {
|
|
5100
|
+
borderColor: 'colorPalette.300',
|
|
5101
|
+
bg: 'bg.muted',
|
|
5102
|
+
}, 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 && imageUrl && !imageFailed ? (jsx(Image, { src: imageUrl, alt: fileName, boxSize: "60px", objectFit: "cover", borderRadius: "md", onError: () => handleImageError(fileIdentifier) })) : isImage && (imageFailed || !imageUrl) ? (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: fileName }), fileSize !== undefined && (jsx(Text, { fontSize: "xs", color: "fg.muted", children: formatBytes(fileSize) }))] }), jsx(Icon, { as: TiDeleteOutline, boxSize: 5, color: "fg.muted" })] }) }, fileIdentifier));
|
|
4832
5103
|
}) })] }));
|
|
4833
5104
|
};
|
|
4834
5105
|
|
|
@@ -5952,59 +6223,62 @@ const DateTimePicker = ({ column, schema, prefix, }) => {
|
|
|
5952
6223
|
const SchemaRenderer = ({ schema, prefix, column, }) => {
|
|
5953
6224
|
const colSchema = schema;
|
|
5954
6225
|
const { type, variant, properties: innerProperties, foreign_key, format, items, } = schema;
|
|
5955
|
-
if (variant ===
|
|
6226
|
+
if (variant === 'custom-input') {
|
|
5956
6227
|
return jsx(CustomInput, { schema: colSchema, prefix, column });
|
|
5957
6228
|
}
|
|
5958
|
-
if (type ===
|
|
6229
|
+
if (type === 'string') {
|
|
5959
6230
|
if ((schema.enum ?? []).length > 0) {
|
|
5960
6231
|
return jsx(EnumPicker, { schema: colSchema, prefix, column });
|
|
5961
6232
|
}
|
|
5962
|
-
if (variant ===
|
|
6233
|
+
if (variant === 'id-picker') {
|
|
5963
6234
|
idPickerSanityCheck(column, foreign_key);
|
|
5964
6235
|
return jsx(IdPicker, { schema: colSchema, prefix, column });
|
|
5965
6236
|
}
|
|
5966
|
-
if (format ===
|
|
6237
|
+
if (format === 'date') {
|
|
5967
6238
|
return jsx(DatePicker, { schema: colSchema, prefix, column });
|
|
5968
6239
|
}
|
|
5969
|
-
if (format ===
|
|
6240
|
+
if (format === 'time') {
|
|
5970
6241
|
return jsx(TimePicker, { schema: colSchema, prefix, column });
|
|
5971
6242
|
}
|
|
5972
|
-
if (format ===
|
|
6243
|
+
if (format === 'date-time') {
|
|
5973
6244
|
return jsx(DateTimePicker, { schema: colSchema, prefix, column });
|
|
5974
6245
|
}
|
|
5975
|
-
if (variant ===
|
|
6246
|
+
if (variant === 'text-area') {
|
|
5976
6247
|
return jsx(TextAreaInput, { schema: colSchema, prefix, column });
|
|
5977
6248
|
}
|
|
5978
6249
|
return jsx(StringInputField, { schema: colSchema, prefix, column });
|
|
5979
6250
|
}
|
|
5980
|
-
if (type ===
|
|
6251
|
+
if (type === 'number' || type === 'integer') {
|
|
5981
6252
|
return jsx(NumberInputField, { schema: colSchema, prefix, column });
|
|
5982
6253
|
}
|
|
5983
|
-
if (type ===
|
|
6254
|
+
if (type === 'boolean') {
|
|
5984
6255
|
return jsx(BooleanPicker, { schema: colSchema, prefix, column });
|
|
5985
6256
|
}
|
|
5986
|
-
if (type ===
|
|
6257
|
+
if (type === 'object') {
|
|
5987
6258
|
if (innerProperties) {
|
|
5988
6259
|
return jsx(ObjectInput, { schema: colSchema, prefix, column });
|
|
5989
6260
|
}
|
|
5990
6261
|
return jsx(RecordInput$1, { schema: colSchema, prefix, column });
|
|
5991
6262
|
}
|
|
5992
|
-
if (type ===
|
|
5993
|
-
if (variant ===
|
|
6263
|
+
if (type === 'array') {
|
|
6264
|
+
if (variant === 'id-picker') {
|
|
5994
6265
|
idPickerSanityCheck(column, foreign_key);
|
|
5995
6266
|
return (jsx(IdPicker, { schema: colSchema, prefix, column, isMultiple: true }));
|
|
5996
6267
|
}
|
|
5997
|
-
if (variant ===
|
|
6268
|
+
if (variant === 'tag-picker') {
|
|
5998
6269
|
return jsx(TagPicker, { schema: colSchema, prefix, column });
|
|
5999
6270
|
}
|
|
6000
|
-
if (variant ===
|
|
6271
|
+
if (variant === 'file-picker') {
|
|
6001
6272
|
return jsx(FilePicker, { schema: colSchema, prefix, column });
|
|
6002
6273
|
}
|
|
6003
|
-
if (variant ===
|
|
6274
|
+
if (variant === 'date-range') {
|
|
6275
|
+
return jsx(DateRangePicker, { schema: colSchema, prefix, column });
|
|
6276
|
+
}
|
|
6277
|
+
if (variant === 'enum-picker') {
|
|
6004
6278
|
const { items } = colSchema;
|
|
6005
6279
|
const { enum: enumItems } = items;
|
|
6006
6280
|
const enumSchema = {
|
|
6007
|
-
type:
|
|
6281
|
+
type: 'string',
|
|
6008
6282
|
enum: enumItems,
|
|
6009
6283
|
};
|
|
6010
6284
|
return (jsx(EnumPicker, { isMultiple: true, schema: enumSchema, prefix, column }));
|
|
@@ -6014,7 +6288,7 @@ const SchemaRenderer = ({ schema, prefix, column, }) => {
|
|
|
6014
6288
|
}
|
|
6015
6289
|
return jsx(Text, { children: `array ${column}` });
|
|
6016
6290
|
}
|
|
6017
|
-
if (type ===
|
|
6291
|
+
if (type === 'null') {
|
|
6018
6292
|
return jsx(Text, { children: `null ${column}` });
|
|
6019
6293
|
}
|
|
6020
6294
|
return jsx(Text, { children: "missing type" });
|
|
@@ -3,7 +3,7 @@ import { JSONSchema7 } from 'json-schema';
|
|
|
3
3
|
import { Dispatch, ReactNode, SetStateAction } from 'react';
|
|
4
4
|
import { FieldValues, Resolver } from 'react-hook-form';
|
|
5
5
|
import { UseTranslationResponse } from 'react-i18next';
|
|
6
|
-
import { DateTimePickerLabels, IdPickerLabels, EnumPickerLabels } from './components/types/CustomJSONSchema7';
|
|
6
|
+
import { DateTimePickerLabels, IdPickerLabels, EnumPickerLabels, FilePickerLabels } from './components/types/CustomJSONSchema7';
|
|
7
7
|
export interface SchemaFormContext<TData extends FieldValues> {
|
|
8
8
|
schema: JSONSchema7;
|
|
9
9
|
serverUrl: string;
|
|
@@ -43,6 +43,7 @@ export interface SchemaFormContext<TData extends FieldValues> {
|
|
|
43
43
|
dateTimePickerLabels?: DateTimePickerLabels;
|
|
44
44
|
idPickerLabels?: IdPickerLabels;
|
|
45
45
|
enumPickerLabels?: EnumPickerLabels;
|
|
46
|
+
filePickerLabels?: FilePickerLabels;
|
|
46
47
|
ajvResolver: Resolver<FieldValues>;
|
|
47
48
|
}
|
|
48
49
|
export declare const SchemaFormContext: import("react").Context<SchemaFormContext<unknown>>;
|
|
@@ -4,7 +4,7 @@ import { JSONSchema7 } from 'json-schema';
|
|
|
4
4
|
import { Dispatch, ReactNode, SetStateAction } from 'react';
|
|
5
5
|
import { FieldValues, SubmitHandler, UseFormReturn } from 'react-hook-form';
|
|
6
6
|
import { UseTranslationResponse } from 'react-i18next';
|
|
7
|
-
import { CustomJSONSchema7, DateTimePickerLabels, IdPickerLabels, EnumPickerLabels } from '../types/CustomJSONSchema7';
|
|
7
|
+
import { CustomJSONSchema7, DateTimePickerLabels, IdPickerLabels, EnumPickerLabels, FilePickerLabels } from '../types/CustomJSONSchema7';
|
|
8
8
|
export interface FormRootProps<TData extends FieldValues> {
|
|
9
9
|
schema: CustomJSONSchema7;
|
|
10
10
|
serverUrl: string;
|
|
@@ -32,6 +32,7 @@ export interface FormRootProps<TData extends FieldValues> {
|
|
|
32
32
|
dateTimePickerLabels?: DateTimePickerLabels;
|
|
33
33
|
idPickerLabels?: IdPickerLabels;
|
|
34
34
|
enumPickerLabels?: EnumPickerLabels;
|
|
35
|
+
filePickerLabels?: FilePickerLabels;
|
|
35
36
|
}
|
|
36
37
|
export interface CustomJSONSchema7Definition extends JSONSchema7 {
|
|
37
38
|
variant: string;
|
|
@@ -48,4 +49,4 @@ export declare const idPickerSanityCheck: (column: string, foreign_key?: {
|
|
|
48
49
|
column?: string | undefined;
|
|
49
50
|
display_column?: string | undefined;
|
|
50
51
|
} | undefined) => void;
|
|
51
|
-
export declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, serverUrl, translate, children, order, ignore, include, onSubmit, rowNumber, requestOptions, getUpdatedData, customErrorRenderer, customSuccessRenderer, displayConfig, requireConfirmation, dateTimePickerLabels, idPickerLabels, enumPickerLabels, }: FormRootProps<TData>) => import("react/jsx-runtime").JSX.Element;
|
|
52
|
+
export declare const FormRoot: <TData extends FieldValues>({ schema, idMap, setIdMap, form, serverUrl, translate, children, order, ignore, include, onSubmit, rowNumber, requestOptions, getUpdatedData, customErrorRenderer, customSuccessRenderer, displayConfig, requireConfirmation, dateTimePickerLabels, idPickerLabels, enumPickerLabels, filePickerLabels, }: FormRootProps<TData>) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -29,6 +29,17 @@ export interface EnumPickerLabels {
|
|
|
29
29
|
emptySearchResult?: string;
|
|
30
30
|
initialResults?: string;
|
|
31
31
|
}
|
|
32
|
+
export interface FilePickerLabels {
|
|
33
|
+
fileDropzone?: string;
|
|
34
|
+
browseLibrary?: string;
|
|
35
|
+
dialogTitle?: string;
|
|
36
|
+
searchPlaceholder?: string;
|
|
37
|
+
loading?: string;
|
|
38
|
+
loadingFailed?: string;
|
|
39
|
+
noFilesFound?: string;
|
|
40
|
+
cancel?: string;
|
|
41
|
+
select?: string;
|
|
42
|
+
}
|
|
32
43
|
export interface CustomJSONSchema7 extends JSONSchema7 {
|
|
33
44
|
gridColumn?: string;
|
|
34
45
|
gridRow?: string;
|
|
@@ -55,9 +66,23 @@ export interface CustomJSONSchema7 extends JSONSchema7 {
|
|
|
55
66
|
formatOptions?: Intl.NumberFormatOptions;
|
|
56
67
|
numberStorageType?: 'string' | 'number';
|
|
57
68
|
errorMessages?: Partial<Record<ValidationErrorType | string, string>>;
|
|
69
|
+
filePicker?: FilePickerProps;
|
|
58
70
|
}
|
|
59
71
|
export interface TagPickerProps {
|
|
60
72
|
column: string;
|
|
61
73
|
schema: CustomJSONSchema7;
|
|
62
74
|
prefix: string;
|
|
63
75
|
}
|
|
76
|
+
export interface FilePickerMediaFile {
|
|
77
|
+
id: string;
|
|
78
|
+
name: string;
|
|
79
|
+
url?: string;
|
|
80
|
+
size?: string | number;
|
|
81
|
+
comment?: string;
|
|
82
|
+
type?: string;
|
|
83
|
+
}
|
|
84
|
+
export interface FilePickerProps {
|
|
85
|
+
onFetchFiles?: (search: string) => Promise<FilePickerMediaFile[]>;
|
|
86
|
+
enableMediaLibrary?: boolean;
|
|
87
|
+
filterImageOnly?: boolean;
|
|
88
|
+
}
|