mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 06:10:31 +00:00
feat(webapp): wip tax rates management
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
// @ts-nocheck
|
||||
import * as Yup from 'yup';
|
||||
|
||||
const getSchema = () =>
|
||||
Yup.object().shape({
|
||||
name: Yup.string().required().label('Name'),
|
||||
code: Yup.string().required().label('Code'),
|
||||
active: Yup.boolean().optional().label('Active'),
|
||||
describtion: Yup.string().optional().label('Description'),
|
||||
rate: Yup.number().required().label('Rate'),
|
||||
is_compound: Yup.boolean().optional().label('Is Compound'),
|
||||
is_non_recoverable: Yup.boolean().optional().label('Is Non Recoverable'),
|
||||
});
|
||||
|
||||
export const CreateTaxRateFormSchema = getSchema;
|
||||
export const EditTaxRateFormSchema = getSchema;
|
||||
@@ -0,0 +1,39 @@
|
||||
// @ts-nocheck
|
||||
import React, { lazy } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Dialog, DialogSuspense } from '@/components';
|
||||
import withDialogRedux from '@/components/DialogReduxConnect';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
const TaxRateFormDialogContent = lazy(
|
||||
() => import('./TaxRateFormDialogContent'),
|
||||
);
|
||||
|
||||
const TaxRateDialog = styled(Dialog)`
|
||||
max-width: 450px;
|
||||
`;
|
||||
|
||||
/**
|
||||
* Account form dialog.
|
||||
*/
|
||||
function TaxRateFormDialog({
|
||||
dialogName,
|
||||
payload = { action: '', id: null },
|
||||
isOpen,
|
||||
}) {
|
||||
return (
|
||||
<TaxRateDialog
|
||||
name={dialogName}
|
||||
title={payload.action === 'edit' ? 'Edit Tax Rate' : 'Create Tax Rate'}
|
||||
autoFocus={true}
|
||||
canEscapeKeyClose={true}
|
||||
isOpen={isOpen}
|
||||
>
|
||||
<DialogSuspense>
|
||||
<TaxRateFormDialogContent dialogName={dialogName} payload={payload} />
|
||||
</DialogSuspense>
|
||||
</TaxRateDialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDialogRedux())(TaxRateFormDialog);
|
||||
@@ -0,0 +1,37 @@
|
||||
// @ts-nocheck
|
||||
import React, { useState } from 'react';
|
||||
import { DialogContent } from '@/components';
|
||||
import { useTaxRates } from '@/hooks/query/taxRates';
|
||||
|
||||
const TaxRateFormDialogContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Money in dialog provider.
|
||||
*/
|
||||
function TaxRateFormDialogBoot({ ...props }) {
|
||||
const {
|
||||
data: taxRates,
|
||||
isLoading: isTaxRatesLoading,
|
||||
isSuccess: isTaxRatesSuccess,
|
||||
} = useTaxRates({});
|
||||
|
||||
// Provider data.
|
||||
const provider = {
|
||||
taxRates,
|
||||
isTaxRatesLoading,
|
||||
isTaxRatesSuccess,
|
||||
};
|
||||
|
||||
const isLoading = isTaxRatesLoading;
|
||||
|
||||
return (
|
||||
<DialogContent isLoading={isLoading}>
|
||||
<TaxRateFormDialogContext.Provider value={provider} {...props} />
|
||||
</DialogContent>
|
||||
);
|
||||
}
|
||||
|
||||
const useTaxRateFormDialogContext = () =>
|
||||
React.useContext(TaxRateFormDialogContext);
|
||||
|
||||
export { TaxRateFormDialogBoot, useTaxRateFormDialogContext };
|
||||
@@ -0,0 +1,15 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import TaxRateFormDialogForm from './TaxRateFormDialogForm';
|
||||
import { TaxRateFormDialogBoot } from './TaxRateFormDialogBoot';
|
||||
|
||||
/**
|
||||
* Account dialog content.
|
||||
*/
|
||||
export default function TaxRateFormDialogContent({ dialogName, payload }) {
|
||||
return (
|
||||
<TaxRateFormDialogBoot dialogName={dialogName} payload={payload}>
|
||||
<TaxRateFormDialogForm />
|
||||
</TaxRateFormDialogBoot>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// @ts-nocheck
|
||||
import React, { useCallback } from 'react';
|
||||
import { Classes, Intent } from '@blueprintjs/core';
|
||||
import { Form, Formik } from 'formik';
|
||||
import { AppToaster } from '@/components';
|
||||
|
||||
import TaxRateFormDialogFormContent from './TaxRateFormDialogFormContent';
|
||||
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import {
|
||||
CreateTaxRateFormSchema,
|
||||
EditTaxRateFormSchema,
|
||||
} from './TaxRateForm.schema';
|
||||
import { transformApiErrors, transformFormToReq } 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.
|
||||
*/
|
||||
function TaxRateFormDialogForm({
|
||||
// #withDialogActions
|
||||
closeDialog,
|
||||
}) {
|
||||
// Account form context.
|
||||
const {
|
||||
account,
|
||||
|
||||
payload,
|
||||
isNewMode,
|
||||
dialogName,
|
||||
} = useTaxRateFormDialogContext();
|
||||
|
||||
// Form validation schema in create and edit mode.
|
||||
const validationSchema = isNewMode
|
||||
? CreateTaxRateFormSchema
|
||||
: EditTaxRateFormSchema;
|
||||
|
||||
const { mutateAsync: createTaxRateMutate } = useCreateTaxRate();
|
||||
const { mutateAsync: editTaxRateMutate } = useEditTaxRate();
|
||||
|
||||
// Callbacks handles form submit.
|
||||
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||
const form = transformFormToReq(values);
|
||||
|
||||
// Handle request success.
|
||||
const handleSuccess = () => {
|
||||
closeDialog(dialogName);
|
||||
|
||||
AppToaster.show({
|
||||
message: 'The tax rate has been created successfully.',
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
};
|
||||
// Handle request error.
|
||||
const handleError = (error) => {
|
||||
const {
|
||||
response: {
|
||||
data: { errors },
|
||||
},
|
||||
} = error;
|
||||
|
||||
const errorsTransformed = transformApiErrors(errors);
|
||||
setErrors({ ...errorsTransformed });
|
||||
setSubmitting(false);
|
||||
};
|
||||
if (payload.accountId) {
|
||||
editTaxRateMutate([payload.accountId, form])
|
||||
.then(handleSuccess)
|
||||
.catch(handleError);
|
||||
} else {
|
||||
createTaxRateMutate({ ...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);
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik
|
||||
validationSchema={validationSchema}
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleFormSubmit}
|
||||
>
|
||||
<Form>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
<TaxRateFormDialogFormContent
|
||||
dialogName={dialogName}
|
||||
action={payload?.action}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
</div>
|
||||
<TaxRateFormDialogFormFooter />
|
||||
</Form>
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDialogActions)(TaxRateFormDialogForm);
|
||||
@@ -0,0 +1,77 @@
|
||||
import {
|
||||
FCheckbox,
|
||||
FFormGroup,
|
||||
FInputGroup,
|
||||
FieldHint,
|
||||
Hint,
|
||||
} from '@/components';
|
||||
import { Tag } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
export default function TaxRateFormDialogContent() {
|
||||
return (
|
||||
<div>
|
||||
<FFormGroup
|
||||
name={'name'}
|
||||
label={'Name'}
|
||||
labelInfo={<Tag minimal>Required</Tag>}
|
||||
subLabel={
|
||||
'The name as you would like it to appear in customers invoices.'
|
||||
}
|
||||
>
|
||||
<FInputGroup name={'name'} />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'code'}
|
||||
label={'Code'}
|
||||
labelInfo={<Tag minimal>Required</Tag>}
|
||||
>
|
||||
<FInputGroup name={'code'} />
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'rate'}
|
||||
label={'Rate (%)'}
|
||||
labelInfo={<Tag minimal>Required</Tag>}
|
||||
>
|
||||
<RateFormGroup
|
||||
name={'rate'}
|
||||
rightElement={<Tag minimal>%</Tag>}
|
||||
fill={false}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<FFormGroup
|
||||
name={'description'}
|
||||
label={'Description'}
|
||||
labelInfo={
|
||||
<FieldHint content="This description is for internal use only and will not be visiable to your customers." />
|
||||
}
|
||||
>
|
||||
<FInputGroup name={'description'} />
|
||||
</FFormGroup>
|
||||
|
||||
<CompoundFormGroup name={'is_compound'}>
|
||||
<FCheckbox label={'Is compound'} name={'is_compound'} />
|
||||
</CompoundFormGroup>
|
||||
|
||||
<CompoundFormGroup name={'is_non_recoverable'}>
|
||||
<FCheckbox label={'Is non recoverable'} name={'is_non_recoverable'} />
|
||||
</CompoundFormGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const RateFormGroup = styled(FInputGroup)`
|
||||
max-width: 100px;
|
||||
`;
|
||||
|
||||
const CompoundFormGroup = styled(FFormGroup)`
|
||||
margin-bottom: 0;
|
||||
`;
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import * as R from 'ramda';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { Button, Classes, Intent } from '@blueprintjs/core';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
|
||||
function TaxRateFormDialogFormFooterRoot({ closeDialog }) {
|
||||
const { isSubmitting } = useFormikContext();
|
||||
|
||||
const handleClose = () => {
|
||||
closeDialog(DialogsName.TaxRateForm);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
onClick={handleClose}
|
||||
style={{ minWidth: '75px' }}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
intent={Intent.PRIMARY}
|
||||
loading={isSubmitting}
|
||||
style={{ minWidth: '95px' }}
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const TaxRateFormDialogFormFooter = R.compose(withDialogActions)(
|
||||
TaxRateFormDialogFormFooterRoot,
|
||||
);
|
||||
@@ -0,0 +1,7 @@
|
||||
export const transformApiErrors = () => {
|
||||
return {};
|
||||
};
|
||||
|
||||
export const transformFormToReq = () => {
|
||||
return {};
|
||||
};
|
||||
Reference in New Issue
Block a user