mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 22:00:31 +00:00
feat(webapp): wip tax rate form dialog
This commit is contained in:
@@ -7,9 +7,14 @@ const getSchema = () =>
|
||||
code: Yup.string().required().label('Code'),
|
||||
active: Yup.boolean().optional().label('Active'),
|
||||
describtion: Yup.string().optional().label('Description'),
|
||||
rate: Yup.number().required().label('Rate'),
|
||||
rate: Yup.number()
|
||||
.min(0.01, 'Enter a rate percentage of at least 0.01%')
|
||||
.max(100, 'Enter a rate percentage of at most 100%')
|
||||
.required()
|
||||
.label('Rate'),
|
||||
is_compound: Yup.boolean().optional().label('Is Compound'),
|
||||
is_non_recoverable: Yup.boolean().optional().label('Is Non Recoverable'),
|
||||
confirm_edit: Yup.boolean().optional(),
|
||||
});
|
||||
|
||||
export const CreateTaxRateFormSchema = getSchema;
|
||||
|
||||
@@ -24,13 +24,16 @@ function TaxRateFormDialog({
|
||||
return (
|
||||
<TaxRateDialog
|
||||
name={dialogName}
|
||||
title={payload.action === 'edit' ? 'Edit Tax Rate' : 'Create Tax Rate'}
|
||||
title={payload.id ? 'Edit Tax Rate' : 'Create Tax Rate'}
|
||||
autoFocus={true}
|
||||
canEscapeKeyClose={true}
|
||||
isOpen={isOpen}
|
||||
>
|
||||
<DialogSuspense>
|
||||
<TaxRateFormDialogContent dialogName={dialogName} payload={payload} />
|
||||
<TaxRateFormDialogContent
|
||||
dialogName={dialogName}
|
||||
taxRateId={payload.id}
|
||||
/>
|
||||
</DialogSuspense>
|
||||
</TaxRateDialog>
|
||||
);
|
||||
|
||||
@@ -1,28 +1,51 @@
|
||||
// @ts-nocheck
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { DialogContent } from '@/components';
|
||||
import { useTaxRates } from '@/hooks/query/taxRates';
|
||||
import { useTaxRate, useTaxRates } from '@/hooks/query/taxRates';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
const TaxRateFormDialogContext = React.createContext();
|
||||
|
||||
interface TaxRateFormDialogBootProps {
|
||||
taxRateId: number;
|
||||
children?: JSX.Element;
|
||||
}
|
||||
|
||||
interface TaxRateFormDialogBootContext {
|
||||
taxRateId: number;
|
||||
taxRate: any;
|
||||
isTaxRateLoading: boolean;
|
||||
isTaxRateSuccess: boolean;
|
||||
isNewMode: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Money in dialog provider.
|
||||
*/
|
||||
function TaxRateFormDialogBoot({ ...props }) {
|
||||
function TaxRateFormDialogBoot({
|
||||
taxRateId,
|
||||
...props
|
||||
}: TaxRateFormDialogBootProps) {
|
||||
const {
|
||||
data: taxRates,
|
||||
isLoading: isTaxRatesLoading,
|
||||
isSuccess: isTaxRatesSuccess,
|
||||
} = useTaxRates({});
|
||||
data: taxRate,
|
||||
isLoading: isTaxRateLoading,
|
||||
isSuccess: isTaxRateSuccess,
|
||||
} = useTaxRate(taxRateId, {
|
||||
enabled: !!taxRateId,
|
||||
});
|
||||
|
||||
const isNewMode = !taxRateId;
|
||||
|
||||
// Provider data.
|
||||
const provider = {
|
||||
taxRates,
|
||||
isTaxRatesLoading,
|
||||
isTaxRatesSuccess,
|
||||
taxRateId,
|
||||
taxRate,
|
||||
isTaxRateLoading,
|
||||
isTaxRateSuccess,
|
||||
isNewMode,
|
||||
dialogName: DialogsName.TaxRateForm,
|
||||
};
|
||||
|
||||
const isLoading = isTaxRatesLoading;
|
||||
const isLoading = isTaxRateLoading;
|
||||
|
||||
return (
|
||||
<DialogContent isLoading={isLoading}>
|
||||
@@ -32,6 +55,6 @@ function TaxRateFormDialogBoot({ ...props }) {
|
||||
}
|
||||
|
||||
const useTaxRateFormDialogContext = () =>
|
||||
React.useContext(TaxRateFormDialogContext);
|
||||
React.useContext<TaxRateFormDialogBootContext>(TaxRateFormDialogContext);
|
||||
|
||||
export { TaxRateFormDialogBoot, useTaxRateFormDialogContext };
|
||||
|
||||
@@ -3,12 +3,20 @@ import React from 'react';
|
||||
import TaxRateFormDialogForm from './TaxRateFormDialogForm';
|
||||
import { TaxRateFormDialogBoot } from './TaxRateFormDialogBoot';
|
||||
|
||||
interface TaxRateFormDialogContentProps {
|
||||
dialogName: string;
|
||||
taxRateId: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Account dialog content.
|
||||
*/
|
||||
export default function TaxRateFormDialogContent({ dialogName, payload }) {
|
||||
export default function TaxRateFormDialogContent({
|
||||
dialogName,
|
||||
taxRateId,
|
||||
}: TaxRateFormDialogContentProps) {
|
||||
return (
|
||||
<TaxRateFormDialogBoot dialogName={dialogName} payload={payload}>
|
||||
<TaxRateFormDialogBoot dialogName={dialogName} taxRateId={taxRateId}>
|
||||
<TaxRateFormDialogForm />
|
||||
</TaxRateFormDialogBoot>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-nocheck
|
||||
import React, { useCallback } from 'react';
|
||||
import React from 'react';
|
||||
import { Classes, Intent } from '@blueprintjs/core';
|
||||
import { Form, Formik } from 'formik';
|
||||
import { AppToaster } from '@/components';
|
||||
@@ -11,21 +11,12 @@ import {
|
||||
CreateTaxRateFormSchema,
|
||||
EditTaxRateFormSchema,
|
||||
} from './TaxRateForm.schema';
|
||||
import { transformApiErrors, transformFormToReq } from './utils';
|
||||
import { transformApiErrors, transformFormToReq, transformTaxRateToForm } from './utils';
|
||||
import { useCreateTaxRate, useEditTaxRate } from '@/hooks/query/taxRates';
|
||||
import { useTaxRateFormDialogContext } from './TaxRateFormDialogBoot';
|
||||
import { TaxRateFormDialogFormFooter } from './TaxRateFormDialogFormFooter';
|
||||
import { compose, transformToForm } from '@/utils';
|
||||
|
||||
// Default initial form values.
|
||||
const defaultInitialValues = {
|
||||
name: '',
|
||||
code: '',
|
||||
rate: '',
|
||||
description: '',
|
||||
is_compound: false,
|
||||
is_non_recoverable: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Tax rate form dialog content.
|
||||
@@ -35,13 +26,8 @@ function TaxRateFormDialogForm({
|
||||
closeDialog,
|
||||
}) {
|
||||
// Account form context.
|
||||
const {
|
||||
account,
|
||||
|
||||
payload,
|
||||
isNewMode,
|
||||
dialogName,
|
||||
} = useTaxRateFormDialogContext();
|
||||
const { taxRate, taxRateId, isNewMode, dialogName } =
|
||||
useTaxRateFormDialogContext();
|
||||
|
||||
// Form validation schema in create and edit mode.
|
||||
const validationSchema = isNewMode
|
||||
@@ -76,30 +62,18 @@ function TaxRateFormDialogForm({
|
||||
setErrors({ ...errorsTransformed });
|
||||
setSubmitting(false);
|
||||
};
|
||||
if (payload.accountId) {
|
||||
editTaxRateMutate([payload.accountId, form])
|
||||
if (isNewMode) {
|
||||
createTaxRateMutate({ ...form })
|
||||
.then(handleSuccess)
|
||||
.catch(handleError);
|
||||
} else {
|
||||
createTaxRateMutate({ ...form })
|
||||
editTaxRateMutate([taxRateId, { ...form }])
|
||||
.then(handleSuccess)
|
||||
.catch(handleError);
|
||||
}
|
||||
};
|
||||
// Form initial values in create and edit mode.
|
||||
const initialValues = {
|
||||
...defaultInitialValues,
|
||||
/**
|
||||
* We only care about the fields in the form. Previously unfilled optional
|
||||
* values such as `notes` come back from the API as null, so remove those
|
||||
* as well.
|
||||
*/
|
||||
...transformToForm(account, defaultInitialValues),
|
||||
};
|
||||
// Handles dialog close.
|
||||
const handleClose = () => {
|
||||
closeDialog(dialogName);
|
||||
};
|
||||
const initialValues = transformTaxRateToForm(taxRate);
|
||||
|
||||
return (
|
||||
<Formik
|
||||
@@ -109,11 +83,7 @@ function TaxRateFormDialogForm({
|
||||
>
|
||||
<Form>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
<TaxRateFormDialogFormContent
|
||||
dialogName={dialogName}
|
||||
action={payload?.action}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
<TaxRateFormDialogFormContent />
|
||||
</div>
|
||||
<TaxRateFormDialogFormFooter />
|
||||
</Form>
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import {
|
||||
FCheckbox,
|
||||
FFormGroup,
|
||||
FInputGroup,
|
||||
FieldHint,
|
||||
Hint,
|
||||
} from '@/components';
|
||||
import { Tag } from '@blueprintjs/core';
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { Tag, Text } from '@blueprintjs/core';
|
||||
import styled from 'styled-components';
|
||||
import { FCheckbox, FFormGroup, FInputGroup, Hint } from '@/components';
|
||||
import { transformTaxRateCodeValue, useIsTaxRateChanged } from './utils';
|
||||
import { useTaxRateFormDialogContext } from './TaxRateFormDialogBoot';
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
* Tax rate form content.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export default function TaxRateFormDialogContent() {
|
||||
return (
|
||||
@@ -23,27 +21,23 @@ export default function TaxRateFormDialogContent() {
|
||||
subLabel={
|
||||
'The name as you would like it to appear in customers invoices.'
|
||||
}
|
||||
fastField={true}
|
||||
>
|
||||
<FInputGroup name={'name'} />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'code'}
|
||||
label={'Code'}
|
||||
labelInfo={<Tag minimal>Required</Tag>}
|
||||
>
|
||||
<FInputGroup name={'code'} />
|
||||
<FInputGroup name={'name'} fastField={true} />
|
||||
</FFormGroup>
|
||||
|
||||
<TaxRateCodeField />
|
||||
<FFormGroup
|
||||
name={'rate'}
|
||||
label={'Rate (%)'}
|
||||
labelInfo={<Tag minimal>Required</Tag>}
|
||||
fastField={true}
|
||||
>
|
||||
<RateFormGroup
|
||||
name={'rate'}
|
||||
rightElement={<Tag minimal>%</Tag>}
|
||||
fill={false}
|
||||
fastField={true}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
@@ -51,23 +45,81 @@ export default function TaxRateFormDialogContent() {
|
||||
name={'description'}
|
||||
label={'Description'}
|
||||
labelInfo={
|
||||
<FieldHint content="This description is for internal use only and will not be visiable to your customers." />
|
||||
<Hint content="This description is for internal use only and will not be visiable to your customers." />
|
||||
}
|
||||
fastField={true}
|
||||
>
|
||||
<FInputGroup name={'description'} />
|
||||
<FInputGroup name={'description'} fastField={true} />
|
||||
</FFormGroup>
|
||||
|
||||
<CompoundFormGroup name={'is_compound'}>
|
||||
<FCheckbox label={'Is compound'} name={'is_compound'} />
|
||||
<CompoundFormGroup name={'is_compound'} fastField={true}>
|
||||
<FCheckbox
|
||||
label={'Is compound'}
|
||||
name={'is_compound'}
|
||||
fastField={true}
|
||||
/>
|
||||
</CompoundFormGroup>
|
||||
|
||||
<CompoundFormGroup name={'is_non_recoverable'}>
|
||||
<FCheckbox label={'Is non recoverable'} name={'is_non_recoverable'} />
|
||||
<CompoundFormGroup name={'is_non_recoverable'} fastField={true}>
|
||||
<FCheckbox
|
||||
label={'Is non recoverable'}
|
||||
name={'is_non_recoverable'}
|
||||
fastField={true}
|
||||
/>
|
||||
</CompoundFormGroup>
|
||||
|
||||
<ConfirmEditingTaxRate />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tax rate code input group
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
function TaxRateCodeField() {
|
||||
const { setFieldValue } = useFormikContext();
|
||||
|
||||
// Handle the field change.
|
||||
const handleChange = (event) => {
|
||||
const transformedValue = transformTaxRateCodeValue(event.target.value);
|
||||
setFieldValue('code', transformedValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<FFormGroup
|
||||
name={'code'}
|
||||
label={'Code'}
|
||||
labelInfo={<Tag minimal>Required</Tag>}
|
||||
fastField={true}
|
||||
>
|
||||
<FInputGroup name={'code'} fastField={true} onChange={handleChange} />
|
||||
</FFormGroup>
|
||||
);
|
||||
}
|
||||
|
||||
function ConfirmEditingTaxRate() {
|
||||
const isTaxRateChanged = useIsTaxRateChanged();
|
||||
const { isNewMode } = useTaxRateFormDialogContext();
|
||||
|
||||
// Can't continue if it is new mode or tax rate not changed.
|
||||
if (!isTaxRateChanged || isNewMode) return null;
|
||||
|
||||
return (
|
||||
<EditWarningWrap>
|
||||
<Text color={'#766f58'}>Please Note:</Text>
|
||||
<ConfirmEditFormGroup name={'confirm_edit'}>
|
||||
<FCheckbox
|
||||
name={'confirm_edit'}
|
||||
label={`I understand that updating the
|
||||
tax will mark the existing tax inactive, create a new tax, and update
|
||||
it in the chosen transactions.`}
|
||||
/>
|
||||
</ConfirmEditFormGroup>
|
||||
</EditWarningWrap>
|
||||
);
|
||||
}
|
||||
|
||||
const RateFormGroup = styled(FInputGroup)`
|
||||
max-width: 100px;
|
||||
`;
|
||||
@@ -75,3 +127,18 @@ const RateFormGroup = styled(FInputGroup)`
|
||||
const CompoundFormGroup = styled(FFormGroup)`
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
|
||||
const EditWarningWrap = styled(`div`)`
|
||||
background: #fcf8ec;
|
||||
margin-left: -20px;
|
||||
margin-right: -20px;
|
||||
padding: 14px 20px;
|
||||
font-size: 13px;
|
||||
margin-top: 8px;
|
||||
border-top: 1px solid #f2eddf;
|
||||
border-bottom: 1px solid #f2eddf;
|
||||
`;
|
||||
|
||||
const ConfirmEditFormGroup = styled(FFormGroup)`
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import * as R from 'ramda';
|
||||
import { useFormikContext } from 'formik';
|
||||
|
||||
@@ -1,7 +1,60 @@
|
||||
// @ts-nocheck
|
||||
import { useFormikContext } from 'formik';
|
||||
import * as R from 'ramda';
|
||||
import { omit } from 'lodash';
|
||||
import { transformToForm } from '@/utils';
|
||||
|
||||
// Default initial form values.
|
||||
export const defaultInitialValues = {
|
||||
name: '',
|
||||
code: '',
|
||||
rate: '',
|
||||
description: '',
|
||||
is_compound: false,
|
||||
is_non_recoverable: false,
|
||||
confirm_edit: false,
|
||||
};
|
||||
|
||||
export const transformApiErrors = () => {
|
||||
return {};
|
||||
};
|
||||
|
||||
export const transformFormToReq = () => {
|
||||
return {};
|
||||
export const transformFormToReq = (form) => {
|
||||
return omit({ ...form }, ['confirm_edit']);
|
||||
};
|
||||
|
||||
export const useIsTaxRateChanged = () => {
|
||||
const { initialValues, values } = useFormikContext();
|
||||
|
||||
return initialValues.rate !== values.rate;
|
||||
};
|
||||
|
||||
const convertFormAttrsToBoolean = (form) => {
|
||||
return {
|
||||
...form,
|
||||
is_compound: !!form.is_compound,
|
||||
is_non_recoverable: !!form.is_non_recoverable,
|
||||
};
|
||||
};
|
||||
|
||||
export const transformTaxRateToForm = (taxRate) => {
|
||||
return R.compose(convertFormAttrsToBoolean)({
|
||||
...defaultInitialValues,
|
||||
/**
|
||||
* We only care about the fields in the form. Previously unfilled optional
|
||||
* values such as `notes` come back from the API as null, so remove those
|
||||
* as well.
|
||||
*/
|
||||
...transformToForm(taxRate, defaultInitialValues),
|
||||
});
|
||||
};
|
||||
|
||||
export const transformTaxRateCodeValue = (input: string) => {
|
||||
// Remove non-alphanumeric characters and spaces using a regular expression
|
||||
const cleanedString = input.replace(/\s+/g, '');
|
||||
|
||||
// Convert the cleaned string to uppercase
|
||||
const uppercasedString = cleanedString.toUpperCase();
|
||||
|
||||
return uppercasedString;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user