@firecms/data_import_export 3.0.0-canary.7 → 3.0.0-canary.70

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.
Files changed (40) hide show
  1. package/LICENSE +113 -21
  2. package/README.md +1 -1
  3. package/dist/components/DataNewPropertiesMapping.d.ts +3 -5
  4. package/dist/components/ImportFileUpload.d.ts +1 -1
  5. package/dist/export_import/BasicExportAction.d.ts +7 -0
  6. package/dist/export_import/ExportCollectionAction.d.ts +2 -1
  7. package/dist/export_import/ImportCollectionAction.d.ts +3 -1
  8. package/dist/export_import/export.d.ts +15 -4
  9. package/dist/export_import/index.d.ts +4 -0
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.es.js +954 -554
  12. package/dist/index.es.js.map +1 -1
  13. package/dist/index.umd.js +2 -2
  14. package/dist/index.umd.js.map +1 -1
  15. package/dist/types/column_mapping.d.ts +5 -7
  16. package/dist/useImportExportPlugin.d.ts +2 -1
  17. package/dist/utils/data.d.ts +3 -10
  18. package/dist/utils/file_headers.d.ts +1 -0
  19. package/dist/utils/file_to_json.d.ts +6 -1
  20. package/dist/utils/get_properties_mapping.d.ts +0 -3
  21. package/dist/utils/index.d.ts +0 -1
  22. package/package.json +25 -23
  23. package/src/components/DataNewPropertiesMapping.tsx +155 -41
  24. package/src/components/ImportFileUpload.tsx +12 -4
  25. package/src/components/ImportNewPropertyFieldPreview.tsx +7 -2
  26. package/src/components/ImportSaveInProgress.tsx +24 -2
  27. package/src/export_import/BasicExportAction.tsx +147 -0
  28. package/src/export_import/ExportCollectionAction.tsx +50 -13
  29. package/src/export_import/ImportCollectionAction.tsx +54 -33
  30. package/src/export_import/export.ts +64 -30
  31. package/src/export_import/index.ts +4 -0
  32. package/src/hooks/useImportConfig.tsx +6 -0
  33. package/src/index.ts +1 -0
  34. package/src/types/column_mapping.ts +6 -6
  35. package/src/useImportExportPlugin.tsx +4 -3
  36. package/src/utils/data.ts +50 -127
  37. package/src/utils/file_headers.ts +90 -0
  38. package/src/utils/file_to_json.ts +33 -15
  39. package/src/utils/get_properties_mapping.ts +63 -59
  40. package/src/utils/index.ts +0 -1
@@ -1,11 +1,4 @@
1
- import { DataType, Entity, Properties } from "@firecms/core";
2
- type DataTypeMapping = {
3
- from: DataType;
4
- fromSubtype?: DataType;
5
- to: DataType;
6
- toSubtype?: DataType;
7
- };
8
- export declare function convertDataToEntity(data: Record<any, any>, idColumn: string | undefined, headersMapping: Record<string, string | null>, properties: Properties, propertiesMapping: Record<string, DataTypeMapping>, path: string): Entity<any>;
1
+ import { Entity, Properties, PropertyOrBuilder } from "@firecms/core";
2
+ export declare function convertDataToEntity(data: Record<any, any>, idColumn: string | undefined, headersMapping: Record<string, string | null>, properties: Properties, path: string, defaultValues: Record<string, any>): Entity<any>;
9
3
  export declare function flattenEntry(obj: any, parent?: string): any;
