mege exchange rates conflict.

This commit is contained in:
a.bouhuolia
2021-02-20 15:36:53 +02:00
18 changed files with 779 additions and 603 deletions

View File

@@ -0,0 +1,71 @@
import React, { useState } from 'react';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { Intent, Alert } from '@blueprintjs/core';
import { size } from 'lodash';
import { AppToaster } from 'components';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
/**
* Exchange rate bulk delete alert.
*/
function ExchangeRateBulkDeleteAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { exchangeRatesIds },
// #withAlertActions
closeAlert,
}) {
// handle cancel item bulk delete alert.
const handleCancelBulkDelete = () => {
closeAlert(name);
};
// handle confirm Exchange Rates bulk delete.
// const handleConfirmBulkDelete = () => {
// bulkDeleteExchangeRate(exchangeRatesIds)
// .then(() => {
// AppToaster.show({
// message: formatMessage({
// id: 'the_exchange_rates_has_been_successfully_deleted',
// }),
// intent: Intent.SUCCESS,
// });
// })
// .catch(({ errors }) => {
// handleDeleteErrors(errors);
// });
// };
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={
<T id={'delete_count'} values={{ count: size(exchangeRatesIds) }} />
}
icon="trash"
intent={Intent.DANGER}
isOpen={isOpen}
onCancel={handleCancelBulkDelete}
// onConfirm={}
// loading={isLoading}
>
<p>
<T
id={'once_delete_these_exchange_rates_you_will_not_able_restore_them'}
/>
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
)(ExchangeRateBulkDeleteAlert);

View File

@@ -0,0 +1,76 @@
import React from 'react';
import {
FormattedMessage as T,
FormattedHTMLMessage,
useIntl,
} from 'react-intl';
import { Intent, Alert } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { useDeleteExchangeRate } from 'hooks/query';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
/**
* exchange rate delete alerts.
*/
function ExchangeRateDeleteAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { exchangeRateId },
// #withAlertActions
closeAlert,
}) {
const {
mutateAsync: deleteExchangeRate,
isLoading,
} = useDeleteExchangeRate();
const { formatMessage } = useIntl();
// Handle cancel delete exchange rate alert.
const handleCancelExchangeRateDelete = () => closeAlert(name);
const handelConfirmExchangeRateDelete = () => {
deleteExchangeRate(exchangeRateId)
.then((response) => {
AppToaster.show({
message: formatMessage({
id: 'the_exchange_rates_has_been_deleted_successfully',
}),
intent: Intent.SUCCESS,
});
closeAlert(name);
})
.catch(() => {
closeAlert(name);
});
};
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />}
intent={Intent.DANGER}
isOpen={isOpen}
onCancel={handleCancelExchangeRateDelete}
onConfirm={handelConfirmExchangeRateDelete}
loading={isLoading}
>
<p>
<FormattedHTMLMessage
id={'once_delete_this_exchange_rate_you_will_able_to_restore_it'}
/>
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
)(ExchangeRateDeleteAlert);

View File

@@ -0,0 +1,114 @@
import React, { useMemo } from 'react';
import { Intent } from '@blueprintjs/core';
import { Formik } from 'formik';
import moment from 'moment';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { AppToaster } from 'components';
import {
CreateExchangeRateFormSchema,
EditExchangeRateFormSchema,
} from './ExchangeRateForm.schema';
import ExchangeRateFormContent from './ExchangeRateFormContent';
import { useExchangeRateFromContext } from './ExchangeRateFormProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose, transformToForm } from 'utils';
const defaultInitialValues = {
exchange_rate: '',
currency_code: '',
date: moment(new Date()).format('YYYY-MM-DD'),
};
/**
* Exchange rate form.
*/
function ExchangeRateForm({
// #withDialogActions
closeDialog,
}) {
const { formatMessage } = useIntl();
const {
createExchangeRateMutate,
editExchangeRateMutate,
isNewMode,
dialogName,
exchangeRate,
} = useExchangeRateFromContext();
// Form validation schema in create and edit mode.
const validationSchema = isNewMode
? CreateExchangeRateFormSchema
: EditExchangeRateFormSchema;
const initialValues = useMemo(
() => ({
...defaultInitialValues,
...transformToForm(exchangeRate, defaultInitialValues),
}),
[],
);
// Transformers response errors.
const transformErrors = (errors, { setErrors }) => {
if (
errors.find((error) => error.type === 'EXCHANGE.RATE.DATE.PERIOD.DEFINED')
) {
setErrors({
exchange_rate: formatMessage({
id: 'there_is_exchange_rate_in_this_date_with_the_same_currency',
}),
});
}
};
// Handle the form submit.
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
setSubmitting(true);
// Handle close the dialog after success response.
const afterSubmit = () => {
closeDialog(dialogName);
};
const onSuccess = ({ response }) => {
AppToaster.show({
message: formatMessage({
id: !isNewMode
? 'the_exchange_rate_has_been_edited_successfully'
: 'the_exchange_rate_has_been_created_successfully',
}),
intent: Intent.SUCCESS,
});
afterSubmit(response);
};
// Handle the response error.
const onError = (error) => {
const {
response: {
data: { errors },
},
} = error;
transformErrors(errors, { setErrors });
setSubmitting(false);
};
if (isNewMode) {
createExchangeRateMutate(values).then(onSuccess).catch(onError);
} else {
editExchangeRateMutate([exchangeRate.id, values])
.then(onSuccess)
.catch(onError);
}
};
return (
<Formik
validationSchema={validationSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
<ExchangeRateFormContent />
</Formik>
);
}
export default compose(withDialogActions)(ExchangeRateForm);

