mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 05:40:31 +00:00
feat: Auto re-calculate the items rate once changing the invoice exchange rate.
This commit is contained in:
@@ -34,7 +34,7 @@ function CustomerSelectRoot({
|
||||
<FSelect
|
||||
items={items}
|
||||
textAccessor={'display_name'}
|
||||
labelAccessor={'code'}
|
||||
labelAccessor={'currency_code'}
|
||||
valueAccessor={'id'}
|
||||
popoverProps={{ minimal: true, usePortal: true, inline: false }}
|
||||
createNewItemRenderer={maybeCreateNewItemRenderer}
|
||||
|
||||
@@ -3,8 +3,6 @@ import InviteUserDialog from '@/containers/Dialogs/InviteUserDialog';
|
||||
import UserFormDialog from '@/containers/Dialogs/UserFormDialog';
|
||||
import ItemCategoryDialog from '@/containers/Dialogs/ItemCategoryDialog';
|
||||
import CurrencyFormDialog from '@/containers/Dialogs/CurrencyFormDialog';
|
||||
import ExchangeRateFormDialog from '@/containers/Dialogs/ExchangeRateFormDialog';
|
||||
|
||||
import InventoryAdjustmentDialog from '@/containers/Dialogs/InventoryAdjustmentFormDialog';
|
||||
import PaymentViaVoucherDialog from '@/containers/Dialogs/PaymentViaVoucherDialog';
|
||||
import KeyboardShortcutsDialog from '@/containers/Dialogs/keyboardShortcutsDialog';
|
||||
@@ -47,6 +45,7 @@ import ProjectInvoicingFormDialog from '@/containers/Projects/containers/Project
|
||||
import ProjectBillableEntriesFormDialog from '@/containers/Projects/containers/ProjectBillableEntriesFormDialog';
|
||||
import TaxRateFormDialog from '@/containers/TaxRates/dialogs/TaxRateFormDialog/TaxRateFormDialog';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
import InvoiceExchangeRateChangeDialog from '@/containers/Sales/Invoices/InvoiceForm/Dialogs/InvoiceExchangeRateChangeDialog';
|
||||
|
||||
/**
|
||||
* Dialogs container.
|
||||
@@ -58,7 +57,6 @@ export default function DialogsContainer() {
|
||||
<CurrencyFormDialog dialogName={DialogsName.CurrencyForm} />
|
||||
<InviteUserDialog dialogName={DialogsName.InviteForm} />
|
||||
<UserFormDialog dialogName={DialogsName.UserForm} />
|
||||
<ExchangeRateFormDialog dialogName={DialogsName.ExchangeRateForm} />
|
||||
<ItemCategoryDialog dialogName={DialogsName.ItemCategoryForm} />
|
||||
<InventoryAdjustmentDialog
|
||||
dialogName={DialogsName.InventoryAdjustmentForm}
|
||||
@@ -137,6 +135,9 @@ export default function DialogsContainer() {
|
||||
dialogName={DialogsName.ProjectBillableEntriesForm}
|
||||
/>
|
||||
<TaxRateFormDialog dialogName={DialogsName.TaxRateForm} />
|
||||
<InvoiceExchangeRateChangeDialog
|
||||
dialogName={DialogsName.InvoiceExchangeRateChangeNotice}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,30 +1,154 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { ControlGroup } from '@blueprintjs/core';
|
||||
|
||||
import { useFormikContext } from 'formik';
|
||||
import {
|
||||
Button,
|
||||
Classes,
|
||||
ControlGroup,
|
||||
Intent,
|
||||
Popover,
|
||||
Spinner,
|
||||
} from '@blueprintjs/core';
|
||||
import { FlagIcon } from '../Tags';
|
||||
import { FMoneyInputGroup, FFormGroup } from '../Forms';
|
||||
import { useUncontrolled } from '@/hooks/useUncontrolled';
|
||||
|
||||
interface ExchangeRateValuesBag {
|
||||
oldExchangeRate: string;
|
||||
exchangeRate: string;
|
||||
}
|
||||
|
||||
interface ExchangeRateInputGroupProps {
|
||||
name: string;
|
||||
fromCurrency: string;
|
||||
toCurrency: string;
|
||||
isLoading?: boolean;
|
||||
|
||||
inputGroupProps?: any;
|
||||
formGroupProps?: any;
|
||||
|
||||
popoverRecalcConfirm?: boolean;
|
||||
|
||||
onRecalcConfirm: (bag: ExchangeRateValuesBag) => void;
|
||||
onCancel: (bag: ExchangeRateValuesBag) => void;
|
||||
|
||||
isConfirmPopoverOpen?: boolean;
|
||||
initialConfirmPopoverOpen?: boolean;
|
||||
onConfirmPopoverOpen?: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
export function ExchangeRateInputGroup({
|
||||
name,
|
||||
fromCurrency,
|
||||
toCurrency,
|
||||
isLoading,
|
||||
|
||||
inputGroupProps,
|
||||
formGroupProps,
|
||||
name,
|
||||
}) {
|
||||
|
||||
popoverRecalcConfirm = false,
|
||||
|
||||
onRecalcConfirm,
|
||||
onCancel,
|
||||
|
||||
isConfirmPopoverOpen,
|
||||
initialConfirmPopoverOpen,
|
||||
onConfirmPopoverOpen,
|
||||
}: ExchangeRateInputGroupProps) {
|
||||
const [isOpen, handlePopoverOpen] = useUncontrolled<boolean>({
|
||||
value: isConfirmPopoverOpen,
|
||||
initialValue: initialConfirmPopoverOpen,
|
||||
finalValue: false,
|
||||
onChange: onConfirmPopoverOpen,
|
||||
});
|
||||
const { values, setFieldValue } = useFormikContext();
|
||||
const [oldExchangeRate, setOldExchangeRate] = useState<string>('');
|
||||
|
||||
const exchangeRate = values[name];
|
||||
const exchangeRateValuesBag: ExchangeRateValuesBag = {
|
||||
exchangeRate,
|
||||
oldExchangeRate,
|
||||
};
|
||||
// Handle re-calc confirm button click.
|
||||
const handleRecalcConfirmBtn = () => {
|
||||
handlePopoverOpen(false);
|
||||
onRecalcConfirm && onRecalcConfirm(exchangeRateValuesBag);
|
||||
};
|
||||
// Handle cancel button click.
|
||||
const handleCancelBtn = () => {
|
||||
handlePopoverOpen(false);
|
||||
onCancel && onCancel(exchangeRateValuesBag);
|
||||
};
|
||||
// Handle exchange rate field blur.
|
||||
const handleExchangeRateFieldBlur = (value: string) => {
|
||||
if (value !== values[name]) {
|
||||
handlePopoverOpen(true);
|
||||
setFieldValue(name, value);
|
||||
setOldExchangeRate(values[name]);
|
||||
}
|
||||
};
|
||||
|
||||
const exchangeRateField = (
|
||||
<ExchangeRateField
|
||||
allowDecimals={true}
|
||||
allowNegativeValue={true}
|
||||
asyncControl={true}
|
||||
onChange={() => null}
|
||||
onBlur={handleExchangeRateFieldBlur}
|
||||
rightElement={isLoading && <Spinner size={16} />}
|
||||
{...inputGroupProps}
|
||||
name={name}
|
||||
/>
|
||||
);
|
||||
|
||||
const popoverConfirmContent = (
|
||||
<PopoverContent>
|
||||
<p>
|
||||
Are you want to re-calculate item prices based on this exchange rate
|
||||
</p>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
marginTop: 15,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
className={Classes.POPOVER_DISMISS}
|
||||
style={{ marginRight: 10 }}
|
||||
onClick={handleCancelBtn}
|
||||
small
|
||||
minimal
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
intent={Intent.WARNING}
|
||||
className={Classes.POPOVER_DISMISS}
|
||||
onClick={handleRecalcConfirmBtn}
|
||||
small
|
||||
>
|
||||
Re-calculate
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
);
|
||||
|
||||
return (
|
||||
<FFormGroup inline={true} {...formGroupProps} name={name}>
|
||||
<ControlGroup>
|
||||
<ExchangeRatePrepend>
|
||||
<ExchangeFlagIcon currencyCode={fromCurrency} /> 1 {fromCurrency} =
|
||||
</ExchangeRatePrepend>
|
||||
<ExchangeRateField
|
||||
allowDecimals={true}
|
||||
allowNegativeValue={true}
|
||||
{...inputGroupProps}
|
||||
name={name}
|
||||
/>
|
||||
|
||||
{popoverRecalcConfirm ? (
|
||||
<Popover isOpen={isOpen} content={popoverConfirmContent}>
|
||||
{exchangeRateField}
|
||||
</Popover>
|
||||
) : (
|
||||
exchangeRateField
|
||||
)}
|
||||
<ExchangeRateAppend>
|
||||
<ExchangeFlagIcon currencyCode={toCurrency} /> {toCurrency}
|
||||
</ExchangeRateAppend>
|
||||
@@ -34,7 +158,7 @@ export function ExchangeRateInputGroup({
|
||||
}
|
||||
|
||||
const ExchangeRateField = styled(FMoneyInputGroup)`
|
||||
max-width: 75px;
|
||||
max-width: 85px;
|
||||
`;
|
||||
|
||||
const ExchangeRateSideIcon = styled.div`
|
||||
@@ -57,3 +181,8 @@ const ExchangeFlagIcon = styled(FlagIcon)`
|
||||
margin-left: 5px;
|
||||
display: inline-block;
|
||||
`;
|
||||
|
||||
const PopoverContent = styled('div')`
|
||||
padding: 20px;
|
||||
width: 300px;
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user