mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-22 15:50:32 +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 EstimatedExpenseFormDialog from '@/containers/Projects/containers/EstimatedExpenseFormDialog';
|
||||||
import ProjectInvoicingFormDialog from '@/containers/Projects/containers/ProjectInvoicingFormDialog';
|
import ProjectInvoicingFormDialog from '@/containers/Projects/containers/ProjectInvoicingFormDialog';
|
||||||
import ProjectBillableEntriesFormDialog from '@/containers/Projects/containers/ProjectBillableEntriesFormDialog';
|
import ProjectBillableEntriesFormDialog from '@/containers/Projects/containers/ProjectBillableEntriesFormDialog';
|
||||||
|
import TaxRateFormDialog from '@/containers/TaxRates/dialogs/TaxRateFormDialog/TaxRateFormDialog';
|
||||||
import { DialogsName } from '@/constants/dialogs';
|
import { DialogsName } from '@/constants/dialogs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -134,7 +135,10 @@ export default function DialogsContainer() {
|
|||||||
<ProjectInvoicingFormDialog
|
<ProjectInvoicingFormDialog
|
||||||
dialogName={DialogsName.ProjectInvoicingForm}
|
dialogName={DialogsName.ProjectInvoicingForm}
|
||||||
/>
|
/>
|
||||||
<ProjectBillableEntriesFormDialog dialogName={DialogsName.ProjectBillableEntriesForm}/>
|
<ProjectBillableEntriesFormDialog
|
||||||
|
dialogName={DialogsName.ProjectBillableEntriesForm}
|
||||||
|
/>
|
||||||
|
<TaxRateFormDialog dialogName={DialogsName.TaxRateForm} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import VendorCreditDetailDrawer from '@/containers/Drawers/VendorCreditDetailDra
|
|||||||
import RefundCreditNoteDetailDrawer from '@/containers/Drawers/RefundCreditNoteDetailDrawer';
|
import RefundCreditNoteDetailDrawer from '@/containers/Drawers/RefundCreditNoteDetailDrawer';
|
||||||
import RefundVendorCreditDetailDrawer from '@/containers/Drawers/RefundVendorCreditDetailDrawer';
|
import RefundVendorCreditDetailDrawer from '@/containers/Drawers/RefundVendorCreditDetailDrawer';
|
||||||
import WarehouseTransferDetailDrawer from '@/containers/Drawers/WarehouseTransferDetailDrawer';
|
import WarehouseTransferDetailDrawer from '@/containers/Drawers/WarehouseTransferDetailDrawer';
|
||||||
|
import TaxRateDetailsDrawer from '@/containers/TaxRates/drawers/TaxRateDetailsDrawer/TaxRateDetailsDrawer';
|
||||||
|
|
||||||
import { DRAWERS } from '@/constants/drawers';
|
import { DRAWERS } from '@/constants/drawers';
|
||||||
|
|
||||||
@@ -43,16 +44,25 @@ export default function DrawersContainer() {
|
|||||||
<ItemDetailDrawer name={DRAWERS.ITEM_DETAILS} />
|
<ItemDetailDrawer name={DRAWERS.ITEM_DETAILS} />
|
||||||
<CustomerDetailsDrawer name={DRAWERS.CUSTOMER_DETAILS} />
|
<CustomerDetailsDrawer name={DRAWERS.CUSTOMER_DETAILS} />
|
||||||
<VendorDetailsDrawer name={DRAWERS.VENDOR_DETAILS} />
|
<VendorDetailsDrawer name={DRAWERS.VENDOR_DETAILS} />
|
||||||
<InventoryAdjustmentDetailDrawer name={DRAWERS.INVENTORY_ADJUSTMENT_DETAILS} />
|
<InventoryAdjustmentDetailDrawer
|
||||||
<CashflowTransactionDetailDrawer name={DRAWERS.CASHFLOW_TRNASACTION_DETAILS} />
|
name={DRAWERS.INVENTORY_ADJUSTMENT_DETAILS}
|
||||||
|
/>
|
||||||
|
<CashflowTransactionDetailDrawer
|
||||||
|
name={DRAWERS.CASHFLOW_TRNASACTION_DETAILS}
|
||||||
|
/>
|
||||||
<QuickCreateCustomerDrawer name={DRAWERS.QUICK_CREATE_CUSTOMER} />
|
<QuickCreateCustomerDrawer name={DRAWERS.QUICK_CREATE_CUSTOMER} />
|
||||||
<QuickCreateItemDrawer name={DRAWERS.QUICK_CREATE_ITEM} />
|
<QuickCreateItemDrawer name={DRAWERS.QUICK_CREATE_ITEM} />
|
||||||
<QuickWriteVendorDrawer name={DRAWERS.QUICK_WRITE_VENDOR} />
|
<QuickWriteVendorDrawer name={DRAWERS.QUICK_WRITE_VENDOR} />
|
||||||
<CreditNoteDetailDrawer name={DRAWERS.CREDIT_NOTE_DETAILS} />
|
<CreditNoteDetailDrawer name={DRAWERS.CREDIT_NOTE_DETAILS} />
|
||||||
<VendorCreditDetailDrawer name={DRAWERS.VENDOR_CREDIT_DETAILS} />
|
<VendorCreditDetailDrawer name={DRAWERS.VENDOR_CREDIT_DETAILS} />
|
||||||
<RefundCreditNoteDetailDrawer name={DRAWERS.REFUND_CREDIT_NOTE_DETAILS} />
|
<RefundCreditNoteDetailDrawer name={DRAWERS.REFUND_CREDIT_NOTE_DETAILS} />
|
||||||
<RefundVendorCreditDetailDrawer name={DRAWERS.REFUND_VENDOR_CREDIT_DETAILS} />
|
<RefundVendorCreditDetailDrawer
|
||||||
<WarehouseTransferDetailDrawer name={DRAWERS.WAREHOUSE_TRANSFER_DETAILS} />
|
name={DRAWERS.REFUND_VENDOR_CREDIT_DETAILS}
|
||||||
|
/>
|
||||||
|
<WarehouseTransferDetailDrawer
|
||||||
|
name={DRAWERS.WAREHOUSE_TRANSFER_DETAILS}
|
||||||
|
/>
|
||||||
|
<TaxRateDetailsDrawer name={DRAWERS.TAX_RATE_DETAILS} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ export const AbilitySubject = {
|
|||||||
SubscriptionBilling: 'SubscriptionBilling',
|
SubscriptionBilling: 'SubscriptionBilling',
|
||||||
CreditNote: 'CreditNote',
|
CreditNote: 'CreditNote',
|
||||||
VendorCredit: 'VendorCredit',
|
VendorCredit: 'VendorCredit',
|
||||||
Project:'Project'
|
Project:'Project',
|
||||||
|
TaxRate: 'TaxRate',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ItemAction = {
|
export const ItemAction = {
|
||||||
@@ -186,3 +187,11 @@ export const SubscriptionBillingAbility = {
|
|||||||
View: 'view',
|
View: 'view',
|
||||||
Payment: 'payment',
|
Payment: 'payment',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const TaxRateAction = {
|
||||||
|
View: 'View',
|
||||||
|
Create: 'Create',
|
||||||
|
Edit: 'Edit',
|
||||||
|
Delete: 'Delete',
|
||||||
|
};
|
||||||
|
|||||||
@@ -46,5 +46,6 @@ export enum DialogsName {
|
|||||||
EstimateExpenseForm = 'estimate-expense-form',
|
EstimateExpenseForm = 'estimate-expense-form',
|
||||||
ProjectInvoicingForm = 'project-invoicing-form',
|
ProjectInvoicingForm = 'project-invoicing-form',
|
||||||
ProjectBillableEntriesForm = 'project-billable-entries',
|
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_CREDIT_NOTE_DETAILS = 'refund-credit-detail-drawer',
|
||||||
REFUND_VENDOR_CREDIT_DETAILS = 'refund-vendor-detail-drawer',
|
REFUND_VENDOR_CREDIT_DETAILS = 'refund-vendor-detail-drawer',
|
||||||
WAREHOUSE_TRANSFER_DETAILS = 'warehouse-transfer-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',
|
href: '/transactions-locking',
|
||||||
type: ISidebarMenuItemType.Link,
|
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 WarehousesTransfersAlerts from '@/containers/WarehouseTransfers/WarehousesTransfersAlerts';
|
||||||
import BranchesAlerts from '@/containers/Preferences/Branches/BranchesAlerts';
|
import BranchesAlerts from '@/containers/Preferences/Branches/BranchesAlerts';
|
||||||
import ProjectAlerts from '@/containers/Projects/containers/ProjectAlerts';
|
import ProjectAlerts from '@/containers/Projects/containers/ProjectAlerts';
|
||||||
|
import TaxRatesAlerts from '@/containers/TaxRates/alerts';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
...AccountsAlerts,
|
...AccountsAlerts,
|
||||||
@@ -53,4 +54,5 @@ export default [
|
|||||||
...WarehousesTransfersAlerts,
|
...WarehousesTransfersAlerts,
|
||||||
...BranchesAlerts,
|
...BranchesAlerts,
|
||||||
...ProjectAlerts,
|
...ProjectAlerts,
|
||||||
|
...TaxRatesAlerts
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -25,14 +25,12 @@ import { InvoiceDetailsStatus } from './utils';
|
|||||||
export default function InvoiceDetailHeader() {
|
export default function InvoiceDetailHeader() {
|
||||||
const { invoice } = useInvoiceDetailDrawerContext();
|
const { invoice } = useInvoiceDetailDrawerContext();
|
||||||
|
|
||||||
const handleCustomerLinkClick = () => {};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommercialDocHeader>
|
<CommercialDocHeader>
|
||||||
<CommercialDocTopHeader>
|
<CommercialDocTopHeader>
|
||||||
<DetailsMenu>
|
<DetailsMenu>
|
||||||
<AmountDetailItem label={intl.get('amount')}>
|
<AmountDetailItem label={intl.get('amount')}>
|
||||||
<h3 class="big-number">{invoice.formatted_amount}</h3>
|
<h3 class="big-number">{invoice.total_formatted}</h3>
|
||||||
</AmountDetailItem>
|
</AmountDetailItem>
|
||||||
|
|
||||||
<StatusDetailItem label={''}>
|
<StatusDetailItem label={''}>
|
||||||
@@ -75,11 +73,11 @@ export default function InvoiceDetailHeader() {
|
|||||||
textAlign={'right'}
|
textAlign={'right'}
|
||||||
>
|
>
|
||||||
<DetailItem label={intl.get('due_amount')}>
|
<DetailItem label={intl.get('due_amount')}>
|
||||||
<strong>{invoice.formatted_due_amount}</strong>
|
<strong>{invoice.due_amount_formatted}</strong>
|
||||||
</DetailItem>
|
</DetailItem>
|
||||||
|
|
||||||
<DetailItem label={intl.get('invoice.details.payment_amount')}>
|
<DetailItem label={intl.get('invoice.details.payment_amount')}>
|
||||||
<strong>{invoice.formatted_payment_amount}</strong>
|
<strong>{invoice.payment_amount_formatted}</strong>
|
||||||
</DetailItem>
|
</DetailItem>
|
||||||
|
|
||||||
<DetailItem
|
<DetailItem
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ export function transformToEditForm(invoice) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...transformToForm(invoice, defaultInvoice),
|
...transformToForm(invoice, defaultInvoice),
|
||||||
|
inclusive_exclusive_tax: invoice.is_inclusive_tax
|
||||||
|
? TaxType.Inclusive
|
||||||
|
: TaxType.Exclusive,
|
||||||
entries,
|
entries,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -225,8 +225,8 @@ export function useInvoicesTableColumns() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'amount',
|
id: 'amount',
|
||||||
Header: intl.get('balance'),
|
Header: intl.get('amount'),
|
||||||
accessor: 'formatted_amount',
|
accessor: 'total_formatted',
|
||||||
width: 120,
|
width: 120,
|
||||||
align: 'right',
|
align: 'right',
|
||||||
clickable: true,
|
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
|
// @ts-nocheck
|
||||||
|
import { useMutation, useQueryClient } from 'react-query';
|
||||||
import { useRequestQuery } from '../useQueryRequest';
|
import { useRequestQuery } from '../useQueryRequest';
|
||||||
import QUERY_TYPES from './types';
|
import QUERY_TYPES from './types';
|
||||||
|
import useApiRequest from '../useRequest';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves tax rates.
|
* 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'),
|
pageTitle: intl.get('sidebar.projects'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/tax-rates',
|
||||||
|
component: lazy(
|
||||||
|
() =>
|
||||||
|
import('@/containers/TaxRates/pages/TaxRatesLanding'),
|
||||||
|
),
|
||||||
|
pageTitle: 'Tax Rates',
|
||||||
|
},
|
||||||
// Homepage
|
// Homepage
|
||||||
{
|
{
|
||||||
path: `/`,
|
path: `/`,
|
||||||
|
|||||||
Reference in New Issue
Block a user