From cc2e80e35e724504ad44ba46ae4d39f1d4d350d2 Mon Sep 17 00:00:00 2001 From: D-Unit Date: Wed, 21 Aug 2024 09:11:50 +0200 Subject: [PATCH 1/6] updated repo advisor payload --- apps/4tu/index.html | 2 +- packages/repo-advisor/src/features/repoAdvisorApi.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/4tu/index.html b/apps/4tu/index.html index 4a7ee13d..a97a55e3 100644 --- a/apps/4tu/index.html +++ b/apps/4tu/index.html @@ -41,4 +41,4 @@ To create a production bundle, use `npm run build` or `yarn build`. --> - + \ No newline at end of file diff --git a/packages/repo-advisor/src/features/repoAdvisorApi.ts b/packages/repo-advisor/src/features/repoAdvisorApi.ts index 247df8aa..7a584199 100644 --- a/packages/repo-advisor/src/features/repoAdvisorApi.ts +++ b/packages/repo-advisor/src/features/repoAdvisorApi.ts @@ -22,8 +22,8 @@ export const repoAdvisorApi = createApi({ method: "POST", headers: headers, body: JSON.stringify({ - affiliation: ror, - domain: narcis, + affiliation: ror.value, + domain: narcis.value, "deposit-type": depositType, ...(fileType && { "file-type": fileType }), }), From c857f5984b517bbff837bf313813dcf5bf91fe66 Mon Sep 17 00:00:00 2001 From: D-Unit Date: Wed, 21 Aug 2024 10:45:21 +0200 Subject: [PATCH 2/6] split code over files, added data preview function for table --- packages/file-mapper/package.json | 1 + .../file-mapper/src/features/FileMapper.tsx | 13 +- .../file-mapper/src/features/SaveMapping.tsx | 17 + .../file-mapper/src/features/SelectFile.tsx | 136 ++++++++ .../file-mapper/src/features/SetMapping.tsx | 187 +++++++++++ packages/file-mapper/src/features/Steps.tsx | 300 +----------------- .../file-mapper/src/features/fileMapperApi.ts | 1 + .../src/features/fileMapperSlice.ts | 31 +- .../src/languages/locales/en/steps.json | 3 +- .../src/languages/locales/nl/steps.json | 3 +- packages/file-mapper/src/types/index.ts | 8 +- pnpm-lock.yaml | 3 + 12 files changed, 393 insertions(+), 310 deletions(-) create mode 100644 packages/file-mapper/src/features/SaveMapping.tsx create mode 100644 packages/file-mapper/src/features/SelectFile.tsx create mode 100644 packages/file-mapper/src/features/SetMapping.tsx diff --git a/packages/file-mapper/package.json b/packages/file-mapper/package.json index f2cf9697..834c18ea 100644 --- a/packages/file-mapper/package.json +++ b/packages/file-mapper/package.json @@ -6,6 +6,7 @@ "version": "1.0.0", "dependencies": { "@dans-framework/utils": "workspace:*", + "@mui/icons-material": "^5.14.3", "@mui/material": "^5.14.3", "@reduxjs/toolkit": "^1.9.5", "i18next": "^23.4.1", diff --git a/packages/file-mapper/src/features/FileMapper.tsx b/packages/file-mapper/src/features/FileMapper.tsx index 58b2795b..20bd8e93 100644 --- a/packages/file-mapper/src/features/FileMapper.tsx +++ b/packages/file-mapper/src/features/FileMapper.tsx @@ -9,7 +9,9 @@ import Stepper from '@mui/material/Stepper'; import Step from '@mui/material/Step'; import StepLabel from '@mui/material/StepLabel'; import { useTranslation } from "react-i18next"; -import { Step1, Step2, Step3 } from './Steps'; +import SelectFile from './SelectFile'; +import SetMapping from './SetMapping'; +import SaveMapping from './SaveMapping'; import { getActiveStep, setActiveStep, getFile, getSavedMap, getMapping, getFileError } from './fileMapperSlice'; import { useSubmitMapMutation } from './fileMapperApi'; import { useAppSelector, useAppDispatch } from "../redux/hooks"; @@ -18,8 +20,7 @@ import { useSiteTitle, setSiteTitle } from "@dans-framework/utils/sitetitle"; import { lookupLanguageString } from "@dans-framework/utils/language"; import { type Page } from "@dans-framework/pages"; import type { FormConfig } from "@dans-framework/deposit"; - -const steps = ['selectFile', 'createMapping', 'finish']; +import { steps } from "./Steps"; const FileMapper = ({setMappedForm, page}: { setMappedForm: Dispatch>; @@ -85,10 +86,10 @@ const FileMapper = ({setMappedForm, page}: { { activeStep === 0 - ? + ? : activeStep === 1 - ? - : + ? + : } { + const { t } = useTranslation("steps"); + + return ( + + {t("saveMappingExtra")} + + + ) +} + +export default SaveMapping; \ No newline at end of file diff --git a/packages/file-mapper/src/features/SelectFile.tsx b/packages/file-mapper/src/features/SelectFile.tsx new file mode 100644 index 00000000..c387480c --- /dev/null +++ b/packages/file-mapper/src/features/SelectFile.tsx @@ -0,0 +1,136 @@ +import { useDropzone } from "react-dropzone"; +import Box from "@mui/material/Box"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import Alert from "@mui/material/Alert"; +import { useTranslation } from "react-i18next"; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import Checkbox from '@mui/material/Checkbox'; +import Divider from '@mui/material/Divider'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import { useTheme } from '@mui/material/styles'; +import type { Saves, SerializedFile } from "../types"; +import { + getFile, + setFile, + getSavedMap, + setSavedMap, + resetMapping, + getFileError, + resetFileError, + resetFileData, +} from './fileMapperSlice'; +import { useAppSelector, useAppDispatch } from "../redux/hooks"; +import { StepWrap, maxRows, saves } from "./Steps"; + +const SelectFile = () => { + const { t } = useTranslation("steps"); + const theme = useTheme(); + const matches = useMediaQuery(theme.breakpoints.up('md')); + const dispatch = useAppDispatch(); + const file = useAppSelector(getFile); + const savedMap = useAppSelector(getSavedMap); + const fileError = useAppSelector(getFileError); + + const onDrop = async (files: File[]) => { + // serialize files to store in redux + const serializedFile: SerializedFile = { + name: files[0].name, + size: files[0].size, + url: URL.createObjectURL(files[0]), + }; + + dispatch(setFile(serializedFile)); + + // reset saved mapping and column values after selecting a different file + dispatch(resetMapping()); + dispatch(resetFileData()); + dispatch(resetFileError()); + } + + const { + getRootProps, + getInputProps, + isDragActive, + } = useDropzone({ + onDrop, + multiple: false, + accept: { + "text/csv": [".csv"], + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"], + }, + }); + + return ( + + } + > + + {t("uploadNew")} + + + + {isDragActive ? t("dropNow") : t("drop")} + + + { file && + + + {t("selectedFile", { name: file.name, size: (file.size / 1024).toFixed(0) })} + + {fileError && + + {t(fileError, {max: maxRows})} + + } + + } + + {saves.length > 0 && + + {t("selectSave")} + + {saves.map( (save: Saves) => + + dispatch(setSavedMap(savedMap === save.id ? "" : save.id))} dense> + + + + + + + )} + + + } + + + ) +} + +export default SelectFile; \ No newline at end of file diff --git a/packages/file-mapper/src/features/SetMapping.tsx b/packages/file-mapper/src/features/SetMapping.tsx new file mode 100644 index 00000000..ba7bbff5 --- /dev/null +++ b/packages/file-mapper/src/features/SetMapping.tsx @@ -0,0 +1,187 @@ +import { useState, useEffect } from "react"; +import Typography from "@mui/material/Typography"; +import Paper from "@mui/material/Paper"; +import Alert from "@mui/material/Alert"; +import { useTranslation } from "react-i18next"; +import * as XLSX from "xlsx"; +import TableContainer from '@mui/material/TableContainer'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import TextField from '@mui/material/TextField'; +import Autocomplete from '@mui/material/Autocomplete'; +import CircularProgress from '@mui/material/CircularProgress'; +import IconButton from '@mui/material/IconButton'; +import Collapse from '@mui/material/Collapse'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; +import type { DarwinOptions, FileError, SheetData } from "../types"; +import { + getFile, + getMapping, + setMapping, + getFileError, + setFileError, + setFileData, + getFileData, +} from './fileMapperSlice'; +import { useFetchDarwinTermsQuery } from "./fileMapperApi"; +import { useAppSelector, useAppDispatch } from "../redux/hooks"; +import { StepWrap, maxRows } from "./Steps"; + +export const SetMapping = () => { + const { t } = useTranslation("steps"); + const dispatch = useAppDispatch(); + const file = useAppSelector(getFile); + const fileData = useAppSelector(getFileData); + const fileError = useAppSelector(getFileError); + // loading indicator only if new file needs to be read + const [ loading, setLoading ] = useState( !Array.isArray(fileData) && !fileError ); + + useEffect(() => { + const reader = new FileReader(); + + reader.onload = (event) => { + // parse the xlsx/csv file + const workbook = XLSX.read(event.target?.result, { type: 'binary' }); + const sheetName = workbook.SheetNames[0]; + const sheet = workbook.Sheets[sheetName]; + const sheetData: SheetData[] = XLSX.utils.sheet_to_json(sheet, { header: "A" }); + // check to see if sheet doesn't have too many rows (first row is headers, so doesn't count) + const rowCount = sheetData.length - 1; + if (rowCount > maxRows) { + dispatch(setFileError("tooManyRows")); + } else { + // save sheet data so we only need to load and parse once + dispatch(setFileData(sheetData)); + } + setLoading(false); + }; + + if (file && !fileData && !fileError) { + dispatch(setFileError(undefined)); + // if no file loaded yet, convert file url back to blob + (async () => { + const fetchedFile = await fetch(file.url); + const blob = await fetchedFile.blob(); + if (blob) { + reader.readAsBinaryString(blob); + } + })(); + } + }, [file, fileData, fileError]); + + return ( + + + + + + + {t("dataValue")} + {t("mapTo")} + + + + { ( loading || fileError ) && + + + { + loading ? + : + + {t(fileError as FileError, {max: maxRows})} + + } + + + } + {fileData && Object.entries(fileData[0]).map(([key, value]) => + + )} + +
+
+
+ ) +} + +export default SetMapping; + +const Row = ({rowKey, row}: {rowKey: string; row: string;}) => { + const { t } = useTranslation("steps"); + const [inputValue, setInputValue] = useState(''); + const mapping = useAppSelector(getMapping); + const dispatch = useAppDispatch(); + const fileData = useAppSelector(getFileData); + const columnData = fileData && fileData.slice(1).map( data => data[rowKey]) + const [ open, setOpen ] = useState(false); + console.log(columnData) + + const { data, isLoading } = useFetchDarwinTermsQuery(''); + + const selectValue = (row: string, value: DarwinOptions | null) => { + if (value === null) { + // Create a new object excluding the [row] key + const { [row]: _, ...rest } = mapping; + dispatch(setMapping(rest)); + } else { + dispatch(setMapping({ + ...mapping, + [row]: value, + })); + } + }; + + return ( + <> + + + setOpen(!open)} + > + {open ? : } + + + + {row} + + + option.label} + groupBy={(option) => option.header} + sx={{ width: 300 }} + renderInput={(params) => } + onChange={(_e, value) => selectValue(row, value)} + value={(mapping.hasOwnProperty(row) && mapping[row]) || null} + inputValue={inputValue} + isOptionEqualToValue={(option, value) => option.term_localName === value.term_localName} + onInputChange={(_e, newInputValue) => { + setInputValue(newInputValue); + }} + loading={isLoading} + size="small" + /> + + + {columnData && + + + + + {t('dataHeader')} + {columnData.map((item, i) => + {item} + )} + + + + } + + ) +} diff --git a/packages/file-mapper/src/features/Steps.tsx b/packages/file-mapper/src/features/Steps.tsx index 4d030347..8447d821 100644 --- a/packages/file-mapper/src/features/Steps.tsx +++ b/packages/file-mapper/src/features/Steps.tsx @@ -1,50 +1,9 @@ -import { useState, useEffect, type ReactNode } from "react"; -import { useDropzone } from "react-dropzone"; +import type { ReactNode } from "react"; import Box from "@mui/material/Box"; -import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import Paper from "@mui/material/Paper"; -import Alert from "@mui/material/Alert"; -import { useTranslation } from "react-i18next"; -import * as XLSX from "xlsx"; -import TableContainer from '@mui/material/TableContainer'; -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableCell from '@mui/material/TableCell'; -import TableHead from '@mui/material/TableHead'; -import TableRow from '@mui/material/TableRow'; -import TextField from '@mui/material/TextField'; -import Autocomplete from '@mui/material/Autocomplete'; -import CircularProgress from '@mui/material/CircularProgress'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemButton from '@mui/material/ListItemButton'; -import ListItemText from '@mui/material/ListItemText'; -import ListItemIcon from '@mui/material/ListItemIcon'; -import Checkbox from '@mui/material/Checkbox'; -import Divider from '@mui/material/Divider'; -import useMediaQuery from '@mui/material/useMediaQuery'; -import { useTheme } from '@mui/material/styles'; -import type { DarwinOptions, Saves, SerializedFile, FileError } from "../types"; -import { - getFile, - setFile, - getMapping, - setMapping, - getSavedMap, - setSavedMap, - resetMapping, - setFileCols, - getFileCols, - resetFileCols, - getFileError, - setFileError, - resetFileError, -} from './fileMapperSlice'; -import { useFetchDarwinTermsQuery } from "./fileMapperApi"; -import { useAppSelector, useAppDispatch } from "../redux/hooks"; -const saves = [ +// some fake placeholder saves. todo. +export const saves = [ { name: "Fake save #1", date: "8-8-2024", @@ -58,253 +17,14 @@ const saves = [ ]; // define max number of rows a file selected for processing can contain -const maxRows = 10; +export const maxRows = 10; -const StepWrap = ({ title, children }: { title: string; children: ReactNode }) => +// define steps +export const steps = ['selectFile', 'createMapping', 'finish']; + +// wrapper for individual steps +export const StepWrap = ({ title, children }: { title: string; children: ReactNode }) => {title} {children} - - -export const Step1 = () => { - const { t } = useTranslation("steps"); - const theme = useTheme(); - const matches = useMediaQuery(theme.breakpoints.up('md')); - const dispatch = useAppDispatch(); - const file = useAppSelector(getFile); - const savedMap = useAppSelector(getSavedMap); - const fileError = useAppSelector(getFileError); - - const onDrop = async (files: File[]) => { - // serialize files to store in redux - const serializedFile: SerializedFile = { - name: files[0].name, - size: files[0].size, - url: URL.createObjectURL(files[0]), - }; - - dispatch(setFile(serializedFile)); - - // reset saved mapping and column values after selecting a different file - dispatch(resetMapping()); - dispatch(resetFileCols()); - dispatch(resetFileError()); - } - - const { - getRootProps, - getInputProps, - isDragActive, - } = useDropzone({ - onDrop, - multiple: false, - accept: { - "text/csv": [".csv"], - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"], - }, - }); - - return ( - - } - > - - {t("uploadNew")} - - - - {isDragActive ? t("dropNow") : t("drop")} - - - { file && - - - {t("selectedFile", { name: file.name, size: (file.size / 1024).toFixed(0) })} - - {fileError && - - {t(fileError, {max: maxRows})} - - } - - } - - {saves.length > 0 && - - {t("selectSave")} - - {saves.map( (save: Saves) => - - dispatch(setSavedMap(savedMap === save.id ? "" : save.id))} dense> - - - - - - - )} - - - } - - - ) -} - -export const Step2 = () => { - const { t } = useTranslation("steps"); - const dispatch = useAppDispatch(); - const file = useAppSelector(getFile); - const fileCols = useAppSelector(getFileCols); - const fileError = useAppSelector(getFileError); - // loading indicator only if new file needs to be read - const [ loading, setLoading ] = useState( !Array.isArray(fileCols) && !fileError ); - - useEffect(() => { - const reader = new FileReader(); - - reader.onload = (event) => { - // parse the xlsx/csv file - const workbook = XLSX.read(event.target?.result, { type: 'binary' }); - const sheetName = workbook.SheetNames[0]; - const sheet = workbook.Sheets[sheetName]; - const sheetData = XLSX.utils.sheet_to_json(sheet, { header: 1 })[0]; - // check to see if sheet doesn't have too many rows - const rowCount = XLSX.utils.sheet_to_json(sheet).length; - if (rowCount > maxRows) { - dispatch(setFileError("tooManyRows")); - } else { - // save column data to store so we only have to load and parse once - Array.isArray(sheetData) && dispatch(setFileCols(sheetData)); - } - setLoading(false); - }; - - if (file && !fileCols && !fileError) { - dispatch(setFileError(undefined)); - // if no file loaded yet, convert file url back to blob - (async () => { - const fetchedFile = await fetch(file.url); - const blob = await fetchedFile.blob(); - if (blob) { - reader.readAsBinaryString(blob); - } - })(); - } - }, [file, fileCols, fileError]); - - return ( - - - - - - {t("dataValue")} - {t("mapTo")} - - - - { ( loading || fileError ) && - - - { - loading ? - : - - {t(fileError as FileError, {max: maxRows})} - - } - - - } - {fileCols && fileCols.map((row) => ( - - ))} - -
-
-
- ) -} - -const Row = ({row}: {row: string}) => { - const { t } = useTranslation("steps"); - const [inputValue, setInputValue] = useState(''); - const mapping = useAppSelector(getMapping); - const dispatch = useAppDispatch(); - - const { data, isLoading } = useFetchDarwinTermsQuery(''); - - const selectValue = (row: string, value: DarwinOptions | null) => { - if (value === null) { - // Create a new object excluding the [row] key - const { [row]: _, ...rest } = mapping; - dispatch(setMapping(rest)); - } else { - dispatch(setMapping({ - ...mapping, - [row]: value, - })); - } - }; - - return ( - - - {row} - - - option.label} - groupBy={(option) => option.header} - sx={{ width: 300 }} - renderInput={(params) => } - onChange={(_e, value) => selectValue(row, value)} - value={(mapping.hasOwnProperty(row) && mapping[row]) || null} - inputValue={inputValue} - isOptionEqualToValue={(option, value) => option.term_localName === value.term_localName} - onInputChange={(_e, newInputValue) => { - setInputValue(newInputValue); - }} - loading={isLoading} - /> - - - ) -} - -export const Step3 = () => { - const { t } = useTranslation("steps"); - - return ( - - {t("saveMappingExtra")} - - - ) -} \ No newline at end of file +
\ No newline at end of file diff --git a/packages/file-mapper/src/features/fileMapperApi.ts b/packages/file-mapper/src/features/fileMapperApi.ts index 738737d9..cd249335 100644 --- a/packages/file-mapper/src/features/fileMapperApi.ts +++ b/packages/file-mapper/src/features/fileMapperApi.ts @@ -45,6 +45,7 @@ export const submitMappingApi = createApi({ // format headers const headers = { Authorization: `Bearer ${user?.access_token}`, + "auth-env-name": import.meta.env.VITE_ENV_NAME, }; let formData = new FormData(); diff --git a/packages/file-mapper/src/features/fileMapperSlice.ts b/packages/file-mapper/src/features/fileMapperSlice.ts index e7f00665..277bcfde 100644 --- a/packages/file-mapper/src/features/fileMapperSlice.ts +++ b/packages/file-mapper/src/features/fileMapperSlice.ts @@ -1,6 +1,6 @@ import { createSlice, type PayloadAction } from "@reduxjs/toolkit"; import { RootState } from "../redux/store"; -import type { SerializedFile, Mapping, FileError } from "../types"; +import type { SerializedFile, Mapping, FileError, SheetData } from "../types"; import type { FormConfig } from "@dans-framework/deposit"; const initialState: { @@ -9,16 +9,16 @@ const initialState: { file: SerializedFile | undefined; savedMap: string; form: FormConfig | undefined; - fileCols: string[] | undefined; fileError: string | undefined; + fileData: SheetData[] | undefined; } = { activeStep: 0, mapping: {}, file: undefined, savedMap: '', form: undefined, - fileCols: undefined, fileError: undefined, + fileData: undefined, }; export const fileMapperSlice = createSlice({ @@ -43,8 +43,11 @@ export const fileMapperSlice = createSlice({ saveData: (state, action) => { state.form = action.payload; }, - setFileCols: (state, action: PayloadAction) => { - state.fileCols = action.payload; + setFileData: (state, action: PayloadAction) => { + state.fileData = action.payload; + }, + resetFileData: (state) => { + state.fileData = initialState.fileData; }, setFileError: (state, action: PayloadAction) => { state.fileError = action.payload; @@ -52,20 +55,28 @@ export const fileMapperSlice = createSlice({ resetFileError: (state) => { state.fileError = initialState.fileError; }, - resetFileCols: (state) => { - state.fileCols = initialState.fileCols; - } }, }); -export const { setActiveStep, setMapping, resetMapping, setSavedMap, setFile, saveData, setFileCols, resetFileCols, setFileError, resetFileError } = fileMapperSlice.actions; +export const { + setActiveStep, + setMapping, + resetMapping, + setSavedMap, + setFile, + saveData, + setFileData, + resetFileData, + setFileError, + resetFileError, +} = fileMapperSlice.actions; export const getActiveStep = (state: RootState) => state.fileMapper.activeStep; export const getMapping = (state: RootState) => state.fileMapper.mapping; export const getSavedMap = (state: RootState) => state.fileMapper.savedMap; export const getFile = (state: RootState) => state.fileMapper.file; export const getForm = (state: RootState) => state.fileMapper.form; -export const getFileCols = (state: RootState) => state.fileMapper.fileCols; +export const getFileData = (state: RootState) => state.fileMapper.fileData; export const getFileError = (state: RootState) => state.fileMapper.fileError; export default fileMapperSlice.reducer; diff --git a/packages/file-mapper/src/languages/locales/en/steps.json b/packages/file-mapper/src/languages/locales/en/steps.json index 8c978e74..ce1f5bd3 100644 --- a/packages/file-mapper/src/languages/locales/en/steps.json +++ b/packages/file-mapper/src/languages/locales/en/steps.json @@ -18,5 +18,6 @@ "saveMapping": "Save as", "saveMappingExtra": "You can save your mapping for future datasets for easy reference. Please enter a name below.", "isLoading": "Loading...", - "tooManyRows": "Your data file contains too many rows to process using this tool. Make sure your file has less than {{max}} rows." + "tooManyRows": "Your data file contains too many rows to process using this tool. Make sure your file has less than {{max}} rows.", + "dataHeader": "Sheet data for this column" } \ No newline at end of file diff --git a/packages/file-mapper/src/languages/locales/nl/steps.json b/packages/file-mapper/src/languages/locales/nl/steps.json index 1c9de6af..fbac7560 100644 --- a/packages/file-mapper/src/languages/locales/nl/steps.json +++ b/packages/file-mapper/src/languages/locales/nl/steps.json @@ -18,5 +18,6 @@ "saveMapping": "Save as", "saveMappingExtra": "You can save your mapping for future datasets for easy reference. Please enter a name below.", "isLoading": "Loading...", - "tooManyRows": "Je data file contains too many rows to process using this tool. Make sure your file has less than {{max}} rows." + "tooManyRows": "Je data file contains too many rows to process using this tool. Make sure your file has less than {{max}} rows.", + "dataHeader": "Sheet data for this column" } \ No newline at end of file diff --git a/packages/file-mapper/src/types/index.ts b/packages/file-mapper/src/types/index.ts index 128b8cb0..d98b5218 100644 --- a/packages/file-mapper/src/types/index.ts +++ b/packages/file-mapper/src/types/index.ts @@ -10,7 +10,7 @@ export type DarwinOptions = { } export interface Mapping { - [key: string]: DarwinOptions + [key: string]: DarwinOptions; } export interface Saves { @@ -26,4 +26,8 @@ export interface SerializedFile { } export type FileError = - | "tooManyRows"; \ No newline at end of file + | "tooManyRows"; + +export interface SheetData { + [key: string]: string; +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aacf28a5..653d5c75 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -644,6 +644,9 @@ importers: '@dans-framework/utils': specifier: workspace:* version: link:../utils + '@mui/icons-material': + specifier: ^5.14.3 + version: 5.14.3(@mui/material@5.14.3(@emotion/react@11.11.1(@types/react@18.2.15)(react@18.2.0))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.2.15)(react@18.2.0))(@types/react@18.2.15)(react@18.2.0))(@types/react@18.2.15)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/react@18.2.15)(react@18.2.0) '@mui/material': specifier: ^5.14.3 version: 5.14.3(@emotion/react@11.11.1(@types/react@18.2.15)(react@18.2.0))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.2.15)(react@18.2.0))(@types/react@18.2.15)(react@18.2.0))(@types/react@18.2.15)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) From 0d32c931b173a94e1e2492a56da2d26abb5b6495 Mon Sep 17 00:00:00 2001 From: Laurens Tobias Date: Fri, 23 Aug 2024 15:18:00 +0200 Subject: [PATCH 3/6] Updated dashboard config for new es instance and update details a bit --- apps/rda/src/config/elasticSearch.tsx | 48 +++++++++++++++++++++++---- apps/rda/src/pages/record/index.tsx | 10 ++++-- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/apps/rda/src/config/elasticSearch.tsx b/apps/rda/src/config/elasticSearch.tsx index dc6f3c99..269209a7 100644 --- a/apps/rda/src/config/elasticSearch.tsx +++ b/apps/rda/src/config/elasticSearch.tsx @@ -24,7 +24,7 @@ const fieldConfig: Partial = { export const elasticConfig = [ { name: "RDA Catalogue", - url: "https://es.ohsmart.dansdemo.nl/dans-rda2", + url: "https://tiger.laurenstobias.com/rda", fullTextFields: fieldConfig.fullTextFields, fullTextHighlight: fieldConfig.fullTextHighlight, resultBodyComponent: Rda2Result, @@ -33,7 +33,7 @@ export const elasticConfig = [ , + , + , + , @@ -224,9 +228,9 @@ function Metadata({ export function MetadataList({ record }: { record: RdaRecord | Result }) { const individuals = - record.individuals ? record.individuals.map((i: any) => i.fullname) : []; + record.individuals ? record.individuals.map((i: any) => i.fullName) : []; const workflows = - record.workflows ? record.workflows.map((w: any) => w.workflowstate) : []; + record.workflows ? record.workflows.map((w: any) => w.WorkflowState) : []; const rights = record.rights ? record.rights.map((r: any) => r.description) : []; const pathways = From 58f12d8a981274e20f02d5d1221b0f1533baee6e Mon Sep 17 00:00:00 2001 From: D-Unit Date: Wed, 21 Aug 2024 10:45:21 +0200 Subject: [PATCH 4/6] split code over files, added data preview function for table --- packages/file-mapper/package.json | 1 + .../file-mapper/src/features/FileMapper.tsx | 13 +- .../file-mapper/src/features/SaveMapping.tsx | 17 + .../file-mapper/src/features/SelectFile.tsx | 136 ++++++++ .../file-mapper/src/features/SetMapping.tsx | 187 +++++++++++ packages/file-mapper/src/features/Steps.tsx | 300 +----------------- .../file-mapper/src/features/fileMapperApi.ts | 1 + .../src/features/fileMapperSlice.ts | 31 +- .../src/languages/locales/en/steps.json | 3 +- .../src/languages/locales/nl/steps.json | 3 +- packages/file-mapper/src/types/index.ts | 8 +- pnpm-lock.yaml | 3 + 12 files changed, 393 insertions(+), 310 deletions(-) create mode 100644 packages/file-mapper/src/features/SaveMapping.tsx create mode 100644 packages/file-mapper/src/features/SelectFile.tsx create mode 100644 packages/file-mapper/src/features/SetMapping.tsx diff --git a/packages/file-mapper/package.json b/packages/file-mapper/package.json index f2cf9697..834c18ea 100644 --- a/packages/file-mapper/package.json +++ b/packages/file-mapper/package.json @@ -6,6 +6,7 @@ "version": "1.0.0", "dependencies": { "@dans-framework/utils": "workspace:*", + "@mui/icons-material": "^5.14.3", "@mui/material": "^5.14.3", "@reduxjs/toolkit": "^1.9.5", "i18next": "^23.4.1", diff --git a/packages/file-mapper/src/features/FileMapper.tsx b/packages/file-mapper/src/features/FileMapper.tsx index 58b2795b..20bd8e93 100644 --- a/packages/file-mapper/src/features/FileMapper.tsx +++ b/packages/file-mapper/src/features/FileMapper.tsx @@ -9,7 +9,9 @@ import Stepper from '@mui/material/Stepper'; import Step from '@mui/material/Step'; import StepLabel from '@mui/material/StepLabel'; import { useTranslation } from "react-i18next"; -import { Step1, Step2, Step3 } from './Steps'; +import SelectFile from './SelectFile'; +import SetMapping from './SetMapping'; +import SaveMapping from './SaveMapping'; import { getActiveStep, setActiveStep, getFile, getSavedMap, getMapping, getFileError } from './fileMapperSlice'; import { useSubmitMapMutation } from './fileMapperApi'; import { useAppSelector, useAppDispatch } from "../redux/hooks"; @@ -18,8 +20,7 @@ import { useSiteTitle, setSiteTitle } from "@dans-framework/utils/sitetitle"; import { lookupLanguageString } from "@dans-framework/utils/language"; import { type Page } from "@dans-framework/pages"; import type { FormConfig } from "@dans-framework/deposit"; - -const steps = ['selectFile', 'createMapping', 'finish']; +import { steps } from "./Steps"; const FileMapper = ({setMappedForm, page}: { setMappedForm: Dispatch>; @@ -85,10 +86,10 @@ const FileMapper = ({setMappedForm, page}: { { activeStep === 0 - ? + ? : activeStep === 1 - ? - : + ? + : } { + const { t } = useTranslation("steps"); + + return ( + + {t("saveMappingExtra")} + + + ) +} + +export default SaveMapping; \ No newline at end of file diff --git a/packages/file-mapper/src/features/SelectFile.tsx b/packages/file-mapper/src/features/SelectFile.tsx new file mode 100644 index 00000000..c387480c --- /dev/null +++ b/packages/file-mapper/src/features/SelectFile.tsx @@ -0,0 +1,136 @@ +import { useDropzone } from "react-dropzone"; +import Box from "@mui/material/Box"; +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import Alert from "@mui/material/Alert"; +import { useTranslation } from "react-i18next"; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import Checkbox from '@mui/material/Checkbox'; +import Divider from '@mui/material/Divider'; +import useMediaQuery from '@mui/material/useMediaQuery'; +import { useTheme } from '@mui/material/styles'; +import type { Saves, SerializedFile } from "../types"; +import { + getFile, + setFile, + getSavedMap, + setSavedMap, + resetMapping, + getFileError, + resetFileError, + resetFileData, +} from './fileMapperSlice'; +import { useAppSelector, useAppDispatch } from "../redux/hooks"; +import { StepWrap, maxRows, saves } from "./Steps"; + +const SelectFile = () => { + const { t } = useTranslation("steps"); + const theme = useTheme(); + const matches = useMediaQuery(theme.breakpoints.up('md')); + const dispatch = useAppDispatch(); + const file = useAppSelector(getFile); + const savedMap = useAppSelector(getSavedMap); + const fileError = useAppSelector(getFileError); + + const onDrop = async (files: File[]) => { + // serialize files to store in redux + const serializedFile: SerializedFile = { + name: files[0].name, + size: files[0].size, + url: URL.createObjectURL(files[0]), + }; + + dispatch(setFile(serializedFile)); + + // reset saved mapping and column values after selecting a different file + dispatch(resetMapping()); + dispatch(resetFileData()); + dispatch(resetFileError()); + } + + const { + getRootProps, + getInputProps, + isDragActive, + } = useDropzone({ + onDrop, + multiple: false, + accept: { + "text/csv": [".csv"], + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"], + }, + }); + + return ( + + } + > + + {t("uploadNew")} + + + + {isDragActive ? t("dropNow") : t("drop")} + + + { file && + + + {t("selectedFile", { name: file.name, size: (file.size / 1024).toFixed(0) })} + + {fileError && + + {t(fileError, {max: maxRows})} + + } + + } + + {saves.length > 0 && + + {t("selectSave")} + + {saves.map( (save: Saves) => + + dispatch(setSavedMap(savedMap === save.id ? "" : save.id))} dense> + + + + + + + )} + + + } + + + ) +} + +export default SelectFile; \ No newline at end of file diff --git a/packages/file-mapper/src/features/SetMapping.tsx b/packages/file-mapper/src/features/SetMapping.tsx new file mode 100644 index 00000000..ba7bbff5 --- /dev/null +++ b/packages/file-mapper/src/features/SetMapping.tsx @@ -0,0 +1,187 @@ +import { useState, useEffect } from "react"; +import Typography from "@mui/material/Typography"; +import Paper from "@mui/material/Paper"; +import Alert from "@mui/material/Alert"; +import { useTranslation } from "react-i18next"; +import * as XLSX from "xlsx"; +import TableContainer from '@mui/material/TableContainer'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import TextField from '@mui/material/TextField'; +import Autocomplete from '@mui/material/Autocomplete'; +import CircularProgress from '@mui/material/CircularProgress'; +import IconButton from '@mui/material/IconButton'; +import Collapse from '@mui/material/Collapse'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; +import type { DarwinOptions, FileError, SheetData } from "../types"; +import { + getFile, + getMapping, + setMapping, + getFileError, + setFileError, + setFileData, + getFileData, +} from './fileMapperSlice'; +import { useFetchDarwinTermsQuery } from "./fileMapperApi"; +import { useAppSelector, useAppDispatch } from "../redux/hooks"; +import { StepWrap, maxRows } from "./Steps"; + +export const SetMapping = () => { + const { t } = useTranslation("steps"); + const dispatch = useAppDispatch(); + const file = useAppSelector(getFile); + const fileData = useAppSelector(getFileData); + const fileError = useAppSelector(getFileError); + // loading indicator only if new file needs to be read + const [ loading, setLoading ] = useState( !Array.isArray(fileData) && !fileError ); + + useEffect(() => { + const reader = new FileReader(); + + reader.onload = (event) => { + // parse the xlsx/csv file + const workbook = XLSX.read(event.target?.result, { type: 'binary' }); + const sheetName = workbook.SheetNames[0]; + const sheet = workbook.Sheets[sheetName]; + const sheetData: SheetData[] = XLSX.utils.sheet_to_json(sheet, { header: "A" }); + // check to see if sheet doesn't have too many rows (first row is headers, so doesn't count) + const rowCount = sheetData.length - 1; + if (rowCount > maxRows) { + dispatch(setFileError("tooManyRows")); + } else { + // save sheet data so we only need to load and parse once + dispatch(setFileData(sheetData)); + } + setLoading(false); + }; + + if (file && !fileData && !fileError) { + dispatch(setFileError(undefined)); + // if no file loaded yet, convert file url back to blob + (async () => { + const fetchedFile = await fetch(file.url); + const blob = await fetchedFile.blob(); + if (blob) { + reader.readAsBinaryString(blob); + } + })(); + } + }, [file, fileData, fileError]); + + return ( + + + + + + + {t("dataValue")} + {t("mapTo")} + + + + { ( loading || fileError ) && + + + { + loading ? + : + + {t(fileError as FileError, {max: maxRows})} + + } + + + } + {fileData && Object.entries(fileData[0]).map(([key, value]) => + + )} + +
+
+
+ ) +} + +export default SetMapping; + +const Row = ({rowKey, row}: {rowKey: string; row: string;}) => { + const { t } = useTranslation("steps"); + const [inputValue, setInputValue] = useState(''); + const mapping = useAppSelector(getMapping); + const dispatch = useAppDispatch(); + const fileData = useAppSelector(getFileData); + const columnData = fileData && fileData.slice(1).map( data => data[rowKey]) + const [ open, setOpen ] = useState(false); + console.log(columnData) + + const { data, isLoading } = useFetchDarwinTermsQuery(''); + + const selectValue = (row: string, value: DarwinOptions | null) => { + if (value === null) { + // Create a new object excluding the [row] key + const { [row]: _, ...rest } = mapping; + dispatch(setMapping(rest)); + } else { + dispatch(setMapping({ + ...mapping, + [row]: value, + })); + } + }; + + return ( + <> + + + setOpen(!open)} + > + {open ? : } + + + + {row} + + + option.label} + groupBy={(option) => option.header} + sx={{ width: 300 }} + renderInput={(params) => } + onChange={(_e, value) => selectValue(row, value)} + value={(mapping.hasOwnProperty(row) && mapping[row]) || null} + inputValue={inputValue} + isOptionEqualToValue={(option, value) => option.term_localName === value.term_localName} + onInputChange={(_e, newInputValue) => { + setInputValue(newInputValue); + }} + loading={isLoading} + size="small" + /> + + + {columnData && + + + + + {t('dataHeader')} + {columnData.map((item, i) => + {item} + )} + + + + } + + ) +} diff --git a/packages/file-mapper/src/features/Steps.tsx b/packages/file-mapper/src/features/Steps.tsx index 4d030347..8447d821 100644 --- a/packages/file-mapper/src/features/Steps.tsx +++ b/packages/file-mapper/src/features/Steps.tsx @@ -1,50 +1,9 @@ -import { useState, useEffect, type ReactNode } from "react"; -import { useDropzone } from "react-dropzone"; +import type { ReactNode } from "react"; import Box from "@mui/material/Box"; -import Stack from "@mui/material/Stack"; import Typography from "@mui/material/Typography"; -import Paper from "@mui/material/Paper"; -import Alert from "@mui/material/Alert"; -import { useTranslation } from "react-i18next"; -import * as XLSX from "xlsx"; -import TableContainer from '@mui/material/TableContainer'; -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableCell from '@mui/material/TableCell'; -import TableHead from '@mui/material/TableHead'; -import TableRow from '@mui/material/TableRow'; -import TextField from '@mui/material/TextField'; -import Autocomplete from '@mui/material/Autocomplete'; -import CircularProgress from '@mui/material/CircularProgress'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemButton from '@mui/material/ListItemButton'; -import ListItemText from '@mui/material/ListItemText'; -import ListItemIcon from '@mui/material/ListItemIcon'; -import Checkbox from '@mui/material/Checkbox'; -import Divider from '@mui/material/Divider'; -import useMediaQuery from '@mui/material/useMediaQuery'; -import { useTheme } from '@mui/material/styles'; -import type { DarwinOptions, Saves, SerializedFile, FileError } from "../types"; -import { - getFile, - setFile, - getMapping, - setMapping, - getSavedMap, - setSavedMap, - resetMapping, - setFileCols, - getFileCols, - resetFileCols, - getFileError, - setFileError, - resetFileError, -} from './fileMapperSlice'; -import { useFetchDarwinTermsQuery } from "./fileMapperApi"; -import { useAppSelector, useAppDispatch } from "../redux/hooks"; -const saves = [ +// some fake placeholder saves. todo. +export const saves = [ { name: "Fake save #1", date: "8-8-2024", @@ -58,253 +17,14 @@ const saves = [ ]; // define max number of rows a file selected for processing can contain -const maxRows = 10; +export const maxRows = 10; -const StepWrap = ({ title, children }: { title: string; children: ReactNode }) => +// define steps +export const steps = ['selectFile', 'createMapping', 'finish']; + +// wrapper for individual steps +export const StepWrap = ({ title, children }: { title: string; children: ReactNode }) => {title} {children} - - -export const Step1 = () => { - const { t } = useTranslation("steps"); - const theme = useTheme(); - const matches = useMediaQuery(theme.breakpoints.up('md')); - const dispatch = useAppDispatch(); - const file = useAppSelector(getFile); - const savedMap = useAppSelector(getSavedMap); - const fileError = useAppSelector(getFileError); - - const onDrop = async (files: File[]) => { - // serialize files to store in redux - const serializedFile: SerializedFile = { - name: files[0].name, - size: files[0].size, - url: URL.createObjectURL(files[0]), - }; - - dispatch(setFile(serializedFile)); - - // reset saved mapping and column values after selecting a different file - dispatch(resetMapping()); - dispatch(resetFileCols()); - dispatch(resetFileError()); - } - - const { - getRootProps, - getInputProps, - isDragActive, - } = useDropzone({ - onDrop, - multiple: false, - accept: { - "text/csv": [".csv"], - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"], - }, - }); - - return ( - - } - > - - {t("uploadNew")} - - - - {isDragActive ? t("dropNow") : t("drop")} - - - { file && - - - {t("selectedFile", { name: file.name, size: (file.size / 1024).toFixed(0) })} - - {fileError && - - {t(fileError, {max: maxRows})} - - } - - } - - {saves.length > 0 && - - {t("selectSave")} - - {saves.map( (save: Saves) => - - dispatch(setSavedMap(savedMap === save.id ? "" : save.id))} dense> - - - - - - - )} - - - } - - - ) -} - -export const Step2 = () => { - const { t } = useTranslation("steps"); - const dispatch = useAppDispatch(); - const file = useAppSelector(getFile); - const fileCols = useAppSelector(getFileCols); - const fileError = useAppSelector(getFileError); - // loading indicator only if new file needs to be read - const [ loading, setLoading ] = useState( !Array.isArray(fileCols) && !fileError ); - - useEffect(() => { - const reader = new FileReader(); - - reader.onload = (event) => { - // parse the xlsx/csv file - const workbook = XLSX.read(event.target?.result, { type: 'binary' }); - const sheetName = workbook.SheetNames[0]; - const sheet = workbook.Sheets[sheetName]; - const sheetData = XLSX.utils.sheet_to_json(sheet, { header: 1 })[0]; - // check to see if sheet doesn't have too many rows - const rowCount = XLSX.utils.sheet_to_json(sheet).length; - if (rowCount > maxRows) { - dispatch(setFileError("tooManyRows")); - } else { - // save column data to store so we only have to load and parse once - Array.isArray(sheetData) && dispatch(setFileCols(sheetData)); - } - setLoading(false); - }; - - if (file && !fileCols && !fileError) { - dispatch(setFileError(undefined)); - // if no file loaded yet, convert file url back to blob - (async () => { - const fetchedFile = await fetch(file.url); - const blob = await fetchedFile.blob(); - if (blob) { - reader.readAsBinaryString(blob); - } - })(); - } - }, [file, fileCols, fileError]); - - return ( - - - - - - {t("dataValue")} - {t("mapTo")} - - - - { ( loading || fileError ) && - - - { - loading ? - : - - {t(fileError as FileError, {max: maxRows})} - - } - - - } - {fileCols && fileCols.map((row) => ( - - ))} - -
-
-
- ) -} - -const Row = ({row}: {row: string}) => { - const { t } = useTranslation("steps"); - const [inputValue, setInputValue] = useState(''); - const mapping = useAppSelector(getMapping); - const dispatch = useAppDispatch(); - - const { data, isLoading } = useFetchDarwinTermsQuery(''); - - const selectValue = (row: string, value: DarwinOptions | null) => { - if (value === null) { - // Create a new object excluding the [row] key - const { [row]: _, ...rest } = mapping; - dispatch(setMapping(rest)); - } else { - dispatch(setMapping({ - ...mapping, - [row]: value, - })); - } - }; - - return ( - - - {row} - - - option.label} - groupBy={(option) => option.header} - sx={{ width: 300 }} - renderInput={(params) => } - onChange={(_e, value) => selectValue(row, value)} - value={(mapping.hasOwnProperty(row) && mapping[row]) || null} - inputValue={inputValue} - isOptionEqualToValue={(option, value) => option.term_localName === value.term_localName} - onInputChange={(_e, newInputValue) => { - setInputValue(newInputValue); - }} - loading={isLoading} - /> - - - ) -} - -export const Step3 = () => { - const { t } = useTranslation("steps"); - - return ( - - {t("saveMappingExtra")} - - - ) -} \ No newline at end of file +
\ No newline at end of file diff --git a/packages/file-mapper/src/features/fileMapperApi.ts b/packages/file-mapper/src/features/fileMapperApi.ts index 738737d9..cd249335 100644 --- a/packages/file-mapper/src/features/fileMapperApi.ts +++ b/packages/file-mapper/src/features/fileMapperApi.ts @@ -45,6 +45,7 @@ export const submitMappingApi = createApi({ // format headers const headers = { Authorization: `Bearer ${user?.access_token}`, + "auth-env-name": import.meta.env.VITE_ENV_NAME, }; let formData = new FormData(); diff --git a/packages/file-mapper/src/features/fileMapperSlice.ts b/packages/file-mapper/src/features/fileMapperSlice.ts index e7f00665..277bcfde 100644 --- a/packages/file-mapper/src/features/fileMapperSlice.ts +++ b/packages/file-mapper/src/features/fileMapperSlice.ts @@ -1,6 +1,6 @@ import { createSlice, type PayloadAction } from "@reduxjs/toolkit"; import { RootState } from "../redux/store"; -import type { SerializedFile, Mapping, FileError } from "../types"; +import type { SerializedFile, Mapping, FileError, SheetData } from "../types"; import type { FormConfig } from "@dans-framework/deposit"; const initialState: { @@ -9,16 +9,16 @@ const initialState: { file: SerializedFile | undefined; savedMap: string; form: FormConfig | undefined; - fileCols: string[] | undefined; fileError: string | undefined; + fileData: SheetData[] | undefined; } = { activeStep: 0, mapping: {}, file: undefined, savedMap: '', form: undefined, - fileCols: undefined, fileError: undefined, + fileData: undefined, }; export const fileMapperSlice = createSlice({ @@ -43,8 +43,11 @@ export const fileMapperSlice = createSlice({ saveData: (state, action) => { state.form = action.payload; }, - setFileCols: (state, action: PayloadAction) => { - state.fileCols = action.payload; + setFileData: (state, action: PayloadAction) => { + state.fileData = action.payload; + }, + resetFileData: (state) => { + state.fileData = initialState.fileData; }, setFileError: (state, action: PayloadAction) => { state.fileError = action.payload; @@ -52,20 +55,28 @@ export const fileMapperSlice = createSlice({ resetFileError: (state) => { state.fileError = initialState.fileError; }, - resetFileCols: (state) => { - state.fileCols = initialState.fileCols; - } }, }); -export const { setActiveStep, setMapping, resetMapping, setSavedMap, setFile, saveData, setFileCols, resetFileCols, setFileError, resetFileError } = fileMapperSlice.actions; +export const { + setActiveStep, + setMapping, + resetMapping, + setSavedMap, + setFile, + saveData, + setFileData, + resetFileData, + setFileError, + resetFileError, +} = fileMapperSlice.actions; export const getActiveStep = (state: RootState) => state.fileMapper.activeStep; export const getMapping = (state: RootState) => state.fileMapper.mapping; export const getSavedMap = (state: RootState) => state.fileMapper.savedMap; export const getFile = (state: RootState) => state.fileMapper.file; export const getForm = (state: RootState) => state.fileMapper.form; -export const getFileCols = (state: RootState) => state.fileMapper.fileCols; +export const getFileData = (state: RootState) => state.fileMapper.fileData; export const getFileError = (state: RootState) => state.fileMapper.fileError; export default fileMapperSlice.reducer; diff --git a/packages/file-mapper/src/languages/locales/en/steps.json b/packages/file-mapper/src/languages/locales/en/steps.json index 8c978e74..ce1f5bd3 100644 --- a/packages/file-mapper/src/languages/locales/en/steps.json +++ b/packages/file-mapper/src/languages/locales/en/steps.json @@ -18,5 +18,6 @@ "saveMapping": "Save as", "saveMappingExtra": "You can save your mapping for future datasets for easy reference. Please enter a name below.", "isLoading": "Loading...", - "tooManyRows": "Your data file contains too many rows to process using this tool. Make sure your file has less than {{max}} rows." + "tooManyRows": "Your data file contains too many rows to process using this tool. Make sure your file has less than {{max}} rows.", + "dataHeader": "Sheet data for this column" } \ No newline at end of file diff --git a/packages/file-mapper/src/languages/locales/nl/steps.json b/packages/file-mapper/src/languages/locales/nl/steps.json index 1c9de6af..fbac7560 100644 --- a/packages/file-mapper/src/languages/locales/nl/steps.json +++ b/packages/file-mapper/src/languages/locales/nl/steps.json @@ -18,5 +18,6 @@ "saveMapping": "Save as", "saveMappingExtra": "You can save your mapping for future datasets for easy reference. Please enter a name below.", "isLoading": "Loading...", - "tooManyRows": "Je data file contains too many rows to process using this tool. Make sure your file has less than {{max}} rows." + "tooManyRows": "Je data file contains too many rows to process using this tool. Make sure your file has less than {{max}} rows.", + "dataHeader": "Sheet data for this column" } \ No newline at end of file diff --git a/packages/file-mapper/src/types/index.ts b/packages/file-mapper/src/types/index.ts index 128b8cb0..d98b5218 100644 --- a/packages/file-mapper/src/types/index.ts +++ b/packages/file-mapper/src/types/index.ts @@ -10,7 +10,7 @@ export type DarwinOptions = { } export interface Mapping { - [key: string]: DarwinOptions + [key: string]: DarwinOptions; } export interface Saves { @@ -26,4 +26,8 @@ export interface SerializedFile { } export type FileError = - | "tooManyRows"; \ No newline at end of file + | "tooManyRows"; + +export interface SheetData { + [key: string]: string; +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aacf28a5..653d5c75 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -644,6 +644,9 @@ importers: '@dans-framework/utils': specifier: workspace:* version: link:../utils + '@mui/icons-material': + specifier: ^5.14.3 + version: 5.14.3(@mui/material@5.14.3(@emotion/react@11.11.1(@types/react@18.2.15)(react@18.2.0))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.2.15)(react@18.2.0))(@types/react@18.2.15)(react@18.2.0))(@types/react@18.2.15)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/react@18.2.15)(react@18.2.0) '@mui/material': specifier: ^5.14.3 version: 5.14.3(@emotion/react@11.11.1(@types/react@18.2.15)(react@18.2.0))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.2.15)(react@18.2.0))(@types/react@18.2.15)(react@18.2.0))(@types/react@18.2.15)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) From a2bcf6d53f3c861a01395905dbec2ba908308b3b Mon Sep 17 00:00:00 2001 From: Laurens Tobias Date: Fri, 23 Aug 2024 15:18:00 +0200 Subject: [PATCH 5/6] Updated dashboard config for new es instance and update details a bit --- apps/rda/src/config/elasticSearch.tsx | 48 +++++++++++++++++++++++---- apps/rda/src/pages/record/index.tsx | 10 ++++-- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/apps/rda/src/config/elasticSearch.tsx b/apps/rda/src/config/elasticSearch.tsx index dc6f3c99..269209a7 100644 --- a/apps/rda/src/config/elasticSearch.tsx +++ b/apps/rda/src/config/elasticSearch.tsx @@ -24,7 +24,7 @@ const fieldConfig: Partial = { export const elasticConfig = [ { name: "RDA Catalogue", - url: "https://es.ohsmart.dansdemo.nl/dans-rda2", + url: "https://tiger.laurenstobias.com/rda", fullTextFields: fieldConfig.fullTextFields, fullTextHighlight: fieldConfig.fullTextHighlight, resultBodyComponent: Rda2Result, @@ -33,7 +33,7 @@ export const elasticConfig = [ , + , + , + , @@ -224,9 +228,9 @@ function Metadata({ export function MetadataList({ record }: { record: RdaRecord | Result }) { const individuals = - record.individuals ? record.individuals.map((i: any) => i.fullname) : []; + record.individuals ? record.individuals.map((i: any) => i.fullName) : []; const workflows = - record.workflows ? record.workflows.map((w: any) => w.workflowstate) : []; + record.workflows ? record.workflows.map((w: any) => w.WorkflowState) : []; const rights = record.rights ? record.rights.map((r: any) => r.description) : []; const pathways = From aeb547e431b017e4a4105cf926527c6ee09763b1 Mon Sep 17 00:00:00 2001 From: Daan Janssen Date: Mon, 26 Aug 2024 14:41:35 +0200 Subject: [PATCH 6/6] updated disallowed filenames --- apps/ohsmart/index.html | 2 +- packages/deposit/src/features/files/FilesUpload.tsx | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/ohsmart/index.html b/apps/ohsmart/index.html index 0887a759..17279fb8 100644 --- a/apps/ohsmart/index.html +++ b/apps/ohsmart/index.html @@ -41,4 +41,4 @@ To create a production bundle, use `npm run build` or `yarn build`. --> - \ No newline at end of file + diff --git a/packages/deposit/src/features/files/FilesUpload.tsx b/packages/deposit/src/features/files/FilesUpload.tsx index a75ac637..523a5b11 100644 --- a/packages/deposit/src/features/files/FilesUpload.tsx +++ b/packages/deposit/src/features/files/FilesUpload.tsx @@ -88,7 +88,12 @@ const FilesUpload = () => { } // No files with these file names - if (file.name.indexOf("__generated__") !== -1) { + if ( + file.name.indexOf("__generated__form-metadata") !== -1 + // oh smart specific. todo: move this all to form config. + || file.name.toLowerCase() === "oral history metadata private.txt" + || file.name.toLowerCase() === "oral history metadata public.txt" + ) { return { code: "file-not-allowed", message: t("fileNotAllowed"),