import { useState } from "react"; import { useAtom } from "jotai"; import useMemoValue from "use-memo-value"; import { find, findIndex, camelCase, isEqual } from "lodash-es"; import { Grid, Typography, Divider, FormControlLabel, Checkbox, Chip, FormControl, RadioGroup, Radio, TextField, MenuItem, Alert, AlertTitle, Stack, Box, } from "@mui/material"; import ArrowIcon from "@mui/icons-material/ArrowForward"; import { TableColumn as TableColumnIcon } from "@src/assets/icons"; import { IStepProps } from "."; import { CsvConfig } from "@src/components/TableModals/ImportCsvWizard"; import FadeList from "@src/components/TableModals/ScrollableList"; import Column, { COLUMN_HEADER_HEIGHT, } from "@src/components/Table/Mock/Column"; import ColumnSelect from "@src/components/Table/ColumnSelect"; import { tableScope, tableSchemaAtom, tableColumnsOrderedAtom, ImportCsvData, } from "@src/atoms/tableScope"; import { FieldType } from "@src/constants/fields"; import { getFieldProp } from "@src/components/fields"; import { suggestType } from "@src/components/TableModals/ImportExistingWizard/utils"; export default function Step1Columns({ csvData, config, updateConfig, setConfig, isXs, }: IStepProps & { csvData: NonNullable }>; }) { const [tableSchema] = useAtom(tableSchemaAtom, tableScope); const [tableColumnsOrdered] = useAtom(tableColumnsOrderedAtom, tableScope); const tableColumns = useMemoValue( tableColumnsOrdered .filter((column) => column.type !== FieldType.id) .map((column) => ({ label: column.name, value: column.fieldName })), isEqual ); const [selectedFields, setSelectedFields] = useState( config.pairs.map((pair) => pair.csvKey) ); const handleSelectAll = () => { if (selectedFields.length !== csvData.columns.length) { setSelectedFields(csvData.columns); csvData.columns.forEach((field) => { // Try to match each field to a column in the table const match = find(tableColumns, (column) => column.label.toLowerCase().includes(field.toLowerCase()) )?.value ?? null; const columnKey = camelCase(field); const columnConfig: Partial = { pairs: [], newColumns: [] }; columnConfig.pairs = [{ csvKey: field, columnKey: match ?? columnKey }]; if (!match) { columnConfig.newColumns = [ { name: field, fieldName: columnKey, key: columnKey, type: suggestType(csvData.rows, field) || FieldType.shortText, index: -1, config: {}, }, ]; } updateConfig(columnConfig); }); } else { setSelectedFields([]); setConfig((config) => ({ ...config, newColumns: [], pairs: [] })); } }; // When a field is selected to be imported const handleSelect = (field: string) => (e: React.ChangeEvent) => { const checked = e.target.checked; if (checked) { setSelectedFields((x) => [...x, field]); // Try to match the field to a column in the table const match = find(tableColumns, (column) => column.label.toLowerCase().includes(field.toLowerCase()) )?.value ?? null; const columnKey = camelCase(field); const columnConfig: Partial = { pairs: [], newColumns: [] }; columnConfig.pairs = [{ csvKey: field, columnKey: match ?? columnKey }]; if (!match) { columnConfig.newColumns = [ { name: field, fieldName: columnKey, key: columnKey, type: suggestType(csvData.rows, field) || FieldType.shortText, index: -1, config: {}, }, ]; } updateConfig(columnConfig); } else { const newValue = [...selectedFields]; newValue.splice(newValue.indexOf(field), 1); setSelectedFields(newValue); // Check if this pair was already pushed to main config const configPair = find(config.pairs, { csvKey: field }); const configIndex = findIndex(config.pairs, { csvKey: field }); // Delete matching newColumn if it was created if (configPair) { const newColumnIndex = findIndex(config.newColumns, { key: configPair.columnKey, }); if (newColumnIndex > -1) { const newColumns = [...config.newColumns]; newColumns.splice(newColumnIndex, 1); setConfig((config) => ({ ...config, newColumns })); } } // Delete pair from main config if (configIndex > -1) { const newConfig = [...config.pairs]; newConfig.splice(configIndex, 1); setConfig((config) => ({ ...config, pairs: newConfig })); } } }; // When a field is mapped to a new column const handleChange = (csvKey: string) => (value: string) => { const columnKey = !!tableSchema.columns?.[value] ? value : camelCase(value); if (columnKey === "") return; // Check if this pair already exists in config const configIndex = findIndex(config.pairs, { csvKey }); if (configIndex > -1) { const pairs = [...config.pairs]; pairs[configIndex].columnKey = columnKey; setConfig((config) => ({ ...config, pairs })); } else { updateConfig({ pairs: [{ csvKey, columnKey }], }); } if (!tableSchema.columns?.[value]) { updateConfig({ newColumns: [ { name: value, fieldName: columnKey, key: columnKey, type: suggestType(csvData.rows, csvKey) || FieldType.shortText, index: -1, config: {}, }, ], }); } }; const stepErrors = () => { const errors = []; if (config.pairs.length < 1) { errors.push("You must select at least one column to import!"); } if (config.documentId === "column" && !config.documentIdCsvKey) { errors.push("You must select a column for document ID!"); } return errors; }; return (
{!isXs && ( Select columns ({config.pairs.length} of {csvData.columns.length}) )} Table columns
  • } label={ selectedFields.length === csvData.columns.length ? "Clear all" : "Select all" } sx={{ height: 42, mr: 0, alignItems: "center", "& .MuiFormControlLabel-label": { mt: 0, flex: 1 }, }} />
  • {csvData.columns.map((field) => { const selected = selectedFields.indexOf(field) > -1; const columnKey = find(config.pairs, { csvKey: field })?.columnKey ?? null; const matchingColumn = columnKey ? tableSchema.columns?.[columnKey] ?? find(config.newColumns, { key: columnKey }) ?? null : null; const isNewColumn = !!find(config.newColumns, { key: columnKey }); return ( } label={} sx={{ marginRight: 0, flex: 1, alignItems: "center", "& .MuiFormControlLabel-label": { mt: 0, flex: 1 }, }} /> theme.spacing(7), display: "flex", alignItems: "center", justifyContent: "center", }} > {selected && ( { if (!columnKey) return "Select or add column"; else return ( {!isNewColumn ? ( getFieldProp("icon", matchingColumn?.type) ) : ( )} {matchingColumn?.name} {isNewColumn && ( )} ); }, sx: [ { backgroundColor: "background.default", border: (theme) => `1px solid ${theme.palette.divider}`, borderRadius: 0, boxShadow: "none", "& .MuiSelect-select": { boxSizing: "border-box", height: COLUMN_HEADER_HEIGHT - 2, typography: "caption", fontWeight: "medium", lineHeight: "28px", }, color: "text.secondary", "&:hover": { backgroundColor: "background.default", color: "text.primary", boxShadow: "none", }, "&::before": { content: "none" }, "&::after": { pointerEvents: "none" }, }, !columnKey && { color: "text.disabled" }, ], }, sx: { "& .MuiInputLabel-root": { display: "none" } }, }} clearable={false} displayEmpty freeText AddButtonProps={{ children: "Create column…" }} AddDialogProps={{ title: "Create column", textFieldLabel: "Column name", }} /> )} ); })}
    Document Ids (Optional) { const documentId = e.currentTarget.value as "auto" | "column"; setConfig((prev: CsvConfig) => ({ ...prev, documentId, documentIdCsvKey: null, })); }} > } label="Auto-Generated" /> } label="Pick Column" /> setConfig((prev) => ({ ...prev, documentIdCsvKey: e.target.value, })) } sx={{ width: isXs ? "100%" : 200, margin: "auto" }} SelectProps={{ displayEmpty: true, renderValue: (value) => ( <>{value ? value : "Select ID Column"} ), MenuProps: { sx: { height: 200 }, anchorOrigin: { vertical: "bottom", horizontal: "right" }, transformOrigin: { vertical: "top", horizontal: "right" }, }, }} helperText={ config.documentId === "column" && csvData.invalidRows && `Invalid Rows: ${csvData.invalidRows.length}/${csvData.rows.length}` } > {csvData.columns.map((column) => ( {column} ))} {stepErrors().map((error, index) => ( {error} ))}
    ); }