mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 05:40:31 +00:00
feat(webapp): wip tax rates management
This commit is contained in:
@@ -47,6 +47,7 @@ import ProjectExpenseForm from '@/containers/Projects/containers/ProjectExpenseF
|
||||
import EstimatedExpenseFormDialog from '@/containers/Projects/containers/EstimatedExpenseFormDialog';
|
||||
import ProjectInvoicingFormDialog from '@/containers/Projects/containers/ProjectInvoicingFormDialog';
|
||||
import ProjectBillableEntriesFormDialog from '@/containers/Projects/containers/ProjectBillableEntriesFormDialog';
|
||||
import TaxRateFormDialog from '@/containers/TaxRates/dialogs/TaxRateFormDialog/TaxRateFormDialog';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
/**
|
||||
@@ -134,7 +135,10 @@ export default function DialogsContainer() {
|
||||
<ProjectInvoicingFormDialog
|
||||
dialogName={DialogsName.ProjectInvoicingForm}
|
||||
/>
|
||||
<ProjectBillableEntriesFormDialog dialogName={DialogsName.ProjectBillableEntriesForm}/>
|
||||
<ProjectBillableEntriesFormDialog
|
||||
dialogName={DialogsName.ProjectBillableEntriesForm}
|
||||
/>
|
||||
<TaxRateFormDialog dialogName={DialogsName.TaxRateForm} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import VendorCreditDetailDrawer from '@/containers/Drawers/VendorCreditDetailDra
|
||||
import RefundCreditNoteDetailDrawer from '@/containers/Drawers/RefundCreditNoteDetailDrawer';
|
||||
import RefundVendorCreditDetailDrawer from '@/containers/Drawers/RefundVendorCreditDetailDrawer';
|
||||
import WarehouseTransferDetailDrawer from '@/containers/Drawers/WarehouseTransferDetailDrawer';
|
||||
import TaxRateDetailsDrawer from '@/containers/TaxRates/drawers/TaxRateDetailsDrawer/TaxRateDetailsDrawer';
|
||||
|
||||
import { DRAWERS } from '@/constants/drawers';
|
||||
|
||||
@@ -43,16 +44,25 @@ export default function DrawersContainer() {
|
||||
<ItemDetailDrawer name={DRAWERS.ITEM_DETAILS} />
|
||||
<CustomerDetailsDrawer name={DRAWERS.CUSTOMER_DETAILS} />
|
||||
<VendorDetailsDrawer name={DRAWERS.VENDOR_DETAILS} />
|
||||
<InventoryAdjustmentDetailDrawer name={DRAWERS.INVENTORY_ADJUSTMENT_DETAILS} />
|
||||
<CashflowTransactionDetailDrawer name={DRAWERS.CASHFLOW_TRNASACTION_DETAILS} />
|
||||
<InventoryAdjustmentDetailDrawer
|
||||
name={DRAWERS.INVENTORY_ADJUSTMENT_DETAILS}
|
||||
/>
|
||||
<CashflowTransactionDetailDrawer
|
||||
name={DRAWERS.CASHFLOW_TRNASACTION_DETAILS}
|
||||
/>
|
||||
<QuickCreateCustomerDrawer name={DRAWERS.QUICK_CREATE_CUSTOMER} />
|
||||
<QuickCreateItemDrawer name={DRAWERS.QUICK_CREATE_ITEM} />
|
||||
<QuickWriteVendorDrawer name={DRAWERS.QUICK_WRITE_VENDOR} />
|
||||
<CreditNoteDetailDrawer name={DRAWERS.CREDIT_NOTE_DETAILS} />
|
||||
<VendorCreditDetailDrawer name={DRAWERS.VENDOR_CREDIT_DETAILS} />
|
||||
<RefundCreditNoteDetailDrawer name={DRAWERS.REFUND_CREDIT_NOTE_DETAILS} />
|
||||
<RefundVendorCreditDetailDrawer name={DRAWERS.REFUND_VENDOR_CREDIT_DETAILS} />
|
||||
<WarehouseTransferDetailDrawer name={DRAWERS.WAREHOUSE_TRANSFER_DETAILS} />
|
||||
<RefundVendorCreditDetailDrawer
|
||||
name={DRAWERS.REFUND_VENDOR_CREDIT_DETAILS}
|
||||
/>
|
||||
<WarehouseTransferDetailDrawer
|
||||
name={DRAWERS.WAREHOUSE_TRANSFER_DETAILS}
|
||||
/>
|
||||
<TaxRateDetailsDrawer name={DRAWERS.TAX_RATE_DETAILS} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ export const AbilitySubject = {
|
||||
SubscriptionBilling: 'SubscriptionBilling',
|
||||
CreditNote: 'CreditNote',
|
||||
VendorCredit: 'VendorCredit',
|
||||
Project:'Project'
|
||||
Project:'Project',
|
||||
TaxRate: 'TaxRate',
|
||||
};
|
||||
|
||||
export const ItemAction = {
|
||||
@@ -186,3 +187,11 @@ export const SubscriptionBillingAbility = {
|
||||
View: 'view',
|
||||
Payment: 'payment',
|
||||
};
|
||||
|
||||
|
||||
export const TaxRateAction = {
|
||||
View: 'View',
|
||||
Create: 'Create',
|
||||
Edit: 'Edit',
|
||||
Delete: 'Delete',
|
||||
};
|
||||
|
||||
@@ -46,5 +46,6 @@ export enum DialogsName {
|
||||
EstimateExpenseForm = 'estimate-expense-form',
|
||||
ProjectInvoicingForm = 'project-invoicing-form',
|
||||
ProjectBillableEntriesForm = 'project-billable-entries',
|
||||
InvoiceNumberSettings = 'InvoiceNumberSettings'
|
||||
InvoiceNumberSettings = 'InvoiceNumberSettings',
|
||||
TaxRateForm = 'tax-rate-form',
|
||||
}
|
||||
|
||||
@@ -22,4 +22,5 @@ export enum DRAWERS {
|
||||
REFUND_CREDIT_NOTE_DETAILS = 'refund-credit-detail-drawer',
|
||||
REFUND_VENDOR_CREDIT_DETAILS = 'refund-vendor-detail-drawer',
|
||||
WAREHOUSE_TRANSFER_DETAILS = 'warehouse-transfer-detail-drawer',
|
||||
TAX_RATE_DETAILS = 'tax-rate-detail-drawer',
|
||||
}
|
||||
|
||||
@@ -406,6 +406,11 @@ export const SidebarMenu = [
|
||||
href: '/transactions-locking',
|
||||
type: ISidebarMenuItemType.Link,
|
||||
},
|
||||
{
|
||||
text: 'Tax Rates',
|
||||
href: '/tax-rates',
|
||||
type: ISidebarMenuItemType.Link,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -25,6 +25,7 @@ import WarehousesAlerts from '@/containers/Preferences/Warehouses/WarehousesAler
|
||||
import WarehousesTransfersAlerts from '@/containers/WarehouseTransfers/WarehousesTransfersAlerts';
|
||||
import BranchesAlerts from '@/containers/Preferences/Branches/BranchesAlerts';
|
||||
import ProjectAlerts from '@/containers/Projects/containers/ProjectAlerts';
|
||||
import TaxRatesAlerts from '@/containers/TaxRates/alerts';
|
||||
|
||||
export default [
|
||||
...AccountsAlerts,
|
||||
@@ -53,4 +54,5 @@ export default [
|
||||
...WarehousesTransfersAlerts,
|
||||
...BranchesAlerts,
|
||||
...ProjectAlerts,
|
||||
...TaxRatesAlerts
|
||||
];
|
||||
|
||||
@@ -25,14 +25,12 @@ import { InvoiceDetailsStatus } from './utils';
|
||||
export default function InvoiceDetailHeader() {
|
||||
const { invoice } = useInvoiceDetailDrawerContext();
|
||||
|
||||
const handleCustomerLinkClick = () => {};
|
||||
|
||||
return (
|
||||
<CommercialDocHeader>
|
||||
<CommercialDocTopHeader>
|
||||
<DetailsMenu>
|
||||
<AmountDetailItem label={intl.get('amount')}>
|
||||
<h3 class="big-number">{invoice.formatted_amount}</h3>
|
||||
<h3 class="big-number">{invoice.total_formatted}</h3>
|
||||
</AmountDetailItem>
|
||||
|
||||
<StatusDetailItem label={''}>
|
||||
@@ -75,11 +73,11 @@ export default function InvoiceDetailHeader() {
|
||||
textAlign={'right'}
|
||||
>
|
||||
<DetailItem label={intl.get('due_amount')}>
|
||||
<strong>{invoice.formatted_due_amount}</strong>
|
||||
<strong>{invoice.due_amount_formatted}</strong>
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem label={intl.get('invoice.details.payment_amount')}>
|
||||
<strong>{invoice.formatted_payment_amount}</strong>
|
||||
<strong>{invoice.payment_amount_formatted}</strong>
|
||||
</DetailItem>
|
||||
|
||||
<DetailItem
|
||||
|
||||
@@ -80,6 +80,9 @@ export function transformToEditForm(invoice) {
|
||||
|
||||
return {
|
||||
...transformToForm(invoice, defaultInvoice),
|
||||
inclusive_exclusive_tax: invoice.is_inclusive_tax
|
||||
? TaxType.Inclusive
|
||||
: TaxType.Exclusive,
|
||||
entries,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -225,8 +225,8 @@ export function useInvoicesTableColumns() {
|
||||
},
|
||||
{
|
||||
id: 'amount',
|
||||
Header: intl.get('balance'),
|
||||
accessor: 'formatted_amount',
|
||||
Header: intl.get('amount'),
|
||||
accessor: 'total_formatted',
|
||||
width: 120,
|
||||
align: 'right',
|
||||
clickable: true,
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Intent, Alert } from '@blueprintjs/core';
|
||||
import {
|
||||
AppToaster,
|
||||
FormattedMessage as T,
|
||||
FormattedHTMLMessage,
|
||||
} from '@/components';
|
||||
|
||||
import { useDeleteTaxRate } from '@/hooks/query/taxRates';
|
||||
|
||||
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||
import withItemsActions from '@/containers/Items/withItemsActions';
|
||||
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
import { DRAWERS } from '@/constants/drawers';
|
||||
|
||||
/**
|
||||
* Item delete alerts.
|
||||
*/
|
||||
function TaxRateDeleteAlert({
|
||||
name,
|
||||
|
||||
// #withAlertStoreConnect
|
||||
isOpen,
|
||||
payload: { taxRateId },
|
||||
|
||||
// #withAlertActions
|
||||
closeAlert,
|
||||
|
||||
// #withDrawerActions
|
||||
closeDrawer,
|
||||
}) {
|
||||
const { mutateAsync: deleteTaxRate, isLoading } = useDeleteTaxRate();
|
||||
|
||||
// Handle cancel delete item alert.
|
||||
const handleCancelItemDelete = () => {
|
||||
closeAlert(name);
|
||||
};
|
||||
|
||||
// Handle confirm delete item.
|
||||
const handleConfirmDeleteItem = () => {
|
||||
deleteTaxRate(taxRateId)
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: 'The tax rate has been deleted successfully.',
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
closeDrawer(DRAWERS.TAX_RATE_DETAILS);
|
||||
})
|
||||
.catch(
|
||||
({
|
||||
response: {
|
||||
data: { errors },
|
||||
},
|
||||
}) => {
|
||||
// handleDeleteErrors(errors);
|
||||
},
|
||||
)
|
||||
.finally(() => {
|
||||
closeAlert(name);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Alert
|
||||
cancelButtonText={<T id={'cancel'} />}
|
||||
confirmButtonText={<T id={'delete'} />}
|
||||
icon="trash"
|
||||
intent={Intent.DANGER}
|
||||
isOpen={isOpen}
|
||||
onCancel={handleCancelItemDelete}
|
||||
onConfirm={handleConfirmDeleteItem}
|
||||
loading={isLoading}
|
||||
>
|
||||
<p>
|
||||
Once you delete this tax rate, you won't be able to restore the item
|
||||
later.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Are you sure you want to delete ? If you're not sure, you can inactivate
|
||||
it instead.
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withAlertStoreConnect(),
|
||||
withAlertActions,
|
||||
withItemsActions,
|
||||
withDrawerActions,
|
||||
)(TaxRateDeleteAlert);
|
||||
11
packages/webapp/src/containers/TaxRates/alerts/index.ts
Normal file
11
packages/webapp/src/containers/TaxRates/alerts/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
|
||||
const TaxRateDeleteAlert = React.lazy(() => import('./TaxRateDeleteAlert'));
|
||||
|
||||
/**
|
||||
* Project alerts.
|
||||
*/
|
||||
export default [
|
||||
{ name: 'tax-rate-delete', component: TaxRateDeleteAlert },
|
||||
];
|
||||
@@ -0,0 +1,61 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { NavbarGroup, NavbarDivider, Button, Classes } from '@blueprintjs/core';
|
||||
import {
|
||||
DashboardActionsBar,
|
||||
FormattedMessage as T,
|
||||
Can,
|
||||
Icon,
|
||||
} from '@/components';
|
||||
import { AbilitySubject, TaxRateAction } from '@/constants/abilityOption';
|
||||
import { useTaxRatesLandingContext } from './TaxRatesLandingProvider';
|
||||
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
/**
|
||||
* Tax rates actions bar.
|
||||
*/
|
||||
function TaxRatesActionsBar({
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
}) {
|
||||
// Items list context.
|
||||
const {} = useTaxRatesLandingContext();
|
||||
|
||||
// Handle `new item` button click.
|
||||
const onClickNewItem = () => {
|
||||
openDialog(DialogsName.TaxRateForm);
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Can I={TaxRateAction.Create} a={AbilitySubject.TaxRate}>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="plus" />}
|
||||
text={'New Tax Rate'}
|
||||
onClick={onClickNewItem}
|
||||
/>
|
||||
</Can>
|
||||
<NavbarDivider />
|
||||
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-import-16" iconSize={16} />}
|
||||
text={<T id={'import'} />}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="file-export-16" iconSize={16} />}
|
||||
text={<T id={'export'} />}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDialogActions)(TaxRatesActionsBar);
|
||||
@@ -0,0 +1,39 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Button, Intent } from '@blueprintjs/core';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { EmptyStatus, Can, FormattedMessage as T } from '@/components';
|
||||
import { SaleInvoiceAction, AbilitySubject } from '@/constants/abilityOption';
|
||||
|
||||
export function TaxRatesLandingEmptyState() {
|
||||
const history = useHistory();
|
||||
|
||||
return (
|
||||
<EmptyStatus
|
||||
title={<T id={'the_organization_doesn_t_receive_money_yet'} />}
|
||||
description={
|
||||
<p>
|
||||
<T id={'invoices_empty_status_description'} />
|
||||
</p>
|
||||
}
|
||||
action={
|
||||
<>
|
||||
<Can I={SaleInvoiceAction.Create} a={AbilitySubject.Invoice}>
|
||||
<Button
|
||||
intent={Intent.PRIMARY}
|
||||
large={true}
|
||||
onClick={() => {
|
||||
history.push('/invoices/new');
|
||||
}}
|
||||
>
|
||||
<T id={'new_sale_invoice'} />
|
||||
</Button>
|
||||
<Button intent={Intent.NONE} large={true}>
|
||||
<T id={'learn_more'} />
|
||||
</Button>
|
||||
</Can>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { DashboardInsider } from '@/components/Dashboard';
|
||||
import { useTaxRates } from '@/hooks/query/taxRates';
|
||||
|
||||
const TaxRatesLandingContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Cash Flow data provider.
|
||||
*/
|
||||
function TaxRatesLandingProvider({ tableState, ...props }) {
|
||||
// Fetch cash flow list .
|
||||
const {
|
||||
data: taxRates,
|
||||
isFetching: isTaxRatesFetching,
|
||||
isLoading: isTaxRatesLoading,
|
||||
} = useTaxRates({}, { keepPreviousData: true });
|
||||
|
||||
// Detarmines whether the table should show empty state.
|
||||
const isEmptyStatus = isEmpty(taxRates) && !isTaxRatesLoading;
|
||||
|
||||
// Provider payload.
|
||||
const provider = {
|
||||
taxRates,
|
||||
isTaxRatesFetching,
|
||||
isTaxRatesLoading,
|
||||
isEmptyStatus
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardInsider name={'cashflow-accounts'}>
|
||||
<TaxRatesLandingContext.Provider value={provider} {...props} />
|
||||
</DashboardInsider>
|
||||
);
|
||||
}
|
||||
|
||||
const useTaxRatesLandingContext = () =>
|
||||
React.useContext(TaxRatesLandingContext);
|
||||
|
||||
export { TaxRatesLandingProvider, useTaxRatesLandingContext };
|
||||
@@ -0,0 +1,110 @@
|
||||
// @ts-nocheck
|
||||
import React, { useCallback } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
DataTable,
|
||||
DashboardContentTable,
|
||||
TableSkeletonHeader,
|
||||
TableSkeletonRows,
|
||||
} from '@/components';
|
||||
|
||||
import withAlertsActions from '@/containers/Alert/withAlertActions';
|
||||
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
|
||||
import withSettings from '@/containers/Settings/withSettings';
|
||||
|
||||
// import { useMemorizedColumnsWidths } from '@/hooks';
|
||||
// import { ActionsMenu } from './components';
|
||||
// import { useInvoicesListContext } from './InvoicesListProvider';
|
||||
|
||||
import { useTaxRatesTableColumns } from './_utils';
|
||||
import { useTaxRatesLandingContext } from './TaxRatesLandingProvider';
|
||||
import { TaxRatesLandingEmptyState } from './TaxRatesLandingEmptyState';
|
||||
import { TaxRatesTableActionsMenu } from './_components';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
import { DRAWERS } from '@/constants/drawers';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
/**
|
||||
* Invoices datatable.
|
||||
*/
|
||||
function TaxRatesDataTable({
|
||||
// #withAlertsActions
|
||||
openAlert,
|
||||
|
||||
// #withDrawerActions
|
||||
openDrawer,
|
||||
|
||||
// #withDialogAction
|
||||
openDialog,
|
||||
}) {
|
||||
// Invoices list context.
|
||||
const { taxRates, isTaxRatesLoading, isEmptyStatus } =
|
||||
useTaxRatesLandingContext();
|
||||
|
||||
// Invoices table columns.
|
||||
const columns = useTaxRatesTableColumns();
|
||||
|
||||
// Handle delete tax rate.
|
||||
const handleDeleteTaxRate = ({ id }) => {
|
||||
openAlert('tax-rate-delete', { taxRateId: id });
|
||||
};
|
||||
// Handle edit tax rate.
|
||||
const handleEditTaxRate = (taxRate) => {
|
||||
openDialog(DialogsName.TaxRateForm, { id: taxRate.id });
|
||||
};
|
||||
// Handle view details tax rate.
|
||||
const handleViewDetails = (taxRate) => {
|
||||
openDrawer(DRAWERS.TAX_RATE_DETAILS, { taxRateId: taxRate.id });
|
||||
};
|
||||
// Handle table cell click.
|
||||
const handleCellClick = (cell, event) => {
|
||||
openDrawer(DRAWERS.TAX_RATE_DETAILS, { taxRateId: cell.row.original.id });
|
||||
};
|
||||
// Display invoice empty status instead of the table.
|
||||
if (isEmptyStatus) {
|
||||
return <TaxRatesLandingEmptyState />;
|
||||
}
|
||||
|
||||
return (
|
||||
<DashboardContentTable>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={taxRates}
|
||||
loading={isTaxRatesLoading}
|
||||
headerLoading={isTaxRatesLoading}
|
||||
progressBarLoading={isTaxRatesLoading}
|
||||
manualSortBy={false}
|
||||
selectionColumn={false}
|
||||
noInitialFetch={true}
|
||||
sticky={true}
|
||||
pagination={false}
|
||||
manualPagination={false}
|
||||
autoResetSortBy={false}
|
||||
autoResetPage={false}
|
||||
TableLoadingRenderer={TableSkeletonRows}
|
||||
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||
ContextMenu={TaxRatesTableActionsMenu}
|
||||
onCellClick={handleCellClick}
|
||||
size={'medium'}
|
||||
payload={{
|
||||
onViewDetails: handleViewDetails,
|
||||
onDelete: handleDeleteTaxRate,
|
||||
onEdit: handleEditTaxRate,
|
||||
}}
|
||||
/>
|
||||
</DashboardContentTable>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withDashboardActions,
|
||||
withAlertsActions,
|
||||
withDrawerActions,
|
||||
withDialogActions,
|
||||
withSettings(({ invoiceSettings }) => ({
|
||||
invoicesTableSize: invoiceSettings?.tableSize,
|
||||
})),
|
||||
)(TaxRatesDataTable);
|
||||
@@ -0,0 +1,42 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Can, Icon } from '@/components';
|
||||
import { AbilitySubject, TaxRateAction } from '@/constants/abilityOption';
|
||||
import { safeCallback } from '@/utils';
|
||||
import { Intent, Menu, MenuDivider, MenuItem } from '@blueprintjs/core';
|
||||
|
||||
/**
|
||||
* Tax rates table actions menu.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export function TaxRatesTableActionsMenu({
|
||||
payload: { onEdit, onDelete, onViewDetails },
|
||||
row: { original },
|
||||
}) {
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={<Icon icon="reader-18" />}
|
||||
text={'View Details'}
|
||||
onClick={safeCallback(onViewDetails, original)}
|
||||
/>
|
||||
<Can I={TaxRateAction.Edit} a={AbilitySubject.TaxRate}>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={'Edit Tax Rate'}
|
||||
onClick={safeCallback(onEdit, original)}
|
||||
/>
|
||||
</Can>
|
||||
<Can I={TaxRateAction.Delete} a={AbilitySubject.TaxRate}>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
text={'Delete Tax Rate'}
|
||||
intent={Intent.DANGER}
|
||||
onClick={safeCallback(onDelete, original)}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
/>
|
||||
</Can>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Button, Intent, Tag, Icon } from '@blueprintjs/core';
|
||||
import { Align } from '@/constants';
|
||||
import { FormatDateCell } from '@/components';
|
||||
|
||||
const codeAccessor = (taxRate) => {
|
||||
return (
|
||||
<Tag minimal={true} round={false} intent={Intent.NONE} interactive={true}>
|
||||
{taxRate.code}
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
|
||||
const statusAccessor = (taxRate) => {
|
||||
return (
|
||||
<Tag round={false} intent={Intent.SUCCESS}>
|
||||
Active
|
||||
</Tag>
|
||||
);
|
||||
};
|
||||
|
||||
export const useTaxRatesTableColumns = () => {
|
||||
return [
|
||||
{
|
||||
Header: 'Name',
|
||||
accessor: 'name',
|
||||
width: 40,
|
||||
},
|
||||
{
|
||||
Header: 'Code',
|
||||
accessor: codeAccessor,
|
||||
width: 40,
|
||||
},
|
||||
{
|
||||
Header: 'Rate',
|
||||
accessor: 'rate_formatted',
|
||||
align: Align.Right,
|
||||
width: 30,
|
||||
},
|
||||
{
|
||||
Header: 'Description',
|
||||
accessor: () => <span>Specital tax for certain goods and services.</span>,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
Header: 'Status',
|
||||
accessor: statusAccessor,
|
||||
width: 30,
|
||||
align: Align.Right,
|
||||
},
|
||||
];
|
||||
};
|
||||
@@ -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 {};
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import TaxRateDetailsContentActionsBar from './TaxRateDetailsContentActionsBar';
|
||||
import { TaxRateDetailsContentBoot } from './TaxRateDetailsContentBoot';
|
||||
import { DrawerBody, DrawerHeaderContent } from '@/components';
|
||||
import TaxRateDetailsContentDetails from './TaxRateDetailsContentDetails';
|
||||
import { DRAWERS } from '@/constants/drawers';
|
||||
|
||||
interface TaxRateDetailsContentProps {
|
||||
taxRateid: number;
|
||||
}
|
||||
|
||||
export default function TaxRateDetailsContent({
|
||||
taxRateId,
|
||||
}: TaxRateDetailsContentProps) {
|
||||
return (
|
||||
<TaxRateDetailsContentBoot taxRateId={taxRateId}>
|
||||
<DrawerHeaderContent
|
||||
name={DRAWERS.TAX_RATE_DETAILS}
|
||||
title={'Tax Rate Details'}
|
||||
/>
|
||||
<TaxRateDetailsContentActionsBar />
|
||||
|
||||
<DrawerBody>
|
||||
<TaxRateDetailsContentDetails />
|
||||
</DrawerBody>
|
||||
</TaxRateDetailsContentBoot>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import {
|
||||
Button,
|
||||
Classes,
|
||||
Intent,
|
||||
NavbarDivider,
|
||||
NavbarGroup,
|
||||
} from '@blueprintjs/core';
|
||||
import * as R from 'ramda';
|
||||
import { Can, DashboardActionsBar, Icon } from '@/components';
|
||||
import { AbilitySubject, TaxRateAction } from '@/constants/abilityOption';
|
||||
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||
import withAlertsActions from '@/containers/Alert/withAlertActions';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import { useTaxRateDetailsContext } from './TaxRateDetailsContentBoot';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
|
||||
/**
|
||||
* Tax rate details content actions bar.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
function TaxRateDetailsContentActionsBar({
|
||||
// #withDrawerActions
|
||||
openDialog,
|
||||
|
||||
// #withAlertsActions
|
||||
openAlert,
|
||||
}) {
|
||||
const { taxRateId } = useTaxRateDetailsContext();
|
||||
|
||||
// Handle edit tax rate.
|
||||
const handleEditTaxRate = () => {
|
||||
openDialog(DialogsName.TaxRateForm, { id: taxRateId });
|
||||
};
|
||||
// Handle delete tax rate.
|
||||
const handleDeleteTaxRate = () => {
|
||||
openAlert('tax-rate-delete', { taxRateId });
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Can I={TaxRateAction.Edit} a={AbilitySubject.TaxRate}>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={'Edit Tax Rate'}
|
||||
onClick={handleEditTaxRate}
|
||||
/>
|
||||
</Can>
|
||||
<Can I={TaxRateAction.Delete} a={AbilitySubject.Item}>
|
||||
<NavbarDivider />
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
text={'Delete'}
|
||||
icon={<Icon icon={'trash-16'} iconSize={16} />}
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDeleteTaxRate}
|
||||
/>
|
||||
</Can>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default R.compose(
|
||||
withDrawerActions,
|
||||
withDialogActions,
|
||||
withAlertsActions
|
||||
)(TaxRateDetailsContentActionsBar);
|
||||
@@ -0,0 +1,40 @@
|
||||
// @ts-nocheck
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import { DrawerLoading } from '@/components';
|
||||
import { useTaxRate } from '@/hooks/query/taxRates';
|
||||
|
||||
const TaxRateDetailsContext = createContext();
|
||||
|
||||
interface TaxRateDetailsContentBootProps {
|
||||
taxRateId: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tax rate details content boot.
|
||||
* @returns {JSX}
|
||||
*/
|
||||
export function TaxRateDetailsContentBoot({
|
||||
taxRateId,
|
||||
...props
|
||||
}: TaxRateDetailsContentBootProps) {
|
||||
const {
|
||||
data: taxRate,
|
||||
isFetching: isTaxRateFetching,
|
||||
isLoading: isTaxRateLoading,
|
||||
} = useTaxRate(taxRateId, { keepPreviousData: true });
|
||||
|
||||
const provider = {
|
||||
isTaxRateLoading,
|
||||
isTaxRateFetching,
|
||||
taxRate,
|
||||
taxRateId,
|
||||
};
|
||||
|
||||
return (
|
||||
<DrawerLoading loading={isTaxRateLoading}>
|
||||
<TaxRateDetailsContext.Provider value={provider} {...props} />
|
||||
</DrawerLoading>
|
||||
);
|
||||
}
|
||||
|
||||
export const useTaxRateDetailsContext = () => useContext(TaxRateDetailsContext);
|
||||
@@ -0,0 +1,68 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Card, DetailItem, DetailsMenu } from '@/components';
|
||||
import { useTaxRateDetailsContext } from './TaxRateDetailsContentBoot';
|
||||
import { Intent, Tag } from '@blueprintjs/core';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export default function TaxRateDetailsContentDetails() {
|
||||
const { taxRate } = useTaxRateDetailsContext();
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<div>
|
||||
<TaxRateHeader>
|
||||
<TaxRateAmount>{taxRate.rate}%</TaxRateAmount>
|
||||
<TaxRateActiveTag round={false} intent={Intent.SUCCESS} minimal>
|
||||
Active
|
||||
</TaxRateActiveTag>
|
||||
</TaxRateHeader>
|
||||
<DetailsMenu direction={'horizantal'} minLabelSize={200}>
|
||||
<DetailItem label={'Tax Rate Name'} children={taxRate.name} />
|
||||
<DetailItem label={'Code'} children={taxRate.code} />
|
||||
<DetailItem
|
||||
label={'Description'}
|
||||
children={taxRate.description || '-'}
|
||||
/>
|
||||
<DetailItem
|
||||
label={'Non Recoverable'}
|
||||
children={
|
||||
<Tag round={false} intent={Intent.SUCCESS} minimal>
|
||||
Enabled
|
||||
</Tag>
|
||||
}
|
||||
/>
|
||||
<DetailItem
|
||||
label={'Compound'}
|
||||
children={
|
||||
<Tag round={false} intent={Intent.SUCCESS} minimal>
|
||||
Enabled
|
||||
</Tag>
|
||||
}
|
||||
/>
|
||||
</DetailsMenu>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
const TaxRateHeader = styled(`div`)`
|
||||
margin-bottom: 1.25rem;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-top: 0.25rem;
|
||||
`;
|
||||
|
||||
const TaxRateAmount = styled('div')`
|
||||
line-height: 1;
|
||||
font-size: 30px;
|
||||
color: #565b71;
|
||||
font-weight: 600;
|
||||
display: inline-block;
|
||||
`;
|
||||
|
||||
const TaxRateActiveTag = styled(Tag)`
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
margin-left: 1rem;
|
||||
`;
|
||||
@@ -0,0 +1,35 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import * as R from 'ramda';
|
||||
import { Drawer, DrawerHeaderContent, DrawerSuspense } from '@/components';
|
||||
import withDrawers from '@/containers/Drawer/withDrawers';
|
||||
import { DRAWERS } from '@/constants/drawers';
|
||||
|
||||
const TaxRateDetailsDrawerContent = React.lazy(
|
||||
() => import('./TaxRateDetailsContent'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Tax rate details drawer.
|
||||
*/
|
||||
function TaxRateDetailsDrawer({
|
||||
name,
|
||||
// #withDrawer
|
||||
isOpen,
|
||||
payload: { taxRateId },
|
||||
}) {
|
||||
return (
|
||||
<Drawer
|
||||
isOpen={isOpen}
|
||||
name={name}
|
||||
style={{ minWidth: '650px', maxWidth: '650px' }}
|
||||
size={'65%'}
|
||||
>
|
||||
<DrawerSuspense>
|
||||
<TaxRateDetailsDrawerContent name={name} taxRateId={taxRateId} />
|
||||
</DrawerSuspense>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
export default R.compose(withDrawers())(TaxRateDetailsDrawer);
|
||||
@@ -0,0 +1,23 @@
|
||||
// @ts-nocheck
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { DashboardPageContent } from '@/components';
|
||||
import { TaxRatesLandingProvider } from '../containers/TaxRatesLandingProvider';
|
||||
import TaxRatesLandingActionsBar from '../containers/TaxRatesLandingActionsBar';
|
||||
import TaxRatesDataTable from '../containers/TaxRatesLandingTable';
|
||||
|
||||
/**
|
||||
* Tax rates landing page.
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
export default function TaxRatesLanding() {
|
||||
return (
|
||||
<TaxRatesLandingProvider>
|
||||
<TaxRatesLandingActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<TaxRatesDataTable />
|
||||
</DashboardPageContent>
|
||||
</TaxRatesLandingProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
// @ts-nocheck
|
||||
import { useMutation, useQueryClient } from 'react-query';
|
||||
import { useRequestQuery } from '../useQueryRequest';
|
||||
import QUERY_TYPES from './types';
|
||||
import useApiRequest from '../useRequest';
|
||||
|
||||
/**
|
||||
* Retrieves tax rates.
|
||||
@@ -20,3 +22,75 @@ export function useTaxRates(props) {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves tax rate.
|
||||
* @param {number} taxRateId - Tax rate id.
|
||||
*/
|
||||
export function useTaxRate(taxRateId: string, props) {
|
||||
return useRequestQuery(
|
||||
[QUERY_TYPES.TAX_RATES, taxRateId],
|
||||
{
|
||||
method: 'get',
|
||||
url: `tax-rates/${taxRateId}}`,
|
||||
},
|
||||
{
|
||||
select: (res) => res.data.data,
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the given tax rate.
|
||||
*/
|
||||
export function useEditTaxRate(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation(
|
||||
([id, values]) => apiRequest.post(`tax-rates/${id}`, values),
|
||||
{
|
||||
onSuccess: (res, id) => {
|
||||
// Invalidate specific item.
|
||||
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES, id]);
|
||||
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES]);
|
||||
},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new tax rate.
|
||||
*/
|
||||
export function useCreateTaxRate(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation(([values]) => apiRequest.post('tax-rates', values), {
|
||||
onSuccess: (res, id) => {
|
||||
// Invalidate specific item.
|
||||
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES, id]);
|
||||
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES]);
|
||||
},
|
||||
...props,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a new tax rate.
|
||||
*/
|
||||
export function useDeleteTaxRate(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation(([id]) => apiRequest.delete(`tax-rates/${id}`), {
|
||||
onSuccess: (res, id) => {
|
||||
// Invalidate specific item.
|
||||
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES, id]);
|
||||
queryClient.invalidateQueries([QUERY_TYPES.TAX_RATES]);
|
||||
},
|
||||
...props,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1069,6 +1069,14 @@ export const getDashboardRoutes = () => [
|
||||
),
|
||||
pageTitle: intl.get('sidebar.projects'),
|
||||
},
|
||||
{
|
||||
path: '/tax-rates',
|
||||
component: lazy(
|
||||
() =>
|
||||
import('@/containers/TaxRates/pages/TaxRatesLanding'),
|
||||
),
|
||||
pageTitle: 'Tax Rates',
|
||||
},
|
||||
// Homepage
|
||||
{
|
||||
path: `/`,
|
||||
|
||||
Reference in New Issue
Block a user