10
- export declare function processValueMapping(value: any, valueMapping?: DataTypeMapping): any;
11
- export {};
4
+ export declare function processValueMapping(value: any, property?: PropertyOrBuilder): any;
@@ -0,0 +1 @@
1
+ export declare function getXLSXHeaders(sheet: any): any[];
@@ -1,4 +1,8 @@
1
- export declare function convertFileToJson(file: File): Promise<object[]>;
1
+ type ConversionResult = {
2
+ data: object[];
3
+ propertiesOrder: string[];
4
+ };
5
+ export declare function convertFileToJson(file: File): Promise<ConversionResult>;
2
6
  /**
3
7
  * Take an object with keys of type `address.street`, `address.city` and
4
8
  * convert it to an object with nested objects like `{ address: { street: ..., city: ... } }`
@@ -9,3 +13,4 @@ export declare function unflattenObject(flatObj: {
9
13
  }): {
10
14
  [key: string]: any;
11
15
  };
16
+ export {};
@@ -1,3 +0,0 @@
1
- import { Properties } from "@firecms/core";
2
- import { DataTypeMapping } from "../types";
3
- export declare function getPropertiesMapping(originProperties: Properties, newProperties: Properties): Record<string, DataTypeMapping>;
@@ -1,4 +1,3 @@
1
1
  export * from "./file_to_json";
2
2
  export * from "./data";
3
3
  export * from "./get_import_inference_type";
4
- export * from "./get_properties_mapping";
package/package.json CHANGED
@@ -1,29 +1,31 @@
1
1
  {
2
2
  "name": "@firecms/data_import_export",
3
3
  "type": "module",
4
- "version": "3.0.0-canary.7",
4
+ "version": "3.0.0-canary.70",
5
5
  "access": "public",
6
6
  "main": "./dist/index.umd.js",
7
7
  "module": "./dist/index.es.js",
8
8
  "types": "./dist/index.d.ts",
9
9
  "source": "src/index.ts",
10
10
  "dependencies": {
11
- "@firecms/core": "^3.0.0-canary.7",
12
- "@firecms/schema_inference": "^3.0.0-canary.7",
11
+ "@firecms/core": "^3.0.0-canary.70",
12
+ "@firecms/formex": "^3.0.0-canary.70",
13
+ "@firecms/schema_inference": "^3.0.0-canary.70",
14
+ "@firecms/ui": "^3.0.0-canary.70",
13
15
  "xlsx": "^0.18.5"
14
16
  },
15
17
  "peerDependencies": {
16
18
  "firebase": "^10.7.1",
17
- "react": "^18.2.0",
18
- "react-dom": "^18.2.0",
19
- "react-router": "^6.12.0",
20
- "react-router-dom": "^6.12.0"
19
+ "react": "^18.3.1",
20
+ "react-dom": "^18.3.1",
21
+ "react-router": "^6.22.0",
22
+ "react-router-dom": "^6.22.0"
21
23
  },
22
24
  "exports": {
23
25
  ".": {
24
26
  "import": "./dist/index.es.js",
25
27
  "require": "./dist/index.umd.js",
26
- "types": "./dist/src/index.d.ts"
28
+ "types": "./dist/index.d.ts"
27
29
  },
28
30
  "./package.json": "./package.json"
29
31
  },
@@ -54,25 +56,25 @@
54
56
  },
55
57
  "devDependencies": {
56
58
  "@jest/globals": "^29.7.0",
57
- "@testing-library/jest-dom": "^5.17.0",
59
+ "@testing-library/jest-dom": "^6.4.5",
58
60
  "@types/jest": "^29.5.12",
59
- "@types/react": "^18.2.54",
60
- "@types/react-dom": "^18.2.18",
61
- "@typescript-eslint/eslint-plugin": "^5.62.0",
62
- "@typescript-eslint/parser": "^5.62.0",
63
- "@vitejs/plugin-react": "^4.2.1",
61
+ "@types/react": "^18.3.3",
62
+ "@types/react-dom": "^18.3.0",
63
+ "@typescript-eslint/eslint-plugin": "^7.11.0",
64
+ "@typescript-eslint/parser": "^7.11.0",
65
+ "@vitejs/plugin-react": "^4.3.0",
64
66
  "babel-jest": "^29.7.0",
65
- "eslint": "^8.56.0",
67
+ "eslint": "^8.57.0",
66
68
  "eslint-config-standard": "^17.1.0",
67
69
  "eslint-plugin-import": "^2.29.1",
68
- "eslint-plugin-n": "^15.7.0",
69
- "eslint-plugin-promise": "^6.1.1",
70
- "eslint-plugin-react": "^7.33.2",
71
- "eslint-plugin-react-hooks": "^4.6.0",
70
+ "eslint-plugin-n": "^16.6.2",
71
+ "eslint-plugin-promise": "^6.2.0",
72
+ "eslint-plugin-react": "^7.34.2",
73
+ "eslint-plugin-react-hooks": "^4.6.2",
72
74
  "jest": "^29.7.0",
73
- "ts-jest": "^29.1.2",
74
- "typescript": "^5.3.3",
75
- "vite": "^5.1.1",
75
+ "ts-jest": "^29.1.4",
76
+ "typescript": "^5.4.5",
77
+ "vite": "^5.2.12",
76
78
  "vite-plugin-fonts": "^0.7.0"
77
79
  },
78
80
  "jest": {
@@ -99,5 +101,5 @@
99
101
  "publishConfig": {
100
102
  "access": "public"
101
103
  },
102
- "gitHead": "30ef9b29f5f624fca64e11ffda6001a98f998102"
104
+ "gitHead": "8782754f770575b610bb23f89d284841bae03bdb"
103
105
  }
@@ -1,6 +1,9 @@
1
- import { getPropertyInPath, Property, } from "@firecms/core";
1
+ import { getPropertyInPath, PropertiesOrBuilders, Property } from "@firecms/core";
2
2
  import {
3
+ BooleanSwitchWithLabel,
3
4
  ChevronRightIcon,
5
+ DateTimeField,
6
+ ExpandablePanel,
4
7
  Select,
5
8
  SelectItem,
6
9
  Table,
@@ -8,15 +11,15 @@ import {
8
11
  TableCell,
9
12
  TableHeader,
10
13
  TableRow,
14
+ TextField,
11
15
  Typography
12
16
  } from "@firecms/ui";
17
+ import { ImportConfig } from "../types";
18
+ import { getIn, setIn } from "@firecms/formex";
13
19
 
14
20
  export interface DataPropertyMappingProps {
15
- idColumn?: string;
16
- headersMapping: Record<string, string | null>;
17
- originProperties: Record<string, Property>;
21
+ importConfig: ImportConfig;
18
22
  destinationProperties: Record<string, Property>;
19
- onIdPropertyChanged: (value: string) => void;
20
23
  buildPropertyView?: (props: {
21
24
  isIdColumn: boolean,
22
25
  property: Property | null,
@@ -26,20 +29,24 @@ export interface DataPropertyMappingProps {
26
29
  }
27
30
 
28
31
  export function DataNewPropertiesMapping({
29
- idColumn,
30
- headersMapping,
31
- originProperties,
32
+ importConfig,
32
33
  destinationProperties,
33
- onIdPropertyChanged,
34
- buildPropertyView,
34
+ buildPropertyView
35
35
  }: DataPropertyMappingProps) {
36
36
 
37
+ const headersMapping = importConfig.headersMapping;
38
+ const headingsOrder = importConfig.headingsOrder;
39
+ const idColumn = importConfig.idColumn;
40
+ const originProperties = importConfig.originProperties;
41
+
37
42
  return (
38
43
  <>
39
44
 
40
45
  <IdSelectField idColumn={idColumn}
41
46
  headersMapping={headersMapping}
42
- onChange={onIdPropertyChanged}/>
47
+ onChange={(value) => importConfig.setIdColumn(value ?? undefined)}/>
48
+
49
+ <div className={"h-4"}/>
43
50
 
44
51
  <Table style={{
45
52
  tableLayout: "fixed"
@@ -51,51 +58,113 @@ export function DataNewPropertiesMapping({
51
58
  <TableCell header={true}>
52
59
  </TableCell>
53
60
  <TableCell header={true} style={{ width: "75%" }}>
54
- Property
61
+ Map to Property
55
62
  </TableCell>
56
63
  </TableHeader>
57
64
  <TableBody>
58
65
  {destinationProperties &&
59
- Object.entries(headersMapping)
60
- .map(([importKey, mappedKey]) => {
61
- const propertyKey = headersMapping[importKey];
62
- const property = mappedKey ? getPropertyInPath(destinationProperties, mappedKey) as Property : null;
66
+ headingsOrder.map((importKey) => {
67
+ const mappedKey = headersMapping[importKey];
68
+ const propertyKey = headersMapping[importKey];
69
+ const property = mappedKey ? getPropertyInPath(destinationProperties, mappedKey) as Property : null;
63
70
 
64
- const originProperty = getPropertyInPath(originProperties, importKey) as Property | undefined;
65
- const originDataType = originProperty ? (originProperty.dataType === "array" && typeof originProperty.of === "object"
66
- ? `${originProperty.dataType} - ${(originProperty.of as Property).dataType}`
67
- : originProperty.dataType)
68
- : undefined;
69
- return <TableRow key={importKey} style={{ height: "90px" }}>
71
+ const originProperty = getPropertyInPath(originProperties, importKey) as Property | undefined;
72
+ const originDataType = originProperty
73
+ ? (originProperty.dataType === "array" && typeof originProperty.of === "object"
74
+ ? `${originProperty.dataType} - ${(originProperty.of as Property).dataType}`
75
+ : originProperty.dataType)
76
+ : undefined;
77
+ return <TableRow key={importKey} style={{ height: "90px" }}>
78
+ <TableCell style={{ width: "20%" }}>
79
+ <Typography variant={"body2"}>{importKey}</Typography>
80
+ {originProperty && <Typography
81
+ variant={"caption"}
82
+ color={"secondary"}
83
+ >{originDataType}</Typography>}
84
+ </TableCell>
85
+ <TableCell>
86
+ <ChevronRightIcon/>
87
+ </TableCell>
88
+ <TableCell className={importKey === idColumn ? "text-center" : undefined}
89
+ style={{ width: "75%" }}>
90
+ {buildPropertyView?.({
91
+ isIdColumn: importKey === idColumn,
92
+ property,
93
+ propertyKey,
94
+ importKey
95
+ })}
96
+ </TableCell>
97
+ </TableRow>;
98
+ }
99
+ )}
100
+ </TableBody>
101
+ </Table>
102
+
103
+ <ExpandablePanel title="Default values" initiallyExpanded={false} className={"p-4 mt-4"}>
104
+
105
+ <div className={"text-sm text-slate-500 dark:text-slate-300 font-medium ml-3.5 mb-1"}>
106
+ You can select a default value for unmapped columns and empty values:
107
+ </div>
108
+ <Table style={{
109
+ tableLayout: "fixed"
110
+ }}>
111
+ <TableHeader>
112
+ <TableCell header={true} style={{ width: "30%" }}>
113
+ Property
114
+ </TableCell>
115
+ <TableCell header={true}>
116
+ </TableCell>
117
+ <TableCell header={true} style={{ width: "65%" }}>
118
+ Default value
119
+ </TableCell>
120
+ </TableHeader>
121
+ <TableBody>
122
+ {destinationProperties &&
123
+ getAllPropertyKeys(destinationProperties).map((key) => {
124
+ const property = getPropertyInPath(destinationProperties, key);
125
+ if (typeof property !== "object" || property === null) {
126
+ return null;
127
+ }
128
+ if (!["number", "string", "boolean", "map"].includes(property.dataType)) {
129
+ return null;
130
+ }
131
+ return <TableRow key={key} style={{ height: "70px" }}>
70
132
  <TableCell style={{ width: "20%" }}>
71
- <Typography variant={"body2"}>{importKey}</Typography>
72
- {originProperty && <Typography
73
- variant={"caption"}
74
- color={"secondary"}
75
- >{originDataType}</Typography>}
133
+ <Typography variant={"body2"}>{key}</Typography>
76
134
  </TableCell>
77
135
  <TableCell>
78
136
  <ChevronRightIcon/>
79
137
  </TableCell>
80
- <TableCell className={importKey === idColumn ? "text-center" : undefined}
138
+ <TableCell className={key === idColumn ? "text-center" : undefined}
81
139
  style={{ width: "75%" }}>
82
- {buildPropertyView?.({
83
- isIdColumn: importKey === idColumn,
84
- property,
85
- propertyKey,
86
- importKey
87
- })
88
- }
140
+ <DefaultValuesField property={property}
141
+ defaultValue={getIn(importConfig.defaultValues, key)}
142
+ onValueChange={(value) => {
143
+ const newValues = setIn(importConfig.defaultValues, key, value);
144
+ importConfig.setDefaultValues(newValues);
145
+ }}/>
89
146
  </TableCell>
90
147
  </TableRow>;
91
148
  }
92
149
  )}
93
- </TableBody>
94
- </Table>
150
+ </TableBody>
151
+ </Table>
152
+ </ExpandablePanel>
95
153
  </>
96
154
  );
97
155
  }
98
156
 
157
+ function getAllPropertyKeys(properties: PropertiesOrBuilders, currentKey?: string): string[] {
158
+ return Object.entries(properties).reduce((acc, [key, property]) => {
159
+ const accumulatedKey = currentKey ? `${currentKey}.${key}` : key;
160
+ if (typeof property !== "function" && property.dataType === "map" && property.properties) {
161
+ const childProperties = getAllPropertyKeys(property.properties, accumulatedKey);
162
+ return [...acc, ...childProperties];
163
+ }
164
+ return [...acc, accumulatedKey];
165
+ }, [] as string[]);
166
+ }
167
+
99
168
  function IdSelectField({
100
169
  idColumn,
101
170
  headersMapping,
@@ -103,25 +172,70 @@ function IdSelectField({
103
172
  }: {
104
173
  idColumn?: string,
105
174
  headersMapping: Record<string, string | null>;
106
- onChange: (value: string) => void
175
+ onChange: (value: string | null) => void
107
176
  }) {
108
177
  return <div>
109
178
  <Select
110
179
  size={"small"}
111
180
  value={idColumn ?? ""}
112
181
  onChange={(event) => {
113
- onChange(event.target.value as string);
182
+ const value = event.target.value;
183
+ onChange(value === "__none__" ? null : value);
114
184
  }}
185
+ placeholder={"Autogenerate ID"}
115
186
  renderValue={(value) => {
116
187
  return <Typography variant={"body2"}>
117
- {value !== "" ? value : "Autogenerate ID"}
188
+ {value !== "__none__" ? value : "Autogenerate ID"}
118
189
  </Typography>;
119
190
  }}
120
191
  label={"Column that will be used as ID for each document"}>
121
- <SelectItem value={""}>Autogenerate ID</SelectItem>
192
+ <SelectItem value={"__none__"}>Autogenerate ID</SelectItem>
122
193
  {Object.entries(headersMapping).map(([key, value]) => {
123
194
  return <SelectItem key={key} value={key}>{key}</SelectItem>;
124
195
  })}
125
196
  </Select>
126
197
  </div>;
127
198
  }
199
+
200
+ function DefaultValuesField({
201
+ property,
202
+ onValueChange,
203
+ defaultValue
204
+ }: { property: Property, onValueChange: (value: any) => void, defaultValue?: any }) {
205
+ if (property.dataType === "string") {
206
+ return <TextField size={"small"}
207
+ placeholder={"Default value"}
208
+ value={defaultValue ?? ""}
209
+ onChange={(event) => onValueChange(event.target.value)}/>;
210
+ } else if (property.dataType === "number") {
211
+ return <TextField size={"small"}
212
+ type={"number"}
213
+ value={defaultValue ?? ""}
214
+ placeholder={"Default value"}
215
+ onChange={(event) => onValueChange(event.target.value)}/>;
216
+ } else if (property.dataType === "boolean") {
217
+ return <BooleanSwitchWithLabel
218
+ value={defaultValue ?? null}
219
+ allowIndeterminate={true}
220
+ size={"small"}
221
+ onValueChange={(v: boolean | null) => onValueChange(v === null ? undefined : v)}
222
+ label={defaultValue === undefined
223
+ ? "Do not set value"
224
+ : defaultValue === true
225
+ ? "Set value to true"
226
+ : "Set value to false"}
227
+ />
228
+ } else if (property.dataType === "date") {
229
+ return <DateTimeField
230
+ mode={property.mode ?? "date"}
231
+ size={"small"}
232
+ value={defaultValue ?? undefined}
233
+ onChange={(dateValue: Date | undefined) => {
234
+ onValueChange(dateValue);
235
+ }}
236
+ clearable={true}
237
+ />
238
+ }
239
+
240
+ return null;
241
+ }
@@ -2,7 +2,9 @@ import { FileUpload, UploadIcon } from "@firecms/ui";
2
2
  import { convertFileToJson } from "../utils/file_to_json";
3
3
  import { useSnackbarController } from "@firecms/core";
4
4
 
5
- export function ImportFileUpload({ onDataAdded }: { onDataAdded: (data: object[]) => void }) {
5
+ export function ImportFileUpload({ onDataAdded }: {
6
+ onDataAdded: (data: object[], propertiesOrder?: string[]) => void
7
+ }) {
6
8
  const snackbarController = useSnackbarController();
7
9
  return <FileUpload
8
10
  accept={{
@@ -22,12 +24,18 @@ export function ImportFileUpload({ onDataAdded }: { onDataAdded: (data: object[]
22
24
  onFilesAdded={(files: File[]) => {
23
25
  if (files.length > 0) {
24
26
  convertFileToJson(files[0])
25
- .then((jsonData) => {
26
- onDataAdded(jsonData);
27
+ .then(({
28
+ data,
29
+ propertiesOrder
30
+ }) => {
31
+ onDataAdded(data, propertiesOrder);
27
32
  })
28
33
  .catch((error) => {
29
34
  console.error("Error parsing file", error);
30
- snackbarController.open({ type: "error", message: error.message });
35
+ snackbarController.open({
36
+ type: "error",
37
+ message: error.message
38
+ });
31
39
  });
32
40
  }
33
41
  }}/>
@@ -1,5 +1,11 @@
1
1
  import React from "react";
2
- import { ErrorBoundary, PropertyConfigBadge, getFieldConfig, Property, useCustomizationController } from "@firecms/core";
2
+ import {
3
+ ErrorBoundary,
4
+ getFieldConfig,
5
+ Property,
6
+ PropertyConfigBadge,
7
+ useCustomizationController
8
+ } from "@firecms/core";
3
9
  import { EditIcon, IconButton, TextField, } from "@firecms/ui";
4
10
 
5
11
  export function ImportNewPropertyFieldPreview({
@@ -49,7 +55,6 @@ export function ImportNewPropertyFieldPreview({
49
55
 
50
56
  </div>
51
57
 
52
-
53
58
  </div>
54
59
  </ErrorBoundary>
55
60
  }
@@ -1,5 +1,5 @@
1
1
  import { DataSource, Entity, EntityCollection, useDataSource } from "@firecms/core";
2
- import { CenteredView, CircularProgress, Typography, } from "@firecms/ui";
2
+ import { Button, CenteredView, CircularProgress, Typography, } from "@firecms/ui";
3
3
  import { useEffect, useRef, useState } from "react";
4
4
  import { ImportConfig } from "../types";
5
5
 
@@ -17,7 +17,7 @@ export function ImportSaveInProgress<C extends EntityCollection>
17
17
  onImportSuccess: (collection: C) => void
18
18
  }) {
19
19
 
20
- console.log("ImportSaveInProgress", path)
20
+ const [errorSaving, setErrorSaving] = useState<Error | undefined>(undefined);
21
21
  const dataSource = useDataSource();
22
22
 
23
23
  const savingRef = useRef<boolean>(false);
@@ -42,6 +42,9 @@ export function ImportSaveInProgress<C extends EntityCollection>
42
42
  ).then(() => {
43
43
  onImportSuccess(collection);
44
44
  savingRef.current = false;
45
+ }).catch((e) => {
46
+ setErrorSaving(e);
47
+ savingRef.current = false;
45
48
  });
46
49
  }
47
50
 
@@ -49,6 +52,25 @@ export function ImportSaveInProgress<C extends EntityCollection>
49
52
  save();
50
53
  }, []);
51
54
 
55
+ if (errorSaving) {
56
+ return (
57
+ <CenteredView className={"flex flex-col gap-4 items-center"}>
58
+ <Typography variant={"h6"}>
59
+ Error saving data
60
+ </Typography>
61
+
62
+ <Typography variant={"body2"} color={"error"}>
63
+ {errorSaving.message}
64
+ </Typography>
65
+ <Button
66
+ onClick={save}
67
+ variant={"outlined"}>
68
+ Retry
69
+ </Button>
70
+ </CenteredView>
71
+ );
72
+ }
73
+
52
74
  return (
53
75
  <CenteredView className={"flex flex-col gap-4 items-center"}>
54
76
  <CircularProgress/>
@@ -0,0 +1,147 @@
1
+ import React, { useCallback } from "react";
2
+
3
+ import { Entity, ResolvedProperties } from "@firecms/core";
4
+ import {
5
+ BooleanSwitchWithLabel,
6
+ Button,
7
+ cls,
8
+ Dialog,
9
+ DialogActions,
10
+ DialogContent,
11
+ focusedMixin,
12
+ GetAppIcon,
13
+ IconButton,
14
+ Tooltip,
15
+ Typography
16
+ } from "@firecms/ui";
17
+ import { downloadEntitiesExport } from "./export";
18
+
19
+ export type BasicExportActionProps = {
20
+ data: Entity<any>[];
21
+ properties: ResolvedProperties;
22
+ propertiesOrder?: string[];
23
+ }
24
+
25
+ export function BasicExportAction({
26
+ data,
27
+ properties,
28
+ propertiesOrder
29
+ }: BasicExportActionProps) {
30
+
31
+ const dateRef = React.useRef<Date>(new Date());
32
+ const [flattenArrays, setFlattenArrays] = React.useState<boolean>(true);
33
+ const [exportType, setExportType] = React.useState<"csv" | "json">("csv");
34
+ const [dateExportType, setDateExportType] = React.useState<"timestamp" | "string">("string");
35
+
36
+ const [open, setOpen] = React.useState(false);
37
+
38
+ const handleClickOpen = useCallback(() => {
39
+ setOpen(true);
40
+ }, [setOpen]);
41
+
42
+ const handleClose = useCallback(() => {
43
+ setOpen(false);
44
+ }, [setOpen]);
45
+
46
+ const onOkClicked = useCallback(() => {
47
+ downloadEntitiesExport({
48
+ data,
49
+ additionalData: [],
50
+ properties,
51
+ propertiesOrder,
52
+ name: "export.csv",
53
+ flattenArrays,
54
+ additionalHeaders: [],
55
+ exportType,
56
+ dateExportType
57
+ });
58
+ handleClose();
59
+ }, []);
60
+
61
+ return <>
62
+
63
+ <Tooltip title={"Export"}>
64
+ <IconButton color={"primary"} onClick={handleClickOpen}>
65
+ <GetAppIcon/>
66
+ </IconButton>
67
+ </Tooltip>
68
+
69
+ <Dialog
70
+ open={open}
71
+ onOpenChange={setOpen}
72
+ maxWidth={"xl"}>
73
+ <DialogContent className={"flex flex-col gap-4 my-4"}>
74
+
75
+ <Typography variant={"h6"}>Export data</Typography>
76
+
77
+ <div>Download the the content of this table as a CSV</div>
78
+
79
+ <div className={"flex flex-row gap-4"}>
80
+ <div className={"p-4 flex flex-col"}>
81
+ <div className="flex items-center">
82
+ <input id="radio-csv" type="radio" value="csv" name="exportType"
83
+ checked={exportType === "csv"}
84
+ onChange={() => setExportType("csv")}
85
+ className={cls(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
86
+ <label htmlFor="radio-csv"
87
+ className="p-2 text-sm font-medium text-gray-900 dark:text-slate-300">CSV</label>
88
+ </div>
89
+ <div className="flex items-center">
90
+ <input id="radio-json" type="radio" value="json" name="exportType"
91
+ checked={exportType === "json"}
92
+ onChange={() => setExportType("json")}
93
+ className={cls(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
94
+ <label htmlFor="radio-json"
95
+ className="p-2 text-sm font-medium text-gray-900 dark:text-slate-300">JSON</label>
96
+ </div>
97
+ </div>
98
+
99
+ <div className={"p-4 flex flex-col"}>
100
+ <div className="flex items-center">
101
+ <input id="radio-timestamp" type="radio" value="timestamp" name="dateExportType"
102
+ checked={dateExportType === "timestamp"}
103
+ onChange={() => setDateExportType("timestamp")}
104
+ className={cls(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
105
+ <label htmlFor="radio-timestamp"
106
+ className="p-2 text-sm font-medium text-gray-900 dark:text-slate-300">Dates as
107
+ timestamps ({dateRef.current.getTime()})</label>
108
+ </div>
109
+ <div className="flex items-center">
110
+ <input id="radio-string" type="radio" value="string" name="dateExportType"
111
+ checked={dateExportType === "string"}
112
+ onChange={() => setDateExportType("string")}
113
+ className={cls(focusedMixin, "w-4 text-primary-dark bg-gray-100 border-gray-300 dark:bg-gray-700 dark:border-gray-600")}/>
114
+ <label htmlFor="radio-string"
115
+ className="p-2 text-sm font-medium text-gray-900 dark:text-slate-300">Dates as
116
+ strings ({dateRef.current.toISOString()})</label>
117
+ </div>
118
+ </div>
119
+ </div>
120
+
121
+ <BooleanSwitchWithLabel
122
+ size={"small"}
123
+ disabled={exportType !== "csv"}
124
+ value={flattenArrays}
125
+ onValueChange={setFlattenArrays}
126
+ label={"Flatten arrays"}/>
127
+
128
+ </DialogContent>
129
+
130
+ <DialogActions>
131
+
132
+ <Button onClick={handleClose}
133
+ variant={"text"}>
134
+ Cancel
135
+ </Button>
136
+
137
+ <Button variant="filled"
138
+ onClick={onOkClicked}>
139
+ Download
140
+ </Button>
141
+
142
+ </DialogActions>
143
+
144
+ </Dialog>
145
+
146
+ </>;
147
+ }