mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 14:50:32 +00:00
BC-12 feat: store accounts table columns resizing to local storage.
This commit is contained in:
@@ -32,6 +32,8 @@ import TableWrapper from './Datatable/TableWrapper';
|
|||||||
import TableIndeterminateCheckboxRow from './Datatable/TableIndeterminateCheckboxRow';
|
import TableIndeterminateCheckboxRow from './Datatable/TableIndeterminateCheckboxRow';
|
||||||
import TableIndeterminateCheckboxHeader from './Datatable/TableIndeterminateCheckboxHeader';
|
import TableIndeterminateCheckboxHeader from './Datatable/TableIndeterminateCheckboxHeader';
|
||||||
|
|
||||||
|
import { useResizeObserver } from './Datatable/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Datatable component.
|
* Datatable component.
|
||||||
*/
|
*/
|
||||||
@@ -78,6 +80,9 @@ export default function DataTable(props) {
|
|||||||
TablePaginationRenderer,
|
TablePaginationRenderer,
|
||||||
TableFooterRenderer,
|
TableFooterRenderer,
|
||||||
|
|
||||||
|
onColumnResizing,
|
||||||
|
initialColumnsWidths,
|
||||||
|
|
||||||
...restProps
|
...restProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
@@ -106,6 +111,9 @@ export default function DataTable(props) {
|
|||||||
pageIndex: initialPageIndex,
|
pageIndex: initialPageIndex,
|
||||||
pageSize: initialPageSize,
|
pageSize: initialPageSize,
|
||||||
expanded,
|
expanded,
|
||||||
|
columnResizing: {
|
||||||
|
columnWidths: initialColumnsWidths || {},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
manualPagination,
|
manualPagination,
|
||||||
pageCount: controlledPageCount,
|
pageCount: controlledPageCount,
|
||||||
@@ -164,6 +172,11 @@ export default function DataTable(props) {
|
|||||||
saveInvoke(onSelectedRowsChange, selectedFlatRows);
|
saveInvoke(onSelectedRowsChange, selectedFlatRows);
|
||||||
}, [selectedRowIds, onSelectedRowsChange]);
|
}, [selectedRowIds, onSelectedRowsChange]);
|
||||||
|
|
||||||
|
// Column resizing observer.
|
||||||
|
useResizeObserver(table.state, (current, columnWidth, columnsResizing) => {
|
||||||
|
onColumnResizing && onColumnResizing(current, columnWidth, columnsResizing);
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableContext.Provider value={{ table, props }}>
|
<TableContext.Provider value={{ table, props }}>
|
||||||
<TableWrapperRenderer>
|
<TableWrapperRenderer>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { React } from 'react';
|
||||||
|
|
||||||
export const isCellLoading = (loading, cellsCoords, rowIndex, columnId) => {
|
export const isCellLoading = (loading, cellsCoords, rowIndex, columnId) => {
|
||||||
if (!loading) {
|
if (!loading) {
|
||||||
return false;
|
return false;
|
||||||
@@ -8,3 +10,26 @@ export const isCellLoading = (loading, cellsCoords, rowIndex, columnId) => {
|
|||||||
(cellCoord) => cellCoord[0] === rowIndex && cellCoord[1] === columnId,
|
(cellCoord) => cellCoord[0] === rowIndex && cellCoord[1] === columnId,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useResizeObserver = (state, callback) => {
|
||||||
|
// This Ref will contain the id of the column being resized or undefined
|
||||||
|
const columnResizeRef = React.useRef();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
// We are interested in calling the resize event only when "state.columnResizing?.isResizingColumn" changes from
|
||||||
|
// a string to undefined, because it indicates that it WAS resizing but it no longer is.
|
||||||
|
if (
|
||||||
|
state.columnResizing &&
|
||||||
|
!state.columnResizing?.isResizingColumn &&
|
||||||
|
columnResizeRef.current
|
||||||
|
) {
|
||||||
|
// Trigger resize event
|
||||||
|
callback(
|
||||||
|
columnResizeRef.current,
|
||||||
|
state.columnResizing.columnWidths[columnResizeRef.current],
|
||||||
|
state.columnResizing,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
columnResizeRef.current = state.columnResizing?.isResizingColumn;
|
||||||
|
}, [callback, state.columnResizing]);
|
||||||
|
};
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import withAlertsActions from 'containers/Alert/withAlertActions';
|
|||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
import withDrawerActions from 'containers/Drawer/withDrawerActions';
|
||||||
|
|
||||||
|
import { useMemorizedColumnsWidths } from '../../hooks';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accounts data-table.
|
* Accounts data-table.
|
||||||
*/
|
*/
|
||||||
@@ -28,11 +30,8 @@ function AccountsDataTable({
|
|||||||
// #withDrawerActions
|
// #withDrawerActions
|
||||||
openDrawer,
|
openDrawer,
|
||||||
}) {
|
}) {
|
||||||
const {
|
const { isAccountsLoading, isAccountsFetching, accounts } =
|
||||||
isAccountsLoading,
|
useAccountsChartContext();
|
||||||
isAccountsFetching,
|
|
||||||
accounts,
|
|
||||||
} = useAccountsChartContext();
|
|
||||||
|
|
||||||
// Retrieve accounts table columns.
|
// Retrieve accounts table columns.
|
||||||
const columns = useAccountsTableColumns();
|
const columns = useAccountsTableColumns();
|
||||||
@@ -80,6 +79,9 @@ function AccountsDataTable({
|
|||||||
const handleCellClick = (cell, event) => {
|
const handleCellClick = (cell, event) => {
|
||||||
openDrawer('account-drawer', { accountId: cell.row.original.id });
|
openDrawer('account-drawer', { accountId: cell.row.original.id });
|
||||||
};
|
};
|
||||||
|
// Local storage memorizing columns widths.
|
||||||
|
const [initialColumnsWidths, , handleColumnResizing] =
|
||||||
|
useMemorizedColumnsWidths('accounts');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable
|
<DataTable
|
||||||
@@ -98,7 +100,7 @@ function AccountsDataTable({
|
|||||||
autoResetSelectedRows={false}
|
autoResetSelectedRows={false}
|
||||||
expandColumnSpace={1}
|
expandColumnSpace={1}
|
||||||
expandToggleColumn={2}
|
expandToggleColumn={2}
|
||||||
selectionColumnWidth={50}
|
selectionColumnWidth={45}
|
||||||
TableCellRenderer={TableFastCell}
|
TableCellRenderer={TableFastCell}
|
||||||
TableRowsRenderer={TableVirtualizedListRows}
|
TableRowsRenderer={TableVirtualizedListRows}
|
||||||
TableLoadingRenderer={TableSkeletonRows}
|
TableLoadingRenderer={TableSkeletonRows}
|
||||||
@@ -108,6 +110,8 @@ function AccountsDataTable({
|
|||||||
vListrowHeight={42}
|
vListrowHeight={42}
|
||||||
vListOverscanRowCount={0}
|
vListOverscanRowCount={0}
|
||||||
onCellClick={handleCellClick}
|
onCellClick={handleCellClick}
|
||||||
|
initialColumnsWidths={initialColumnsWidths}
|
||||||
|
onColumnResizing={handleColumnResizing}
|
||||||
payload={{
|
payload={{
|
||||||
onEdit: handleEditAccount,
|
onEdit: handleEditAccount,
|
||||||
onDelete: handleDeleteAccount,
|
onDelete: handleDeleteAccount,
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { useRef, useEffect, useMemo } from 'react';
|
import React, { useRef, useEffect, useMemo } from 'react';
|
||||||
import useAsync from './async';
|
import useAsync from './async';
|
||||||
import useAutofocus from './useAutofocus';
|
import useAutofocus from './useAutofocus';
|
||||||
|
|
||||||
// import use from 'async';
|
// import use from 'async';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A custom useEffect hook that only triggers on updates, not on initial mount
|
* A custom useEffect hook that only triggers on updates, not on initial mount
|
||||||
* Idea stolen from: https://stackoverflow.com/a/55075818/1526448
|
* Idea stolen from: https://stackoverflow.com/a/55075818/1526448
|
||||||
@@ -49,11 +48,10 @@ const isCurrentFocus = (autoFocus, columnId, rowIndex) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function useCellAutoFocus(ref, autoFocus, columnId, rowIndex) {
|
export function useCellAutoFocus(ref, autoFocus, columnId, rowIndex) {
|
||||||
const focus = useMemo(() => isCurrentFocus(autoFocus, columnId, rowIndex), [
|
const focus = useMemo(
|
||||||
autoFocus,
|
() => isCurrentFocus(autoFocus, columnId, rowIndex),
|
||||||
columnId,
|
[autoFocus, columnId, rowIndex],
|
||||||
rowIndex,
|
);
|
||||||
]);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ref.current && focus) {
|
if (ref.current && focus) {
|
||||||
ref.current.focus();
|
ref.current.focus();
|
||||||
@@ -65,3 +63,46 @@ export function useCellAutoFocus(ref, autoFocus, columnId, rowIndex) {
|
|||||||
|
|
||||||
export * from './useRequestPdf';
|
export * from './useRequestPdf';
|
||||||
export { useAsync, useAutofocus };
|
export { useAsync, useAutofocus };
|
||||||
|
|
||||||
|
// Hook
|
||||||
|
export function useLocalStorage(key, initialValue) {
|
||||||
|
// State to store our value
|
||||||
|
// Pass initial state function to useState so logic is only executed once
|
||||||
|
const [storedValue, setStoredValue] = React.useState(() => {
|
||||||
|
try {
|
||||||
|
// Get from local storage by key
|
||||||
|
const item = window.localStorage.getItem(key);
|
||||||
|
// Parse stored json or if none return initialValue
|
||||||
|
return item ? JSON.parse(item) : initialValue;
|
||||||
|
} catch (error) {
|
||||||
|
return initialValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Return a wrapped version of useState's setter function that ...
|
||||||
|
// ... persists the new value to localStorage.
|
||||||
|
const setValue = (value) => {
|
||||||
|
try {
|
||||||
|
// Allow value to be a function so we have same API as useState
|
||||||
|
const valueToStore =
|
||||||
|
value instanceof Function ? value(storedValue) : value;
|
||||||
|
// Save state
|
||||||
|
setStoredValue(valueToStore);
|
||||||
|
// Save to local storage
|
||||||
|
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
||||||
|
} catch (error) {
|
||||||
|
// A more advanced implementation would handle the error case
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return [storedValue, setValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function useMemorizedColumnsWidths(tableName) {
|
||||||
|
const [get, save] = useLocalStorage(`${tableName}.columns_widths`, {});
|
||||||
|
|
||||||
|
const handleColumnResizing = (current, columnWidth, columnsResizing) => {
|
||||||
|
save(columnsResizing.columnWidths);
|
||||||
|
};
|
||||||
|
return [get, save, handleColumnResizing];
|
||||||
|
}
|
||||||
@@ -6,12 +6,12 @@ import {
|
|||||||
useSetGlobalErrors,
|
useSetGlobalErrors,
|
||||||
useAuthToken,
|
useAuthToken,
|
||||||
} from './state';
|
} from './state';
|
||||||
import { useAppIntlContext } from '../components/AppIntlProvider';
|
import { getCookie } from '../utils';
|
||||||
|
|
||||||
export default function useApiRequest() {
|
export default function useApiRequest() {
|
||||||
const setGlobalErrors = useSetGlobalErrors();
|
const setGlobalErrors = useSetGlobalErrors();
|
||||||
const { setLogout } = useAuthActions();
|
const { setLogout } = useAuthActions();
|
||||||
const { currentLocale } = useAppIntlContext();
|
const currentLocale = getCookie('locale');
|
||||||
|
|
||||||
// Authentication token.
|
// Authentication token.
|
||||||
const token = useAuthToken();
|
const token = useAuthToken();
|
||||||
|
|||||||
Reference in New Issue
Block a user