View File

@@ -0,0 +1,18 @@
import * as Yup from 'yup';
import { formatMessage } from 'services/intl';
import { DATATYPES_LENGTH } from 'common/dataTypes';
const Schema = Yup.object().shape({
exchange_rate: Yup.number()
.required()
.label(formatMessage({ id: 'exchange_rate_' })),
currency_code: Yup.string()
.max(3)
.required(formatMessage({ id: 'currency_code_' })),
date: Yup.date()
.required()
.label(formatMessage({ id: 'date' })),
});
export const CreateExchangeRateFormSchema = Schema;
export const EditExchangeRateFormSchema = Schema;

View File

@@ -0,0 +1,13 @@
import React from 'react';
import { Form } from 'formik';
import ExchangeRateFormFields from './ExchangeRateFormFields';
import ExchangeRateFormFooter from './ExchangeRateFormFooter';
export default function ExchangeRateFormContent() {
return (
<Form>
<ExchangeRateFormFields />
<ExchangeRateFormFooter />
</Form>
);
}

View File

@@ -1,35 +1,9 @@
import React, { useState, useMemo, useCallback } from 'react';
import {
Button,
Classes,
FormGroup,
InputGroup,
Intent,
Position,
} from '@blueprintjs/core';
import { pick } from 'lodash';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import { useQuery, queryCache } from 'react-query';
import moment from 'moment';
import { DateInput } from '@blueprintjs/datetime';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { momentFormatter, tansformDateValue } from 'utils';
import {
AppToaster,
ErrorMessage,
DialogContent,
FieldRequiredHint,
CurrencySelectList,
} from 'components';
import classNames from 'classnames';
import React from 'react';
import ExchangeRateForm from './ExchangeRateForm';
import { ExchangeRateFormProvider } from './ExchangeRateFormProvider';
import withExchangeRateDetail from 'containers/ExchangeRates/withExchangeRateDetail';
import withExchangeRatesActions from 'containers/ExchangeRates/withExchangeRatesActions';
import withCurrencies from 'containers/Currencies/withCurrencies';
import withCurrenciesActions from 'containers/Currencies/withCurrenciesActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
import 'style/pages/ExchangeRate/ExchangeRateDialog.scss';
@@ -38,226 +12,20 @@ import 'style/pages/ExchangeRate/ExchangeRateDialog.scss';
* Exchange rate form content.
*/
function ExchangeRateFormDialogContent({
// #withDialogActions
closeDialog,
// #withCurrencies
currenciesList,
//#WithExchangeRateDetail
exchangeRate,
// #withExchangeRatesActions
requestSubmitExchangeRate,
requestEditExchangeRate,
// #wihtCurrenciesActions
requestFetchCurrencies,
// #ownProp
action,
exchangeRateId,
dialogName,
}) {
const { formatMessage } = useIntl();
const [selectedItems, setSelectedItems] = useState({});
const fetchCurrencies = useQuery(
'currencies',
() => requestFetchCurrencies(),
{ enabled: true },
);
const validationSchema = Yup.object().shape({
exchange_rate: Yup.number()
.required()
.label(formatMessage({ id: 'exchange_rate_' })),
currency_code: Yup.string()
.max(3)
.required(formatMessage({ id: 'currency_code_' })),
date: Yup.date()
.required()
.label(formatMessage({ id: 'date' })),
});
const initialValues = useMemo(
() => ({
exchange_rate: '',
currency_code: '',
date: moment(new Date()).format('YYYY-MM-DD'),
}),
[],
);
const {
values,
touched,
errors,
isSubmitting,
handleSubmit,
getFieldProps,
setFieldValue,
resetForm,
} = useFormik({
enableReinitialize: true,
validationSchema,
initialValues: {
...initialValues,
...(action === 'edit' && pick(exchangeRate, Object.keys(initialValues))),
},
onSubmit: (values, { setSubmitting, setErrors }) => {
if (action === 'edit') {
requestEditExchangeRate(exchangeRateId, values)
.then((response) => {
closeDialog(dialogName);
AppToaster.show({
message: formatMessage({
id: 'the_exchange_rate_has_been_edited_successfully',
}),
intent: Intent.SUCCESS,
});
setSubmitting(false);
queryCache.invalidateQueries('exchange-rates-table');
})
.catch((error) => {
setSubmitting(false);
});
} else {
requestSubmitExchangeRate(values)
.then((response) => {
closeDialog(dialogName);
AppToaster.show({
message: formatMessage({
id: 'the_exchange_rate_has_been_created_successfully',
}),
intent: Intent.SUCCESS,
});
setSubmitting(false);
queryCache.invalidateQueries('exchange-rates-table');
})
.catch((errors) => {
if (
errors.find((e) => e.type === 'EXCHANGE.RATE.DATE.PERIOD.DEFINED')
) {
setErrors({
exchange_rate: formatMessage({
id:
'there_is_exchange_rate_in_this_date_with_the_same_currency',
}),
});
}
});
}
},
});
const handleClose = useCallback(() => {
closeDialog(dialogName);
resetForm();
}, [dialogName, closeDialog]);
const handleDateChange = useCallback(
(date_filed) => (date) => {
const formatted = moment(date).format('YYYY-MM-DD');
setFieldValue(date_filed, formatted);
},
[setFieldValue],
);
const onItemsSelect = useCallback(
(filedName) => {
return (filed) => {
setSelectedItems({
...selectedItems,
[filedName]: filed,
});
setFieldValue(filedName, filed.currency_code);
};
},
[setFieldValue, selectedItems],
);
return (
<DialogContent isLoading={fetchCurrencies.isFetching}>
<form onSubmit={handleSubmit}>
<div className={Classes.DIALOG_BODY}>
<FormGroup
label={<T id={'date'} />}
inline={true}
labelInfo={FieldRequiredHint}
intent={errors.date && touched.date && Intent.DANGER}
helperText={<ErrorMessage name="date" {...{ errors, touched }} />}
>
<DateInput
fill={true}
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(values.date)}
onChange={handleDateChange('date')}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
disabled={action === 'edit'}
/>
</FormGroup>
<FormGroup
label={<T id={'currency_code'} />}
labelInfo={FieldRequiredHint}
className={classNames('form-group--select-list', Classes.FILL)}
inline={true}
intent={
errors.currency_code && touched.currency_code && Intent.DANGER
}
helperText={
<ErrorMessage name="currency_code" {...{ errors, touched }} />
}
>
<CurrencySelectList
currenciesList={currenciesList}
selectedCurrencyCode={values.currency_code}
onCurrencySelected={onItemsSelect('currency_code')}
disabled={action === 'edit'}
/>
</FormGroup>
<FormGroup
label={<T id={'exchange_rate'} />}
labelInfo={FieldRequiredHint}
intent={
errors.exchange_rate && touched.exchange_rate && Intent.DANGER
}
helperText={
<ErrorMessage name="exchange_rate" {...{ errors, touched }} />
}
inline={true}
>
<InputGroup
medium={true}
intent={
errors.exchange_rate && touched.exchange_rate && Intent.DANGER
}
{...getFieldProps('exchange_rate')}
/>
</FormGroup>
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={handleClose}>
<T id={'close'} />
</Button>
<Button
intent={Intent.PRIMARY}
type="submit"
disabled={isSubmitting}
>
{action === 'edit' ? <T id={'edit'} /> : <T id={'submit'} />}
</Button>
</div>
</div>
</form>
</DialogContent>
<ExchangeRateFormProvider
dialogName={dialogName}
exchangeRate={exchangeRateId}
action={action}
>
<ExchangeRateForm />
</ExchangeRateFormProvider>
);
}
export default compose(
withDialogActions,
withExchangeRatesActions,
withExchangeRateDetail,
withCurrenciesActions,
withCurrencies(({ currenciesList }) => ({ currenciesList })),
)(ExchangeRateFormDialogContent);
export default compose(withExchangeRateDetail)(ExchangeRateFormDialogContent);

