Skip to content

Commit 0b7d1a6

Browse files
audi2014Yo1Lmasiulish-bragg
authored
Feat/custom fields hive (#2)
* Auto Select values (Column with select fields) Simple solution to select the right value for select and tick the column header if all values matched :) * Update src/steps/MatchColumnsStep/utils/setColumn.ts Co-authored-by: Harry Bragg <h.bragg+dev@gmail.com> * Add autoMapSelectValues flag and automatic matching. * Add tests for automatching * Match select values by field option value or label * Rename option to record * Fix exhaustive deps * 4.4.0 * Update README.md with correct default * Fix mobile viewport size calculation * Use polyfilled version for older browsers * Enforce vh or dvh as fill-available cannot be used in calc() * 4.4.1 * feat(custom-fields-ugn-master): * add customFieldsHook into RsiProps: Runs on Match Columns step for each column when their state did change. Used to define custom fields with keys that are can be generated only from csv column header. * implement customFieldsHook logic * add dropDownLabel into Field - UI-facing label for dropdown option * feat/custom-fields-hive - revert MatchIcon changes - more generic example of custom fields - mergeCustomFields - add custom fields to end of fields array * feat/custom-fields-hive - revert MatchIcon changes - more generic example of custom fields - mergeCustomFields - add custom fields to end of fields array --------- Co-authored-by: Yo1L <15970286+Yo1L@users.noreply.github.com> Co-authored-by: Karolis Masiulis <karolis.masiulis@ugnis.com> Co-authored-by: Harry Bragg <h.bragg+dev@gmail.com>
1 parent a9034f5 commit 0b7d1a6

15 files changed

