feat: Pagination component.

This commit is contained in:
Ahmed Bouhuolia
2020-06-21 19:21:27 +02:00
parent 3e15cd42c8
commit 15bcd55979
19 changed files with 668 additions and 167 deletions

View File

@@ -16,7 +16,7 @@ import { ScrollSync, ScrollSyncPane } from 'react-scroll-sync';
import { ConditionalWrapper } from 'utils'; import { ConditionalWrapper } from 'utils';
import { useUpdateEffect } from 'hooks'; import { useUpdateEffect } from 'hooks';
import { If } from 'components'; import { If, Pagination } from 'components';
const IndeterminateCheckbox = React.forwardRef( const IndeterminateCheckbox = React.forwardRef(
({ indeterminate, ...rest }, ref) => { ({ indeterminate, ...rest }, ref) => {
@@ -46,6 +46,11 @@ export default function DataTable({
expandToggleColumn = 2, expandToggleColumn = 2,
noInitialFetch = false, noInitialFetch = false,
spinnerProps = { size: 40 }, spinnerProps = { size: 40 },
pagination = false,
pagesCount: controlledPageCount,
initialPageIndex,
initialPageSize,
}) { }) {
const { const {
getTableProps, getTableProps,
@@ -59,18 +64,28 @@ export default function DataTable({
isAllRowsExpanded, isAllRowsExpanded,
totalColumnsWidth, totalColumnsWidth,
// page,
pageCount,
canPreviousPage,
canNextPage,
gotoPage,
previousPage,
nextPage,
setPageSize,
// Get the state from the instance // Get the state from the instance
state: { pageIndex, pageSize, sortBy, selectedRowIds }, state: { pageIndex, pageSize, sortBy, selectedRowIds },
} = useTable( } = useTable(
{ {
columns, columns,
data: data, data: data,
initialState: { pageIndex: 0, expanded }, // Pass our hoisted table state initialState: {
manualPagination: true, // Tell the usePagination pageIndex: initialPageIndex,
// hook that we'll handle our own data fetching pageSize: initialPageSize,
// This means we'll also have to provide our own expanded,
// pageCount. }, // Pass our hoisted table state
// pageCount: controlledPageCount, manualPagination: true,
pageCount: controlledPageCount,
getSubRows: (row) => row.children, getSubRows: (row) => row.children,
manualSortBy, manualSortBy,
expandSubRows, expandSubRows,
@@ -80,10 +95,10 @@ export default function DataTable({
useSortBy, useSortBy,
useExpanded, useExpanded,
useRowSelect, useRowSelect,
usePagination,
useResizeColumns, useResizeColumns,
useFlexLayout, useFlexLayout,
useSticky, useSticky,
usePagination,
(hooks) => { (hooks) => {
hooks.visibleColumns.push((columns) => [ hooks.visibleColumns.push((columns) => [
// Let's make a column for selection // Let's make a column for selection
@@ -197,6 +212,7 @@ export default function DataTable({
{...cell.getCellProps({ {...cell.getCellProps({
className: classnames(cell.column.className || '', 'td'), className: classnames(cell.column.className || '', 'td'),
})} })}
onContextMenu={handleRowContextMenu(cell, row)}
> >
{RenderCell({ cell, row, index })} {RenderCell({ cell, row, index })}
</div> </div>
@@ -337,6 +353,21 @@ export default function DataTable({
</ScrollSyncPane> </ScrollSyncPane>
</div> </div>
</ScrollSync> </ScrollSync>
<If condition={pagination && pageCount}>
<Pagination
initialPage={pageIndex + 1}
total={pageSize * pageCount}
size={pageSize}
onPageChange={(currentPage) => {
gotoPage(currentPage - 1);
}}
onPageSizeChange={(pageSize, currentPage) => {
gotoPage(currentPage - 1);
setPageSize(pageSize);
}}
/>
</If>
</div> </div>
); );
} }

View File

@@ -0,0 +1,218 @@
import React, { useReducer, useEffect } from 'react';
import classNames from 'classnames';
import { Button, ButtonGroup, Intent, HTMLSelect, } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl';
import PropTypes from 'prop-types';
import { range } from 'lodash';
import { Icon } from 'components';
const getState = ({ currentPage, size, total }) => {
const totalPages = Math.ceil(total / size);
const visibleItems = 5;
const halfVisibleItems = Math.ceil(visibleItems / 2);
// 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
startPage = 1;
endPage = totalPages;
} else {
// more than {visibleItems} total pages so calculate start and end pages
if (currentPage <= halfVisibleItems) {
startPage = 1;
endPage = visibleItems;
} else if (currentPage + (halfVisibleItems - 1) >= totalPages) {
startPage = totalPages - (visibleItems - 1);
endPage = totalPages;
} else {
startPage = currentPage - halfVisibleItems;
endPage = currentPage + halfVisibleItems - 1;
}
}
const pages = [...Array(endPage + 1 - startPage).keys()].map(
(i) => startPage + i,
);
// Too large or small currentPage
let correctCurrentpage = currentPage;
if (currentPage > totalPages) correctCurrentpage = totalPages;
if (currentPage <= 0) correctCurrentpage = 1;
return {
currentPage: correctCurrentpage,
size,
total,
pages,
totalPages,
};
};
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:
return getState({
currentPage: action.page,
size: state.size,
total: state.total,
});
case TYPE.PAGE_SIZE_CHANGE:
return getState({
currentPage: state.currentPage,
size: action.size,
total: state.total,
});
case TYPE.INITIALIZE:
return getState({
currentPage: state.currentPage,
size: action.size,
total: action.total,
});
default:
throw new Error();
}
};
const Pagination = ({
initialPage,
total,
size,
pageSizesOptions = [5, 12, 20, 30, 50, 75, 100, 150],
onPageChange,
onPageSizeChange,
}) => {
const [state, dispatch] = useReducer(
reducer,
{ currentPage: initialPage, total, size },
getState,
);
useEffect(() => {
dispatch({
type: 'INITIALIZE',
total,
size,
});
}, [total, size]);
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) => (
<Button
key={page}
intent={state.currentPage === page ? Intent.PRIMARY : Intent.NONE}
disabled={state.currentPage === page}
onClick={() => {
dispatch({ type: 'PAGE_CHANGE', page });
onPageChange(page);
}}
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
});
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>
</div>
<div class="pagination__controls">
<div class="pagination__goto-control">
Go to
<HTMLSelect
minimal={true}
options={range(1, state.totalPages + 1)}
value={state.currentPage}
onChange={(event) => {
const page = parseInt(event.currentTarget.value, 10);
dispatch({ type: 'PAGE_CHANGE', page });
onPageChange(page);
}}
minimal={true}
/>
</div>
<div class="pagination__pagesize-control">
Page size
<HTMLSelect
minimal={true}
options={pageSizesOptions}
value={size}
onChange={(event) => {
const pageSize = parseInt(event.currentTarget.value, 10);
dispatch({ type: 'PAGE_SIZE_CHANGE', size: pageSize });
dispatch({ type: 'PAGE_CHANGE', page: 1 });
onPageSizeChange(pageSize, 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,
}} />
</div>
</div>
);
};
Pagination.propTypes = {
initialPage: PropTypes.number.isRequired,
size: PropTypes.number.isRequired,
total: PropTypes.number.isRequired,
onPageChange: PropTypes.func,
onPageSizeChange: PropTypes.func,
};
Pagination.defaultProps = {
initialPage: 1,
size: 25,
};
export default Pagination;

View File

@@ -10,6 +10,7 @@ import ErrorMessage from './ErrorMessage';
import MODIFIER from './modifiers'; import MODIFIER from './modifiers';
import FieldHint from './FieldHint'; import FieldHint from './FieldHint';
import MenuItemLabel from './MenuItemLabel'; import MenuItemLabel from './MenuItemLabel';
import Pagination from './Pagination';
const Hint = FieldHint; const Hint = FieldHint;
@@ -27,5 +28,6 @@ export {
FieldHint, FieldHint,
Hint, Hint,
MenuItemLabel, MenuItemLabel,
Pagination,
// For, // For,
}; };

View File

@@ -11,7 +11,7 @@ import {
Position, Position,
Tag, Tag,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { useParams } from 'react-router-dom'; import { withRouter, useParams } from 'react-router-dom';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import moment from 'moment'; import moment from 'moment';
@@ -29,18 +29,21 @@ import withViewDetails from 'containers/Views/withViewDetails';
import withManualJournals from 'containers/Accounting/withManualJournals'; import withManualJournals from 'containers/Accounting/withManualJournals';
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions'; import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
function ManualJournalsDataTable({ function ManualJournalsDataTable({
loading, loading,
manualJournals, // #withManualJournals
manualJournalsCurrentPage,
manualJournalsLoading, manualJournalsLoading,
manualJournalsPagination,
manualJournalsTableQuery,
// #withDashboardActions // #withDashboardActions
changeCurrentView, changeCurrentView,
changePageSubtitle, changePageSubtitle,
setTopbarEditView, setTopbarEditView,
// #withViewDetails
viewId, viewId,
viewMeta, viewMeta,
@@ -96,27 +99,6 @@ function ManualJournalsDataTable({
[onDeleteJournal], [onDeleteJournal],
); );
// const actionMenuList = (journal) => (
// <Menu>
// <MenuItem text={<T id={'view_details'} />} />
// <MenuDivider />
// {!journal.status && (
// <MenuItem
// text={<T id={'publish_journal'} />}
// onClick={handlePublishJournal(journal)}
// />
// )}
// <MenuItem
// text={<T id={'edit_journal'} />}
// onClick={handleEditJournal(journal)}
// />
// <MenuItem
// text={<T id={'delete_journal'} />}
// intent={Intent.DANGER}
// onClick={handleDeleteJournal(journal)}
// />
// </Menu>
// );
const actionMenuList = useCallback( const actionMenuList = useCallback(
(journal) => ( (journal) => (
<Menu> <Menu>
@@ -171,13 +153,9 @@ function ManualJournalsDataTable({
Header: formatMessage({ id: 'status' }), Header: formatMessage({ id: 'status' }),
accessor: (r) => { accessor: (r) => {
return r.status ? ( return r.status ? (
<Tag minimal={true}> <Tag minimal={true}><T id={'published'} /></Tag>
<T id={'published'} />
</Tag>
) : ( ) : (
<Tag minimal={true} intent={Intent.WARNING}> <Tag minimal={true} intent={Intent.WARNING}><T id={'draft'} /></Tag>
<T id={'draft'} />
</Tag>
); );
}, },
disableResizing: true, disableResizing: true,
@@ -227,7 +205,7 @@ function ManualJournalsDataTable({
content={actionMenuList(cell.row.original)} content={actionMenuList(cell.row.original)}
position={Position.RIGHT_BOTTOM} position={Position.RIGHT_BOTTOM}
> >
<Button icon={<Icon icon='more-h-16' iconSize={16} />} /> <Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover> </Popover>
), ),
className: 'actions', className: 'actions',
@@ -257,26 +235,41 @@ function ManualJournalsDataTable({
<LoadingIndicator loading={loading} mount={false}> <LoadingIndicator loading={loading} mount={false}>
<DataTable <DataTable
columns={columns} columns={columns}
data={manualJournals} data={manualJournalsCurrentPage}
onFetchData={handleDataTableFetchData} onFetchData={handleDataTableFetchData}
manualSortBy={true} manualSortBy={true}
selectionColumn={true} selectionColumn={true}
noInitialFetch={true} noInitialFetch={true}
sticky={true} sticky={true}
loading={manualJournalsLoading && !initialMount} loading={manualJournalsLoading && !manualJournalsCurrentPage.length > 0}
onSelectedRowsChange={handleSelectedRowsChange} onSelectedRowsChange={handleSelectedRowsChange}
pagination={true}
pagesCount={manualJournalsPagination.pagesCount}
initialPageSize={manualJournalsTableQuery.page_size}
initialPageIndex={manualJournalsTableQuery.page - 1}
/> />
</LoadingIndicator> </LoadingIndicator>
); );
} }
export default compose( export default compose(
withRouter,
withDialogActions, withDialogActions,
withDashboardActions, withDashboardActions,
withManualJournalsActions, withManualJournalsActions,
withManualJournals(({ manualJournals, manualJournalsLoading }) => ({ withManualJournals(
manualJournals, ({
manualJournalsLoading, manualJournalsCurrentPage,
})), manualJournalsLoading,
manualJournalsPagination,
manualJournalsTableQuery,
}) => ({
manualJournalsCurrentPage,
manualJournalsLoading,
manualJournalsPagination,
manualJournalsTableQuery,
}),
),
withViewDetails, withViewDetails,
)(ManualJournalsDataTable); )(ManualJournalsDataTable);

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState, useMemo, useCallback } from 'react'; import React, { useEffect, useState, useMemo, useCallback } from 'react';
import { Route, Switch, useHistory } from 'react-router-dom'; import { Route, Switch, useHistory, withRouter } from 'react-router-dom';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import { Alert, Intent } from '@blueprintjs/core'; import { Alert, Intent } from '@blueprintjs/core';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
@@ -13,8 +13,10 @@ import ManualJournalsDataTable from 'containers/Accounting/ManualJournalsDataTab
import ManualJournalsActionsBar from 'containers/Accounting/ManualJournalActionsBar'; import ManualJournalsActionsBar from 'containers/Accounting/ManualJournalActionsBar';
import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withManualJournals from 'containers/Accounting/withManualJournals';
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions'; import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
import withViewsActions from 'containers/Views/withViewsActions'; import withViewsActions from 'containers/Views/withViewsActions';
import withRouteActions from 'containers/Router/withRouteActions';
import { compose } from 'utils'; import { compose } from 'utils';
@@ -28,12 +30,17 @@ function ManualJournalsTable({
// #withViewsActions // #withViewsActions
requestFetchResourceViews, requestFetchResourceViews,
// #withManualJournals
manualJournalsTableQuery,
// #withManualJournalsActions // #withManualJournalsActions
requestFetchManualJournalsTable, requestFetchManualJournalsTable,
requestDeleteManualJournal, requestDeleteManualJournal,
requestPublishManualJournal, requestPublishManualJournal,
requestDeleteBulkManualJournals, requestDeleteBulkManualJournals,
addManualJournalsTableQueries, addManualJournalsTableQueries,
addQuery,
}) { }) {
const history = useHistory(); const history = useHistory();
@@ -47,19 +54,21 @@ function ManualJournalsTable({
return requestFetchResourceViews('manual_journals'); return requestFetchResourceViews('manual_journals');
}); });
const fetchManualJournals = useQuery('manual-journals-table', const fetchManualJournals = useQuery(
() => requestFetchManualJournalsTable()); ['manual-journals-table', manualJournalsTableQuery],
(key, q) => requestFetchManualJournalsTable(q),
);
useEffect(() => { useEffect(() => {
changePageTitle(formatMessage({id:'manual_journals'})); changePageTitle(formatMessage({ id: 'manual_journals' }));
}, [changePageTitle,formatMessage]); }, [changePageTitle, formatMessage]);
// Handle delete manual journal click. // Handle delete manual journal click.
const handleDeleteJournal = useCallback( const handleDeleteJournal = useCallback(
(journal) => { (journal) => {
setDeleteManualJournal(journal); setDeleteManualJournal(journal);
}, },
[setDeleteManualJournal] [setDeleteManualJournal],
); );
// Handle cancel delete manual journal. // Handle cancel delete manual journal.
@@ -71,28 +80,26 @@ function ManualJournalsTable({
const handleConfirmManualJournalDelete = useCallback(() => { const handleConfirmManualJournalDelete = useCallback(() => {
requestDeleteManualJournal(deleteManualJournal.id).then(() => { requestDeleteManualJournal(deleteManualJournal.id).then(() => {
AppToaster.show({ AppToaster.show({
message: formatMessage({ message: formatMessage(
id: 'the_journal_has_been_successfully_deleted', { id: 'the_journal_has_been_successfully_deleted' },
}, { { number: deleteManualJournal.journal_number },
number: deleteManualJournal.journal_number, ),
}),
intent: Intent.SUCCESS, intent: Intent.SUCCESS,
}); });
setDeleteManualJournal(false); setDeleteManualJournal(false);
}); });
}, [deleteManualJournal, requestDeleteManualJournal,formatMessage]); }, [deleteManualJournal, requestDeleteManualJournal, formatMessage]);
// Calculates the selected rows count. // Calculates the selected rows count.
const selectedRowsCount = useMemo( const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [
() => Object.values(selectedRows).length, selectedRows,
[selectedRows]); ]);
const handleBulkDelete = useCallback( const handleBulkDelete = useCallback(
(accountsIds) => { (accountsIds) => {
setBulkDelete(accountsIds); setBulkDelete(accountsIds);
}, },
[setBulkDelete] [setBulkDelete],
); );
// Handle confirm journals bulk delete. // Handle confirm journals bulk delete.
@@ -100,11 +107,10 @@ function ManualJournalsTable({
requestDeleteBulkManualJournals(bulkDelete) requestDeleteBulkManualJournals(bulkDelete)
.then(() => { .then(() => {
AppToaster.show({ AppToaster.show({
message: formatMessage({ message: formatMessage(
id: 'the_journals_has_been_successfully_deleted', { id: 'the_journals_has_been_successfully_deleted', },
}, { { count: selectedRowsCount, },
count: selectedRowsCount, ),
}),
intent: Intent.SUCCESS, intent: Intent.SUCCESS,
}); });
setBulkDelete(false); setBulkDelete(false);
@@ -112,7 +118,12 @@ function ManualJournalsTable({
.catch((error) => { .catch((error) => {
setBulkDelete(false); setBulkDelete(false);
}); });
}, [requestDeleteBulkManualJournals, bulkDelete,formatMessage,selectedRowsCount]); }, [
requestDeleteBulkManualJournals,
bulkDelete,
formatMessage,
selectedRowsCount,
]);
// Handle cancel bulk delete alert. // Handle cancel bulk delete alert.
const handleCancelBulkDelete = useCallback(() => { const handleCancelBulkDelete = useCallback(() => {
@@ -123,13 +134,11 @@ function ManualJournalsTable({
(journal) => { (journal) => {
history.push(`/manual-journals/${journal.id}/edit`); history.push(`/manual-journals/${journal.id}/edit`);
}, },
[history] [history],
); );
// Handle filter change to re-fetch data-table. // Handle filter change to re-fetch data-table.
const handleFilterChanged = useCallback(() => { const handleFilterChanged = useCallback(() => {}, [fetchManualJournals]);
}, [fetchManualJournals]);
// Handle view change to re-fetch data table. // Handle view change to re-fetch data table.
// const handleViewChanged = useCallback(() => { // const handleViewChanged = useCallback(() => {
@@ -137,24 +146,37 @@ function ManualJournalsTable({
// }, [fetchManualJournals]); // }, [fetchManualJournals]);
// Handle fetch data of manual jouranls datatable. // Handle fetch data of manual jouranls datatable.
const handleFetchData = useCallback(({ pageIndex, pageSize, sortBy }) => { const handleFetchData = useCallback(
addManualJournalsTableQueries({ ({ pageIndex, pageSize, sortBy }) => {
...(sortBy.length > 0) ? { const page = pageIndex + 1;
column_sort_by: sortBy[0].id, // addQuery('page_size', pageSize);
sort_order: sortBy[0].desc ? 'desc' : 'asc', // addQuery('page', page);
} : {},
}); addManualJournalsTableQueries({
}, [addManualJournalsTableQueries]); ...(sortBy.length > 0
? {
column_sort_by: sortBy[0].id,
sort_order: sortBy[0].desc ? 'desc' : 'asc',
}
: {}),
page_size: pageSize,
page,
});
},
[addManualJournalsTableQueries],
);
const handlePublishJournal = useCallback( const handlePublishJournal = useCallback(
(journal) => { (journal) => {
requestPublishManualJournal(journal.id).then(() => { requestPublishManualJournal(journal.id).then(() => {
AppToaster.show({ AppToaster.show({
message: formatMessage({id:'the_manual_journal_id_has_been_published'}) message: formatMessage({
id: 'the_manual_journal_id_has_been_published',
}),
}); });
}); });
}, },
[requestPublishManualJournal,formatMessage] [requestPublishManualJournal, formatMessage],
); );
// Handle selected rows change. // Handle selected rows change.
@@ -162,12 +184,12 @@ function ManualJournalsTable({
(accounts) => { (accounts) => {
setSelectedRows(accounts); setSelectedRows(accounts);
}, },
[setSelectedRows] [setSelectedRows],
); );
return ( return (
<DashboardInsider <DashboardInsider
loading={fetchViews.isFetching || fetchManualJournals.isFetching} loading={fetchViews.isFetching}
name={'manual-journals'} name={'manual-journals'}
> >
<ManualJournalsActionsBar <ManualJournalsActionsBar
@@ -183,7 +205,8 @@ function ManualJournalsTable({
path={[ path={[
'/manual-journals/:custom_view_id/custom_view', '/manual-journals/:custom_view_id/custom_view',
'/manual-journals', '/manual-journals',
]}> ]}
>
<ManualJournalsViewTabs /> <ManualJournalsViewTabs />
<ManualJournalsDataTable <ManualJournalsDataTable
@@ -199,25 +222,37 @@ function ManualJournalsTable({
<Alert <Alert
cancelButtonText={<T id={'cancel'} />} cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />} confirmButtonText={<T id={'delete'} />}
icon='trash' icon="trash"
intent={Intent.DANGER} intent={Intent.DANGER}
isOpen={deleteManualJournal} isOpen={deleteManualJournal}
onCancel={handleCancelManualJournalDelete} onCancel={handleCancelManualJournalDelete}
onConfirm={handleConfirmManualJournalDelete} onConfirm={handleConfirmManualJournalDelete}
> >
<p><T id={'once_delete_this_journal_category_you_will_able_to_restore_it'} /></p> <p>
<T
id={
'once_delete_this_journal_category_you_will_able_to_restore_it'
}
/>
</p>
</Alert> </Alert>
<Alert <Alert
cancelButtonText={<T id={'cancel'} />} cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete_count'} values={{ count: selectedRowsCount }} />} confirmButtonText={
icon='trash' <T id={'delete_count'} values={{ count: selectedRowsCount }} />
}
icon="trash"
intent={Intent.DANGER} intent={Intent.DANGER}
isOpen={bulkDelete} isOpen={bulkDelete}
onCancel={handleCancelBulkDelete} onCancel={handleCancelBulkDelete}
onConfirm={handleConfirmBulkDelete} onConfirm={handleConfirmBulkDelete}
> >
<p><T id={'once_delete_these_journalss_you_will_not_able_restore_them'} /></p> <p>
<T
id={'once_delete_these_journalss_you_will_not_able_restore_them'}
/>
</p>
</Alert> </Alert>
</DashboardPageContent> </DashboardPageContent>
</DashboardInsider> </DashboardInsider>
@@ -225,7 +260,12 @@ function ManualJournalsTable({
} }
export default compose( export default compose(
withRouter,
withRouteActions,
withDashboardActions, withDashboardActions,
withManualJournalsActions, withManualJournalsActions,
withViewsActions withViewsActions,
withManualJournals(({ manualJournalsTableQuery }) => ({
manualJournalsTableQuery,
})),
)(ManualJournalsTable); )(ManualJournalsTable);

View File

@@ -1,16 +1,37 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { pick, mapValues } from 'lodash';
import { getResourceViews } from 'store/customViews/customViews.selectors'; import { getResourceViews } from 'store/customViews/customViews.selectors';
import { import { getManualJournalsItems } from 'store/manualJournals/manualJournals.selectors';
getManualJournalsItems,
} from 'store/manualJournals/manualJournals.selectors' const queryParamsKeys = ['page_size', 'page'];
export default (mapState) => { export default (mapState) => {
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
const queryParams = props.location
? new URLSearchParams(props.location.search)
: null;
const manualJournalsTableQuery = {
...state.manualJournals.tableQuery,
...(queryParams
? mapValues(
pick(Object.fromEntries(queryParams), queryParamsKeys),
(v) => parseInt(v, 10),
)
: {}),
};
const mapped = { const mapped = {
manualJournals: getManualJournalsItems(state, state.manualJournals.currentViewId), manualJournalsCurrentPage: getManualJournalsItems(
state,
state.manualJournals.currentViewId,
manualJournalsTableQuery.page,
),
manualJournalsTableQuery,
manualJournalsViews: getResourceViews(state, 'manual_journals'), manualJournalsViews: getResourceViews(state, 'manual_journals'),
manualJournalsItems: state.manualJournals.items, manualJournalsItems: state.manualJournals.items,
manualJournalsTableQuery: state.manualJournals.tableQuery,
manualJournalsPagination: state.manualJournals.paginationMeta,
manualJournalsLoading: state.manualJournals.loading, manualJournalsLoading: state.manualJournals.loading,
}; };
return mapState ? mapState(mapped, state, props) : mapped; return mapState ? mapState(mapped, state, props) : mapped;

View File

@@ -10,11 +10,10 @@ import {
const mapActionsToProps = (dispatch) => ({ const mapActionsToProps = (dispatch) => ({
requestDeleteManualJournal: (id) => dispatch(deleteManualJournal({ id })), requestDeleteManualJournal: (id) => dispatch(deleteManualJournal({ id })),
requestFetchManualJournalsTable: (query = {}) => dispatch(fetchManualJournalsTable({ query: { ...query } })), requestFetchManualJournalsTable: (query = {}) => dispatch(fetchManualJournalsTable({ query })),
requestFetchManualJournal: (id) => dispatch(fetchManualJournal({ id })), requestFetchManualJournal: (id) => dispatch(fetchManualJournal({ id })),
requestPublishManualJournal: (id) => dispatch(publishManualJournal({ id })), requestPublishManualJournal: (id) => dispatch(publishManualJournal({ id })),
requestDeleteBulkManualJournals: (ids) => dispatch(deleteBulkManualJournals({ ids })), requestDeleteBulkManualJournals: (ids) => dispatch(deleteBulkManualJournals({ ids })),
changeCurrentView: (id) => dispatch({ changeCurrentView: (id) => dispatch({
type: t.MANUAL_JOURNALS_SET_CURRENT_VIEW, type: t.MANUAL_JOURNALS_SET_CURRENT_VIEW,
currentViewId: parseInt(id, 10), currentViewId: parseInt(id, 10),

View File

@@ -0,0 +1,31 @@
import { connect } from 'react-redux';
const mapDispatchToProps = (dispatch, props) => {
return {
addQuery: (key, value) => {
let pathname = props.location.pathname;
let searchParams = new URLSearchParams(props.location.search);
searchParams.set(key, value);
props.history.push({
pathname: pathname,
search: searchParams.toString(),
});
},
removeQuery: (key) => {
let pathname = props.location.pathname;
let searchParams = new URLSearchParams(props.location.search);
// returns the existing query string: '?type=fiction&author=fahid'
searchParams.delete(key);
props.history.push({
pathname: pathname,
search: searchParams.toString(),
});
},
}
}
export default connect(null, mapDispatchToProps)

View File

@@ -468,4 +468,7 @@ export default {
remove_the_line: 'Remove the line', remove_the_line: 'Remove the line',
no_results: 'No results', no_results: 'No results',
all_reports: 'All Reports', all_reports: 'All Reports',
next: 'Next',
previous: 'Previous',
showing_current_page_to_total: 'Showing {currentPage} to {totalPages} of {total} entries',
}; };

View File

@@ -41,6 +41,18 @@ export default {
], ],
viewBox: '0 0 448 512', viewBox: '0 0 448 512',
}, },
'arrow-back-24': {
path: [
'M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z',
],
viewBox: '0 0 24, 24',
},
'arrow-forward-24': {
path: [
'M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8-8-8z',
],
viewBox: '0 0 24 24',
},
'ellipsis-h': { 'ellipsis-h': {
path: [ path: [
'M304 256c0 26.5-21.5 48-48 48s-48-21.5-48-48 21.5-48 48-48 48 21.5 48 48zm120-48c-26.5 0-48 21.5-48 48s21.5 48 48 48 48-21.5 48-48-21.5-48-48-48zm-336 0c-26.5 0-48 21.5-48 48s21.5 48 48 48 48-21.5 48-48-21.5-48-48-48z', 'M304 256c0 26.5-21.5 48-48 48s-48-21.5-48-48 21.5-48 48-48 48 21.5 48 48zm120-48c-26.5 0-48 21.5-48 48s21.5 48 48 48 48-21.5 48-48-21.5-48-48-48zm-336 0c-26.5 0-48 21.5-48 48s21.5 48 48 48 48-21.5 48-48-21.5-48-48-48z',

View File

@@ -7,7 +7,10 @@ const initialState = {
views: {}, views: {},
accountsTypes: [], accountsTypes: [],
accountsById: {}, accountsById: {},
tableQuery: {}, tableQuery: {
pageSize: 2,
page: 1,
},
currentViewId: -1, currentViewId: -1,
selectedRows: [], selectedRows: [],
loading: false, loading: false,
@@ -91,8 +94,3 @@ export default createTableQueryReducers('accounts', accountsReducer);
export const getAccountById = (state, id) => { export const getAccountById = (state, id) => {
return state.accounts.accountsById[id]; return state.accounts.accountsById[id];
}; };
// export default {
// // ...accountsReducer,
// // testReducer,
// }

View File

@@ -2,44 +2,55 @@ import ApiService from 'services/ApiService';
import t from 'store/types'; import t from 'store/types';
export const makeJournalEntries = ({ form }) => { export const makeJournalEntries = ({ form }) => {
return (dispatch) => new Promise((resolve, reject) => { return (dispatch) =>
ApiService.post('accounting/make-journal-entries', form).then((response) => { new Promise((resolve, reject) => {
resolve(response); ApiService.post('accounting/make-journal-entries', form)
}).catch((error) => { .then((response) => {
const { response } = error; resolve(response);
const { data } = response; })
.catch((error) => {
const { response } = error;
const { data } = response;
reject(data?.errors); reject(data?.errors);
});
}); });
});
}; };
export const fetchManualJournal = ({ id }) => { export const fetchManualJournal = ({ id }) => {
return (dispatch) => new Promise((resolve, reject) => { return (dispatch) =>
ApiService.get(`accounting/manual-journals/${id}`).then((response) => { new Promise((resolve, reject) => {
dispatch({ ApiService.get(`accounting/manual-journals/${id}`)
type: t.MANUAL_JOURNAL_SET, .then((response) => {
payload: { dispatch({
id, type: t.MANUAL_JOURNAL_SET,
manualJournal: response.data.manual_journal, payload: {
}, id,
}); manualJournal: response.data.manual_journal,
resolve(response); },
}).catch((error) => { reject(error); }); });
}); resolve(response);
})
.catch((error) => {
reject(error);
});
});
}; };
export const editManualJournal = ({ form, id }) => { export const editManualJournal = ({ form, id }) => {
return (dispatch) => new Promise((resolve, reject) => { return (dispatch) =>
ApiService.post(`accounting/manual-journals/${id}`, form).then((response) => { new Promise((resolve, reject) => {
resolve(response); ApiService.post(`accounting/manual-journals/${id}`, form)
}).catch((error) => { .then((response) => {
const { response } = error; resolve(response);
const { data } = response; })
.catch((error) => {
const { response } = error;
const { data } = response;
reject(data?.errors); reject(data?.errors);
});
}); });
});
}; };
export const deleteManualJournal = ({ id }) => { export const deleteManualJournal = ({ id }) => {
@@ -53,24 +64,27 @@ export const deleteManualJournal = ({ id }) => {
}); });
resolve(response); resolve(response);
}) })
.catch((error) => { reject(error); }); .catch((error) => {
reject(error);
});
}); });
}; };
export const deleteBulkManualJournals = ({ ids }) => { export const deleteBulkManualJournals = ({ ids }) => {
return (dispatch) => new Promise((resolve, reject) => { return (dispatch) =>
ApiService.delete('accounting/manual-journals', { params: { ids } }) new Promise((resolve, reject) => {
.then((response) => { ApiService.delete('accounting/manual-journals', { params: { ids } })
dispatch({ .then((response) => {
type: t.MANUAL_JOURNALS_BULK_DELETE, dispatch({
payload: { ids }, type: t.MANUAL_JOURNALS_BULK_DELETE,
payload: { ids },
});
resolve(response);
})
.catch((error) => {
reject(error.response.data.errors || []);
}); });
resolve(response); });
}).catch((error) => {
reject(error.response.data.errors || []);
});
});
}; };
export const publishManualJournal = ({ id }) => { export const publishManualJournal = ({ id }) => {
@@ -84,40 +98,44 @@ export const publishManualJournal = ({ id }) => {
}); });
resolve(response); resolve(response);
}) })
.catch((error) => { reject(error); }); .catch((error) => {
reject(error);
});
}); });
} };
export const fetchManualJournalsTable = ({ query } = {}) => { export const fetchManualJournalsTable = ({ query } = {}) => {
return (dispatch, getState) => return (dispatch, getState) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
const pageQuery = getState().manualJournals.tableQuery;
dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING,
});
dispatch({ dispatch({
type: t.MANUAL_JOURNALS_TABLE_LOADING, type: t.MANUAL_JOURNALS_TABLE_LOADING,
loading: true, loading: true,
}); });
ApiService.get('accounting/manual-journals', { ApiService.get('accounting/manual-journals', {
params: { ...pageQuery, ...query }, params: { ...query },
}) })
.then((response) => { .then((response) => {
dispatch({ dispatch({
type: t.MANUAL_JOURNALS_PAGE_SET, type: t.MANUAL_JOURNALS_PAGE_SET,
manual_journals: response.data.manualJournals.results, payload: {
customViewId: response.data.customViewId || -1, manualJournals: response.data.manualJournals.results,
customViewId: response.data.customViewId || -1,
pagination: response.data.manualJournals.pagination,
}
}); });
dispatch({ dispatch({
type: t.MANUAL_JOURNALS_ITEMS_SET, type: t.MANUAL_JOURNALS_ITEMS_SET,
manual_journals: response.data.manualJournals.results, manual_journals: response.data.manualJournals.results,
}); });
dispatch({ dispatch({
type: t.MANUAL_JOURNALS_TABLE_LOADING, type: 'MANUAL_JOURNALS_PAGINATION_SET',
loading: false, payload: {
pagination: response.data.manualJournals.pagination,
},
}); });
dispatch({ dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED, type: t.MANUAL_JOURNALS_TABLE_LOADING,
loading: false,
}); });
resolve(response); resolve(response);
}) })

View File

@@ -8,7 +8,13 @@ const initialState = {
views: {}, views: {},
loading: false, loading: false,
currentViewId: -1, currentViewId: -1,
tableQuery: {}, tableQuery: {
page_size: 6,
page: 1,
},
paginationMeta: {
total: 0,
}
}; };
const defaultJournal = { const defaultJournal = {
@@ -45,12 +51,19 @@ const reducer = createReducer(initialState, {
}, },
[t.MANUAL_JOURNALS_PAGE_SET]: (state, action) => { [t.MANUAL_JOURNALS_PAGE_SET]: (state, action) => {
const viewId = action.customViewId || -1; const { customViewId, manualJournals, pagination } = action.payload;
const viewId = customViewId || -1;
const view = state.views[viewId] || {}; const view = state.views[viewId] || {};
state.views[viewId] = { state.views[viewId] = {
...view, ...view,
ids: action.manual_journals.map((i) => i.id), pages: {
...(state.views?.[viewId]?.pages || {}),
[pagination.page]: {
ids: (manualJournals.map((i) => i.id)),
},
}
}; };
}, },
@@ -78,6 +91,22 @@ const reducer = createReducer(initialState, {
}); });
state.items = items; state.items = items;
}, },
[t.MANUAL_JOURNALS_PAGINATION_SET]: (state, action) => {
const { pagination } = action.payload;
const mapped = {
pageSize: parseInt(pagination.pageSize, 10),
page: parseInt(pagination.page, 10),
total: parseInt(pagination.total, 10),
};
state.paginationMeta = {
...state.paginationMeta,
...mapped,
pagesCount: Math.ceil(mapped.total / mapped.pageSize),
pageIndex: Math.max(mapped.page - 1, 0),
};
}
}); });
export default createTableQueryReducers('manual_journals', reducer); export default createTableQueryReducers('manual_journals', reducer);

View File

@@ -1,10 +1,11 @@
import { pickItemsFromIds } from 'store/selectors'; import { pickItemsFromIds } from 'store/selectors';
export const getManualJournalsItems = (state, viewId) => { export const getManualJournalsItems = (state, viewId, pageNumber) => {
const accountsView = state.manualJournals.views[viewId || -1]; const accountsViewPages = state.manualJournals.views[viewId || -1];
const accountsView = accountsViewPages?.pages?.[pageNumber]?.ids || {};
const accountsItems = state.manualJournals.items; const accountsItems = state.manualJournals.items;
return typeof accountsView === 'object' return typeof accountsView === 'object'
? pickItemsFromIds(accountsItems, accountsView.ids) || [] ? pickItemsFromIds(accountsItems, accountsView) || []
: []; : [];
}; };

View File

@@ -11,4 +11,5 @@ export default {
MANUAL_JOURNAL_PUBLISH: 'MANUAL_JOURNAL_PUBLISH', MANUAL_JOURNAL_PUBLISH: 'MANUAL_JOURNAL_PUBLISH',
MANUAL_JOURNALS_BULK_DELETE: 'MANUAL_JOURNALS_BULK_DELETE', MANUAL_JOURNALS_BULK_DELETE: 'MANUAL_JOURNALS_BULK_DELETE',
MANUAL_JOURNALS_PAGINATION_SET: 'MANUAL_JOURNALS_PAGINATION_SET',
}; };

View File

@@ -38,6 +38,7 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
@import 'components/dialog'; @import 'components/dialog';
@import 'components/custom-scrollbar'; @import 'components/custom-scrollbar';
@import 'components/dragzone'; @import 'components/dragzone';
@import 'components/pagination';
// Pages // Pages
@import 'pages/dashboard'; @import 'pages/dashboard';

View File

@@ -0,0 +1,101 @@
.pagination{
display: flex;
padding: 25px 14px;
font-size: 13px;
.bp3-button{
background: transparent;
padding: 4px 4px;
}
&__item{
min-width: 24px;
min-height: 24px;
&:not([class*="bp3-intent-"]){
color: #666666;
border-radius: 3px;
&:hover{
background-color: #E6EFFB;
}
}
.bp3-icon{
margin-right: 4px;
color: #666666;
}
.pagination .pagination__buttons-group .bp3-button-group &{
border-radius: 3px;
}
&--next,
&--previous{
&.bp3-button{
padding-left: 6px;
padding-right: 6px;
}
}
&.is-active{
&.bp3-intent-primary:disabled,
&.bp3-intent-primary.bp3-disabled{
background-color: #E6EFFB;
}
&:not([class*="bp3-intent-"]) {
}
.bp3-button-text{
color: #000;
}
}
&--next{
.bp3-icon{
order: 1;
margin-right: 0;
margin-left: 4px;
}
}
}
&__info{
padding-top: 6px;
color: #999;
margin-left: 12px;
}
&__controls{
display: flex;
margin-left: auto;
.bp3-html-select{
margin-left: 6px;
select{
height: 20px;
width: auto;
padding: 0;
padding-right: 0px;
border: 1px solid #e8e8e8;
font-size: 13px;
border-radius: 3px;
color: #666;
padding-right: 8px;
}
}
}
&__goto-control{
display: none;
}
&__pagesize-control{
margin-left: 12px;
padding-top: 4px;
color: #777;
}
}

View File

@@ -72,6 +72,8 @@ export default {
*/ */
manualJournals: { manualJournals: {
validation: [ validation: [
query('page').optional().isNumeric().toInt(),
query('page_size').optional().isNumeric().toInt(),
query('custom_view_id').optional().isNumeric().toInt(), query('custom_view_id').optional().isNumeric().toInt(),
query('stringified_filter_roles').optional().isJSON(), query('stringified_filter_roles').optional().isJSON(),
], ],