diff --git a/src/components/DropZone.tsx b/src/components/DropZone.tsx index c5784e60..52c7c9f9 100644 --- a/src/components/DropZone.tsx +++ b/src/components/DropZone.tsx @@ -1,6 +1,6 @@ import { Box, Button, Text, useTheme, useToast } from "@chakra-ui/react" import { useDropzone } from "react-dropzone" -import XLSX from "xlsx" +import * as XLSX from "xlsx" import { useState } from "react" import { getDropZoneBorder } from "../utils/getDropZoneBorder" diff --git a/src/components/EditableTable.tsx b/src/components/EditableTable.tsx index 37565104..f9ce799c 100644 --- a/src/components/EditableTable.tsx +++ b/src/components/EditableTable.tsx @@ -22,11 +22,6 @@ const createGlobalStyleOverride = () => css` --rdg-selection-color: none; } - .rdg-header-row .rdg-cell { - --rdg-selection-color: none; - border: none; - } - .rdg-checkbox { --rdg-selection-color: none; background-color: var(--rdg-header-background-color); @@ -51,7 +46,6 @@ const createGlobalStyleOverride = () => css` } .rdg-cell[aria-selected="true"] { - border-radius: 2px; box-shadow: inset 0 0 0 1px var(--rdg-selection-color); } @@ -59,6 +53,14 @@ const createGlobalStyleOverride = () => css` background-color: var(--chakra-colors-red-50); box-shadow: inset 0 0 0 1px var(--chakra-colors-red-100); } + .rdg-cell-warning { + background-color: var(--chakra-colors-orange-50); + box-shadow: 0 1px 0 0 var(--chakra-colors-orange-100); + } + .rdg-cell-info { + background-color: var(--chakra-colors-blue-50); + box-shadow: inset 0 0 0 1px var(--chakra-colors-blue-100); + } .rdg { contain: size layout style paint; @@ -74,7 +76,7 @@ const createGlobalStyleOverride = () => css` --rdg-font-size: 14px; } ` -const ROW_HEIGHT = 42 +const ROW_HEIGHT = 35 interface Props extends DataGridProps { rowHeight?: number diff --git a/src/stories/Table.stories.tsx b/src/stories/Table.stories.tsx index 36437ad2..22765fba 100644 --- a/src/stories/Table.stories.tsx +++ b/src/stories/Table.stories.tsx @@ -1,16 +1,19 @@ import { Box, ChakraProvider, Checkbox, extendTheme, Input, Select, Switch } from "@chakra-ui/react" import { EditableTable } from "../components/EditableTable" -import { connect, useLape } from "lape" +import { connect, ignoreState, useLape } from "lape" import type { ChangeEvent } from "react" import { colorSchemeOverrides, themeOverrides } from "../theme" -import type { Field, Fields } from "../types" +import type { Field, Fields, Info } from "../types" import type { Column, FormatterProps } from "react-data-grid" import { useRowSelection } from "react-data-grid" export default { title: "React spreadsheet import", } -export const SELECT_COLUMN_KEY = "select-row" +type Errors = { [id: string]: { [key: string]: Info } } +type Data = { [key: string]: string | number | boolean | null }[] + +const SELECT_COLUMN_KEY = "select-row" function SelectFormatter(props: FormatterProps) { const [isRowSelected, onRowSelectionChange] = useRowSelection() @@ -48,26 +51,22 @@ const theme = extendTheme(colorSchemeOverrides, themeOverrides) const TableComponent = connect(() => { const data = [ { - id: 0, test: "Hello", second: "one", bool: true, }, { - id: 1, - test: "Hello", + test: "123123", second: "two", bool: true, }, { - id: 2, - test: "Hello", - second: "one", + test: "123123", + second: null, bool: false, }, { - id: 3, - test: "Hello", + test: "111", second: "two", bool: true, }, @@ -78,6 +77,19 @@ const TableComponent = connect(() => { key: "test", label: "Tests", fieldType: { type: "input" }, + validations: [ + { + rule: "unique", + errorMessage: "Test must be unique", + level: "info", + }, + { + rule: "regex", + value: "^\\d+$", + errorMessage: "Test must be number", + level: "warning", + }, + ], }, { key: "second", @@ -89,6 +101,12 @@ const TableComponent = connect(() => { { label: "Two", value: "two" }, ], }, + validations: [ + { + rule: "required", + errorMessage: "Second is required", + }, + ], }, { key: "bool", @@ -97,25 +115,81 @@ const TableComponent = connect(() => { }, ] + const runValidation = (data: Data): Errors => { + let errors: Errors = {} + fields.forEach((field) => { + field.validations?.forEach((validation) => { + switch (validation.rule) { + case "unique": { + const values = data.map((entry) => entry[field.key]) + values.forEach((value, index) => { + if (values.indexOf(value) !== values.lastIndexOf(value)) { + errors[index] = { + ...errors[index], + [field.key]: { + level: validation.level || "error", + message: validation.errorMessage || "Field must be unique", + }, + } + } + }) + break + } + case "required": { + data.forEach((entry, index) => { + if (entry[field.key] === null || entry[field.key] === undefined || entry[field.key] === "") { + errors[index] = { + ...errors[index], + [field.key]: { + level: validation.level || "error", + message: validation.errorMessage || "Field is required", + }, + } + } + }) + break + } + case "regex": { + const regex = new RegExp(validation.value, validation.flags) + data.forEach((entry, index) => { + if (!entry[field.key]?.toString()?.match(regex)) { + errors[index] = { + ...errors[index], + [field.key]: { + level: validation.level || "error", + message: + validation.errorMessage || + `Field did not match the regex /${validation.value}/${validation.flags} `, + }, + } + } + }) + break + } + } + }) + }) + return errors + } + const state = useLape<{ - data: any[] + data: Data errorCount: number filterErrors: boolean + errors: Errors selectedRows: ReadonlySet }>({ - data: data, + data: ignoreState(data), errorCount: 0, + errors: runValidation(data), filterErrors: false, selectedRows: new Set(), }) - const updateSelect = (row: any, key: string) => (event: ChangeEvent) => { - row[key] = event.target.value - } - const updateInput = (row: any, key: string) => (event: ChangeEvent) => { - row[key] = event.target.value + const updateRow = (rows: any[]) => { + state.data = rows + state.errors = runValidation(state.data) } - const updateSwitch = () => {} const columns = [ SelectColumn, ...fields.map((column: Field) => ({ @@ -123,7 +197,7 @@ const TableComponent = connect(() => { name: column.label, resizable: true, editable: column.fieldType.type !== "checkbox", - editor: ({ row }: any) => + editor: ({ row, onRowChange, onClose }: any) => column.fieldType.type === "select" ? (