mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 05:10:31 +00:00
refactoring: expenses landing list.
refactoring: customers landing list. refactoring: vendors landing list. refactoring: manual journals landing list.
This commit is contained in:
@@ -13,18 +13,14 @@ import {
|
||||
import classNames from 'classnames';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { useManualJournalsContext } from 'containers/Accounting/ManualJournalsListProvider';
|
||||
import { useManualJournalsContext } from './ManualJournalsListProvider';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
|
||||
import { If, DashboardActionViewsList } from 'components';
|
||||
|
||||
import withResourceDetail from 'containers/Resources/withResourceDetails';
|
||||
import withManualJournals from 'containers/Accounting/withManualJournals';
|
||||
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
|
||||
|
||||
import withManualJournalsActions from './withManualJournalsActions';
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
@@ -32,9 +28,12 @@ import { compose } from 'utils';
|
||||
*/
|
||||
function ManualJournalActionsBar({
|
||||
// #withManualJournalsActions
|
||||
addManualJournalsTableQueries,
|
||||
setManualJournalsTableState,
|
||||
}) {
|
||||
// History context.
|
||||
const history = useHistory();
|
||||
|
||||
// Manual journals context.
|
||||
const { journalsViews } = useManualJournalsContext();
|
||||
|
||||
// Handle click a new manual journal.
|
||||
@@ -44,13 +43,13 @@ function ManualJournalActionsBar({
|
||||
|
||||
// Handle delete button click.
|
||||
const handleBulkDelete = () => {
|
||||
|
||||
|
||||
};
|
||||
|
||||
// Handle tab change.
|
||||
const handleTabChange = (viewId) => {
|
||||
addManualJournalsTableQueries({
|
||||
custom_view_id: viewId.id || null,
|
||||
setManualJournalsTableState({
|
||||
customViewid: viewId.id || null,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -115,20 +114,7 @@ function ManualJournalActionsBar({
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
resourceName: 'manual_journals',
|
||||
});
|
||||
|
||||
const withManualJournalsActionsBar = connect(mapStateToProps);
|
||||
|
||||
export default compose(
|
||||
withManualJournalsActionsBar,
|
||||
withDialogActions,
|
||||
withResourceDetail(({ resourceFields }) => ({
|
||||
resourceFields,
|
||||
})),
|
||||
withManualJournals(({ manualJournalsViews }) => ({
|
||||
manualJournalsViews,
|
||||
})),
|
||||
withManualJournalsActions,
|
||||
)(ManualJournalActionsBar);
|
||||
@@ -0,0 +1,118 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { DataTable, Choose } from 'components';
|
||||
import { CLASSES } from 'common/classes';
|
||||
|
||||
import ManualJournalsEmptyStatus from './ManualJournalsEmptyStatus';
|
||||
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
|
||||
|
||||
import withManualJournalsActions from './withManualJournalsActions';
|
||||
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||
|
||||
import { useManualJournalsContext } from './ManualJournalsListProvider';
|
||||
import { useManualJournalsColumns } from './utils';
|
||||
import { ActionsMenu } from './components';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Manual journals data-table.
|
||||
*/
|
||||
function ManualJournalsDataTable({
|
||||
// #withManualJournalsActions
|
||||
setManualJournalsTableState,
|
||||
|
||||
// #withAlertsActions
|
||||
openAlert,
|
||||
|
||||
// #ownProps
|
||||
onSelectedRowsChange,
|
||||
}) {
|
||||
// Manual journals context.
|
||||
const {
|
||||
manualJournals,
|
||||
pagination,
|
||||
isManualJournalsLoading,
|
||||
isManualJournalsFetching,
|
||||
isEmptyStatus
|
||||
} = useManualJournalsContext();
|
||||
|
||||
// Manual journals columns.
|
||||
const columns = useManualJournalsColumns();
|
||||
|
||||
// Handles the journal publish action.
|
||||
const handlePublishJournal = ({ id }) => {
|
||||
openAlert('journal-publish', { manualJournalId: id })
|
||||
};
|
||||
|
||||
// Handle the journal edit action.
|
||||
const handleEditJournal = ({ id }) => {
|
||||
|
||||
};
|
||||
|
||||
// Handle the journal delete action.
|
||||
const handleDeleteJournal = ({ id }) => {
|
||||
openAlert('journal-delete', { manualJournalId: id });
|
||||
};
|
||||
|
||||
// Handle fetch data once the page index, size or sort by of the table change.
|
||||
const handleFetchData = React.useCallback(
|
||||
({ pageSize, pageIndex, sortBy }) => {
|
||||
setManualJournalsTableState({
|
||||
pageIndex,
|
||||
pageSize,
|
||||
sortBy,
|
||||
});
|
||||
},
|
||||
[setManualJournalsTableState],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
|
||||
<Choose>
|
||||
<Choose.When condition={isEmptyStatus}>
|
||||
<ManualJournalsEmptyStatus />
|
||||
</Choose.When>
|
||||
|
||||
<Choose.Otherwise>
|
||||
<DataTable
|
||||
noInitialFetch={true}
|
||||
columns={columns}
|
||||
data={manualJournals}
|
||||
|
||||
manualSortBy={true}
|
||||
selectionColumn={true}
|
||||
expandable={true}
|
||||
sticky={true}
|
||||
|
||||
loading={isManualJournalsLoading}
|
||||
headerLoading={isManualJournalsLoading}
|
||||
progressBarLoading={isManualJournalsFetching}
|
||||
|
||||
pagesCount={pagination.pagesCount}
|
||||
pagination={true}
|
||||
|
||||
autoResetSortBy={false}
|
||||
autoResetPage={false}
|
||||
|
||||
TableLoadingRenderer={TableSkeletonRows}
|
||||
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||
ContextMenu={ActionsMenu}
|
||||
|
||||
onFetchData={handleFetchData}
|
||||
payload={{
|
||||
onDelete: handleDeleteJournal,
|
||||
onPublish: handlePublishJournal
|
||||
}}
|
||||
/>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withManualJournalsActions,
|
||||
withAlertsActions
|
||||
)(ManualJournalsDataTable);
|
||||
@@ -0,0 +1,56 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
|
||||
import { ManualJournalsListProvider } from './ManualJournalsListProvider';
|
||||
import ManualJournalsAlerts from './ManualJournalsAlerts';
|
||||
import ManualJournalsViewTabs from './ManualJournalsViewTabs';
|
||||
import ManualJournalsDataTable from './ManualJournalsDataTable';
|
||||
import ManualJournalsActionsBar from './ManualJournalActionsBar';
|
||||
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withManualJournals from './withManualJournals';
|
||||
|
||||
import { transformTableStateToQuery, compose } from 'utils';
|
||||
|
||||
import 'style/pages/ManualJournal/List.scss';
|
||||
|
||||
/**
|
||||
* Manual journals table.
|
||||
*/
|
||||
function ManualJournalsTable({
|
||||
// #withDashboardActions
|
||||
changePageTitle,
|
||||
|
||||
// #withManualJournals
|
||||
journalsTableState,
|
||||
}) {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
// Handle update the page title.
|
||||
useEffect(() => {
|
||||
changePageTitle(formatMessage({ id: 'manual_journals' }));
|
||||
}, [changePageTitle, formatMessage]);
|
||||
|
||||
return (
|
||||
<ManualJournalsListProvider
|
||||
query={transformTableStateToQuery(journalsTableState)}
|
||||
>
|
||||
<ManualJournalsActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<ManualJournalsViewTabs />
|
||||
<ManualJournalsDataTable />
|
||||
<ManualJournalsAlerts />
|
||||
</DashboardPageContent>
|
||||
</ManualJournalsListProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDashboardActions,
|
||||
withManualJournals(({ manualJournalsTableState }) => ({
|
||||
journalsTableState: manualJournalsTableState,
|
||||
})),
|
||||
)(ManualJournalsTable);
|
||||
@@ -1,28 +1,39 @@
|
||||
import React, { createContext } from 'react';
|
||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||
import { useResourceViews, useJournals } from 'hooks/query';
|
||||
import { isTableEmptyStatus } from 'utils';
|
||||
|
||||
const ManualJournalsContext = createContext();
|
||||
|
||||
function ManualJournalsListProvider({ query, ...props }) {
|
||||
// Fetches accounts resource views and fields.
|
||||
const { data: journalsViews, isFetching: isViewsLoading } = useResourceViews(
|
||||
const { data: journalsViews, isLoading: isViewsLoading } = useResourceViews(
|
||||
'manual_journals',
|
||||
);
|
||||
|
||||
// Fetches the manual journals transactions with pagination meta.
|
||||
const {
|
||||
data: { manualJournals },
|
||||
isFetching: isManualJournalsLoading,
|
||||
} = useJournals(query);
|
||||
data: { manualJournals, pagination, filterMeta },
|
||||
isLoading: isManualJournalsLoading,
|
||||
isFetching: isManualJournalsFetching
|
||||
} = useJournals(query, { keepPreviousData: true });
|
||||
|
||||
// Detarmines the datatable empty status.
|
||||
const isEmptyStatus = isTableEmptyStatus({
|
||||
data: manualJournals, pagination, filterMeta,
|
||||
}) && !isManualJournalsFetching;
|
||||
|
||||
// Global state.
|
||||
const state = {
|
||||
manualJournals,
|
||||
pagination,
|
||||
journalsViews,
|
||||
|
||||
isManualJournalsLoading,
|
||||
isManualJournalsFetching,
|
||||
isViewsLoading,
|
||||
|
||||
isEmptyStatus
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -8,17 +8,21 @@ import { DashboardViewsTabs } from 'components';
|
||||
import { useManualJournalsContext } from './ManualJournalsListProvider';
|
||||
import withManualJournalsActions from './withManualJournalsActions';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withManualJournals from './withManualJournals';
|
||||
|
||||
import { compose, saveInvoke } from 'utils';
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Manual journal views tabs.
|
||||
*/
|
||||
function ManualJournalsViewTabs({
|
||||
// #withManualJournalsActions
|
||||
addManualJournalsTableQueries,
|
||||
setManualJournalsTableState,
|
||||
|
||||
// #withManualJournals
|
||||
journalsTableState
|
||||
}) {
|
||||
const { custom_view_id: customViewId } = useParams();
|
||||
// Manual journals context.
|
||||
const { journalsViews } = useManualJournalsContext();
|
||||
|
||||
const tabs = journalsViews.map((view) => ({
|
||||
@@ -27,9 +31,10 @@ function ManualJournalsViewTabs({
|
||||
|
||||
const handleClickNewView = () => {};
|
||||
|
||||
// Handles the tab change.
|
||||
const handleTabChange = (viewId) => {
|
||||
addManualJournalsTableQueries({
|
||||
custom_view_id: viewId || null,
|
||||
setManualJournalsTableState({
|
||||
customViewId: viewId || null,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -38,7 +43,7 @@ function ManualJournalsViewTabs({
|
||||
<NavbarGroup align={Alignment.LEFT}>
|
||||
<DashboardViewsTabs
|
||||
resourceName={'manual-journals'}
|
||||
initialViewId={customViewId}
|
||||
customViewId={journalsTableState.customViewId}
|
||||
tabs={tabs}
|
||||
onChange={handleTabChange}
|
||||
onNewViewTabClick={handleClickNewView}
|
||||
@@ -51,4 +56,7 @@ function ManualJournalsViewTabs({
|
||||
export default compose(
|
||||
withManualJournalsActions,
|
||||
withDashboardActions,
|
||||
withManualJournals(({ manualJournalsTableState }) => ({
|
||||
journalsTableState: manualJournalsTableState,
|
||||
})),
|
||||
)(ManualJournalsViewTabs);
|
||||
@@ -6,16 +6,20 @@ import {
|
||||
Position,
|
||||
Tag,
|
||||
Button,
|
||||
MenuItem,
|
||||
Menu,
|
||||
MenuDivider,
|
||||
Popover,
|
||||
} from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import moment from 'moment';
|
||||
import { Choose, Money, If, Icon, Hint } from 'components';
|
||||
import { Choose, Money, If, Icon } from 'components';
|
||||
import { safeCallback } from 'utils';
|
||||
import { formatMessage } from 'services/intl';
|
||||
|
||||
import withAccountDetails from 'containers/Accounts/withAccountDetail';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
// Amount accessor.
|
||||
/**
|
||||
* Amount accessor.
|
||||
*/
|
||||
export const AmountAccessor = (r) => (
|
||||
<Tooltip
|
||||
content={<AmountPopoverContent journalEntries={r.entries} />}
|
||||
@@ -26,15 +30,13 @@ export const AmountAccessor = (r) => (
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const AmountPopoverContentLineRender = ({
|
||||
journalEntry,
|
||||
accountId,
|
||||
|
||||
// #withAccountDetail
|
||||
account,
|
||||
}) => {
|
||||
/**
|
||||
* Amount popover content line.
|
||||
*/
|
||||
export const AmountPopoverContentLine = ({ journalEntry }) => {
|
||||
const isCredit = !!journalEntry.credit;
|
||||
const isDebit = !!journalEntry.debit;
|
||||
const { account } = journalEntry;
|
||||
|
||||
return (
|
||||
<Choose>
|
||||
@@ -55,10 +57,9 @@ const AmountPopoverContentLineRender = ({
|
||||
);
|
||||
};
|
||||
|
||||
const AmountPopoverContentLine = compose(withAccountDetails)(
|
||||
AmountPopoverContentLineRender,
|
||||
);
|
||||
|
||||
/**
|
||||
* Amount popover content.
|
||||
*/
|
||||
export function AmountPopoverContent({ journalEntries }) {
|
||||
const journalLinesProps = journalEntries.map((journalEntry) => ({
|
||||
journalEntry,
|
||||
@@ -78,7 +79,7 @@ export function AmountPopoverContent({ journalEntries }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* publish column accessor.
|
||||
* Publish column accessor.
|
||||
*/
|
||||
export const StatusAccessor = (row) => {
|
||||
return (
|
||||
@@ -123,79 +124,52 @@ export function NoteAccessor(row) {
|
||||
);
|
||||
}
|
||||
|
||||
// Contact header cell.
|
||||
export function ContactHeaderCell() {
|
||||
/**
|
||||
* Table actions cell.
|
||||
*/
|
||||
export const ActionsCell = (props) => {
|
||||
return (
|
||||
<>
|
||||
<T id={'contact'} />
|
||||
<Hint
|
||||
content={<T id={'contact_column_hint'} />}
|
||||
position={Position.LEFT_BOTTOM}
|
||||
/>
|
||||
</>
|
||||
<Popover
|
||||
content={<ActionsMenu {...props} />}
|
||||
position={Position.RIGHT_BOTTOM}
|
||||
>
|
||||
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Actions cell renderer.
|
||||
export const ActionsCellRenderer = ({
|
||||
row: { index },
|
||||
column: { id },
|
||||
cell: { value: initialValue },
|
||||
data,
|
||||
payload,
|
||||
/**
|
||||
* Actions menu of the table.
|
||||
*/
|
||||
export const ActionsMenu = ({
|
||||
payload: { onPublish, onEdit, onDelete },
|
||||
row: { original },
|
||||
}) => {
|
||||
if (data.length <= index + 1) {
|
||||
return '';
|
||||
}
|
||||
const onClickRemoveRole = () => {
|
||||
payload.removeRow(index);
|
||||
};
|
||||
return (
|
||||
<Tooltip content={<T id={'remove_the_line'} />} position={Position.LEFT}>
|
||||
<Button
|
||||
icon={<Icon icon="times-circle" iconSize={14} />}
|
||||
iconSize={14}
|
||||
className="ml2"
|
||||
minimal={true}
|
||||
intent={Intent.DANGER}
|
||||
onClick={onClickRemoveRole}
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={<Icon icon="reader-18" />}
|
||||
text={formatMessage({ id: 'view_details' })}
|
||||
/>
|
||||
</Tooltip>
|
||||
<MenuDivider />
|
||||
<If condition={!original.is_published}>
|
||||
<MenuItem
|
||||
icon={<Icon icon="arrow-to-top" />}
|
||||
text={formatMessage({ id: 'publish_journal' })}
|
||||
onClick={safeCallback(onPublish, original)}
|
||||
/>
|
||||
</If>
|
||||
<MenuItem
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={formatMessage({ id: 'edit_journal' })}
|
||||
onClick={safeCallback(onEdit, original)}
|
||||
/>
|
||||
<MenuItem
|
||||
text={formatMessage({ id: 'delete_journal' })}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
intent={Intent.DANGER}
|
||||
onClick={safeCallback(onDelete, original)}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
// Total text cell renderer.
|
||||
export const TotalAccountCellRenderer = (chainedComponent) => (props) => {
|
||||
if (props.data.length === props.row.index + 1) {
|
||||
return <span>{'Total USD'}</span>;
|
||||
}
|
||||
return chainedComponent(props);
|
||||
};
|
||||
|
||||
// Total credit/debit cell renderer.
|
||||
export const TotalCreditDebitCellRenderer = (chainedComponent, type) => (
|
||||
props,
|
||||
) => {
|
||||
if (props.data.length === props.row.index + 1) {
|
||||
const total = props.data.reduce((total, entry) => {
|
||||
const amount = parseInt(entry[type], 10);
|
||||
const computed = amount ? total + amount : total;
|
||||
|
||||
return computed;
|
||||
}, 0);
|
||||
|
||||
return (
|
||||
<span>
|
||||
<Money amount={total} currency={'USD'} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return chainedComponent(props);
|
||||
};
|
||||
|
||||
export const NoteCellRenderer = (chainedComponent) => (props) => {
|
||||
if (props.data.length === props.row.index + 1) {
|
||||
return '';
|
||||
}
|
||||
return chainedComponent(props);
|
||||
};
|
||||
79
client/src/containers/Accounting/JournalsLanding/utils.js
Normal file
79
client/src/containers/Accounting/JournalsLanding/utils.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import React from 'react';
|
||||
import { formatMessage } from 'services/intl';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
NoteAccessor,
|
||||
StatusAccessor,
|
||||
DateAccessor,
|
||||
AmountAccessor,
|
||||
ActionsCell,
|
||||
} from './components';
|
||||
|
||||
/**
|
||||
* Retrieve the manual journals columns.
|
||||
*/
|
||||
export const useManualJournalsColumns = () => {
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'date',
|
||||
Header: formatMessage({ id: 'date' }),
|
||||
accessor: DateAccessor,
|
||||
width: 115,
|
||||
className: 'date',
|
||||
},
|
||||
{
|
||||
id: 'amount',
|
||||
Header: formatMessage({ id: 'amount' }),
|
||||
accessor: AmountAccessor,
|
||||
className: 'amount',
|
||||
width: 115,
|
||||
},
|
||||
{
|
||||
id: 'journal_number',
|
||||
Header: formatMessage({ id: 'journal_no' }),
|
||||
accessor: (row) => `#${row.journal_number}`,
|
||||
className: 'journal_number',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
id: 'journal_type',
|
||||
Header: formatMessage({ id: 'journal_type' }),
|
||||
accessor: 'journal_type',
|
||||
width: 110,
|
||||
className: 'journal_type',
|
||||
},
|
||||
{
|
||||
id: 'publish',
|
||||
Header: formatMessage({ id: 'publish' }),
|
||||
accessor: (row) => StatusAccessor(row),
|
||||
width: 95,
|
||||
className: 'publish',
|
||||
},
|
||||
{
|
||||
id: 'note',
|
||||
Header: formatMessage({ id: 'note' }),
|
||||
accessor: NoteAccessor,
|
||||
disableSorting: true,
|
||||
width: 85,
|
||||
className: 'note',
|
||||
},
|
||||
{
|
||||
id: 'created_at',
|
||||
Header: formatMessage({ id: 'created_at' }),
|
||||
accessor: (r) => moment(r.created_at).format('YYYY MMM DD'),
|
||||
width: 125,
|
||||
className: 'created_at',
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Header: '',
|
||||
Cell: ActionsCell,
|
||||
className: 'actions',
|
||||
width: 50,
|
||||
disableResizing: true,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
getManualJournalsTableStateFactory
|
||||
} from 'store/manualJournals/manualJournals.selectors';
|
||||
|
||||
export default (mapState) => {
|
||||
const getJournalsTableQuery = getManualJournalsTableStateFactory();
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const mapped = {
|
||||
manualJournalsTableState: getJournalsTableQuery(state, props),
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
return connect(mapStateToProps);
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
setManualJournalsTableState,
|
||||
} from 'store/manualJournals/manualJournals.actions';
|
||||
|
||||
const mapActionsToProps = (dispatch) => ({
|
||||
setManualJournalsTableState: (queries) =>
|
||||
dispatch(setManualJournalsTableState(queries)),
|
||||
});
|
||||
|
||||
export default connect(null, mapActionsToProps);
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React from 'react';
|
||||
import {
|
||||
InputGroup,
|
||||
FormGroup,
|
||||
@@ -26,6 +26,9 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
|
||||
import { compose, inputIntent, handleDateChange } from 'utils';
|
||||
|
||||
/**
|
||||
* Make journal entries header.
|
||||
*/
|
||||
function MakeJournalEntriesHeader({
|
||||
// #ownProps
|
||||
onJournalNumberChanged,
|
||||
91
client/src/containers/Accounting/MakeJournal/components.js
Normal file
91
client/src/containers/Accounting/MakeJournal/components.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import React from 'react';
|
||||
import { Position } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
import { Money, Hint } from 'components';
|
||||
|
||||
/**
|
||||
* Contact header cell.
|
||||
*/
|
||||
export function ContactHeaderCell() {
|
||||
return (
|
||||
<>
|
||||
<T id={'contact'} />
|
||||
<Hint
|
||||
content={<T id={'contact_column_hint'} />}
|
||||
position={Position.LEFT_BOTTOM}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Total text cell renderer.
|
||||
*/
|
||||
export const TotalAccountCellRenderer = (chainedComponent) => (props) => {
|
||||
if (props.data.length === props.row.index + 1) {
|
||||
return <span>{'Total USD'}</span>;
|
||||
}
|
||||
return chainedComponent(props);
|
||||
};
|
||||
|
||||
/**
|
||||
* Total credit/debit cell renderer.
|
||||
*/
|
||||
export const TotalCreditDebitCellRenderer = (chainedComponent, type) => (
|
||||
props,
|
||||
) => {
|
||||
if (props.data.length === props.row.index + 1) {
|
||||
const total = props.data.reduce((total, entry) => {
|
||||
const amount = parseInt(entry[type], 10);
|
||||
const computed = amount ? total + amount : total;
|
||||
|
||||
return computed;
|
||||
}, 0);
|
||||
|
||||
return (
|
||||
<span>
|
||||
<Money amount={total} currency={'USD'} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return chainedComponent(props);
|
||||
};
|
||||
|
||||
export const NoteCellRenderer = (chainedComponent) => (props) => {
|
||||
if (props.data.length === props.row.index + 1) {
|
||||
return '';
|
||||
}
|
||||
return chainedComponent(props);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Actions cell renderer.
|
||||
*/
|
||||
export const ActionsCellRenderer = ({
|
||||
row: { index },
|
||||
column: { id },
|
||||
cell: { value: initialValue },
|
||||
data,
|
||||
payload,
|
||||
}) => {
|
||||
if (data.length <= index + 1) {
|
||||
return '';
|
||||
}
|
||||
const onClickRemoveRole = () => {
|
||||
payload.removeRow(index);
|
||||
};
|
||||
return (
|
||||
<Tooltip content={<T id={'remove_the_line'} />} position={Position.LEFT}>
|
||||
<Button
|
||||
icon={<Icon icon="times-circle" iconSize={14} />}
|
||||
iconSize={14}
|
||||
className="ml2"
|
||||
minimal={true}
|
||||
intent={Intent.DANGER}
|
||||
onClick={onClickRemoveRole}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { get, sumBy, setWith, toSafeInteger } from 'lodash';
|
||||
import { sumBy, setWith, toSafeInteger, get } from 'lodash';
|
||||
|
||||
import { transformUpdatedRows } from 'utils';
|
||||
import { AppToaster } from 'components';
|
||||
import { formatMessage } from 'services/intl';
|
||||
import { transformUpdatedRows } from 'utils';
|
||||
|
||||
const ERROR = {
|
||||
JOURNAL_NUMBER_ALREADY_EXISTS: 'JOURNAL.NUMBER.ALREADY.EXISTS',
|
||||
@@ -16,15 +17,17 @@ const ERROR = {
|
||||
ENTRIES_SHOULD_ASSIGN_WITH_CONTACT: 'ENTRIES_SHOULD_ASSIGN_WITH_CONTACT',
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Entries adjustment.
|
||||
*/
|
||||
function adjustmentEntries(entries) {
|
||||
const credit = sumBy(entries, e => toSafeInteger(e.credit));
|
||||
const debit = sumBy(entries, e => toSafeInteger(e.debit));
|
||||
const credit = sumBy(entries, (e) => toSafeInteger(e.credit));
|
||||
const debit = sumBy(entries, (e) => toSafeInteger(e.debit));
|
||||
|
||||
return {
|
||||
debit: Math.max(credit - debit, 0),
|
||||
credit: Math.max(debit - credit, 0),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const updateDataReducer = (rows, rowIndex, columnId, value) => {
|
||||
@@ -1,249 +0,0 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import {
|
||||
Intent,
|
||||
Button,
|
||||
Popover,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
import { useIntl } from 'react-intl';
|
||||
import moment from 'moment';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import {
|
||||
DataTable,
|
||||
If,
|
||||
Choose,
|
||||
Icon,
|
||||
} from 'components';
|
||||
import { CLASSES } from 'common/classes';
|
||||
import ManualJournalsEmptyStatus from './ManualJournalsEmptyStatus';
|
||||
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
|
||||
import {
|
||||
NoteAccessor,
|
||||
StatusAccessor,
|
||||
DateAccessor,
|
||||
AmountAccessor
|
||||
} from './components';
|
||||
|
||||
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
|
||||
|
||||
import { compose, saveInvoke } from 'utils';
|
||||
import { useManualJournalsContext } from './ManualJournalsListProvider';
|
||||
|
||||
|
||||
/**
|
||||
* Manual journals data-table.
|
||||
*/
|
||||
function ManualJournalsDataTable({
|
||||
// #withManualJournalsActions
|
||||
addManualJournalsTableQueries,
|
||||
|
||||
// #ownProps
|
||||
onSelectedRowsChange,
|
||||
}) {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const { manualJournals, isManualJournalsLoading } = useManualJournalsContext();
|
||||
|
||||
const handlePublishJournal = useCallback(
|
||||
(journal) => () => {
|
||||
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleEditJournal = useCallback(
|
||||
(journal) => () => {
|
||||
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleDeleteJournal = useCallback(
|
||||
(journal) => () => {
|
||||
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const actionMenuList = useCallback(
|
||||
(journal) => (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={<Icon icon="reader-18" />}
|
||||
text={formatMessage({ id: 'view_details' })}
|
||||
/>
|
||||
<MenuDivider />
|
||||
<If condition={!journal.is_published}>
|
||||
<MenuItem
|
||||
icon={<Icon icon="arrow-to-top" />}
|
||||
text={formatMessage({ id: 'publish_journal' })}
|
||||
onClick={handlePublishJournal(journal)}
|
||||
/>
|
||||
</If>
|
||||
<MenuItem
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={formatMessage({ id: 'edit_journal' })}
|
||||
onClick={handleEditJournal(journal)}
|
||||
/>
|
||||
<MenuItem
|
||||
text={formatMessage({ id: 'delete_journal' })}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDeleteJournal(journal)}
|
||||
/>
|
||||
</Menu>
|
||||
),
|
||||
[
|
||||
handleEditJournal,
|
||||
handleDeleteJournal,
|
||||
handlePublishJournal,
|
||||
formatMessage,
|
||||
],
|
||||
);
|
||||
|
||||
const onRowContextMenu = useCallback(
|
||||
(cell) => actionMenuList(cell.row.original),
|
||||
[actionMenuList],
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'date',
|
||||
Header: formatMessage({ id: 'date' }),
|
||||
accessor: DateAccessor,
|
||||
width: 115,
|
||||
className: 'date',
|
||||
},
|
||||
{
|
||||
id: 'amount',
|
||||
Header: formatMessage({ id: 'amount' }),
|
||||
accessor: AmountAccessor,
|
||||
className: 'amount',
|
||||
width: 115,
|
||||
},
|
||||
{
|
||||
id: 'journal_number',
|
||||
Header: formatMessage({ id: 'journal_no' }),
|
||||
accessor: (row) => `#${row.journal_number}`,
|
||||
className: 'journal_number',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
id: 'journal_type',
|
||||
Header: formatMessage({ id: 'journal_type' }),
|
||||
accessor: 'journal_type',
|
||||
width: 110,
|
||||
className: 'journal_type',
|
||||
},
|
||||
{
|
||||
id: 'publish',
|
||||
Header: formatMessage({ id: 'publish' }),
|
||||
accessor: (row) => StatusAccessor(row),
|
||||
width: 95,
|
||||
className: 'publish',
|
||||
},
|
||||
{
|
||||
id: 'note',
|
||||
Header: formatMessage({ id: 'note' }),
|
||||
accessor: NoteAccessor,
|
||||
disableSorting: true,
|
||||
width: 85,
|
||||
className: 'note',
|
||||
},
|
||||
{
|
||||
id: 'created_at',
|
||||
Header: formatMessage({ id: 'created_at' }),
|
||||
accessor: (r) => moment(r.created_at).format('YYYY MMM DD'),
|
||||
width: 125,
|
||||
className: 'created_at',
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Header: '',
|
||||
Cell: ({ cell }) => (
|
||||
<Popover
|
||||
content={actionMenuList(cell.row.original)}
|
||||
position={Position.RIGHT_BOTTOM}
|
||||
>
|
||||
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
|
||||
</Popover>
|
||||
),
|
||||
className: 'actions',
|
||||
width: 50,
|
||||
disableResizing: true,
|
||||
},
|
||||
],
|
||||
[actionMenuList, formatMessage],
|
||||
);
|
||||
|
||||
const handleDataTableFetchData = useCallback(
|
||||
({ pageIndex, pageSize, sortBy }) => {
|
||||
addManualJournalsTableQueries({
|
||||
...(sortBy.length > 0
|
||||
? {
|
||||
column_sort_by: sortBy[0].id,
|
||||
sort_order: sortBy[0].desc ? 'desc' : 'asc',
|
||||
}
|
||||
: {}),
|
||||
page_size: pageSize,
|
||||
page: pageIndex + 1,
|
||||
});
|
||||
},
|
||||
[addManualJournalsTableQueries],
|
||||
);
|
||||
|
||||
const handleSelectedRowsChange = useCallback(
|
||||
(selectedRows) => {
|
||||
saveInvoke(
|
||||
onSelectedRowsChange,
|
||||
selectedRows.map((s) => s.original),
|
||||
);
|
||||
},
|
||||
[onSelectedRowsChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
|
||||
<Choose>
|
||||
<Choose.When condition={false}>
|
||||
<ManualJournalsEmptyStatus />
|
||||
</Choose.When>
|
||||
|
||||
<Choose.Otherwise>
|
||||
<DataTable
|
||||
noInitialFetch={true}
|
||||
columns={columns}
|
||||
data={manualJournals}
|
||||
onFetchData={handleDataTableFetchData}
|
||||
manualSortBy={true}
|
||||
selectionColumn={true}
|
||||
expandable={true}
|
||||
sticky={true}
|
||||
loading={isManualJournalsLoading}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
rowContextMenu={onRowContextMenu}
|
||||
// pagesCount={manualJournalsPagination.pagesCount}
|
||||
pagination={true}
|
||||
// initialPageSize={manualJournalsTableQuery.page_size}
|
||||
// initialPageIndex={manualJournalsTableQuery.page - 1}
|
||||
autoResetSortBy={false}
|
||||
autoResetPage={false}
|
||||
|
||||
TableLoadingRenderer={TableSkeletonRows}
|
||||
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||
/>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withManualJournalsActions,
|
||||
)(ManualJournalsDataTable);
|
||||
@@ -1,80 +0,0 @@
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import { Route, Switch, useHistory } from 'react-router-dom';
|
||||
import {
|
||||
FormattedMessage as T,
|
||||
useIntl,
|
||||
} from 'react-intl';
|
||||
|
||||
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
|
||||
|
||||
import { ManualJournalsListProvider } from './ManualJournalsListProvider';
|
||||
import ManualJournalsAlerts from './ManualJournalsAlerts';
|
||||
import ManualJournalsViewTabs from './ManualJournalsViewTabs';
|
||||
import ManualJournalsDataTable from './ManualJournalsDataTable';
|
||||
import ManualJournalsActionsBar from './ManualJournalActionsBar';
|
||||
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import withManualJournals from 'containers/Accounting/withManualJournals';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
import 'style/pages/ManualJournal/List.scss';
|
||||
|
||||
|
||||
/**
|
||||
* Manual journals table.
|
||||
*/
|
||||
function ManualJournalsTable({
|
||||
// #withDashboardActions
|
||||
changePageTitle,
|
||||
|
||||
// #withManualJournals
|
||||
manualJournalsTableQuery,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
// Handle update the page title.
|
||||
useEffect(() => {
|
||||
changePageTitle(formatMessage({ id: 'manual_journals' }));
|
||||
}, [changePageTitle, formatMessage]);
|
||||
|
||||
const handleEditJournal = useCallback(
|
||||
(journal) => {
|
||||
history.push(`/manual-journals/${journal.id}/edit`);
|
||||
},
|
||||
[history],
|
||||
);
|
||||
|
||||
// Handle filter change to re-fetch data-table.
|
||||
const handleFilterChanged = useCallback(() => {}, []);
|
||||
|
||||
return (
|
||||
<ManualJournalsListProvider query={manualJournalsTableQuery}>
|
||||
<ManualJournalsActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<Switch>
|
||||
<Route
|
||||
exact={true}
|
||||
path={[
|
||||
'/manual-journals/:custom_view_id/custom_view',
|
||||
'/manual-journals',
|
||||
]}
|
||||
>
|
||||
<ManualJournalsViewTabs />
|
||||
<ManualJournalsDataTable />
|
||||
<ManualJournalsAlerts />
|
||||
</Route>
|
||||
</Switch>
|
||||
</DashboardPageContent>
|
||||
</ManualJournalsListProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDashboardActions,
|
||||
withManualJournals(({ manualJournalsTableQuery }) => ({
|
||||
manualJournalsTableQuery,
|
||||
})),
|
||||
)(ManualJournalsTable);
|
||||
@@ -1,10 +0,0 @@
|
||||
import {connect} from 'react-redux';
|
||||
import {
|
||||
getManualJournal,
|
||||
} from 'store/manualJournals/manualJournals.reducers';
|
||||
|
||||
export const mapStateToProps = (state, props) => ({
|
||||
manualJournal: getManualJournal(state, props.manualJournalId),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps);
|
||||
@@ -1,14 +0,0 @@
|
||||
import {connect} from 'react-redux';
|
||||
import {
|
||||
makeJournalEntries,
|
||||
fetchManualJournal,
|
||||
editManualJournal,
|
||||
} from 'store/manualJournals/manualJournals.actions';
|
||||
|
||||
export const mapDispatchToProps = (dispatch) => ({
|
||||
requestMakeJournalEntries: (form) => dispatch(makeJournalEntries({ form })),
|
||||
requestFetchManualJournal: (id) => dispatch(fetchManualJournal({ id })),
|
||||
requestEditManualJournal: (id, form) => dispatch(editManualJournal({ id, form }))
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps);
|
||||
@@ -1,11 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { getManualJournalByIdFactory } from 'store/manualJournals/manualJournals.selectors';
|
||||
|
||||
export default () => {
|
||||
const getManualJournalById = getManualJournalByIdFactory();
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
manualJournal: getManualJournalById(state, props),
|
||||
});
|
||||
return connect(mapStateToProps);
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { getResourceViews } from 'store/customViews/customViews.selectors';
|
||||
import {
|
||||
getManualJournalsItems,
|
||||
getManualJournalsPagination,
|
||||
getManualJournalsTableQuery,
|
||||
getManualJournalsCurrentViewIdFactory
|
||||
} from 'store/manualJournals/manualJournals.selectors';
|
||||
|
||||
|
||||
export default (mapState) => {
|
||||
const getManualJournalsCurrentViewId = getManualJournalsCurrentViewIdFactory();
|
||||
const mapStateToProps = (state, props) => {
|
||||
const query = getManualJournalsTableQuery(state, props);
|
||||
|
||||
const mapped = {
|
||||
manualJournalsCurrentPage: getManualJournalsItems(state, props, query),
|
||||
manualJournalsTableQuery: query,
|
||||
manualJournalsViews: getResourceViews(state, props, 'manual_journals'),
|
||||
manualJournalsItems: state.manualJournals.items,
|
||||
|
||||
manualJournalsPagination: getManualJournalsPagination(state, props, query),
|
||||
manualJournalsLoading: state.manualJournals.loading,
|
||||
|
||||
journalNumberChanged: state.manualJournals.journalNumberChanged,
|
||||
|
||||
manualJournalsCurrentViewId: getManualJournalsCurrentViewId(state, props),
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
|
||||
return connect(mapStateToProps);
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import t from 'store/types';
|
||||
import {
|
||||
deleteManualJournal,
|
||||
fetchManualJournalsTable,
|
||||
publishManualJournal,
|
||||
deleteBulkManualJournals,
|
||||
fetchManualJournal,
|
||||
} from 'store/manualJournals/manualJournals.actions';
|
||||
|
||||
const mapActionsToProps = (dispatch) => ({
|
||||
requestDeleteManualJournal: (id) => dispatch(deleteManualJournal({ id })),
|
||||
requestFetchManualJournalsTable: (query = {}) => dispatch(fetchManualJournalsTable({ query })),
|
||||
requestFetchManualJournal: (id) => dispatch(fetchManualJournal({ id })),
|
||||
requestPublishManualJournal: (id) => dispatch(publishManualJournal({ id })),
|
||||
requestDeleteBulkManualJournals: (ids) => dispatch(deleteBulkManualJournals({ ids })),
|
||||
changeManualJournalCurrentView: (id) => dispatch({
|
||||
type: t.MANUAL_JOURNALS_SET_CURRENT_VIEW,
|
||||
payload: {
|
||||
currentViewId: parseInt(id, 10),
|
||||
}
|
||||
}),
|
||||
addManualJournalsTableQueries: (queries) => dispatch({
|
||||
type: t.MANUAL_JOURNALS_TABLE_QUERIES_ADD,
|
||||
payload: { queries },
|
||||
}),
|
||||
setJournalNumberChanged: (isChanged) => dispatch({
|
||||
type: t.MANUAL_JOURNAL_NUMBER_CHANGED,
|
||||
payload: { isChanged },
|
||||
}),
|
||||
});
|
||||
|
||||
export default connect(null, mapActionsToProps);
|
||||
Reference in New Issue
Block a user