View File

@@ -0,0 +1,88 @@
import React from 'react';
import { Classes, FormGroup, InputGroup, Position } from '@blueprintjs/core';
import { FastField } from 'formik';
import { DateInput } from '@blueprintjs/datetime';
import { FormattedMessage as T } from 'react-intl';
import {
momentFormatter,
tansformDateValue,
handleDateChange,
inputIntent,
} from 'utils';
import {
ErrorMessage,
FieldRequiredHint,
CurrencySelectList,
} from 'components';
import { useExchangeRateFromContext } from './ExchangeRateFormProvider';
import classNames from 'classnames';
export default function ExchangeRateFormFields() {
const { action, currencies } = useExchangeRateFromContext();
return (
<div className={Classes.DIALOG_BODY}>
{/* ----------- Date ----------- */}
<FastField name={'date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'date'} />}
labelInfo={FieldRequiredHint}
className={classNames('form-group--select-list', Classes.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="date" />}
inline={true}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(value)}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('date', formattedDate);
})}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
disabled={action === 'edit'}
/>
</FormGroup>
)}
</FastField>
{/* ----------- Currency Code ----------- */}
<FastField name={'currency_code'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'currency_code'} />}
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--currency', Classes.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="currency_code" />}
inline={true}
>
<CurrencySelectList
currenciesList={currencies}
selectedCurrencyCode={value}
onCurrencySelected={({ currency_code }) => {
form.setFieldValue('currency_code', currency_code);
}}
disabled={action === 'edit'}
/>
</FormGroup>
)}
</FastField>
{/*------------ Exchange Rate -----------*/}
<FastField name={'exchange_rate'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'exchange_rate'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="exchange_rate" />}
inline={true}
>
<InputGroup intent={inputIntent({ error, touched })} {...field} />
</FormGroup>
)}
</FastField>
</div>
);
}

