@firecms/data_import_export 3.0.0-canary.41 → 3.0.0-canary.42

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.
@@ -14,6 +14,7 @@ import {
14
14
  export interface DataPropertyMappingProps {
15
15
  idColumn?: string;
16
16
  headersMapping: Record<string, string | null>;
17
+ headingsOrder: string[];
17
18
  originProperties: Record<string, Property>;
18
19
  destinationProperties: Record<string, Property>;
19
20
  onIdPropertyChanged: (value: string | null) => void;
@@ -28,12 +29,18 @@ export interface DataPropertyMappingProps {
28
29
  export function DataNewPropertiesMapping({
29
30
  idColumn,
30
31
  headersMapping,
32
+ headingsOrder,
31
33
  originProperties,
32
34
  destinationProperties,
33
35
  onIdPropertyChanged,
34
36
  buildPropertyView,
35
37
  }: DataPropertyMappingProps) {
36
38
 
39
+ console.log({
40
+ headersMapping,
41
+ headingsOrder,
42
+ })
43
+
37
44
  return (
38
45
  <>
39
46
 
@@ -41,6 +48,8 @@ export function DataNewPropertiesMapping({
41
48
  headersMapping={headersMapping}
42
49
  onChange={onIdPropertyChanged}/>
43
50
 
51
+ <div className={"h-4"}/>
52
+
44
53
  <Table style={{
45
54
  tableLayout: "fixed"
46
55
  }}>
@@ -51,45 +60,45 @@ export function DataNewPropertiesMapping({
51
60
  <TableCell header={true}>
52
61
  </TableCell>
53
62
  <TableCell header={true} style={{ width: "75%" }}>
54
- Property
63
+ Map to Property
55
64
  </TableCell>
56
65
  </TableHeader>
57
66
  <TableBody>
58
67
  {destinationProperties &&
59
- Object.entries(headersMapping)
60
- .map(([importKey, mappedKey]) => {
61
- const propertyKey = headersMapping[importKey];
62
- const property = mappedKey ? getPropertyInPath(destinationProperties, mappedKey) as Property : null;
68
+ headingsOrder.map((importKey) => {
69
+ const mappedKey = headersMapping[importKey];
70
+ const propertyKey = headersMapping[importKey];
71
+ const property = mappedKey ? getPropertyInPath(destinationProperties, mappedKey) as Property : null;
63
72
 
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" }}>
70
- <TableCell style={{ width: "20%" }}>
71
- <Typography variant={"body2"}>{importKey}</Typography>
72
- {originProperty && <Typography
73
- variant={"caption"}
74
- color={"secondary"}
75
- >{originDataType}</Typography>}
76
- </TableCell>
77
- <TableCell>
78
- <ChevronRightIcon/>
79
- </TableCell>
80
- <TableCell className={importKey === idColumn ? "text-center" : undefined}
81
- style={{ width: "75%" }}>
82
- {buildPropertyView?.({
83
- isIdColumn: importKey === idColumn,
84
- property,
85
- propertyKey,
86
- importKey
87
- })
88
- }
89
- </TableCell>
90
- </TableRow>;
91
- }
92
- )}
73
+ const originProperty = getPropertyInPath(originProperties, importKey) as Property | undefined;
74
+ const originDataType = originProperty ? (originProperty.dataType === "array" && typeof originProperty.of === "object"
75
+ ? `${originProperty.dataType} - ${(originProperty.of as Property).dataType}`
76
+ : originProperty.dataType)
77
+ : undefined;
78
+ return <TableRow key={importKey} style={{ height: "90px" }}>
79
+ <TableCell style={{ width: "20%" }}>
80
+ <Typography variant={"body2"}>{importKey}</Typography>
81
+ {originProperty && <Typography
82
+ variant={"caption"}
83
+ color={"secondary"}
84
+ >{originDataType}</Typography>}
85
+ </TableCell>
86
+ <TableCell>
87
+ <ChevronRightIcon/>
88
+ </TableCell>
89
+ <TableCell className={importKey === idColumn ? "text-center" : undefined}
90
+ style={{ width: "75%" }}>
91
+ {buildPropertyView?.({
92
+ isIdColumn: importKey === idColumn,
93
+ property,
94
+ propertyKey,
95
+ importKey
96
+ })
97
+ }
98
+ </TableCell>
99
+ </TableRow>;
100
+ }
101
+ )}
93
102
  </TableBody>
94
103
  </Table>
95
104
  </>
@@ -111,15 +120,16 @@ function IdSelectField({
111
120
  value={idColumn ?? ""}
112
121
  onChange={(event) => {
113
122
  const value = event.target.value;
114
- onChange(value === "none" ? null : value);
123
+ onChange(value === "__none__" ? null : value);
115
124
  }}
125
+ placeholder={"Autogenerate ID"}
116
126
  renderValue={(value) => {
117
127
  return <Typography variant={"body2"}>
118
- {value !== "" ? value : "Autogenerate ID"}
128
+ {value !== "__none__" ? value : "Autogenerate ID"}
119
129
  </Typography>;
120
130
  }}
121
131
  label={"Column that will be used as ID for each document"}>
122
- <SelectItem value={"none"}>Autogenerate ID</SelectItem>
132
+ <SelectItem value={"__none__"}>Autogenerate ID</SelectItem>
123
133
  {Object.entries(headersMapping).map(([key, value]) => {
124
134
  return <SelectItem key={key} value={key}>{key}</SelectItem>;
125
135
  })}
@@ -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
  }
@@ -135,13 +135,14 @@ export function ImportCollectionAction<M extends Record<string, any>, UserType e
135
135
  <DataNewPropertiesMapping headersMapping={importConfig.headersMapping}
136
136
  idColumn={importConfig.idColumn}
137
137
  originProperties={importConfig.originProperties}
138
+ headingsOrder={importConfig.headingsOrder}
138
139
  destinationProperties={properties}
139
140
  onIdPropertyChanged={(value) => importConfig.setIdColumn(value ?? undefined)}
140
141
  buildPropertyView={({
141
142
  isIdColumn,
142
143
  property,
143
144
  propertyKey,
144
- importKey
145
+ importKey,
145
146
  }) => {
146
147
  return <PropertyTreeSelect
147
148
  selectedPropertyKey={propertyKey ?? ""}
@@ -9,6 +9,7 @@ export const useImportConfig = (): ImportConfig => {
9
9
  const [importData, setImportData] = useState<object[]>([]);
10
10
  const [entities, setEntities] = useState<Entity<any>[]>([]);
11
11
  const [headersMapping, setHeadersMapping] = useState<Record<string, string | null>>({});
12
+ const [headingsOrder, setHeadingsOrder] = useState<string[]>([]);
12
13
  const [originProperties, setOriginProperties] = useState<Record<string, Property>>({});
13
14
 
14
15
  return {
@@ -20,6 +21,8 @@ export const useImportConfig = (): ImportConfig => {
20
21
  setEntities,
21
22
  importData,
22
23
  setImportData,
24
+ headingsOrder: (headingsOrder ?? []).length > 0 ? headingsOrder : Object.keys(headersMapping),
25
+ setHeadingsOrder,
23
26
  headersMapping,
24
27
  setHeadersMapping,
25
28
  originProperties,
@@ -22,6 +22,10 @@ export type ImportConfig = {
22
22
  originProperties: Record<string, Property>;
23
23
  setOriginProperties: React.Dispatch<React.SetStateAction<Record<string, Property>>>;
24
24
 
25
+ // unmapped headings order
26
+ headingsOrder: string[];
27
+ setHeadingsOrder: React.Dispatch<React.SetStateAction<string[]>>;
28
+
25
29
  }
26
30
 
27
31
  export type DataTypeMapping = {
@@ -0,0 +1,90 @@
1
+ import * as XLSX from "xlsx";
2
+ export function getXLSXHeaders(sheet: any) {
3
+ let header = 0; let offset = 1;
4
+ const hdr = [];
5
+ const o:any = {};
6
+ if (sheet == null || sheet["!ref"] == null) return [];
7
+ const range = o.range !== undefined ? o.range : sheet["!ref"];
8
+ let r;
9
+ if (o.header === 1) header = 1;
10
+ else if (o.header === "A") header = 2;
11
+ else if (Array.isArray(o.header)) header = 3;
12
+ switch (typeof range) {
13
+ case "string":
14
+ r = safeDecodeRange(range);
15
+ break;
16
+ case "number":
17
+ r = safeDecodeRange(sheet["!ref"]);
18
+ r.s.r = range;
19
+ break;
20
+ default:
21
+ r = range;
22
+ }
23
+ if (header > 0) offset = 0;
24
+ const rr = XLSX.utils.encode_row(r.s.r);
25
+ const cols = new Array(r.e.c - r.s.c + 1);
26
+ for (let C = r.s.c; C <= r.e.c; ++C) {
27
+ cols[C] = XLSX.utils.encode_col(C);
28
+ const val = sheet[cols[C] + rr];
29
+ switch (header) {
30
+ case 1:
31
+ hdr.push(C);
32
+ break;
33
+ case 2:
34
+ hdr.push(cols[C]);
35
+ break;
36
+ case 3:
37
+ hdr.push(o.header[C - r.s.c]);
38
+ break;
39
+ default:
40
+ if (val === undefined) continue;
41
+ hdr.push(XLSX.utils.format_cell(val));
42
+ }
43
+ }
44
+ return hdr;
45
+ }
46
+
47
+ function safeDecodeRange(range:any) {
48
+ const o = {
49
+ s: {
50
+ c: 0,
51
+ r: 0
52
+ },
53
+ e: {
54
+ c: 0,
55
+ r: 0
56
+ }
57
+ };
58
+ let idx = 0; let i = 0; let cc = 0;
59
+ const len = range.length;
60
+ for (idx = 0; i < len; ++i) {
61
+ if ((cc = range.charCodeAt(i) - 64) < 1 || cc > 26) break;
62
+ idx = 26 * idx + cc;
63
+ }
64
+ o.s.c = --idx;
65
+
66
+ for (idx = 0; i < len; ++i) {
67
+ if ((cc = range.charCodeAt(i) - 48) < 0 || cc > 9) break;
68
+ idx = 10 * idx + cc;
69
+ }
70
+ o.s.r = --idx;
71
+
72
+ if (i === len || range.charCodeAt(++i) === 58) {
73
+ o.e.c = o.s.c;
74
+ o.e.r = o.s.r;
75
+ return o;
76
+ }
77
+
78
+ for (idx = 0; i !== len; ++i) {
79
+ if ((cc = range.charCodeAt(i) - 64) < 1 || cc > 26) break;
80
+ idx = 26 * idx + cc;
81
+ }
82
+ o.e.c = --idx;
83
+
84
+ for (idx = 0; i !== len; ++i) {
85
+ if ((cc = range.charCodeAt(i) - 48) < 0 || cc > 9) break;
86
+ idx = 10 * idx + cc;
87
+ }
88
+ o.e.r = --idx;
89
+ return o;
90
+ }
@@ -1,8 +1,13 @@
1
1
  import * as XLSX from "xlsx";
2
+ import { getXLSXHeaders } from "./file_headers";
2
3
 
3
- export function convertFileToJson(file: File): Promise<object[]> {
4
+ type ConversionResult = {
5
+ data: object[];
6
+ propertiesOrder: string[]
7
+ }
8
+
9
+ export function convertFileToJson(file: File): Promise<ConversionResult> {
4
10
  return new Promise((resolve, reject) => {
5
- // check if file is a JSON file
6
11
  if (file.type === "application/json") {
7
12
  console.debug("Converting JSON file to JSON", file.name);
8
13
  const reader = new FileReader();
@@ -12,8 +17,14 @@ export function convertFileToJson(file: File): Promise<object[]> {
12
17
  const jsonData = JSON.parse(data);
13
18
  if (!Array.isArray(jsonData)) {
14
19
  reject(new Error("JSON file should contain an array of objects"));
20
+ } else {
21
+ // Assuming all objects in the array have the same structure/order
22
+ const propertiesOrder = jsonData.length > 0 ? Object.keys(jsonData[0]) : [];
23
+ resolve({
24
+ data: jsonData,
25
+ propertiesOrder
26
+ });
15
27
  }
16
- resolve(jsonData);
17
28
  } catch (e) {
18
29
  console.error("Error parsing JSON file", e);
19
30
  reject(e);
@@ -24,20 +35,22 @@ export function convertFileToJson(file: File): Promise<object[]> {
24
35
  console.debug("Converting Excel file to JSON", file.name);
25
36
  const reader = new FileReader();
26
37
  reader.onload = function (e) {
27
-
28
38
  const data = new Uint8Array(e.target?.result as ArrayBuffer);
29
- const workbook = XLSX.read(data,
30
- {
31
- type: "array",
32
- codepage: 65001,
33
- cellDates: true,
34
- });
39
+ const workbook = XLSX.read(data, {
40
+ type: "array",
41
+ codepage: 65001,
42
+ cellDates: true,
43
+ });
35
44
  const worksheetName = workbook.SheetNames[0];
36
45
  const worksheet = workbook.Sheets[worksheetName];
37
46
  const parsedData: Array<any> = XLSX.utils.sheet_to_json(worksheet);
47
+ const headers = getXLSXHeaders(worksheet);
38
48
  const cleanedData = parsedData.map(mapJsonParse);
39
49
  const jsonData = cleanedData.map(unflattenObject);
40
- resolve(jsonData);
50
+ resolve({
51
+ data: jsonData,
52
+ propertiesOrder: headers
53
+ });
41
54
  };
42
55
  reader.readAsArrayBuffer(file);
43
56
  }