mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 13:50:31 +00:00
feat: fix items list datatable.
This commit is contained in:
52
client/src/components/ContextMenu.js
Normal file
52
client/src/components/ContextMenu.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import React, { memo } from 'react';
|
||||
import { Popover, Position, Classes } from '@blueprintjs/core';
|
||||
import { saveInvoke } from 'utils';
|
||||
|
||||
const POPPER_MODIFIERS = {
|
||||
preventOverflow: { boundariesElement: 'viewport' },
|
||||
};
|
||||
|
||||
function ContextMenu(props) {
|
||||
const { bindMenu, isOpen, children, onClosed, popoverProps } = props;
|
||||
|
||||
const handleClosed = () => {
|
||||
requestAnimationFrame(() => saveInvoke(onClosed));
|
||||
};
|
||||
|
||||
const handleInteraction = (nextOpenState) => {
|
||||
if (!nextOpenState) {
|
||||
// Delay the actual hiding till the event queue clears
|
||||
// to avoid flicker of opening twice
|
||||
requestAnimationFrame(() => saveInvoke(onClosed));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={Classes.CONTEXT_MENU_POPOVER_TARGET} {...bindMenu}>
|
||||
<Popover
|
||||
onClosed={handleClosed}
|
||||
modifiers={POPPER_MODIFIERS}
|
||||
content={children}
|
||||
enforceFocus={true}
|
||||
isOpen={isOpen}
|
||||
minimal={true}
|
||||
position={Position.RIGHT_TOP}
|
||||
target={<div />}
|
||||
usePortal={false}
|
||||
onInteraction={handleInteraction}
|
||||
{...popoverProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(ContextMenu, (prevProps, nextProps) => {
|
||||
if (
|
||||
prevProps.isOpen === nextProps.isOpen &&
|
||||
prevProps.bindMenu.style === nextProps.bindMenu.style
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Palantir Technologies, Inc. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// import classNames from 'classnames';
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
// import { polyfill } from "react-lifecycles-compat";
|
||||
|
||||
import {
|
||||
Popover,
|
||||
Classes,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
|
||||
|
||||
// import { IOverlayLifecycleProps } from "../overlay/overlay";
|
||||
// import { Popover } from "../popover/popover";
|
||||
// import { PopperModifiers } from "../popover/popoverSharedProps";
|
||||
|
||||
export interface IOffset {
|
||||
left: number;
|
||||
top: number;
|
||||
}
|
||||
|
||||
interface IContextMenuState {
|
||||
isOpen: boolean;
|
||||
isDarkTheme: boolean;
|
||||
menu?: JSX.Element;
|
||||
offset?: IOffset;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
const POPPER_MODIFIERS = {
|
||||
preventOverflow: { boundariesElement: "viewport" },
|
||||
};
|
||||
const TRANSITION_DURATION = 100;
|
||||
|
||||
// type IContextMenuProps = IOverlayLifecycleProps;
|
||||
|
||||
/* istanbul ignore next */
|
||||
|
||||
export default class ContextMenu extends React.PureComponent {
|
||||
public state: IContextMenuState = {
|
||||
isDarkTheme: false,
|
||||
isOpen: false,
|
||||
};
|
||||
|
||||
public render() {
|
||||
// prevent right-clicking in a context menu
|
||||
const content = <div onContextMenu={this.cancelContextMenu}>{this.state.menu}</div>;
|
||||
const popoverClassName = {};
|
||||
|
||||
// HACKHACK: workaround until we have access to Popper#scheduleUpdate().
|
||||
// https://github.com/palantir/blueprint/issues/692
|
||||
// Generate key based on offset so a new Popover instance is created
|
||||
// when offset changes, to force recomputing position.
|
||||
const key = this.state.offset === undefined ? "" : `${this.state.offset.left}x${this.state.offset.top}`;
|
||||
|
||||
// wrap the popover in a positioned div to make sure it is properly
|
||||
// offset on the screen.
|
||||
return (
|
||||
<div className={Classes.CONTEXT_MENU_POPOVER_TARGET} style={this.state.offset}>
|
||||
<Popover
|
||||
{...this.props}
|
||||
backdropProps={{ onContextMenu: this.handleBackdropContextMenu }}
|
||||
content={content}
|
||||
enforceFocus={false}
|
||||
key={key}
|
||||
hasBackdrop={true}
|
||||
isOpen={this.state.isOpen}
|
||||
minimal={true}
|
||||
// modifiers={POPPER_MODIFIERS}
|
||||
onInteraction={this.handlePopoverInteraction}
|
||||
position={Position.RIGHT_TOP}
|
||||
// popoverClassName={popoverClassName}
|
||||
target={<div />}
|
||||
transitionDuration={TRANSITION_DURATION}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public show(menu: JSX.Element, offset: IOffset, onClose?: () => void, isDarkTheme = false) {
|
||||
this.setState({ isOpen: true, menu, offset, onClose, isDarkTheme });
|
||||
}
|
||||
|
||||
public hide() {
|
||||
this.state.onClose?.();
|
||||
this.setState({ isOpen: false, onClose: undefined });
|
||||
}
|
||||
|
||||
private cancelContextMenu = (e: React.SyntheticEvent<HTMLDivElement>) => e.preventDefault();
|
||||
|
||||
private handleBackdropContextMenu = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
// React function to remove from the event pool, useful when using a event within a callback
|
||||
e.persist();
|
||||
e.preventDefault();
|
||||
// wait for backdrop to disappear so we can find the "real" element at event coordinates.
|
||||
// timeout duration is equivalent to transition duration so we know it's animated out.
|
||||
setTimeout(() => {
|
||||
// retrigger context menu event at the element beneath the backdrop.
|
||||
// if it has a `contextmenu` event handler then it'll be invoked.
|
||||
// if it doesn't, no native menu will show (at least on OSX) :(
|
||||
const newTarget = document.elementFromPoint(e.clientX, e.clientY);
|
||||
const { view, ...newEventInit } = e;
|
||||
newTarget?.dispatchEvent(new MouseEvent("contextmenu", newEventInit));
|
||||
}, TRANSITION_DURATION);
|
||||
};
|
||||
|
||||
private handlePopoverInteraction = (nextOpenState: boolean) => {
|
||||
if (!nextOpenState) {
|
||||
// delay the actual hiding till the event queue clears
|
||||
// to avoid flicker of opening twice
|
||||
this.hide();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -10,22 +10,20 @@ import {
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Icon } from 'components';
|
||||
|
||||
/**
|
||||
* Dashboard action views list.
|
||||
*/
|
||||
export default function DashboardActionViewsList({
|
||||
resourceName,
|
||||
views,
|
||||
onChange,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
const handleClickViewItem = (view) => {
|
||||
history.push(
|
||||
view ? `/${resourceName}/${view.id}/custom_view` : '/accounts',
|
||||
);
|
||||
onChange && onChange(view);
|
||||
};
|
||||
|
||||
const viewsMenuItems = views.map((view) => {
|
||||
return (
|
||||
<MenuItem onClick={() => handleClickViewItem(view)} text={view.name} />
|
||||
|
||||
@@ -19,13 +19,14 @@ import { Icon, Hint, If } from 'components';
|
||||
import withSearch from 'containers/GeneralSearch/withSearch';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withDashboard from 'containers/Dashboard/withDashboard';
|
||||
import withSettings from 'containers/Settings/withSettings';
|
||||
|
||||
import QuickNewDropdown from 'containers/QuickNewDropdown/QuickNewDropdown';
|
||||
import { compose } from 'utils';
|
||||
|
||||
function DashboardTopbar({
|
||||
// #withDashboard
|
||||
pageTitle,
|
||||
pageSubtitle,
|
||||
editViewId,
|
||||
|
||||
// #withDashboardActions
|
||||
@@ -35,6 +36,9 @@ function DashboardTopbar({
|
||||
// #withDashboard
|
||||
sidebarExpended,
|
||||
|
||||
// #withSettings
|
||||
organizationName,
|
||||
|
||||
// #withGlobalSearch
|
||||
openGlobalSearch,
|
||||
}) {
|
||||
@@ -100,11 +104,7 @@ function DashboardTopbar({
|
||||
</div>
|
||||
</If>
|
||||
|
||||
<If condition={pageSubtitle}>
|
||||
<h3>{pageSubtitle}</h3>
|
||||
</If>
|
||||
|
||||
<If condition={pageSubtitle && editViewId}>
|
||||
<If condition={editViewId}>
|
||||
<Button
|
||||
className={Classes.MINIMAL + ' button--view-edit'}
|
||||
icon={<Icon icon="pen" iconSize={13} />}
|
||||
@@ -117,6 +117,10 @@ function DashboardTopbar({
|
||||
<DashboardBreadcrumbs />
|
||||
</div>
|
||||
|
||||
{/* <div class="dashboard__organization-name">
|
||||
{ organizationName }
|
||||
</div> */}
|
||||
|
||||
<DashboardBackLink />
|
||||
</div>
|
||||
|
||||
@@ -158,11 +162,13 @@ function DashboardTopbar({
|
||||
|
||||
export default compose(
|
||||
withSearch,
|
||||
withDashboard(({ pageTitle, pageSubtitle, editViewId, sidebarExpended }) => ({
|
||||
withDashboard(({ pageTitle, editViewId, sidebarExpended }) => ({
|
||||
pageTitle,
|
||||
pageSubtitle,
|
||||
editViewId,
|
||||
sidebarExpended,
|
||||
})),
|
||||
withSettings(({ organizationSettings }) => ({
|
||||
organizationName: organizationSettings.name,
|
||||
})),
|
||||
withDashboardActions,
|
||||
)(DashboardTopbar);
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import React, { useState, useRef, useMemo } from 'react';
|
||||
import React, { useRef, useState, useEffect } from 'react';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Tabs, Tab, Tooltip, Position } from '@blueprintjs/core';
|
||||
import { debounce } from 'lodash';
|
||||
import { useHistory } from 'react-router';
|
||||
import { debounce } from 'lodash';
|
||||
import { If, Icon } from 'components';
|
||||
import { saveInvoke } from 'utils';
|
||||
import { saveInvoke } from 'utils';
|
||||
|
||||
/**
|
||||
* Dashboard views tabs.
|
||||
*
|
||||
*/
|
||||
export default function DashboardViewsTabs({
|
||||
initialViewId = 0,
|
||||
viewId,
|
||||
currentViewId,
|
||||
tabs,
|
||||
defaultTabText = <T id={'all'} />,
|
||||
allTab = true,
|
||||
@@ -17,41 +21,38 @@ export default function DashboardViewsTabs({
|
||||
resourceName,
|
||||
onNewViewTabClick,
|
||||
onChange,
|
||||
onTabClick,
|
||||
OnThrottledChange,
|
||||
throttleTime = 250,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const [currentView, setCurrentView] = useState(initialViewId || 0);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof currentViewId !== 'undefined' && currentViewId !== currentView) {
|
||||
setCurrentView(currentViewId || 0);
|
||||
}
|
||||
}, [currentView, setCurrentView, currentViewId]);
|
||||
|
||||
const throttledOnChange = useRef(
|
||||
debounce((viewId) => saveInvoke(OnThrottledChange, viewId), throttleTime),
|
||||
);
|
||||
|
||||
// Trigger `onChange` and `onThrottledChange` events.
|
||||
const triggerOnChange = (viewId) => {
|
||||
saveInvoke(onChange, viewId);
|
||||
throttledOnChange.current(viewId);
|
||||
};
|
||||
|
||||
// Handles click a new view.
|
||||
const handleClickNewView = () => {
|
||||
history.push(`/custom_views/${resourceName}/new`);
|
||||
onNewViewTabClick && onNewViewTabClick();
|
||||
};
|
||||
|
||||
const handleTabClick = (viewId) => {
|
||||
saveInvoke(onTabClick, viewId);
|
||||
};
|
||||
|
||||
const mappedTabs = useMemo(
|
||||
() => tabs.map((tab) => ({ ...tab, onTabClick: handleTabClick })),
|
||||
[tabs, handleTabClick],
|
||||
);
|
||||
|
||||
const handleViewLinkClick = () => {
|
||||
saveInvoke(onNewViewTabClick);
|
||||
};
|
||||
|
||||
const debounceChangeHistory = useRef(
|
||||
debounce((toUrl) => {
|
||||
history.push(toUrl);
|
||||
}, 250),
|
||||
);
|
||||
|
||||
// Handle tabs change.
|
||||
const handleTabsChange = (viewId) => {
|
||||
const toPath = viewId ? `${viewId}/custom_view` : '';
|
||||
debounceChangeHistory.current(`/${resourceName}/${toPath}`);
|
||||
|
||||
setCurrentView(viewId);
|
||||
saveInvoke(onChange, viewId);
|
||||
triggerOnChange(viewId)
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -62,13 +63,11 @@ export default function DashboardViewsTabs({
|
||||
className="tabs--dashboard-views"
|
||||
onChange={handleTabsChange}
|
||||
>
|
||||
{allTab && (
|
||||
<Tab id={0} title={defaultTabText} onClick={handleViewLinkClick} />
|
||||
)}
|
||||
{mappedTabs.map((tab) => (
|
||||
<Tab id={tab.id} title={tab.name} onClick={handleTabClick} />
|
||||
))}
|
||||
{allTab && <Tab id={0} title={defaultTabText} />}
|
||||
|
||||
{tabs.map((tab) => (
|
||||
<Tab id={tab.id} title={tab.name} />
|
||||
))}
|
||||
<If condition={newViewTab}>
|
||||
<Tooltip
|
||||
content={<T id={'create_a_new_view'} />}
|
||||
@@ -93,5 +92,6 @@ DashboardViewsTabs.propTypes = {
|
||||
|
||||
onNewViewTabClick: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
onTabClick: PropTypes.func,
|
||||
OnThrottledChange: PropTypes.func,
|
||||
throttleTime: PropTypes.number,
|
||||
};
|
||||
|
||||
@@ -75,6 +75,8 @@ export default function DataTable(props) {
|
||||
TableWrapperRenderer,
|
||||
TableTBodyRenderer,
|
||||
TablePaginationRenderer,
|
||||
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
const selectionColumnObj = {
|
||||
@@ -117,6 +119,8 @@ export default function DataTable(props) {
|
||||
autoResetSortBy,
|
||||
autoResetFilters,
|
||||
autoResetRowState,
|
||||
|
||||
...restProps
|
||||
},
|
||||
useSortBy,
|
||||
useExpanded,
|
||||
|
||||
@@ -3,6 +3,7 @@ import classNames from 'classnames';
|
||||
import { ScrollSyncPane } from 'react-scroll-sync';
|
||||
import { If } from 'components';
|
||||
import TableContext from './TableContext';
|
||||
import MaterialProgressBar from 'components/MaterialProgressBar';
|
||||
|
||||
function TableHeaderCell({ column, index }) {
|
||||
const {
|
||||
@@ -77,7 +78,7 @@ function TableHeaderGroup({ headerGroup }) {
|
||||
export default function TableHeader() {
|
||||
const {
|
||||
table: { headerGroups, page },
|
||||
props: { TableHeaderSkeletonRenderer, headerLoading },
|
||||
props: { TableHeaderSkeletonRenderer, headerLoading, progressBarLoading },
|
||||
} = useContext(TableContext);
|
||||
|
||||
if (headerLoading && TableHeaderSkeletonRenderer) {
|
||||
@@ -89,6 +90,9 @@ export default function TableHeader() {
|
||||
{headerGroups.map((headerGroup) => (
|
||||
<TableHeaderGroup headerGroup={headerGroup} />
|
||||
))}
|
||||
<If condition={progressBarLoading}>
|
||||
<MaterialProgressBar />
|
||||
</If>
|
||||
</div>
|
||||
</ScrollSyncPane>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import React, { useCallback, useContext } from 'react';
|
||||
import { If, Pagination } from 'components';
|
||||
import TableContext from './TableContext';
|
||||
import { saveInvoke } from 'utils';
|
||||
|
||||
/**
|
||||
* Table pagination.
|
||||
*/
|
||||
export default function TablePagination({}) {
|
||||
export default function TablePagination() {
|
||||
const {
|
||||
table: {
|
||||
gotoPage,
|
||||
@@ -13,28 +14,39 @@ export default function TablePagination({}) {
|
||||
pageCount,
|
||||
state: { pageIndex, pageSize },
|
||||
},
|
||||
props: { pagination, loading },
|
||||
props: { pagination, loading, onPaginationChange },
|
||||
} = useContext(TableContext);
|
||||
|
||||
const triggerOnPaginationChange = useCallback((payload) => {
|
||||
saveInvoke(onPaginationChange, payload)
|
||||
}, [onPaginationChange]);
|
||||
|
||||
// Handles the page changing.
|
||||
const handlePageChange = useCallback(
|
||||
(currentPage) => {
|
||||
gotoPage(currentPage - 1);
|
||||
({ page, pageSize }) => {
|
||||
const pageIndex = page - 1;
|
||||
|
||||
gotoPage(pageIndex);
|
||||
triggerOnPaginationChange({ page, pageSize });
|
||||
},
|
||||
[gotoPage],
|
||||
[gotoPage, triggerOnPaginationChange],
|
||||
);
|
||||
|
||||
// Handles the page size changing.
|
||||
const handlePageSizeChange = useCallback(
|
||||
(pageSize, currentPage) => {
|
||||
({ pageSize, page }) => {
|
||||
gotoPage(0);
|
||||
setPageSize(pageSize);
|
||||
|
||||
triggerOnPaginationChange({ page, pageSize });
|
||||
},
|
||||
[gotoPage, setPageSize],
|
||||
[gotoPage, setPageSize, triggerOnPaginationChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<If condition={pagination && !loading}>
|
||||
<Pagination
|
||||
initialPage={pageIndex + 1}
|
||||
currentPage={pageIndex + 1}
|
||||
total={pageSize * pageCount}
|
||||
size={pageSize}
|
||||
onPageChange={handlePageChange}
|
||||
|
||||
@@ -1,32 +1,36 @@
|
||||
import React, { useContext } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { ContextMenu } from '@blueprintjs/core';
|
||||
import useContextMenu from 'react-use-context-menu';
|
||||
|
||||
import TableContext from './TableContext';
|
||||
import { saveInvoke } from 'utils';
|
||||
import { ContextMenu } from 'components';
|
||||
|
||||
|
||||
/**
|
||||
* Table row.
|
||||
*/
|
||||
export default function TableRow({ row, className, style }) {
|
||||
const {
|
||||
props: { TableCellRenderer, rowContextMenu, rowClassNames },
|
||||
props: {
|
||||
TableCellRenderer,
|
||||
rowContextMenu,
|
||||
rowClassNames,
|
||||
ContextMenu: ContextMenuContent,
|
||||
},
|
||||
table,
|
||||
} = useContext(TableContext);
|
||||
|
||||
// Handle rendering row context menu.
|
||||
const handleRowContextMenu = (row) => (e) => {
|
||||
if (typeof rowContextMenu === 'function') {
|
||||
e.preventDefault();
|
||||
const tr = e.currentTarget.closest('.tr');
|
||||
tr.classList.add('is-context-menu-active');
|
||||
const [
|
||||
bindMenu,
|
||||
bindMenuItem,
|
||||
useContextTrigger,
|
||||
{ coords, setVisible, isVisible },
|
||||
] = useContextMenu();
|
||||
|
||||
const DropdownEl = rowContextMenu({ row });
|
||||
|
||||
ContextMenu.show(DropdownEl, { left: e.clientX, top: e.clientY }, () => {
|
||||
tr.classList.remove('is-context-menu-active');
|
||||
});
|
||||
}
|
||||
};
|
||||
const [bindTrigger] = useContextTrigger({
|
||||
collect: () => 'Title',
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -40,12 +44,21 @@ export default function TableRow({ row, className, style }) {
|
||||
className,
|
||||
),
|
||||
style,
|
||||
onContextMenu: handleRowContextMenu(row)
|
||||
})}
|
||||
{...bindTrigger}
|
||||
>
|
||||
{row.cells.map((cell, index) => (
|
||||
<TableCellRenderer cell={cell} row={row} index={index + 1} />
|
||||
))}
|
||||
|
||||
<ContextMenu
|
||||
bindMenu={bindMenu}
|
||||
isOpen={isVisible}
|
||||
coords={coords}
|
||||
onClosed={() => setVisible(false)}
|
||||
>
|
||||
<ContextMenuContent {...table} row={row} />
|
||||
</ContextMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
13
client/src/components/MaterialProgressBar.js
Normal file
13
client/src/components/MaterialProgressBar.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
import 'style/components/MaterialProgressBar.scss';
|
||||
|
||||
export default function MaterialProgressBar() {
|
||||
return (
|
||||
<div class="progress-container">
|
||||
<div class="progress-materializecss">
|
||||
<div class="indeterminate"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useReducer, useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Button, ButtonGroup, Intent, HTMLSelect, } from '@blueprintjs/core';
|
||||
import { Button, ButtonGroup, Intent, HTMLSelect } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import { range } from 'lodash';
|
||||
@@ -8,6 +8,12 @@ import { Icon } from 'components';
|
||||
|
||||
import 'style/components/DataTable/Pagination.scss';
|
||||
|
||||
const TYPE = {
|
||||
PAGE_CHANGE: 'PAGE_CHANGE',
|
||||
PAGE_SIZE_CHANGE: 'PAGE_SIZE_CHANGE',
|
||||
INITIALIZE: 'INITIALIZE',
|
||||
};
|
||||
|
||||
const getState = ({ currentPage, size, total }) => {
|
||||
const totalPages = Math.ceil(total / size);
|
||||
const visibleItems = 5;
|
||||
@@ -16,7 +22,7 @@ const getState = ({ currentPage, size, total }) => {
|
||||
// create an array of pages to ng-repeat in the pager control
|
||||
let startPage, endPage;
|
||||
if (totalPages <= visibleItems) {
|
||||
// less than {visibleItems} total pages so show
|
||||
// less than {visibleItems} total pages so show
|
||||
startPage = 1;
|
||||
endPage = totalPages;
|
||||
} else {
|
||||
@@ -50,11 +56,6 @@ const getState = ({ currentPage, size, total }) => {
|
||||
};
|
||||
};
|
||||
|
||||
const TYPE = {
|
||||
PAGE_CHANGE: 'PAGE_CHANGE',
|
||||
PAGE_SIZE_CHANGE: 'PAGE_SIZE_CHANGE',
|
||||
INITIALIZE: 'INITIALIZE',
|
||||
};
|
||||
const reducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case TYPE.PAGE_CHANGE:
|
||||
@@ -63,15 +64,15 @@ const reducer = (state, action) => {
|
||||
size: state.size,
|
||||
total: state.total,
|
||||
});
|
||||
case TYPE.PAGE_SIZE_CHANGE:
|
||||
case TYPE.PAGE_SIZE_CHANGE:
|
||||
return getState({
|
||||
currentPage: state.currentPage,
|
||||
size: action.size,
|
||||
total: state.total,
|
||||
});
|
||||
case TYPE.INITIALIZE:
|
||||
case TYPE.INITIALIZE:
|
||||
return getState({
|
||||
currentPage: state.currentPage,
|
||||
currentPage: action.page,
|
||||
size: action.size,
|
||||
total: action.total,
|
||||
});
|
||||
@@ -80,82 +81,92 @@ const reducer = (state, action) => {
|
||||
}
|
||||
};
|
||||
|
||||
const Pagination = ({
|
||||
initialPage,
|
||||
function Pagination({
|
||||
currentPage,
|
||||
total,
|
||||
size,
|
||||
pageSizesOptions = [5, 12, 20, 30, 50, 75, 100, 150],
|
||||
onPageChange,
|
||||
onPageSizeChange,
|
||||
}) => {
|
||||
}) {
|
||||
const [state, dispatch] = useReducer(
|
||||
reducer,
|
||||
{ currentPage: initialPage, total, size },
|
||||
{ currentPage, total, size },
|
||||
getState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: 'INITIALIZE',
|
||||
type: TYPE.INITIALIZE,
|
||||
total,
|
||||
size,
|
||||
page: currentPage,
|
||||
});
|
||||
}, [total, size]);
|
||||
}, [total, size, currentPage]);
|
||||
|
||||
return (
|
||||
<div class="pagination">
|
||||
<div class="pagination__buttons-group">
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
disabled={state.currentPage === 1}
|
||||
onClick={() => {
|
||||
dispatch({ type: 'PAGE_CHANGE', page: state.currentPage - 1 });
|
||||
onPageChange(state.currentPage - 1);
|
||||
}}
|
||||
minimal={true}
|
||||
className={'pagination__item pagination__item--previous'}
|
||||
icon={<Icon icon={'arrow-back-24'} iconSize={12} />}
|
||||
>
|
||||
<T id='previous' />
|
||||
</Button>
|
||||
|
||||
{state.pages.map((page) => (
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
key={page}
|
||||
intent={state.currentPage === page ? Intent.PRIMARY : Intent.NONE}
|
||||
disabled={state.currentPage === page}
|
||||
disabled={state.currentPage <= 1}
|
||||
onClick={() => {
|
||||
dispatch({ type: 'PAGE_CHANGE', page });
|
||||
onPageChange(page);
|
||||
dispatch({ type: 'PAGE_CHANGE', page: state.currentPage - 1 });
|
||||
|
||||
const page = state.currentPage - 1;
|
||||
const { size: pageSize } = state;
|
||||
|
||||
onPageChange({ page, pageSize });
|
||||
}}
|
||||
minimal={true}
|
||||
className={classNames(
|
||||
'pagination__item',
|
||||
'pagination__item--page',
|
||||
{
|
||||
'is-active': state.currentPage === page,
|
||||
}
|
||||
)}
|
||||
className={'pagination__item pagination__item--previous'}
|
||||
icon={<Icon icon={'arrow-back-24'} iconSize={12} />}
|
||||
>
|
||||
{ page }
|
||||
<T id="previous" />
|
||||
</Button>
|
||||
))}
|
||||
<Button
|
||||
disabled={state.currentPage === state.totalPages}
|
||||
onClick={() => {
|
||||
dispatch({
|
||||
type: 'PAGE_CHANGE',
|
||||
page: state.currentPage + 1
|
||||
});
|
||||
onPageChange(state.currentPage + 1);
|
||||
}}
|
||||
minimal={true}
|
||||
className={'pagination__item pagination__item--next'}
|
||||
icon={<Icon icon={'arrow-forward-24'} iconSize={12} />}
|
||||
>
|
||||
<T id='next' />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
||||
{state.pages.map((page) => (
|
||||
<Button
|
||||
key={page}
|
||||
intent={state.currentPage === page ? Intent.PRIMARY : Intent.NONE}
|
||||
disabled={state.currentPage === page}
|
||||
onClick={() => {
|
||||
dispatch({ type: 'PAGE_CHANGE', page });
|
||||
const { size: pageSize } = state;
|
||||
|
||||
onPageChange({ page, pageSize });
|
||||
}}
|
||||
minimal={true}
|
||||
className={classNames(
|
||||
'pagination__item',
|
||||
'pagination__item--page',
|
||||
{
|
||||
'is-active': state.currentPage === page,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{page}
|
||||
</Button>
|
||||
))}
|
||||
<Button
|
||||
disabled={state.currentPage === state.totalPages}
|
||||
onClick={() => {
|
||||
dispatch({
|
||||
type: 'PAGE_CHANGE',
|
||||
page: state.currentPage + 1,
|
||||
});
|
||||
const page = state.currentPage + 1;
|
||||
const { size: pageSize } = state;
|
||||
|
||||
onPageChange({ page, pageSize });
|
||||
}}
|
||||
minimal={true}
|
||||
className={'pagination__item pagination__item--next'}
|
||||
icon={<Icon icon={'arrow-forward-24'} iconSize={12} />}
|
||||
>
|
||||
<T id="next" />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
<div class="pagination__controls">
|
||||
@@ -167,11 +178,11 @@ const Pagination = ({
|
||||
value={state.currentPage}
|
||||
onChange={(event) => {
|
||||
const page = parseInt(event.currentTarget.value, 10);
|
||||
const { size: pageSize } = state;
|
||||
|
||||
dispatch({ type: 'PAGE_CHANGE', page });
|
||||
onPageChange(page);
|
||||
onPageChange({ page, pageSize });
|
||||
}}
|
||||
minimal={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -186,26 +197,28 @@ const Pagination = ({
|
||||
dispatch({ type: 'PAGE_SIZE_CHANGE', size: pageSize });
|
||||
dispatch({ type: 'PAGE_CHANGE', page: 1 });
|
||||
|
||||
onPageSizeChange(pageSize, 1);
|
||||
onPageSizeChange({ pageSize, page: 1 });
|
||||
}}
|
||||
minimal={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pagination__info">
|
||||
<T id={'showing_current_page_to_total'} values={{
|
||||
currentPage: state.currentPage,
|
||||
totalPages: state.totalPages,
|
||||
total: total,
|
||||
}} />
|
||||
<T
|
||||
id={'showing_current_page_to_total'}
|
||||
values={{
|
||||
currentPage: state.currentPage,
|
||||
totalPages: state.totalPages,
|
||||
total: total,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
Pagination.propTypes = {
|
||||
initialPage: PropTypes.number.isRequired,
|
||||
currentPage: PropTypes.number.isRequired,
|
||||
size: PropTypes.number.isRequired,
|
||||
total: PropTypes.number.isRequired,
|
||||
onPageChange: PropTypes.func,
|
||||
@@ -213,7 +226,7 @@ Pagination.propTypes = {
|
||||
};
|
||||
|
||||
Pagination.defaultProps = {
|
||||
initialPage: 1,
|
||||
currentPage: 1,
|
||||
size: 25,
|
||||
};
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ import PageFormBigNumber from './PageFormBigNumber';
|
||||
import AccountsMultiSelect from './AccountsMultiSelect';
|
||||
import CustomersMultiSelect from './CustomersMultiSelect';
|
||||
import Skeleton from './Skeleton'
|
||||
|
||||
import ContextMenu from './ContextMenu'
|
||||
import TableFastCell from './Datatable/TableFastCell';
|
||||
|
||||
const Hint = FieldHint;
|
||||
@@ -99,4 +99,5 @@ export {
|
||||
CustomersMultiSelect,
|
||||
TableFastCell,
|
||||
Skeleton,
|
||||
ContextMenu
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user