feat: fix items list datatable.

This commit is contained in:
a.bouhuolia
2021-02-08 13:17:11 +02:00
parent adac2386bb
commit 304f0c9ae5
43 changed files with 777 additions and 835 deletions

View 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;
}
});

View File

@@ -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();
}
};
}

View File

@@ -10,22 +10,20 @@ import {
Position, Position,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl'; import { FormattedMessage as T } from 'react-intl';
import { useHistory } from 'react-router-dom';
import { Icon } from 'components'; import { Icon } from 'components';
/**
* Dashboard action views list.
*/
export default function DashboardActionViewsList({ export default function DashboardActionViewsList({
resourceName, resourceName,
views, views,
onChange, onChange,
}) { }) {
const history = useHistory();
const handleClickViewItem = (view) => { const handleClickViewItem = (view) => {
history.push(
view ? `/${resourceName}/${view.id}/custom_view` : '/accounts',
);
onChange && onChange(view); onChange && onChange(view);
}; };
const viewsMenuItems = views.map((view) => { const viewsMenuItems = views.map((view) => {
return ( return (
<MenuItem onClick={() => handleClickViewItem(view)} text={view.name} /> <MenuItem onClick={() => handleClickViewItem(view)} text={view.name} />

View File

@@ -19,13 +19,14 @@ import { Icon, Hint, If } from 'components';
import withSearch from 'containers/GeneralSearch/withSearch'; import withSearch from 'containers/GeneralSearch/withSearch';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withDashboard from 'containers/Dashboard/withDashboard'; import withDashboard from 'containers/Dashboard/withDashboard';
import withSettings from 'containers/Settings/withSettings';
import QuickNewDropdown from 'containers/QuickNewDropdown/QuickNewDropdown'; import QuickNewDropdown from 'containers/QuickNewDropdown/QuickNewDropdown';
import { compose } from 'utils'; import { compose } from 'utils';
function DashboardTopbar({ function DashboardTopbar({
// #withDashboard // #withDashboard
pageTitle, pageTitle,
pageSubtitle,
editViewId, editViewId,
// #withDashboardActions // #withDashboardActions
@@ -35,6 +36,9 @@ function DashboardTopbar({
// #withDashboard // #withDashboard
sidebarExpended, sidebarExpended,
// #withSettings
organizationName,
// #withGlobalSearch // #withGlobalSearch
openGlobalSearch, openGlobalSearch,
}) { }) {
@@ -100,11 +104,7 @@ function DashboardTopbar({
</div> </div>
</If> </If>
<If condition={pageSubtitle}> <If condition={editViewId}>
<h3>{pageSubtitle}</h3>
</If>
<If condition={pageSubtitle && editViewId}>
<Button <Button
className={Classes.MINIMAL + ' button--view-edit'} className={Classes.MINIMAL + ' button--view-edit'}
icon={<Icon icon="pen" iconSize={13} />} icon={<Icon icon="pen" iconSize={13} />}
@@ -117,6 +117,10 @@ function DashboardTopbar({
<DashboardBreadcrumbs /> <DashboardBreadcrumbs />
</div> </div>
{/* <div class="dashboard__organization-name">
{ organizationName }
</div> */}
<DashboardBackLink /> <DashboardBackLink />
</div> </div>
@@ -158,11 +162,13 @@ function DashboardTopbar({
export default compose( export default compose(
withSearch, withSearch,
withDashboard(({ pageTitle, pageSubtitle, editViewId, sidebarExpended }) => ({ withDashboard(({ pageTitle, editViewId, sidebarExpended }) => ({
pageTitle, pageTitle,
pageSubtitle,
editViewId, editViewId,
sidebarExpended, sidebarExpended,
})), })),
withSettings(({ organizationSettings }) => ({
organizationName: organizationSettings.name,
})),
withDashboardActions, withDashboardActions,
)(DashboardTopbar); )(DashboardTopbar);

View File

@@ -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 { FormattedMessage as T } from 'react-intl';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Button, Tabs, Tab, Tooltip, Position } from '@blueprintjs/core'; import { Button, Tabs, Tab, Tooltip, Position } from '@blueprintjs/core';
import { debounce } from 'lodash';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { debounce } from 'lodash';
import { If, Icon } from 'components'; import { If, Icon } from 'components';
import { saveInvoke } from 'utils'; import { saveInvoke } from 'utils';
/**
* Dashboard views tabs.
*
*/
export default function DashboardViewsTabs({ export default function DashboardViewsTabs({
initialViewId = 0, initialViewId = 0,
viewId, currentViewId,
tabs, tabs,
defaultTabText = <T id={'all'} />, defaultTabText = <T id={'all'} />,
allTab = true, allTab = true,
@@ -17,41 +21,38 @@ export default function DashboardViewsTabs({
resourceName, resourceName,
onNewViewTabClick, onNewViewTabClick,
onChange, onChange,
onTabClick, OnThrottledChange,
throttleTime = 250,
}) { }) {
const history = useHistory(); const history = useHistory();
const [currentView, setCurrentView] = useState(initialViewId || 0); 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 = () => { const handleClickNewView = () => {
history.push(`/custom_views/${resourceName}/new`); history.push(`/custom_views/${resourceName}/new`);
onNewViewTabClick && onNewViewTabClick(); onNewViewTabClick && onNewViewTabClick();
}; };
const handleTabClick = (viewId) => { // Handle tabs change.
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),
);
const handleTabsChange = (viewId) => { const handleTabsChange = (viewId) => {
const toPath = viewId ? `${viewId}/custom_view` : '';
debounceChangeHistory.current(`/${resourceName}/${toPath}`);
setCurrentView(viewId); setCurrentView(viewId);
saveInvoke(onChange, viewId); triggerOnChange(viewId)
}; };
return ( return (
@@ -62,13 +63,11 @@ export default function DashboardViewsTabs({
className="tabs--dashboard-views" className="tabs--dashboard-views"
onChange={handleTabsChange} onChange={handleTabsChange}
> >
{allTab && ( {allTab && <Tab id={0} title={defaultTabText} />}
<Tab id={0} title={defaultTabText} onClick={handleViewLinkClick} />
)}
{mappedTabs.map((tab) => (
<Tab id={tab.id} title={tab.name} onClick={handleTabClick} />
))}
{tabs.map((tab) => (
<Tab id={tab.id} title={tab.name} />
))}
<If condition={newViewTab}> <If condition={newViewTab}>
<Tooltip <Tooltip
content={<T id={'create_a_new_view'} />} content={<T id={'create_a_new_view'} />}
@@ -93,5 +92,6 @@ DashboardViewsTabs.propTypes = {
onNewViewTabClick: PropTypes.func, onNewViewTabClick: PropTypes.func,
onChange: PropTypes.func, onChange: PropTypes.func,
onTabClick: PropTypes.func, OnThrottledChange: PropTypes.func,
throttleTime: PropTypes.number,
}; };

View File

@@ -75,6 +75,8 @@ export default function DataTable(props) {
TableWrapperRenderer, TableWrapperRenderer,
TableTBodyRenderer, TableTBodyRenderer,
TablePaginationRenderer, TablePaginationRenderer,
...restProps
} = props; } = props;
const selectionColumnObj = { const selectionColumnObj = {
@@ -117,6 +119,8 @@ export default function DataTable(props) {
autoResetSortBy, autoResetSortBy,
autoResetFilters, autoResetFilters,
autoResetRowState, autoResetRowState,
...restProps
}, },
useSortBy, useSortBy,
useExpanded, useExpanded,

View File

@@ -3,6 +3,7 @@ import classNames from 'classnames';
import { ScrollSyncPane } from 'react-scroll-sync'; import { ScrollSyncPane } from 'react-scroll-sync';
import { If } from 'components'; import { If } from 'components';
import TableContext from './TableContext'; import TableContext from './TableContext';
import MaterialProgressBar from 'components/MaterialProgressBar';
function TableHeaderCell({ column, index }) { function TableHeaderCell({ column, index }) {
const { const {
@@ -77,7 +78,7 @@ function TableHeaderGroup({ headerGroup }) {
export default function TableHeader() { export default function TableHeader() {
const { const {
table: { headerGroups, page }, table: { headerGroups, page },
props: { TableHeaderSkeletonRenderer, headerLoading }, props: { TableHeaderSkeletonRenderer, headerLoading, progressBarLoading },
} = useContext(TableContext); } = useContext(TableContext);
if (headerLoading && TableHeaderSkeletonRenderer) { if (headerLoading && TableHeaderSkeletonRenderer) {
@@ -89,6 +90,9 @@ export default function TableHeader() {
{headerGroups.map((headerGroup) => ( {headerGroups.map((headerGroup) => (
<TableHeaderGroup headerGroup={headerGroup} /> <TableHeaderGroup headerGroup={headerGroup} />
))} ))}
<If condition={progressBarLoading}>
<MaterialProgressBar />
</If>
</div> </div>
</ScrollSyncPane> </ScrollSyncPane>
); );

View File

@@ -1,11 +1,12 @@
import React, { useCallback, useContext } from 'react'; import React, { useCallback, useContext } from 'react';
import { If, Pagination } from 'components'; import { If, Pagination } from 'components';
import TableContext from './TableContext'; import TableContext from './TableContext';
import { saveInvoke } from 'utils';
/** /**
* Table pagination. * Table pagination.
*/ */
export default function TablePagination({}) { export default function TablePagination() {
const { const {
table: { table: {
gotoPage, gotoPage,
@@ -13,28 +14,39 @@ export default function TablePagination({}) {
pageCount, pageCount,
state: { pageIndex, pageSize }, state: { pageIndex, pageSize },
}, },
props: { pagination, loading }, props: { pagination, loading, onPaginationChange },
} = useContext(TableContext); } = useContext(TableContext);
const triggerOnPaginationChange = useCallback((payload) => {
saveInvoke(onPaginationChange, payload)
}, [onPaginationChange]);
// Handles the page changing.
const handlePageChange = useCallback( const handlePageChange = useCallback(
(currentPage) => { ({ page, pageSize }) => {
gotoPage(currentPage - 1); const pageIndex = page - 1;
gotoPage(pageIndex);
triggerOnPaginationChange({ page, pageSize });
}, },
[gotoPage], [gotoPage, triggerOnPaginationChange],
); );
// Handles the page size changing.
const handlePageSizeChange = useCallback( const handlePageSizeChange = useCallback(
(pageSize, currentPage) => { ({ pageSize, page }) => {
gotoPage(0); gotoPage(0);
setPageSize(pageSize); setPageSize(pageSize);
triggerOnPaginationChange({ page, pageSize });
}, },
[gotoPage, setPageSize], [gotoPage, setPageSize, triggerOnPaginationChange],
); );
return ( return (
<If condition={pagination && !loading}> <If condition={pagination && !loading}>
<Pagination <Pagination
initialPage={pageIndex + 1} currentPage={pageIndex + 1}
total={pageSize * pageCount} total={pageSize * pageCount}
size={pageSize} size={pageSize}
onPageChange={handlePageChange} onPageChange={handlePageChange}

View File

@@ -1,32 +1,36 @@
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { ContextMenu } from '@blueprintjs/core'; import useContextMenu from 'react-use-context-menu';
import TableContext from './TableContext'; import TableContext from './TableContext';
import { saveInvoke } from 'utils'; import { saveInvoke } from 'utils';
import { ContextMenu } from 'components';
/** /**
* Table row. * Table row.
*/ */
export default function TableRow({ row, className, style }) { export default function TableRow({ row, className, style }) {
const { const {
props: { TableCellRenderer, rowContextMenu, rowClassNames }, props: {
TableCellRenderer,
rowContextMenu,
rowClassNames,
ContextMenu: ContextMenuContent,
},
table,
} = useContext(TableContext); } = useContext(TableContext);
// Handle rendering row context menu. const [
const handleRowContextMenu = (row) => (e) => { bindMenu,
if (typeof rowContextMenu === 'function') { bindMenuItem,
e.preventDefault(); useContextTrigger,
const tr = e.currentTarget.closest('.tr'); { coords, setVisible, isVisible },
tr.classList.add('is-context-menu-active'); ] = useContextMenu();
const DropdownEl = rowContextMenu({ row }); const [bindTrigger] = useContextTrigger({
collect: () => 'Title',
ContextMenu.show(DropdownEl, { left: e.clientX, top: e.clientY }, () => {
tr.classList.remove('is-context-menu-active');
}); });
}
};
return ( return (
<div <div
@@ -40,12 +44,21 @@ export default function TableRow({ row, className, style }) {
className, className,
), ),
style, style,
onContextMenu: handleRowContextMenu(row)
})} })}
{...bindTrigger}
> >
{row.cells.map((cell, index) => ( {row.cells.map((cell, index) => (
<TableCellRenderer cell={cell} row={row} index={index + 1} /> <TableCellRenderer cell={cell} row={row} index={index + 1} />
))} ))}
<ContextMenu
bindMenu={bindMenu}
isOpen={isVisible}
coords={coords}
onClosed={() => setVisible(false)}
>
<ContextMenuContent {...table} row={row} />
</ContextMenu>
</div> </div>
); );
} }

View 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>
);
}

View File

@@ -1,6 +1,6 @@
import React, { useReducer, useEffect } from 'react'; import React, { useReducer, useEffect } from 'react';
import classNames from 'classnames'; 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 { FormattedMessage as T } from 'react-intl';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { range } from 'lodash'; import { range } from 'lodash';
@@ -8,6 +8,12 @@ import { Icon } from 'components';
import 'style/components/DataTable/Pagination.scss'; 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 getState = ({ currentPage, size, total }) => {
const totalPages = Math.ceil(total / size); const totalPages = Math.ceil(total / size);
const visibleItems = 5; const visibleItems = 5;
@@ -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) => { const reducer = (state, action) => {
switch (action.type) { switch (action.type) {
case TYPE.PAGE_CHANGE: case TYPE.PAGE_CHANGE:
@@ -71,7 +72,7 @@ const reducer = (state, action) => {
}); });
case TYPE.INITIALIZE: case TYPE.INITIALIZE:
return getState({ return getState({
currentPage: state.currentPage, currentPage: action.page,
size: action.size, size: action.size,
total: action.total, total: action.total,
}); });
@@ -80,43 +81,48 @@ const reducer = (state, action) => {
} }
}; };
const Pagination = ({ function Pagination({
initialPage, currentPage,
total, total,
size, size,
pageSizesOptions = [5, 12, 20, 30, 50, 75, 100, 150], pageSizesOptions = [5, 12, 20, 30, 50, 75, 100, 150],
onPageChange, onPageChange,
onPageSizeChange, onPageSizeChange,
}) => { }) {
const [state, dispatch] = useReducer( const [state, dispatch] = useReducer(
reducer, reducer,
{ currentPage: initialPage, total, size }, { currentPage, total, size },
getState, getState,
); );
useEffect(() => { useEffect(() => {
dispatch({ dispatch({
type: 'INITIALIZE', type: TYPE.INITIALIZE,
total, total,
size, size,
page: currentPage,
}); });
}, [total, size]); }, [total, size, currentPage]);
return ( return (
<div class="pagination"> <div class="pagination">
<div class="pagination__buttons-group"> <div class="pagination__buttons-group">
<ButtonGroup> <ButtonGroup>
<Button <Button
disabled={state.currentPage === 1} disabled={state.currentPage <= 1}
onClick={() => { onClick={() => {
dispatch({ type: 'PAGE_CHANGE', page: state.currentPage - 1 }); dispatch({ type: 'PAGE_CHANGE', page: state.currentPage - 1 });
onPageChange(state.currentPage - 1);
const page = state.currentPage - 1;
const { size: pageSize } = state;
onPageChange({ page, pageSize });
}} }}
minimal={true} minimal={true}
className={'pagination__item pagination__item--previous'} className={'pagination__item pagination__item--previous'}
icon={<Icon icon={'arrow-back-24'} iconSize={12} />} icon={<Icon icon={'arrow-back-24'} iconSize={12} />}
> >
<T id='previous' /> <T id="previous" />
</Button> </Button>
{state.pages.map((page) => ( {state.pages.map((page) => (
@@ -126,7 +132,9 @@ const Pagination = ({
disabled={state.currentPage === page} disabled={state.currentPage === page}
onClick={() => { onClick={() => {
dispatch({ type: 'PAGE_CHANGE', page }); dispatch({ type: 'PAGE_CHANGE', page });
onPageChange(page); const { size: pageSize } = state;
onPageChange({ page, pageSize });
}} }}
minimal={true} minimal={true}
className={classNames( className={classNames(
@@ -134,7 +142,7 @@ const Pagination = ({
'pagination__item--page', 'pagination__item--page',
{ {
'is-active': state.currentPage === page, 'is-active': state.currentPage === page,
} },
)} )}
> >
{page} {page}
@@ -145,15 +153,18 @@ const Pagination = ({
onClick={() => { onClick={() => {
dispatch({ dispatch({
type: 'PAGE_CHANGE', type: 'PAGE_CHANGE',
page: state.currentPage + 1 page: state.currentPage + 1,
}); });
onPageChange(state.currentPage + 1); const page = state.currentPage + 1;
const { size: pageSize } = state;
onPageChange({ page, pageSize });
}} }}
minimal={true} minimal={true}
className={'pagination__item pagination__item--next'} className={'pagination__item pagination__item--next'}
icon={<Icon icon={'arrow-forward-24'} iconSize={12} />} icon={<Icon icon={'arrow-forward-24'} iconSize={12} />}
> >
<T id='next' /> <T id="next" />
</Button> </Button>
</ButtonGroup> </ButtonGroup>
</div> </div>
@@ -167,11 +178,11 @@ const Pagination = ({
value={state.currentPage} value={state.currentPage}
onChange={(event) => { onChange={(event) => {
const page = parseInt(event.currentTarget.value, 10); const page = parseInt(event.currentTarget.value, 10);
const { size: pageSize } = state;
dispatch({ type: 'PAGE_CHANGE', page }); dispatch({ type: 'PAGE_CHANGE', page });
onPageChange(page); onPageChange({ page, pageSize });
}} }}
minimal={true}
/> />
</div> </div>
@@ -186,26 +197,28 @@ const Pagination = ({
dispatch({ type: 'PAGE_SIZE_CHANGE', size: pageSize }); dispatch({ type: 'PAGE_SIZE_CHANGE', size: pageSize });
dispatch({ type: 'PAGE_CHANGE', page: 1 }); dispatch({ type: 'PAGE_CHANGE', page: 1 });
onPageSizeChange(pageSize, 1); onPageSizeChange({ pageSize, page: 1 });
}} }}
minimal={true}
/> />
</div> </div>
</div> </div>
<div class="pagination__info"> <div class="pagination__info">
<T id={'showing_current_page_to_total'} values={{ <T
id={'showing_current_page_to_total'}
values={{
currentPage: state.currentPage, currentPage: state.currentPage,
totalPages: state.totalPages, totalPages: state.totalPages,
total: total, total: total,
}} /> }}
/>
</div> </div>
</div> </div>
); );
}; }
Pagination.propTypes = { Pagination.propTypes = {
initialPage: PropTypes.number.isRequired, currentPage: PropTypes.number.isRequired,
size: PropTypes.number.isRequired, size: PropTypes.number.isRequired,
total: PropTypes.number.isRequired, total: PropTypes.number.isRequired,
onPageChange: PropTypes.func, onPageChange: PropTypes.func,
@@ -213,7 +226,7 @@ Pagination.propTypes = {
}; };
Pagination.defaultProps = { Pagination.defaultProps = {
initialPage: 1, currentPage: 1,
size: 25, size: 25,
}; };

View File

@@ -45,7 +45,7 @@ import PageFormBigNumber from './PageFormBigNumber';
import AccountsMultiSelect from './AccountsMultiSelect'; import AccountsMultiSelect from './AccountsMultiSelect';
import CustomersMultiSelect from './CustomersMultiSelect'; import CustomersMultiSelect from './CustomersMultiSelect';
import Skeleton from './Skeleton' import Skeleton from './Skeleton'
import ContextMenu from './ContextMenu'
import TableFastCell from './Datatable/TableFastCell'; import TableFastCell from './Datatable/TableFastCell';
const Hint = FieldHint; const Hint = FieldHint;
@@ -99,4 +99,5 @@ export {
CustomersMultiSelect, CustomersMultiSelect,
TableFastCell, TableFastCell,
Skeleton, Skeleton,
ContextMenu
}; };

View File

@@ -1,20 +1,18 @@
import React, { useState } from 'react'; import React from 'react';
import { import {
FormattedMessage as T, FormattedMessage as T,
FormattedHTMLMessage, FormattedHTMLMessage,
useIntl, useIntl,
} from 'react-intl'; } from 'react-intl';
import { Intent, Alert } from '@blueprintjs/core'; import { Intent, Alert } from '@blueprintjs/core';
import { queryCache } from 'react-query';
import { AppToaster } from 'components'; import { AppToaster } from 'components';
import { handleDeleteErrors } from 'containers/Items/utils'; import { handleDeleteErrors } from 'containers/Items/utils';
import { useDeleteItem } from 'hooks/query';
import {
useDeleteItem
} from 'hooks/query';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions'; import withAlertActions from 'containers/Alert/withAlertActions';
import withItemsActions from 'containers/Items/withItemsActions';
import { compose } from 'utils'; import { compose } from 'utils';
@@ -30,15 +28,19 @@ function ItemDeleteAlert({
// #withAlertActions // #withAlertActions
closeAlert, closeAlert,
// #withItemsActions
addItemsTableQueries
}) { }) {
const { mutateAsync: deleteItem, isLoading } = useDeleteItem(); const { mutateAsync: deleteItem, isLoading } = useDeleteItem();
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
// handle cancel delete item alert. // Handle cancel delete item alert.
const handleCancelItemDelete = () => { const handleCancelItemDelete = () => {
closeAlert(name); closeAlert(name);
}; };
// Handle confirm delete item.
const handleConfirmDeleteItem = () => { const handleConfirmDeleteItem = () => {
deleteItem(itemId) deleteItem(itemId)
.then(() => { .then(() => {
@@ -48,6 +50,8 @@ function ItemDeleteAlert({
}), }),
intent: Intent.SUCCESS, intent: Intent.SUCCESS,
}); });
// Reset to page number one.
addItemsTableQueries({ page: 1 });
}) })
.catch(({ errors }) => { .catch(({ errors }) => {
handleDeleteErrors(errors); handleDeleteErrors(errors);
@@ -80,4 +84,5 @@ function ItemDeleteAlert({
export default compose( export default compose(
withAlertStoreConnect(), withAlertStoreConnect(),
withAlertActions, withAlertActions,
withItemsActions
)(ItemDeleteAlert); )(ItemDeleteAlert);

View File

@@ -179,6 +179,7 @@ function InventoryAdjustmentDataTable({
autoResetSortBy={false} autoResetSortBy={false}
autoResetPage={false} autoResetPage={false}
isLoading={isLoading} isLoading={isLoading}
noResults={'There is no inventory adjustments transactions yet.'}
// pagesCount={inventoryAdjustmentsPagination.pagesCount} // pagesCount={inventoryAdjustmentsPagination.pagesCount}
// initialPageSize={inventoryAdjustmentsPagination.pageSize} // initialPageSize={inventoryAdjustmentsPagination.pageSize}
// initialPageIndex={inventoryAdjustmentsPagination.page - 1} // initialPageIndex={inventoryAdjustmentsPagination.page - 1}

View File

@@ -1,4 +1,4 @@
import React from 'react'; import React, { useEffect } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { ItemFormProvider } from './ItemFormProvider'; import { ItemFormProvider } from './ItemFormProvider';
@@ -12,9 +12,22 @@ import { compose } from 'utils';
/** /**
* Item form page. * Item form page.
*/ */
function ItemFormPage() { function ItemFormPage({
// #withDashboardActions
setDashboardBackLink
}) {
const { id } = useParams(); const { id } = useParams();
useEffect(() => {
// Show the back link on dashboard topbar.
setDashboardBackLink(true);
return () => {
// Hide the back link on dashboard topbar.
setDashboardBackLink(false);
};
}, [setDashboardBackLink]);
return ( return (
<ItemFormProvider itemId={id}> <ItemFormProvider itemId={id}>
<DashboardCard page> <DashboardCard page>
@@ -24,4 +37,6 @@ function ItemFormPage() {
); );
} }
export default compose(withDashboardActions)(ItemFormPage); export default compose(
withDashboardActions,
)(ItemFormPage);

View File

@@ -53,7 +53,7 @@ function ItemsActionsBar({
// Handle tab changing. // Handle tab changing.
const handleTabChange = (viewId) => { const handleTabChange = (viewId) => {
addItemsTableQueries({ addItemsTableQueries({
custom_view_id: viewId.id || null, customViewId: viewId.id || null,
}); });
}; };
@@ -87,7 +87,7 @@ function ItemsActionsBar({
> >
<Button <Button
className={classNames(Classes.MINIMAL, 'button--filter')} className={classNames(Classes.MINIMAL, 'button--filter')}
text={`${formatMessage({ id: 'filters_applied' })}`} text={`${formatMessage({ id: 'filter' })}`}
icon={<Icon icon="filter-16" iconSize={16} />} icon={<Icon icon="filter-16" iconSize={16} />}
/> />
</Popover> </Popover>

View File

@@ -18,6 +18,7 @@ import {
CostPriceCell, CostPriceCell,
ItemTypeAccessor, ItemTypeAccessor,
ItemsActionsTableCell, ItemsActionsTableCell,
ItemsActionMenuList
} from './components'; } from './components';
// Items datatable. // Items datatable.
@@ -118,11 +119,14 @@ function ItemsDataTable({
manualSortBy={true} manualSortBy={true}
pagesCount={1} pagesCount={1}
autoResetSortBy={false} autoResetSortBy={false}
autoResetPage={false} autoResetPage={true}
manualPagination={true}
TableLoadingRenderer={TableSkeletonRows} TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader} TableHeaderSkeletonRenderer={TableSkeletonHeader}
initialPageSize={pagination.pageSize}
initialPageIndex={pagination.page} pageSize={pagination.pageSize}
pageIndex={pagination.page - 1}
ContextMenu={ItemsActionMenuList}
{...tableProps} {...tableProps}
/> />
</Choose.Otherwise> </Choose.Otherwise>

View File

@@ -1,13 +1,21 @@
import React, { useEffect, createContext } from 'react'; import React, { useEffect, createContext } from 'react';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { isEmpty } from 'lodash';
import { transformTableQueryToParams, isTableEmptyStatus } from 'utils';
import DashboardInsider from 'components/Dashboard/DashboardInsider'; import DashboardInsider from 'components/Dashboard/DashboardInsider';
import { useResourceViews, useResourceFields, useItems } from 'hooks/query'; import { useResourceViews, useResourceFields, useItems } from 'hooks/query';
import { useDashboardPageTitle } from 'hooks/state'; import { useDashboardPageTitle } from 'hooks/state';
const ItemsContext = createContext(); const ItemsContext = createContext();
function ItemsListProvider({ query, ...props }) { /**
* Items list provider.
*/
function ItemsListProvider({
query,
...props
}) {
// Fetch accounts resource views and fields. // Fetch accounts resource views and fields.
const { data: itemsViews, isFetching: isViewsLoading } = useResourceViews( const { data: itemsViews, isFetching: isViewsLoading } = useResourceViews(
'items', 'items',
@@ -21,11 +29,16 @@ function ItemsListProvider({ query, ...props }) {
// Handle fetching the items table based on the given query. // Handle fetching the items table based on the given query.
const { const {
data: { items, pagination, filterMeta }, data: { items, pagination, filterMeta },
isFetching: isItemsLoading, isFetching: isItemsFetching,
} = useItems(query); isLoading: isItemsLoading,
} = useItems({
...transformTableQueryToParams(query)
}, { keepPreviousData: true });
// Detarmines the datatable empty status. // Detarmines the datatable empty status.
const isEmptyStatus = isEmpty(items) && !isItemsLoading && !filterMeta.view; const isEmptyStatus = isTableEmptyStatus({
data: items, pagination, filterMeta,
}) && !isItemsFetching;
// Format message intl. // Format message intl.
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
@@ -42,15 +55,15 @@ function ItemsListProvider({ query, ...props }) {
itemsFields, itemsFields,
items, items,
pagination, pagination,
isViewsLoading, isViewsLoading,
isItemsLoading, isItemsLoading,
isEmptyStatus: false, isItemsFetching: isItemsFetching,
isEmptyStatus,
}; };
return ( return (
<DashboardInsider <DashboardInsider
loading={isFieldsLoading || isViewsLoading} loading={isFieldsLoading}
name={'items-list'} name={'items-list'}
> >
<ItemsContext.Provider value={state} {...props} /> <ItemsContext.Provider value={state} {...props} />

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React, { useMemo } from 'react';
import { Switch, Route, useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import ItemsViewsTabs from './ItemsViewsTabs'; import ItemsViewsTabs from './ItemsViewsTabs';
import ItemsDataTable from './ItemsDataTable'; import ItemsDataTable from './ItemsDataTable';
@@ -7,9 +7,39 @@ import ItemsDataTable from './ItemsDataTable';
import withItemsActions from 'containers/Items/withItemsActions'; import withItemsActions from 'containers/Items/withItemsActions';
import withAlertsActions from 'containers/Alert/withAlertActions'; import withAlertsActions from 'containers/Alert/withAlertActions';
import withDialogActions from 'containers/Dialog/withDialogActions'; import withDialogActions from 'containers/Dialog/withDialogActions';
import withItems from 'containers/Items/withItems';
import { compose } from 'utils'; import { compose } from 'utils';
import { useItemsListContext } from './ItemsListProvider';
function transformPaginationToProps(pagination) {
const { page, pageSize, total } = pagination;
return {
initialPageIndex: Math.max(page - 1, 0),
initialPageSize: pageSize,
pagesCount: Math.ceil(total / pageSize),
};
}
function transformPaginationToQuery(query) {
const { pageSize, pageIndex, sortBy } = query;
return {
page_size: pageSize,
page: pageIndex + 1,
...(sortBy.length > 0
? {
column_sort_by: sortBy[0].id,
sort_order: sortBy[0].desc ? 'desc' : 'asc',
}
: {}),
};
}
/**
* Items view page.
*/
function ItemsViewPage({ function ItemsViewPage({
// #withAlertsActions. // #withAlertsActions.
openAlert, openAlert,
@@ -19,10 +49,14 @@ function ItemsViewPage({
// #withItemsActions. // #withItemsActions.
setSelectedRowsItems, setSelectedRowsItems,
addItemsTableQueries addItemsTableQueries,
itemsTableQuery,
}) { }) {
const history = useHistory(); const history = useHistory();
const { pagination, isItemsFetching } = useItemsListContext();
// Handle delete action Item. // Handle delete action Item.
const handleDeleteItem = ({ id }) => { const handleDeleteItem = ({ id }) => {
openAlert('item-delete', { itemId: id }); openAlert('item-delete', { itemId: id });
@@ -52,28 +86,24 @@ function ItemsViewPage({
// Handle item make adjustment. // Handle item make adjustment.
const handleMakeAdjustment = ({ id }) => { const handleMakeAdjustment = ({ id }) => {
openDialog('inventory-adjustment', { itemId: id }); openDialog('inventory-adjustment', { itemId: id });
} };
// Handle fetch data once the page index, size or sort by of the table change. // Handle fetch data once the page index, size or sort by of the table change.
const handleFetchData = ({ pageIndex, pageSize, sortBy }) => { const handlePaginationChange = ({ pageSize, page }) => {
addItemsTableQueries({ addItemsTableQueries({
page_size: pageSize, // ...transformPaginationToQuery(query),
page: pageIndex, page,
...(sortBy.length > 0 pageSize,
? {
column_sort_by: sortBy[0].id,
sort_order: sortBy[0].desc ? 'desc' : 'asc',
}
: {}),
}); });
}; };
const controlledState = (state) => ({
...state,
pageIndex: itemsTableQuery.page - 1,
});
return ( return (
<Switch> <>
<Route
exact={true}
path={['/items/:custom_view_id/custom_view', '/items']}
>
<ItemsViewsTabs /> <ItemsViewsTabs />
<ItemsDataTable <ItemsDataTable
tableProps={{ tableProps={{
@@ -82,19 +112,23 @@ function ItemsViewPage({
onEditItem: handleEditItem, onEditItem: handleEditItem,
onInactivateItem: handleInactiveItem, onInactivateItem: handleInactiveItem,
onActivateItem: handleActivateItem, onActivateItem: handleActivateItem,
onMakeAdjustment: handleMakeAdjustment onMakeAdjustment: handleMakeAdjustment,
}, },
onFetchData: handleFetchData ...transformPaginationToProps(pagination),
onPaginationChange: handlePaginationChange,
progressBarLoading: isItemsFetching,
// useControlledState: controlledState
// progressBarLoading: true
}} }}
onSelectedRowsChange={handleSelectedRowsChange} onSelectedRowsChange={handleSelectedRowsChange}
/> />
</Route> </>
</Switch>
); );
} }
export default compose( export default compose(
withAlertsActions, withAlertsActions,
withItemsActions, withItemsActions,
withDialogActions withDialogActions,
withItems(({ itemsTableQuery }) => ({ itemsTableQuery })),
)(ItemsViewPage); )(ItemsViewPage);

View File

@@ -1,11 +1,13 @@
import React from 'react'; import React from 'react';
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core'; import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
import { useParams } from 'react-router-dom';
import { compose } from 'utils'; import { compose } from 'utils';
import { DashboardViewsTabs } from 'components'; import { DashboardViewsTabs } from 'components';
import { pick } from 'lodash'; import { pick } from 'lodash';
import { withRouter } from 'react-router-dom';
import withItemsActions from 'containers/Items/withItemsActions'; import withItemsActions from 'containers/Items/withItemsActions';
import withItems from 'containers/Items/withItems';
import { useItemsListContext } from './ItemsListProvider'; import { useItemsListContext } from './ItemsListProvider';
/** /**
@@ -14,17 +16,22 @@ import { useItemsListContext } from './ItemsListProvider';
function ItemsViewsTabs({ function ItemsViewsTabs({
// #withItemsActions // #withItemsActions
addItemsTableQueries, addItemsTableQueries,
// #withItems
itemsTableQuery
}) { }) {
const { custom_view_id: customViewId = null } = useParams();
const { itemsViews } = useItemsListContext(); const { itemsViews } = useItemsListContext();
// Mapped items views.
const tabs = itemsViews.map((view) => ({ const tabs = itemsViews.map((view) => ({
...pick(view, ['name', 'id']), ...pick(view, ['name', 'id']),
})); }));
// Handles the active tab change.
const handleTabChange = (viewId) => { const handleTabChange = (viewId) => {
addItemsTableQueries({ addItemsTableQueries({
custom_view_id: viewId || null, page: 1,
customViewId: viewId || null,
}); });
}; };
@@ -32,7 +39,7 @@ function ItemsViewsTabs({
<Navbar className="navbar--dashboard-views"> <Navbar className="navbar--dashboard-views">
<NavbarGroup align={Alignment.LEFT}> <NavbarGroup align={Alignment.LEFT}>
<DashboardViewsTabs <DashboardViewsTabs
initialViewId={customViewId} currentViewId={itemsTableQuery.customViewId}
resourceName={'items'} resourceName={'items'}
tabs={tabs} tabs={tabs}
onChange={handleTabChange} onChange={handleTabChange}
@@ -42,7 +49,8 @@ function ItemsViewsTabs({
); );
} }
export default compose( export default compose(
withRouter,
withItems(({ itemsTableQuery }) => ({ itemsTableQuery })),
withItemsActions, withItemsActions,
)(ItemsViewsTabs); )(ItemsViewsTabs);

View File

@@ -71,7 +71,7 @@ export const ItemTypeAccessor = (row) => {
) : null; ) : null;
}; };
export const ItemsActionMenuList = ({ export function ItemsActionMenuList({
row: { original }, row: { original },
payload: { payload: {
onEditItem, onEditItem,
@@ -80,7 +80,7 @@ export const ItemsActionMenuList = ({
onMakeAdjustment, onMakeAdjustment,
onDeleteItem, onDeleteItem,
}, },
}) => { }) {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
return ( return (
@@ -112,6 +112,7 @@ export const ItemsActionMenuList = ({
<If condition={original.type === 'inventory'}> <If condition={original.type === 'inventory'}>
<MenuItem <MenuItem
text={formatMessage({ id: 'make_adjustment' })} text={formatMessage({ id: 'make_adjustment' })}
icon={<Icon icon={'swap-vert'} iconSize={16} />}
onClick={safeCallback(onMakeAdjustment, original)} onClick={safeCallback(onMakeAdjustment, original)}
/> />
</If> </If>

View File

@@ -1,30 +1,15 @@
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import { import {
getResourceViews,
} from 'store/customViews/customViews.selectors'
import {
getItemsCurrentPageFactory,
getItemsPaginationMetaFactory,
getItemsTableQueryFactory, getItemsTableQueryFactory,
getItemsCurrentViewIdFactory
} from 'store/items/items.selectors'; } from 'store/items/items.selectors';
export default (mapState) => { export default (mapState) => {
const getItemsCurrentPage = getItemsCurrentPageFactory();
const getItemsPaginationMeta = getItemsPaginationMetaFactory();
const getItemsTableQuery = getItemsTableQueryFactory(); const getItemsTableQuery = getItemsTableQueryFactory();
const getItemsCurrentViewId = getItemsCurrentViewIdFactory();
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
const mapped = { const mapped = {
itemsViews: getResourceViews(state, props, 'items'),
itemsCurrentPage: getItemsCurrentPage(state, props),
itemsBulkSelected: state.items.bulkActions,
itemsTableLoading: state.items.loading,
itemsSelectedRows: state.items.selectedRows, itemsSelectedRows: state.items.selectedRows,
itemsTableQuery: getItemsTableQuery(state, props), itemsTableQuery: getItemsTableQuery(state, props),
itemsPagination: getItemsPaginationMeta(state, props),
itemsCurrentViewId: getItemsCurrentViewId(state, props),
}; };
return mapState ? mapState(mapped, state, props) : mapped; return mapState ? mapState(mapped, state, props) : mapped;
}; };

View File

@@ -1,35 +1,7 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import {
fetchItems,
fetchItem,
deleteItem,
submitItem,
editItem,
deleteBulkItems,
activateItem,
inactiveItem,
} from 'store/items/items.actions';
import t from 'store/types'; import t from 'store/types';
export const mapDispatchToProps = (dispatch) => ({ export const mapDispatchToProps = (dispatch) => ({
requestFetchItems: (query) => dispatch(fetchItems({ query })),
requestFetchItem: (id) => dispatch(fetchItem({ id })),
requestDeleteItem: (id) => dispatch(deleteItem({ id })),
requestDeleteBulkItems: (ids) => dispatch(deleteBulkItems({ ids })),
requestSubmitItem: (form) => dispatch(submitItem({ form })),
requestEditItem: (id, form) => dispatch(editItem(id, form)),
requestInactiveItem: (id) => dispatch(inactiveItem({ id })),
requestActivateItem: (id) => dispatch(activateItem({ id })),
addBulkActionItem: (id) =>
dispatch({
type: t.ITEM_BULK_ACTION_ADD,
itemId: id,
}),
removeBulkActionItem: (id) =>
dispatch({
type: t.ITEM_BULK_ACTION_REMOVE,
itemId: id,
}),
setItemsTableQuery: (key, value) => setItemsTableQuery: (key, value) =>
dispatch({ dispatch({
type: t.ITEMS_TABLE_QUERY_SET, type: t.ITEMS_TABLE_QUERY_SET,
@@ -41,12 +13,6 @@ export const mapDispatchToProps = (dispatch) => ({
type: t.ITEMS_TABLE_QUERIES_ADD, type: t.ITEMS_TABLE_QUERIES_ADD,
payload: { queries }, payload: { queries },
}), }),
changeItemsCurrentView: (id) =>
dispatch({
type: t.ITEMS_SET_CURRENT_VIEW,
currentViewId: parseInt(id, 10),
}),
setSelectedRowsItems: (selectedRows) => setSelectedRowsItems: (selectedRows) =>
dispatch({ dispatch({
type: t.ITEM_SELECTED_ROWS_SET, type: t.ITEM_SELECTED_ROWS_SET,

View File

@@ -68,7 +68,7 @@ export default function ItemsCategoryTable({
sticky={true} sticky={true}
selectionColumn={true} selectionColumn={true}
TableLoadingRenderer={TableSkeletonRows} TableLoadingRenderer={TableSkeletonRows}
{...tableProps} noResults={'There is no items categories in table yet.'}
/> />
</div> </div>
); );

View File

@@ -1,6 +1,8 @@
import { useMutation, useQuery, useQueryClient } from 'react-query'; import { useMutation, useQuery, useQueryClient } from 'react-query';
import { defaultTo } from 'lodash';
import ApiService from 'services/ApiService'; import ApiService from 'services/ApiService';
// Transform the account.
const transformAccount = (response) => { const transformAccount = (response) => {
return response.data.account; return response.data.account;
}; };
@@ -9,17 +11,18 @@ const transformAccount = (response) => {
* Retrieve accounts list. * Retrieve accounts list.
*/ */
export function useAccounts(query, props) { export function useAccounts(query, props) {
return useQuery( const states = useQuery(
['ACCOUNTS', query], ['ACCOUNTS', query],
() => () =>
ApiService.get('accounts', { params: query }).then( ApiService.get('accounts', { params: query }).then(
(response) => response.data.accounts, (response) => response.data.accounts,
), ),
{ props,
initialData: [],
...props
},
); );
return {
...states,
data: defaultTo(states.data, []),
};
} }
/** /**
@@ -27,23 +30,30 @@ export function useAccounts(query, props) {
* @param {number} id - * @param {number} id -
*/ */
export function useAccount(id, props) { export function useAccount(id, props) {
return useQuery( const states = useQuery(
['ACCOUNT', id], ['ACCOUNT', id],
() => ApiService.get(`accounts/${id}`).then(transformAccount), () => ApiService.get(`accounts/${id}`).then(transformAccount),
{ props,
initialData: {},
...props,
},
); );
return {
...states,
data: defaultTo(states.data, {}),
};
} }
/** /**
* Retrieve accounts types list. * Retrieve accounts types list.
*/ */
export function useAccountsTypes() { export function useAccountsTypes(props) {
return useQuery(['ACCOUNTS_TYPES'], () => ApiService.get('account_types'), { const states = useQuery(
initialData: [], ['ACCOUNTS_TYPES'],
}); () => ApiService.get('account_types'),
props,
);
return {
...states,
data: defaultTo(states.data, {}),
};
} }
/** /**
@@ -56,7 +66,7 @@ export function useCreateAccount(props) {
onSuccess: () => { onSuccess: () => {
client.invalidateQueries('ACCOUNTS'); client.invalidateQueries('ACCOUNTS');
}, },
...props ...props,
}); });
} }
@@ -72,7 +82,7 @@ export function useEditAccount(props) {
onSuccess: () => { onSuccess: () => {
query.invalidateQueries('ACCOUNTS'); query.invalidateQueries('ACCOUNTS');
}, },
...props ...props,
}, },
); );
} }
@@ -103,15 +113,10 @@ export function useDeleteAccount(props) {
export function useActivateAccount(props) { export function useActivateAccount(props) {
const query = useQueryClient(); const query = useQueryClient();
return useMutation( return useMutation((id) => ApiService.post(`accounts/${id}/activate`), {
(id) => ApiService.post(`accounts/${id}/activate`), onSuccess: () => {},
{ ...props,
onSuccess: () => { });
},
...props
}
);
} }
/** /**
@@ -120,13 +125,8 @@ export function useActivateAccount(props) {
export function useInactivateAccount(props) { export function useInactivateAccount(props) {
const query = useQueryClient(); const query = useQueryClient();
return useMutation( return useMutation((id) => ApiService.post(`accounts/${id}/inactivate`), {
(id) => ApiService.post(`accounts/${id}/inactivate`), onSuccess: () => {},
{ ...props,
onSuccess: () => { });
},
...props
},
);
} }

View File

@@ -1,28 +1,55 @@
import { useMutation, useQuery } from 'react-query'; import { useMutation, useQueryClient, useQuery } from 'react-query';
import { defaultTo } from 'lodash';
import ApiService from 'services/ApiService'; import ApiService from 'services/ApiService';
/** /**
* Create a new currency. * Create a new currency.
*/ */
export function useCreateCurrency() { export function useCreateCurrency(props) {
return useMutation((values) => ApiService.post('currencies', values)); const queryClient = useQueryClient();
return useMutation(
(values) => ApiService.post('currencies', values),
{
onSuccess: () => {
queryClient.invalidateQueries('CURRENCIES');
},
...props,
}
);
} }
/** /**
* Edits the given currency code. * Edits the given currency code.
*/ */
export function useEditCurrency() { export function useEditCurrency(props) {
const queryClient = useQueryClient();
return useMutation((currencyCode, values) => return useMutation((currencyCode, values) =>
ApiService.post(`currencies/${currencyCode}`, values), ApiService.post(`currencies/${currencyCode}`, values),
{
onSuccess: () => {
queryClient.invalidateQueries('CURRENCIES');
},
...props,
}
); );
} }
/** /**
* Deletes the given currency. * Deletes the given currency.
*/ */
export function useDeleteCurrency() { export function useDeleteCurrency(props) {
const queryClient = useQueryClient();
return useMutation((currencyCode) => return useMutation((currencyCode) =>
ApiService.delete(`currencies/${currencyCode}`), ApiService.delete(`currencies/${currencyCode}`),
{
onSuccess: () => {
queryClient.invalidateQueries('CURRENCIES');
},
...props
}
); );
} }
@@ -30,12 +57,14 @@ export function useDeleteCurrency() {
* Retrieve the currencies list. * Retrieve the currencies list.
*/ */
export function useCurrencies(props) { export function useCurrencies(props) {
return useQuery( const states = useQuery(
['CURRENCIES'], ['CURRENCIES'],
() => ApiService.get('currencies').then(res => res.data.currencies), () => ApiService.get('currencies').then(res => res.data.currencies),
{ props,
initialData: [],
...props,
},
); );
return {
...states,
data: defaultTo(states.data, []),
}
} }

View File

@@ -1,4 +1,5 @@
import { useMutation, useQuery, useQueryClient } from 'react-query'; import { useMutation, useQuery, useQueryClient } from 'react-query';
import { defaultTo } from 'lodash';
import ApiService from 'services/ApiService'; import ApiService from 'services/ApiService';
import { transformResponse } from 'utils'; import { transformResponse } from 'utils';
@@ -12,7 +13,14 @@ const defaultPagination = {
* Creates a new item. * Creates a new item.
*/ */
export function useCreateItem(props) { export function useCreateItem(props) {
return useMutation((values) => ApiService.post('items', values), props); const queryClient = useQueryClient();
return useMutation((values) => ApiService.post('items', values), {
onSuccess: () => {
queryClient.invalidateQueries('ITEMS');
},
...props,
});
} }
/** /**
@@ -58,19 +66,21 @@ const transformItemsResponse = (response) => {
* Retrieves items list. * Retrieves items list.
*/ */
export function useItems(query, props) { export function useItems(query, props) {
return useQuery( const result = useQuery(
['ITEMS', query], ['ITEMS', query],
() => () =>
ApiService.get(`items`, { params: query }).then(transformItemsResponse), ApiService.get(`items`, { params: query }).then(transformItemsResponse),
{ props,
initialData: { );
return {
...result,
data: defaultTo(result.data, {
items: [], items: [],
pagination: defaultPagination, pagination: defaultPagination,
filterMeta: {}, filterMeta: {},
}, }),
...props, };
},
);
} }
/** /**

View File

@@ -1,6 +1,6 @@
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import { defaultTo } from 'lodash';
import ApiService from "services/ApiService"; import ApiService from "services/ApiService";
// export function useSaveView(values) { // export function useSaveView(values) {
// return ApiService.post('views', form); // return ApiService.post('views', form);
// } // }
@@ -18,14 +18,16 @@ import ApiService from "services/ApiService";
// } // }
export function useResourceViews(resourceSlug) { export function useResourceViews(resourceSlug) {
return useQuery( const states = useQuery(
['RESOURCE_VIEW', resourceSlug], ['RESOURCE_VIEW', resourceSlug],
() => ApiService.get(`views/resource/${resourceSlug}`) () => ApiService.get(`views/resource/${resourceSlug}`)
.then((response) => response.data.views), .then((response) => response.data.views),
{
initialData: [],
}
); );
return {
...states,
data: defaultTo(states.data, []),
}
} }
@@ -39,13 +41,16 @@ export function useResourceColumns(resourceSlug) {
); );
} }
export function useResourceFields(resourceSlug) { export function useResourceFields(resourceSlug, props) {
return useQuery( const states = useQuery(
['RESOURCE_FIELDS', resourceSlug], ['RESOURCE_FIELDS', resourceSlug],
() => ApiService.get(`resources/${resourceSlug}/fields`) () => ApiService.get(`resources/${resourceSlug}/fields`)
.then((res) => res.data.resource_fields), .then((res) => res.data.resource_fields),
{ props
initialData: [],
},
); );
return {
...states,
data: defaultTo(states.data, []),
}
} }

View File

@@ -1,10 +1,27 @@
import { useCallback } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { dashboardPageTitle } from 'store/dashboard/dashboard.actions'; import { dashboardPageTitle } from 'store/dashboard/dashboard.actions';
export const useDashboardPageTitle = () => { export const useDispatchAction = (action) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
return (pageTitle) => { return useCallback(
dispatch(dashboardPageTitle(pageTitle)); (payload) => {
} dispatch(action(payload));
},
[dispatch, action],
);
}; };
export const useDashboardPageTitle = () => {
return useDispatchAction(dashboardPageTitle);
};
export const useSetAccountsTableQuery = () => {
};
export const useAccountsTableQuery = () => {
}

View File

@@ -367,5 +367,11 @@ export default {
'M10 20C4.48 20 0 15.52 0 10S4.48 0 10 0s10 4.48 10 10-4.48 10-10 10zm5-14c-.28 0-.53.11-.71.29L8 12.59l-2.29-2.3a1.003 1.003 0 00-1.42 1.42l3 3c.18.18.43.29.71.29.28 0 .53-.11.71-.29l7-7A1.003 1.003 0 0015 6z' 'M10 20C4.48 20 0 15.52 0 10S4.48 0 10 0s10 4.48 10 10-4.48 10-10 10zm5-14c-.28 0-.53.11-.71.29L8 12.59l-2.29-2.3a1.003 1.003 0 00-1.42 1.42l3 3c.18.18.43.29.71.29.28 0 .53-.11.71-.29l7-7A1.003 1.003 0 0015 6z'
], ],
viewBox: '0 0 20 20' viewBox: '0 0 20 20'
},
'swap-vert': {
path: [
'M10.6,10.9V5.4H9v5.5H6.7L9.8,14l3.1-3.1ZM5.1,0,2,3.1H4.3V8.6H5.9V3.1H8.2Z',
],
viewBox: '0 0 14 14'
} }
}; };

View File

@@ -1,139 +0,0 @@
import ApiService from 'services/ApiService';
import t from 'store/types';
export const submitItem = ({ form }) => {
return (dispatch) =>
new Promise((resolve, reject) => {
ApiService.post('items', form)
.then((response) => {
resolve(response);
})
.catch((error) => {
const { response } = error;
const { data } = response;
reject(data?.errors);
});
});
};
export const editItem = (id, form) => {
return (dispatch) =>
new Promise((resolve, reject) => {
ApiService.post(`items/${id}`, form)
.then((response) => {
resolve(response);
})
.catch((error) => {
const { response } = error;
const { data } = response;
reject(data?.errors);
});
});
};
export const fetchItems = ({ query }) => {
return (dispatch, getState) =>
new Promise((resolve, reject) => {
let pageQuery = getState().items.tableQuery;
dispatch({
type: t.ITEMS_TABLE_LOADING,
payload: { loading: true },
});
ApiService.get(`items`, { params: { ...pageQuery, ...query } })
.then((response) => {
dispatch({
type: t.ITEMS_SET,
items: response.data.items,
});
dispatch({
type: t.ITEMS_PAGE_SET,
items: response.data.items,
customViewId:
response.data?.filter_meta?.view?.custom_view_id || -1,
paginationMeta: response.data.pagination,
});
dispatch({
type: t.ITEMS_PAGINATION_SET,
payload: {
pagination: response.data.pagination,
customViewId:
response.data?.filter_meta?.view?.custom_view_id || -1,
},
});
dispatch({
type: t.ITEMS_TABLE_LOADING,
payload: { loading: false },
});
resolve(response);
})
.catch((error) => {
reject(error);
});
});
};
export const fetchItem = ({ id }) => {
return (dispatch) =>
new Promise((resolve, reject) => {
ApiService.get(`items/${id}`)
.then((response) => {
dispatch({
type: t.ITEM_SET,
payload: {
id,
item: response.data.item,
},
});
resolve(response);
})
.catch((error) => {
reject(error);
});
});
};
export const deleteItem = ({ id }) => {
return (dispatch) =>
new Promise((resolve, reject) => {
ApiService.delete(`items/${id}`)
.then((response) => {
dispatch({
type: t.ITEM_DELETE,
payload: { id },
});
resolve(response);
})
.catch((error) => {
reject(error?.response?.data);
});
});
};
export const deleteBulkItems = ({ ids }) => {
return (dispatch) =>
new Promise((resolve, reject) => {
ApiService.delete('items', { params: { ids } })
.then((response) => {
dispatch({
type: t.ITEMS_BULK_DELETE,
payload: { ids },
});
resolve(response);
})
.catch((error) => {
reject(error.response.data.errors || []);
});
});
};
export const activateItem = ({ id }) => {
return (dispatch) => ApiService.post(`items/${id}/activate`);
};
export const inactiveItem = ({ id }) => {
return (dispatch) => ApiService.post(`items/${id}/inactivate`);
};

View File

@@ -1,111 +1,16 @@
import t from 'store/types';
import { createReducer } from '@reduxjs/toolkit'; import { createReducer } from '@reduxjs/toolkit';
import { getItemsViewPages } from 'store/items/items.selectors';
import { import {
viewPaginationSetReducer,
createTableQueryReducers, createTableQueryReducers,
} from 'store/journalNumber.reducer'; } from 'store/journalNumber.reducer';
const initialState = { const initialState = {
items: {},
views: {},
itemsRelation: {},
currentPage: 1,
currentViewId: -1,
bulkActions: {},
loading: false,
tableQuery: { tableQuery: {
page_size: 12, pageSize: 12,
page: 1, page: 1,
}, },
selectedRows: [], selectedRows: [],
}; };
export default createReducer(initialState, { export default createReducer(initialState, {
[t.ITEMS_SET]: (state, action) => {
const _items = {};
action.items.forEach((item) => {
_items[item.id] = item;
});
state.items = {
...state.items,
..._items,
};
},
[t.ITEM_SET]: (state, action) => {
const { id, item } = action.payload;
state.items[id] = { ...item };
},
[t.ITEMS_PAGE_SET]: (state, action) => {
const { items, customViewId, paginationMeta } = action;
const viewId = customViewId || -1;
const view = state.views[viewId] || {};
state.views[viewId] = {
...view,
pages: {
...(state.views?.[viewId]?.pages || {}),
[paginationMeta.page]: {
ids: items.map((item) => item.id),
},
},
};
},
[t.ITEM_BULK_ACTION_ADD]: (state, action) => {
state.bulkActions[action.itemId] = true;
},
[t.ITEM_SELECTED_ROWS_SET]: (state, action) => {
const { selectedRows } = action.payload;
state.selectedRows = selectedRows;
},
[t.ITEM_BULK_ACTION_REMOVE]: (state, action) => {
delete state.bulkActions[action.itemId];
},
[t.ITEM_DELETE]: (state, action) => {
const { id } = action.payload;
const items = { ...state.items };
if (items[id]) {
const item = items[id];
delete items[id];
state.items = items;
}
},
[t.ITEMS_TABLE_LOADING]: (state, action) => {
const { loading } = action.payload;
state.loading = !!loading;
},
[t.ITEMS_SET_CURRENT_VIEW]: (state, action) => {
state.currentViewId = action.currentViewId;
},
[t.ITEMS_BULK_DELETE]: (state, action) => {
const { ids } = action.payload;
const items = { ...state.items };
ids.forEach((id) => {
if (typeof items[id] !== 'undefined') {
delete items[id];
}
});
state.items = items;
},
...viewPaginationSetReducer(t.ITEMS_PAGINATION_SET),
...createTableQueryReducers('ITEMS'), ...createTableQueryReducers('ITEMS'),
}); });
export const getItemById = (state, id) => {
return state.items.items[id];
};

View File

@@ -1,28 +1,11 @@
import { paginationLocationQuery } from 'store/selectors'; import { paginationLocationQuery } from 'store/selectors';
import { createSelector } from 'reselect'; import { createDeepEqualSelector } from 'utils';
import { pickItemsFromIds, defaultPaginationMeta } from 'store/selectors';
const itemsTableQuerySelector = (state) => state.items.tableQuery; const itemsTableQuerySelector = (state) => state.items.tableQuery;
const itemsCurrentPageSelector = (state, props) => {
const currentViewId = state.items.currentViewId;
const currentView = state.items.views?.[currentViewId];
const currentPageId = currentView?.paginationMeta?.page;
return currentView?.pages?.[currentPageId];
};
const itemsDataSelector = (state) => state.items.items;
const itemsPaginationSelector = (state, props) => {
const viewId = state.items.currentViewId;
return state.items.views?.[viewId]?.paginationMeta;
};
const itemsCurrentViewIdSelector = (state) => {
return state.items.currentViewId;
};
// Get items table query marged with location query. // Get items table query marged with location query.
export const getItemsTableQueryFactory = () => export const getItemsTableQueryFactory = () =>
createSelector( createDeepEqualSelector(
paginationLocationQuery, paginationLocationQuery,
itemsTableQuerySelector, itemsTableQuerySelector,
(locationQuery, tableQuery) => { (locationQuery, tableQuery) => {
@@ -32,30 +15,3 @@ export const getItemsTableQueryFactory = () =>
}; };
}, },
); );
// Retrieve items current page and view.
export const getItemsCurrentPageFactory = () =>
createSelector(
itemsDataSelector,
itemsCurrentPageSelector,
(items, itemsIdsCurrentPage) => {
return typeof itemsIdsCurrentPage === 'object'
? pickItemsFromIds(items, itemsIdsCurrentPage.ids) || []
: [];
},
);
// Retrieve items pagination meta.
export const getItemsPaginationMetaFactory = () =>
createSelector(itemsPaginationSelector, (itemsPagination) => {
return {
...defaultPaginationMeta(),
...itemsPagination,
};
});
// Retrieve items current view id.
export const getItemsCurrentViewIdFactory = () =>
createSelector(itemsCurrentViewIdSelector, (currentViewId) => {
return currentViewId;
});

View File

@@ -40,7 +40,7 @@ export const paginationLocationQuery = (state, props) => {
? new URLSearchParams(props.location.search) ? new URLSearchParams(props.location.search)
: null; : null;
const queryParamsKeys = ['page_size', 'page']; const queryParamsKeys = ['page_size', 'page', 'custom_view_id'];
return queryParams return queryParams
? mapValues(pick(Object.fromEntries(queryParams), queryParamsKeys), (v) => ? mapValues(pick(Object.fromEntries(queryParams), queryParamsKeys), (v) =>

View File

@@ -44,14 +44,7 @@ body.hide-scrollbar .Pane2{
} }
.select-list--fill-popover {
position: relative;
.bp3-transition-container,
.bp3-popover {
min-width: 100%;
}
}
.bp3-fill{ .bp3-fill{
.bp3-popover-wrapper, .bp3-popover-wrapper,
@@ -91,12 +84,6 @@ body.hide-scrollbar .Pane2{
max-width: 300px; max-width: 300px;
} }
.form-group--select-list{
button{
justify-content: start;
}
}
.bp3-timezone-picker{ .bp3-timezone-picker{
.bp3-button{ .bp3-button{
@@ -106,3 +93,5 @@ body.hide-scrollbar .Pane2{
} }
} }
} }

View File

@@ -64,6 +64,10 @@
border-bottom: 0; border-bottom: 0;
} }
} }
.bp3-context-menu-popover-target{
z-index: 100;
}
} }
.th, .th,

View File

@@ -1,12 +1,12 @@
.pagination{ .pagination{
display: flex; display: flex;
padding: 25px 14px; padding: 28px 14px;
font-size: 13px; font-size: 13px;
.bp3-button{ .bp3-button{
background: transparent; background: transparent;
padding: 4px 4px; padding: 5px;
} }
&__item{ &__item{
@@ -15,7 +15,7 @@
&:not([class*="bp3-intent-"]){ &:not([class*="bp3-intent-"]){
color: #666666; color: #666666;
border-radius: 3px; border-radius: 5px;
&:hover{ &:hover{
background-color: #E6EFFB; background-color: #E6EFFB;
@@ -28,14 +28,14 @@
} }
.pagination .pagination__buttons-group .bp3-button-group &{ .pagination .pagination__buttons-group .bp3-button-group &{
border-radius: 3px; border-radius: 5px;
} }
&--next, &--next,
&--previous{ &--previous{
&.bp3-button{ &.bp3-button{
padding-left: 6px; padding-left: 10px;
padding-right: 6px; padding-right: 10px;
} }
} }
@@ -63,20 +63,22 @@
} }
&__info{ &__info{
padding-top: 6px; color: #888;
color: #999;
margin-left: 12px; margin-left: 12px;
display: flex;
align-items: center;
} }
&__controls{ &__controls{
display: flex; display: flex;
align-items: center;
margin-left: auto; margin-left: auto;
.bp3-html-select{ .bp3-html-select{
margin-left: 6px; margin-left: 6px;
select{ select{
height: 20px; height: 24px;
width: auto; width: auto;
padding: 0; padding: 0;
padding-right: 0px; padding-right: 0px;
@@ -103,7 +105,6 @@
&__pagesize-control{ &__pagesize-control{
margin-left: 12px; margin-left: 12px;
padding-top: 4px; color: #888;
color: #777;
} }
} }

View File

@@ -0,0 +1,101 @@
// IONIC DEFAULT THEME COLORS
$colors: (
primary: #387ef5,
secondary: #32db64,
danger: #f53d3d,
light: #f4f4f4,
dark: #222
);
// https://css-tricks.com/html5-progress-element/
.progress, progress[value] {
width: 100%;
border: none;
margin: 5px 0;
height: 5px;
display: block;
appearance: none;
-webkit-appearance: none;
&::-webkit-progress-bar {
background-color: lighten( map-get($colors, primary), 35% );
}
&::-webkit-progress-value {
background-color: map-get($colors, primary);
}
}
// http://materializecss.com/preloader.html
// https://github.com/Dogfalo/materialize/blob/master/dist/css/materialize.css
.progress-materializecss {
position: absolute;
top: -1px;
height: 2px;
display: block;
width: 100%;
background-color: transparent;
margin: 0;
overflow: hidden;
.indeterminate {
background-color: #002fff;
&:before{
content: '';
position: absolute;
background-color: inherit;
top: 0;
left: 0;
bottom: 0;
will-change: left, right;
animation: indeterminate 3.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
}
&:after {
content: '';
position: absolute;
background-color: inherit;
top: 0;
left: 0;
bottom: 0;
will-change: left, right;
animation: indeterminate-short 3.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
animation-delay: 2.15s;
}
}
}
@keyframes indeterminate {
0% {
left: -35%;
right: 100%;
}
60% {
left: 100%;
right: -90%;
}
100% {
left: 100%;
right: -90%;
}
}
@keyframes indeterminate-short {
0% {
left: -200%;
right: 100%;
}
60% {
left: 107%;
right: -8%;
}
100% {
left: 107%;
right: -8%;
}
}
.progress-container{
width: 100%;
position: relative;
}
// IRRELEVANTS STYLES //
/////////////////////////

View File

@@ -1,30 +1,4 @@
.form-group--select-list {
.bp3-popover-target {
.bp3-icon {
position: absolute;
top: 0;
left: 0;
margin: 7px;
+ .bp3-button-text {
padding-left: 20px;
}
}
.bp3-button {
padding-left: 10px;
color: #333;
}
}
}
.form-group--select-list {
.bp3-popover-open {
.bp3-button {
border-color: #80bdff;
}
}
}
.bp3-button { .bp3-button {
min-width: 32px; min-width: 32px;
@@ -41,24 +15,6 @@
font-size: 13px; font-size: 13px;
min-height: 29px; min-height: 29px;
} }
.form-group--select-list & {
border-radius: 2px;
&,
&:hover {
box-shadow: 0 0 0 transparent;
border: 1px solid #ced4da;
&:not(:disabled) {
background: #fff;
}
}
}
.form-group--select-list.bp3-intent-danger & {
& {
border-color: #db3737;
}
}
} }
.bp3-button-group.bp3-minimal .bp3-button { .bp3-button-group.bp3-minimal .bp3-button {

View File

@@ -167,16 +167,70 @@ label.bp3-label {
} }
.form-group--select-list { .form-group--select-list {
.form-group--select-list.bp3-intent-danger & {
& {
border-color: #db3737;
}
}
.#{$ns}-button { .#{$ns}-button {
color: #8d8d8d; color: #8d8d8d;
position: relative; position: relative;
padding-right: 25px; padding-right: 25px;
justify-content: start;
&:not([class*="bp3-intent-"]):not(.bp3-minimal){
&,
&:hover {
outline: none;
box-shadow: 0 0 0 transparent;
border: 1px solid #ced4da;
&:not(:disabled) {
background: #fff;
}
}
&:focus{
box-shadow: 0 0 0 1px #116cd0;
border-color: #116cd0;
}
}
} }
&.bp3-fill { &.bp3-fill {
width: 100%; width: 100%;
} }
.bp3-popover-target.bp3-popover-open{
.#{$ns}-button {
&:not([class*="bp3-intent-"]):not(.bp3-minimal):not(:disabled),
&:not([class*="bp3-intent-"]):not(.bp3-minimal):hover:not(:disabled){
background-color: #fafafa;
}
}
}
.bp3-popover-target {
.bp3-icon {
position: absolute;
top: 0;
left: 0;
margin: 7px;
+ .bp3-button-text {
padding-left: 20px;
}
}
.bp3-button {
padding-left: 10px;
color: #333;
}
}
&.bp3-intent-danger { &.bp3-intent-danger {
.bp3-button:not(.bp3-minimal) { .bp3-button:not(.bp3-minimal) {
border-color: #db3737; border-color: #db3737;
@@ -184,6 +238,15 @@ label.bp3-label {
} }
} }
.select-list--fill-popover {
position: relative;
.bp3-transition-container,
.bp3-popover {
min-width: 100%;
}
}
.select-list--tooltip-items .bp3-popover-target { .select-list--tooltip-items .bp3-popover-target {
display: block; display: block;
} }

View File

@@ -79,19 +79,18 @@ $dashboard-views-bar-height: 45px;
.bp3-button:not([class*='bp3-intent-']):not(.bp3-minimal) { .bp3-button:not([class*='bp3-intent-']):not(.bp3-minimal) {
padding: 0; padding: 0;
background-size: contain; background-size: contain;
background-color: #eed1f2;
border-radius: 50%; border-radius: 50%;
height: 35px; height: 32px;
width: 35px; width: 32px;
.user-text { .user-text {
font-size: 12px; font-size: 12px;
color: #804F87; color: #fff;
} }
&, &,
&:hover, &:hover,
&:focus { &:focus {
background-color: #EEB1F7; background-color: #cb20e5;
border: 0; border: 0;
box-shadow: none; box-shadow: none;
} }
@@ -120,7 +119,7 @@ $dashboard-views-bar-height: 45px;
} }
&__back-link { &__back-link {
margin-left: 24px; margin-left: 20px;
display: flex; display: flex;
a { a {
@@ -128,6 +127,15 @@ $dashboard-views-bar-height: 45px;
} }
} }
&__organization-name{
font-size: 14px;
font-weight: 400;
color: #818ca9;
margin: 0 0 0 15px;
display: flex;
align-items: center;
}
&__actions-bar { &__actions-bar {
border-bottom: 2px solid #eaeaea; border-bottom: 2px solid #eaeaea;
@@ -224,14 +232,7 @@ $dashboard-views-bar-height: 45px;
margin: 0; margin: 0;
} }
h3 { h3 {
border-left: 1px solid #d9d9d9;
padding-left: 10px;
font-size: 16px;
font-weight: 400;
color: #76768b;
margin: 0 0 0 12px;
padding-top: 4px;
padding-bottom: 4px;
} }
.button--view-edit { .button--view-edit {

View File

@@ -32,17 +32,17 @@
background-size: contain; background-size: contain;
background-color: #eed1f2; background-color: #eed1f2;
border-radius: 50%; border-radius: 50%;
height: 35px; height: 32px;
width: 35px; width: 32px;
.user-text { .user-text {
font-size: 12px; font-size: 12px;
color: #804f87; color: #fff;
} }
&, &,
&:hover, &:hover,
&:focus { &:focus {
background-color: #eed1f2; background-color: #cb20e5;
border: 0; border: 0;
box-shadow: none; box-shadow: none;
} }

View File

@@ -5,7 +5,8 @@ import Currency from 'js-money/lib/currency';
import PProgress from 'p-progress'; import PProgress from 'p-progress';
import accounting from 'accounting'; import accounting from 'accounting';
import deepMapKeys from 'deep-map-keys'; import deepMapKeys from 'deep-map-keys';
import { createSelectorCreator, defaultMemoize } from 'reselect';
import { isEqual } from 'lodash';
export function removeEmptyFromObject(obj) { export function removeEmptyFromObject(obj) {
obj = Object.assign({}, obj); obj = Object.assign({}, obj);
@@ -411,6 +412,10 @@ export const transfromToSnakeCase = (object) => {
return deepMapKeys(object, (key) => _.snakeCase(key)); return deepMapKeys(object, (key) => _.snakeCase(key));
}; };
export const transformTableQueryToParams = (object) => {
return transfromToSnakeCase(object);
};
export function flatObject(obj) { export function flatObject(obj) {
const flatObject = {}; const flatObject = {};
const path = []; // current path const path = []; // current path
@@ -467,3 +472,19 @@ export function transactionNumber(prefix, number) {
export function safeCallback(callback, ...args) { export function safeCallback(callback, ...args) {
return () => callback && callback(...args); return () => callback && callback(...args);
} }
export const createDeepEqualSelector = createSelectorCreator(
defaultMemoize,
isEqual
);
/**
* Detarmines whether the table has empty status.
*/
export const isTableEmptyStatus = ({ data, pagination, filterMeta }) => {
return [
_.isEmpty(data),
_.isEmpty(filterMeta.view),
pagination.page === 1,
].every(cond => cond === true)
}