View File

@@ -0,0 +1,35 @@
import React from 'react';
import { useFormikContext } from 'formik';
import { Button, Classes, Intent } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl';
import { useExchangeRateFromContext } from './ExchangeRateFormProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
function ExchangeRateFormFooter({
// #withDialogActions
closeDialog,
}) {
const { isSubmitting } = useFormikContext();
const { dialogName, action } = useExchangeRateFromContext();
const handleClose = () => {
closeDialog(dialogName);
};
return (
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={handleClose}>
<T id={'close'} />
</Button>
<Button intent={Intent.PRIMARY} type="submit" disabled={isSubmitting}>
{action === 'edit' ? <T id={'edit'} /> : <T id={'submit'} />}
</Button>
</div>
</div>
);
}
export default compose(withDialogActions)(ExchangeRateFormFooter);

View File

@@ -0,0 +1,52 @@
import React, { createContext, useContext } from 'react';
import {
useCreateExchangeRate,
useEdiExchangeRate,
useCurrencies,
useExchangeRates,
} from 'hooks/query';
import { DialogContent } from 'components';
const ExchangeRateFormContext = createContext();
/**
* Exchange rate Form page provider.
*/
function ExchangeRateFormProvider({
exchangeRate,
action,
dialogName,
...props
}) {
// Create and edit exchange rate mutations.
const { mutateAsync: createExchangeRateMutate } = useCreateExchangeRate();
const { mutateAsync: editExchangeRateMutate } = useEdiExchangeRate();
// Load Currencies list.
const { data: currencies, isFetching: isCurrenciesLoading } = useCurrencies();
const { isFetching: isExchangeRatesLoading } = useExchangeRates();
const isNewMode = !exchangeRate;
// Provider state.
const provider = {
createExchangeRateMutate,
editExchangeRateMutate,
dialogName,
exchangeRate,
action,
currencies,
isExchangeRatesLoading,
isNewMode,
};
return (
<DialogContent isLoading={isCurrenciesLoading} name={'exchange-rate-form'}>
<ExchangeRateFormContext.Provider value={provider} {...props} />
</DialogContent>
);
}
const useExchangeRateFromContext = () => useContext(ExchangeRateFormContext);
export { ExchangeRateFormProvider, useExchangeRateFromContext };

View File

