refactoring: migrating to react-query to manage service-side state.

This commit is contained in:
a.bouhuolia
2021-02-07 08:10:21 +02:00
parent e093be0663
commit adac2386bb
284 changed files with 8255 additions and 6610 deletions

View File

@@ -7,7 +7,6 @@ import { orderingLinesIndexes, repeatValue } from 'utils';
export default function MakeJournalEntriesField({
defaultRow,
linesNumber = 4,
}) {
return (

View File

@@ -18,23 +18,20 @@ import MakeJournalEntriesField from './MakeJournalEntriesField';
import MakeJournalNumberWatcher from './MakeJournalNumberWatcher';
import MakeJournalFormFooter from './MakeJournalFormFooter';
import withJournalsActions from 'containers/Accounting/withJournalsActions';
import withManualJournalDetail from 'containers/Accounting/withManualJournalDetail';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withSettings from 'containers/Settings/withSettings';
import AppToaster from 'components/AppToaster';
import Dragzone from 'components/Dragzone';
import withMediaActions from 'containers/Media/withMediaActions';
import {
compose,
repeatValue,
orderingLinesIndexes,
defaultToTransform,
transactionNumber,
} from 'utils';
import { transformErrors } from './utils';
import withManualJournalsActions from './withManualJournalsActions';
import { useMakeJournalFormContext } from './MakeJournalProvider';
const defaultEntry = {
index: 0,
@@ -59,18 +56,6 @@ const defaultInitialValues = {
* Journal entries form.
*/
function MakeJournalEntriesForm({
// #withMedia
requestSubmitMedia,
requestDeleteMedia,
// #withJournalsActions
requestMakeJournalEntries,
requestEditManualJournal,
setJournalNumberChanged,
// #withManualJournals
journalNumberChanged,
// #withDashboard
changePageTitle,
changePageSubtitle,
@@ -79,30 +64,33 @@ function MakeJournalEntriesForm({
journalNextNumber,
journalNumberPrefix,
baseCurrency,
// #ownProps
manualJournalId,
manualJournal,
onFormSubmit,
onCancelForm,
}) {
const isNewMode = !manualJournalId;
const [submitPayload, setSubmitPayload] = useState({});
const {
createJournalMutate,
editJournalMutate,
isNewMode,
manualJournal,
submitPayload,
} = useMakeJournalFormContext();
const { formatMessage } = useIntl();
const history = useHistory();
const journalNumber = isNewMode
? `${journalNumberPrefix}-${journalNextNumber}`
: journalNextNumber;
// New journal number.
const journalNumber = transactionNumber(
journalNumberPrefix,
journalNextNumber,
);
// Changes the page title based on the form in new and edit mode.
useEffect(() => {
const transactionNumber = manualJournal
? manualJournal.journal_number
: journalNumber;
if (manualJournal && manualJournal.id) {
changePageTitle(formatMessage({ id: 'edit_journal' }));
} else {
if (isNewMode) {
changePageTitle(formatMessage({ id: 'new_journal' }));
} else {
changePageTitle(formatMessage({ id: 'edit_journal' }));
}
changePageSubtitle(
defaultToTransform(transactionNumber, `No. ${transactionNumber}`, ''),
@@ -113,6 +101,7 @@ function MakeJournalEntriesForm({
journalNumber,
manualJournal,
formatMessage,
isNewMode,
]);
const initialValues = useMemo(
@@ -131,7 +120,7 @@ function MakeJournalEntriesForm({
entries: orderingLinesIndexes(defaultInitialValues.entries),
}),
}),
[manualJournal, journalNumber],
[manualJournal, baseCurrency, journalNumber],
);
// Handle journal number field change.
@@ -144,6 +133,7 @@ function MakeJournalEntriesForm({
[changePageSubtitle],
);
// Handle the form submiting.
const handleSubmit = (values, { setErrors, setSubmitting, resetForm }) => {
setSubmitting(true);
const entries = values.entries.filter(
@@ -179,11 +169,13 @@ function MakeJournalEntriesForm({
}
const form = { ...values, publish: submitPayload.publish, entries };
// Handle the request error.
const handleError = (error) => {
transformErrors(error, { setErrors });
setSubmitting(false);
};
// Handle the request success.
const handleSuccess = (errors) => {
AppToaster.show({
message: formatMessage(
@@ -196,7 +188,6 @@ function MakeJournalEntriesForm({
),
intent: Intent.SUCCESS,
});
setSubmitting(false);
if (submitPayload.redirect) {
@@ -208,25 +199,14 @@ function MakeJournalEntriesForm({
};
if (isNewMode) {
requestMakeJournalEntries(form).then(handleSuccess).catch(handleError);
createJournalMutate(form).then(handleSuccess).catch(handleError);
} else {
requestEditManualJournal(manualJournal.id, form)
editJournalMutate(manualJournal.id, form)
.then(handleSuccess)
.catch(handleError);
}
};
const handleCancelClick = useCallback(() => {
history.goBack();
}, [history]);
const handleSubmitClick = useCallback(
(event, payload) => {
setSubmitPayload({ ...payload });
},
[setSubmitPayload],
);
return (
<div
className={classNames(
@@ -240,39 +220,21 @@ function MakeJournalEntriesForm({
validationSchema={isNewMode ? CreateJournalSchema : EditJournalSchema}
onSubmit={handleSubmit}
>
{({ isSubmitting }) => (
<Form>
<MakeJournalEntriesHeader
manualJournal={manualJournalId}
onJournalNumberChanged={handleJournalNumberChanged}
/>
<MakeJournalNumberWatcher journalNumber={journalNumber} />
<MakeJournalEntriesField defaultRow={defaultEntry} />
<MakeJournalFormFooter />
<MakeJournalFormFloatingActions
isSubmitting={isSubmitting}
manualJournal={manualJournal}
// manualJournalPublished={values.status}
onCancelClick={handleCancelClick}
onSubmitClick={handleSubmitClick}
/>
</Form>
)}
<Form>
<MakeJournalEntriesHeader
onJournalNumberChanged={handleJournalNumberChanged}
/>
<MakeJournalNumberWatcher journalNumber={journalNumber} />
<MakeJournalEntriesField defaultRow={defaultEntry} />
<MakeJournalFormFooter />
<MakeJournalFormFloatingActions />
</Form>
</Formik>
{/* <Dragzone
initialFiles={initialAttachmentFiles}
onDrop={handleDropFiles}
onDeleteFile={handleDeleteFile}
hint={'Attachments: Maxiumum size: 20MB'}
/> */}
</div>
);
}
export default compose(
withJournalsActions,
withManualJournalDetail(),
withAccountsActions,
withDashboardActions,
withMediaActions,
withSettings(({ manualJournalsSettings, organizationSettings }) => ({
@@ -280,5 +242,4 @@ export default compose(
journalNumberPrefix: manualJournalsSettings?.numberPrefix,
baseCurrency: organizationSettings?.baseCurrency,
})),
withManualJournalsActions,
)(MakeJournalEntriesForm);

View File

@@ -21,25 +21,24 @@ import {
CurrencySelectList,
} from 'components';
import { useMakeJournalFormContext } from './MakeJournalProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withCurrencies from 'containers/Currencies/withCurrencies';
import { compose, inputIntent, handleDateChange } from 'utils';
function MakeJournalEntriesHeader({
// #ownProps
manualJournal,
onJournalNumberChanged,
// #withCurrencies
currenciesList,
// #withDialog
openDialog,
}) {
const handleJournalNumberChange = useCallback(() => {
const { currencies } = useMakeJournalFormContext();
// Handle journal number change.
const handleJournalNumberChange = () => {
openDialog('journal-number-form', {});
}, [openDialog]);
};
// Handle journal number field blur event.
const handleJournalNumberChanged = (event) => {
@@ -165,7 +164,7 @@ function MakeJournalEntriesHeader({
inline={true}
>
<CurrencySelectList
currenciesList={currenciesList}
currenciesList={currencies}
selectedCurrencyCode={value}
onCurrencySelected={(currencyItem) => {
form.setFieldValue('currency_code', currencyItem.currency_code);
@@ -181,7 +180,4 @@ function MakeJournalEntriesHeader({
export default compose(
withDialogActions,
withCurrencies(({ currenciesList }) => ({
currenciesList,
})),
)(MakeJournalEntriesHeader);

View File

@@ -1,44 +1,26 @@
import React, { useCallback, useEffect } from 'react';
import { useParams, useHistory } from 'react-router-dom';
import { useQuery } from 'react-query';
import MakeJournalEntriesForm from './MakeJournalEntriesForm';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import { MakeJournalProvider } from './MakeJournalProvider';
import withCustomersActions from 'containers/Customers/withCustomersActions';
import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
import withCurrenciesActions from 'containers/Currencies/withCurrenciesActions';
import withSettingsActions from 'containers/Settings/withSettingsActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import { compose } from 'utils';
import 'style/pages/ManualJournal/MakeJournal.scss'
import 'style/pages/ManualJournal/MakeJournal.scss';
/**
* Make journal entries page.
*/
function MakeJournalEntriesPage({
// #withCustomersActions
requestFetchCustomers,
// #withAccountsActions
requestFetchAccounts,
// #withManualJournalActions
requestFetchManualJournal,
// #wihtCurrenciesActions
requestFetchCurrencies,
// #withSettingsActions
requestFetchOptions,
// #withDashboardActions
setSidebarShrink,
resetSidebarPreviousExpand,
setDashboardBackLink
setDashboardBackLink,
}) {
const history = useHistory();
const { id } = useParams();
const { id: journalId } = useParams();
useEffect(() => {
// Shrink the sidebar by foce.
@@ -52,27 +34,7 @@ function MakeJournalEntriesPage({
// Hide the back link on dashboard topbar.
setDashboardBackLink(false);
};
}, [resetSidebarPreviousExpand, setSidebarShrink]);
const fetchAccounts = useQuery('accounts-list', (key) =>
requestFetchAccounts(),
);
const fetchCustomers = useQuery('customers-list', (key) =>
requestFetchCustomers(),
);
const fetchCurrencies = useQuery('currencies', () =>
requestFetchCurrencies(),
);
const fetchSettings = useQuery(['settings'], () => requestFetchOptions({}));
const fetchJournal = useQuery(
['manual-journal', id],
(key, journalId) => requestFetchManualJournal(journalId),
{ enabled: id && id },
);
}, [resetSidebarPreviousExpand, setDashboardBackLink, setSidebarShrink]);
const handleFormSubmit = useCallback(
(payload) => {
@@ -86,29 +48,15 @@ function MakeJournalEntriesPage({
}, [history]);
return (
<DashboardInsider
loading={
fetchJournal.isFetching ||
fetchAccounts.isFetching ||
fetchCurrencies.isFetching ||
fetchCustomers.isFetching
}
name={'make-journal-page'}
>
<MakeJournalProvider journalId={journalId}>
<MakeJournalEntriesForm
onFormSubmit={handleFormSubmit}
manualJournalId={id}
onCancelForm={handleCancel}
/>
</DashboardInsider>
</MakeJournalProvider>
);
}
export default compose(
withAccountsActions,
withCustomersActions,
withManualJournalsActions,
withCurrenciesActions,
withSettingsActions,
withDashboardActions,
)(MakeJournalEntriesPage);

View File

@@ -2,7 +2,7 @@ import React, { useState, useMemo, useEffect, useCallback } from 'react';
import { Button } from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { omit } from 'lodash';
import { compose, saveInvoke } from 'utils';
import { saveInvoke } from 'utils';
import {
AccountsListFieldCell,
MoneyFieldCell,
@@ -18,21 +18,13 @@ import {
} from './components';
import { DataTableEditable } from 'components';
import withAccounts from 'containers/Accounts/withAccounts';
import withCustomers from 'containers/Customers/withCustomers';
import { updateDataReducer } from './utils';
import { useMakeJournalFormContext } from './MakeJournalProvider';
/**
* Make journal entries table component.
*/
function MakeJournalEntriesTable({
// #withCustomers
customers,
// #withAccounts
accountsList,
export default function MakeJournalEntriesTable({
// #ownPorps
onClickRemoveRow,
onClickAddNewRow,
@@ -44,6 +36,8 @@ function MakeJournalEntriesTable({
const [rows, setRows] = useState([]);
const { formatMessage } = useIntl();
const { accounts, customers } = useMakeJournalFormContext();
useEffect(() => {
setRows([...entries.map((e) => ({ ...e, rowType: 'editor' }))]);
}, [entries, setRows]);
@@ -175,7 +169,7 @@ function MakeJournalEntriesTable({
sticky={true}
totalRow={true}
payload={{
accounts: accountsList,
accounts,
errors: error,
updateData: handleUpdateData,
removeRow: handleRemoveRow,
@@ -209,12 +203,3 @@ function MakeJournalEntriesTable({
/>
);
}
export default compose(
withAccounts(({ accountsList }) => ({
accountsList,
})),
withCustomers(({ customers }) => ({
customers,
})),
)(MakeJournalEntriesTable);

View File

@@ -12,73 +12,64 @@ import {
import { useFormikContext } from 'formik';
import classNames from 'classnames';
import { FormattedMessage as T } from 'react-intl';
import { saveInvoke } from 'utils';
import { CLASSES } from 'common/classes';
import { Icon, If } from 'components';
import { useHistory } from 'react-router-dom';
import { useMakeJournalFormContext } from './MakeJournalProvider';
/**
* Make Journal floating actions bar.
*/
export default function MakeJournalFloatingAction({
isSubmitting,
onSubmitClick,
onCancelClick,
manualJournal,
}) {
const { submitForm, resetForm } = useFormikContext();
export default function MakeJournalFloatingAction() {
const history = useHistory();
// Formik context.
const { submitForm, resetForm, isSubmitting } = useFormikContext();
// Make journal form context.
const { setSubmitPayload, manualJournal } = useMakeJournalFormContext();
// Handle submit & publish button click.
const handleSubmitPublishBtnClick = (event) => {
saveInvoke(onSubmitClick, event, {
redirect: true,
publish: true,
});
submitForm();
setSubmitPayload({ redirect: true, publish: true });
};
// Handle submit, publish & new button click.
const handleSubmitPublishAndNewBtnClick = (event) => {
submitForm();
saveInvoke(onSubmitClick, event, {
redirect: false,
publish: true,
resetForm: true,
});
setSubmitPayload({ redirect: false, publish: true, resetForm: true });
};
// Handle submit, publish & edit button click.
const handleSubmitPublishContinueEditingBtnClick = (event) => {
submitForm();
saveInvoke(onSubmitClick, event, {
redirect: false,
publish: true,
});
setSubmitPayload({ redirect: false, publish: true });
};
// Handle submit as draft button click.
const handleSubmitDraftBtnClick = (event) => {
saveInvoke(onSubmitClick, event, {
redirect: true,
publish: false,
});
setSubmitPayload({ redirect: true, publish: false });
};
// Handle submit as draft & new button click.
const handleSubmitDraftAndNewBtnClick = (event) => {
submitForm();
saveInvoke(onSubmitClick, event, {
redirect: false,
publish: false,
resetForm: true,
});
setSubmitPayload({ redirect: false, publish: false, resetForm: true });
};
// Handle submit as draft & continue editing button click.
const handleSubmitDraftContinueEditingBtnClick = (event) => {
submitForm();
saveInvoke(onSubmitClick, event, {
redirect: false,
publish: false,
});
setSubmitPayload({ redirect: false, publish: false });
};
// Handle cancel button click.
const handleCancelBtnClick = (event) => {
saveInvoke(onCancelClick, event);
history.goBack();
};
// Handle clear button click.
const handleClearBtnClick = (event) => {
resetForm();
};
@@ -90,6 +81,7 @@ export default function MakeJournalFloatingAction({
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
onClick={handleSubmitPublishBtnClick}
text={<T id={'save_publish'} />}
@@ -114,6 +106,7 @@ export default function MakeJournalFloatingAction({
<Button
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
disabled={isSubmitting}
/>
</Popover>
</ButtonGroup>
@@ -144,6 +137,7 @@ export default function MakeJournalFloatingAction({
>
<Button
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
disabled={isSubmitting}
/>
</Popover>
</ButtonGroup>
@@ -173,6 +167,7 @@ export default function MakeJournalFloatingAction({
<Button
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
disabled={isSubmitting}
/>
</Popover>
</ButtonGroup>

View File

@@ -9,14 +9,20 @@ import withManualJournals from './withManualJournals';
import { defaultToTransform } from 'utils';
/**
*
* Journal number chaلing watcher.
*/
function MakeJournalNumberChangingWatcher({
journalNumber,
// #withDashboardActions
changePageSubtitle,
// #withManualJournals
journalNumberChanged,
// #withManualJournalsActions
setJournalNumberChanged,
changePageSubtitle
// #ownProps
journalNumber,
}) {
const { setFieldValue } = useFormikContext();

View File

@@ -0,0 +1,87 @@
import React, { createContext, useState } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import {
useAccounts,
useCustomers,
useCurrencies,
useJournal,
useCreateJournal,
useEditJournal,
useSettings
} from 'hooks/query';
const MakeJournalFormContext = createContext();
/**
* Make journal form provider.
*/
function MakeJournalProvider({ journalId, ...props }) {
// Load the accounts list.
const { data: accounts, isFetching: isAccountsLoading } = useAccounts();
// Load the customers list.
const {
data: { customers },
isFetching: isCustomersLoading,
} = useCustomers();
// Load the currencies list.
const { data: currencies, isFetching: isCurrenciesLoading } = useCurrencies();
// Load the details of the given manual journal.
const { data: manualJournal, isFetching: isJournalLoading } = useJournal(
journalId,
{
enabled: !!journalId,
},
);
// Create and edit journal mutations.
const { mutateAsync: createJournalMutate } = useCreateJournal();
const { mutateAsync: editJournalMutate } = useEditJournal();
// Loading the journal settings.
const { isFetching: isSettingsLoading } = useSettings();
const [submitPayload, setSubmitPayload] = useState({});
const provider = {
accounts,
customers,
currencies,
manualJournal,
createJournalMutate,
editJournalMutate,
isAccountsLoading,
isCustomersLoading,
isCurrenciesLoading,
isJournalLoading,
isSettingsLoading,
isNewMode: !journalId,
submitPayload,
setSubmitPayload
};
return (
<DashboardInsider
loading={
isJournalLoading ||
isAccountsLoading ||
isCurrenciesLoading ||
isCustomersLoading ||
isSettingsLoading
}
name={'make-journal-page'}
>
<MakeJournalFormContext.Provider value={provider} {...props} />
</DashboardInsider>
);
}
const useMakeJournalFormContext = () =>
React.useContext(MakeJournalFormContext);
export { MakeJournalProvider, useMakeJournalFormContext };

View File

@@ -1,23 +1,21 @@
import React, { useMemo, useState, useCallback } from 'react';
import React from 'react';
import Icon from 'components/Icon';
import {
Button,
NavbarGroup,
Classes,
NavbarDivider,
MenuItem,
Menu,
Popover,
PopoverInteractionKind,
Position,
Intent,
} from '@blueprintjs/core';
import classNames from 'classnames';
import { useRouteMatch, useHistory } from 'react-router-dom';
import { useHistory } from 'react-router-dom';
import { FormattedMessage as T } from 'react-intl';
import { connect } from 'react-redux';
import FilterDropdown from 'components/FilterDropdown';
import { useManualJournalsContext } from 'containers/Accounting/ManualJournalsListProvider';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import withDialogActions from 'containers/Dialog/withDialogActions';
@@ -33,53 +31,24 @@ import { compose } from 'utils';
* Manual journal actions bar.
*/
function ManualJournalActionsBar({
// #withResourceDetail
resourceFields,
// #withManualJournals
manualJournalsViews,
// #withManualJournalsActions
addManualJournalsTableQueries,
changeManualJournalCurrentView,
onFilterChanged,
selectedRows = [],
onBulkDelete,
}) {
const [filterCount, setFilterCount] = useState(0);
const history = useHistory();
const { journalsViews } = useManualJournalsContext();
const onClickNewManualJournal = useCallback(() => {
// Handle click a new manual journal.
const onClickNewManualJournal = () => {
history.push('/make-journal-entry');
}, [history]);
// const filterDropdown = FilterDropdown({
// fields: resourceFields,
// initialCondition: {
// fieldKey: 'journal_number',
// compatator: 'contains',
// value: '',
// },
// onFilterChange: (filterConditions) => {
// setFilterCount(filterConditions.length || 0);
// addManualJournalsTableQueries({
// filter_roles: filterConditions || '',
// });
// onFilterChanged && onFilterChanged(filterConditions);
// },
// });
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [
selectedRows,
]);
};
// Handle delete button click.
const handleBulkDelete = useCallback(() => {
onBulkDelete && onBulkDelete(selectedRows.map((r) => r.id));
}, [onBulkDelete, selectedRows]);
const handleBulkDelete = () => {
};
// Handle tab change.
const handleTabChange = (viewId) => {
changeManualJournalCurrentView(viewId.id || -1);
addManualJournalsTableQueries({
custom_view_id: viewId.id || null,
});
@@ -90,7 +59,7 @@ function ManualJournalActionsBar({
<NavbarGroup>
<DashboardActionViewsList
resourceName={'manual-journals'}
views={manualJournalsViews}
views={journalsViews}
onChange={handleTabChange}
/>
<NavbarDivider />
@@ -109,14 +78,14 @@ function ManualJournalActionsBar({
>
<Button
className={classNames(Classes.MINIMAL, 'button--filter', {
'has-active-filters': filterCount > 0,
'has-active-filters': false,
})}
text="Filter"
icon={<Icon icon="filter-16" iconSize={16} />}
/>
</Popover>
<If condition={hasSelectedRows}>
<If condition={false}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="trash-16" iconSize={16} />}

View File

@@ -0,0 +1,15 @@
import React from 'react';
import JournalDeleteAlert from 'containers/Alerts/ManualJournals/JournalDeleteAlert';
import JournalPublishAlert from 'containers/Alerts/ManualJournals/JournalPublishAlert';
/**
* Manual journals alerts.
*/
export default function ManualJournalsAlerts() {
return (
<div>
<JournalDeleteAlert name={'journal-delete'} />
<JournalPublishAlert name={'journal-publish'} />
</div>
)
}

View File

@@ -3,13 +3,11 @@ import {
Intent,
Button,
Popover,
Tooltip,
Menu,
MenuItem,
MenuDivider,
Position,
} from '@blueprintjs/core';
import { withRouter, useParams } from 'react-router-dom';
import { useIntl } from 'react-intl';
import moment from 'moment';
import classNames from 'classnames';
@@ -17,68 +15,59 @@ import classNames from 'classnames';
import {
DataTable,
If,
Money,
Choose,
Icon,
LoadingIndicator,
} from 'components';
import { CLASSES } from 'common/classes';
import { useIsValuePassed } from 'hooks';
import ManualJournalsEmptyStatus from './ManualJournalsEmptyStatus';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
import {
AmountPopoverContent,
NoteAccessor,
StatusAccessor,
DateAccessor,
AmountAccessor
} from './components';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withManualJournals from 'containers/Accounting/withManualJournals';
import withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
import { compose, saveInvoke } from 'utils';
import { useManualJournalsContext } from './ManualJournalsListProvider';
/**
* Manual journals data-table.
*/
function ManualJournalsDataTable({
// #withManualJournals
manualJournalsCurrentPage,
manualJournalsLoading,
manualJournalsPagination,
manualJournalsTableQuery,
manualJournalsCurrentViewId,
// #withManualJournalsActions
addManualJournalsTableQueries,
// #ownProps
onEditJournal,
onDeleteJournal,
onPublishJournal,
onSelectedRowsChange,
manualJournalViewLoading,
}) {
const { custom_view_id: customViewId } = useParams();
const { formatMessage } = useIntl();
const isLoadedBefore = useIsValuePassed(manualJournalsLoading, false);
const { manualJournals, isManualJournalsLoading } = useManualJournalsContext();
const handlePublishJournal = useCallback(
(journal) => () => {
saveInvoke(onPublishJournal, journal);
},
[onPublishJournal],
[],
);
const handleEditJournal = useCallback(
(journal) => () => {
saveInvoke(onEditJournal, journal);
},
[onEditJournal],
[],
);
const handleDeleteJournal = useCallback(
(journal) => () => {
saveInvoke(onDeleteJournal, journal);
},
[onDeleteJournal],
[],
);
const actionMenuList = useCallback(
@@ -127,22 +116,14 @@ function ManualJournalsDataTable({
{
id: 'date',
Header: formatMessage({ id: 'date' }),
accessor: (r) => moment(r.date).format('YYYY MMM DD'),
accessor: DateAccessor,
width: 115,
className: 'date',
},
{
id: 'amount',
Header: formatMessage({ id: 'amount' }),
accessor: (r) => (
<Tooltip
content={<AmountPopoverContent journalEntries={r.entries} />}
position={Position.RIGHT_TOP}
boundary={'viewport'}
>
<Money amount={r.amount} currency={'USD'} />
</Tooltip>
),
accessor: AmountAccessor,
className: 'amount',
width: 115,
},
@@ -227,66 +208,42 @@ function ManualJournalsDataTable({
[onSelectedRowsChange],
);
const showEmptyStatus = [
manualJournalsCurrentViewId === -1,
manualJournalsCurrentPage.length === 0,
].every((condition) => condition === true);
return (
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator
loading={
(manualJournalsLoading && !isLoadedBefore) || manualJournalViewLoading
}
>
<Choose>
<Choose.When condition={showEmptyStatus}>
<ManualJournalsEmptyStatus />
</Choose.When>
<Choose>
<Choose.When condition={false}>
<ManualJournalsEmptyStatus />
</Choose.When>
<Choose.Otherwise>
<DataTable
noInitialFetch={true}
columns={columns}
data={manualJournalsCurrentPage}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
expandable={true}
sticky={true}
onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu}
pagesCount={manualJournalsPagination.pagesCount}
pagination={true}
initialPageSize={manualJournalsTableQuery.page_size}
initialPageIndex={manualJournalsTableQuery.page - 1}
autoResetSortBy={false}
autoResetPage={false}
/>
</Choose.Otherwise>
</Choose>
</LoadingIndicator>
<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(
withRouter,
withDialogActions,
withManualJournalsActions,
withManualJournals(
({
manualJournalsCurrentPage,
manualJournalsLoading,
manualJournalsPagination,
manualJournalsTableQuery,
manualJournalsCurrentViewId,
}) => ({
manualJournalsCurrentPage,
manualJournalsLoading,
manualJournalsPagination,
manualJournalsTableQuery,
manualJournalsCurrentViewId,
}),
),
)(ManualJournalsDataTable);

View File

@@ -1,31 +1,26 @@
import React, { useEffect, useState, useMemo, useCallback } from 'react';
import { Route, Switch, useHistory, withRouter } from 'react-router-dom';
import { queryCache, useQuery } from 'react-query';
import { Alert, Intent } from '@blueprintjs/core';
import AppToaster from 'components/AppToaster';
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 DashboardInsider from 'components/Dashboard/DashboardInsider';
import ManualJournalsViewTabs from 'containers/Accounting/ManualJournalsViewTabs';
import ManualJournalsDataTable from 'containers/Accounting/ManualJournalsDataTable';
import ManualJournalsActionsBar from 'containers/Accounting/ManualJournalActionsBar';
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 withManualJournalsActions from 'containers/Accounting/withManualJournalsActions';
import withViewsActions from 'containers/Views/withViewsActions';
import withRouteActions from 'containers/Router/withRouteActions';
import withResourceActions from 'containers/Resources/withResourcesActions';
import { compose } from 'utils';
import 'style/pages/ManualJournal/List.scss';
/**
* Manual journals table.
*/
@@ -33,117 +28,17 @@ function ManualJournalsTable({
// #withDashboardActions
changePageTitle,
// #withViewsActions
requestFetchResourceViews,
// #withResourceActions
requestFetchResourceFields,
// #withManualJournals
manualJournalsTableQuery,
// #withManualJournalsActions
requestFetchManualJournalsTable,
requestDeleteManualJournal,
requestPublishManualJournal,
requestDeleteBulkManualJournals,
addManualJournalsTableQueries,
}) {
const history = useHistory();
const [deleteManualJournal, setDeleteManualJournal] = useState(false);
const [selectedRows, setSelectedRows] = useState([]);
const [bulkDelete, setBulkDelete] = useState(false);
const [publishManualJournal, setPublishManualJournal] = useState(false);
const { formatMessage } = useIntl();
const fetchResourceViews = useQuery(
['resource-views', 'manual-journals'],
() => requestFetchResourceViews('manual_journals'),
);
// const fetchResourceFields = useQuery(
// ['resource-fields', 'manual-journals'],
// () => requestFetchResourceFields('manual_journals'),
// );
const fetchManualJournals = useQuery(
['manual-journals-table', manualJournalsTableQuery],
(key, q) => requestFetchManualJournalsTable(),
);
// Handle update the page title.
useEffect(() => {
changePageTitle(formatMessage({ id: 'manual_journals' }));
}, [changePageTitle, formatMessage]);
// Handle delete manual journal click.
const handleDeleteJournal = useCallback(
(journal) => {
setDeleteManualJournal(journal);
},
[setDeleteManualJournal],
);
// Handle cancel delete manual journal.
const handleCancelManualJournalDelete = useCallback(() => {
setDeleteManualJournal(false);
}, [setDeleteManualJournal]);
// Handle confirm delete manual journal.
const handleConfirmManualJournalDelete = useCallback(() => {
requestDeleteManualJournal(deleteManualJournal.id).then(() => {
AppToaster.show({
message: formatMessage(
{ id: 'the_journal_has_been_deleted_successfully' },
{ number: deleteManualJournal.journal_number },
),
intent: Intent.SUCCESS,
});
setDeleteManualJournal(false);
});
}, [deleteManualJournal, requestDeleteManualJournal, formatMessage]);
// Calculates the selected rows count.
const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [
selectedRows,
]);
const handleBulkDelete = useCallback(
(accountsIds) => {
setBulkDelete(accountsIds);
},
[setBulkDelete],
);
// Handle confirm journals bulk delete.
const handleConfirmBulkDelete = useCallback(() => {
requestDeleteBulkManualJournals(bulkDelete)
.then(() => {
setBulkDelete(false);
AppToaster.show({
message: formatMessage(
{ id: 'the_journals_has_been_deleted_successfully' },
{ count: selectedRowsCount },
),
intent: Intent.SUCCESS,
});
})
.catch((error) => {
setBulkDelete(false);
});
}, [
requestDeleteBulkManualJournals,
bulkDelete,
formatMessage,
selectedRowsCount,
]);
// Handle cancel bulk delete alert.
const handleCancelBulkDelete = useCallback(() => {
setBulkDelete(false);
}, []);
const handleEditJournal = useCallback(
(journal) => {
history.push(`/manual-journals/${journal.id}/edit`);
@@ -154,79 +49,9 @@ function ManualJournalsTable({
// Handle filter change to re-fetch data-table.
const handleFilterChanged = useCallback(() => {}, []);
// Handle view change to re-fetch data table.
// const handleViewChanged = useCallback(() => {
// }, [fetchManualJournals]);
// Handle fetch data of manual jouranls datatable.
const handleFetchData = useCallback(
({ pageIndex, pageSize, sortBy }) => {
const page = pageIndex + 1;
addManualJournalsTableQueries({
...(sortBy.length > 0
? {
column_sort_by: sortBy[0].id,
sort_order: sortBy[0].desc ? 'desc' : 'asc',
}
: {}),
page_size: pageSize,
page,
});
},
[addManualJournalsTableQueries],
);
// Handle publish manual Journal click.
const handlePublishMaunalJournal = useCallback(
(jouranl) => {
setPublishManualJournal(jouranl);
},
[setPublishManualJournal],
);
// Handle cancel manual journal alert.
const handleCancelPublishMaunalJournal = useCallback(() => {
setPublishManualJournal(false);
}, [setPublishManualJournal]);
// Handle publish manual journal confirm.
const handleConfirmPublishManualJournal = useCallback(() => {
requestPublishManualJournal(publishManualJournal.id)
.then(() => {
setPublishManualJournal(false);
AppToaster.show({
message: formatMessage({
id: 'the_manual_journal_has_been_published',
}),
intent: Intent.SUCCESS,
});
queryCache.invalidateQueries('manual-journals-table');
})
.catch((error) => {
setPublishManualJournal(false);
});
}, [publishManualJournal, requestPublishManualJournal, formatMessage]);
// Handle selected rows change.
const handleSelectedRowsChange = useCallback(
(accounts) => {
setSelectedRows(accounts);
},
[setSelectedRows],
);
return (
<DashboardInsider
loading={fetchResourceViews.isFetching}
name={'manual-journals'}
>
<ManualJournalsActionsBar
onBulkDelete={handleBulkDelete}
selectedRows={selectedRows}
onFilterChanged={handleFilterChanged}
/>
<ManualJournalsListProvider query={manualJournalsTableQuery}>
<ManualJournalsActionsBar />
<DashboardPageContent>
<Switch>
@@ -238,72 +63,17 @@ function ManualJournalsTable({
]}
>
<ManualJournalsViewTabs />
<ManualJournalsDataTable
onDeleteJournal={handleDeleteJournal}
onEditJournal={handleEditJournal}
onPublishJournal={handlePublishMaunalJournal}
onSelectedRowsChange={handleSelectedRowsChange}
manualJournalViewLoading={fetchManualJournals.isFetching}
/>
<ManualJournalsDataTable />
<ManualJournalsAlerts />
</Route>
</Switch>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />}
icon="trash"
intent={Intent.DANGER}
isOpen={deleteManualJournal}
onCancel={handleCancelManualJournalDelete}
onConfirm={handleConfirmManualJournalDelete}
>
<p>
<T id={'once_delete_this_journal_you_will_able_to_restore_it'} />
</p>
</Alert>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={
<T id={'delete_count'} values={{ count: selectedRowsCount }} />
}
icon="trash"
intent={Intent.DANGER}
isOpen={bulkDelete}
onCancel={handleCancelBulkDelete}
onConfirm={handleConfirmBulkDelete}
>
<p>
<T
id={'once_delete_these_journals_you_will_not_able_restore_them'}
/>
</p>
</Alert>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'publish'} />}
intent={Intent.WARNING}
isOpen={publishManualJournal}
onCancel={handleCancelPublishMaunalJournal}
onConfirm={handleConfirmPublishManualJournal}
>
<p>
<T id={'are_sure_to_publish_this_manual_journal'} />
</p>
</Alert>
</DashboardPageContent>
</DashboardInsider>
</ManualJournalsListProvider>
);
}
export default compose(
withRouter,
withRouteActions,
withDashboardActions,
withManualJournalsActions,
withViewsActions,
withResourceActions,
withManualJournals(({ manualJournalsTableQuery }) => ({
manualJournalsTableQuery,
})),

View File

@@ -0,0 +1,37 @@
import React, { createContext } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import { useResourceViews, useJournals } from 'hooks/query';
const ManualJournalsContext = createContext();
function ManualJournalsListProvider({ query, ...props }) {
// Fetches accounts resource views and fields.
const { data: journalsViews, isFetching: isViewsLoading } = useResourceViews(
'manual_journals',
);
// Fetches the manual journals transactions with pagination meta.
const {
data: { manualJournals },
isFetching: isManualJournalsLoading,
} = useJournals(query);
// Global state.
const state = {
manualJournals,
journalsViews,
isManualJournalsLoading,
isViewsLoading,
};
return (
<DashboardInsider loading={isViewsLoading} name={'manual-journals'}>
<ManualJournalsContext.Provider value={state} {...props} />
</DashboardInsider>
);
}
const useManualJournalsContext = () => React.useContext(ManualJournalsContext);
export { ManualJournalsListProvider, useManualJournalsContext };

View File

@@ -1,61 +1,38 @@
import React, { useEffect } from 'react';
import { useHistory } from 'react-router';
import React from 'react';
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
import { useParams, withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { useParams } from 'react-router-dom';
import { pick } from 'lodash';
import { DashboardViewsTabs, Icon } from 'components';
import { DashboardViewsTabs } from 'components';
import withManualJournals from './withManualJournals';
import { useManualJournalsContext } from './ManualJournalsListProvider';
import withManualJournalsActions from './withManualJournalsActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withViewDetail from 'containers/Views/withViewDetails';
import { compose } from 'utils';
import { compose, saveInvoke } from 'utils';
/**
* Manual journal views tabs.
*/
function ManualJournalsViewTabs({
// #withViewDetail
viewId,
viewItem,
// #withManualJournals
manualJournalsViews,
// #withManualJournalsActions
addManualJournalsTableQueries,
changeManualJournalCurrentView,
// #withDashboardActions
setTopbarEditView,
changePageSubtitle,
// #ownProps
onViewChanged,
}) {
const { custom_view_id: customViewId } = useParams();
const { journalsViews } = useManualJournalsContext();
useEffect(() => {
setTopbarEditView(customViewId);
changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
}, [customViewId]);
const tabs = manualJournalsViews.map((view) => ({
const tabs = journalsViews.map((view) => ({
...pick(view, ['name', 'id']),
}));
const handleClickNewView = () => {};
const handleTabChange = (viewId) => {
changeManualJournalCurrentView(viewId || -1);
addManualJournalsTableQueries({
custom_view_id: viewId || null,
});
};
return (
<Navbar className="navbar--dashboard-views">
<NavbarGroup align={Alignment.LEFT}>
@@ -71,19 +48,7 @@ function ManualJournalsViewTabs({
);
}
const mapStateToProps = (state, ownProps) => ({
viewId: parseInt(ownProps.match.params.custom_view_id, 10),
});
const withManualJournalsViewTabs = connect(mapStateToProps);
export default compose(
withRouter,
withManualJournalsViewTabs,
withManualJournals(({ manualJournalsViews }) => ({
manualJournalsViews,
})),
withManualJournalsActions,
withDashboardActions,
withViewDetail(),
)(ManualJournalsViewTabs);

View File

@@ -1,10 +1,31 @@
import React from 'react';
import { Intent, Classes, Tooltip, Position, Tag, Button } from '@blueprintjs/core';
import {
Intent,
Classes,
Tooltip,
Position,
Tag,
Button,
} from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl';
import moment from 'moment';
import { Choose, Money, If, Icon, Hint } from 'components';
import withAccountDetails from 'containers/Accounts/withAccountDetail';
import { compose } from 'utils';
// Amount accessor.
export const AmountAccessor = (r) => (
<Tooltip
content={<AmountPopoverContent journalEntries={r.entries} />}
position={Position.RIGHT_TOP}
boundary={'viewport'}
>
<Money amount={r.amount} currency={'USD'} />
</Tooltip>
);
const AmountPopoverContentLineRender = ({
journalEntry,
accountId,
@@ -77,6 +98,13 @@ export const StatusAccessor = (row) => {
);
};
/**
* Date accessor.
*/
export const DateAccessor = (row) => {
return moment(row.date).format('YYYY MMM DD');
};
/**
* Note column accessor.
*/
@@ -156,7 +184,11 @@ export const TotalCreditDebitCellRenderer = (chainedComponent, type) => (
return computed;
}, 0);
return <span><Money amount={total} currency={'USD'} /></span>;
return (
<span>
<Money amount={total} currency={'USD'} />
</span>
);
}
return chainedComponent(props);
};