Lines changed: 366 additions & 39 deletions

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ Common date-time formats can be viewed [here](https://docs.sheetjs.com/docs/csf/
203203
maxFileSize?: number
204204
// Automatically map imported headers to specified fields if possible. Default: true
205205
autoMapHeaders?: boolean
206+
// When field type is "select", automatically match values if possible. Default: false
207+
autoMapSelectValues?: boolean
206208
// Headers matching accuracy: 1 for strict and up for more flexible matching. Default: 2
207209
autoMapDistance?: number
208210
// Enable navigation in stepper component and show back button. Default: false

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-spreadsheet-import",
3-
"version": "4.3.0",
3+
"version": "4.4.1",
44
"description": "React spreadsheet import for xlsx and csv files with column matching and validation",
55
"main": "./dist-commonjs/index.js",
66
"module": "./dist/index.js",

src/ReactSpreadsheetImport.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const defaultTheme = themeOverrides
1111

1212
export const defaultRSIProps: Partial<RsiProps<any>> = {
1313
autoMapHeaders: true,
14+
autoMapSelectValues: false,
1415
allowInvalidSubmit: true,
1516
autoMapDistance: 2,
1617
translations: translations,

src/steps/MatchColumnsStep/MatchColumnsStep.tsx

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ import { setColumn } from "./utils/setColumn"
88
import { setIgnoreColumn } from "./utils/setIgnoreColumn"
99
import { setSubColumn } from "./utils/setSubColumn"
1010
import { normalizeTableData } from "./utils/normalizeTableData"
11-
import type { Field, RawData } from "../../types"
11+
import type { Field, Fields, RawData } from "../../types"
1212
import { getMatchedColumns } from "./utils/getMatchedColumns"
1313
import { UnmatchedFieldsAlert } from "../../components/Alerts/UnmatchedFieldsAlert"
1414
import { findUnmatchedRequiredFields } from "./utils/findUnmatchedRequiredFields"
15+
import { createHeaderCustomFieldsMap, mergeCustomFields, selectColumnCustomFields } from "./utils/customFields"
1516

1617
export type MatchColumnsProps<T extends string> = {
1718
data: RawData[]
1819
headerValues: RawData
19-
onContinue: (data: any[], rawData: RawData[], columns: Columns<T>) => void
20+
onContinue: (data: any[], rawData: RawData[], columns: Columns<T>, fields: Fields<T>) => void
2021
onBack?: () => void
2122
}
2223

@@ -62,6 +63,7 @@ export type Column<T extends string> =
6263
| MatchedSelectOptionsColumn<T>
6364

6465
export type Columns<T extends string> = Column<T>[]
66+
export type HeaderCustomFieldsMap = Record<string, Field<string>[]>
6567

6668
export const MatchColumnsStep = <T extends string>({
6769
data,
@@ -71,23 +73,29 @@ export const MatchColumnsStep = <T extends string>({
7173
}: MatchColumnsProps<T>) => {
7274
const toast = useToast()
7375
const dataExample = data.slice(0, 2)
74-
const { fields, autoMapHeaders, autoMapDistance, translations } = useRsi<T>()
76+
const { fields, autoMapHeaders, autoMapSelectValues, autoMapDistance, translations, customFieldsHook } = useRsi<T>()
7577
const [isLoading, setIsLoading] = useState(false)
7678
const [columns, setColumns] = useState<Columns<T>>(
7779
// Do not remove spread, it indexes empty array elements, otherwise map() skips over them
7880
([...headerValues] as string[]).map((value, index) => ({ type: ColumnType.empty, index, header: value ?? "" })),
7981
)
82+
83+
const headerCustomFieldsMap = useMemo(
84+
() => createHeaderCustomFieldsMap(columns, customFieldsHook),
85+
[columns, customFieldsHook],
86+
)
8087
const [showUnmatchedFieldsAlert, setShowUnmatchedFieldsAlert] = useState(false)
8188

8289
const onChange = useCallback(
8390
(value: T, columnIndex: number) => {
84-
const field = fields.find((field) => field.key === value) as unknown as Field<T>
91+
const customFields = selectColumnCustomFields(columns[columnIndex], headerCustomFieldsMap)
92+
const customField = customFields.find((field) => field.key === value)
93+
const field = (customField || fields.find((field) => field.key === value)) as Field<T>
8594
const existingFieldIndex = columns.findIndex((column) => "value" in column && column.value === field.key)
8695
setColumns(
8796
columns.map<Column<T>>((column, index) => {
88-
columnIndex === index ? setColumn(column, field, data) : column
8997
if (columnIndex === index) {
90-
return setColumn(column, field, data)
98+
return setColumn(column, field, data, autoMapSelectValues)
9199
} else if (index === existingFieldIndex) {
92100
toast({
93101
status: "warning",
@@ -105,6 +113,8 @@ export const MatchColumnsStep = <T extends string>({
105113
)
106114
},
107115
[
116+
headerCustomFieldsMap,
117+
autoMapSelectValues,
108118
columns,
109119
data,
110120
fields,
@@ -145,24 +155,30 @@ export const MatchColumnsStep = <T extends string>({
145155
setShowUnmatchedFieldsAlert(true)
146156
} else {
147157
setIsLoading(true)
148-
await onContinue(normalizeTableData(columns, data, fields), data, columns)
158+
const mergedFields = mergeCustomFields<T>(columns, fields, headerCustomFieldsMap)
159+
await onContinue(normalizeTableData(columns, data, mergedFields), data, columns, mergedFields)
149160
setIsLoading(false)
150161
}
151-
}, [unmatchedRequiredFields.length, onContinue, columns, data, fields])
162+
}, [unmatchedRequiredFields.length, onContinue, columns, data, fields, headerCustomFieldsMap])
152163

153164
const handleAlertOnContinue = useCallback(async () => {
154165
setShowUnmatchedFieldsAlert(false)
155166
setIsLoading(true)
156-
await onContinue(normalizeTableData(columns, data, fields), data, columns)
167+
const mergedFields = mergeCustomFields<T>(columns, fields, headerCustomFieldsMap)
168+
await onContinue(normalizeTableData(columns, data, mergedFields), data, columns, mergedFields)
157169
setIsLoading(false)
158-
}, [onContinue, columns, data, fields])
170+
}, [onContinue, columns, data, fields, headerCustomFieldsMap])
159171

160-
useEffect(() => {
161-
if (autoMapHeaders) {
162-
setColumns(getMatchedColumns(columns, fields, data, autoMapDistance))
163-
}
172+
useEffect(
173+
() => {
174+
if (autoMapHeaders) {
175+
const mergedFields = [...fields, ...Object.values(headerCustomFieldsMap).flat()] as Fields<T>
176+
setColumns(getMatchedColumns(columns, mergedFields, data, autoMapDistance, autoMapSelectValues))
177+
}
178+
},
164179
// eslint-disable-next-line react-hooks/exhaustive-deps
165-
}, [])
180+
[],
181+
)
166182

167183
return (
168184
<>
@@ -185,7 +201,14 @@ export const MatchColumnsStep = <T extends string>({
185201
entries={dataExample.map((row) => row[column.index])}
186202
/>
187203
)}
188-
templateColumn={(column) => <TemplateColumn column={column} onChange={onChange} onSubChange={onSubChange} />}
204+
templateColumn={(column) => (
205+
<TemplateColumn
206+
column={column}
207+
onChange={onChange}
208+
onSubChange={onSubChange}
209+
headerCustomFieldsMap={headerCustomFieldsMap}
210+
/>
211+
)}
189212
/>
190213
</>
191214
)

src/steps/MatchColumnsStep/components/TemplateColumn.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,49 @@ import {
1111
} from "@chakra-ui/react"
1212
import { useRsi } from "../../../hooks/useRsi"
1313
import type { Column } from "../MatchColumnsStep"
14-
import { ColumnType } from "../MatchColumnsStep"
14+
import { ColumnType, HeaderCustomFieldsMap } from "../MatchColumnsStep"
1515
import { MatchIcon } from "./MatchIcon"
1616
import type { Fields } from "../../../types"
1717
import type { Translations } from "../../../translationsRSIProps"
1818
import { MatchColumnSelect } from "../../../components/Selects/MatchColumnSelect"
1919
import { SubMatchingSelect } from "./SubMatchingSelect"
2020
import type { Styles } from "./ColumnGrid"
21+
import { selectColumnCustomFields } from "../utils/customFields"
2122

2223
const getAccordionTitle = <T extends string>(fields: Fields<T>, column: Column<T>, translations: Translations) => {
2324
const fieldLabel = fields.find((field) => "value" in column && field.key === column.value)!.label
2425
return `${translations.matchColumnsStep.matchDropdownTitle} ${fieldLabel} (${
25-
"matchedOptions" in column && column.matchedOptions.length
26+
"matchedOptions" in column && column.matchedOptions.filter((option) => !option.value).length
2627
} ${translations.matchColumnsStep.unmatched})`
2728
}
2829

2930
type TemplateColumnProps<T extends string> = {
3031
onChange: (val: T, index: number) => void
3132
onSubChange: (val: T, index: number, option: string) => void
3233
column: Column<T>
34+
headerCustomFieldsMap: HeaderCustomFieldsMap
3335
}
3436

35-
export const TemplateColumn = <T extends string>({ column, onChange, onSubChange }: TemplateColumnProps<T>) => {
36-
const { translations, fields } = useRsi<T>()
37+
export const TemplateColumn = <T extends string>({
38+
column,
39+
onChange,
40+
onSubChange,
41+
headerCustomFieldsMap,
42+
}: TemplateColumnProps<T>) => {
43+
const { translations, fields: originalFields } = useRsi<T>()
3744
const styles = useStyleConfig("MatchColumnsStep") as Styles
45+
const customFields = selectColumnCustomFields(column, headerCustomFieldsMap)
46+
const fields = [...originalFields, ...customFields] as Fields<T>
3847
const isIgnored = column.type === ColumnType.ignored
3948
const isChecked =
4049
column.type === ColumnType.matched ||
4150
column.type === ColumnType.matchedCheckbox ||
4251
column.type === ColumnType.matchedSelectOptions
4352
const isSelect = "matchedOptions" in column
44-
const selectOptions = fields.map(({ label, key }) => ({ value: key, label }))
53+
const selectOptions = fields.map(({ key, label, dropDownLabel }) => ({
54+
value: key,
55+
label: dropDownLabel ?? label,
56+
}))
4557
const selectValue = selectOptions.find(({ value }) => "value" in column && column.value === value)
4658

4759
return (

0 commit comments

Comments
 (0)