WIP / Fix_ExchangeRate / localize

This commit is contained in:
elforjani3
2020-05-11 04:12:23 +02:00
parent cceb4786c2
commit 98edc66dd2
20 changed files with 636 additions and 413 deletions

View File

@@ -21,14 +21,19 @@ import MediaConnect from 'connectors/Media.connect';
import useMedia from 'hooks/useMedia'; import useMedia from 'hooks/useMedia';
import {compose} from 'utils'; import {compose} from 'utils';
import { FormattedMessage as T, useIntl } from 'react-intl';
function MakeJournalEntriesForm({ function MakeJournalEntriesForm({
// #withMedia
requestSubmitMedia, requestSubmitMedia,
requestDeleteMedia, requestDeleteMedia,
// #withJournalsActions
requestMakeJournalEntries, requestMakeJournalEntries,
requestEditManualJournal, requestEditManualJournal,
// #withDashboard
changePageTitle, changePageTitle,
changePageSubtitle, changePageSubtitle,
@@ -47,13 +52,14 @@ function MakeJournalEntriesForm({
const savedMediaIds = useRef([]); const savedMediaIds = useRef([]);
const clearSavedMediaIds = () => { savedMediaIds.current = []; } const clearSavedMediaIds = () => { savedMediaIds.current = []; }
const {formatMessage} =useIntl()
useEffect(() => { useEffect(() => {
if (manualJournal && manualJournal.id) { if (manualJournal && manualJournal.id) {
changePageTitle('Edit Journal'); changePageTitle(formatMessage({id:'edit_journal'}));
changePageSubtitle(`No. ${manualJournal.journal_number}`); changePageSubtitle(`No. ${manualJournal.journal_number}`);
} else { } else {
changePageTitle('New Journal'); changePageTitle(formatMessage({id:'new_journal'}));
} }
}, [changePageTitle, changePageSubtitle, manualJournal]); }, [changePageTitle, changePageSubtitle, manualJournal]);
@@ -252,8 +258,6 @@ function MakeJournalEntriesForm({
} }
export default compose( export default compose(
// ManualJournalsConnect,
// MakeJournalEntriesConnect,
withJournalsActions, withJournalsActions,
withManualJournalDetail, withManualJournalDetail,
withAccountsActions, withAccountsActions,

View File

@@ -22,22 +22,24 @@ import { compose } from 'utils';
* Manual journals table. * Manual journals table.
*/ */
function ManualJournalsTable({ function ManualJournalsTable({
// #withDashboardActions
changePageTitle, changePageTitle,
// #withViewsActions
requestFetchResourceViews, requestFetchResourceViews,
// #withManualJournalsActions
requestFetchManualJournalsTable, requestFetchManualJournalsTable,
requestDeleteManualJournal, requestDeleteManualJournal,
requestPublishManualJournal, requestPublishManualJournal,
requestDeleteBulkManualJournals, requestDeleteBulkManualJournals,
addManualJournalsTableQueries, addManualJournalsTableQueries,
}) { }) {
const history = useHistory(); const history = useHistory();
const [deleteManualJournal, setDeleteManualJournal] = useState(false); const [deleteManualJournal, setDeleteManualJournal] = useState(false);
const [selectedRows, setSelectedRows] = useState([]); const [selectedRows, setSelectedRows] = useState([]);
const [bulkDelete, setBulkDelete] = useState(false); const [bulkDelete, setBulkDelete] = useState(false);
const { formatMessage } = useIntl();
const fetchViews = useQuery('journals-resource-views', () => { const fetchViews = useQuery('journals-resource-views', () => {
return requestFetchResourceViews('manual_journals'); return requestFetchResourceViews('manual_journals');
}); });
@@ -47,7 +49,7 @@ function ManualJournalsTable({
); );
useEffect(() => { useEffect(() => {
changePageTitle('Manual Journals'); changePageTitle(formatMessage({id:'manual_journals'}));
}, [changePageTitle]); }, [changePageTitle]);
// Handle delete manual journal click. // Handle delete manual journal click.
@@ -163,9 +165,7 @@ function ManualJournalsTable({
'/dashboard/accounting/manual-journals/:custom_view_id/custom_view', '/dashboard/accounting/manual-journals/:custom_view_id/custom_view',
'/dashboard/accounting/manual-journals', '/dashboard/accounting/manual-journals',
]} ]}
> ></Route>
<ManualJournalsViewTabs onViewChanged={handleViewChanged} />
</Route>
</Switch> </Switch>
<ManualJournalsDataTable <ManualJournalsDataTable

View File

@@ -1,10 +1,7 @@
import React, { useEffect, useState, useCallback } from 'react'; import React, { useEffect, useState, useCallback } from 'react';
import { import { Route, Switch } from 'react-router-dom';
Route,
Switch,
} from 'react-router-dom';
import { Alert, Intent } from '@blueprintjs/core'; import { Alert, Intent } from '@blueprintjs/core';
import { useQuery } from 'react-query' import { useQuery } from 'react-query';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
@@ -26,7 +23,6 @@ import { compose } from 'utils';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
function AccountsChart({ function AccountsChart({
// #withDashboard // #withDashboard
changePageTitle, changePageTitle,
@@ -54,7 +50,7 @@ function AccountsChart({
const [selectedRows, setSelectedRows] = useState([]); const [selectedRows, setSelectedRows] = useState([]);
const [tableLoading, setTableLoading] = useState(false); const [tableLoading, setTableLoading] = useState(false);
const { formatMessage } = useIntl();
// Fetch accounts resource views and fields. // Fetch accounts resource views and fields.
const fetchHook = useQuery('resource-accounts', () => { const fetchHook = useQuery('resource-accounts', () => {
return Promise.all([ return Promise.all([
@@ -64,38 +60,49 @@ function AccountsChart({
}); });
// Fetch accounts list according to the given custom view id. // Fetch accounts list according to the given custom view id.
const fetchAccountsHook = useQuery(['accounts-table', accountsTableQuery], const fetchAccountsHook = useQuery(
() => requestFetchAccountsTable()); ['accounts-table', accountsTableQuery],
() => requestFetchAccountsTable(),
{ refetchInterval: 3000 }
);
useEffect(() => { useEffect(() => {
changePageTitle('Chart of Accounts'); changePageTitle(formatMessage({ id: 'chart_of_accounts' }));
}, [changePageTitle]); }, [changePageTitle]);
// Handle click and cancel/confirm account delete // Handle click and cancel/confirm account delete
const handleDeleteAccount = (account) => { setDeleteAccount(account); }; const handleDeleteAccount = (account) => {
setDeleteAccount(account);
};
// handle cancel delete account alert. // handle cancel delete account alert.
const handleCancelAccountDelete = useCallback(() => { setDeleteAccount(false); }, []); const handleCancelAccountDelete = useCallback(() => {
setDeleteAccount(false);
}, []);
// Handle confirm account delete // Handle confirm account delete
const handleConfirmAccountDelete = useCallback(() => { const handleConfirmAccountDelete = useCallback(() => {
requestDeleteAccount(deleteAccount.id).then(() => { requestDeleteAccount(deleteAccount.id)
setDeleteAccount(false); .then(() => {
AppToaster.show({ message: 'the_account_has_been_deleted' }); setDeleteAccount(false);
}).catch(errors => { AppToaster.show({ message: 'the_account_has_been_deleted' });
setDeleteAccount(false); })
if (errors.find((e) => e.type === 'ACCOUNT.PREDEFINED')) { .catch((errors) => {
AppToaster.show({ setDeleteAccount(false);
message: 'cannot_delete_predefined_account', if (errors.find((e) => e.type === 'ACCOUNT.PREDEFINED')) {
intent: Intent.DANGER, AppToaster.show({
}); message: 'cannot_delete_predefined_account',
} intent: Intent.DANGER,
if (errors.find((e) => e.type === 'ACCOUNT.HAS.ASSOCIATED.TRANSACTIONS')) { });
AppToaster.show({ }
message: 'cannot_delete_account_has_associated_transactions' if (
}); errors.find((e) => e.type === 'ACCOUNT.HAS.ASSOCIATED.TRANSACTIONS')
} ) {
}); AppToaster.show({
message: 'cannot_delete_account_has_associated_transactions',
});
}
});
}, [deleteAccount, requestDeleteAccount]); }, [deleteAccount, requestDeleteAccount]);
// Handle cancel/confirm account inactive. // Handle cancel/confirm account inactive.
@@ -117,43 +124,44 @@ function AccountsChart({
}); });
}, [inactiveAccount, requestFetchAccountsTable, requestInactiveAccount]); }, [inactiveAccount, requestFetchAccountsTable, requestInactiveAccount]);
const handleEditAccount = (account) => {};
const handleEditAccount = (account) => { const handleRestoreAccount = (account) => {};
}; const handleBulkDelete = useCallback(
(accountsIds) => {
const handleRestoreAccount = (account) => { setBulkDelete(accountsIds);
},
}; [setBulkDelete]
);
const handleBulkDelete = useCallback((accountsIds) => {
setBulkDelete(accountsIds);
}, [setBulkDelete]);
const handleConfirmBulkDelete = useCallback(() => { const handleConfirmBulkDelete = useCallback(() => {
requestDeleteBulkAccounts(bulkDelete).then(() => { requestDeleteBulkAccounts(bulkDelete)
setBulkDelete(false); .then(() => {
AppToaster.show({ message: 'the_accounts_have_been_deleted' }); setBulkDelete(false);
}).catch((error) => { AppToaster.show({ message: 'the_accounts_have_been_deleted' });
setBulkDelete(false); })
}); .catch((error) => {
setBulkDelete(false);
});
}, [requestDeleteBulkAccounts, bulkDelete]); }, [requestDeleteBulkAccounts, bulkDelete]);
const handleCancelBulkDelete = useCallback(() => { const handleCancelBulkDelete = useCallback(() => {
setBulkDelete(false); setBulkDelete(false);
}, []); }, []);
const handleBulkArchive = useCallback((accounts) => { const handleBulkArchive = useCallback((accounts) => {}, []);
}, []);
// Handle selected rows change. // Handle selected rows change.
const handleSelectedRowsChange = useCallback((accounts) => { const handleSelectedRowsChange = useCallback(
setSelectedRows(accounts); (accounts) => {
}, [setSelectedRows]); setSelectedRows(accounts);
},
[setSelectedRows]
);
// Refetches accounts data table when current custom view changed. // Refetches accounts data table when current custom view changed.
const handleFilterChanged = useCallback(() => { const handleFilterChanged = useCallback(() => {
fetchAccountsHook.refetch(); fetchAccountsHook.refetch();
}, [fetchAccountsHook]); }, [fetchAccountsHook]);
@@ -169,25 +177,29 @@ function AccountsChart({
}, [tableLoading, fetchAccountsHook.isFetching]); }, [tableLoading, fetchAccountsHook.isFetching]);
// Handle fetch data of accounts datatable. // Handle fetch data of accounts datatable.
const handleFetchData = useCallback(({ pageIndex, pageSize, sortBy }) => { const handleFetchData = useCallback(
addAccountsTableQueries({ ({ pageIndex, pageSize, sortBy }) => {
...(sortBy.length > 0) ? { addAccountsTableQueries({
column_sort_by: sortBy[0].id, ...(sortBy.length > 0
sort_order: sortBy[0].desc ? 'desc' : 'asc', ? {
} : {}, column_sort_by: sortBy[0].id,
}); sort_order: sortBy[0].desc ? 'desc' : 'asc',
fetchAccountsHook.refetch(); }
}, [fetchAccountsHook, addAccountsTableQueries]); : {}),
});
fetchAccountsHook.refetch();
},
[fetchAccountsHook, addAccountsTableQueries]
);
return ( return (
<DashboardInsider <DashboardInsider loading={fetchHook.isFetching} name={'accounts-chart'}>
loading={fetchHook.isFetching}
name={'accounts-chart'}>
<DashboardActionsBar <DashboardActionsBar
selectedRows={selectedRows} selectedRows={selectedRows}
onFilterChanged={handleFilterChanged} onFilterChanged={handleFilterChanged}
onBulkDelete={handleBulkDelete} onBulkDelete={handleBulkDelete}
onBulkArchive={handleBulkArchive} /> onBulkArchive={handleBulkArchive}
/>
<DashboardPageContent> <DashboardPageContent>
<Switch> <Switch>
@@ -196,9 +208,9 @@ function AccountsChart({
path={[ path={[
'/dashboard/accounts/:custom_view_id/custom_view', '/dashboard/accounts/:custom_view_id/custom_view',
'/dashboard/accounts', '/dashboard/accounts',
]}> ]}
<AccountsViewsTabs >
onViewChanged={handleViewChanged} /> <AccountsViewsTabs onViewChanged={handleViewChanged} />
<AccountsDataTable <AccountsDataTable
onDeleteAccount={handleDeleteAccount} onDeleteAccount={handleDeleteAccount}
@@ -207,49 +219,53 @@ function AccountsChart({
onEditAccount={handleEditAccount} onEditAccount={handleEditAccount}
onFetchData={handleFetchData} onFetchData={handleFetchData}
onSelectedRowsChange={handleSelectedRowsChange} onSelectedRowsChange={handleSelectedRowsChange}
loading={tableLoading} /> loading={tableLoading}
/>
</Route> </Route>
</Switch> </Switch>
<Alert <Alert
cancelButtonText="Cancel" cancelButtonText='Cancel'
confirmButtonText="Move to Trash" confirmButtonText='Move to Trash'
icon="trash" icon='trash'
intent={Intent.DANGER} intent={Intent.DANGER}
isOpen={deleteAccount} isOpen={deleteAccount}
onCancel={handleCancelAccountDelete} onCancel={handleCancelAccountDelete}
onConfirm={handleConfirmAccountDelete}> onConfirm={handleConfirmAccountDelete}
>
<p> <p>
Are you sure you want to move <b>filename</b> to Trash? You will be able to restore it later, Are you sure you want to move <b>filename</b> to Trash? You will be
but it will become private to you. able to restore it later, but it will become private to you.
</p> </p>
</Alert> </Alert>
<Alert <Alert
cancelButtonText={<T id={'cancel'}/>} cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'inactivate'}/>} confirmButtonText={<T id={'inactivate'} />}
icon="trash" icon='trash'
intent={Intent.WARNING} intent={Intent.WARNING}
isOpen={inactiveAccount} isOpen={inactiveAccount}
onCancel={handleCancelInactiveAccount} onCancel={handleCancelInactiveAccount}
onConfirm={handleConfirmAccountActive}> onConfirm={handleConfirmAccountActive}
>
<p> <p>
Are you sure you want to move <b>filename</b> to Trash? You will be able to restore it later, Are you sure you want to move <b>filename</b> to Trash? You will be
but it will become private to you. able to restore it later, but it will become private to you.
</p> </p>
</Alert> </Alert>
<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={bulkDelete} isOpen={bulkDelete}
onCancel={handleCancelBulkDelete} onCancel={handleCancelBulkDelete}
onConfirm={handleConfirmBulkDelete}> onConfirm={handleConfirmBulkDelete}
>
<p> <p>
Are you sure you want to move <b>filename</b> to Trash? You will be able to restore it later, Are you sure you want to move <b>filename</b> to Trash? You will be
but it will become private to you. able to restore it later, but it will become private to you.
</p> </p>
</Alert> </Alert>
</DashboardPageContent> </DashboardPageContent>
@@ -263,6 +279,7 @@ export default compose(
withViewsActions, withViewsActions,
withResourceActions, withResourceActions,
withDashboardActions, withDashboardActions,
withAccounts(({ accountsTableQuery }) => ({
withAccounts, accountsTableQuery,
)(AccountsChart); }))
)(AccountsChart);

View File

@@ -21,16 +21,15 @@ import withDashboardActions from 'containers/Dashboard/withDashboard';
import withAccountsActions from 'containers/Accounts/withAccountsActions'; import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withAccounts from 'containers/Accounts/withAccounts'; import withAccounts from 'containers/Accounts/withAccounts';
import {If} from 'components';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
function AccountsDataTable({ function AccountsDataTable({
// # withAccounts // #withAccounts
accounts, accounts,
accountsLoading, accountsLoading,
// # withDialog. // #withDialog.
openDialog, openDialog,
// own properties // own properties
@@ -58,7 +57,7 @@ function AccountsDataTable({
const actionMenuList = useCallback((account) => ( const actionMenuList = useCallback((account) => (
<Menu> <Menu>
<MenuItem text='View Details' /> <MenuItem text={<T id={'view_details'}/>} />
<MenuDivider /> <MenuDivider />
<MenuItem <MenuItem
text={<T id={'edit_account'}/>} text={<T id={'edit_account'}/>}
@@ -166,9 +165,6 @@ function AccountsDataTable({
return ( return (
<LoadingIndicator loading={loading} mount={false}> <LoadingIndicator loading={loading} mount={false}>
<If condition={loading}>
asdasdsadsa
</If>
<DataTable <DataTable
noInitialFetch={true} noInitialFetch={true}
columns={columns} columns={columns}
@@ -189,5 +185,8 @@ export default compose(
DialogConnect, DialogConnect,
withDashboardActions, withDashboardActions,
withAccountsActions, withAccountsActions,
withAccounts, withAccounts(({ accountsLoading, accounts }) => ({
accountsLoading,
accounts,
})),
)(AccountsDataTable); )(AccountsDataTable);

View File

@@ -13,32 +13,36 @@ import * as Yup from 'yup';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import Dialog from 'components/Dialog'; import Dialog from 'components/Dialog';
import AppToaster from 'components/AppToaster';
import { useQuery, queryCache } from 'react-query'; import { useQuery, queryCache } from 'react-query';
import AppToaster from 'components/AppToaster';
import ErrorMessage from 'components/ErrorMessage'; import ErrorMessage from 'components/ErrorMessage';
import classNames from 'classnames'; import classNames from 'classnames';
import { Select } from '@blueprintjs/select'; import { Select } from '@blueprintjs/select';
import moment from 'moment'; import moment from 'moment';
import { DateInput } from '@blueprintjs/datetime'; import { DateInput } from '@blueprintjs/datetime';
import { momentFormatter } from 'utils'; import { momentFormatter } from 'utils';
import ExchangeRatesDialogConnect from 'connectors/ExchangeRatesFromDialog.connect';
import CurrencyFromDialogConnect from 'connectors/CurrencyFromDialog.connect'
import withExchangeRatesDialog from 'containers/FinancialStatements/ExchangeRates/withExchangeRateDialog';
import withExchangeRate from 'containers/FinancialStatements/ExchangeRates/withExchangeRates'
function ExchangeRateDialog({ function ExchangeRateDialog({
name, name,
payload, payload,
isOpen, isOpen,
openDialog, // #withDialog
closeDialog, closeDialog,
currencies, currencies,
// #withExchangeRatesActions
requestSubmitExchangeRate, requestSubmitExchangeRate,
requestFetchExchangeRates, requestFetchExchangeRates,
requestEditExchangeRate, requestEditExchangeRate,
requestFetchCurrencies, requestFetchCurrencies,
editExchangeRate, editExchangeRate,
addExchangeRatesTableQueries,
}) { }) {
const {formatMessage} = useIntl(); const {formatMessage} = useIntl();
@@ -107,9 +111,14 @@ function ExchangeRateDialog({
closeDialog(name); closeDialog(name);
}, [name, closeDialog]); }, [name, closeDialog]);
const fetchHook = useQuery('exchange-rates-dialog', () => { // const fetchHook = useQuery('exchange-rates-dialog', () => {
return Promise.all([requestFetchExchangeRates(), requestFetchCurrencies()]); // return Promise.all([requestFetchExchangeRates(), requestFetchCurrencies()]);
}); // });
const fetchExchangeRatesDialog = useQuery('exchange-rates-dialog',
() => requestFetchExchangeRates(),
{ refetchInterval: 3000 });
const onDialogClosed = useCallback(() => { const onDialogClosed = useCallback(() => {
formik.resetForm(); formik.resetForm();
@@ -117,8 +126,8 @@ function ExchangeRateDialog({
}, [formik, closeDialog, name]); }, [formik, closeDialog, name]);
const onDialogOpening = useCallback(() => { const onDialogOpening = useCallback(() => {
fetchHook.refetch(); fetchExchangeRatesDialog.refetch();
}, [fetchHook]); }, [fetchExchangeRatesDialog]);
const handleDateChange = useCallback( const handleDateChange = useCallback(
(date) => { (date) => {
@@ -185,14 +194,14 @@ function ExchangeRateDialog({
} }
className={classNames( className={classNames(
{ {
'dialog--loading': fetchHook.pending, 'dialog--loading': fetchExchangeRatesDialog.pending,
}, },
'dialog--exchangeRate-form' 'dialog--exchangeRate-form'
)} )}
isOpen={isOpen} isOpen={isOpen}
onClosed={onDialogClosed} onClosed={onDialogClosed}
onOpening={onDialogOpening} onOpening={onDialogOpening}
isLoading={fetchHook.pending} isLoading={fetchExchangeRatesDialog.pending}
onClose={handleClose} onClose={handleClose}
> >
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
@@ -279,4 +288,4 @@ function ExchangeRateDialog({
); );
} }
export default ExchangeRatesDialogConnect(ExchangeRateDialog); export default withExchangeRatesDialog(ExchangeRateDialog);

View File

@@ -4,6 +4,7 @@ import {useParams} from 'react-router-dom';
import Connector from 'connectors/ExpenseForm.connector'; import Connector from 'connectors/ExpenseForm.connector';
import DashboardInsider from 'components/Dashboard/DashboardInsider'; import DashboardInsider from 'components/Dashboard/DashboardInsider';
import ExpenseForm from 'components/Expenses/ExpenseForm'; import ExpenseForm from 'components/Expenses/ExpenseForm';
import { FormattedMessage as T, useIntl } from 'react-intl';
function ExpenseFormContainer({ function ExpenseFormContainer({
fetchAccounts, fetchAccounts,
@@ -15,12 +16,12 @@ function ExpenseFormContainer({
currencies, currencies,
}) { }) {
const { id } = useParams(); const { id } = useParams();
const { formatMessage } = useIntl();
useEffect(() => { useEffect(() => {
if (id) { if (id) {
changePageTitle('Edit Expense Details'); changePageTitle(formatMessage({id:'edit_expense_details'}));
} else { } else {
changePageTitle('New Expense'); changePageTitle(formatMessage({id:'new_expense'}));
} }
}, []); }, []);

View File

@@ -18,8 +18,9 @@ function ExpensesList({
getResourceViews, getResourceViews,
changePageTitle changePageTitle
}) { }) {
const {formatMessage} =useIntl()
useEffect(() => { useEffect(() => {
changePageTitle('Expenses List'); changePageTitle(formatMessage({id:'expenses_list'}));
}, []); }, []);
const [deleteExpenseState, setDeleteExpense] = useState(); const [deleteExpenseState, setDeleteExpense] = useState();

View File

@@ -18,9 +18,12 @@ import { compose } from 'utils';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
function ExchangeRate({ function ExchangeRate({
views, // #withDashboard
changePageTitle, changePageTitle,
//#withExchangeRatesActions
requestFetchResourceFields, requestFetchResourceFields,
requestFetchExchangeRates, requestFetchExchangeRates,
requestDeleteExchangeRate, requestDeleteExchangeRate,
addExchangeRatesTableQueries, addExchangeRatesTableQueries,
@@ -28,15 +31,21 @@ function ExchangeRate({
const { id } = useParams(); const { id } = useParams();
const [deleteExchangeRate, setDeleteExchangeRate] = useState(false); const [deleteExchangeRate, setDeleteExchangeRate] = useState(false);
const [selectedRows, setSelectedRows] = useState([]); const [selectedRows, setSelectedRows] = useState([]);
const { formatMessage } = useIntl();
// const fetchExchangeRates = useQuery('exchange-rates-table', () => {
// return Promise.all([requestFetchExchangeRates()]);
// });
const fetchExchangeRates = useQuery('exchange-rates-table',
() => requestFetchExchangeRates(),
{ refetchInterval: 3000 });
const fetchHook = useQuery('exchange-rates', () => {
return Promise.all([requestFetchExchangeRates()]);
});
useEffect(() => { useEffect(() => {
id id
? changePageTitle('Exchange Rate Details') ? changePageTitle(formatMessage({id:'exchange_rate_details'}))
: changePageTitle('Exchange Rate List'); : changePageTitle(formatMessage({id:'exchange_rate_list'}));
}, [id, changePageTitle]); }, [id, changePageTitle]);
const handelDeleteExchangeRate = useCallback( const handelDeleteExchangeRate = useCallback(
@@ -84,9 +93,8 @@ function ExchangeRate({
); );
return ( return (
<DashboardInsider loading={fetchHook.pending}> <DashboardInsider>
<ExchangeRateActionsBar <ExchangeRateActionsBar
views={views}
onDeleteExchangeRate={handelDeleteExchangeRate} onDeleteExchangeRate={handelDeleteExchangeRate}
selectedRows={selectedRows} selectedRows={selectedRows}
/> />
@@ -98,8 +106,8 @@ function ExchangeRate({
onSelectedRowsChange={handleSelectedRowsChange} onSelectedRowsChange={handleSelectedRowsChange}
/> />
<Alert <Alert
cancelButtonText={<T id={'cancel'}/>} cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'move_to_trash'}/>} confirmButtonText={<T id={'move_to_trash'} />}
icon='trash' icon='trash'
intent={Intent.DANGER} intent={Intent.DANGER}
isOpen={deleteExchangeRate} isOpen={deleteExchangeRate}
@@ -118,5 +126,6 @@ function ExchangeRate({
export default compose( export default compose(
withExchangeRatesActions, withExchangeRatesActions,
withResourceActions,
withDashboardActions withDashboardActions
)(ExchangeRate); )(ExchangeRate);

View File

@@ -1,6 +1,4 @@
import React, { useCallback, useState, useMemo } from 'react'; import React, { useCallback, useState, useMemo } from 'react';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import { compose } from 'utils';
import { import {
NavbarGroup, NavbarGroup,
Button, Button,
@@ -10,37 +8,39 @@ import {
Position, Position,
PopoverInteractionKind, PopoverInteractionKind,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { connect } from 'react-redux';
import classNames from 'classnames'; import classNames from 'classnames';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import DashboardConnect from 'connectors/Dashboard.connector'; import { connect } from 'react-redux';
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
import DialogConnect from 'connectors/Dialog.connector';
import FilterDropdown from 'components/FilterDropdown'; import FilterDropdown from 'components/FilterDropdown';
import ExchangeRatesDialogConnect from 'connectors/ExchangeRatesFromDialog.connect';
import withResourceDetail from 'containers/Resources/withResourceDetails'; import withResourceDetail from 'containers/Resources/withResourceDetails';
import withExchangeRates from 'containers/FinancialStatements/ExchangeRates/withExchangeRates';
import withExchangeRatesActions from 'containers/FinancialStatements/ExchangeRates/withExchangeRatesActions';
import withExchangeRateDialog from 'containers/FinancialStatements/ExchangeRates/withExchangeRateDialog';
import { compose } from 'utils';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
function ExchangeRateActionsBar({ function ExchangeRateActionsBar({
// #withDialog.
openDialog, openDialog,
// #withResourceDetail
resourceFields,
selectedRows = [],
onDeleteExchangeRate, onDeleteExchangeRate,
onFilterChanged, onFilterChanged,
resourceFields,
selectedRows = [],
}) { }) {
const onClickNewExchangeRate = useCallback(() => {
openDialog('exchangeRate-form', {});
}, [openDialog]);
const handelDeleteExchangeRate = useCallback(
(exchangeRate) => {
onDeleteExchangeRate(exchangeRate);
},
[selectedRows, onDeleteExchangeRate]
);
const [filterCount, setFilterCount] = useState(0); const [filterCount, setFilterCount] = useState(0);
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [
selectedRows, const onClickNewExchangeRate = () => {
]); openDialog('exchangeRate-form', {});
};
const filterDropdown = FilterDropdown({ const filterDropdown = FilterDropdown({
fields: resourceFields, fields: resourceFields,
@@ -51,13 +51,24 @@ function ExchangeRateActionsBar({
}, },
}); });
const handelDeleteExchangeRate = useCallback(
(exchangeRate) => {
onDeleteExchangeRate(exchangeRate);
},
[selectedRows, onDeleteExchangeRate]
);
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [
selectedRows,
]);
return ( return (
<DashboardActionsBar> <DashboardActionsBar>
<NavbarGroup> <NavbarGroup>
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon='plus' />} icon={<Icon icon='plus' />}
text={<T id={'new_exchange_rate'}/>} text={<T id={'new_exchange_rate'} />}
onClick={onClickNewExchangeRate} onClick={onClickNewExchangeRate}
/> />
<Popover <Popover
@@ -69,7 +80,11 @@ function ExchangeRateActionsBar({
<Button <Button
className={classNames(Classes.MINIMAL, 'button--filter')} className={classNames(Classes.MINIMAL, 'button--filter')}
text={ text={
filterCount <= 0 ? <T id={'filter'}/> : `${filterCount} filters applied` filterCount <= 0 ? (
<T id={'filter'} />
) : (
`${filterCount} filters applied`
)
} }
icon={<Icon icon='filter' />} icon={<Icon icon='filter' />}
/> />
@@ -78,7 +93,7 @@ function ExchangeRateActionsBar({
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon='trash' iconSize={15} />} icon={<Icon icon='trash' iconSize={15} />}
text={<T id={'delete'}/>} text={<T id={'delete'} />}
intent={Intent.DANGER} intent={Intent.DANGER}
onClick={handelDeleteExchangeRate} onClick={handelDeleteExchangeRate}
/> />
@@ -86,12 +101,12 @@ function ExchangeRateActionsBar({
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon='file-import' />} icon={<Icon icon='file-import' />}
text={<T id={'import'}/>} text={<T id={'import'} />}
/> />
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon='file-export' />} icon={<Icon icon='file-export' />}
text={<T id={'export'}/>} text={<T id={'export'} />}
/> />
</NavbarGroup> </NavbarGroup>
</DashboardActionsBar> </DashboardActionsBar>
@@ -106,7 +121,8 @@ const withExchangeRateActionBar = connect(mapStateToProps);
export default compose( export default compose(
withExchangeRateActionBar, withExchangeRateActionBar,
DashboardConnect, DialogConnect,
ExchangeRatesDialogConnect, withResourceDetail(({ resourceFields }) => ({
withResourceDetail resourceFields,
}))
)(ExchangeRateActionsBar); )(ExchangeRateActionsBar);

View File

@@ -1,41 +1,40 @@
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo,useState } from 'react';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import DialogConnect from 'connectors/Dialog.connector'; import DialogConnect from 'connectors/Dialog.connector';
import LoadingIndicator from 'components/LoadingIndicator'; import LoadingIndicator from 'components/LoadingIndicator';
import DataTable from 'components/DataTable'; import DataTable from 'components/DataTable';
import { Button, Popover, Menu, MenuItem, Position } from '@blueprintjs/core'; import { Button, Popover, Menu, MenuItem, Position } from '@blueprintjs/core';
import withExchangeRates from 'containers/FinancialStatements/ExchangeRates/withExchangeRates';
import withExchangeRatesActions from 'containers/FinancialStatements/ExchangeRates/withExchangeRatesActions'; import withExchangeRatesActions from 'containers/FinancialStatements/ExchangeRates/withExchangeRatesActions';
import withExchangeRates from 'containers/FinancialStatements/ExchangeRates/withExchangeRates';
import { compose } from 'utils'; import { compose } from 'utils';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
function ExchangeRateTable({ function ExchangeRateTable({
// #withExchangeRates
exchangeRatesList, exchangeRatesList,
onFetchData, exchangeRatesLoading,
// #withDialog.
openDialog, openDialog,
// own properties
loading,
onFetchData,
onDeleteExchangeRate, onDeleteExchangeRate,
onEditExchangeRate, onEditExchangeRate,
onSelectedRowsChange, onSelectedRowsChange,
}) { }) {
const {formatMessage} = useIntl(); const [initialMount, setInitialMount] = useState(false);
const { formatMessage } = useIntl();
const handelEditExchangeRate = (exchange_rate) => () => { const handelEditExchangeRate = (exchange_rate) => () => {
openDialog('exchangeRate-form', { action: 'edit', id: exchange_rate.id }); openDialog('exchangeRate-form', { action: 'edit', id: exchange_rate.id });
onEditExchangeRate(exchange_rate.id); onEditExchangeRate(exchange_rate.id);
}; };
// const handelEditExchangeRate = useCallback(
// (exchange_rate) => () => {
// openDialog('exchangeRate-form', { action: 'edit', id: exchange_rate.id });
// onEditExchangeRate(exchange_rate.id);
// },
// [openDialog]
// );
const handleDeleteExchangeRate = (exchange_rate) => () => { const handleDeleteExchangeRate = (exchange_rate) => () => {
onDeleteExchangeRate(exchange_rate); onDeleteExchangeRate(exchange_rate);
@@ -45,36 +44,36 @@ function ExchangeRateTable({
(ExchangeRate) => ( (ExchangeRate) => (
<Menu> <Menu>
<MenuItem <MenuItem
text={<T id={'edit_exchange_rate'}/>} text={<T id={'edit_exchange_rate'} />}
onClick={handelEditExchangeRate(ExchangeRate)} onClick={handelEditExchangeRate(ExchangeRate)}
/> />
<MenuItem <MenuItem
text={<T id={'delete_exchange_rate'}/>} text={<T id={'delete_exchange_rate'} />}
onClick={handleDeleteExchangeRate(ExchangeRate)} onClick={handleDeleteExchangeRate(ExchangeRate)}
/> />
</Menu> </Menu>
), ),
[] [handelEditExchangeRate, handleDeleteExchangeRate]
); );
const columns = useMemo( const columns = useMemo(
() => [ () => [
{ {
id: 'date', id: 'date',
Header: formatMessage({id:'date'}), Header: formatMessage({ id: 'date' }),
// accessor: 'date', // accessor: 'date',
width: 150, width: 150,
}, },
{ {
id: 'currency_code', id: 'currency_code',
Header: formatMessage({id:'currency_code'}), Header: formatMessage({ id: 'currency_code' }),
accessor: 'currency_code', accessor: 'currency_code',
className: 'currency_code', className: 'currency_code',
width: 150, width: 150,
}, },
{ {
id: 'exchange_rate', id: 'exchange_rate',
Header: formatMessage({id:'exchange_rate'}), Header: formatMessage({ id: 'exchange_rate' }),
accessor: 'exchange_rate', accessor: 'exchange_rate',
className: 'exchange_rate', className: 'exchange_rate',
width: 150, width: 150,
@@ -126,6 +125,7 @@ function ExchangeRateTable({
columns={columns} columns={columns}
data={exchangeRatesList} data={exchangeRatesList}
onFetchData={handelFetchData} onFetchData={handelFetchData}
loading={exchangeRatesLoading && !initialMount}
manualSortBy={true} manualSortBy={true}
selectionColumn={selectionColumn} selectionColumn={selectionColumn}
expandable={true} expandable={true}
@@ -139,5 +139,8 @@ function ExchangeRateTable({
export default compose( export default compose(
DialogConnect, DialogConnect,
withExchangeRatesActions, withExchangeRatesActions,
withExchangeRates withExchangeRates(({ exchangeRatesList ,exchangeRatesLoading }) => ({
exchangeRatesList,
exchangeRatesLoading
}))
)(ExchangeRateTable); )(ExchangeRateTable);

View File

@@ -0,0 +1,32 @@
import { connect } from 'react-redux';
import { compose } from 'utils';
import DialogConnect from 'connectors/Dialog.connector';
import DialogReduxConnect from 'components/DialogReduxConnect';
import {getDialogPayload} from 'store/dashboard/dashboard.reducer';
import withExchangeRatesActions from 'containers/FinancialStatements/ExchangeRates/withExchangeRatesActions';
import withExchangeRates from 'containers/FinancialStatements/ExchangeRates/withExchangeRates';
import CurrencyFromDialogConnect from 'connectors/CurrencyFromDialog.connect'
export const mapStateToProps = (state, props) => {
const dialogPayload = getDialogPayload(state, 'exchangeRate-form');
return {
name: 'exchangeRate-form',
payload: { action: 'new', id: null, ...dialogPayload },
editExchangeRate:
dialogPayload && dialogPayload.action === 'edit'
? state.exchangeRates.exchangeRates[dialogPayload.id]
: {},
};
};
const ExchangeRatesDialogConnect = connect(mapStateToProps);
export default compose(
CurrencyFromDialogConnect,
ExchangeRatesDialogConnect,
withExchangeRatesActions,
withExchangeRates(({ exchangeRatesList }) => ({
exchangeRatesList
})),
DialogReduxConnect,
DialogConnect
);

View File

@@ -1,8 +1,14 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { getResourceViews } from 'store/customViews/customViews.selectors'; import { getResourceViews } from 'store/customViews/customViews.selectors';
const mapStateToProps = (state, props) => ({ export default (mapState) => {
exchangeRatesList: Object.values(state.exchangeRates.exchangeRates), const mapStateToProps = (state, props) => {
}); const mapped = {
exchangeRatesList: Object.values(state.exchangeRates.exchangeRates),
exchangeRatesLoading: state.exchangeRates.loading,
};
return mapState ? mapState(mapped, state, props) : mapped;
};
export default connect(mapStateToProps); return connect(mapStateToProps);
};

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useState, useCallback } from 'react'; import React, { useEffect, useState, useCallback } from 'react';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import useAsync from 'hooks/async'; import { useQuery } from 'react-query';
import DashboardInsider from 'components/Dashboard/DashboardInsider'; import DashboardInsider from 'components/Dashboard/DashboardInsider';
import ItemCategoriesDataTable from 'containers/Items/ItemCategoriesTable'; import ItemCategoriesDataTable from 'containers/Items/ItemCategoriesTable';
@@ -8,40 +8,46 @@ import ItemsCategoryActionsBar from 'containers/Items/ItemsCategoryActionsBar';
import withDashboardActions from 'containers/Dashboard/withDashboard'; import withDashboardActions from 'containers/Dashboard/withDashboard';
import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions'; import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions';
import withItemCategories from 'containers/Items/withItemCategories';
import { compose } from 'utils'; import { compose } from 'utils';
import { FormattedMessage as T, useIntl } from 'react-intl';
const ItemCategoryList = ({ const ItemCategoryList = ({
// #withDashboardActions
changePageTitle, changePageTitle,
// #withItemCategoriesActions
requestFetchItemCategories, requestFetchItemCategories,
}) => { }) => {
const { id } = useParams(); const { id } = useParams();
const [selectedRows, setSelectedRows] = useState([]); const [selectedRows, setSelectedRows] = useState([]);
const {formatMessage} =useIntl()
useEffect(() => { useEffect(() => {
id id
? changePageTitle('Edit Category Details') ? changePageTitle(formatMessage({id:'edit_category_details'}))
: changePageTitle('Category List'); : changePageTitle(formatMessage({id:'category_list'}));
}, []); }, []);
const fetchCategories = useAsync(() => { const fetchCategories = useQuery('items-categories-table',
return Promise.all([ () => { requestFetchItemCategories(); });
requestFetchItemCategories(),
]);
});
const handleFilterChanged = useCallback(() => { const handleFilterChanged = useCallback(() => {
}, []); }, []);
// Handle selected rows change.
const handleSelectedRowsChange = useCallback((itemCategories) => {
setSelectedRows(itemCategories);
}, [setSelectedRows]);
return ( return (
<DashboardInsider name={'item-category-list'}> <DashboardInsider name={'item-category-list'}>
<ItemsCategoryActionsBar <ItemsCategoryActionsBar
onFilterChanged={handleFilterChanged} onFilterChanged={handleFilterChanged}
selectedRows={selectedRows} /> selectedRows={selectedRows} />
<ItemCategoriesDataTable /> <ItemCategoriesDataTable
onSelectedRowsChange={handleSelectedRowsChange} />
</DashboardInsider> </DashboardInsider>
); );
}; };
@@ -49,5 +55,4 @@ const ItemCategoryList = ({
export default compose( export default compose(
withDashboardActions, withDashboardActions,
withItemCategoriesActions, withItemCategoriesActions,
withItemCategories,
)(ItemCategoryList); )(ItemCategoryList);

View File

@@ -10,6 +10,7 @@ import withAccountsActions from 'containers/Accounts/withAccountsActions';
import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions'; import withItemCategoriesActions from 'containers/Items/withItemCategoriesActions';
import { compose } from 'utils'; import { compose } from 'utils';
import { FormattedMessage as T, useIntl } from 'react-intl';
const ItemFormContainer = ({ const ItemFormContainer = ({
@@ -23,11 +24,11 @@ const ItemFormContainer = ({
requestFetchItemCategories, requestFetchItemCategories,
}) => { }) => {
const { id } = useParams(); const { id } = useParams();
const {formatMessage} =useIntl()
useEffect(() => { useEffect(() => {
id ? id ?
changePageTitle('Edit Item Details') : changePageTitle(formatMessage({id:'edit_item_details'})) :
changePageTitle('New Item'); changePageTitle(formatMessage({id:'new_item'}));
}, [id, changePageTitle]); }, [id, changePageTitle]);
const fetchAccounts = useQuery('accounts-list', const fetchAccounts = useQuery('accounts-list',

View File

@@ -1,6 +1,7 @@
import React, {useState, useEffect, useCallback, useMemo} from 'react'; import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { useFormik } from "formik"; import { useFormik } from 'formik';
import {useIntl} from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import { useParams, useHistory } from 'react-router-dom'; import { useParams, useHistory } from 'react-router-dom';
import { import {
InputGroup, InputGroup,
@@ -13,26 +14,24 @@ import {
Menu, Menu,
H5, H5,
H6, H6,
} from "@blueprintjs/core"; } from '@blueprintjs/core';
import {Row, Col} from 'react-grid-system'; import { Row, Col } from 'react-grid-system';
import { ReactSortable } from 'react-sortablejs'; import { ReactSortable } from 'react-sortablejs';
import * as Yup from 'yup'; import * as Yup from 'yup';
import {pick, get} from 'lodash'; import { pick, get } from 'lodash';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import ErrorMessage from 'components/ErrorMessage'; import ErrorMessage from 'components/ErrorMessage';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import { If } from 'components'; import { If } from 'components';
import ViewFormContainer from 'containers/Views/ViewForm.container.js'; import ViewFormContainer from 'containers/Views/ViewForm.container.js';
function ViewForm({
function ViewForm({
requestSubmitView, requestSubmitView,
requestEditView, requestEditView,
onDelete, onDelete,
viewId, viewId,
viewMeta, viewMeta,
viewItem,
resourceName, resourceName,
resourceColumns, resourceColumns,
@@ -52,20 +51,30 @@ function ViewForm({
}, []); }, []);
const [draggedColumns, setDraggedColumn] = useState([ const [draggedColumns, setDraggedColumn] = useState([
...(viewMeta && viewMeta.columns) ? viewMeta.columns : [] ...(viewMeta && viewMeta.columns ? viewMeta.columns : []),
]);
const draggedColumnsIds = useMemo(() => draggedColumns.map((c) => c.id), [
draggedColumns,
]); ]);
const draggedColumnsIds = useMemo(() => draggedColumns.map(c => c.id), [draggedColumns]);
const [availableColumns, setAvailableColumns] = useState([ const [availableColumns, setAvailableColumns] = useState([
...(viewMeta && viewMeta.columns) ? resourceColumns.filter((column) => ...(viewMeta && viewMeta.columns
draggedColumnsIds.indexOf(column.id) === -1 ? resourceColumns.filter(
) : resourceColumns, (column) => draggedColumnsIds.indexOf(column.id) === -1
)
: resourceColumns),
]); ]);
const defaultViewRole = useMemo(() => ({ const defaultViewRole = useMemo(
field_key: '', comparator: '', value: '', index: 1, () => ({
}), []); field_key: '',
comparator: '',
value: '',
index: 1,
}),
[]
);
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
resource_name: Yup.string().required(), resource_name: Yup.string().required(),
@@ -83,27 +92,33 @@ function ViewForm({
Yup.object().shape({ Yup.object().shape({
key: Yup.string().required(), key: Yup.string().required(),
index: Yup.string().required(), index: Yup.string().required(),
}), })
), ),
}); });
const initialEmptyForm = useMemo(() => ({ const initialEmptyForm = useMemo(
resource_name: resourceName || '', () => ({
name: '', resource_name: resourceName || '',
logic_expression: '', name: '',
roles: [ logic_expression: '',
defaultViewRole, roles: [defaultViewRole],
], columns: [],
columns: [], }),
}), [defaultViewRole, resourceName]); [defaultViewRole, resourceName]
);
const initialForm = useMemo(() => ({ const initialForm = useMemo(
...initialEmptyForm, () => ({
...viewMeta ? { ...initialEmptyForm,
...viewMeta, ...(viewMeta
resource_name: viewMeta.resource?.name || resourceName, ? {
} : {}, ...viewMeta,
}), [initialEmptyForm, viewMeta, resourceName]); resource_name: viewMeta.resource?.name || resourceName,
}
: {}),
}),
[initialEmptyForm, viewMeta, resourceName]
);
const { const {
values, values,
@@ -136,7 +151,9 @@ function ViewForm({
message: 'the_view_has_been_edited', message: 'the_view_has_been_edited',
intent: Intent.SUCCESS, intent: Intent.SUCCESS,
}); });
history.push(`${resourceMetadata.baseRoute}/${viewMeta.id}/custom_view`); history.push(
`${resourceMetadata.baseRoute}/${viewMeta.id}/custom_view`
);
setSubmitting(false); setSubmitting(false);
}); });
} else { } else {
@@ -145,7 +162,9 @@ function ViewForm({
message: 'the_view_has_been_submit', message: 'the_view_has_been_submit',
intent: Intent.SUCCESS, intent: Intent.SUCCESS,
}); });
history.push(`${resourceMetadata.baseRoute}/${viewMeta.id}/custom_view`); history.push(
`${resourceMetadata.baseRoute}/${viewMeta.id}/custom_view`
);
setSubmitting(false); setSubmitting(false);
}); });
} }
@@ -153,39 +172,55 @@ function ViewForm({
}); });
useEffect(() => { useEffect(() => {
setFieldValue('columns', setFieldValue(
'columns',
draggedColumns.map((column, index) => ({ draggedColumns.map((column, index) => ({
index, key: column.key, index,
}))); key: column.key,
}))
);
}, [setFieldValue, draggedColumns]); }, [setFieldValue, draggedColumns]);
const conditionalsItems = useMemo(() => ([ const conditionalsItems = useMemo(
{ value: 'and', label: 'AND' }, () => [
{ value: 'or', label: 'OR' }, { value: 'and', label: 'AND' },
]), []); { value: 'or', label: 'OR' },
],
[]
);
const whenConditionalsItems = useMemo(() => ([ const whenConditionalsItems = useMemo(
{ value: '', label: 'When' }, () => [{ value: '', label: 'When' }],
]), []); []
);
// Compatotors items. // Compatotors items.
const compatatorsItems = useMemo(() => ([ const compatatorsItems = useMemo(
{value: '', label: 'Compatator'}, () => [
{value: 'equals', label: 'Equals'}, { value: '', label: 'Compatator' },
{value: 'not_equal', label: 'Not Equal'}, { value: 'equals', label: 'Equals' },
{value: 'contain', label: 'Contain'}, { value: 'not_equal', label: 'Not Equal' },
{value: 'not_contain', label: 'Not Contain'}, { value: 'contain', label: 'Contain' },
]), []); { value: 'not_contain', label: 'Not Contain' },
],
[]
);
// Resource fields. // Resource fields.
const resourceFieldsOptions = useMemo(() => ([ const resourceFieldsOptions = useMemo(
{value: '', label: 'Select a field'}, () => [
...resourceFields.map((field) => ({ value: field.key, label: field.label_name, })), { value: '', label: 'Select a field' },
]), [resourceFields]); ...resourceFields.map((field) => ({
value: field.key,
label: field.label_name,
})),
],
[resourceFields]
);
// Account item of select accounts field. // Account item of select accounts field.
const selectItem = (item, { handleClick, modifiers, query }) => { const selectItem = (item, { handleClick, modifiers, query }) => {
return (<MenuItem text={item.label} key={item.key} onClick={handleClick} />) return <MenuItem text={item.label} key={item.key} onClick={handleClick} />;
}; };
// Handle click new condition button. // Handle click new condition button.
const onClickNewRole = useCallback(() => { const onClickNewRole = useCallback(() => {
@@ -194,219 +229,273 @@ function ViewForm({
{ {
...defaultViewRole, ...defaultViewRole,
index: values.roles.length + 1, index: values.roles.length + 1,
} },
]); ]);
}, [defaultViewRole, setFieldValue, values]); }, [defaultViewRole, setFieldValue, values]);
// Handle click remove view role button. // Handle click remove view role button.
const onClickRemoveRole = useCallback((viewRole, index) => { const onClickRemoveRole = useCallback(
let viewRoles = [...values.roles]; (viewRole, index) => {
let viewRoles = [...values.roles];
// Can't continue if view roles equals or less than 1. // Can't continue if view roles equals or less than 1.
if (viewRoles.length > 1) { if (viewRoles.length > 1) {
viewRoles.splice(index, 1); viewRoles.splice(index, 1);
setFieldValue(
'roles',
viewRoles.map((role) => {
return role;
})
);
}
},
[values, setFieldValue]
);
setFieldValue('roles', viewRoles.map((role) => {
return role;
}));
}
}, [values, setFieldValue]);
const onClickDeleteView = useCallback(() => { const onClickDeleteView = useCallback(() => {
onDelete && onDelete(viewMeta); onDelete && onDelete(viewMeta);
}, [onDelete, viewMeta]); }, [onDelete, viewMeta]);
const hasError = (path) => get(errors, path) && get(touched, path); const hasError = (path) => get(errors, path) && get(touched, path);
const handleClickCancelBtn = () => { const handleClickCancelBtn = () => {
history.goBack(); history.goBack();
}; };
return ( return (
<div class="view-form"> <div class='view-form'>
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div class="view-form--name-section"> <div class='view-form--name-section'>
<Row> <Row>
<Col sm={8}> <Col sm={8}>
<FormGroup <FormGroup
label={intl.formatMessage({'id': 'View Name'})} label={<T id={'view_name'} />}
className={'form-group--name'} className={'form-group--name'}
intent={(errors.name && touched.name) && Intent.DANGER} intent={errors.name && touched.name && Intent.DANGER}
helperText={<ErrorMessage {...{errors, touched}} name={'name'} />} helperText={
<ErrorMessage {...{ errors, touched }} name={'name'} />
}
inline={true} inline={true}
fill={true}> fill={true}
>
<InputGroup <InputGroup
intent={(errors.name && touched.name) && Intent.DANGER} intent={errors.name && touched.name && Intent.DANGER}
fill={true} fill={true}
{...getFieldProps('name')} /> {...getFieldProps('name')}
/>
</FormGroup> </FormGroup>
</Col> </Col>
</Row> </Row>
</div> </div>
<H5 className="mb2">Define the conditionals</H5> <H5 className='mb2'>Define the conditionals</H5>
{values.roles.map((role, index) => ( {values.roles.map((role, index) => (
<Row class="view-form__role-conditional"> <Row class='view-form__role-conditional'>
<Col sm={2} class="flex"> <Col sm={2} class='flex'>
<div class="mr2 pt1 condition-number">{ index + 1 }</div> <div class='mr2 pt1 condition-number'>{index + 1}</div>
{(index === 0) ? ( {index === 0 ? (
<HTMLSelect options={whenConditionalsItems} className={Classes.FILL} /> <HTMLSelect
options={whenConditionalsItems}
className={Classes.FILL}
/>
) : ( ) : (
<HTMLSelect options={conditionalsItems} className={Classes.FILL} /> <HTMLSelect
options={conditionalsItems}
className={Classes.FILL}
/>
)} )}
</Col> </Col>
<Col sm={2}> <Col sm={2}>
<FormGroup <FormGroup
intent={hasError(`roles[${index}].field_key`) && Intent.DANGER}> intent={hasError(`roles[${index}].field_key`) && Intent.DANGER}
>
<HTMLSelect <HTMLSelect
options={resourceFieldsOptions} options={resourceFieldsOptions}
value={role.field_key} value={role.field_key}
className={Classes.FILL} className={Classes.FILL}
{...getFieldProps(`roles[${index}].field_key`)} /> {...getFieldProps(`roles[${index}].field_key`)}
/>
</FormGroup> </FormGroup>
</Col> </Col>
<Col sm={2}> <Col sm={2}>
<FormGroup <FormGroup
intent={hasError(`roles[${index}].comparator`) && Intent.DANGER}> intent={hasError(`roles[${index}].comparator`) && Intent.DANGER}
>
<HTMLSelect <HTMLSelect
options={compatatorsItems} options={compatatorsItems}
value={role.comparator} value={role.comparator}
className={Classes.FILL} className={Classes.FILL}
{...getFieldProps(`roles[${index}].comparator`)} /> {...getFieldProps(`roles[${index}].comparator`)}
/>
</FormGroup> </FormGroup>
</Col> </Col>
<Col sm={5} class="flex"> <Col sm={5} class='flex'>
<FormGroup <FormGroup
intent={hasError(`roles[${index}].value`) && Intent.DANGER}> intent={hasError(`roles[${index}].value`) && Intent.DANGER}
>
<InputGroup <InputGroup
placeholder={intl.formatMessage({'id': 'value'})} placeholder={intl.formatMessage({ id: 'value' })}
{...getFieldProps(`roles[${index}].value`)} /> {...getFieldProps(`roles[${index}].value`)}
/>
</FormGroup> </FormGroup>
<Button <Button
icon={<Icon icon="times-circle" iconSize={14} />} icon={<Icon icon='times-circle' iconSize={14} />}
iconSize={14} iconSize={14}
className="ml2" className='ml2'
minimal={true} minimal={true}
intent={Intent.DANGER} intent={Intent.DANGER}
onClick={() => onClickRemoveRole(role, index)} /> onClick={() => onClickRemoveRole(role, index)}
/>
</Col> </Col>
</Row> </Row>
))} ))}
<div className={'view-form__role-conditions-actions'}> <div className={'view-form__role-conditions-actions'}>
<Button <Button
minimal={true} minimal={true}
intent={Intent.PRIMARY} intent={Intent.PRIMARY}
onClick={onClickNewRole}> onClick={onClickNewRole}
New Conditional >
</Button> <T id={'new_conditional'} />
</div> </Button>
</div>
<div class="view-form--logic-expression-section"> <div class='view-form--logic-expression-section'>
<Row> <Row>
<Col sm={8}> <Col sm={8}>
<FormGroup <FormGroup
label={intl.formatMessage({'id': 'Logic Expression'})} label={intl.formatMessage({ id: 'Logic Expression' })}
className={'form-group--logic-expression'} className={'form-group--logic-expression'}
intent={(errors.logic_expression && touched.logic_expression) && Intent.DANGER} intent={
helperText={<ErrorMessage {...{errors, touched}} name='logic_expression' />} errors.logic_expression &&
inline={true} touched.logic_expression &&
fill={true}> Intent.DANGER
}
helperText={
<ErrorMessage
{...{ errors, touched }}
name='logic_expression'
/>
}
inline={true}
fill={true}
>
<InputGroup
intent={
errors.logic_expression &&
touched.logic_expression &&
Intent.DANGER
}
fill={true}
{...getFieldProps('logic_expression')}
/>
</FormGroup>
</Col>
</Row>
</div>
<H5 className={'mb2'}>Columns Preferences</H5>
<div class='dragable-columns'>
<Row gutterWidth={14}>
<Col sm={4} className='dragable-columns__column'>
<H6 className='dragable-columns__title'>Available Columns</H6>
<InputGroup <InputGroup
intent={(errors.logic_expression && touched.logic_expression) && Intent.DANGER} placeholder={intl.formatMessage({ id: 'search' })}
fill={true} leftIcon='search'
{...getFieldProps('logic_expression')} /> />
</FormGroup>
</Col>
</Row>
</div>
<H5 className={'mb2'}>Columns Preferences</H5> <div class='dragable-columns__items'>
<Menu>
<div class="dragable-columns"> <ReactSortable
<Row gutterWidth={14}> list={availableColumns}
<Col sm={4} className="dragable-columns__column"> setList={setAvailableColumns}
<H6 className="dragable-columns__title">Available Columns</H6> group='shared-group-name'
>
{availableColumns.map((field) => (
<MenuItem key={field.id} text={field.label} />
))}
</ReactSortable>
</Menu>
</div>
</Col>
<InputGroup <Col sm={1}>
placeholder={intl.formatMessage({id: 'search'})} <div class='dragable-columns__arrows'>
leftIcon="search" /> <div>
<Icon
icon='arrow-circle-left'
iconSize={30}
color='#cecece'
/>
</div>
<div class='mt2'>
<Icon
icon='arrow-circle-right'
iconSize={30}
color='#cecece'
/>
</div>
</div>
</Col>
<div class="dragable-columns__items"> <Col sm={4} className='dragable-columns__column'>
<Menu> <H6 className='dragable-columns__title'>Selected Columns</H6>
<ReactSortable <InputGroup
list={availableColumns} placeholder={intl.formatMessage({ id: 'search' })}
setList={setAvailableColumns} leftIcon='search'
group="shared-group-name"> />
{availableColumns.map((field) => (
<MenuItem key={field.id} text={field.label} />
))}
</ReactSortable>
</Menu>
</div>
</Col>
<Col sm={1}> <div class='dragable-columns__items'>
<div class="dragable-columns__arrows"> <Menu>
<div><Icon icon="arrow-circle-left" iconSize={30} color="#cecece" /></div> <ReactSortable
<div class="mt2"><Icon icon="arrow-circle-right" iconSize={30} color="#cecece" /></div> list={draggedColumns}
</div> setList={setDraggedColumn}
</Col> group='shared-group-name'
>
{draggedColumns.map((field) => (
<MenuItem key={field.id} text={field.label} />
))}
</ReactSortable>
</Menu>
</div>
</Col>
</Row>
</div>
<Col sm={4} className="dragable-columns__column"> <div class='form__floating-footer'>
<H6 className="dragable-columns__title">Selected Columns</H6> <Button intent={Intent.PRIMARY} type='submit' disabled={isSubmitting}>
<InputGroup placeholder={intl.formatMessage({id: 'search'})} leftIcon="search" /> <T id={'submit'} />
<div class="dragable-columns__items">
<Menu>
<ReactSortable
list={draggedColumns}
setList={setDraggedColumn}
group="shared-group-name">
{draggedColumns.map((field) => (
<MenuItem key={field.id} text={field.label} />
))}
</ReactSortable>
</Menu>
</div>
</Col>
</Row>
</div>
<div class="form__floating-footer">
<Button
intent={Intent.PRIMARY}
type="submit"
disabled={isSubmitting}>
Submit
</Button>
<Button
intent={Intent.NONE}
className="ml1"
onClick={handleClickCancelBtn}>
Cancel
</Button>
<If condition={viewMeta && viewMeta.id}>
<Button
intent={Intent.DANGER}
onClick={onClickDeleteView}
className={"right mr2"}>
Delete
</Button> </Button>
</If>
</div> <Button
</form> intent={Intent.NONE}
</div> className='ml1'
onClick={handleClickCancelBtn}
>
<T id={'cancel'} />
</Button>
<If condition={viewMeta && viewMeta.id}>
<Button
intent={Intent.DANGER}
onClick={onClickDeleteView}
className={'right mr2'}
>
<T id={'delete'} />
</Button>
</If>
</div>
</form>
</div>
); );
} }
export default ViewFormContainer(ViewForm); export default ViewFormContainer(ViewForm);

View File

@@ -5,14 +5,17 @@ import { Intent, Alert } from '@blueprintjs/core';
import DashboardInsider from 'components/Dashboard/DashboardInsider'; import DashboardInsider from 'components/Dashboard/DashboardInsider';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import ViewForm from 'containers/Views/ViewForm'; import ViewForm from 'containers/Views/ViewForm';
import withResourcesActions from 'containers/Resources/withResourcesActions';
import withViewsActions from 'containers/Views/withViewsActions';
import withDashboard from 'containers/Dashboard/withDashboard';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import {compose} from 'utils'; import {compose} from 'utils';
import { If } from 'components'; import { If } from 'components';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import withResourcesActions from 'containers/Resources/withResourcesActions';
import withViewsActions from 'containers/Views/withViewsActions';
import withDashboard from 'containers/Dashboard/withDashboard';
// @flow // @flow
function ViewFormPage({ function ViewFormPage({
changePageTitle, changePageTitle,
@@ -27,6 +30,7 @@ function ViewFormPage({
}) { }) {
const { resource_slug: resourceSlug, view_id: viewId } = useParams(); const { resource_slug: resourceSlug, view_id: viewId } = useParams();
const [stateDeleteView, setStateDeleteView] = useState(null); const [stateDeleteView, setStateDeleteView] = useState(null);
const {formatMessage} =useIntl()
const fetchHook = useAsync(async () => { const fetchHook = useAsync(async () => {
return Promise.all([ return Promise.all([
@@ -44,9 +48,9 @@ function ViewFormPage({
useEffect(() => { useEffect(() => {
if (viewId) { if (viewId) {
changePageTitle('Edit Custom View'); changePageTitle(formatMessage({id:'edit_custom_view'}));
} else { } else {
changePageTitle('New Custom View'); changePageTitle(formatMessage({id:'new_custom_view'}));
} }
return () => { return () => {
changePageTitle(''); changePageTitle('');

View File

@@ -149,7 +149,22 @@ bulk_update:'Bulk Update',
all_accounts:'All accounts', all_accounts:'All accounts',
go_to_bigcapital_com:'← Go to bigcapital.com', go_to_bigcapital_com:'← Go to bigcapital.com',
currency:'Currency', currency:'Currency',
new_conditional:'+ New Conditional' new_conditional:'+ New Conditional',
chart_of_accounts:'Chart of Accounts',
exchange_rate_details:'Exchange Rate Details',
exchange_rate_list:'Exchange Rate List',
manual_journals:'Manual Journals',
edit_expense_details:'Edit Expense Details',
expenses_list:'Expenses List',
edit_category_details:'Edit Category Details',
category_list:'Category List',
edit_item_details:'Edit Item Details',
items_list:'Items List',
edit_custom_view:'Edit Custom View',
new_custom_view:'New Custom View',
view_name:'View Name',
new_conditional:'New Conditional'

View File

@@ -7,6 +7,10 @@ export const fetchExchangeRates = () => {
dispatch({ dispatch({
type: t.SET_DASHBOARD_REQUEST_LOADING, type: t.SET_DASHBOARD_REQUEST_LOADING,
}); });
dispatch({
type: t.EXCHANGE_RATE_TABLE_LOADING,
loading: true,
});
ApiService.get('exchange_rates') ApiService.get('exchange_rates')
.then((response) => { .then((response) => {
dispatch({ dispatch({
@@ -16,6 +20,10 @@ export const fetchExchangeRates = () => {
dispatch({ dispatch({
type: t.SET_DASHBOARD_REQUEST_COMPLETED, type: t.SET_DASHBOARD_REQUEST_COMPLETED,
}); });
dispatch({
type: t.EXCHANGE_RATE_TABLE_LOADING,
loading: false,
});
resolve(response); resolve(response);
}) })
.catch((error) => { .catch((error) => {

View File

@@ -17,4 +17,7 @@ export default createReducer(initialState, {
..._exchangeRates, ..._exchangeRates,
}; };
}, },
[t.EXCHANGE_RATE_TABLE_LOADING]: (state, action) => {
state.loading = action.loading;
},
}); });

View File

@@ -4,4 +4,5 @@ export default {
EXCHANGE_RATE_LIST_SET: 'EXCHANGE_RATE_LIST_SET', EXCHANGE_RATE_LIST_SET: 'EXCHANGE_RATE_LIST_SET',
CLEAR_EXCHANGE_RATE_FORM_ERRORS: 'CLEAR_EXCHANGE_RATE_FORM_ERRORS', CLEAR_EXCHANGE_RATE_FORM_ERRORS: 'CLEAR_EXCHANGE_RATE_FORM_ERRORS',
ExchangeRates_TABLE_QUERIES_ADD: 'ExchangeRates_TABLE_QUERIES_ADD', ExchangeRates_TABLE_QUERIES_ADD: 'ExchangeRates_TABLE_QUERIES_ADD',
EXCHANGE_RATE_TABLE_LOADING:'EXCHANGE_RATE_TABLE_LOADING'
}; };