mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 14:50:32 +00:00
feat(webapp): wip tax rate form dialog
This commit is contained in:
@@ -1,12 +1,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import {
|
import { AppToaster, FormattedMessage as T } from '@/components';
|
||||||
AppToaster,
|
|
||||||
FormattedMessage as T,
|
|
||||||
FormattedHTMLMessage,
|
|
||||||
} from '@/components';
|
|
||||||
|
|
||||||
import { useDeleteTaxRate } from '@/hooks/query/taxRates';
|
import { useDeleteTaxRate } from '@/hooks/query/taxRates';
|
||||||
|
|
||||||
@@ -40,7 +35,6 @@ function TaxRateDeleteAlert({
|
|||||||
const handleCancelItemDelete = () => {
|
const handleCancelItemDelete = () => {
|
||||||
closeAlert(name);
|
closeAlert(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle confirm delete item.
|
// Handle confirm delete item.
|
||||||
const handleConfirmDeleteItem = () => {
|
const handleConfirmDeleteItem = () => {
|
||||||
deleteTaxRate(taxRateId)
|
deleteTaxRate(taxRateId)
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React, { useCallback } from 'react';
|
import React from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { Intent } from '@blueprintjs/core';
|
||||||
import {
|
import {
|
||||||
DataTable,
|
DataTable,
|
||||||
DashboardContentTable,
|
DashboardContentTable,
|
||||||
TableSkeletonHeader,
|
TableSkeletonHeader,
|
||||||
TableSkeletonRows,
|
TableSkeletonRows,
|
||||||
|
AppToaster,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
|
|
||||||
import withAlertsActions from '@/containers/Alert/withAlertActions';
|
import withAlertsActions from '@/containers/Alert/withAlertActions';
|
||||||
@@ -14,10 +15,6 @@ import withDialogActions from '@/containers/Dialog/withDialogActions';
|
|||||||
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
|
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
|
||||||
import withSettings from '@/containers/Settings/withSettings';
|
import withSettings from '@/containers/Settings/withSettings';
|
||||||
|
|
||||||
// import { useMemorizedColumnsWidths } from '@/hooks';
|
|
||||||
// import { ActionsMenu } from './components';
|
|
||||||
// import { useInvoicesListContext } from './InvoicesListProvider';
|
|
||||||
|
|
||||||
import { useTaxRatesTableColumns } from './_utils';
|
import { useTaxRatesTableColumns } from './_utils';
|
||||||
import { useTaxRatesLandingContext } from './TaxRatesLandingProvider';
|
import { useTaxRatesLandingContext } from './TaxRatesLandingProvider';
|
||||||
import { TaxRatesLandingEmptyState } from './TaxRatesLandingEmptyState';
|
import { TaxRatesLandingEmptyState } from './TaxRatesLandingEmptyState';
|
||||||
@@ -26,6 +23,10 @@ import { TaxRatesTableActionsMenu } from './_components';
|
|||||||
import { compose } from '@/utils';
|
import { compose } from '@/utils';
|
||||||
import { DRAWERS } from '@/constants/drawers';
|
import { DRAWERS } from '@/constants/drawers';
|
||||||
import { DialogsName } from '@/constants/dialogs';
|
import { DialogsName } from '@/constants/dialogs';
|
||||||
|
import {
|
||||||
|
useActivateTaxRate,
|
||||||
|
useInactivateTaxRate,
|
||||||
|
} from '@/hooks/query/taxRates';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoices datatable.
|
* Invoices datatable.
|
||||||
@@ -47,6 +48,9 @@ function TaxRatesDataTable({
|
|||||||
// Invoices table columns.
|
// Invoices table columns.
|
||||||
const columns = useTaxRatesTableColumns();
|
const columns = useTaxRatesTableColumns();
|
||||||
|
|
||||||
|
const { mutateAsync: activateTaxRateMutate } = useActivateTaxRate();
|
||||||
|
const { mutateAsync: inactivateTaxRateMutate } = useInactivateTaxRate();
|
||||||
|
|
||||||
// Handle delete tax rate.
|
// Handle delete tax rate.
|
||||||
const handleDeleteTaxRate = ({ id }) => {
|
const handleDeleteTaxRate = ({ id }) => {
|
||||||
openAlert('tax-rate-delete', { taxRateId: id });
|
openAlert('tax-rate-delete', { taxRateId: id });
|
||||||
@@ -63,6 +67,38 @@ function TaxRatesDataTable({
|
|||||||
const handleCellClick = (cell, event) => {
|
const handleCellClick = (cell, event) => {
|
||||||
openDrawer(DRAWERS.TAX_RATE_DETAILS, { taxRateId: cell.row.original.id });
|
openDrawer(DRAWERS.TAX_RATE_DETAILS, { taxRateId: cell.row.original.id });
|
||||||
};
|
};
|
||||||
|
// Handles activating the given tax rate.
|
||||||
|
const handleActivateTaxRate = (taxRate) => {
|
||||||
|
activateTaxRateMutate(taxRate.id)
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'The tax rate has been activated successfully.',
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'Something went wrong.',
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// Handles inactivating the given tax rate.
|
||||||
|
const handleInactivateTaxRate = (taxRate) => {
|
||||||
|
inactivateTaxRateMutate(taxRate.id)
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'The tax rate has been inactivated successfully.',
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'Something went wrong.',
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
// Display invoice empty status instead of the table.
|
// Display invoice empty status instead of the table.
|
||||||
if (isEmptyStatus) {
|
if (isEmptyStatus) {
|
||||||
return <TaxRatesLandingEmptyState />;
|
return <TaxRatesLandingEmptyState />;
|
||||||
@@ -93,6 +129,8 @@ function TaxRatesDataTable({
|
|||||||
onViewDetails: handleViewDetails,
|
onViewDetails: handleViewDetails,
|
||||||
onDelete: handleDeleteTaxRate,
|
onDelete: handleDeleteTaxRate,
|
||||||
onEdit: handleEditTaxRate,
|
onEdit: handleEditTaxRate,
|
||||||
|
onActivate: handleActivateTaxRate,
|
||||||
|
onInactivate: handleInactivateTaxRate,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</DashboardContentTable>
|
</DashboardContentTable>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { Intent, Menu, MenuDivider, MenuItem } from '@blueprintjs/core';
|
|||||||
* @returns {JSX.Element}
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
export function TaxRatesTableActionsMenu({
|
export function TaxRatesTableActionsMenu({
|
||||||
payload: { onEdit, onDelete, onViewDetails },
|
payload: { onEdit, onDelete, onViewDetails, onActivate, onInactivate },
|
||||||
row: { original },
|
row: { original },
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
@@ -28,6 +28,21 @@ export function TaxRatesTableActionsMenu({
|
|||||||
onClick={safeCallback(onEdit, original)}
|
onClick={safeCallback(onEdit, original)}
|
||||||
/>
|
/>
|
||||||
</Can>
|
</Can>
|
||||||
|
<MenuDivider />
|
||||||
|
{!original.active && (
|
||||||
|
<MenuItem
|
||||||
|
icon={<Icon icon="play-16" iconSize={16} />}
|
||||||
|
text={'Activate Tax Rate'}
|
||||||
|
onClick={safeCallback(onActivate, original)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{original.active && (
|
||||||
|
<MenuItem
|
||||||
|
icon={<Icon icon="pause-16" iconSize={16} />}
|
||||||
|
text={'Inactivate Tax Rate'}
|
||||||
|
onClick={safeCallback(onInactivate, original)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Can I={TaxRateAction.Delete} a={AbilitySubject.TaxRate}>
|
<Can I={TaxRateAction.Delete} a={AbilitySubject.TaxRate}>
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button, Intent, Tag, Icon } from '@blueprintjs/core';
|
import { Intent, Tag } from '@blueprintjs/core';
|
||||||
import { Align } from '@/constants';
|
import { Align } from '@/constants';
|
||||||
import { FormatDateCell } from '@/components';
|
|
||||||
|
|
||||||
const codeAccessor = (taxRate) => {
|
const codeAccessor = (taxRate) => {
|
||||||
return (
|
return (
|
||||||
@@ -13,19 +12,26 @@ const codeAccessor = (taxRate) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const statusAccessor = (taxRate) => {
|
const statusAccessor = (taxRate) => {
|
||||||
return (
|
return taxRate.active ? (
|
||||||
<Tag round={false} intent={Intent.SUCCESS}>
|
<Tag round={false} intent={Intent.SUCCESS}>
|
||||||
Active
|
Active
|
||||||
</Tag>
|
</Tag>
|
||||||
|
) : (
|
||||||
|
<Tag round={false} intent={Intent.NONE}>
|
||||||
|
Inactive
|
||||||
|
</Tag>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the tax rates table columns.
|
||||||
|
*/
|
||||||
export const useTaxRatesTableColumns = () => {
|
export const useTaxRatesTableColumns = () => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
Header: 'Name',
|
Header: 'Name',
|
||||||
accessor: 'name',
|
accessor: 'name',
|
||||||
width: 40,
|
width: 50,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Code',
|
Header: 'Code',
|
||||||
@@ -40,8 +46,8 @@ export const useTaxRatesTableColumns = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Description',
|
Header: 'Description',
|
||||||
accessor: () => <span>Specital tax for certain goods and services.</span>,
|
accessor: 'description',
|
||||||
width: 120,
|
width: 110,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: 'Status',
|
Header: 'Status',
|
||||||
|
|||||||
@@ -7,9 +7,14 @@ const getSchema = () =>
|
|||||||
code: Yup.string().required().label('Code'),
|
code: Yup.string().required().label('Code'),
|
||||||
active: Yup.boolean().optional().label('Active'),
|
active: Yup.boolean().optional().label('Active'),
|
||||||
describtion: Yup.string().optional().label('Description'),
|
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_compound: Yup.boolean().optional().label('Is Compound'),
|
||||||
is_non_recoverable: Yup.boolean().optional().label('Is Non Recoverable'),
|
is_non_recoverable: Yup.boolean().optional().label('Is Non Recoverable'),
|
||||||
|
confirm_edit: Yup.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CreateTaxRateFormSchema = getSchema;
|
export const CreateTaxRateFormSchema = getSchema;
|
||||||
|
|||||||
@@ -24,13 +24,16 @@ function TaxRateFormDialog({
|
|||||||
return (
|
return (
|
||||||
<TaxRateDialog
|
<TaxRateDialog
|
||||||
name={dialogName}
|
name={dialogName}
|
||||||
title={payload.action === 'edit' ? 'Edit Tax Rate' : 'Create Tax Rate'}
|
title={payload.id ? 'Edit Tax Rate' : 'Create Tax Rate'}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
canEscapeKeyClose={true}
|
canEscapeKeyClose={true}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
>
|
>
|
||||||
<DialogSuspense>
|
<DialogSuspense>
|
||||||
<TaxRateFormDialogContent dialogName={dialogName} payload={payload} />
|
<TaxRateFormDialogContent
|
||||||
|
dialogName={dialogName}
|
||||||
|
taxRateId={payload.id}
|
||||||
|
/>
|
||||||
</DialogSuspense>
|
</DialogSuspense>
|
||||||
</TaxRateDialog>
|
</TaxRateDialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,28 +1,51 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import { DialogContent } from '@/components';
|
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();
|
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.
|
* Money in dialog provider.
|
||||||
*/
|
*/
|
||||||
function TaxRateFormDialogBoot({ ...props }) {
|
function TaxRateFormDialogBoot({
|
||||||
|
taxRateId,
|
||||||
|
...props
|
||||||
|
}: TaxRateFormDialogBootProps) {
|
||||||
const {
|
const {
|
||||||
data: taxRates,
|
data: taxRate,
|
||||||
isLoading: isTaxRatesLoading,
|
isLoading: isTaxRateLoading,
|
||||||
isSuccess: isTaxRatesSuccess,
|
isSuccess: isTaxRateSuccess,
|
||||||
} = useTaxRates({});
|
} = useTaxRate(taxRateId, {
|
||||||
|
enabled: !!taxRateId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isNewMode = !taxRateId;
|
||||||
|
|
||||||
// Provider data.
|
// Provider data.
|
||||||
const provider = {
|
const provider = {
|
||||||
taxRates,
|
taxRateId,
|
||||||
isTaxRatesLoading,
|
taxRate,
|
||||||
isTaxRatesSuccess,
|
isTaxRateLoading,
|
||||||
|
isTaxRateSuccess,
|
||||||
|
isNewMode,
|
||||||
|
dialogName: DialogsName.TaxRateForm,
|
||||||
};
|
};
|
||||||
|
const isLoading = isTaxRateLoading;
|
||||||
const isLoading = isTaxRatesLoading;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogContent isLoading={isLoading}>
|
<DialogContent isLoading={isLoading}>
|
||||||
@@ -32,6 +55,6 @@ function TaxRateFormDialogBoot({ ...props }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const useTaxRateFormDialogContext = () =>
|
const useTaxRateFormDialogContext = () =>
|
||||||
React.useContext(TaxRateFormDialogContext);
|
React.useContext<TaxRateFormDialogBootContext>(TaxRateFormDialogContext);
|
||||||
|
|
||||||
export { TaxRateFormDialogBoot, useTaxRateFormDialogContext };
|
export { TaxRateFormDialogBoot, useTaxRateFormDialogContext };
|
||||||
|
|||||||
@@ -3,12 +3,20 @@ import React from 'react';
|
|||||||
import TaxRateFormDialogForm from './TaxRateFormDialogForm';
|
import TaxRateFormDialogForm from './TaxRateFormDialogForm';
|
||||||
import { TaxRateFormDialogBoot } from './TaxRateFormDialogBoot';
|
import { TaxRateFormDialogBoot } from './TaxRateFormDialogBoot';
|
||||||
|
|
||||||
|
interface TaxRateFormDialogContentProps {
|
||||||
|
dialogName: string;
|
||||||
|
taxRateId: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account dialog content.
|
* Account dialog content.
|
||||||
*/
|
*/
|
||||||
export default function TaxRateFormDialogContent({ dialogName, payload }) {
|
export default function TaxRateFormDialogContent({
|
||||||
|
dialogName,
|
||||||
|
taxRateId,
|
||||||
|
}: TaxRateFormDialogContentProps) {
|
||||||
return (
|
return (
|
||||||
<TaxRateFormDialogBoot dialogName={dialogName} payload={payload}>
|
<TaxRateFormDialogBoot dialogName={dialogName} taxRateId={taxRateId}>
|
||||||
<TaxRateFormDialogForm />
|
<TaxRateFormDialogForm />
|
||||||
</TaxRateFormDialogBoot>
|
</TaxRateFormDialogBoot>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React, { useCallback } from 'react';
|
import React from 'react';
|
||||||
import { Classes, Intent } from '@blueprintjs/core';
|
import { Classes, Intent } from '@blueprintjs/core';
|
||||||
import { Form, Formik } from 'formik';
|
import { Form, Formik } from 'formik';
|
||||||
import { AppToaster } from '@/components';
|
import { AppToaster } from '@/components';
|
||||||
@@ -11,21 +11,12 @@ import {
|
|||||||
CreateTaxRateFormSchema,
|
CreateTaxRateFormSchema,
|
||||||
EditTaxRateFormSchema,
|
EditTaxRateFormSchema,
|
||||||
} from './TaxRateForm.schema';
|
} from './TaxRateForm.schema';
|
||||||
import { transformApiErrors, transformFormToReq } from './utils';
|
import { transformApiErrors, transformFormToReq, transformTaxRateToForm } from './utils';
|
||||||
import { useCreateTaxRate, useEditTaxRate } from '@/hooks/query/taxRates';
|
import { useCreateTaxRate, useEditTaxRate } from '@/hooks/query/taxRates';
|
||||||
import { useTaxRateFormDialogContext } from './TaxRateFormDialogBoot';
|
import { useTaxRateFormDialogContext } from './TaxRateFormDialogBoot';
|
||||||
import { TaxRateFormDialogFormFooter } from './TaxRateFormDialogFormFooter';
|
import { TaxRateFormDialogFormFooter } from './TaxRateFormDialogFormFooter';
|
||||||
import { compose, transformToForm } from '@/utils';
|
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.
|
* Tax rate form dialog content.
|
||||||
@@ -35,13 +26,8 @@ function TaxRateFormDialogForm({
|
|||||||
closeDialog,
|
closeDialog,
|
||||||
}) {
|
}) {
|
||||||
// Account form context.
|
// Account form context.
|
||||||
const {
|
const { taxRate, taxRateId, isNewMode, dialogName } =
|
||||||
account,
|
useTaxRateFormDialogContext();
|
||||||
|
|
||||||
payload,
|
|
||||||
isNewMode,
|
|
||||||
dialogName,
|
|
||||||
} = useTaxRateFormDialogContext();
|
|
||||||
|
|
||||||
// Form validation schema in create and edit mode.
|
// Form validation schema in create and edit mode.
|
||||||
const validationSchema = isNewMode
|
const validationSchema = isNewMode
|
||||||
@@ -76,30 +62,18 @@ function TaxRateFormDialogForm({
|
|||||||
setErrors({ ...errorsTransformed });
|
setErrors({ ...errorsTransformed });
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
};
|
};
|
||||||
if (payload.accountId) {
|
if (isNewMode) {
|
||||||
editTaxRateMutate([payload.accountId, form])
|
createTaxRateMutate({ ...form })
|
||||||
.then(handleSuccess)
|
.then(handleSuccess)
|
||||||
.catch(handleError);
|
.catch(handleError);
|
||||||
} else {
|
} else {
|
||||||
createTaxRateMutate({ ...form })
|
editTaxRateMutate([taxRateId, { ...form }])
|
||||||
.then(handleSuccess)
|
.then(handleSuccess)
|
||||||
.catch(handleError);
|
.catch(handleError);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Form initial values in create and edit mode.
|
// Form initial values in create and edit mode.
|
||||||
const initialValues = {
|
const initialValues = transformTaxRateToForm(taxRate);
|
||||||
...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 (
|
return (
|
||||||
<Formik
|
<Formik
|
||||||
@@ -109,11 +83,7 @@ function TaxRateFormDialogForm({
|
|||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<div className={Classes.DIALOG_BODY}>
|
<div className={Classes.DIALOG_BODY}>
|
||||||
<TaxRateFormDialogFormContent
|
<TaxRateFormDialogFormContent />
|
||||||
dialogName={dialogName}
|
|
||||||
action={payload?.action}
|
|
||||||
onClose={handleClose}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<TaxRateFormDialogFormFooter />
|
<TaxRateFormDialogFormFooter />
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import {
|
// @ts-nocheck
|
||||||
FCheckbox,
|
|
||||||
FFormGroup,
|
|
||||||
FInputGroup,
|
|
||||||
FieldHint,
|
|
||||||
Hint,
|
|
||||||
} from '@/components';
|
|
||||||
import { Tag } from '@blueprintjs/core';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
import { Tag, Text } from '@blueprintjs/core';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { FCheckbox, FFormGroup, FInputGroup, Hint } from '@/components';
|
||||||
|
import { transformTaxRateCodeValue, useIsTaxRateChanged } from './utils';
|
||||||
|
import { useTaxRateFormDialogContext } from './TaxRateFormDialogBoot';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Tax rate form content.
|
||||||
* @returns
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
export default function TaxRateFormDialogContent() {
|
export default function TaxRateFormDialogContent() {
|
||||||
return (
|
return (
|
||||||
@@ -23,27 +21,23 @@ export default function TaxRateFormDialogContent() {
|
|||||||
subLabel={
|
subLabel={
|
||||||
'The name as you would like it to appear in customers invoices.'
|
'The name as you would like it to appear in customers invoices.'
|
||||||
}
|
}
|
||||||
|
fastField={true}
|
||||||
>
|
>
|
||||||
<FInputGroup name={'name'} />
|
<FInputGroup name={'name'} fastField={true} />
|
||||||
</FFormGroup>
|
|
||||||
|
|
||||||
<FFormGroup
|
|
||||||
name={'code'}
|
|
||||||
label={'Code'}
|
|
||||||
labelInfo={<Tag minimal>Required</Tag>}
|
|
||||||
>
|
|
||||||
<FInputGroup name={'code'} />
|
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
|
<TaxRateCodeField />
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
name={'rate'}
|
name={'rate'}
|
||||||
label={'Rate (%)'}
|
label={'Rate (%)'}
|
||||||
labelInfo={<Tag minimal>Required</Tag>}
|
labelInfo={<Tag minimal>Required</Tag>}
|
||||||
|
fastField={true}
|
||||||
>
|
>
|
||||||
<RateFormGroup
|
<RateFormGroup
|
||||||
name={'rate'}
|
name={'rate'}
|
||||||
rightElement={<Tag minimal>%</Tag>}
|
rightElement={<Tag minimal>%</Tag>}
|
||||||
fill={false}
|
fill={false}
|
||||||
|
fastField={true}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
@@ -51,23 +45,81 @@ export default function TaxRateFormDialogContent() {
|
|||||||
name={'description'}
|
name={'description'}
|
||||||
label={'Description'}
|
label={'Description'}
|
||||||
labelInfo={
|
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>
|
</FFormGroup>
|
||||||
|
|
||||||
<CompoundFormGroup name={'is_compound'}>
|
<CompoundFormGroup name={'is_compound'} fastField={true}>
|
||||||
<FCheckbox label={'Is compound'} name={'is_compound'} />
|
<FCheckbox
|
||||||
|
label={'Is compound'}
|
||||||
|
name={'is_compound'}
|
||||||
|
fastField={true}
|
||||||
|
/>
|
||||||
</CompoundFormGroup>
|
</CompoundFormGroup>
|
||||||
|
|
||||||
<CompoundFormGroup name={'is_non_recoverable'}>
|
<CompoundFormGroup name={'is_non_recoverable'} fastField={true}>
|
||||||
<FCheckbox label={'Is non recoverable'} name={'is_non_recoverable'} />
|
<FCheckbox
|
||||||
|
label={'Is non recoverable'}
|
||||||
|
name={'is_non_recoverable'}
|
||||||
|
fastField={true}
|
||||||
|
/>
|
||||||
</CompoundFormGroup>
|
</CompoundFormGroup>
|
||||||
|
|
||||||
|
<ConfirmEditingTaxRate />
|
||||||
</div>
|
</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)`
|
const RateFormGroup = styled(FInputGroup)`
|
||||||
max-width: 100px;
|
max-width: 100px;
|
||||||
`;
|
`;
|
||||||
@@ -75,3 +127,18 @@ const RateFormGroup = styled(FInputGroup)`
|
|||||||
const CompoundFormGroup = styled(FFormGroup)`
|
const CompoundFormGroup = styled(FFormGroup)`
|
||||||
margin-bottom: 0;
|
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 React from 'react';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { useFormikContext } from 'formik';
|
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 = () => {
|
export const transformApiErrors = () => {
|
||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transformFormToReq = () => {
|
export const transformFormToReq = (form) => {
|
||||||
return {};
|
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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,17 +27,29 @@ export default function TaxRateDetailsContentDetails() {
|
|||||||
<DetailItem
|
<DetailItem
|
||||||
label={'Non Recoverable'}
|
label={'Non Recoverable'}
|
||||||
children={
|
children={
|
||||||
<Tag round={false} intent={Intent.SUCCESS} minimal>
|
taxRate.is_non_recoverable ? (
|
||||||
Enabled
|
<Tag round={false} intent={Intent.SUCCESS} minimal>
|
||||||
</Tag>
|
Enabled
|
||||||
|
</Tag>
|
||||||
|
) : (
|
||||||
|
<Tag round={false} intent={Intent.NONE} minimal>
|
||||||
|
Disabled
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<DetailItem
|
<DetailItem
|
||||||
label={'Compound'}
|
label={'Compound'}
|
||||||
children={
|
children={
|
||||||
<Tag round={false} intent={Intent.SUCCESS} minimal>
|
taxRate.is_compound ? (
|
||||||
Enabled
|
<Tag round={false} intent={Intent.SUCCESS} minimal>
|
||||||
</Tag>
|
Enabled
|
||||||
|
</Tag>
|
||||||
|
) : (
|
||||||
|
<Tag round={false} intent={Intent.NONE} minimal>
|
||||||
|
Disabled
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</DetailsMenu>
|
</DetailsMenu>
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ import { useRequestQuery } from '../useQueryRequest';
|
|||||||
import QUERY_TYPES from './types';
|
import QUERY_TYPES from './types';
|
||||||
import useApiRequest from '../useRequest';
|
import useApiRequest from '../useRequest';
|
||||||
|
|
||||||
|
// Common invalidate queries.
|
||||||
|
const commonInvalidateQueries = (queryClient) => {
|
||||||
|
queryClient.invalidateQueries(QUERY_TYPES.TAX_RATES);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves tax rates.
|
* Retrieves tax rates.
|
||||||
* @param {number} customerId - Customer id.
|
* @param {number} customerId - Customer id.
|
||||||
@@ -52,9 +57,8 @@ export function useEditTaxRate(props) {
|
|||||||
([id, values]) => apiRequest.post(`tax-rates/${id}`, values),
|
([id, values]) => apiRequest.post(`tax-rates/${id}`, values),
|
||||||
{
|
{
|
||||||
onSuccess: (res, id) => {
|
onSuccess: (res, id) => {
|
||||||
// Invalidate specific item.
|
commonInvalidateQueries(queryClient);
|
||||||
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES, id]);
|
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES, id]);
|
||||||
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES]);
|
|
||||||
},
|
},
|
||||||
...props,
|
...props,
|
||||||
},
|
},
|
||||||
@@ -68,28 +72,58 @@ export function useCreateTaxRate(props) {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const apiRequest = useApiRequest();
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
return useMutation(([values]) => apiRequest.post('tax-rates', values), {
|
return useMutation((values) => apiRequest.post('tax-rates', values), {
|
||||||
onSuccess: (res, id) => {
|
onSuccess: (res, id) => {
|
||||||
// Invalidate specific item.
|
commonInvalidateQueries(queryClient);
|
||||||
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES, id]);
|
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES, id]);
|
||||||
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES]);
|
|
||||||
},
|
},
|
||||||
...props,
|
...props,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a new tax rate.
|
* Delete the given tax rate.
|
||||||
*/
|
*/
|
||||||
export function useDeleteTaxRate(props) {
|
export function useDeleteTaxRate(props) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const apiRequest = useApiRequest();
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
return useMutation(([id]) => apiRequest.delete(`tax-rates/${id}`), {
|
return useMutation((id) => apiRequest.post(`tax-rates/${id}`), {
|
||||||
onSuccess: (res, id) => {
|
onSuccess: (res, id) => {
|
||||||
// Invalidate specific item.
|
commonInvalidateQueries(queryClient);
|
||||||
|
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES, id]);
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the given tax rate.
|
||||||
|
*/
|
||||||
|
export function useActivateTaxRate(props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation((id) => apiRequest.post(`tax-rates/${id}/active`), {
|
||||||
|
onSuccess: (res, id) => {
|
||||||
|
commonInvalidateQueries(queryClient);
|
||||||
|
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES, id]);
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inactivate the given tax rate.
|
||||||
|
*/
|
||||||
|
export function useInactivateTaxRate(props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation((id) => apiRequest.delete(`tax-rates/${id}/inactive`), {
|
||||||
|
onSuccess: (res, id) => {
|
||||||
|
commonInvalidateQueries(queryClient);
|
||||||
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES, id]);
|
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES, id]);
|
||||||
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES]);
|
|
||||||
},
|
},
|
||||||
...props,
|
...props,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user