feat: wip invoice customizer

This commit is contained in:
Ahmed Bouhuolia
2024-09-09 19:40:23 +02:00
parent 132c1dfdbe
commit dc18bde6be
18 changed files with 602 additions and 194 deletions

View File

@@ -23,10 +23,11 @@ import WarehouseTransferDetailDrawer from '@/containers/Drawers/WarehouseTransfe
import TaxRateDetailsDrawer from '@/containers/TaxRates/drawers/TaxRateDetailsDrawer/TaxRateDetailsDrawer'; import TaxRateDetailsDrawer from '@/containers/TaxRates/drawers/TaxRateDetailsDrawer/TaxRateDetailsDrawer';
import CategorizeTransactionDrawer from '@/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/CategorizeTransactionDrawer'; import CategorizeTransactionDrawer from '@/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/CategorizeTransactionDrawer';
import ChangeSubscriptionPlanDrawer from '@/containers/Subscriptions/drawers/ChangeSubscriptionPlanDrawer/ChangeSubscriptionPlanDrawer'; import ChangeSubscriptionPlanDrawer from '@/containers/Subscriptions/drawers/ChangeSubscriptionPlanDrawer/ChangeSubscriptionPlanDrawer';
import { InvoiceCustomizeDrawer } from '@/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer';
import { EstimateCustomizeDrawer } from '@/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawer';
import { ReceiptCustomizeDrawer } from '@/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeDrawer';
import { DRAWERS } from '@/constants/drawers'; import { DRAWERS } from '@/constants/drawers';
import { InvoiceCustomizeDrawer } from '@/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer';
/** /**
* Drawers container of the dashboard. * Drawers container of the dashboard.
*/ */
@@ -67,6 +68,8 @@ export default function DrawersContainer() {
<CategorizeTransactionDrawer name={DRAWERS.CATEGORIZE_TRANSACTION} /> <CategorizeTransactionDrawer name={DRAWERS.CATEGORIZE_TRANSACTION} />
<ChangeSubscriptionPlanDrawer name={DRAWERS.CHANGE_SUBSCARIPTION_PLAN} /> <ChangeSubscriptionPlanDrawer name={DRAWERS.CHANGE_SUBSCARIPTION_PLAN} />
<InvoiceCustomizeDrawer name={DRAWERS.INVOICE_CUSTOMIZE} /> <InvoiceCustomizeDrawer name={DRAWERS.INVOICE_CUSTOMIZE} />
<EstimateCustomizeDrawer name={DRAWERS.ESTIMATE_CUSTOMIZE} />
<ReceiptCustomizeDrawer name={DRAWERS.RECEIPT_CUSTOMIZE} />
</div> </div>
); );
} }

View File

@@ -0,0 +1,3 @@
export default function EstimateCustomizeContent() {
return <h1>Hello World</h1>;
}

View File

@@ -0,0 +1,33 @@
// @ts-nocheck
import React from 'react';
import * as R from 'ramda';
import { Drawer, DrawerSuspense } from '@/components';
import withDrawers from '@/containers/Drawer/withDrawers';
const EstimateCustomizeContent = React.lazy(
() => import('./EstimateCustomizeContent'),
);
/**
* Estimate customize drawer.
* @returns {React.ReactNode}
*/
function EstimateCustomizeDrawerRoot({
name,
// #withDrawer
isOpen,
payload: {},
}) {
return (
<Drawer isOpen={isOpen} name={name} size={'100%'}>
<DrawerSuspense>
<EstimateCustomizeContent />
</DrawerSuspense>
</Drawer>
);
}
export const EstimateCustomizeDrawer = R.compose(withDrawers())(
EstimateCustomizeDrawerRoot,
);

View File