@@ -16,7 +16,7 @@ const ExchangeRateFormDialogContent = lazy(() =>
*/
function ExchangeRateFormDialog({
dialogName,
payload = { action: '', id: null },
payload = { action: '', id: null , exchangeRate:"" },
isOpen,
}) {
return (
@@ -38,7 +38,7 @@ function ExchangeRateFormDialog({
<ExchangeRateFormDialogContent
dialogName={dialogName}
action={payload.action}
exchangeRateId={payload.id}
exchangeRateId={payload.exchangeRate}
/>
</DialogSuspense>
</Dialog>

View File

@@ -1,183 +1,75 @@
import React, { useCallback, useMemo, useState } from 'react';
import {
Button,
Popover,
Menu,
MenuItem,
Position,
Intent,
} from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import moment from 'moment';
import classNames from 'classnames';
import React from 'react';
import { CLASSES } from 'common/classes';
import { DataTable } from 'components';
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
import { DataTable, Icon, Money } from 'components';
import LoadingIndicator from 'components/LoadingIndicator';
import { useExchangeRatesContext } from './ExchangeRatesProvider';
import { useExchangeRatesTableColumns, ActionMenuList } from './components';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withExchangeRatesActions from 'containers/ExchangeRates/withExchangeRatesActions';
import withExchangeRates from 'containers/ExchangeRates/withExchangeRates';
import withAlertActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
/**
* Exchange rates table.
*/
function ExchangeRateTable({
// #withExchangeRates
exchangeRatesList,
exchangeRatesLoading,
exchangeRatesPageination,
// #ownProps
tableProps,
// #withDialogActions.
openDialog,
// own properties
loading,
onFetchData,
onDeleteExchangeRate,
onSelectedRowsChange,
// #withAlertActions
openAlert,
}) {
const [initialMount, setInitialMount] = useState(false);
const { formatMessage } = useIntl();
const {
isExchangeRatesFetching,
isExchangeRatesLoading,
const handelEditExchangeRate = useCallback(
(exchange_rate) => () => {
openDialog('exchangeRate-form', { action: 'edit', id: exchange_rate.id });
},
[openDialog],
);
exchangesRates,
pagination,
} = useExchangeRatesContext();
const handleDeleteExchangeRate = (exchange_rate) => () => {
onDeleteExchangeRate(exchange_rate);
// Table columns.
const columns = useExchangeRatesTableColumns();
// Handle delete exchange rate.
const handleDeleteExchangeRate = ({ id }) => {
openAlert('exchange-rate-delete', { exchangeRateId: id });
};
const actionMenuList = useCallback(
(ExchangeRate) => (
<Menu>
<MenuItem
icon={<Icon icon="pen-18" />}
text={formatMessage({ id: 'edit_exchange_rate' })}
onClick={handelEditExchangeRate(ExchangeRate)}
/>
<MenuItem
text={formatMessage({ id: 'delete_exchange_rate' })}
intent={Intent.DANGER}
onClick={handleDeleteExchangeRate(ExchangeRate)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Menu>
),
[handelEditExchangeRate, handleDeleteExchangeRate, formatMessage],
);
const rowContextMenu = (cell) => {
return actionMenuList(cell.row.original);
// Handle Edit exchange rate.
const handelEditExchangeRate = (exchangeRate) => {
openDialog('exchangeRate-form', {
action: 'edit',
exchangeRate: exchangeRate,
});
};
const columns = useMemo(
() => [
{
id: 'date',
Header: formatMessage({ id: 'date' }),
accessor: (r) => moment(r.date).format('YYYY MMM DD'),
width: 150,
},
{
id: 'currency_code',
Header: formatMessage({ id: 'currency_code' }),
accessor: 'currency_code',
className: 'currency_code',
width: 150,
},
{
id: 'exchange_rate',
Header: formatMessage({ id: 'exchange_rate' }),
accessor: (r) => (
<Money
amount={r.exchange_rate}
currency={r.currency_code}
/>
),
className: 'exchange_rate',
width: 150,
},
{
id: 'actions',
Header: '',
Cell: ({ cell }) => (
<Popover
content={actionMenuList(cell.row.original)}
position={Position.RIGHT_TOP}
>
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover>
),
className: 'actions',
width: 50,
},
],
[actionMenuList, formatMessage],
);
const selectionColumn = useMemo(
() => ({
minWidth: 42,
width: 42,
maxWidth: 42,
}),
[],
);
const handelFetchData = useCallback(
(...params) => {
onFetchData && onFetchData(...params);
},
[onFetchData],
);
const handelSelectedRowsChange = useCallback(
(selectRows) => {
onSelectedRowsChange &&
onSelectedRowsChange(selectRows.map((c) => c.original));
},
[onSelectedRowsChange],
);
return (
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator loading={loading} mount={false}>
<DataTable
columns={columns}
data={exchangeRatesList}
onFetchData={handelFetchData}
loading={exchangeRatesLoading && !initialMount}
manualSortBy={true}
selectionColumn={selectionColumn}
expandable={true}
treeGraph={true}
onSelectedRowsChange={handelSelectedRowsChange}
rowContextMenu={rowContextMenu}
pagination={true}
pagesCount={exchangeRatesPageination.pagesCount}
initialPageSize={exchangeRatesPageination.pageSize}
initialPageIndex={exchangeRatesPageination.page - 1}
/>
</LoadingIndicator>
</div>
<DataTable
columns={columns}
data={exchangesRates}
noInitialFetch={true}
loading={isExchangeRatesLoading}
headerLoading={isExchangeRatesLoading}
progressBarLoading={isExchangeRatesFetching}
selectionColumn={true}
manualSortBy={true}
expandable={true}
sticky={true}
// pagination={true}
TableLoadingRenderer={TableSkeletonRows}
payload={{
onDeleteExchangeRate: handleDeleteExchangeRate,
onEditExchangeRate: handelEditExchangeRate,
}}
ContextMenu={ActionMenuList}
{...tableProps}
/>
);
}
export default compose(
withDialogActions,
withExchangeRatesActions,
withExchangeRates(
({
exchangeRatesList,
exchangeRatesLoading,
exchangeRatesPageination,
}) => ({
exchangeRatesList,
exchangeRatesLoading,
exchangeRatesPageination,
}),
),
)(ExchangeRateTable);
export default compose(withDialogActions, withAlertActions)(ExchangeRateTable);

View File

@@ -0,0 +1,12 @@
import React from 'react';
import ExchangeRateDeleteAlert from 'containers/Alerts/ExchangeRates/ExchangeRateDeleteAlert';
// import ExchangeRateBulkDeleteAlert from 'containers/Alerts/ExchangeRates/ExchangeRateBulkDeleteAlert';
export default function ExchangeRatesAlerts() {
return (
<div>
<ExchangeRateDeleteAlert name={'exchange-rate-delete'} />
</div>
);
}

View File

@@ -1,204 +1,27 @@
import React, { useEffect, useState, useCallback, useMemo } from 'react';
import { useQuery, queryCache } from 'react-query';
import { useParams } from 'react-router-dom';
import { Alert, Intent } from '@blueprintjs/core';
import {
FormattedMessage as T,
useIntl,
FormattedHTMLMessage,
} from 'react-intl';
import AppToaster from 'components/AppToaster';
import React from 'react';
import { DashboardContentTable, DashboardPageContent } from 'components';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import ExchangeRateTable from './ExchangeRateTable';
import ExchangeRateActionsBar from './ExchangeRateActionsBar';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withResourceActions from 'containers/Resources/withResourcesActions';
import withExchangeRatesActions from 'containers/ExchangeRates/withExchangeRatesActions';
import { compose } from 'utils';
function ExchangeRatesList({
// #withDashboardActions
changePageTitle,
// #withResourceActions
requestFetchResourceFields,
// #withExchangeRatesActions
requestFetchExchangeRates,
requestDeleteExchangeRate,
addExchangeRatesTableQueries,
requestDeleteBulkExchangeRates,
// #withDialog
openDialog,
}) {
const { id } = useParams();
const [deleteExchangeRate, setDeleteExchangeRate] = useState(false);
const [selectedRows, setSelectedRows] = useState([]);
const { formatMessage } = useIntl();
const [bulkDelete, setBulkDelete] = useState(false);
const [filter, setFilter] = useState({});
const fetchExchangeRates = useQuery('exchange-rates-table', () =>
requestFetchExchangeRates(),
);
useEffect(() => {
id
? changePageTitle(formatMessage({ id: 'exchange_rate_details' }))
: changePageTitle(formatMessage({ id: 'exchange_rates_list' }));
}, [id, changePageTitle, formatMessage]);
const handelDeleteExchangeRate = useCallback(
(exchange_rate) => {
setDeleteExchangeRate(exchange_rate);
},
[setDeleteExchangeRate],
);
const handelEditExchangeRate = (exchange_rate) => {
openDialog('exchangeRate-form', { action: 'edit', id: exchange_rate.id });
};
const handelCancelExchangeRateDelete = useCallback(() => {
setDeleteExchangeRate(false);
}, [setDeleteExchangeRate]);
const handelConfirmExchangeRateDelete = useCallback(() => {
requestDeleteExchangeRate(deleteExchangeRate.id)
.then(() => {
setDeleteExchangeRate(false);
AppToaster.show({
message: formatMessage({
id: 'the_exchange_rates_has_been_deleted_successfully',
}),
intent: Intent.SUCCESS,
});
})
.catch(() => {
setDeleteExchangeRate(false);
});
}, [deleteExchangeRate, requestDeleteExchangeRate, formatMessage]);
// Handle fetch data of Exchange_rates datatable.
const handleFetchData = useCallback(
({ pageIndex, pageSize, sortBy }) => {
addExchangeRatesTableQueries({
...(sortBy.length > 0
? {
column_sort_by: sortBy[0].id,
sort_order: sortBy[0].desc ? 'desc' : 'asc',
}
: {}),
});
},
[addExchangeRatesTableQueries],
);
// Handle selected rows change.
const handleSelectedRowsChange = useCallback(
(exchange_rates) => {
setSelectedRows(exchange_rates);
},
[setSelectedRows],
);
// Handle Exchange Rates bulk delete.
const handleBulkDelete = useCallback(
(exchangeRatesIds) => {
setBulkDelete(exchangeRatesIds);
},
[setBulkDelete],
);
//Handel cancel itemCategories bulk delete.
const handleCancelBulkDelete = useCallback(() => {
setBulkDelete(false);
}, []);
// handle confirm Exchange Rates bulk delete.
const handleConfirmBulkDelete = useCallback(() => {
requestDeleteBulkExchangeRates(bulkDelete)
.then(() => {
setBulkDelete(false);
AppToaster.show({
message: formatMessage({
id: 'the_exchange_rates_has_been_successfully_deleted',
}),
intent: Intent.SUCCESS,
});
})
.catch((errors) => {
setBulkDelete(false);
});
}, [requestDeleteBulkExchangeRates, bulkDelete, formatMessage]);
// Calculates the data table selected rows count.
const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [
selectedRows,
]);
import { ExchangeRatesProvider } from './ExchangeRatesProvider';
import ExchangeRatesAlerts from './ExchangeRatesAlerts';
/**
* Exchange Rates list.
*/
export default function ExchangeRatesList() {
return (
<DashboardInsider loading={fetchExchangeRates.isFetching}>
<ExchangeRateActionsBar
onDeleteExchangeRate={handelDeleteExchangeRate}
selectedRows={selectedRows}
onBulkDelete={handleBulkDelete}
/>
<ExchangeRatesProvider>
<ExchangeRateActionsBar />
<DashboardPageContent>
<ExchangeRateTable
onDeleteExchangeRate={handelDeleteExchangeRate}
onEditExchangeRate={handelEditExchangeRate}
onFetchData={handleFetchData}
onSelectedRowsChange={handleSelectedRowsChange}
/>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />}
icon="trash"
intent={Intent.DANGER}
isOpen={deleteExchangeRate}
onCancel={handelCancelExchangeRateDelete}
onConfirm={handelConfirmExchangeRateDelete}
>
<p>
<FormattedHTMLMessage
id={'once_delete_this_exchange_rate_you_will_able_to_restore_it'}
/>
</p>
</Alert>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={`${formatMessage({
id: 'delete',
})} (${selectedRowsCount})`}
icon="trash"
intent={Intent.DANGER}
isOpen={bulkDelete}
onCancel={handleCancelBulkDelete}
onConfirm={handleConfirmBulkDelete}
>
<p>
<FormattedHTMLMessage
id={
'once_delete_these_exchange_rates_you_will_not_able_restore_them'
}
/>
</p>
</Alert>
<DashboardContentTable>
<ExchangeRateTable />
</DashboardContentTable>
</DashboardPageContent>
</DashboardInsider>
<ExchangeRatesAlerts />
</ExchangeRatesProvider>
);
}
export default compose(
withExchangeRatesActions,
withResourceActions,
withDashboardActions,
withDialogActions,
)(ExchangeRatesList);

View File

@@ -0,0 +1,35 @@
import React, { createContext } from 'react';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import { useExchangeRates } from 'hooks/query';
const ExchangesRatesContext = createContext();
/**
* Exchanges rates list provider.
*/
function ExchangeRatesProvider({ query, ...props }) {
const {
data: { exchangesRates, pagination },
isFetching: isExchangeRatesFetching,
isLoading: isExchangeRatesLoading,
} = useExchangeRates();
const state = {
isExchangeRatesFetching,
isExchangeRatesLoading,
exchangesRates,
pagination,
query,
};
return (
<DashboardInsider name={'exchange-rate-list'}>
<ExchangesRatesContext.Provider value={state} {...props} />
</DashboardInsider>
);
}
const useExchangeRatesContext = () => React.useContext(ExchangesRatesContext);
export { ExchangeRatesProvider, useExchangeRatesContext };

View File

@@ -0,0 +1,94 @@
import React, { useMemo } from 'react';
import {
Menu,
Popover,
Button,
Position,
MenuItem,
MenuDivider,
Intent,
} from '@blueprintjs/core';
import { useIntl } from 'react-intl';
import { Icon, Money } from 'components';
import moment from 'moment';
import { safeCallback } from 'utils';
/**
* Row actions menu list.
*/
export function ActionMenuList({
row: { original },
payload: { onEditExchangeRate, onDeleteExchangeRate },
}) {
const { formatMessage } = useIntl();
return (
<Menu>
<MenuItem
icon={<Icon icon="pen-18" />}
text={formatMessage({ id: 'edit_exchange_rate' })}
onClick={safeCallback(onEditExchangeRate, original)}
/>
<MenuDivider />
<MenuItem
text={formatMessage({ id: 'delete_exchange_rate' })}
intent={Intent.DANGER}
onClick={safeCallback(onDeleteExchangeRate, original)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Menu>
);
}
/**
* Table actions cell.
*/
export function TableActionsCell(props) {
return (
<Popover
content={<ActionMenuList {...props} />}
position={Position.RIGHT_TOP}
>
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover>
);
}
export function useExchangeRatesTableColumns() {
const { formatMessage } = useIntl();
return useMemo(
() => [
{
id: 'date',
Header: formatMessage({ id: 'date' }),
accessor: (r) => moment(r.date).format('YYYY MMM DD'),
width: 150,
},
{
id: 'currency_code',
Header: formatMessage({ id: 'currency_code' }),
accessor: 'currency_code',
className: 'currency_code',
width: 150,
},
{
id: 'exchange_rate',
Header: formatMessage({ id: 'exchange_rate' }),
accessor: (r) => (
<Money amount={r.exchange_rate} currency={r.currency_code} />
),
className: 'exchange_rate',
width: 150,
},
{
id: 'actions',
Header: '',
Cell: TableActionsCell,
className: 'actions',
width: 50,
},
],
[formatMessage],
);
}

View File

@@ -0,0 +1,83 @@
import { useQuery, useMutation, useQueryClient } from 'react-query';
import { defaultTo } from 'lodash';
import useApiRequest from '../useRequest';
/**
* Creates a new exchange rate.
*/
export function useCreateExchangeRate(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation((values) => apiRequest.post('exchange_rates', values), {
onSuccess: () => {
queryClient.invalidateQueries('EXCHANGES_RATES');
},
...props,
});
}
/**
* Edits the exchange rate.
*/
export function useEdiExchangeRate(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation(
([id, values]) => apiRequest.post(`exchange_rates/${id}`, values),
{
onSuccess: () => {
queryClient.invalidateQueries('EXCHANGES_RATES');
},
...props,
},
);
}
/**
* Deletes the exchange rate.
*/
export function useDeleteExchangeRate(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation((id) => apiRequest.delete(`exchange_rates/${id}`), {
onSuccess: () => {
queryClient.invalidateQueries('EXCHANGES_RATES');
},
...props,
});
}
// Transforms items categories.
const transformExchangesRates = (response) => {
return {
exchangesRates: response.data.exchange_rates.results,
pagination: response.data.exchange_rates.pagination,
};
};
/**
* Retrieve the exchange rate list.
*/
export function useExchangeRates(query, props) {
const apiRequest = useApiRequest();
const states = useQuery(
['EXCHANGES_RATES', query],
() =>
apiRequest.get('exchange_rates', { params: { query } }).then(
transformExchangesRates,
),
props,
);
return {
...states,
data: defaultTo(states.data, {
exchangesRates: [],
pagination: {},
}),
};
}

View File

@@ -18,4 +18,5 @@ export * from './paymentReceives';
export * from './paymentMades';
export * from './settings';
export * from './users';
export * from './invite';
export * from './invite';
export * from './exchangeRates';

View File

@@ -167,6 +167,7 @@ export default [
path: `/exchange-rates`,
component: lazy(() => import('containers/ExchangeRates/ExchangeRatesList')),
breadcrumb: 'Exchange Rates',
pageTitle: formatMessage({ id: 'exchange_rates_list' }),
},
// Expenses.
{