import { clsx } from "clsx"; import { Select as SelectPrimitive } from "radix-ui"; import * as React from "react"; import { useDropzone } from "react-dropzone"; import * as XLSX from "xlsx"; import { Button } from ".."; import { useColumnsContext } from "../../contexts"; import * as selectStyles from "../Select/styles.module.css"; import * as styles from "./styles.module.css"; enum ViewState { Upload = 0, Match = 1, } export type ColumnMap = { tableId: string; columns: Record; extra: Record; }; type Classes = { secondaryButton?: string; primaryButton?: string; }; export default function DataImporter({ className, table = { tableId: "", columns: {}, extra: {}, }, conflictAction = "update", onClose, classes = {}, ...props }: { className?: string; onClose: () => void; table: ColumnMap; conflictAction: "ignore" | "update"; classes?: Classes; }) { const [step, setStep] = React.useState(ViewState.Upload); const [sheet, setSheet] = React.useState(null); const renderStep = React.useCallback( (step: ViewState) => { switch (step) { case ViewState.Upload: return ( ); case ViewState.Match: return ( ); default: return null; } }, [sheet, table?.tableId, table?.columns, onClose], ); return (
{renderStep(step)}
); } function Upload({ setStep, setSheet, columnNames, classes = {}, }: { setStep: (step: ViewState) => void; setSheet: (sheet: XLSX.Sheet) => void; columnNames: Record; classes?: Classes; }) { const onDrop = React.useCallback((acceptedFiles: File[]) => { if (acceptedFiles.length === 0) { return; } const reader = new FileReader(); reader.onabort = () => console.log("file reading was aborted"); reader.onerror = () => console.log("file reading has failed"); reader.onload = () => { // Do whatever you want with the file contents const workbook = XLSX.read(reader.result, { type: "buffer", cellDates: false, cellNF: false, cellText: false, dateNF: "yyyy-mm-dd", raw: true, }); const firstSheet = workbook.Sheets[workbook.SheetNames[0]]; for (const addr of Object.keys(firstSheet)) { const cell = firstSheet[addr]; if (cell && cell.t === "n" && XLSX.SSF.is_date(cell.z || "")) { // ✔ real date cell.t = "d"; const dateObj = XLSX.SSF.parse_date_code(cell.v); // Convert to actual JavaScript Date cell.v = new Date( dateObj.y, dateObj.m - 1, dateObj.d, dateObj.H, dateObj.M, dateObj.S, ); } } setSheet(firstSheet); setStep(ViewState.Match); }; const file = acceptedFiles[0]; reader.readAsArrayBuffer(file); }, []); const { getRootProps, getInputProps, open } = useDropzone({ accept: { "": [".csv", ".tsv", ".xls", ".xlsx", ".xml"], }, useFsAccessApi: false, multiple: false, onDrop, }); return (
{Object.values(columnNames ?? {}).map((name) => { return (

{name}

{Array.from({ length: 5 }).map((_, idx) => { return (
{` `}
); })}
); })}

Drag and drop a file here

You can upload: .csv, .tsv, .xls, .xlsx, .xml

); } function Match({ tableId, sheet, setStep, columnNames = {}, conflictAction = "update", extra = {}, onClose, classes = {}, }: { tableId: string; sheet: XLSX.WorkSheet; setStep: (step: ViewState) => void; columnNames: Record; conflictAction: "ignore" | "update"; extra: Record; onClose?: () => void; classes?: Classes; }) { const columns = useColumnsContext(); const [isLoading, setIsLoading] = React.useState(false); const [header, ...data] = React.useMemo( () => XLSX.utils.sheet_to_json(sheet, { header: 1, blankrows: false, }) as any[][], [sheet], ); const [columnToId, setColumnToId] = React.useState>( () => { const namesByKey = Object.entries(columnNames).reduce( (acc, [key, alias]) => { acc[alias.toLowerCase()] = key; return acc; }, {}, ); return header.reduce((acc, header) => { const key = namesByKey[header.toLowerCase()]; if (key) { acc[header] = key; } else if (columnNames[header]) { acc[header] = header; } return acc; }, {}); }, ); const columnToIndex = React.useMemo(() => { const map: Record = {}; header.forEach((col, idx) => { map[col] = idx; }); return map; }, [header]); return (

Your column name

Matches to

{header.map((col, idx) => { return ( d[idx])} onValueChange={(value) => { setColumnToId((t) => ({ ...t, [col]: value ?? "__empty__" })); }} columnNames={columnNames} /> ); })}
); } function MatchRow({ name, data, onValueChange, value, columnNames = {}, }: { name: string; data: string[]; value: string; onValueChange: (value: string) => void; columnNames: Record; }) { const [isOpen, setIsOpen] = React.useState(false); return (
setIsOpen((t) => !t)} >

{name}

{ if ( process.env.PREVIEW && document.body.classList.contains("__design") ) { e.preventDefault(); } }} > Ignore {Object.entries(columnNames ?? {})?.map( ([id, alias], idx) => { return ( {alias} ); }, )}
{isOpen ? (
{data.map((item) => { return (
{itemToString(item)}
); })}
) : null}
); } function itemToString(item: string | Date | null | undefined) { if (item === null || item === undefined) { return "null"; } if (item instanceof Date) { return item.toLocaleString(); } if (typeof item === "object") { return JSON.stringify(item); } return String(item); } function convertDates(item: string) { if (item && typeof item === "string") { const date = new Date(item); if (isNaN(date.getTime())) { return item; } return date.toLocaleString(); } return item; }