mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 22:00:31 +00:00
feat: wip invoice customizer
This commit is contained in:
@@ -23,10 +23,11 @@ import WarehouseTransferDetailDrawer from '@/containers/Drawers/WarehouseTransfe
|
||||
import TaxRateDetailsDrawer from '@/containers/TaxRates/drawers/TaxRateDetailsDrawer/TaxRateDetailsDrawer';
|
||||
import CategorizeTransactionDrawer from '@/containers/CashFlow/CategorizeTransaction/drawers/CategorizeTransactionDrawer/CategorizeTransactionDrawer';
|
||||
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 { InvoiceCustomizeDrawer } from '@/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer';
|
||||
|
||||
/**
|
||||
* Drawers container of the dashboard.
|
||||
*/
|
||||
@@ -67,6 +68,8 @@ export default function DrawersContainer() {
|
||||
<CategorizeTransactionDrawer name={DRAWERS.CATEGORIZE_TRANSACTION} />
|
||||
<ChangeSubscriptionPlanDrawer name={DRAWERS.CHANGE_SUBSCARIPTION_PLAN} />
|
||||
<InvoiceCustomizeDrawer name={DRAWERS.INVOICE_CUSTOMIZE} />
|
||||
<EstimateCustomizeDrawer name={DRAWERS.ESTIMATE_CUSTOMIZE} />
|
||||
<ReceiptCustomizeDrawer name={DRAWERS.RECEIPT_CUSTOMIZE} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function EstimateCustomizeContent() {
|
||||
return <h1>Hello World</h1>;
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
@@ -7,6 +7,11 @@ import {
|
||||
NavbarGroup,
|
||||
Intent,
|
||||
Alignment,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Popover,
|
||||
PopoverInteractionKind,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
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 { compose } from '@/utils';
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||
import { DRAWERS } from '@/constants/drawers';
|
||||
|
||||
/**
|
||||
* Estimates list actions bar.
|
||||
@@ -52,6 +59,9 @@ function EstimateActionsBar({
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
|
||||
// #withDrawerActions
|
||||
openDrawer,
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
}) {
|
||||
@@ -96,6 +106,10 @@ function EstimateActionsBar({
|
||||
const handlePrintBtnClick = () => {
|
||||
downloadExportPdf({ resource: 'SaleEstimate' });
|
||||
};
|
||||
// Handle customize button clicl.
|
||||
const handleCustomizeBtnClick = () => {
|
||||
openDrawer(DRAWERS.ESTIMATE_CUSTOMIZE);
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
@@ -167,6 +181,25 @@ function EstimateActionsBar({
|
||||
</NavbarGroup>
|
||||
|
||||
<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
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="refresh-16" iconSize={14} />}
|
||||
@@ -187,4 +220,5 @@ export default compose(
|
||||
estimatesTableSize: estimatesSettings?.tableSize,
|
||||
})),
|
||||
withDialogActions,
|
||||
withDrawerActions,
|
||||
)(EstimateActionsBar);
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
@@ -1,21 +1,65 @@
|
||||
import { Box, Group } from '@/components';
|
||||
import { InvoiceCustomizePreview } from './InvoiceCustomizePreview';
|
||||
import { InvoiceCustomizeFields } from './InvoiceCustomizeFields';
|
||||
import { InvoiceCustomizeForm } from './InvoiceCustomizerForm';
|
||||
import React from 'react';
|
||||
import { Box } from '@/components';
|
||||
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() {
|
||||
return (
|
||||
<Box className={Classes.DRAWER_BODY}>
|
||||
<InvoiceCustomizeForm>
|
||||
<Group spacing={0} align="stretch">
|
||||
<InvoiceCustomizeTabsControllerProvider>
|
||||
<InvoiceCustomizeFields />
|
||||
<InvoiceCustomizePreview />
|
||||
</InvoiceCustomizeTabsControllerProvider>
|
||||
</Group>
|
||||
</InvoiceCustomizeForm>
|
||||
<InvoiceCustomize<InvoiceCustomizeValues>>
|
||||
<InvoiceCustomize.PaperTemplate>
|
||||
<InvoicePaperTemplate />
|
||||
</InvoiceCustomize.PaperTemplate>
|
||||
|
||||
<InvoiceCustomize.FieldsTab id={'general'} label={'General'}>
|
||||
<InvoiceCustomizeGeneralField />
|
||||
</InvoiceCustomize.FieldsTab>
|
||||
|
||||
<InvoiceCustomize.FieldsTab id={'content'} label={'Content'}>
|
||||
<InvoiceCustomizeContentFields />
|
||||
</InvoiceCustomize.FieldsTab>
|
||||
|
||||
<InvoiceCustomize.FieldsTab id={'totals'} label={'Totals'}>
|
||||
asdfasdfdsaf #3
|
||||
</InvoiceCustomize.FieldsTab>
|
||||
</InvoiceCustomize>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ const InvoiceCustomizeContent = React.lazy(
|
||||
);
|
||||
|
||||
/**
|
||||
* Refund credit note detail.
|
||||
* @returns
|
||||
* Invoice customize drawer.
|
||||
* @returns {React.ReactNode}
|
||||
*/
|
||||
function InvoiceCustomizeDrawerRoot({
|
||||
name,
|
||||
@@ -19,12 +19,7 @@ function InvoiceCustomizeDrawerRoot({
|
||||
payload: {},
|
||||
}) {
|
||||
return (
|
||||
<Drawer
|
||||
isOpen={isOpen}
|
||||
name={name}
|
||||
size={'100%'}
|
||||
>
|
||||
|
||||
<Drawer isOpen={isOpen} name={name} size={'100%'}>
|
||||
<DrawerSuspense>
|
||||
<InvoiceCustomizeContent />
|
||||
</DrawerSuspense>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
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 { InvoiceCustomizeTabs } from './InvoiceCustomizeTabs';
|
||||
import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields';
|
||||
import { useInvoiceCustomizeTabsController } from './InvoiceCustomizeTabsController';
|
||||
import { Button, Intent } from '@blueprintjs/core';
|
||||
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields';
|
||||
import { useInvoiceCustomizeContext } from './InvoiceCustomizeProvider';
|
||||
import styles from './InvoiceCustomizeFields.module.scss';
|
||||
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||
|
||||
export function InvoiceCustomizeFields() {
|
||||
return (
|
||||
@@ -23,14 +23,22 @@ export function InvoiceCustomizeFields() {
|
||||
|
||||
export function InvoiceCustomizeFieldsMain() {
|
||||
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 (
|
||||
<Stack spacing={0} className={styles.mainFields}>
|
||||
<InvoiceCustomizeHeader label={'Customize'} />
|
||||
|
||||
<Stack spacing={0} style={{ flex: '1 1 auto', overflow: 'auto' }}>
|
||||
{currentTabId === 'general' && <InvoiceCustomizeGeneralField />}
|
||||
{currentTabId === 'content' && <InvoiceCustomizeContentFields />}
|
||||
|
||||
{CustomizeTabPanel}
|
||||
<InvoiceCustomizeFooterActions />
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { Box } from '@/components';
|
||||
import { PaperTemplate } from './PaperTemplate';
|
||||
import { useInvoiceCustomizeContext } from './InvoiceCustomizeProvider';
|
||||
|
||||
export function InvoiceCustomizePreviewContent() {
|
||||
const { PaperTemplate } = useInvoiceCustomizeContext();
|
||||
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
padding: '28px 24px 40px',
|
||||
backgroundColor: '#F5F5F5',
|
||||
overflow: 'auto',
|
||||
flex: '1'
|
||||
flex: '1',
|
||||
}}
|
||||
>
|
||||
<PaperTemplate />
|
||||
{PaperTemplate}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -6,10 +6,17 @@ import {
|
||||
useInvoiceCustomizeTabsController,
|
||||
} from './InvoiceCustomizeTabsController';
|
||||
import styles from './InvoiceCustomizeTabs.module.scss';
|
||||
import { useInvoiceCustomizeContext } from './InvoiceCustomizeProvider';
|
||||
import React from 'react';
|
||||
|
||||
export function InvoiceCustomizeTabs() {
|
||||
const { setCurrentTabId } = useInvoiceCustomizeTabsController();
|
||||
|
||||
const { CustomizeTabs } = useInvoiceCustomizeContext();
|
||||
|
||||
const tabItems = React.Children.map(CustomizeTabs, (node) => ({
|
||||
...(React.isValidElement(node) ? node.props : {}),
|
||||
}));
|
||||
const handleChange = (value: InvoiceCustomizeTabsEnum) => {
|
||||
setCurrentTabId(value);
|
||||
};
|
||||
@@ -25,9 +32,9 @@ export function InvoiceCustomizeTabs() {
|
||||
onChange={handleChange}
|
||||
className={styles.tabsList}
|
||||
>
|
||||
<Tab id="general" title="General" />
|
||||
<Tab id="content" title="Content" />
|
||||
<Tab id="total" title="Total" />
|
||||
{tabItems?.map(({ id, label }: { id: string; label: string }) => (
|
||||
<Tab id={id} key={id} title={label} />
|
||||
))}
|
||||
</Tabs>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { Formik, Form } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Formik, Form, FormikHelpers } from 'formik';
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
invoiceNumber: Yup.string().required('Invoice number is required'),
|
||||
customerName: Yup.string().required('Customer name is required'),
|
||||
amount: Yup.number()
|
||||
.required('Amount is required')
|
||||
.positive('Amount must be positive'),
|
||||
});
|
||||
|
||||
interface InvoiceCustomizeFormProps {
|
||||
children: React.ReactNode;
|
||||
export interface InvoiceCustomizeFormProps<T> {
|
||||
initialValues?: T;
|
||||
validationSchema?: any;
|
||||
onSubmit?: (values: T, formikHelpers: FormikHelpers<T>) => void;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function InvoiceCustomizeForm({ children }: InvoiceCustomizeFormProps) {
|
||||
export function InvoiceCustomizeForm<T>({
|
||||
initialValues,
|
||||
validationSchema,
|
||||
onSubmit,
|
||||
children,
|
||||
}: InvoiceCustomizeFormProps<T>) {
|
||||
return (
|
||||
<Formik
|
||||
initialValues={{ invoiceNumber: '', customerName: '', amount: '' }}
|
||||
<Formik<T>
|
||||
initialValues={{ ...initialValues }}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={(values) => {}}
|
||||
onSubmit={(value, helpers) => onSubmit && onSubmit(value, helpers)}
|
||||
>
|
||||
<Form>{children}</Form>
|
||||
</Formik>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default function ReceiptCustomizeContent() {
|
||||
return <h1>asdasdasd</h1>;
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
@@ -7,6 +7,11 @@ import {
|
||||
NavbarGroup,
|
||||
Intent,
|
||||
Alignment,
|
||||
Popover,
|
||||
PopoverInteractionKind,
|
||||
Position,
|
||||
Menu,
|
||||
MenuItem,
|
||||
} from '@blueprintjs/core';
|
||||
|
||||
import { useHistory } from 'react-router-dom';
|
||||
@@ -38,6 +43,8 @@ import { SaleReceiptAction, AbilitySubject } from '@/constants/abilityOption';
|
||||
|
||||
import { DialogsName } from '@/constants/dialogs';
|
||||
import { compose } from '@/utils';
|
||||
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
|
||||
import { DRAWERS } from '@/constants/drawers';
|
||||
|
||||
/**
|
||||
* Receipts actions bar.
|
||||
@@ -55,6 +62,9 @@ function ReceiptActionsBar({
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
|
||||
// #withDrawerActions
|
||||
openDrawer,
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
}) {
|
||||
@@ -103,6 +113,10 @@ function ReceiptActionsBar({
|
||||
const handlePrintButtonClick = () => {
|
||||
downloadExportPdf({ resource: 'SaleReceipt' });
|
||||
};
|
||||
// Handle customize button click.
|
||||
const handleCustomizeBtnClick = () => {
|
||||
openDrawer(DRAWERS.RECEIPT_CUSTOMIZE);
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
@@ -173,6 +187,25 @@ function ReceiptActionsBar({
|
||||
<NavbarDivider />
|
||||
</NavbarGroup>
|
||||
<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
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="refresh-16" iconSize={14} />}
|
||||
@@ -193,4 +226,5 @@ export default compose(
|
||||
receiptsTableSize: receiptSettings?.tableSize,
|
||||
})),
|
||||
withDialogActions,
|
||||
withDrawerActions,
|
||||
)(ReceiptActionsBar);
|
||||
|
||||
10
packages/webapp/src/utils/extract-children.ts
Normal file
10
packages/webapp/src/utils/extract-children.ts
Normal 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[];
|
||||
};
|
||||
Reference in New Issue
Block a user