UNPKG

@o2xp/react-datatable

Version:

@o2xp/react-datatable is a modulable component to render data in a table with some nice features !

1,605 lines (1,425 loc) 39.6 kB
import deepmerge from "deepmerge"; import arrayMove from "array-move"; import { chunk, cloneDeep, orderBy as orderByFunction, differenceBy } from "lodash"; import { tableRef } from "../../components/DatatableCore/Body/Body"; const Fuse = require("fuse.js"); const optionsFuse = { shouldSort: true, threshold: 0.0, location: 0, distance: 100, maxPatternLength: 32, minMatchCharLength: 1 }; const defaultState = { title: "", currentScreen: "", dimensions: { datatable: { width: "100%", height: "100%", widthNumber: 0, totalWidthNumber: 0 }, header: { height: "0px", heightNumber: 0 }, body: { heightNumber: 0 }, row: { height: "33px", heightNumber: 0 }, columnSizeMultiplier: 1, isScrolling: false }, keyColumn: null, font: "Roboto", data: { columns: [], rows: [] }, pagination: { pageSelected: 1, pageTotal: 1, rowsPerPageSelected: "", rowsCurrentPage: [], rowsToUse: [] }, newRows: [], rowsDeleted: [], rowsEdited: [], rowsGlobalEdited: [], rowsSelected: [], actions: null, refreshRows: null, isRefreshing: false, stripped: false, areFilterFieldsDisplayed: false, isSearchFieldDisplayed: false, filterTerms: {}, searchTerm: "", filterResultForEachColumn: {}, orderBy: [], features: { canEdit: false, canEditRow: null, canGlobalEdit: false, canPrint: false, canDownload: false, canAdd: false, canDelete: false, canDuplicate: false, canSearch: false, canCreatePreset: false, canRefreshRows: false, canOrderColumns: false, canSelectRow: false, canSaveUserConfiguration: false, columnsPresetsToDisplay: [], localStoragePresets: [], editableIdNewRow: [], userConfiguration: { columnsOrder: [], copyToClipboard: false }, rowsPerPage: { available: [10, 25, 50, 100, "All"], selected: "All" }, additionalActions: [], additionalIcons: [], selectionIcons: [] } }; const overwriteMerge = (destinationArray, sourceArray) => sourceArray; const convertSizeToNumber = (val, height) => { const splitSize = val.match(/[0-9]+|(px|%|vw|vh)/gi); let valSize = splitSize[0]; const unitSize = splitSize[1]; if (unitSize === "px") { valSize = Number(valSize); } if (unitSize === "vw") { valSize = window.innerWidth * (valSize / 100); } if (unitSize === "vh") { valSize = window.innerHeight * (valSize / 100); } if (unitSize === "%") { if (height) { valSize = document.getElementById("o2xp").parentElement.clientHeight * (valSize / 100); } else { valSize = document.getElementById("o2xp").parentElement.clientWidth * (valSize / 100); } } return valSize; }; const updateRowSizeMultiplier = state => { const { canEdit, canDelete, canSelectRow, canDuplicate } = state.features; const numberOfActions = [ canEdit, canDelete, canSelectRow, canDuplicate ].filter(v => v).length; const colDisplayed = []; state.data.columns.forEach(col => { if (state.features.userConfiguration.columnsOrder.includes(col.id)) { colDisplayed.push(col); } }); const widthColDisplayed = colDisplayed.map(col => { const column = col; if (column.colSize && col.id !== "o2xpActions") { return Number(column.colSize.split("px")[0]) + 35; } if (col.id !== "o2xpActions") { column.colSize = "100px"; return 100; } return 0; }); const totalWidthColDisplayed = widthColDisplayed.reduce((a, b) => { return a + b; }); const splitWidth = state.dimensions.datatable.width.match( /[0-9]+|(px|%|vw|vh)/gi ); let widthTable = splitWidth[0]; const unitWidthTable = splitWidth[1]; if (unitWidthTable === "vw") { widthTable = (window.innerWidth * widthTable) / 100; } if (unitWidthTable === "%") { widthTable = document.getElementById("o2xp").parentElement.clientWidth * (widthTable / 100); } if (numberOfActions.length > 0) { widthTable -= 50; } widthTable -= state.features.additionalActions.length * 50; if (canEdit) { widthTable -= 100; } if (canDelete) { widthTable -= 100; } if (canDuplicate) { widthTable -= 50; } if (canSelectRow) { widthTable -= 50; } if (canEdit && canDelete) { widthTable += 100; } widthTable -= state.features.userConfiguration.columnsOrder.length * 50; widthTable -= 22; let mult = 1; if (widthTable > totalWidthColDisplayed) { mult = widthTable / totalWidthColDisplayed; } return mult; }; const totalWidth = state => { const colDisplayed = []; state.data.columns.forEach(col => { if ( state.features.userConfiguration.columnsOrder.includes(col.id) && col.id !== "o2xpActions" ) { colDisplayed.push(col); } }); const widthColDisplayed = colDisplayed.map( col => Number(col.colSize.split("px")[0]) + 50 ); let totalWidthColDisplayed = widthColDisplayed.reduce((a, b) => { return a + b; }); totalWidthColDisplayed *= state.dimensions.columnSizeMultiplier; totalWidthColDisplayed -= 22; return totalWidthColDisplayed; }; const calcComponentSize = state => { const newState = { ...state, dimensions: { ...state.dimensions, datatable: { ...state.dimensions.datatable, widthNumber: convertSizeToNumber(state.dimensions.datatable.width), totalWidthNumber: totalWidth(state) }, row: { ...state.dimensions.row, heightNumber: convertSizeToNumber(state.dimensions.row.height) }, columnSizeMultiplier: updateRowSizeMultiplier(state) } }; const heightNumber = convertSizeToNumber( newState.dimensions.datatable.height, true ); newState.dimensions.body.heightNumber = heightNumber - newState.dimensions.header.heightNumber - 50 - 70; return newState; }; const setPagination = ({ state, newPageSelected = null, newRowsPerPageSelected = null }) => { const { filterTerms, searchTerm, orderBy, filterResultForEachColumn } = state; let rowsToUse = state.data.rows; const filterTermsKeys = Object.keys(filterTerms); const amountOfFilteredColumn = filterTermsKeys.length > 0 ? Object.keys(filterResultForEachColumn).length : 0; if (amountOfFilteredColumn > 0) { filterTermsKeys.forEach(key => { const fuse = new Fuse(filterResultForEachColumn[key], { ...optionsFuse, keys: [key] }); filterResultForEachColumn[key] = fuse.search(filterTerms[key]); }); if (amountOfFilteredColumn === 1) { rowsToUse = filterResultForEachColumn[filterTermsKeys[0]]; } else { Object.keys(filterResultForEachColumn).forEach(key => { rowsToUse = rowsToUse.filter(row => filterResultForEachColumn[key].includes(row) ); }); } } if (searchTerm.length) { const fuse = new Fuse(state.data.rows, { ...optionsFuse, keys: state.features.userConfiguration.columnsOrder }); rowsToUse = fuse.search(searchTerm); } if (orderBy) { rowsToUse = orderByFunction( rowsToUse, orderBy.map(el => el.id), orderBy.map(el => el.value) ); } const rowsPerPageSelected = newRowsPerPageSelected || state.pagination.rowsPerPageSelected || state.features.rowsPerPage.selected; let pageSelected = rowsPerPageSelected === "All" ? 1 : newPageSelected || state.pagination.pageSelected; const pageTotal = rowsPerPageSelected === "All" ? 1 : Math.ceil(rowsToUse.length / rowsPerPageSelected); pageSelected = pageSelected > pageTotal ? pageTotal : pageSelected; pageSelected = pageSelected < 1 ? 1 : pageSelected; let rowsCurrentPage = []; if (rowsToUse.length > 0) { rowsCurrentPage = rowsPerPageSelected === "All" ? rowsToUse : chunk(rowsToUse, rowsPerPageSelected)[ pageSelected ? pageSelected - 1 : 0 ]; } if ( tableRef && tableRef.current && (newPageSelected || newRowsPerPageSelected) ) { tableRef.current.scrollToItem(0); } return { pageSelected, pageTotal, rowsPerPageSelected, rowsCurrentPage, rowsToUse }; }; const toggleFilterFieldsDisplay = state => { const newState = { ...state, areFilterFieldsDisplayed: !state.areFilterFieldsDisplayed, searchTerm: "", filterTerms: {}, isSearchFieldDisplayed: false }; return { ...newState, pagination: setPagination({ state: newState }) }; }; const toggleSearchFieldDisplay = state => { const newState = { ...state, isSearchFieldDisplayed: !state.isSearchFieldDisplayed, searchTerm: "", filterTerms: {}, areFilterFieldsDisplayed: false }; return { ...newState, pagination: setPagination({ state: newState }) }; }; const removeNullUndefined = obj => { Object.keys(obj).forEach(key => { if ( obj[key] && typeof obj[key] === "object" && key !== "icon" && key !== "rows" ) removeNullUndefined(obj[key]); /* eslint-disable no-param-reassign */ else if ( obj[key] == null || obj[key] === undefined ) delete obj[key]; // delete }); return obj; }; const updateRows = ({ rows, oldRows, rowsEdited, newRows, rowsDeleted, rowsGlobalEdited, keyColumn }) => { const oldRowsId = oldRows.map(row => row[keyColumn]); const toRemove = differenceBy(oldRows, rows, keyColumn); const toRemoveId = toRemove.map(row => row[keyColumn]); const newRowsAdded = newRows.filter( row => !toRemoveId.includes(row[keyColumn]) ); const newRowsGlobalEdited = rowsGlobalEdited.filter( row => !toRemoveId.includes(row[keyColumn]) ); // If rows has beend added then deleted no adding it to rowsDeleted const newRowsAddedId = newRows.map(row => row[keyColumn]); const newRowsDeleted = deepmerge(rowsDeleted, toRemove).filter( row => !newRowsAddedId.includes(row[keyColumn]) ); const newRowsEdited = []; // Add new rows rows.forEach(row => { // if row already exist, push existing if (oldRowsId.includes(row[keyColumn])) { newRowsEdited.push( rowsEdited.find(rowEdited => rowEdited[keyColumn] === row[keyColumn]) ); } else { // if not, push new const newRow = { ...row, idOfColumnErr: [], hasBeenEdited: true }; newRowsAdded.push(newRow); newRowsGlobalEdited.push(newRow); newRowsEdited.push(newRow); } }); return { newRowsEdited, newRowsAdded, newRowsDeleted, newRowsGlobalEdited }; }; const getColumnOrderFromActivePresets = state => { const { columnsOrder } = state.features.userConfiguration; const { currentScreen } = state; const { localStoragePresets } = state.features; if (localStoragePresets === null || localStoragePresets === undefined) return columnsOrder; const currentScreenLocalPresets = localStoragePresets.filter( preset => preset.screen === currentScreen ); const currentScreenActivePresets = currentScreenLocalPresets.filter( preset => preset.isActive ); let newColumnOrder = ["o2xpActions"]; if (currentScreenActivePresets.length > 0) { currentScreenActivePresets.forEach(preset => { newColumnOrder = [ ...new Set([...newColumnOrder, ...preset.columnsToShow]) ]; }); } else { newColumnOrder = columnsOrder; } return newColumnOrder; }; const initializeOptions = ( state, { optionsInit, forceRerender = false, actions = null, refreshRows = null, stripped = false } ) => { let newState = deepmerge( forceRerender ? defaultState : state, removeNullUndefined({ ...optionsInit }), { arrayMerge: overwriteMerge } ); const { canEdit, canDelete, canSelectRow, canDuplicate, isUpdatingRows } = newState.features; if (isUpdatingRows) { const { rows } = newState.data; const { rows: oldRows } = state.data; const { rowsEdited, newRows, rowsDeleted, keyColumn, rowsGlobalEdited } = newState; if (newState.rowsEdited.length > 0) { const { newRowsEdited, newRowsAdded, newRowsDeleted, newRowsGlobalEdited } = updateRows({ rows, oldRows, rowsEdited, newRows, rowsDeleted, rowsGlobalEdited, keyColumn }); const presetList = JSON.parse(localStorage.getItem("presetList")); newState = { ...newState, rowsEdited: newRowsEdited, newRows: newRowsAdded, rowsDeleted: newRowsDeleted, rowsGlobalEdited: newRowsGlobalEdited, features: { ...newState.features, localStoragePresets: presetList ? presetList.filter(preset => preset.screen === state.currentScreen) .features : presetList } }; } } newState.actions = actions; newState.refreshRows = refreshRows; newState.stripped = stripped; const { height } = newState.dimensions.row; newState.dimensions.row.height = height.split("px")[0] < 33 ? "33px" : height; if (newState.features.userConfiguration.columnsOrder.length === 0) { newState.features.userConfiguration.columnsOrder = optionsInit.data.columns.map( col => col.id ); } const colsLocked = optionsInit.data.columns .filter(col => col.locked) .reverse(); if (colsLocked.length > 0) { colsLocked.forEach(({ id }) => { const index = newState.features.userConfiguration.columnsOrder.findIndex( col => col === id ); newState.features.userConfiguration.columnsOrder.splice(index, 1); newState.features.userConfiguration.columnsOrder.unshift(id); }); } const actionsUser = [canEdit, canDelete, canSelectRow]; const numberOfActions = actionsUser.filter(v => v).length; if ( (numberOfActions > 0 || newState.features.additionalActions.length > 0 || canDuplicate) && !newState.data.columns.find(col => col.id === "o2xpActions") ) { newState.features.userConfiguration.columnsOrder = newState.features.userConfiguration.columnsOrder.filter( colId => colId !== "o2xpActions" ); newState.data.columns = newState.data.columns.filter( column => column.id !== "o2xpActions" ); newState.features.userConfiguration.columnsOrder.unshift("o2xpActions"); let colSize = canDuplicate ? 50 : 0; switch (actionsUser.join(" ")) { case "true true true": case "false true true": case "true false true": colSize += 150; break; case "true true false": case "false true false": case "true false false": colSize += 100; break; case "false false true": colSize += 50; break; default: colSize += 0; break; } colSize = `${colSize + newState.features.additionalActions.length * 50}px`; newState.data.columns.unshift({ id: "o2xpActions", label: "Actions", colSize, hiddenCreate: true, editable: false }); } newState.pagination = setPagination({ state: newState, current: newState.pagination.current, rowsPerPageSelected: newState.features.rowsPerPage.selected }); const { title, features } = newState; const { canGlobalEdit, canPrint, canDownload, canSearch, canCreatePreset, canRefreshRows, canOrderColumns, canSaveUserConfiguration, additionalIcons, selectionIcons } = features; const hasHeader = canGlobalEdit || canPrint || canDownload || canSearch || canCreatePreset || canRefreshRows || canOrderColumns || canSaveUserConfiguration || title.length > 0 || additionalIcons.length > 0 || selectionIcons.length > 0; newState.dimensions.columnSizeMultiplier = updateRowSizeMultiplier(newState); newState.dimensions.datatable.widthNumber = convertSizeToNumber( newState.dimensions.datatable.width ); const heightNumber = convertSizeToNumber( newState.dimensions.datatable.height, true ); newState.dimensions.header.height = hasHeader ? "60px" : "0px"; newState.dimensions.header.heightNumber = convertSizeToNumber( newState.dimensions.header.height ); newState.dimensions.row.heightNumber = convertSizeToNumber( newState.dimensions.row.height ); newState.dimensions.body.heightNumber = heightNumber - newState.dimensions.header.heightNumber - 50 - 70; newState = { ...newState, features: { ...newState.features, localStoragePresets: JSON.parse(localStorage.getItem("presetList")) } }; newState = { ...newState, features: { ...newState.features, userConfiguration: { ...newState.features.userConfiguration, columnsOrder: getColumnOrderFromActivePresets(newState) } } }; return newState; }; const sortColumn = (state, { newIndex, oldIndex }) => { const newState = { ...state, features: { ...state.features, userConfiguration: { ...state.features.userConfiguration, columnsOrder: arrayMove( state.features.userConfiguration.columnsOrder, oldIndex, newIndex ) } } }; return newState; }; const setRowsPerPage = (state, newRowsPerPageSelected) => { return { ...state, pagination: setPagination({ state, newPageSelected: 1, newRowsPerPageSelected }) }; }; const setPage = (state, newPageSelected) => { return { ...state, pagination: setPagination({ state, newPageSelected }) }; }; const setIsScrolling = (state, isScrolling) => { return { ...state, dimensions: { ...state.dimensions, isScrolling } }; }; const addRowEdited = (state, row) => { const { rowsEdited, keyColumn } = state; if (rowsEdited.find(re => re[keyColumn] === row[keyColumn])) { return state; } return { ...state, rowsEdited: [ ...rowsEdited, { ...row, idOfColumnErr: [], hasBeenEdited: false } ] }; }; const addAllRowsToEdited = state => { const { actions, data } = state; const { rows } = data; if (actions) { actions({ type: "editMode" }); } const rowsEdited = rows.map(row => { return { ...row, idOfColumnErr: [], hasBeenEdited: false }; }); return { ...state, rowsEdited }; }; const checkHasBeenEdited = ({ rows, rowEdited, keyColumn }) => { const rowNonEdited = rows.find( row => row[keyColumn] === rowEdited[keyColumn] ); let hasBeenEdited = false; Object.keys(rowNonEdited).forEach(key => { if ( rowNonEdited[key] !== rowEdited[key] && !(rowNonEdited[key] == null && rowEdited[key].length === 0) ) { hasBeenEdited = true; } }); return hasBeenEdited; }; const addNewRow = (state, payload) => { const { keyColumn, data, pagination, rowsGlobalEdited, rowsEdited, features } = state; const { columns, rows } = data; let newRow = { hasBeenEdited: true, idOfColumnErr: [], [keyColumn]: `_${Math.random() .toString(36) .substr(2, 18)}` }; if (features.editableIdNewRow.length > 0) { newRow.editableId = features.editableIdNewRow; } columns.forEach(col => { switch (col.id) { case "o2xpActions": case "modalOpen": break; case keyColumn: newRow[keyColumn] = `_${Math.random() .toString(36) .substr(2, 9)}`; break; default: newRow[col.id] = ""; } }); newRow = { ...newRow, ...payload }; const newState = { ...state, rowsGlobalEdited: [newRow, ...rowsGlobalEdited], rowsEdited: [newRow, ...rowsEdited], data: { ...data, rows: [newRow, ...rows] }, newRows: [...state.newRows, newRow] }; const newPagination = setPagination({ state: newState, newPageSelected: pagination.pageSelected, newRowsPerPageSelected: pagination.rowsPerPageSelected }); return { ...newState, pagination: newPagination }; }; const setRowEdited = (state, { columnId, rowId, newValue, error }) => { const { rowsEdited, keyColumn, rowsGlobalEdited, features } = state; let newRowsGlobalEdited = [...rowsGlobalEdited]; const rowsEditedUpdate = rowsEdited.map(row => { if (row[keyColumn] === rowId) { let idOfColumnErrUpdate = row.idOfColumnErr; if (error) { if (!idOfColumnErrUpdate.includes(columnId)) { idOfColumnErrUpdate = [...idOfColumnErrUpdate, columnId]; } } else { idOfColumnErrUpdate = idOfColumnErrUpdate.filter(e => e !== columnId); } /* eslint-disable no-restricted-globals */ const r = { ...row, [columnId]: isNaN(newValue) && typeof newValue !== "string" ? "" : newValue, idOfColumnErr: idOfColumnErrUpdate }; const hasBeenEdited = checkHasBeenEdited({ rows: state.data.rows, rowEdited: r, keyColumn }); if (features.canGlobalEdit) { newRowsGlobalEdited = newRowsGlobalEdited.filter( rowGlobalEdited => rowGlobalEdited[keyColumn] !== r[keyColumn] ); if (hasBeenEdited) { newRowsGlobalEdited = [...newRowsGlobalEdited, r]; } } return { ...r, hasBeenEdited }; } return row; }); return { ...state, rowsEdited: rowsEditedUpdate, rowsGlobalEdited: newRowsGlobalEdited }; }; const mergeArrayInArray = (firstArray, keyColumn, secondArray) => { return [ ...secondArray.map(el => { const object = firstArray.find(obj => el[keyColumn] === obj[keyColumn]); if (object) { return object; } return el; }) ]; }; const saveRowEdited = (state, payload) => { const row = payload; delete row.idOfColumnErr; delete row.hasBeenEdited; const { data, rowsEdited, keyColumn, pagination, actions } = state; const { rows, columns } = state.data; const rowNonEdited = rows.find(r => r[keyColumn] === row[keyColumn]); columns.forEach(col => { if ( row[col.id] != null && row[col.id].length === 0 && rowNonEdited[col.id] == null ) { row[col.id] = rowNonEdited[col.id]; } }); if (actions) { actions({ type: "save", payload: row }); } return { ...state, data: { ...data, rows: mergeArrayInArray([row], keyColumn, data.rows) }, pagination: { ...pagination, rowsCurrentPage: mergeArrayInArray( [row], keyColumn, pagination.rowsCurrentPage ) }, rowsGlobalEdited: [], rowsEdited: [...rowsEdited.filter(r => r[keyColumn] !== row[keyColumn])] }; }; const saveAllRowsEdited = state => { const { data, keyColumn, rowsGlobalEdited, rowsDeleted, actions, pagination, newRows } = state; const rows = rowsGlobalEdited.map(row => { const r = row; delete r.idOfColumnErr; delete r.hasBeenEdited; return r; }); rowsDeleted.forEach(rd => delete rd.indexInsert); const newRowsId = newRows.map(nr => nr[keyColumn]); const rowsEdited = rowsGlobalEdited.filter( rge => !newRowsId.includes(rge[keyColumn]) ); const rowsAdded = rowsGlobalEdited.filter(rge => newRowsId.includes(rge[keyColumn]) ); const { rows: rowsNonEdited, columns } = state.data; const rowsEditedId = rowsEdited.map(re => re[keyColumn]); const rowsNonEditedFiltered = rowsNonEdited.filter(rne => rowsEditedId.includes(rne[keyColumn]) ); rowsEdited.forEach(re => { const rowNonEdited = rowsNonEditedFiltered.find( rne => rne[keyColumn] === re[keyColumn] ); columns.forEach(col => { if ( re[col.id] != null && re[col.id].length === 0 && rowNonEdited[col.id] == null ) { re[col.id] = rowNonEdited[col.id]; } }); }); const rowsMerged = mergeArrayInArray(rows, keyColumn, data.rows); if (actions) { actions({ type: "save", payload: { rows: rowsMerged, rowsDeleted, rowsEdited, rowsAdded } }); } return { ...state, data: { ...data, rows: rowsMerged }, pagination: { ...pagination, rowsCurrentPage: mergeArrayInArray( rows, keyColumn, pagination.rowsCurrentPage ) }, rowsDeleted: [], newRows: [], rowsGlobalEdited: [], rowsEdited: [] }; }; const revertRowEdited = (state, payload) => { const row = payload; const { rowsEdited, keyColumn } = state; return { ...state, rowsEdited: [...rowsEdited.filter(r => r[keyColumn] !== row[keyColumn])] }; }; const revertAllRowsToEdited = state => { const { newRows, data, keyColumn, pagination, rowsDeleted, actions } = state; const newRowsId = newRows.map(r => r[keyColumn]); const { rows } = data; rowsDeleted.forEach((rd, i) => rows.splice(rd.indexInsert + i, 0, rd)); if (actions) { actions({ type: "revert" }); } const newState = { ...state, newRows: [], rowsEdited: [], rowsGlobalEdited: [], rowsDeleted: [], data: { ...data, rows: [...rows.filter(r => !newRowsId.includes(r[keyColumn]))] } }; const newPagination = setPagination({ state: newState, newPageSelected: pagination.pageSelected, newRowsPerPageSelected: pagination.rowsPerPageSelected }); return { ...newState, pagination: newPagination }; }; const addToDeleteRow = (state, payload) => { const row = payload; const { data, keyColumn, rowsEdited, rowsGlobalEdited, rowsDeleted, newRows } = state; const newlyAdded = newRows.filter(nr => nr[keyColumn] === row[keyColumn]).length > 0; const index = data.rows.findIndex(r => r[keyColumn] === row[keyColumn]); row.indexInsert = index; let newState = { ...state, rowsEdited: [...rowsEdited.filter(r => r[keyColumn] !== row[keyColumn])], rowsGlobalEdited: [ ...rowsGlobalEdited.filter(r => r[keyColumn] !== row[keyColumn]) ], data: { ...data, rows: [...data.rows.filter(r => r[keyColumn] !== row[keyColumn])] } }; newState = { ...newState, rowsDeleted: newlyAdded ? rowsDeleted : [...rowsDeleted, row], pagination: setPagination({ state: newState }) }; return newState; }; const deleteRow = (state, payload) => { const row = payload; const { data, keyColumn, actions, rowsEdited, rowsGlobalEdited } = state; if (actions) { actions({ type: "delete", payload: row }); } let newState = { ...state, rowsEdited: [...rowsEdited.filter(r => r[keyColumn] !== row[keyColumn])], rowsGlobalEdited: [ ...rowsGlobalEdited.filter(r => r[keyColumn] !== row[keyColumn]) ], data: { ...data, rows: [...data.rows.filter(r => r[keyColumn] !== row[keyColumn])] } }; newState = { ...newState, pagination: setPagination({ state: newState }) }; return newState; }; const selectRow = (state, payload) => { const { keyColumn, rowsSelected, actions } = state; const { checked, row } = payload; let newRowsSelected; if (checked) { newRowsSelected = [...rowsSelected, row]; } else { newRowsSelected = [ ...rowsSelected.filter(r => r[keyColumn] !== row[keyColumn]) ]; } if (actions) { actions({ type: "select", payload: newRowsSelected }); } return { ...state, rowsSelected: newRowsSelected }; }; const setRowsSelected = (state, payload) => { return { ...state, rowsSelected: payload }; }; const setRowsGlobalSelected = (state, payload) => { const { keyColumn, rowsSelected, actions } = state; const { rows, checked } = payload; const ids = rows.map(row => row[keyColumn]); const idsRowsSelected = rowsSelected.map(row => row[keyColumn]); let newRowsSelected; if (checked) { newRowsSelected = [ ...rowsSelected, ...rows.filter(row => !idsRowsSelected.includes(row[keyColumn])) ]; } else { newRowsSelected = rowsSelected.filter(row => !ids.includes(row[keyColumn])); } if (actions) { actions({ type: "select", payload: newRowsSelected }); } return { ...state, rowsSelected: newRowsSelected }; }; const search = (state, payload) => { const newState = { ...state, searchTerm: payload }; return { ...newState, pagination: setPagination({ state: newState }) }; }; const filterInColumn = (state, [searchTerm, columnToSearchIn]) => { const newfilterTerms = { ...state.filterTerms }; const newfilterResultForEachColumn = { ...state.filterResultForEachColumn }; if (searchTerm.length > 0) { newfilterTerms[columnToSearchIn] = searchTerm; newfilterResultForEachColumn[columnToSearchIn] = state.data.rows; } else { delete newfilterTerms[columnToSearchIn]; delete newfilterResultForEachColumn[columnToSearchIn]; } const newState = { ...state, filterTerms: { ...newfilterTerms }, filterResultForEachColumn: { ...newfilterResultForEachColumn } }; return { ...newState, pagination: setPagination({ state: newState }) }; }; const setColumnVisibilty = (state, payload) => { const { columns } = state.data; const { columnsOrder } = state.features.userConfiguration; const orderDisplayed = columns.filter( col => columnsOrder.includes(col.id) || col.id === payload.id ); const columnsId = orderDisplayed.map(col => col.id); const index = columnsId.indexOf(payload.id); let newColumnsOrder; if (columnsOrder.includes(payload.id)) { newColumnsOrder = columnsOrder.filter(col => col !== payload.id); } else { columnsOrder.splice(index, 0, payload.id); const colsLocked = columns.filter(col => col.locked); if (colsLocked.length > 0) { colsLocked.forEach(({ id }) => { const i = state.features.userConfiguration.columnsOrder.findIndex( col => col === id ); columnsOrder.splice(i, 1); columnsOrder.unshift(id); const o2xpActionsIndex = columnsOrder.findIndex( col => col === "o2xpActions" ); if (o2xpActionsIndex >= 0) { columnsOrder.splice(o2xpActionsIndex, 1); columnsOrder.unshift("o2xpActions"); } }); } newColumnsOrder = cloneDeep(columnsOrder); } const newState = { ...state, orderBy: state.orderBy.filter(el => el.id !== payload.id), features: { ...state.features, userConfiguration: { ...state.features.userConfiguration, columnsOrder: newColumnsOrder } }, searchTerm: "", filterTerms: {} }; return { ...newState, pagination: setPagination({ state: newState }), dimensions: { ...newState.dimensions, columnSizeMultiplier: updateRowSizeMultiplier(newState) } }; }; // TODO: make the function more readable const handlePresetDisplay = (state, payload) => { const currentPreset = payload; const allColumnsNames = state.data.columns.map(column => column.id); const columnsCurrentlyDisplayed = state.features.userConfiguration.columnsOrder; const predefinedPresets = state.features.columnsPresetsToDisplay; const parsedPresetList = JSON.parse(localStorage.getItem("presetList")); const localPresets = parsedPresetList ? parsedPresetList.filter(preset => preset.screen === state.currentScreen) : parsedPresetList; const allPresets = predefinedPresets.concat(localPresets); if (currentPreset.type === "predefinedPreset") { payload.isActive = !payload.isActive; } else { // Reverses isActive state of the selectedState in the localStorage and of currentPreset const presetIndex = localPresets.findIndex( preset => preset.presetName === currentPreset.presetName ); localPresets[presetIndex].isActive = !localPresets[presetIndex].isActive; localStorage.setItem("presetList", JSON.stringify(parsedPresetList)); currentPreset.isActive = !currentPreset.isActive; } let newColumnsOrder = []; // TODO: mettre dans le localstorage const activePresets = allPresets.filter(preset => preset.isActive); if (currentPreset.isActive && activePresets.length === 1) { // If the selected preset is the only one active, show only its columns newColumnsOrder = ["o2xpActions", ...currentPreset.columnsToShow]; } if (!currentPreset.isActive && activePresets.length === 0) { // If no more preset are selected display all columns again newColumnsOrder = allColumnsNames; } if (currentPreset.isActive && activePresets.length >= 2) { // If 2 or more presets are selected, merge their columns without duplicates and display them activePresets.forEach(() => { newColumnsOrder = [ ...new Set([ ...columnsCurrentlyDisplayed, ...currentPreset.columnsToShow ]) ]; }); } if (!currentPreset.isActive && activePresets.length >= 1) { // If there are more than 2 preset selected and you remove one, remove the columns used by the selected preset and not used by the others newColumnsOrder = columnsCurrentlyDisplayed; const activePresetsAllColumns = []; activePresets.forEach(activePreset => { activePreset.columnsToShow.forEach(col => { activePresetsAllColumns.push(col); }); }); const currentPresetUniqueColumns = []; currentPreset.columnsToShow.forEach(col => { if (!activePresetsAllColumns.includes(col)) { currentPresetUniqueColumns.push(col); } }); currentPresetUniqueColumns.forEach(col => { newColumnsOrder = newColumnsOrder.filter(column => column !== col); }); } const newState = { ...state, features: { ...state.features, userConfiguration: { ...state.features.userConfiguration, columnsOrder: newColumnsOrder } }, searchTerm: "", filterTerms: {} }; return { ...newState, pagination: setPagination({ state: newState }) }; }; const setUserConfiguration = (state, payload) => { const { columnsOrder, copyToClipboard, action } = payload; const { actions } = state; if (action === "save" && actions) { actions({ type: "saveUserConfiguration", payload: { columnsOrder, copyToClipboard } }); } const newState = { ...state, features: { ...state.features, userConfiguration: { columnsOrder, copyToClipboard } } }; return { ...newState, dimensions: { ...newState.dimensions, columnSizeMultiplier: updateRowSizeMultiplier(newState) } }; }; const refreshRowsStarted = state => { const newState = { ...state, isRefreshing: true, rowsEdited: [], rowsSelected: [] }; return newState; }; const refreshRowsSuccess = (state, payload) => { const newState = { ...state, searchTerm: "", filterTerms: {}, data: { ...state.data, rows: payload }, isRefreshing: false }; return { ...newState, pagination: setPagination({ state: newState }) }; }; const refreshRowsError = state => { return { ...state, isRefreshing: false }; }; const orderByColumns = (state, payload) => { const { orderBy } = state; const index = orderBy.findIndex(el => el.id === payload); let newOrderBy = [...orderBy]; if (index === -1) { newOrderBy = [...newOrderBy, { id: payload, value: "asc" }]; } else if (newOrderBy[index].value === "desc") { newOrderBy = newOrderBy.filter(el => el.id !== payload); } else { newOrderBy[index].value = "desc"; } const newState = { ...state, orderBy: newOrderBy }; return { ...newState, pagination: setPagination({ state: newState }) }; }; const insert = (arr, index, newItem) => [ ...arr.slice(0, index), newItem, ...arr.slice(index) ]; const duplicateRow = (state, payload) => { const { keyColumn, data, pagination, rowsGlobalEdited, rowsEdited } = state; const { rows } = data; const newRow = { ...payload, hasBeenEdited: true, idOfColumnErr: [], [keyColumn]: `_${Math.random() .toString(36) .substr(2, 18)}` }; const index = data.rows.findIndex(r => r[keyColumn] === payload[keyColumn]) + 1; const newState = { ...state, rowsGlobalEdited: insert(rowsGlobalEdited, index, newRow), rowsEdited: insert(rowsEdited, index, newRow), data: { ...data, rows: insert(rows, index, newRow) }, newRows: [...state.newRows, newRow] }; const newPagination = setPagination({ state: newState, newPageSelected: pagination.pageSelected, newRowsPerPageSelected: pagination.rowsPerPageSelected }); return { ...newState, pagination: newPagination }; }; const datatableReducer = (state = defaultState, action) => { const { payload, type } = action; switch (type) { case "INITIALIZE_OPTIONS": return initializeOptions(state, payload); case "UPDATE_COMPONENT_SIZE": return calcComponentSize(state); case "SORT_COLUMNS": return sortColumn(state, payload); case "SET_ROWS_PER_PAGE": return setRowsPerPage(state, payload); case "SET_PAGE": return setPage(state, payload); case "SET_IS_SCROLLING": return setIsScrolling(state, payload); case "ADD_ROW_EDITED": return addRowEdited(state, payload); case "ADD_NEW_ROW": return addNewRow(state, payload); case "ADD_ALL_ROWS_TO_EDITED": return addAllRowsToEdited(state); case "SET_ROW_EDITED": return setRowEdited(state, payload); case "SAVE_ROW_EDITED": return saveRowEdited(state, payload); case "SAVE_ALL_ROWS_EDITED": return saveAllRowsEdited(state); case "REVERT_ROW_EDITED": return revertRowEdited(state, payload); case "REVERT_ALL_ROWS_TO_EDITED": return revertAllRowsToEdited(state); case "DELETE_ROW": return deleteRow(state, payload); case "ADD_TO_DELETE_ROW": return addToDeleteRow(state, payload); case "SELECT_ROW": return selectRow(state, payload); case "SET_ROWS_SELECTED": return setRowsSelected(state, payload); case "SET_ROWS_GLOBAL_SELECTED": return setRowsGlobalSelected(state, payload); case "SEARCH": return search(state, payload); case "TOGGLE_FILTERFIELDS_DISPLAY": return toggleFilterFieldsDisplay(state); case "TOGGLE_SEARCHFIELD_DISPLAY": return toggleSearchFieldDisplay(state); case "SEARCH_IN_COLUMN": return filterInColumn(state, payload); case "SET_COLUMN_VISIBILITY": return setColumnVisibilty(state, payload); case "HANDLE_PRESET_DISPLAY": return handlePresetDisplay(state, payload); case "SET_USER_CONFIGURATION": return setUserConfiguration(state, payload); case "REFRESH_ROWS_STARTED": return refreshRowsStarted(state); case "REFRESH_ROWS_SUCCESS": return refreshRowsSuccess(state, payload); case "REFRESH_ROWS_ERROR": return refreshRowsError(state); case "ORDER_BY_COLUMNS": return orderByColumns(state, payload); case "DUPLICATE_ROW": return duplicateRow(state, payload); default: return state; } }; export default datatableReducer;