@@ -7,6 +7,11 @@ import {
NavbarGroup, NavbarGroup,
Intent, Intent,
Alignment, Alignment,
Menu,
MenuItem,
Popover,
PopoverInteractionKind,
Position,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
@@ -35,6 +40,8 @@ import { useDownloadExportPdf } from '@/hooks/query/FinancialReports/use-export-
import { SaleEstimateAction, AbilitySubject } from '@/constants/abilityOption'; import { SaleEstimateAction, AbilitySubject } from '@/constants/abilityOption';
import { compose } from '@/utils'; import { compose } from '@/utils';
import { DialogsName } from '@/constants/dialogs'; import { DialogsName } from '@/constants/dialogs';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { DRAWERS } from '@/constants/drawers';
/** /**
* Estimates list actions bar. * Estimates list actions bar.
@@ -52,6 +59,9 @@ function EstimateActionsBar({
// #withDialogActions // #withDialogActions
openDialog, openDialog,
// #withDrawerActions
openDrawer,
// #withSettingsActions // #withSettingsActions
addSetting, addSetting,
}) { }) {
@@ -96,6 +106,10 @@ function EstimateActionsBar({
const handlePrintBtnClick = () => { const handlePrintBtnClick = () => {
downloadExportPdf({ resource: 'SaleEstimate' }); downloadExportPdf({ resource: 'SaleEstimate' });
}; };
// Handle customize button clicl.
const handleCustomizeBtnClick = () => {
openDrawer(DRAWERS.ESTIMATE_CUSTOMIZE);
};
return ( return (
<DashboardActionsBar> <DashboardActionsBar>
@@ -167,6 +181,25 @@ function EstimateActionsBar({
</NavbarGroup> </NavbarGroup>
<NavbarGroup align={Alignment.RIGHT}> <NavbarGroup align={Alignment.RIGHT}>
<Popover
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_RIGHT}
modifiers={{
offset: { offset: '0, 4' },
}}
content={
<Menu>
<MenuItem
onClick={handleCustomizeBtnClick}
text={'Customize Estimate'}
/>
</Menu>
}
>
<Button icon={<Icon icon="cog-16" iconSize={16} />} minimal={true} />
</Popover>
<NavbarDivider />
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon="refresh-16" iconSize={14} />} icon={<Icon icon="refresh-16" iconSize={14} />}
@@ -187,4 +220,5 @@ export default compose(
estimatesTableSize: estimatesSettings?.tableSize, estimatesTableSize: estimatesSettings?.tableSize,
})), })),
withDialogActions, withDialogActions,
withDrawerActions,
)(EstimateActionsBar); )(EstimateActionsBar);

View File

@@ -0,0 +1,74 @@
import React from 'react';
import { Box, Group } from '@/components';
import { InvoiceCustomizeProvider } from './InvoiceCustomizeProvider';
import {
InvoiceCustomizeForm,
InvoiceCustomizeFormProps,
} from './InvoiceCustomizerForm';
import { InvoiceCustomizeTabsControllerProvider } from './InvoiceCustomizeTabsController';
import { InvoiceCustomizeFields } from './InvoiceCustomizeFields';
import { InvoiceCustomizePreview } from './InvoiceCustomizePreview';
import { extractChildren } from '@/utils/extract-children';
export interface InvoiceCustomizeProps<T> extends InvoiceCustomizeFormProps<T> {
children?: React.ReactNode;
}
export function InvoiceCustomize<T>({
initialValues,
validationSchema,
onSubmit,
children,
}: InvoiceCustomizeProps<T>) {
const PaperTemplate = React.useMemo(
() => extractChildren(children, InvoiceCustomize.PaperTemplate),
[children],
);
const CustomizeTabs = React.useMemo(
() => extractChildren(children, InvoiceCustomize.FieldsTab),
[children],
);
const value = { PaperTemplate, CustomizeTabs };
return (
<InvoiceCustomizeForm
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={onSubmit}
>
<InvoiceCustomizeTabsControllerProvider>
<InvoiceCustomizeProvider value={value}>
<Group spacing={0} align="stretch">
<InvoiceCustomizeFields />
<InvoiceCustomizePreview />
</Group>
</InvoiceCustomizeProvider>
</InvoiceCustomizeTabsControllerProvider>
</InvoiceCustomizeForm>
);
}
export interface InvoiceCustomizePaperTemplateProps {
children?: React.ReactNode;
}
InvoiceCustomize.PaperTemplate = ({
children,
}: InvoiceCustomizePaperTemplateProps) => {
return <Box>{children}</Box>;
};
export interface InvoiceCustomizeContentProps {
id: string;
label: string;
children?: React.ReactNode;
}
InvoiceCustomize.FieldsTab = ({
id,
label,
children,
}: InvoiceCustomizeContentProps) => {
return <Box>{children}</Box>;
};

View File

@@ -1,21 +1,65 @@
import { Box, Group } from '@/components'; import React from 'react';
import { InvoiceCustomizePreview } from './InvoiceCustomizePreview'; import { Box } from '@/components';
import { InvoiceCustomizeFields } from './InvoiceCustomizeFields';
import { InvoiceCustomizeForm } from './InvoiceCustomizerForm';
import { Classes } from '@blueprintjs/core'; import { Classes } from '@blueprintjs/core';
import { InvoiceCustomizeTabsControllerProvider } from './InvoiceCustomizeTabsController'; import { InvoicePaperTemplate } from './InvoicePaperTemplate';
import { InvoiceCustomize } from './InvoiceCustomize';
import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields';
import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields';
interface InvoiceCustomizeValues {
invoiceNumber?: string;
invoiceNumberLabel?: string;
dateIssue?: string;
dateIssueLabel?: string;
dueDate?: string;
dueDateLabel?: string;
companyName?: string;
bigtitle?: string;
itemRateLabel?: string;
itemQuantityLabel?: string;
itemTotalLabel?: string;
// Totals
showDueAmount?: boolean;
showDiscount?: boolean;
showPaymentMade?: boolean;
showTaxes?: boolean;
showSubtotal?: boolean;
showTotal?: boolean;
showBalanceDue?: boolean;
paymentMadeLabel?: string;
discountLabel?: string;
subtotalLabel?: string;
totalLabel?: string;
balanceDueLabel?: string;
}
export default function InvoiceCustomizeContent() { export default function InvoiceCustomizeContent() {
return ( return (
<Box className={Classes.DRAWER_BODY}> <Box className={Classes.DRAWER_BODY}>
<InvoiceCustomizeForm> <InvoiceCustomize<InvoiceCustomizeValues>>
<Group spacing={0} align="stretch"> <InvoiceCustomize.PaperTemplate>
<InvoiceCustomizeTabsControllerProvider> <InvoicePaperTemplate />
<InvoiceCustomizeFields /> </InvoiceCustomize.PaperTemplate>
<InvoiceCustomizePreview />
</InvoiceCustomizeTabsControllerProvider> <InvoiceCustomize.FieldsTab id={'general'} label={'General'}>
</Group> <InvoiceCustomizeGeneralField />
</InvoiceCustomizeForm> </InvoiceCustomize.FieldsTab>
<InvoiceCustomize.FieldsTab id={'content'} label={'Content'}>
<InvoiceCustomizeContentFields />
</InvoiceCustomize.FieldsTab>
<InvoiceCustomize.FieldsTab id={'totals'} label={'Totals'}>
asdfasdfdsaf #3
</InvoiceCustomize.FieldsTab>
</InvoiceCustomize>
</Box> </Box>
); );
} }

View File

@@ -9,8 +9,8 @@ const InvoiceCustomizeContent = React.lazy(
); );
/** /**
* Refund credit note detail. * Invoice customize drawer.
* @returns * @returns {React.ReactNode}
*/ */
function InvoiceCustomizeDrawerRoot({ function InvoiceCustomizeDrawerRoot({
name, name,
@@ -19,12 +19,7 @@ function InvoiceCustomizeDrawerRoot({
payload: {}, payload: {},
}) { }) {
return ( return (
<Drawer <Drawer isOpen={isOpen} name={name} size={'100%'}>
isOpen={isOpen}
name={name}
size={'100%'}
>
<DrawerSuspense> <DrawerSuspense>
<InvoiceCustomizeContent /> <InvoiceCustomizeContent />
</DrawerSuspense> </DrawerSuspense>

View File

@@ -1,16 +1,16 @@
// @ts-nocheck // @ts-nocheck
import React from 'react';
import * as R from 'ramda'; import * as R from 'ramda';
import { Box, Group, Stack } from '@/components'; import { Button, Intent } from '@blueprintjs/core';
import { Group, Stack } from '@/components';
import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader'; import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader';
import { InvoiceCustomizeTabs } from './InvoiceCustomizeTabs'; import { InvoiceCustomizeTabs } from './InvoiceCustomizeTabs';
import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields';
import { useInvoiceCustomizeTabsController } from './InvoiceCustomizeTabsController'; import { useInvoiceCustomizeTabsController } from './InvoiceCustomizeTabsController';
import { Button, Intent } from '@blueprintjs/core';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields'; import { useInvoiceCustomizeContext } from './InvoiceCustomizeProvider';
import styles from './InvoiceCustomizeFields.module.scss'; import styles from './InvoiceCustomizeFields.module.scss';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
export function InvoiceCustomizeFields() { export function InvoiceCustomizeFields() {
return ( return (
@@ -23,14 +23,22 @@ export function InvoiceCustomizeFields() {
export function InvoiceCustomizeFieldsMain() { export function InvoiceCustomizeFieldsMain() {
const { currentTabId } = useInvoiceCustomizeTabsController(); const { currentTabId } = useInvoiceCustomizeTabsController();
const { CustomizeTabs } = useInvoiceCustomizeContext();
const CustomizeTabPanel = React.useMemo(
() =>
React.Children.map(CustomizeTabs, (tab) => {
return tab.props.id === currentTabId ? tab : null;
}).filter(Boolean),
[CustomizeTabs, currentTabId],
);
return ( return (
<Stack spacing={0} className={styles.mainFields}> <Stack spacing={0} className={styles.mainFields}>
<InvoiceCustomizeHeader label={'Customize'} /> <InvoiceCustomizeHeader label={'Customize'} />
<Stack spacing={0} style={{ flex: '1 1 auto', overflow: 'auto' }}> <Stack spacing={0} style={{ flex: '1 1 auto', overflow: 'auto' }}>
{currentTabId === 'general' && <InvoiceCustomizeGeneralField />} {CustomizeTabPanel}
{currentTabId === 'content' && <InvoiceCustomizeContentFields />}
<InvoiceCustomizeFooterActions /> <InvoiceCustomizeFooterActions />
</Stack> </Stack>
</Stack> </Stack>

View File

@@ -1,17 +1,19 @@
import { Box } from '@/components'; import { Box } from '@/components';
import { PaperTemplate } from './PaperTemplate'; import { useInvoiceCustomizeContext } from './InvoiceCustomizeProvider';
export function InvoiceCustomizePreviewContent() { export function InvoiceCustomizePreviewContent() {
const { PaperTemplate } = useInvoiceCustomizeContext();
return ( return (
<Box <Box
style={{ style={{
padding: '28px 24px 40px', padding: '28px 24px 40px',
backgroundColor: '#F5F5F5', backgroundColor: '#F5F5F5',
overflow: 'auto', overflow: 'auto',
flex: '1' flex: '1',
}} }}
> >
<PaperTemplate /> {PaperTemplate}
</Box> </Box>
); );
} }

View File

@@ -0,0 +1,32 @@
import React, { createContext, useContext } from 'react';
interface InvoiceCustomizeValue {
PaperTemplate?: React.ReactNode;
CustomizeTabs: React.ReactNode;
}
const InvoiceCustomizeContext = createContext<InvoiceCustomizeValue>(
{} as InvoiceCustomizeValue,
);
export const InvoiceCustomizeProvider: React.FC<{
value: InvoiceCustomizeValue;
children: React.ReactNode;
}> = ({ value, children }) => {
return (
<InvoiceCustomizeContext.Provider value={{ ...value }}>
{children}
</InvoiceCustomizeContext.Provider>
);
};
export const useInvoiceCustomizeContext = (): InvoiceCustomizeValue => {
const context = useContext<InvoiceCustomizeValue>(InvoiceCustomizeContext);
if (!context) {
throw new Error(
'useInvoiceCustomize must be used within an InvoiceCustomizeProvider',
);
}
return context;
};

View File

@@ -6,10 +6,17 @@ import {
useInvoiceCustomizeTabsController, useInvoiceCustomizeTabsController,
} from './InvoiceCustomizeTabsController'; } from './InvoiceCustomizeTabsController';
import styles from './InvoiceCustomizeTabs.module.scss'; import styles from './InvoiceCustomizeTabs.module.scss';
import { useInvoiceCustomizeContext } from './InvoiceCustomizeProvider';
import React from 'react';
export function InvoiceCustomizeTabs() { export function InvoiceCustomizeTabs() {
const { setCurrentTabId } = useInvoiceCustomizeTabsController(); const { setCurrentTabId } = useInvoiceCustomizeTabsController();
const { CustomizeTabs } = useInvoiceCustomizeContext();
const tabItems = React.Children.map(CustomizeTabs, (node) => ({
...(React.isValidElement(node) ? node.props : {}),
}));
const handleChange = (value: InvoiceCustomizeTabsEnum) => { const handleChange = (value: InvoiceCustomizeTabsEnum) => {
setCurrentTabId(value); setCurrentTabId(value);
}; };
@@ -25,9 +32,9 @@ export function InvoiceCustomizeTabs() {
onChange={handleChange} onChange={handleChange}
className={styles.tabsList} className={styles.tabsList}
> >
<Tab id="general" title="General" /> {tabItems?.map(({ id, label }: { id: string; label: string }) => (
<Tab id="content" title="Content" /> <Tab id={id} key={id} title={label} />
<Tab id="total" title="Total" /> ))}
</Tabs> </Tabs>
</Box> </Box>
</Stack> </Stack>

View File

@@ -1,25 +1,25 @@
import { Formik, Form } from 'formik'; // @ts-nocheck
import * as Yup from 'yup';
import React from 'react'; import React from 'react';
import { Formik, Form, FormikHelpers } from 'formik';
const validationSchema = Yup.object().shape({ export interface InvoiceCustomizeFormProps<T> {
invoiceNumber: Yup.string().required('Invoice number is required'), initialValues?: T;
customerName: Yup.string().required('Customer name is required'), validationSchema?: any;
amount: Yup.number() onSubmit?: (values: T, formikHelpers: FormikHelpers<T>) => void;
.required('Amount is required') children?: React.ReactNode;
.positive('Amount must be positive'),
});
interface InvoiceCustomizeFormProps {
children: React.ReactNode;
} }
export function InvoiceCustomizeForm({ children }: InvoiceCustomizeFormProps) { export function InvoiceCustomizeForm<T>({
initialValues,
validationSchema,
onSubmit,
children,
}: InvoiceCustomizeFormProps<T>) {
return ( return (
<Formik <Formik<T>
initialValues={{ invoiceNumber: '', customerName: '', amount: '' }} initialValues={{ ...initialValues }}
validationSchema={validationSchema} validationSchema={validationSchema}
onSubmit={(values) => {}} onSubmit={(value, helpers) => onSubmit && onSubmit(value, helpers)}
> >
<Form>{children}</Form> <Form>{children}</Form>
</Formik> </Formik>

View File

@@ -0,0 +1,235 @@
import clsx from 'classnames';
import styles from './PaperTemplate.module.scss';
interface PaperTemplateProps {
invoiceNumber?: string;
invoiceNumberLabel?: string;
dateIssue?: string;
dateIssueLabel?: string;
dueDate?: string;
dueDateLabel?: string;
companyName?: string;
bigtitle?: string;
itemRateLabel?: string;
itemQuantityLabel?: string;
itemTotalLabel?: string;
// Totals
showDueAmount?: boolean;
showDiscount?: boolean;
showPaymentMade?: boolean;
showTaxes?: boolean;
showSubtotal?: boolean;
showTotal?: boolean;
showBalanceDue?: boolean;
paymentMadeLabel?: string;
discountLabel?: string;
subtotalLabel?: string;
totalLabel?: string;
balanceDueLabel?: string;
}
export function InvoicePaperTemplate({
bigtitle = 'Invoice',
companyName = 'Bigcapital Technology, Inc.',
// dueDateLabel,
dueDate = 'September 3, 2024',
dueDateLabel = 'Date due',
dateIssue = 'September 3, 2024',
dateIssueLabel = 'Date of issue',
// dateIssue,
invoiceNumberLabel = 'Invoice number',
invoiceNumber = '346D3D40-0001',
// Entries
itemQuantityLabel = 'Quantity',
itemRateLabel = 'Rate',
itemTotalLabel = 'Total',
totalLabel = 'Total',
subtotalLabel = 'Subtotal',
discountLabel = 'Discount',
paymentMadeLabel = 'Payment Made',
balanceDueLabel = 'Balance Due',
// Totals
showTotal = true,
showSubtotal = true,
showDiscount = true,
showTaxes = true,
showPaymentMade = true,
showDueAmount = true,
showBalanceDue = true,
}: PaperTemplateProps) {
return (
<div className={styles.root}>
<div>
<h1 className={styles.bigTitle}>{bigtitle}</h1>
<div className={styles.logoWrap}>
<img
alt=""
src="https://cdn-development.mercury.com/demo-assets/avatars/mercury-demo-dark.png"
/>
</div>
</div>
<div className={styles.details}>
<div className={styles.detail}>
<div className={styles.detailLabel}>{invoiceNumberLabel}</div>
<div>{invoiceNumber}</div>
</div>
<div className={styles.detail}>
<div className={styles.detailLabel}>{dateIssueLabel}</div>
<div>{dateIssue}</div>
</div>
<div className={styles.detail}>
<div className={styles.detailLabel}>{dueDateLabel}</div>
<div>{dueDate}</div>
</div>
</div>
<div className={styles.addressRoot}>
<div className={styles.addressBillTo}>
<strong>{companyName}</strong> <br />
131 Continental Dr Suite 305 Newark,
<br />
Delaware 19713
<br />
United States
<br />
+1 762-339-5634
<br />
ahmed@bigcapital.app
</div>
<div className={styles.addressFrom}>
<strong>Billed To</strong> <br />
Bigcapital Technology, Inc. <br />
131 Continental Dr Suite 305 Newark,
<br />
Delaware 19713
<br />
United States
<br />
+1 762-339-5634
<br />
ahmed@bigcapital.app
</div>
</div>
<table className={styles.table}>
<thead>
<tr>
<th>Item</th>
<th>Description</th>
<th className={styles.rate}>{itemRateLabel}</th>
<th className={styles.total}>{itemTotalLabel}</th>
</tr>
</thead>
<tbody className={styles.tableBody}>
<tr>
<td>Simply dummy text</td>
<td>Simply dummy text of the printing and typesetting</td>
<td className={styles.rate}>1 X $100,00</td>
<td className={styles.total}>$100,00</td>
</tr>
</tbody>
</table>
<div style={{ display: 'flex' }}>
<div className={styles.totals}>
{showSubtotal && (
<div
className={clsx(
styles.totalsItem,
styles.totalBottomGrayBordered,
)}
>
<div className={styles.totalsItemLabel}>{subtotalLabel}</div>
<div className={styles.totalsItemAmount}>630.00</div>
</div>
)}
{showDiscount && (
<div className={styles.totalsItem}>
<div className={styles.totalsItemLabel}>{discountLabel}</div>
<div className={styles.totalsItemAmount}>0.00</div>
</div>
)}
{showTaxes && (
<>
<div className={styles.totalsItem}>
<div className={styles.totalsItemLabel}>
Sample Tax1 (4.70%)
</div>
<div className={styles.totalsItemAmount}>11.75</div>
</div>
<div className={styles.totalsItem}>
<div className={styles.totalsItemLabel}>
Sample Tax2 (7.00%)
</div>
<div className={styles.totalsItemAmount}>21.00</div>
</div>
</>
)}
{showTotal && (
<div
className={clsx(styles.totalsItem, styles.totalBottomBordered)}
>
<div className={styles.totalsItemLabel}>{totalLabel}</div>
<div className={styles.totalsItemAmount}>$662.75</div>
</div>
)}
{showPaymentMade && (
<div className={styles.totalsItem}>
<div className={styles.totalsItemLabel}>{paymentMadeLabel}</div>
<div className={styles.totalsItemAmount}>100.00</div>
</div>
)}
{showBalanceDue && (
<div
className={clsx(styles.totalsItem, styles.totalBottomBordered)}
>
<div className={styles.totalsItemLabel}>{balanceDueLabel}</div>
<div className={styles.totalsItemAmount}>$562.75</div>
</div>
)}
</div>
</div>
<div className={styles.paragraph}>
<div className={styles.paragraphLabel}>Terms & Conditions</div>
<div>
It is a long established fact that a reader will be distracted by the
readable content of a page when looking at its layout.
</div>
</div>
<div className={styles.paragraph}>
<div className={styles.paragraphLabel}>Statement</div>
<div>
It is a long established fact that a reader will be distracted by the
readable content of a page when looking at its layout.
</div>
</div>
</div>
);
}

View File

@@ -1,141 +0,0 @@
import clsx from 'classnames';
import styles from './PaperTemplate.module.scss';
export function PaperTemplate() {
return (
<div className={styles.root}>
<div>
<h1 className={styles.bigTitle}>Invoice</h1>
<div className={styles.logoWrap}>
<img
alt=""
src="https://cdn-development.mercury.com/demo-assets/avatars/mercury-demo-dark.png"
/>
</div>
</div>
<div className={styles.details}>
<div className={styles.detail}>
<div className={styles.detailLabel}>Invoice number</div>
<div>346D3D40-0001</div>
</div>
<div className={styles.detail}>
<div className={styles.detailLabel}>Date of issue</div>
<div>September 3, 2024</div>
</div>
<div className={styles.detail}>
<div className={styles.detailLabel}>Date due</div>
<div>October 3, 2024</div>
</div>
</div>
<div className={styles.addressRoot}>
<div className={styles.addressBillTo}>
<strong>Bigcapital Technology, Inc.</strong> <br />
131 Continental Dr Suite 305 Newark,
<br />
Delaware 19713
<br />
United States
<br />
+1 762-339-5634
<br />
ahmed@bigcapital.app
</div>
<div className={styles.addressFrom}>
<strong>Billed To</strong> <br />
Bigcapital Technology, Inc. <br />
131 Continental Dr Suite 305 Newark,
<br />
Delaware 19713
<br />
United States
<br />
+1 762-339-5634
<br />
ahmed@bigcapital.app
</div>
</div>
<table className={styles.table}>
<thead>
<tr>
<th>Item</th>
<th>Description</th>
<th className={styles.rate}>Rate</th>
<th className={styles.total}>Total</th>
</tr>
</thead>
<tbody className={styles.tableBody}>
<tr>
<td>Simply dummy text</td>
<td>Simply dummy text of the printing and typesetting</td>
<td className={styles.rate}>1 X $100,00</td>
<td className={styles.total}>$100,00</td>
</tr>
</tbody>
</table>
<div style={{ display: 'flex' }}>
<div className={styles.totals}>
<div
className={clsx(styles.totalsItem, styles.totalBottomGrayBordered)}
>
<div className={styles.totalsItemLabel}>Sub Total</div>
<div className={styles.totalsItemAmount}>630.00</div>
</div>
<div className={styles.totalsItem}>
<div className={styles.totalsItemLabel}>Discount</div>
<div className={styles.totalsItemAmount}>0.00</div>
</div>
<div className={styles.totalsItem}>
<div className={styles.totalsItemLabel}>Sample Tax1 (4.70%)</div>
<div className={styles.totalsItemAmount}>11.75</div>
</div>
<div className={styles.totalsItem}>
<div className={styles.totalsItemLabel}>Sample Tax2 (7.00%)</div>
<div className={styles.totalsItemAmount}>21.00</div>
</div>
<div className={clsx(styles.totalsItem, styles.totalBottomBordered)}>
<div className={styles.totalsItemLabel}>Total</div>
<div className={styles.totalsItemAmount}>$662.75</div>
</div>
<div className={styles.totalsItem}>
<div className={styles.totalsItemLabel}>Payment Made</div>
<div className={styles.totalsItemAmount}>100.00</div>
</div>
<div className={clsx(styles.totalsItem, styles.totalBottomBordered)}>
<div className={styles.totalsItemLabel}>Balance Due</div>
<div className={styles.totalsItemAmount}>$562.75</div>
</div>
</div>
</div>
<div className={styles.paragraph}>
<div className={styles.paragraphLabel}>Terms & Conditions</div>
<div>
It is a long established fact that a reader will be distracted by the
readable content of a page when looking at its layout.
</div>
</div>
<div className={styles.paragraph}>
<div className={styles.paragraphLabel}>Statement</div>
<div>
It is a long established fact that a reader will be distracted by the
readable content of a page when looking at its layout.
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,3 @@
export default function ReceiptCustomizeContent() {
return <h1>asdasdasd</h1>;
}

View File

@@ -0,0 +1,32 @@
// @ts-nocheck
import React from 'react';
import * as R from 'ramda';
import { Drawer, DrawerSuspense } from '@/components';
import withDrawers from '@/containers/Drawer/withDrawers';
const ReceiptCustomizeContent = React.lazy(
() => import('./ReceiptCustomizeContent'),
);
/**
* Receipt customize drawer.
* @returns {React.ReactNode}
*/
function ReceiptCustomizeDrawerRoot({
name,
// #withDrawer
isOpen,
payload: {},
}) {
return (
<Drawer isOpen={isOpen} name={name} size={'100%'}>
<DrawerSuspense>
<ReceiptCustomizeContent />
</DrawerSuspense>
</Drawer>
);
}
export const ReceiptCustomizeDrawer = R.compose(withDrawers())(
ReceiptCustomizeDrawerRoot,
);

View File

@@ -7,6 +7,11 @@ import {
NavbarGroup, NavbarGroup,
Intent, Intent,
Alignment, Alignment,
Popover,
PopoverInteractionKind,
Position,
Menu,
MenuItem,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
@@ -38,6 +43,8 @@ import { SaleReceiptAction, AbilitySubject } from '@/constants/abilityOption';
import { DialogsName } from '@/constants/dialogs'; import { DialogsName } from '@/constants/dialogs';
import { compose } from '@/utils'; import { compose } from '@/utils';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { DRAWERS } from '@/constants/drawers';
/** /**
* Receipts actions bar. * Receipts actions bar.
@@ -55,6 +62,9 @@ function ReceiptActionsBar({
// #withDialogActions // #withDialogActions
openDialog, openDialog,
// #withDrawerActions
openDrawer,
// #withSettingsActions // #withSettingsActions
addSetting, addSetting,
}) { }) {
@@ -103,6 +113,10 @@ function ReceiptActionsBar({
const handlePrintButtonClick = () => { const handlePrintButtonClick = () => {
downloadExportPdf({ resource: 'SaleReceipt' }); downloadExportPdf({ resource: 'SaleReceipt' });
}; };
// Handle customize button click.
const handleCustomizeBtnClick = () => {
openDrawer(DRAWERS.RECEIPT_CUSTOMIZE);
};
return ( return (
<DashboardActionsBar> <DashboardActionsBar>
@@ -173,6 +187,25 @@ function ReceiptActionsBar({
<NavbarDivider /> <NavbarDivider />
</NavbarGroup> </NavbarGroup>
<NavbarGroup align={Alignment.RIGHT}> <NavbarGroup align={Alignment.RIGHT}>
<Popover
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_RIGHT}
modifiers={{
offset: { offset: '0, 4' },
}}
content={
<Menu>
<MenuItem
onClick={handleCustomizeBtnClick}
text={'Customize Receipt'}
/>
</Menu>
}
>
<Button icon={<Icon icon="cog-16" iconSize={16} />} minimal={true} />
</Popover>
<NavbarDivider />
<Button <Button
className={Classes.MINIMAL} className={Classes.MINIMAL}
icon={<Icon icon="refresh-16" iconSize={14} />} icon={<Icon icon="refresh-16" iconSize={14} />}
@@ -193,4 +226,5 @@ export default compose(
receiptsTableSize: receiptSettings?.tableSize, receiptsTableSize: receiptSettings?.tableSize,
})), })),
withDialogActions, withDialogActions,
withDrawerActions,
)(ReceiptActionsBar); )(ReceiptActionsBar);

View File

@@ -0,0 +1,10 @@
import React from 'react';
export const extractChildren = <T extends React.ReactNode>(
children: React.ReactNode,
type: React.ElementType,
): T[] => {
return React.Children.toArray(children).filter(
(node) => React.isValidElement(node) && node.type === type,
) as T[];
};