feat: Auto re-calculate the items rate once changing the invoice exchange rate.

This commit is contained in:
Ahmed Bouhuolia
2023-10-16 19:14:27 +02:00
parent 1ed1c9ea1d
commit 9531730d7a
32 changed files with 473 additions and 1010 deletions

View File

@@ -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}

View File

@@ -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>
);
}

View File

@@ -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;
`;