From e6bad277711dd33d938f78f9efc0b98deec8dee3 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sat, 7 Sep 2024 21:39:05 +0200 Subject: [PATCH 01/32] feat: wip invoice customizer --- packages/webapp/package.json | 1 + .../src/components/DrawersContainer.tsx | 2 + packages/webapp/src/constants/drawers.ts | 6 +- .../InvoiceCustomize/ColorField.module.scss | 14 ++ .../Invoices/InvoiceCustomize/ColorField.tsx | 30 ++++ .../Invoices/InvoiceCustomize/FColorField.tsx | 9 ++ .../InvoiceCustomizeContent.tsx | 21 +++ .../InvoiceCustomizeDrawer.tsx | 37 +++++ .../InvoiceCustomizeFields.module.scss | 7 + .../InvoiceCustomizeFields.tsx | 26 ++++ .../InvoiceCustomizeGeneralFields.tsx | 16 +++ .../InvoiceCustomizeHeader.module.scss | 18 +++ .../InvoiceCustomizeHeader.tsx | 36 +++++ .../InvoiceCustomizePreview.tsx | 12 ++ .../InvoiceCustomizePreviewContent.tsx | 10 ++ .../InvoiceCustomizeTabs.module.scss | 14 ++ .../InvoiceCustomize/InvoiceCustomizeTabs.tsx | 29 ++++ .../InvoiceCustomizeTabsController.tsx | 46 ++++++ .../InvoiceCustomizerForm.tsx | 27 ++++ .../PaperTemplate.module.scss | 105 ++++++++++++++ .../InvoiceCustomize/PaperTemplate.tsx | 135 ++++++++++++++++++ .../InvoicesLanding/InvoicesActionsBar.tsx | 35 +++++ pnpm-lock.yaml | 13 ++ 23 files changed, 648 insertions(+), 1 deletion(-) create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.module.scss create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/FColorField.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.module.scss create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreview.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.module.scss create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabsController.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizerForm.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.module.scss create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx diff --git a/packages/webapp/package.json b/packages/webapp/package.json index 38c1a8c0c..1fcd405e1 100644 --- a/packages/webapp/package.json +++ b/packages/webapp/package.json @@ -77,6 +77,7 @@ "react": "^18.2.0", "react-app-polyfill": "^1.0.6", "react-body-classname": "^1.3.1", + "react-colorful": "^5.6.1", "react-content-loader": "^6.0.1", "react-dev-utils": "^11.0.4", "react-dom": "^18.2.0", diff --git a/packages/webapp/src/components/DrawersContainer.tsx b/packages/webapp/src/components/DrawersContainer.tsx index 6165fda85..43b594ff3 100644 --- a/packages/webapp/src/components/DrawersContainer.tsx +++ b/packages/webapp/src/components/DrawersContainer.tsx @@ -25,6 +25,7 @@ import CategorizeTransactionDrawer from '@/containers/CashFlow/CategorizeTransac import ChangeSubscriptionPlanDrawer from '@/containers/Subscriptions/drawers/ChangeSubscriptionPlanDrawer/ChangeSubscriptionPlanDrawer'; import { DRAWERS } from '@/constants/drawers'; +import { InvoiceCustomizeDrawer } from '@/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer'; /** * Drawers container of the dashboard. @@ -65,6 +66,7 @@ export default function DrawersContainer() { + ); } diff --git a/packages/webapp/src/constants/drawers.ts b/packages/webapp/src/constants/drawers.ts index 5a3a96cd3..82a4c7967 100644 --- a/packages/webapp/src/constants/drawers.ts +++ b/packages/webapp/src/constants/drawers.ts @@ -24,5 +24,9 @@ export enum DRAWERS { WAREHOUSE_TRANSFER_DETAILS = 'warehouse-transfer-detail-drawer', TAX_RATE_DETAILS = 'tax-rate-detail-drawer', CATEGORIZE_TRANSACTION = 'categorize-transaction', - CHANGE_SUBSCARIPTION_PLAN = 'change-subscription-plan' + CHANGE_SUBSCARIPTION_PLAN = 'change-subscription-plan', + INVOICE_CUSTOMIZE = 'INVOICE_CUSTOMIZE', + ESTIMATE_CUSTOMIZE = 'ESTIMATE_CUSTOMIZE', + PAYMENT_RECEIPT_CUSTOMIZE = 'PAYMENT_RECEIPT_CUSTOMIZE', + RECEIPT_CUSTOMIZE = 'RECEIPT_CUSTOMIZE', } diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.module.scss new file mode 100644 index 000000000..a018fb5c9 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.module.scss @@ -0,0 +1,14 @@ + +.field{ + height: 28px; + line-height: 28px; + border-radius: 5px; +} + +.colorPicker{ + background-color: rgb(103, 114, 229); + border-radius: 3px; + height: 14px; + width: 14px; + cursor: pointer; +} \ No newline at end of file diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.tsx new file mode 100644 index 000000000..3f18db7e1 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.tsx @@ -0,0 +1,30 @@ +import { + InputGroup, + Popover, + PopoverInteractionKind, + Position, +} from '@blueprintjs/core'; +import { useState } from 'react'; +import { HexColorPicker } from 'react-colorful'; +import styles from './ColorField.module.scss'; + +export function ColorField() { + const [color, setColor] = useState('#aabbcc'); + + return ( + } + position={Position.BOTTOM} + interactionKind={PopoverInteractionKind.CLICK} + modifiers={{ + offset: { offset: '0, 4' }, + }} + minimal + > + } + className={styles.field} + /> + + ); +} diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/FColorField.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/FColorField.tsx new file mode 100644 index 000000000..fc2008a77 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/FColorField.tsx @@ -0,0 +1,9 @@ +import { ColorField } from './ColorField'; + +interface FColorFieldProps { + name: string; +} + +export function FColorField({ name }: FColorFieldProps) { + return ; +} diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx new file mode 100644 index 000000000..00f1933b2 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx @@ -0,0 +1,21 @@ +import { Box, Group } from '@/components'; +import { InvoiceCustomizePreview } from './InvoiceCustomizePreview'; +import { InvoiceCustomizeFields } from './InvoiceCustomizeFields'; +import { InvoiceCustomizeForm } from './InvoiceCustomizerForm'; +import { Classes } from '@blueprintjs/core'; +import { InvoiceCustomizeTabsControllerProvider } from './InvoiceCustomizeTabsController'; + +export default function InvoiceCustomizeContent() { + return ( + + + + + + + + + + + ); +} diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer.tsx new file mode 100644 index 000000000..e4e8dbe2c --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer.tsx @@ -0,0 +1,37 @@ +// @ts-nocheck +import React from 'react'; +import * as R from 'ramda'; + +import { Drawer, DrawerSuspense } from '@/components'; +import withDrawers from '@/containers/Drawer/withDrawers'; + +const InvoiceCustomizeContent = React.lazy( + () => import('./InvoiceCustomizeContent'), +); + +/** + * Refund credit note detail. + * @returns + */ +function InvoiceCustomizeDrawerRoot({ + name, + // #withDrawer + isOpen, + payload: {}, +}) { + return ( + + + + + + ); +} + +export const InvoiceCustomizeDrawer = R.compose(withDrawers())( + InvoiceCustomizeDrawerRoot, +); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.module.scss new file mode 100644 index 000000000..4159c65a9 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.module.scss @@ -0,0 +1,7 @@ +.root { + flex: 1; +} + +.mainFields{ + flex: 1; +} \ No newline at end of file diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx new file mode 100644 index 000000000..a50011509 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx @@ -0,0 +1,26 @@ +import { Box, Group } from '@/components'; +import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader'; +import { InvoiceCustomizeTabs } from './InvoiceCustomizeTabs'; +import styles from './InvoiceCustomizeFields.module.scss'; +import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields'; +import { useInvoiceCustomizeTabsController } from './InvoiceCustomizeTabsController'; + +export function InvoiceCustomizeFields() { + return ( + + + + + ); +} + +export function InvoiceCustomizeFieldsMain() { + const { currentTabId } = useInvoiceCustomizeTabsController(); + return ( + + + + {currentTabId === 'general' && } + + ); +} diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx new file mode 100644 index 000000000..2870741ad --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx @@ -0,0 +1,16 @@ +import { Box, FFormGroup } from '@/components'; +import { FColorField } from './FColorField'; + +export function InvoiceCustomizeGeneralField() { + return ( + + + + + + + + + + ); +} diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss new file mode 100644 index 000000000..28d3fa50d --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss @@ -0,0 +1,18 @@ + +.root { + align-items: center; + border-radius: 0; + box-shadow: 0 1px 0 rgba(17, 20, 24, .15); + display: flex; + flex: 0 0 auto; + min-height: 40px; + padding: 5px 5px 5px 20px; + position: relative; + background-color: #fff; +} + +.title{ + margin: 0; + font-size: 19px; + font-weight: 500; +} \ No newline at end of file diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.tsx new file mode 100644 index 000000000..6e6fd1816 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.tsx @@ -0,0 +1,36 @@ +import { Group, Icon } from '@/components'; +import styles from './InvoiceCustomizeHeader.module.scss'; +import { Button, Classes } from '@blueprintjs/core'; + +interface InvoiceCustomizeHeaderProps { + label?: string; + children?: React.ReactNode; + closeButton?: boolean; + onClose?: () => void; +} + +export function InvoiceCustomizeHeader({ + label, + closeButton, + onClose, + children, +}: InvoiceCustomizeHeaderProps) { + const handleClose = () => { + onClose && onClose(); + }; + return ( + + {label &&

{label}

} + {closeButton && ( + + +
+ ); +} + +const InvoiceCustomizeFooterActions = R.compose(withDrawerActions)( + InvoiceCustomizeFooterActionsRoot, +); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx index 2870741ad..cd3e3039c 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx @@ -1,16 +1,44 @@ -import { Box, FFormGroup } from '@/components'; -import { FColorField } from './FColorField'; +import { Box, FFormGroup, FSwitch, Stack } from '@/components'; +import { FColorInput } from './FColorField'; +import styles from './InvoiceCustomizeFields.module.scss'; +import { Classes } from '@blueprintjs/core'; export function InvoiceCustomizeGeneralField() { return ( - - - + + +

General Branding

+

+ Set your invoice details to be automatically applied every time
you + create a new invoice. +

+
+ + + - - + + -
+ + + + + ); } diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss index 28d3fa50d..afca06e9b 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss @@ -9,6 +9,7 @@ padding: 5px 5px 5px 20px; position: relative; background-color: #fff; + z-index: 1; } .title{ diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.tsx index 6e6fd1816..0dc6e209c 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.tsx @@ -1,6 +1,6 @@ import { Group, Icon } from '@/components'; -import styles from './InvoiceCustomizeHeader.module.scss'; import { Button, Classes } from '@blueprintjs/core'; +import styles from './InvoiceCustomizeHeader.module.scss'; interface InvoiceCustomizeHeaderProps { label?: string; diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreview.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreview.tsx index 8ae9a84a0..b6460bf2d 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreview.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreview.tsx @@ -1,12 +1,29 @@ +// @ts-nocheck +import * as R from 'ramda'; import { Stack } from '@/components'; import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader'; import { InvoiceCustomizePreviewContent } from './InvoiceCustomizePreviewContent'; +import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; +import withDrawerActions from '@/containers/Drawer/withDrawerActions'; -export function InvoiceCustomizePreview() { +function InvoiceCustomizePreviewRoot({ closeDrawer }) { + const { name } = useDrawerContext(); + + const handleCloseBtnClick = () => { + closeDrawer(name); + }; return ( - - + + ); } + +export const InvoiceCustomizePreview = R.compose(withDrawerActions)( + InvoiceCustomizePreviewRoot, +); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx index 91df2434c..0e526ccee 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx @@ -3,7 +3,7 @@ import { PaperTemplate } from './PaperTemplate'; export function InvoiceCustomizePreviewContent() { return ( - + ); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.module.scss index 6ca316672..2d928e745 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.module.scss +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.module.scss @@ -7,8 +7,15 @@ .content{ padding: 5px; + flex: 1; + border-right: 1px solid #E1E1E1; } .tabsList{ width: 100%; + flex: 1; + + :global .bp4-tab-list{ + flex: 1; + } } \ No newline at end of file diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.tsx index c5bcabcb0..0d02843b1 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.tsx @@ -1,4 +1,4 @@ -import { Box } from '@/components'; +import { Box, Stack } from '@/components'; import { Tab, Tabs } from '@blueprintjs/core'; import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader'; import styles from './InvoiceCustomizeTabs.module.scss'; @@ -14,16 +14,22 @@ export function InvoiceCustomizeTabs() { setCurrentTabId(value); }; return ( - + - + - + ); } diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx new file mode 100644 index 000000000..ba58df5a3 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx @@ -0,0 +1,37 @@ +import { FInputGroup, FSwitch, Group, Stack } from '@/components'; +import { Classes } from '@blueprintjs/core'; + +const items = [ + { key: 'dueAmount', label: 'Due Amount' }, + { key: 'billedTo', label: 'Billed To' }, + { key: 'balanceDue', label: 'Balance Due' }, + { key: 'termsConditions', label: 'Terms & Conditions' }, +]; + +export function InvoiceCustomizeContentFields() { + return ( + + +

General Branding

+

+ Set your invoice details to be automatically applied every time
you + create a new invoice. +

+
+ +

Header

+ + + {items.map((item, index) => ( + + + + + ))} + +
+ ); +} From c5c0342c7b18364059688f1c248e8353c8aa411c Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 8 Sep 2024 20:13:27 +0200 Subject: [PATCH 03/32] feat: wip styling invoice customize --- .../InvoiceCustomizeFields.module.scss | 5 ++ .../InvoiceCustomizeGeneralFields.module.scss | 3 + .../InvoiceCustomizeGeneralFields.tsx | 62 +++++++++++-------- .../InvoiceCustomizeHeader.module.scss | 4 +- .../InvoiceCustomizePreviewContent.tsx | 2 +- .../PaperTemplate.module.scss | 60 ++++++++++++++---- .../InvoiceCustomize/PaperTemplate.tsx | 38 +++++++----- 7 files changed, 117 insertions(+), 57 deletions(-) create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.module.scss diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.module.scss index 7222b3187..fbcbbbb5e 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.module.scss +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.module.scss @@ -1,5 +1,6 @@ .root { flex: 1; + background: #fff; } .mainFields{ @@ -16,4 +17,8 @@ padding: 10px 16px; border-top: 1px solid #d9d9d9; flex-flow: row-reverse; +} + +.showCompanyLogoField:global(.bp4-large){ + font-size: 14px; } \ No newline at end of file diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.module.scss new file mode 100644 index 000000000..e11e108a5 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.module.scss @@ -0,0 +1,3 @@ +.showCompanyLogoField{ + font-size: 14px; +} \ No newline at end of file diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx index cd3e3039c..7972e40a9 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx @@ -1,4 +1,5 @@ -import { Box, FFormGroup, FSwitch, Stack } from '@/components'; +// @ts-nocheck +import { FFormGroup, FSwitch, Stack } from '@/components'; import { FColorInput } from './FColorField'; import styles from './InvoiceCustomizeFields.module.scss'; import { Classes } from '@blueprintjs/core'; @@ -6,39 +7,48 @@ import { Classes } from '@blueprintjs/core'; export function InvoiceCustomizeGeneralField() { return ( - -

General Branding

+ +

General Branding

Set your invoice details to be automatically applied every time
you create a new invoice.

- - - + + + + - - - + + + - - - + + + +
); } diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss index afca06e9b..72c7764a1 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss @@ -5,7 +5,7 @@ box-shadow: 0 1px 0 rgba(17, 20, 24, .15); display: flex; flex: 0 0 auto; - min-height: 40px; + min-height: 55px; padding: 5px 5px 5px 20px; position: relative; background-color: #fff; @@ -14,6 +14,6 @@ .title{ margin: 0; - font-size: 19px; + font-size: 20px; font-weight: 500; } \ No newline at end of file diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx index 0e526ccee..fd504f2c5 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx @@ -3,7 +3,7 @@ import { PaperTemplate } from './PaperTemplate'; export function InvoiceCustomizePreviewContent() { return ( - + ); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.module.scss index 4989f86f2..ae98db880 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.module.scss +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.module.scss @@ -2,16 +2,19 @@ .root { border-radius: 5px; background-color: #fff; - box-shadow: inset 0 4px 0px 0 #002762, 0 10px 15px rgba(0, 0, 0, 0.15); - padding: 22px; + color: #000; + box-shadow: inset 0 4px 0px 0 #002762, 0 10px 15px rgba(0, 0, 0, 0.05); + padding: 22px 30px; position: relative; } .bigTitle{ font-size: 60px; margin: 0; - LINE-HEIGHT: 1; - MARGIN-BOTTOM: 20px; + line-height: 1; + margin-bottom: 20px; + font-weight: 500; + color: #333; } .details { @@ -22,11 +25,12 @@ } .detail { display: flex; - flex-direction: row; - gap: 10px; + flex-direction: row; + gap: 12px; } .detailLabel { min-width: 120px; + color: #333; } .addressRoot{ @@ -42,7 +46,7 @@ flex: 1; } -.table :global { +.table { margin-top: 40px; width: 100%; border-collapse: collapse; @@ -51,6 +55,18 @@ thead th{ font-weight: 400; border-bottom: 1px solid #000; + padding: 5px 10px; + + &.rate, + &.total{ + text-align: right; + } + &:first-of-type{ + padding-left: 0; + } + &:last-of-type{ + padding-right: 0; + } } tbody{ @@ -59,8 +75,20 @@ } td{ - border: 1px solid #F6F6F6; - padding: 10px 0; + border-bottom: 1px solid #F6F6F6; + padding: 12px 10px; + + &:first-of-type{ + padding-left: 0; + } + &:last-of-type{ + padding-right: 0; + } + + &.rate, + &.total{ + text-align: right; + } } } } @@ -78,6 +106,13 @@ .totalsItemLabel{ min-width: 160px; } +.totalsItemAmount{ + flex: 1 1 auto; + text-align: right; +} +.totalBottomBordered { + border-bottom: 1px solid #000; +} .logoWrap{ height: 120px; @@ -85,21 +120,24 @@ position: absolute; right: 20px; top: 20px; + border-radius: 5px; + overflow: hidden; img{ max-width: 100%; } } - .footer{ } .paragraph{ margin-bottom: 20px; + font-size: 12px; } .paragraphLabel{ - color: #333333; + margin-bottom: 2px; + color: #666; } \ No newline at end of file diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx index eac6baec6..ca542c98c 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx @@ -1,3 +1,4 @@ +import clsx from 'classnames'; import styles from './PaperTemplate.module.scss'; export function PaperTemplate() { @@ -7,7 +8,10 @@ export function PaperTemplate() {

Invoice

- +
@@ -30,7 +34,7 @@ export function PaperTemplate() {
- Bigcapital Technology, Inc.
+ Bigcapital Technology, Inc.
131 Continental Dr Suite 305 Newark,
Delaware 19713 @@ -43,7 +47,7 @@ export function PaperTemplate() {
- Billed To
+ Billed To
Bigcapital Technology, Inc.
131 Continental Dr Suite 305 Newark,
@@ -62,17 +66,17 @@ export function PaperTemplate() { Item Description - Rate - Total + Rate + Total - + Simply dummy text Simply dummy text of the printing and typesetting - 1 X $100,00 - $100,00 + 1 X $100,00 + $100,00 @@ -81,37 +85,37 @@ export function PaperTemplate() {
Sub Total
-
630.00
+
630.00
Discount
-
0.00
+
0.00
Sample Tax1 (4.70%)
-
11.75
+
11.75
Sample Tax2 (7.00%)
-
21.00
+
21.00
-
+
Total
-
$662.75
+
$662.75
Payment Made
-
100.00
+
100.00
-
+
Balance Due
-
$562.75
+
$562.75
From 9247745ab069b0160a4e39750962e725fb1b4e9d Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 8 Sep 2024 21:01:54 +0200 Subject: [PATCH 04/32] feat: wip invoice customize --- .../InvoiceCustomize/CreditCardIcon.tsx | 32 +++++++++++++++++++ .../InvoiceCustomizeFields.module.scss | 4 +-- .../InvoiceCustomizeFields.tsx | 4 +-- .../InvoiceCustomizeGeneralFields.tsx | 28 ++++++++++++++-- .../InvoiceCustomizePreview.tsx | 2 +- .../InvoiceCustomizePreviewContent.tsx | 9 +++++- .../InvoiceCustomizeTabs.module.scss | 2 +- .../InvoiceCustomize/InvoiceCustomizeTabs.tsx | 2 +- .../PaperTemplate.module.scss | 3 ++ 9 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/CreditCardIcon.tsx diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/CreditCardIcon.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/CreditCardIcon.tsx new file mode 100644 index 000000000..e37209087 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/CreditCardIcon.tsx @@ -0,0 +1,32 @@ +import { SVGProps } from 'react'; + + +interface CreditCardIconProps extends SVGProps { +} + + +export function CreditCardIcon(props: CreditCardIconProps) { + return ( + + + + + + + + ); +} diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.module.scss index fbcbbbb5e..8fd643511 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.module.scss +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.module.scss @@ -1,10 +1,10 @@ .root { - flex: 1; background: #fff; } .mainFields{ - flex: 1; + width: 400px; + height: 100vh; } .fieldGroup { diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx index 094e308dc..7bc7c7fdf 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx @@ -3,7 +3,6 @@ import * as R from 'ramda'; import { Box, Group, Stack } from '@/components'; import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader'; import { InvoiceCustomizeTabs } from './InvoiceCustomizeTabs'; -import styles from './InvoiceCustomizeFields.module.scss'; import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields'; import { useInvoiceCustomizeTabsController } from './InvoiceCustomizeTabsController'; import { Button, Intent } from '@blueprintjs/core'; @@ -11,6 +10,7 @@ import withDrawerActions from '@/containers/Drawer/withDrawerActions'; import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; import { useFormikContext } from 'formik'; import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields'; +import styles from './InvoiceCustomizeFields.module.scss'; export function InvoiceCustomizeFields() { return ( @@ -27,7 +27,7 @@ export function InvoiceCustomizeFieldsMain() { - + {currentTabId === 'general' && } {currentTabId === 'content' && } diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx index 7972e40a9..bfe44e947 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx @@ -1,8 +1,9 @@ // @ts-nocheck -import { FFormGroup, FSwitch, Stack } from '@/components'; +import { Box, FFormGroup, FSwitch, Group, Stack } from '@/components'; import { FColorInput } from './FColorField'; import styles from './InvoiceCustomizeFields.module.scss'; -import { Classes } from '@blueprintjs/core'; +import { Classes, Text } from '@blueprintjs/core'; +import { CreditCardIcon } from './CreditCardIcon'; export function InvoiceCustomizeGeneralField() { return ( @@ -49,6 +50,29 @@ export function InvoiceCustomizeGeneralField() { /> + + ); } + +function InvoiceCustomizePaymentManage() { + return ( + + + + Accept payment methods + + + Manage + + ); +} diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreview.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreview.tsx index b6460bf2d..6b74bf581 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreview.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreview.tsx @@ -13,7 +13,7 @@ function InvoiceCustomizePreviewRoot({ closeDrawer }) { closeDrawer(name); }; return ( - + + ); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.module.scss index 2d928e745..73229611d 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.module.scss +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.module.scss @@ -2,7 +2,7 @@ .root { flex: 1; min-width: 165px; - max-width: 185px; + max-width: 165px; } .content{ diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.tsx index 0d02843b1..4298af017 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.tsx @@ -1,11 +1,11 @@ import { Box, Stack } from '@/components'; import { Tab, Tabs } from '@blueprintjs/core'; import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader'; -import styles from './InvoiceCustomizeTabs.module.scss'; import { InvoiceCustomizeTabsEnum, useInvoiceCustomizeTabsController, } from './InvoiceCustomizeTabsController'; +import styles from './InvoiceCustomizeTabs.module.scss'; export function InvoiceCustomizeTabs() { const { setCurrentTabId } = useInvoiceCustomizeTabsController(); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.module.scss index ae98db880..a25e61239 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.module.scss +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.module.scss @@ -6,6 +6,9 @@ box-shadow: inset 0 4px 0px 0 #002762, 0 10px 15px rgba(0, 0, 0, 0.05); padding: 22px 30px; position: relative; + margin: 0 auto; + width: 794px; + height: 1123px; } .bigTitle{ From 132c1dfdbefe264b312cba11936726b4824eec81 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 9 Sep 2024 16:24:01 +0200 Subject: [PATCH 05/32] feat: craft the paper template style --- .../InvoiceCustomizeFields.tsx | 6 ++++- .../InvoiceCustomizeHeader.module.scss | 1 + .../PaperTemplate.module.scss | 27 ++++++++++--------- .../InvoiceCustomize/PaperTemplate.tsx | 4 ++- 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx index 7bc7c7fdf..b0453e94e 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx @@ -50,7 +50,11 @@ function InvoiceCustomizeFooterActionsRoot({ closeDrawer }) { return ( - diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss index 72c7764a1..9d4dd1f36 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss @@ -16,4 +16,5 @@ margin: 0; font-size: 20px; font-weight: 500; + color: #666; } \ No newline at end of file diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.module.scss index a25e61239..b042dd270 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.module.scss +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.module.scss @@ -2,9 +2,10 @@ .root { border-radius: 5px; background-color: #fff; - color: #000; + color: #111; box-shadow: inset 0 4px 0px 0 #002762, 0 10px 15px rgba(0, 0, 0, 0.05); - padding: 22px 30px; + padding: 24px 30px; + font-size: 12px; position: relative; margin: 0 auto; width: 794px; @@ -15,13 +16,13 @@ font-size: 60px; margin: 0; line-height: 1; - margin-bottom: 20px; + margin-bottom: 25px; font-weight: 500; color: #333; } .details { - margin-bottom: 20px; + margin-bottom: 25px; display: flex; flex-direction: column; gap: 4px; @@ -39,6 +40,7 @@ .addressRoot{ display: flex; flex-direction: row; + margin-bottom: 25px; } .addressBillTo{ @@ -50,7 +52,6 @@ } .table { - margin-top: 40px; width: 100%; border-collapse: collapse; text-align: left; @@ -58,7 +59,8 @@ thead th{ font-weight: 400; border-bottom: 1px solid #000; - padding: 5px 10px; + padding: 2px 10px; + color: #333; &.rate, &.total{ @@ -101,10 +103,11 @@ flex-direction: column; margin-bottom: 40px; margin-left: auto; + width: 300px; } .totalsItem{ display: flex; - padding: 6px 0; + padding: 4px 0; } .totalsItemLabel{ min-width: 160px; @@ -116,13 +119,16 @@ .totalBottomBordered { border-bottom: 1px solid #000; } +.totalBottomGrayBordered { + border-bottom: 1px solid #DADADA; +} .logoWrap{ height: 120px; width: 120px; position: absolute; - right: 20px; - top: 20px; + right: 26px; + top: 26px; border-radius: 5px; overflow: hidden; @@ -137,10 +143,7 @@ .paragraph{ margin-bottom: 20px; - font-size: 12px; } - .paragraphLabel{ - margin-bottom: 2px; color: #666; } \ No newline at end of file diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx index ca542c98c..8c83a94b4 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx @@ -83,7 +83,9 @@ export function PaperTemplate() {
-
+
Sub Total
630.00
From dc18bde6be4812526adf27d0aeadaf6e888306f3 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 9 Sep 2024 19:40:23 +0200 Subject: [PATCH 06/32] feat: wip invoice customizer --- .../src/components/DrawersContainer.tsx | 7 +- .../EstimateCustomizeContent.tsx | 3 + .../EstimateCustomizeDrawer.tsx | 33 +++ .../EstimatesLanding/EstimatesActionsBar.tsx | 34 +++ .../InvoiceCustomize/InvoiceCustomize.tsx | 74 ++++++ .../InvoiceCustomizeContent.tsx | 70 +++++- .../InvoiceCustomizeDrawer.tsx | 11 +- .../InvoiceCustomizeFields.tsx | 24 +- .../InvoiceCustomizePreviewContent.tsx | 8 +- .../InvoiceCustomizeProvider.tsx | 32 +++ .../InvoiceCustomize/InvoiceCustomizeTabs.tsx | 13 +- .../InvoiceCustomizerForm.tsx | 32 +-- .../InvoiceCustomize/InvoicePaperTemplate.tsx | 235 ++++++++++++++++++ .../InvoiceCustomize/PaperTemplate.tsx | 141 ----------- .../ReceiptCustomizeContent.tsx | 3 + .../ReceiptCustomizeDrawer.tsx | 32 +++ .../ReceiptsLanding/ReceiptActionsBar.tsx | 34 +++ packages/webapp/src/utils/extract-children.ts | 10 + 18 files changed, 602 insertions(+), 194 deletions(-) create mode 100644 packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx create mode 100644 packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawer.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeProvider.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx delete mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx create mode 100644 packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx create mode 100644 packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeDrawer.tsx create mode 100644 packages/webapp/src/utils/extract-children.ts diff --git a/packages/webapp/src/components/DrawersContainer.tsx b/packages/webapp/src/components/DrawersContainer.tsx index 43b594ff3..88fc83397 100644 --- a/packages/webapp/src/components/DrawersContainer.tsx +++ b/packages/webapp/src/components/DrawersContainer.tsx @@ -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() { + +
); } diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx new file mode 100644 index 000000000..e208e0b2e --- /dev/null +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx @@ -0,0 +1,3 @@ +export default function EstimateCustomizeContent() { + return

Hello World

; +} diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawer.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawer.tsx new file mode 100644 index 000000000..6a6042ba8 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawer.tsx @@ -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 ( + + + + + + ); +} + +export const EstimateCustomizeDrawer = R.compose(withDrawers())( + EstimateCustomizeDrawerRoot, +); diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.tsx index e035bbcf3..0bc42c5d4 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.tsx @@ -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 ( @@ -167,6 +181,25 @@ function EstimateActionsBar({ + + + + } + > +
); } diff --git a/packages/webapp/src/constants/drawers.ts b/packages/webapp/src/constants/drawers.ts index 82a4c7967..43bca2f59 100644 --- a/packages/webapp/src/constants/drawers.ts +++ b/packages/webapp/src/constants/drawers.ts @@ -29,4 +29,5 @@ export enum DRAWERS { ESTIMATE_CUSTOMIZE = 'ESTIMATE_CUSTOMIZE', PAYMENT_RECEIPT_CUSTOMIZE = 'PAYMENT_RECEIPT_CUSTOMIZE', RECEIPT_CUSTOMIZE = 'RECEIPT_CUSTOMIZE', + CREDIT_NOTE_CUSTOMIZE = 'CREDIT_NOTE_CUSTOMIZE' } diff --git a/packages/webapp/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteActionsBar.tsx b/packages/webapp/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteActionsBar.tsx index 5ad2f4edf..75bbcacd6 100644 --- a/packages/webapp/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteActionsBar.tsx +++ b/packages/webapp/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteActionsBar.tsx @@ -8,6 +8,11 @@ import { NavbarGroup, Intent, Alignment, + Menu, + MenuItem, + Popover, + PopoverInteractionKind, + Position, } from '@blueprintjs/core'; import { Icon, @@ -30,9 +35,11 @@ import withSettingsActions from '@/containers/Settings/withSettingsActions'; import withVendorsCreditNotes from './withVendorsCreditNotes'; import withDialogActions from '@/containers/Dialog/withDialogActions'; import withVendorActions from './withVendorActions'; +import withDrawerActions from '@/containers/Drawer/withDrawerActions'; import { compose } from '@/utils'; import { DialogsName } from '@/constants/dialogs'; +import { DRAWERS } from '@/constants/drawers'; /** * Vendors Credit note table actions bar. @@ -54,6 +61,9 @@ function VendorsCreditNoteActionsBar({ // #withDialogActions openDialog, + + // #withDrawerActions + openDrawer, }) { const history = useHistory(); @@ -92,6 +102,10 @@ function VendorsCreditNoteActionsBar({ const handlePrintBtnClick = () => { downloadExportPdf({ resource: 'VendorCredit' }); }; + // Handle the customize button click. + const handleCustomizeBtnClick = () => { + openDrawer(DRAWERS.CREDIT_NOTE_DETAILS); + }; return ( @@ -152,6 +166,25 @@ function VendorsCreditNoteActionsBar({ + + + + } + > +
)}
-
-
Terms & Conditions
-
- It is a long established fact that a reader will be distracted by the - readable content of a page when looking at its layout. + {showTermsConditions && ( +
+
{termsConditionsLabel}
+
{termsConditions}
-
- -
-
Statement
-
- It is a long established fact that a reader will be distracted by the - readable content of a page when looking at its layout. + )} + {showStatement && ( +
+
{statementLabel}
+
{statement}
-
+ )}
); } + +const withFormikProps =

( + Component: React.ComponentType

, +) => { + return (props: Omit) => { + const { values } = useFormikContext(); + + return ; + }; +}; + +export const InvoicePaperTemplate = R.compose(withFormikProps)( + InvoicePaperTemplateRoot, +); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/constants.ts b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/constants.ts new file mode 100644 index 000000000..ac4a03dae --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/constants.ts @@ -0,0 +1,134 @@ +export const initialValues = { + // Colors + primaryColor: '#2c3dd8', + secondaryColor: '#2c3dd8', + + // Company logo. + showCompanyLogo: true, + companyLogo: + 'https://cdn-development.mercury.com/demo-assets/avatars/mercury-demo-dark.png', + + // Top details. + showInvoiceNumber: true, + invoiceNumberLabel: 'Invoice number', + + showDateIssue: true, + dateIssueLabel: 'Date of Issue', + + showDueDate: true, + dueDateLabel: 'Due Date', + + // Company name + companyName: 'Bigcapital Technology, Inc.', + + // Addresses + showBilledFromAddress: true, + showBillingToAddress: true, + billedToLabel: 'Billed To', + + // Entries + itemNameLabel: 'Item', + itemDescriptionLabel: 'Description', + itemRateLabel: 'Rate', + itemTotalLabel: 'Total', + + // Totals + showSubtotal: true, + subtotalLabel: 'Subtotal', + + showDiscount: true, + discountLabel: 'Discount', + + showTaxes: true, + + showTotal: true, + totalLabel: 'Total', + + paymentMadeLabel: 'Payment Made', + showPaymentMade: true, + + dueAmountLabel: 'Due Amount', + showDueAmount: true, + + // Footer paragraphs. + termsConditionsLabel: 'Terms & Conditions', + showTermsConditions: true, + + statementLabel: 'Statement', + showStatement: true, +}; + +export const fieldsGroups = [ + { + label: 'Header', + fields: [ + { + labelKey: 'invoiceNumberLabel', + enableKey: 'showInvoiceNumber', + label: 'Invoice No.', + }, + { + labelKey: 'dateIssueLabel', + enableKey: 'showDateIssue', + label: 'Issue Date', + }, + { + labelKey: 'dueDateLabel', + enableKey: 'showDueDate', + label: 'Due Date', + }, + { + enableKey: 'showBillingToAddress', + labelKey: 'billedToLabel', + label: 'Bill To', + }, + { + enableKey: 'showBilledFromAddress', + label: 'Billed From', + }, + ], + }, + { + label: 'Totals', + fields: [ + { + labelKey: 'subtotalLabel', + enableKey: 'showSubtotal', + label: 'Subtotal', + }, + { + labelKey: 'discountLabel', + enableKey: 'showDiscount', + label: 'Discount', + }, + { enableKey: 'showTaxes', label: 'Taxes' }, + { labelKey: 'totalLabel', enableKey: 'showTotal', label: 'Total' }, + { + labelKey: 'paymentMadeLabel', + enableKey: 'showPaymentMade', + label: 'Payment Made', + }, + { + labelKey: 'dueAmountLabel', + enableKey: 'showDueAmount', + label: 'Due Amount', + }, + ], + }, + { + label: 'Footer', + fields: [ + { + labelKey: 'termsConditionsLabel', + enableKey: 'showTermsConditions', + label: 'Terms & Conditions', + }, + { + labelKey: 'statementLabel', + enableKey: 'showStatement', + label: 'Statement', + labelPlaceholder: 'Statement', + }, + ], + }, +]; diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/types.ts b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/types.ts new file mode 100644 index 000000000..6985bafb4 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/types.ts @@ -0,0 +1,58 @@ +export interface InvoiceCustomizeValues { + // Colors + primaryColor?: string; + secondaryColor?: string; + + // Company Logo + showCompanyLogo?: boolean; + companyLogo?: string; + + // Top details. + showInvoiceNumber?: boolean; + invoiceNumberLabel?: string; + + showDateIssue?: boolean; + dateIssueLabel?: string; + + showDueDate?: boolean; + dueDateLabel?: string; + + // Company name + companyName?: string; + + // Addresses + showBilledFromAddress?: boolean; + showBillingToAddress?: boolean; + billedToLabel?: string; + + // Entries + itemNameLabel?: string; + itemDescriptionLabel?: string; + itemRateLabel?: string; + itemTotalLabel?: string; + + // Totals + showSubtotal?: boolean; + subtotalLabel?: string; + + showDiscount?: boolean; + discountLabel?: string; + + showTaxes?: boolean; + + showTotal?: boolean; + totalLabel?: string; + + paymentMadeLabel?: string; + showPaymentMade?: boolean; + + dueAmountLabel?: string; + showDueAmount?: boolean; + + // Footer paragraphs. + termsConditionsLabel?: string; + showTermsConditions?: boolean; + + statementLabel?: string; + showStatement?: boolean; +} diff --git a/packages/webapp/src/utils/sanitize-hex-color.ts b/packages/webapp/src/utils/sanitize-hex-color.ts new file mode 100644 index 000000000..e89bb76de --- /dev/null +++ b/packages/webapp/src/utils/sanitize-hex-color.ts @@ -0,0 +1,3 @@ +export function sanitizeToHexColor(input: string) { + return input; +} From 317adfa0de0e726b49d35af10d0531fcf0323fdf Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 10 Sep 2024 17:06:17 +0200 Subject: [PATCH 10/32] feat: wip estimate, receipt, payment received customize --- .../src/components/DrawersContainer.tsx | 2 + packages/webapp/src/constants/drawers.ts | 4 +- .../CreditNoteCustomizeContent.tsx | 37 ++++- .../CreditNoteCustomizeGeneralFields.tsx | 58 ++++++++ .../CreditNoteCutomizeContentFields.tsx | 22 +++ .../CreditNoteCustomize/constants.ts | 59 ++++++++ .../CreditNotes/CreditNoteCustomize/types.ts | 5 + .../EstimateCustomizeContent.tsx | 36 ++++- .../EstimateCustomizeFieldsContent.tsx | 46 ++++++ .../EstimateCustomizeFieldsGeneral.tsx | 59 ++++++++ .../Estimates/EstimateCustomize/constants.ts | 121 ++++++++++++++++ .../Estimates/EstimateCustomize/types.ts | 58 ++++++++ .../PaymentReceivedCustomizeContent.tsx | 37 +++++ .../PaymentReceivedCustomizeDrawer.tsx | 32 +++++ .../PaymentReceivedCustomizeFieldsContent.tsx | 22 +++ .../PaymentReceivedCustomizeFieldsGeneral.tsx | 59 ++++++++ .../PaymentReceivedCustomize/constants.ts | 134 ++++++++++++++++++ .../PaymentReceivedCustomize/types.ts | 58 ++++++++ .../PaymentsReceivedActionsBar.tsx | 34 +++++ .../ReceiptCustomizeContent.tsx | 36 ++++- .../ReceiptCustomizeFieldsContent.tsx | 22 +++ .../ReceiptCustomizeFieldsGeneral.tsx | 59 ++++++++ .../Receipts/ReceiptCustomize/constants.ts | 59 ++++++++ .../Sales/Receipts/ReceiptCustomize/types.ts | 58 ++++++++ 24 files changed, 1113 insertions(+), 4 deletions(-) create mode 100644 packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeGeneralFields.tsx create mode 100644 packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCutomizeContentFields.tsx create mode 100644 packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/constants.ts create mode 100644 packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/types.ts create mode 100644 packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsContent.tsx create mode 100644 packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx create mode 100644 packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/constants.ts create mode 100644 packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/types.ts create mode 100644 packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx create mode 100644 packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeDrawer.tsx create mode 100644 packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsContent.tsx create mode 100644 packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsGeneral.tsx create mode 100644 packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/constants.ts create mode 100644 packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/types.ts create mode 100644 packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsContent.tsx create mode 100644 packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsGeneral.tsx create mode 100644 packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/constants.ts create mode 100644 packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/types.ts diff --git a/packages/webapp/src/components/DrawersContainer.tsx b/packages/webapp/src/components/DrawersContainer.tsx index 344c4712d..3e28d06af 100644 --- a/packages/webapp/src/components/DrawersContainer.tsx +++ b/packages/webapp/src/components/DrawersContainer.tsx @@ -29,6 +29,7 @@ import { ReceiptCustomizeDrawer } from '@/containers/Sales/Receipts/ReceiptCusto import { CreditNoteCustomizeDrawer } from '@/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawer'; import { DRAWERS } from '@/constants/drawers'; +import { PaymentReceivedCustomizeDrawer } from '@/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeDrawer'; /** * Drawers container of the dashboard. */ @@ -72,6 +73,7 @@ export default function DrawersContainer() { +

); } diff --git a/packages/webapp/src/constants/drawers.ts b/packages/webapp/src/constants/drawers.ts index 43bca2f59..928d9c189 100644 --- a/packages/webapp/src/constants/drawers.ts +++ b/packages/webapp/src/constants/drawers.ts @@ -29,5 +29,7 @@ export enum DRAWERS { ESTIMATE_CUSTOMIZE = 'ESTIMATE_CUSTOMIZE', PAYMENT_RECEIPT_CUSTOMIZE = 'PAYMENT_RECEIPT_CUSTOMIZE', RECEIPT_CUSTOMIZE = 'RECEIPT_CUSTOMIZE', - CREDIT_NOTE_CUSTOMIZE = 'CREDIT_NOTE_CUSTOMIZE' + CREDIT_NOTE_CUSTOMIZE = 'CREDIT_NOTE_CUSTOMIZE', + PAYMENT_RECEIVED_CUSTOMIZE = 'PAYMENT_RECEIVED_CUSTOMIZE' + } diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx index 8065e7479..6d26b5bd5 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx @@ -1,3 +1,38 @@ +import React from 'react'; +import { Box } from '@/components'; +import { Classes } from '@blueprintjs/core'; +// import { InvoicePaperTemplate } from './InvoicePaperTemplate'; +import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize'; +import { CreditNoteCustomizeGeneralField } from './CreditNoteCustomizeGeneralFields'; +import { CreditNoteCustomizeContentFields } from './CreditNoteCutomizeContentFields'; +import { CreditNoteCustomizeValues } from './types'; +import { initialValues } from './constants'; + export default function CreditNoteCustomizeContent() { - return

asdasd

; + const handleFormSubmit = (values: CreditNoteCustomizeValues) => {}; + + return ( + + + initialValues={initialValues} + onSubmit={handleFormSubmit} + > + + {/* */} + + + + + + + + + + + + asdfasdfdsaf #3 + + + + ); } diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeGeneralFields.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeGeneralFields.tsx new file mode 100644 index 000000000..41f06a231 --- /dev/null +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeGeneralFields.tsx @@ -0,0 +1,58 @@ +// @ts-nocheck +import { Classes } from '@blueprintjs/core'; +import { FFormGroup, FSwitch, Stack } from '@/components'; +import { FColorInput } from '@/components/Forms/FColorInput'; + +export function CreditNoteCustomizeGeneralField() { + return ( + + +

General Branding

+

+ Set your invoice details to be automatically applied every timeyou + create a new invoice. +

+
+ + + + + + + + + + + + + + +
+ ); +} diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCutomizeContentFields.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCutomizeContentFields.tsx new file mode 100644 index 000000000..9bd40caf6 --- /dev/null +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCutomizeContentFields.tsx @@ -0,0 +1,22 @@ +// @ts-nocheck +import { Stack } from '@/components'; +import { Classes } from '@blueprintjs/core'; + +export function CreditNoteCustomizeContentFields() { + return ( + + +

General Branding

+

+ Set your invoice details to be automatically applied every timeyou + create a new invoice. +

+
+ + +
+ ); +} diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/constants.ts b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/constants.ts new file mode 100644 index 000000000..2524d1e88 --- /dev/null +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/constants.ts @@ -0,0 +1,59 @@ +export const initialValues = { + // Colors + primaryColor: '#2c3dd8', + secondaryColor: '#2c3dd8', + + // Company logo. + showCompanyLogo: true, + companyLogo: + 'https://cdn-development.mercury.com/demo-assets/avatars/mercury-demo-dark.png', + + // Top details. + showInvoiceNumber: true, + invoiceNumberLabel: 'Invoice number', + + showDateIssue: true, + dateIssueLabel: 'Date of Issue', + + showDueDate: true, + dueDateLabel: 'Due Date', + + // Company name + companyName: 'Bigcapital Technology, Inc.', + + // Addresses + showBilledFromAddress: true, + showBillingToAddress: true, + billedToLabel: 'Billed To', + + // Entries + itemNameLabel: 'Item', + itemDescriptionLabel: 'Description', + itemRateLabel: 'Rate', + itemTotalLabel: 'Total', + + // Totals + showSubtotal: true, + subtotalLabel: 'Subtotal', + + showDiscount: true, + discountLabel: 'Discount', + + showTaxes: true, + + showTotal: true, + totalLabel: 'Total', + + paymentMadeLabel: 'Payment Made', + showPaymentMade: true, + + dueAmountLabel: 'Due Amount', + showDueAmount: true, + + // Footer paragraphs. + termsConditionsLabel: 'Terms & Conditions', + showTermsConditions: true, + + statementLabel: 'Statement', + showStatement: true, +}; diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/types.ts b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/types.ts new file mode 100644 index 000000000..22067f1ce --- /dev/null +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/types.ts @@ -0,0 +1,5 @@ + + +export interface CreditNoteCustomizeValues { + +} \ No newline at end of file diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx index e208e0b2e..38f8fbe43 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx @@ -1,3 +1,37 @@ +import React from 'react'; +import { Box } from '@/components'; +import { Classes } from '@blueprintjs/core'; +import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize'; +import { EstimateCustomizeGeneralField } from './EstimateCustomizeFieldsGeneral'; +import { EstimateCustomizeContentFields } from './EstimateCustomizeFieldsContent'; +import { EstimateCustomizeValues } from './types'; +import { initialValues } from './constants'; + export default function EstimateCustomizeContent() { - return

Hello World

; + const handleFormSubmit = (values: EstimateCustomizeValues) => {}; + + return ( + + + initialValues={initialValues} + onSubmit={handleFormSubmit} + > + {/* + + */} + + + + + + + + + + + asdfasdfdsaf #3 + + + + ); } diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsContent.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsContent.tsx new file mode 100644 index 000000000..3acec44a7 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsContent.tsx @@ -0,0 +1,46 @@ +// @ts-nocheck +import { FInputGroup, FSwitch, Group, Stack } from '@/components'; +import { CLASSES } from '@/constants'; +import { Classes } from '@blueprintjs/core'; +import { fieldsGroups } from './constants'; + +export function EstimateCustomizeContentFields() { + return ( + + +

General Branding

+

+ Set your invoice details to be automatically applied every time
you + create a new invoice. +

+
+ + + {fieldsGroups.map((group) => ( + <> +

+ {group.label} +

+ + {group.fields.map((item, index) => ( + + + {item.labelKey && ( + + )} + + ))} + + + ))} +
+
+ ); +} diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx new file mode 100644 index 000000000..707a0b44f --- /dev/null +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx @@ -0,0 +1,59 @@ +// @ts-nocheck +import { Classes, Text } from '@blueprintjs/core'; +import { FFormGroup, FSwitch, Group, Stack } from '@/components'; +import { FColorInput } from '@/components/Forms/FColorInput'; +// import styles from './InvoiceCustomizeFields.module.scss'; + +export function EstimateCustomizeGeneralField() { + return ( + + +

General Branding

+

+ Set your invoice details to be automatically applied every time
you + create a new invoice. +

+
+ + + + + + + + + + + + + + +
+ ); +} diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/constants.ts b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/constants.ts new file mode 100644 index 000000000..ad6b6f8d4 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/constants.ts @@ -0,0 +1,121 @@ + +export const initialValues = { + // Colors + primaryColor: '#2c3dd8', + secondaryColor: '#2c3dd8', + + // Company logo. + showCompanyLogo: true, + companyLogo: + 'https://cdn-development.mercury.com/demo-assets/avatars/mercury-demo-dark.png', + + // Top details. + showEstimateNumber: true, + estimateNumberLabel: 'Estimate number', + + showDateIssue: true, + dateIssueLabel: 'Date of Issue', + + showExpirationDate: true, + expirationDateLabel: 'Expiration Date', + + // Company name + companyName: 'Bigcapital Technology, Inc.', + + // Addresses + showBilledFromAddress: true, + showBillingToAddress: true, + billedToLabel: 'Billed To', + + // Entries + itemNameLabel: 'Item', + itemDescriptionLabel: 'Description', + itemRateLabel: 'Rate', + itemTotalLabel: 'Total', + + // Totals + showSubtotal: true, + subtotalLabel: 'Subtotal', + + showDiscount: true, + discountLabel: 'Discount', + + showTaxes: true, + + showTotal: true, + totalLabel: 'Total', + + // Footer paragraphs. + termsConditionsLabel: 'Terms & Conditions', + showTermsConditions: true, + + statementLabel: 'Statement', + showStatement: true, +}; + + + +export const fieldsGroups = [ + { + label: 'Header', + fields: [ + { + labelKey: 'estimateNumberLabel', + enableKey: 'showEstimateeNumber', + label: 'Estimate No.', + }, + { + labelKey: 'dateIssueLabel', + enableKey: 'showDateIssue', + label: 'Issue Date', + }, + { + labelKey: 'expirationDateLabel', + enableKey: 'expirationDueDate', + label: 'Expiration Date', + }, + { + enableKey: 'showBillingToAddress', + labelKey: 'billedToLabel', + label: 'Bill To', + }, + { + enableKey: 'showBilledFromAddress', + label: 'Billed From', + }, + ], + }, + { + label: 'Totals', + fields: [ + { + labelKey: 'subtotalLabel', + enableKey: 'showSubtotal', + label: 'Subtotal', + }, + { + labelKey: 'discountLabel', + enableKey: 'showDiscount', + label: 'Discount', + }, + { enableKey: 'showTaxes', label: 'Taxes' }, + { labelKey: 'totalLabel', enableKey: 'showTotal', label: 'Total' }, + ], + }, + { + label: 'Footer', + fields: [ + { + labelKey: 'termsConditionsLabel', + enableKey: 'showTermsConditions', + label: 'Terms & Conditions', + }, + { + labelKey: 'statementLabel', + enableKey: 'showStatement', + label: 'Statement', + labelPlaceholder: 'Statement', + }, + ], + }, +]; diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/types.ts b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/types.ts new file mode 100644 index 000000000..7dbf7182a --- /dev/null +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/types.ts @@ -0,0 +1,58 @@ +export interface EstimateCustomizeValues { + // Colors + primaryColor?: string; + secondaryColor?: string; + + // Company Logo + showCompanyLogo?: boolean; + companyLogo?: string; + + // Top details. + showInvoiceNumber?: boolean; + invoiceNumberLabel?: string; + + showDateIssue?: boolean; + dateIssueLabel?: string; + + showDueDate?: boolean; + dueDateLabel?: string; + + // Company name + companyName?: string; + + // Addresses + showBilledFromAddress?: boolean; + showBillingToAddress?: boolean; + billedToLabel?: string; + + // Entries + itemNameLabel?: string; + itemDescriptionLabel?: string; + itemRateLabel?: string; + itemTotalLabel?: string; + + // Totals + showSubtotal?: boolean; + subtotalLabel?: string; + + showDiscount?: boolean; + discountLabel?: string; + + showTaxes?: boolean; + + showTotal?: boolean; + totalLabel?: string; + + paymentMadeLabel?: string; + showPaymentMade?: boolean; + + dueAmountLabel?: string; + showDueAmount?: boolean; + + // Footer paragraphs. + termsConditionsLabel?: string; + showTermsConditions?: boolean; + + statementLabel?: string; + showStatement?: boolean; +} diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx new file mode 100644 index 000000000..75afcdf12 --- /dev/null +++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Box } from '@/components'; +import { Classes } from '@blueprintjs/core'; +import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize'; +import { PaymentReceivedCustomizeGeneralField } from './PaymentReceivedCustomizeFieldsGeneral'; +import { PaymentReceivedCustomizeContentFields } from './PaymentReceivedCustomizeFieldsContent'; +import { PaymentReceivedCustomizeValues } from './types'; +import { initialValues } from './constants'; + +export default function PaymentReceivedCustomizeContent() { + const handleFormSubmit = (values: PaymentReceivedCustomizeValues) => {}; + + return ( + + + initialValues={initialValues} + onSubmit={handleFormSubmit} + > + {/* + + */} + + + + + + + + + + + asdfasdfdsaf #3 + + + + ); +} diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeDrawer.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeDrawer.tsx new file mode 100644 index 000000000..78ba99dba --- /dev/null +++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeDrawer.tsx @@ -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 PaymentReceivedCustomizeContent = React.lazy( + () => import('./PaymentReceivedCustomizeContent'), +); + +/** + * PaymentReceived customize drawer. + * @returns {React.ReactNode} + */ +function PaymentReceivedCustomizeDrawerRoot({ + name, + // #withDrawer + isOpen, + payload: {}, +}) { + return ( + + + + + + ); +} + +export const PaymentReceivedCustomizeDrawer = R.compose(withDrawers())( + PaymentReceivedCustomizeDrawerRoot, +); diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsContent.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsContent.tsx new file mode 100644 index 000000000..94519a9b6 --- /dev/null +++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsContent.tsx @@ -0,0 +1,22 @@ +// @ts-nocheck +import { Stack } from '@/components'; +import { Classes } from '@blueprintjs/core'; + +export function PaymentReceivedCustomizeContentFields() { + return ( + + +

General Branding

+

+ Set your invoice details to be automatically applied every timeyou + create a new invoice. +

+
+ + +
+ ); +} diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsGeneral.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsGeneral.tsx new file mode 100644 index 000000000..8f3158263 --- /dev/null +++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsGeneral.tsx @@ -0,0 +1,59 @@ +// @ts-nocheck +import { Classes } from '@blueprintjs/core'; +import { FFormGroup, FSwitch, Stack } from '@/components'; +import { FColorInput } from '@/components/Forms/FColorInput'; +// import styles from './InvoiceCustomizeFields.module.scss'; + +export function PaymentReceivedCustomizeGeneralField() { + return ( + + +

General Branding

+

+ Set your invoice details to be automatically applied every timeyou + create a new invoice. +

+
+ + + + + + + + + + + + + + +
+ ); +} diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/constants.ts b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/constants.ts new file mode 100644 index 000000000..ac4a03dae --- /dev/null +++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/constants.ts @@ -0,0 +1,134 @@ +export const initialValues = { + // Colors + primaryColor: '#2c3dd8', + secondaryColor: '#2c3dd8', + + // Company logo. + showCompanyLogo: true, + companyLogo: + 'https://cdn-development.mercury.com/demo-assets/avatars/mercury-demo-dark.png', + + // Top details. + showInvoiceNumber: true, + invoiceNumberLabel: 'Invoice number', + + showDateIssue: true, + dateIssueLabel: 'Date of Issue', + + showDueDate: true, + dueDateLabel: 'Due Date', + + // Company name + companyName: 'Bigcapital Technology, Inc.', + + // Addresses + showBilledFromAddress: true, + showBillingToAddress: true, + billedToLabel: 'Billed To', + + // Entries + itemNameLabel: 'Item', + itemDescriptionLabel: 'Description', + itemRateLabel: 'Rate', + itemTotalLabel: 'Total', + + // Totals + showSubtotal: true, + subtotalLabel: 'Subtotal', + + showDiscount: true, + discountLabel: 'Discount', + + showTaxes: true, + + showTotal: true, + totalLabel: 'Total', + + paymentMadeLabel: 'Payment Made', + showPaymentMade: true, + + dueAmountLabel: 'Due Amount', + showDueAmount: true, + + // Footer paragraphs. + termsConditionsLabel: 'Terms & Conditions', + showTermsConditions: true, + + statementLabel: 'Statement', + showStatement: true, +}; + +export const fieldsGroups = [ + { + label: 'Header', + fields: [ + { + labelKey: 'invoiceNumberLabel', + enableKey: 'showInvoiceNumber', + label: 'Invoice No.', + }, + { + labelKey: 'dateIssueLabel', + enableKey: 'showDateIssue', + label: 'Issue Date', + }, + { + labelKey: 'dueDateLabel', + enableKey: 'showDueDate', + label: 'Due Date', + }, + { + enableKey: 'showBillingToAddress', + labelKey: 'billedToLabel', + label: 'Bill To', + }, + { + enableKey: 'showBilledFromAddress', + label: 'Billed From', + }, + ], + }, + { + label: 'Totals', + fields: [ + { + labelKey: 'subtotalLabel', + enableKey: 'showSubtotal', + label: 'Subtotal', + }, + { + labelKey: 'discountLabel', + enableKey: 'showDiscount', + label: 'Discount', + }, + { enableKey: 'showTaxes', label: 'Taxes' }, + { labelKey: 'totalLabel', enableKey: 'showTotal', label: 'Total' }, + { + labelKey: 'paymentMadeLabel', + enableKey: 'showPaymentMade', + label: 'Payment Made', + }, + { + labelKey: 'dueAmountLabel', + enableKey: 'showDueAmount', + label: 'Due Amount', + }, + ], + }, + { + label: 'Footer', + fields: [ + { + labelKey: 'termsConditionsLabel', + enableKey: 'showTermsConditions', + label: 'Terms & Conditions', + }, + { + labelKey: 'statementLabel', + enableKey: 'showStatement', + label: 'Statement', + labelPlaceholder: 'Statement', + }, + ], + }, +]; diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/types.ts b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/types.ts new file mode 100644 index 000000000..9e5f89170 --- /dev/null +++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/types.ts @@ -0,0 +1,58 @@ +export interface PaymentReceivedCustomizeValues { + // Colors + primaryColor?: string; + secondaryColor?: string; + + // Company Logo + showCompanyLogo?: boolean; + companyLogo?: string; + + // Top details. + showInvoiceNumber?: boolean; + invoiceNumberLabel?: string; + + showDateIssue?: boolean; + dateIssueLabel?: string; + + showDueDate?: boolean; + dueDateLabel?: string; + + // Company name + companyName?: string; + + // Addresses + showBilledFromAddress?: boolean; + showBillingToAddress?: boolean; + billedToLabel?: string; + + // Entries + itemNameLabel?: string; + itemDescriptionLabel?: string; + itemRateLabel?: string; + itemTotalLabel?: string; + + // Totals + showSubtotal?: boolean; + subtotalLabel?: string; + + showDiscount?: boolean; + discountLabel?: string; + + showTaxes?: boolean; + + showTotal?: boolean; + totalLabel?: string; + + paymentMadeLabel?: string; + showPaymentMade?: boolean; + + dueAmountLabel?: string; + showDueAmount?: boolean; + + // Footer paragraphs. + termsConditionsLabel?: string; + showTermsConditions?: boolean; + + statementLabel?: string; + showStatement?: boolean; +} diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentsLanding/PaymentsReceivedActionsBar.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentsLanding/PaymentsReceivedActionsBar.tsx index ae981ed96..eca4dc208 100644 --- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentsLanding/PaymentsReceivedActionsBar.tsx +++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentsLanding/PaymentsReceivedActionsBar.tsx @@ -7,6 +7,11 @@ import { NavbarGroup, Intent, Alignment, + Popover, + Menu, + MenuItem, + PopoverInteractionKind, + Position, } from '@blueprintjs/core'; import { useHistory } from 'react-router-dom'; @@ -38,6 +43,8 @@ import { useDownloadExportPdf } from '@/hooks/query/FinancialReports/use-export- import { compose } from '@/utils'; import { DialogsName } from '@/constants/dialogs'; +import withDrawerActions from '@/containers/Drawer/withDrawerActions'; +import { DRAWERS } from '@/constants/drawers'; /** * Payment receives actions bar. @@ -57,6 +64,9 @@ function PaymentsReceivedActionsBar({ // #withDialogActions openDialog, + + // #withDrawerActions + openDrawer, }) { // History context. const history = useHistory(); @@ -101,6 +111,10 @@ function PaymentsReceivedActionsBar({ const handlePrintBtnClick = () => { downloadExportPdf({ resource: 'PaymentReceive' }); }; + // Handle the customize button click. + const handleCustomizeBtnClick = () => { + openDrawer(DRAWERS.PAYMENT_RECEIVED_CUSTOMIZE); + }; return ( @@ -170,6 +184,25 @@ function PaymentsReceivedActionsBar({ + + + + } + > +
); } diff --git a/packages/webapp/src/constants/drawers.ts b/packages/webapp/src/constants/drawers.ts index 928d9c189..843212d8e 100644 --- a/packages/webapp/src/constants/drawers.ts +++ b/packages/webapp/src/constants/drawers.ts @@ -30,6 +30,6 @@ export enum DRAWERS { PAYMENT_RECEIPT_CUSTOMIZE = 'PAYMENT_RECEIPT_CUSTOMIZE', RECEIPT_CUSTOMIZE = 'RECEIPT_CUSTOMIZE', CREDIT_NOTE_CUSTOMIZE = 'CREDIT_NOTE_CUSTOMIZE', - PAYMENT_RECEIVED_CUSTOMIZE = 'PAYMENT_RECEIVED_CUSTOMIZE' - + PAYMENT_RECEIVED_CUSTOMIZE = 'PAYMENT_RECEIVED_CUSTOMIZE', + BRANDING_TEMPLATES = 'BRANDING_TEMPLATES' } diff --git a/packages/webapp/src/containers/AlertsContainer/registered.tsx b/packages/webapp/src/containers/AlertsContainer/registered.tsx index ba2d44e41..5094b8783 100644 --- a/packages/webapp/src/containers/AlertsContainer/registered.tsx +++ b/packages/webapp/src/containers/AlertsContainer/registered.tsx @@ -29,6 +29,7 @@ import { CashflowAlerts } from '../CashFlow/CashflowAlerts'; import { BankRulesAlerts } from '../Banking/Rules/RulesList/BankRulesAlerts'; import { SubscriptionAlerts } from '../Subscriptions/alerts/alerts'; import { BankAccountAlerts } from '@/containers/CashFlow/AccountTransactions/alerts'; +import { BrandingTemplatesAlerts } from '../Sales/Invoices/BrandingTemplates/alerts/BrandingTemplatesAlerts'; export default [ ...AccountsAlerts, @@ -61,4 +62,5 @@ export default [ ...BankRulesAlerts, ...SubscriptionAlerts, ...BankAccountAlerts, + ...BrandingTemplatesAlerts, ]; diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandTemplates.module.scss b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandTemplates.module.scss new file mode 100644 index 000000000..b9ae04f02 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandTemplates.module.scss @@ -0,0 +1,15 @@ + + +.table { + :global { + .table .tbody .tr .td{ + padding-top: 14px; + padding-bottom: 14px; + } + + .table .thead .th{ + text-transform: uppercase; + font-size: 13px; + } + } +} \ No newline at end of file diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesActionsBar.tsx b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesActionsBar.tsx new file mode 100644 index 000000000..65aa94e3e --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesActionsBar.tsx @@ -0,0 +1,35 @@ +// @ts-nocheck +import React from 'react'; +import { Button, NavbarGroup, Intent } from '@blueprintjs/core'; +import { DashboardActionsBar, Icon } from '@/components'; +import { DRAWERS } from '@/constants/drawers'; + +import withDrawerActions from '@/containers/Drawer/withDrawerActions'; +import { compose } from '@/utils'; + +/** + * Account drawer action bar. + */ +function BrandingTemplateActionsBarRoot({ openDrawer }) { + // Handle new child button click. + const handleCreateBtnClick = () => { + openDrawer(DRAWERS.INVOICE_CUSTOMIZE); + }; + return ( + + + + + + ); +} +export const BrandingTemplateActionsBar = compose(withDrawerActions)( + BrandingTemplateActionsBarRoot, +); diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesBoot.tsx b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesBoot.tsx new file mode 100644 index 000000000..3a1470253 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesBoot.tsx @@ -0,0 +1,32 @@ +import React, { createContext } from 'react'; +import { useGetPdfTemplates } from '@/hooks/query/pdf-templates'; + +interface BrandingTemplatesBootValues { + pdfTemplates: any; + isPdfTemplatesLoading: boolean; +} + +const BrandingTemplatesBootContext = createContext( + {} as BrandingTemplatesBootValues, +); + +interface BrandingTemplatesBootProps { + children: React.ReactNode; +} + +function BrandingTemplatesBoot({ ...props }: BrandingTemplatesBootProps) { + const { data: pdfTemplates, isLoading: isPdfTemplatesLoading } = + useGetPdfTemplates(); + + const provider = { + pdfTemplates, + isPdfTemplatesLoading, + } as BrandingTemplatesBootValues; + + return ; +} + +const useBrandingTemplatesBoot = () => + React.useContext(BrandingTemplatesBootContext); + +export { BrandingTemplatesBoot, useBrandingTemplatesBoot }; diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesContent.tsx b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesContent.tsx new file mode 100644 index 000000000..cffad144f --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesContent.tsx @@ -0,0 +1,52 @@ +// @ts-nocheck +import * as R from 'ramda'; +import { Button, Classes, Intent } from '@blueprintjs/core'; +import { BrandingTemplatesBoot } from './BrandingTemplatesBoot'; +import { + Box, + Card, + DrawerBody, + DrawerHeaderContent, + Group, +} from '@/components'; +import { DRAWERS } from '@/constants/drawers'; +import { BrandingTemplatesTable } from './BrandingTemplatesTable'; +import withDrawerActions from '@/containers/Drawer/withDrawerActions'; +import { BrandingTemplateActionsBar } from './BrandingTemplatesActionsBar'; + +export default function BrandingTemplateContent() { + return ( + + + + + + + + + + + + + ); +} + +const BrandingTemplateHeader = R.compose(withDrawerActions)( + ({ openDrawer }) => { + const handleCreateBtnClick = () => { + openDrawer(DRAWERS.INVOICE_CUSTOMIZE); + }; + return ( + + + + ); + }, +); + +BrandingTemplateHeader.displayName = 'BrandingTemplateHeader'; diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesDrawer.tsx b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesDrawer.tsx new file mode 100644 index 000000000..afc51c22c --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesDrawer.tsx @@ -0,0 +1,37 @@ +// @ts-nocheck +import React from 'react'; +import * as R from 'ramda'; +import { Drawer, DrawerSuspense } from '@/components'; +import withDrawers from '@/containers/Drawer/withDrawers'; + +const BrandingTemplatesContent = React.lazy( + () => import('./BrandingTemplatesContent'), +); + +/** + * Invoice customize drawer. + * @returns {React.ReactNode} + */ +function BrandingTemplatesDrawerRoot({ + name, + // #withDrawer + isOpen, + payload: {}, +}) { + return ( + + + + + + ); +} + +export const BrandingTemplatesDrawer = R.compose(withDrawers())( + BrandingTemplatesDrawerRoot, +); diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesTable.tsx b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesTable.tsx new file mode 100644 index 000000000..07164d26b --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesTable.tsx @@ -0,0 +1,87 @@ +// @ts-nocheck +import * as R from 'ramda'; +import { Classes, Tag } from '@blueprintjs/core'; +import clsx from 'classnames'; +import { DataTable, Group, TableSkeletonRows } from '@/components'; +import { useBrandingTemplatesBoot } from './BrandingTemplatesBoot'; +import { ActionsMenu } from './_components'; +import withAlertActions from '@/containers/Alert/withAlertActions'; +import withDrawerActions from '@/containers/Drawer/withDrawerActions'; +import { DRAWERS } from '@/constants/drawers'; +import styles from './BrandTemplates.module.scss'; + +interface BrandingTemplatesTableProps {} + +function BrandingTemplateTableRoot({ + openAlert, + openDrawer, +}: BrandingTemplatesTableProps) { + // Table columns. + const columns = useBrandingTemplatesColumns(); + + const { isPdfTemplatesLoading, pdfTemplates } = useBrandingTemplatesBoot(); + + const handleEditTemplate = (template) => { + openDrawer(DRAWERS.INVOICE_CUSTOMIZE, { + templateId: template.id, + resource: template.resource, + }); + }; + + const handleDeleteTemplate = (template) => { + openAlert('branding-template-delete', { templateId: template.id }); + }; + + const handleCellClick = (cell, event) => { + const templateId = cell.row.original.id; + const resource = cell.row.original.resource; + + openDrawer(DRAWERS.INVOICE_CUSTOMIZE, { templateId, resource }); + }; + + return ( + + ); +} + +export const BrandingTemplatesTable = R.compose( + withAlertActions, + withDrawerActions, +)(BrandingTemplateTableRoot); + +const useBrandingTemplatesColumns = () => { + return [ + { + Header: 'Template Name', + accessor: (row) => ( + + {row.template_name} Default + + ), + width: 65, + clickable: true, + }, + { + Header: 'Created At', + accessor: 'created_at_formatted', + width: 35, + className: clsx(Classes.TEXT_MUTED), + clickable: true, + }, + ]; +}; diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/_components.tsx b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/_components.tsx new file mode 100644 index 000000000..7366e3556 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/_components.tsx @@ -0,0 +1,26 @@ +// @ts-nocheck +import { safeCallback } from '@/utils'; +import { Intent, Menu, MenuDivider, MenuItem } from '@blueprintjs/core'; + +/** + * Templates table actions menu. + */ +export function ActionsMenu({ + row: { original }, + payload: { onDeleteTemplate, onEditTemplate }, +}) { + return ( + + + + + + ); +} diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/BrandingTemplatesAlerts.ts b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/BrandingTemplatesAlerts.ts new file mode 100644 index 000000000..9bedcf82e --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/BrandingTemplatesAlerts.ts @@ -0,0 +1,10 @@ +// @ts-nocheck +import React from 'react'; + +const DeleteBrandingTemplateAlert = React.lazy( + () => import('./DeleteBrandingTemplateAlert'), +); + +export const BrandingTemplatesAlerts = [ + { name: 'branding-template-delete', component: DeleteBrandingTemplateAlert }, +]; diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/DeleteBrandingTemplateAlert.tsx b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/DeleteBrandingTemplateAlert.tsx new file mode 100644 index 000000000..a044d5fbe --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/DeleteBrandingTemplateAlert.tsx @@ -0,0 +1,72 @@ +// @ts-nocheck +import React from 'react'; +import intl from 'react-intl-universal'; +import { AppToaster } from '@/components'; +import { Alert, Intent } from '@blueprintjs/core'; +import { useDeletePdfTemplate} from '@/hooks/query/pdf-templates'; + +import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect'; +import withAlertActions from '@/containers/Alert/withAlertActions'; + +import { compose } from '@/utils'; + +/** + * Delete branding template alert. + */ +function DeleteBrandingTemplateAlert({ + // #ownProps + name, + + // #withAlertStoreConnect + isOpen, + payload: { templateId }, + + // #withAlertActions + closeAlert, +}) { + const { mutateAsync: deleteBrandingTemplateMutate } = useDeletePdfTemplate(); + + const handleConfirmDelete = () => { + deleteBrandingTemplateMutate({ templateId }) + .then(() => { + AppToaster.show({ + message: 'The branding template has been deleted successfully.', + intent: Intent.SUCCESS, + }); + closeAlert(name); + }) + .catch((error) => { + AppToaster.show({ + message: 'Something went wrong.', + intent: Intent.DANGER, + }); + closeAlert(name); + }); + }; + + const handleCancel = () => { + closeAlert(name); + }; + + return ( + +

+ Are you sure want to delete branding template? +

+
+ ); +} + +export default compose( + withAlertStoreConnect(), + withAlertActions, +)(DeleteBrandingTemplateAlert); + + diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx index c42dc2e44..966a3cf9e 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx @@ -41,11 +41,11 @@ export default function InvoiceCustomizeContent() { const reqValues = transformToEditRequest(values, templateId); // Edit existing template - editPdfTemplate({ ...reqValues }) - .then(() => handleSuccess('PDF template updated successfully!')) - .catch(() => - handleError('An error occurred while updating the PDF template.'), - ); + // editPdfTemplate({ templateId, values: reqValues }) + // .then(() => handleSuccess('PDF template updated successfully!')) + // .catch(() => + // handleError('An error occurred while updating the PDF template.'), + // ); } else { const reqValues = transformToNewRequest(values); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx index be63b7637..de71f269c 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx @@ -109,7 +109,7 @@ function InvoiceActionsBar({ // Handles the invoice customize button click. const handleCustomizeBtnClick = () => { - openDrawer(DRAWERS.INVOICE_CUSTOMIZE); + openDrawer(DRAWERS.BRANDING_TEMPLATES); }; return ( diff --git a/packages/webapp/src/hooks/query/pdf-templates.ts b/packages/webapp/src/hooks/query/pdf-templates.ts index 2045c0fb3..d01673500 100644 --- a/packages/webapp/src/hooks/query/pdf-templates.ts +++ b/packages/webapp/src/hooks/query/pdf-templates.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { useMutation, useQuery, diff --git a/packages/webapp/src/style/components/DataTable/DataTable.scss b/packages/webapp/src/style/components/DataTable/DataTable.scss index a96d7a003..3d6387028 100644 --- a/packages/webapp/src/style/components/DataTable/DataTable.scss +++ b/packages/webapp/src/style/components/DataTable/DataTable.scss @@ -20,7 +20,7 @@ .th { padding: 0.68rem 0.5rem; - background: #f5f5f5; + background: #f6f7f9; font-size: 14px; color: #424853; font-weight: 400; diff --git a/packages/webapp/src/style/pages/Dashboard/Dashboard.scss b/packages/webapp/src/style/pages/Dashboard/Dashboard.scss index a6ea3968f..02a7c0049 100644 --- a/packages/webapp/src/style/pages/Dashboard/Dashboard.scss +++ b/packages/webapp/src/style/pages/Dashboard/Dashboard.scss @@ -232,6 +232,15 @@ $dashboard-views-bar-height: 44px; &.#{$ns}-minimal.#{$ns}-intent-warning{ color: #cc7e25; } + &.#{$ns}-minimal.#{$ns}-intent-primary{ + color: #223fc5; + + &:hover, + &:focus{ + color: #223fc5; + background: rgba(34, 63, 197, 0.08); + } + } &.button--blue-highlight { background-color: #ebfaff; From 632c4629def3e9406352209ee62439794ecaa7b1 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 12 Sep 2024 14:16:07 +0200 Subject: [PATCH 18/32] feat: hook up the invice customize api --- .../PdfTemplates/PdfTemplatesController.ts | 9 +- .../services/PdfTemplate/EditPdfTemplate.ts | 13 ++- .../src/components/Drawer/DrawerProvider.tsx | 9 +- .../ElementCustomizeFields.tsx | 3 +- .../BrandingTemplateBoot.tsx | 50 ++++++++ .../BrandingTemplatesContent.tsx | 8 +- .../BrandingTemplatesTable.tsx | 3 +- .../InvoiceCustomize/InvoiceCustomize.tsx | 103 ++--------------- .../InvoiceCustomizeContent.tsx | 109 ++++++++++++++++++ .../InvoiceCustomizeDrawer.tsx | 8 +- .../InvoiceCustomizeForm.schema.ts | 5 + .../InvoiceCustomizeGeneralFields.tsx | 95 +++++++++------ .../InvoiceCutomizeContentFields.tsx | 2 +- .../InvoiceCustomize/Overlay.module.scss | 19 +++ .../Invoices/InvoiceCustomize/Overlay.tsx | 20 ++++ .../Invoices/InvoiceCustomize/constants.ts | 2 + .../Sales/Invoices/InvoiceCustomize/types.ts | 2 + .../Sales/Invoices/InvoiceCustomize/utils.ts | 37 +++++- .../InvoicesLanding/InvoicesActionsBar.tsx | 2 +- .../webapp/src/hooks/query/pdf-templates.ts | 52 +++++++-- packages/webapp/src/hooks/state/dashboard.tsx | 9 ++ 21 files changed, 391 insertions(+), 169 deletions(-) create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplateBoot.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeForm.schema.ts create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/Overlay.module.scss create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/Overlay.tsx diff --git a/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts b/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts index 2e6821bdb..172070a80 100644 --- a/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts +++ b/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts @@ -26,20 +26,21 @@ export class PdfTemplatesController extends BaseController { '/:template_id', [ param('template_id').exists().isInt().toInt(), + check('template_name').exists(), check('attributes').exists(), ], this.validationResult, this.editPdfTemplate.bind(this) ); + router.get('/', this.getPdfTemplates.bind(this)); router.get( '/:template_id', [param('template_id').exists().isInt().toInt()], this.validationResult, this.getPdfTemplate.bind(this) ); - router.get('/', this.getPdfTemplates.bind(this)); router.post( - '/invoices', + '/', [check('template_name').exists(), check('attributes').exists()], this.validationResult, this.createPdfInvoiceTemplate.bind(this) @@ -70,13 +71,13 @@ export class PdfTemplatesController extends BaseController { async editPdfTemplate(req: Request, res: Response, next: NextFunction) { const { tenantId } = req; const { template_id: templateId } = req.params; - const { attributes } = this.matchedBodyData(req); + const editTemplateDTO = this.matchedBodyData(req); try { const result = await this.pdfTemplateApplication.editPdfTemplate( tenantId, Number(templateId), - attributes + editTemplateDTO ); return res.status(200).send(result); } catch (error) { diff --git a/packages/server/src/services/PdfTemplate/EditPdfTemplate.ts b/packages/server/src/services/PdfTemplate/EditPdfTemplate.ts index 27a57b543..0e94a2c61 100644 --- a/packages/server/src/services/PdfTemplate/EditPdfTemplate.ts +++ b/packages/server/src/services/PdfTemplate/EditPdfTemplate.ts @@ -30,11 +30,14 @@ export class EditPdfTemplate { const { PdfTemplate } = this.tenancy.models(tenantId); return this.uow.withTransaction(tenantId, async (trx) => { - await PdfTemplate.query(trx) - .patch({ - ...editTemplateDTO, - }) - .where('id', templateId); + await this.eventPublisher.emitAsync(events.pdfTemplate.onEditing, { + tenantId, + templateId, + }); + await PdfTemplate.query(trx).where('id', templateId).update({ + templateName: editTemplateDTO.templateName, + attributes: editTemplateDTO.attributes, + }); await this.eventPublisher.emitAsync(events.pdfTemplate.onEdited, { tenantId, diff --git a/packages/webapp/src/components/Drawer/DrawerProvider.tsx b/packages/webapp/src/components/Drawer/DrawerProvider.tsx index f89256ba0..e5c8ebad2 100644 --- a/packages/webapp/src/components/Drawer/DrawerProvider.tsx +++ b/packages/webapp/src/components/Drawer/DrawerProvider.tsx @@ -1,7 +1,14 @@ // @ts-nocheck import React, { createContext, useContext } from 'react'; -const DrawerContext = createContext(); +interface DrawerContextValue { + name: string; + payload: Record; +} + +const DrawerContext = createContext( + {} as DrawerContextValue, +); /** * Account form provider. diff --git a/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFields.tsx b/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFields.tsx index 25294d3b1..c9846ecf6 100644 --- a/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFields.tsx +++ b/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFields.tsx @@ -47,7 +47,7 @@ export function ElementCustomizeFieldsMain() { function ElementCustomizeFooterActionsRoot({ closeDrawer }) { const { name } = useDrawerContext(); - const { submitForm } = useFormikContext(); + const { submitForm, isSubmitting } = useFormikContext(); const handleSubmitBtnClick = () => { submitForm(); @@ -62,6 +62,7 @@ function ElementCustomizeFooterActionsRoot({ closeDrawer }) { onClick={handleSubmitBtnClick} intent={Intent.PRIMARY} style={{ minWidth: 75 }} + loading={isSubmitting} > Save diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplateBoot.tsx b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplateBoot.tsx new file mode 100644 index 000000000..997436b07 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplateBoot.tsx @@ -0,0 +1,50 @@ +import React, { createContext, useContext } from 'react'; +import { + GetPdfTemplateResponse, + useGetPdfTemplate, +} from '@/hooks/query/pdf-templates'; +import { Spinner } from '@blueprintjs/core'; + +interface PdfTemplateContextValue { + templateId: number | string; + pdfTemplate: GetPdfTemplateResponse | undefined; + isPdfTemplateLoading: boolean; +} + +interface BrandingTemplateProps { + templateId: number; + children: React.ReactNode; +} + +const PdfTemplateContext = createContext( + {} as PdfTemplateContextValue, +); + +export const BrandingTemplateBoot = ({ + templateId, + children, +}: BrandingTemplateProps) => { + const { data: pdfTemplate, isLoading: isPdfTemplateLoading } = + useGetPdfTemplate(templateId, { + enabled: !!templateId, + }); + + const value = { + templateId, + pdfTemplate, + isPdfTemplateLoading, + }; + + if (isPdfTemplateLoading) { + return + } + return ( + + {children} + + ); +}; + +export const useBrandingTemplateBoot = () => { + return useContext(PdfTemplateContext); +}; diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesContent.tsx b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesContent.tsx index cffad144f..cd7fd3e37 100644 --- a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesContent.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesContent.tsx @@ -2,13 +2,7 @@ import * as R from 'ramda'; import { Button, Classes, Intent } from '@blueprintjs/core'; import { BrandingTemplatesBoot } from './BrandingTemplatesBoot'; -import { - Box, - Card, - DrawerBody, - DrawerHeaderContent, - Group, -} from '@/components'; +import { Box, Card, DrawerHeaderContent, Group } from '@/components'; import { DRAWERS } from '@/constants/drawers'; import { BrandingTemplatesTable } from './BrandingTemplatesTable'; import withDrawerActions from '@/containers/Drawer/withDrawerActions'; diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesTable.tsx b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesTable.tsx index 07164d26b..6ae205212 100644 --- a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesTable.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesTable.tsx @@ -18,7 +18,6 @@ function BrandingTemplateTableRoot({ }: BrandingTemplatesTableProps) { // Table columns. const columns = useBrandingTemplatesColumns(); - const { isPdfTemplatesLoading, pdfTemplates } = useBrandingTemplatesBoot(); const handleEditTemplate = (template) => { @@ -70,7 +69,7 @@ const useBrandingTemplatesColumns = () => { Header: 'Template Name', accessor: (row) => ( - {row.template_name} Default + {row.template_name} {row.default && Default} ), width: 65, diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx index 966a3cf9e..9ce80d618 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx @@ -1,98 +1,19 @@ -import React from 'react'; -import * as R from 'ramda'; -import { AppToaster, Box } from '@/components'; -import { Classes, Intent } from '@blueprintjs/core'; -import { useFormikContext } from 'formik'; -import { - InvoicePaperTemplate, - InvoicePaperTemplateProps, -} from './InvoicePaperTemplate'; -import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize'; -import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields'; -import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields'; -import { InvoiceCustomizeValues } from './types'; -import { initialValues } from './constants'; -import { - useCreatePdfTemplate, - useEditPdfTemplate, -} from '@/hooks/query/pdf-templates'; -import { transformToEditRequest, transformToNewRequest } from './utils'; +// @ts-nocheck +import { Classes } from '@blueprintjs/core'; +import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; +import { BrandingTemplateBoot } from '../BrandingTemplates/BrandingTemplateBoot'; +import { InvoiceCustomizeContent } from './InvoiceCustomizeContent'; +import { Box } from '@/components'; -export default function InvoiceCustomizeContent() { - const { mutateAsync: createPdfTemplate } = useCreatePdfTemplate(); - const { mutateAsync: editPdfTemplate } = useEditPdfTemplate(); - - const templateId: number = 1; - - const handleFormSubmit = (values: InvoiceCustomizeValues) => { - const handleSuccess = (message: string) => { - AppToaster.show({ - intent: Intent.SUCCESS, - message, - }); - }; - const handleError = (message: string) => { - AppToaster.show({ - intent: Intent.DANGER, - message, - }); - }; - if (templateId) { - const reqValues = transformToEditRequest(values, templateId); - - // Edit existing template - // editPdfTemplate({ templateId, values: reqValues }) - // .then(() => handleSuccess('PDF template updated successfully!')) - // .catch(() => - // handleError('An error occurred while updating the PDF template.'), - // ); - } else { - const reqValues = transformToNewRequest(values); - - // Create new template - createPdfTemplate(reqValues) - .then(() => handleSuccess('PDF template created successfully!')) - .catch(() => - handleError('An error occurred while creating the PDF template.'), - ); - } - }; +export default function InvoiceCustomize() { + const { payload } = useDrawerContext(); + const templateId = payload.templateId; return ( - - initialValues={initialValues} - onSubmit={handleFormSubmit} - > - - - - - - - - - - - - - - asdfasdfdsaf #3 - - + + + ); } - -const withFormikProps =

( - Component: React.ComponentType

, -) => { - return (props: Omit) => { - const { values } = useFormikContext(); - - return ; - }; -}; - -export const InvoicePaperTemplateFormConnected = - R.compose(withFormikProps)(InvoicePaperTemplate); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx new file mode 100644 index 000000000..ec4b4e056 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx @@ -0,0 +1,109 @@ +import React from 'react'; +import * as R from 'ramda'; +import { AppToaster } from '@/components'; +import { Intent } from '@blueprintjs/core'; +import { FormikHelpers, useFormikContext } from 'formik'; +import { + InvoicePaperTemplate, + InvoicePaperTemplateProps, +} from './InvoicePaperTemplate'; +import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize'; +import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields'; +import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields'; +import { InvoiceCustomizeValues } from './types'; +import { + useCreatePdfTemplate, + useEditPdfTemplate, +} from '@/hooks/query/pdf-templates'; +import { + transformToEditRequest, + transformToNewRequest, + useInvoiceCustomizeInitialValues, +} from './utils'; +import { InvoiceCustomizeSchema } from './InvoiceCustomizeForm.schema'; +import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; +import { useDrawerActions } from '@/hooks/state'; + +export function InvoiceCustomizeContent() { + const { mutateAsync: createPdfTemplate } = useCreatePdfTemplate(); + const { mutateAsync: editPdfTemplate } = useEditPdfTemplate(); + + const { payload, name } = useDrawerContext(); + const { closeDrawer } = useDrawerActions(); + + const templateId = payload?.templateId || null; + + const handleFormSubmit = ( + values: InvoiceCustomizeValues, + { setSubmitting }: FormikHelpers, + ) => { + const handleSuccess = (message: string) => { + AppToaster.show({ intent: Intent.SUCCESS, message }); + setSubmitting(false); + closeDrawer(name); + }; + const handleError = (message: string) => { + AppToaster.show({ intent: Intent.DANGER, message }); + setSubmitting(false); + }; + if (templateId) { + const reqValues = transformToEditRequest(values); + setSubmitting(true); + + // Edit existing template + editPdfTemplate({ templateId, values: reqValues }) + .then(() => handleSuccess('PDF template updated successfully!')) + .catch(() => + handleError('An error occurred while updating the PDF template.'), + ); + } else { + const reqValues = transformToNewRequest(values); + setSubmitting(true); + + // Create new template + createPdfTemplate(reqValues) + .then(() => handleSuccess('PDF template created successfully!')) + .catch(() => + handleError('An error occurred while creating the PDF template.'), + ); + } + }; + const initialValues = useInvoiceCustomizeInitialValues(); + + return ( + + initialValues={initialValues} + validationSchema={InvoiceCustomizeSchema} + onSubmit={handleFormSubmit} + > + + + + + + + + + + + + + + asdfasdfdsaf #3 + + + ); +} + +const withFormikProps =

( + Component: React.ComponentType

, +) => { + return (props: Omit) => { + const { values } = useFormikContext(); + + return ; + }; +}; + +export const InvoicePaperTemplateFormConnected = + R.compose(withFormikProps)(InvoicePaperTemplate); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer.tsx index 167022bec..7c6ae6589 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer.tsx @@ -4,9 +4,7 @@ import * as R from 'ramda'; import { Drawer, DrawerSuspense } from '@/components'; import withDrawers from '@/containers/Drawer/withDrawers'; -const InvoiceCustomize = React.lazy( - () => import('./InvoiceCustomize'), -); +const InvoiceCustomize = React.lazy(() => import('./InvoiceCustomize')); /** * Invoice customize drawer. @@ -16,10 +14,10 @@ function InvoiceCustomizeDrawerRoot({ name, // #withDrawer isOpen, - payload: {}, + payload, }) { return ( - + diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeForm.schema.ts b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeForm.schema.ts new file mode 100644 index 000000000..e972c3407 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeForm.schema.ts @@ -0,0 +1,5 @@ +import * as Yup from 'yup'; + +export const InvoiceCustomizeSchema = Yup.object().shape({ + templateName: Yup.string().required('Template Name is required'), +}); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx index 39c0a3c3f..c9f6d1827 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx @@ -1,61 +1,84 @@ // @ts-nocheck import { Classes, Text } from '@blueprintjs/core'; -import { FFormGroup, FSwitch, Group, Stack } from '@/components'; +import { + FFormGroup, + FieldRequiredHint, + FInputGroup, + FSwitch, + Group, + Stack, +} from '@/components'; import { FColorInput } from '@/components/Forms/FColorInput'; import { CreditCardIcon } from '@/icons/CreditCardIcon'; +import { Overlay } from './Overlay'; +import { useIsTemplateNamedFilled } from './utils'; export function InvoiceCustomizeGeneralField() { + const isTemplateNameFilled = useIsTemplateNamedFilled(); + return (

General Branding

- Set your invoice details to be automatically applied every time
you + Set your invoice details to be automatically applied every timeyou create a new invoice.

- - - } + fastField + style={{ marginBottom: 10 }} + > + + + + + + - + > + + - - - + > + + - - - - + + + + - + + ); } diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx index 98c4cbfe9..2bb666d80 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx @@ -16,7 +16,7 @@ export function InvoiceCustomizeContentFields() {

General Branding

- Set your invoice details to be automatically applied every time
you + Set your invoice details to be automatically applied every timeyou create a new invoice.

diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/Overlay.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/Overlay.module.scss new file mode 100644 index 000000000..dd9ece2b3 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/Overlay.module.scss @@ -0,0 +1,19 @@ + + +.root { + position: relative; + + &.visible::before{ + background: rgba(255, 255, 255, 0.75); + z-index: 2; + } + &::before{ + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: -1000; + } +} \ No newline at end of file diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/Overlay.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/Overlay.tsx new file mode 100644 index 000000000..eceb79424 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/Overlay.tsx @@ -0,0 +1,20 @@ +import clsx from 'classnames'; +import { Box } from '@/components'; +import styles from './Overlay.module.scss'; + +export interface OverlayProps { + visible?: boolean; + children?: React.ReactNode; +} + +export function Overlay({ children, visible }: OverlayProps) { + return ( + + {children} + + ); +} diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/constants.ts b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/constants.ts index ac4a03dae..33b739dc5 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/constants.ts +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/constants.ts @@ -1,4 +1,6 @@ export const initialValues = { + templateName: '', + // Colors primaryColor: '#2c3dd8', secondaryColor: '#2c3dd8', diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/types.ts b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/types.ts index 6985bafb4..3e972b020 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/types.ts +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/types.ts @@ -1,4 +1,6 @@ export interface InvoiceCustomizeValues { + templateName: string; + // Colors primaryColor?: string; secondaryColor?: string; diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/utils.ts b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/utils.ts index a063b9ee2..f3fcd8385 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/utils.ts +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/utils.ts @@ -1,14 +1,19 @@ import { omit } from 'lodash'; +import { useFormikContext } from 'formik'; import { InvoiceCustomizeValues } from './types'; -import { CreatePdfTemplateValues, EditPdfTemplateValues } from '@/hooks/query/pdf-templates'; +import { + CreatePdfTemplateValues, + EditPdfTemplateValues, +} from '@/hooks/query/pdf-templates'; +import { useBrandingTemplateBoot } from '../BrandingTemplates/BrandingTemplateBoot'; +import { transformToForm } from '@/utils'; +import { initialValues } from './constants'; export const transformToEditRequest = ( values: InvoiceCustomizeValues, - templateId: number, ): EditPdfTemplateValues => { return { - templateId, - templateName: 'Template Name', + templateName: values.templateName, attributes: omit(values, ['templateName']), }; }; @@ -18,7 +23,29 @@ export const transformToNewRequest = ( ): CreatePdfTemplateValues => { return { resource: 'SaleInvoice', - templateName: 'Template Name', + templateName: values.templateName, attributes: omit(values, ['templateName']), }; }; + +export const useIsTemplateNamedFilled = () => { + const { values } = useFormikContext(); + + return values.templateName && values.templateName?.length >= 4; +}; + +export const useInvoiceCustomizeInitialValues = (): InvoiceCustomizeValues => { + const { pdfTemplate } = useBrandingTemplateBoot(); + + const defaultPdfTemplate = { + templateName: pdfTemplate?.templateName, + ...pdfTemplate?.attributes, + }; + return { + ...initialValues, + ...(transformToForm( + defaultPdfTemplate, + initialValues, + ) as InvoiceCustomizeValues), + }; +}; diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx index de71f269c..bf8fe5bba 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx @@ -190,7 +190,7 @@ function InvoiceActionsBar({ } diff --git a/packages/webapp/src/hooks/query/pdf-templates.ts b/packages/webapp/src/hooks/query/pdf-templates.ts index d01673500..0e03aa81b 100644 --- a/packages/webapp/src/hooks/query/pdf-templates.ts +++ b/packages/webapp/src/hooks/query/pdf-templates.ts @@ -6,8 +6,12 @@ import { UseQueryOptions, UseMutationResult, UseQueryResult, + useQueryClient, } from 'react-query'; import useApiRequest from '../useRequest'; +import { transformToCamelCase, transfromToSnakeCase } from '@/utils'; + +const PdfTemplatesQueryKey = 'PdfTemplate'; export interface CreatePdfTemplateValues { templateName: string; @@ -18,7 +22,6 @@ export interface CreatePdfTemplateValues { export interface CreatePdfTemplateResponse {} export interface EditPdfTemplateValues { - templateId: string | number; templateName: string; attributes: Record; } @@ -33,7 +36,14 @@ export interface DeletePdfTemplateResponse {} export interface GetPdfTemplateValues {} -export interface GetPdfTemplateResponse {} +export interface GetPdfTemplateResponse { + templateName: string; + attributes: Record; + predefined: boolean; + default: boolean; + createdAt: string; + updatedAt: string | null; +} export interface GetPdfTemplatesValues {} @@ -52,10 +62,18 @@ export const useCreatePdfTemplate = ( CreatePdfTemplateValues > => { const apiRequest = useApiRequest(); + const queryClient = useQueryClient(); return useMutation( (values) => - apiRequest.post('/pdf-templates', values).then((res) => res.data), - options, + apiRequest + .post('/pdf-templates', transfromToSnakeCase(values)) + .then((res) => res.data), + { + onSuccess: () => { + queryClient.invalidateQueries([PdfTemplatesQueryKey]); + }, + ...options, + }, ); }; @@ -71,6 +89,7 @@ export const useEditPdfTemplate = ( Error, { templateId: number; values: EditPdfTemplateValues } > => { + const queryClient = useQueryClient(); const apiRequest = useApiRequest(); return useMutation< EditPdfTemplateResponse, @@ -79,9 +98,14 @@ export const useEditPdfTemplate = ( >( ({ templateId, values }) => apiRequest - .put(`/pdf-templates/${templateId}`, values) + .put(`/pdf-templates/${templateId}`, transfromToSnakeCase(values)) .then((res) => res.data), - options, + { + onSuccess: () => { + queryClient.invalidateQueries([PdfTemplatesQueryKey]); + }, + ...options, + }, ); }; @@ -98,10 +122,16 @@ export const useDeletePdfTemplate = ( { templateId: number } > => { const apiRequest = useApiRequest(); + const queryClient = useQueryClient(); return useMutation( ({ templateId }) => apiRequest.delete(`/pdf-templates/${templateId}`).then((res) => res.data), - options, + { + onSuccess: () => { + queryClient.invalidateQueries([PdfTemplatesQueryKey]); + }, + ...options, + }, ); }; @@ -112,9 +142,11 @@ export const useGetPdfTemplate = ( ): UseQueryResult => { const apiRequest = useApiRequest(); return useQuery( - ['pdfTemplate', templateId], + [PdfTemplatesQueryKey, templateId], () => - apiRequest.get(`/pdf-templates/${templateId}`).then((res) => res.data), + apiRequest + .get(`/pdf-templates/${templateId}`) + .then((res) => transformToCamelCase(res.data)), options, ); }; @@ -125,7 +157,7 @@ export const useGetPdfTemplates = ( ): UseQueryResult => { const apiRequest = useApiRequest(); return useQuery( - 'pdfTemplates', + PdfTemplatesQueryKey, () => apiRequest.get('/pdf-templates').then((res) => res.data), options, ); diff --git a/packages/webapp/src/hooks/state/dashboard.tsx b/packages/webapp/src/hooks/state/dashboard.tsx index 74cdf295e..6f424191b 100644 --- a/packages/webapp/src/hooks/state/dashboard.tsx +++ b/packages/webapp/src/hooks/state/dashboard.tsx @@ -10,6 +10,8 @@ import { closeSidebarSubmenu, openDialog, closeDialog, + openDrawer, + closeDrawer, } from '@/store/dashboard/dashboard.actions'; export const useDispatchAction = (action) => { @@ -77,3 +79,10 @@ export const useDialogActions = () => { closeDialog: useDispatchAction(closeDialog), }; }; + +export const useDrawerActions = () => { + return { + openDrawer: useDispatchAction(openDrawer), + closeDrawer: useDispatchAction(closeDrawer), + }; +}; From 12226d469af9f618b038c6fddf34a8a1bc02c465 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 12 Sep 2024 16:50:44 +0200 Subject: [PATCH 19/32] feat: pdf template customize --- .../PdfTemplates/PdfTemplatesController.ts | 20 ++++- .../services/PdfTemplate/CreatePdfTemplate.ts | 2 +- .../PdfTemplate/PdfTemplateApplication.ts | 2 + .../src/components/DrawersContainer.tsx | 2 +- .../containers/AlertsContainer/registered.tsx | 2 +- .../BrandTemplates.module.scss | 0 .../BrandingTemplateBoot.tsx | 0 .../BrandingTemplateForm.tsx | 87 +++++++++++++++++++ .../BrandingTemplatesActionsBar.tsx | 0 .../BrandingTemplatesBoot.tsx | 6 +- .../BrandingTemplatesContent.tsx | 0 .../BrandingTemplatesDrawer.tsx | 3 +- .../BrandingTemplatesTable.tsx | 0 .../BrandingTemplates/_components.tsx | 0 .../containers/BrandingTemplates/_utils.ts | 52 +++++++++++ .../alerts/BrandingTemplatesAlerts.ts | 0 .../alerts/DeleteBrandingTemplateAlert.tsx | 0 .../src/containers/BrandingTemplates/types.ts | 5 ++ .../CreditNoteCustomizeContent.tsx | 42 ++++----- .../CreditNoteCustomizeDrawerBody.tsx | 18 ++++ .../CreditNotesActionsBar.tsx | 4 +- .../EstimateCustomizeContent.tsx | 57 +++++++----- .../EstimateCustomizeDrawer.tsx | 6 +- .../EstimateCustomizeDrawerBody.tsx | 18 ++++ .../EstimateCustomizeFieldsGeneral.tsx | 21 ++++- .../Estimates/EstimateCustomize/constants.ts | 2 + .../Estimates/EstimateCustomize/types.ts | 4 +- .../EstimatesLanding/EstimatesActionsBar.tsx | 4 +- .../InvoiceCustomize/InvoiceCustomize.tsx | 2 +- .../InvoiceCustomizeContent.tsx | 65 +++----------- .../Sales/Invoices/InvoiceCustomize/types.ts | 4 +- .../Sales/Invoices/InvoiceCustomize/utils.ts | 2 +- .../InvoicesLanding/InvoicesActionsBar.tsx | 4 +- .../PaymentReceivedCustomize.tsx | 18 ++++ .../PaymentReceivedCustomizeContent.tsx | 55 +++++++----- .../PaymentReceivedCustomizeDrawer.tsx | 10 +-- .../PaymentReceivedCustomize/constants.ts | 2 + .../PaymentReceivedCustomize/types.ts | 4 +- .../PaymentsReceivedActionsBar.tsx | 4 +- .../ReceiptCustomizeContent.tsx | 54 +++++++----- .../ReceiptCustomizeDrawer.tsx | 10 +-- .../ReceiptCustomizeDrawerBody.tsx | 18 ++++ .../Receipts/ReceiptCustomize/constants.ts | 2 + .../Sales/Receipts/ReceiptCustomize/types.ts | 4 +- .../ReceiptsLanding/ReceiptActionsBar.tsx | 4 +- .../webapp/src/hooks/query/pdf-templates.ts | 8 +- 46 files changed, 436 insertions(+), 191 deletions(-) rename packages/webapp/src/containers/{Sales/Invoices => }/BrandingTemplates/BrandTemplates.module.scss (100%) rename packages/webapp/src/containers/{Sales/Invoices => }/BrandingTemplates/BrandingTemplateBoot.tsx (100%) create mode 100644 packages/webapp/src/containers/BrandingTemplates/BrandingTemplateForm.tsx rename packages/webapp/src/containers/{Sales/Invoices => }/BrandingTemplates/BrandingTemplatesActionsBar.tsx (100%) rename packages/webapp/src/containers/{Sales/Invoices => }/BrandingTemplates/BrandingTemplatesBoot.tsx (82%) rename packages/webapp/src/containers/{Sales/Invoices => }/BrandingTemplates/BrandingTemplatesContent.tsx (100%) rename packages/webapp/src/containers/{Sales/Invoices => }/BrandingTemplates/BrandingTemplatesDrawer.tsx (95%) rename packages/webapp/src/containers/{Sales/Invoices => }/BrandingTemplates/BrandingTemplatesTable.tsx (100%) rename packages/webapp/src/containers/{Sales/Invoices => }/BrandingTemplates/_components.tsx (100%) create mode 100644 packages/webapp/src/containers/BrandingTemplates/_utils.ts rename packages/webapp/src/containers/{Sales/Invoices => }/BrandingTemplates/alerts/BrandingTemplatesAlerts.ts (100%) rename packages/webapp/src/containers/{Sales/Invoices => }/BrandingTemplates/alerts/DeleteBrandingTemplateAlert.tsx (100%) create mode 100644 packages/webapp/src/containers/BrandingTemplates/types.ts create mode 100644 packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawerBody.tsx create mode 100644 packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawerBody.tsx create mode 100644 packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomize.tsx create mode 100644 packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeDrawerBody.tsx diff --git a/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts b/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts index 172070a80..63a854e22 100644 --- a/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts +++ b/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts @@ -32,7 +32,12 @@ export class PdfTemplatesController extends BaseController { this.validationResult, this.editPdfTemplate.bind(this) ); - router.get('/', this.getPdfTemplates.bind(this)); + router.get( + '/', + [query('resource').optional()], + this.validationResult, + this.getPdfTemplates.bind(this) + ); router.get( '/:template_id', [param('template_id').exists().isInt().toInt()], @@ -41,7 +46,11 @@ export class PdfTemplatesController extends BaseController { ); router.post( '/', - [check('template_name').exists(), check('attributes').exists()], + [ + check('template_name').exists(), + check('resource').exists(), + check('attributes').exists(), + ], this.validationResult, this.createPdfInvoiceTemplate.bind(this) ); @@ -54,12 +63,13 @@ export class PdfTemplatesController extends BaseController { next: NextFunction ) { const { tenantId } = req; - const { templateName, attributes } = this.matchedBodyData(req); + const { templateName, resource, attributes } = this.matchedBodyData(req); try { const result = await this.pdfTemplateApplication.createPdfTemplate( tenantId, templateName, + resource, attributes ); return res.status(201).send(result); @@ -117,10 +127,12 @@ export class PdfTemplatesController extends BaseController { async getPdfTemplates(req: Request, res: Response, next: NextFunction) { const { tenantId } = req; + const query = this.matchedQueryData(req); try { const templates = await this.pdfTemplateApplication.getPdfTemplates( - tenantId + tenantId, + query ); return res.status(200).send(templates); } catch (error) { diff --git a/packages/server/src/services/PdfTemplate/CreatePdfTemplate.ts b/packages/server/src/services/PdfTemplate/CreatePdfTemplate.ts index feab657c6..02efc83f2 100644 --- a/packages/server/src/services/PdfTemplate/CreatePdfTemplate.ts +++ b/packages/server/src/services/PdfTemplate/CreatePdfTemplate.ts @@ -24,10 +24,10 @@ export class CreatePdfTemplate { public createPdfTemplate( tenantId: number, templateName: string, + resource: string, invoiceTemplateDTO: ICreateInvoicePdfTemplateDTO ) { const { PdfTemplate } = this.tennacy.models(tenantId); - const resource = 'SaleInvoice'; const attributes = invoiceTemplateDTO; return this.uow.withTransaction(tenantId, async (trx) => { diff --git a/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts b/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts index 9f06006d7..2007f9a52 100644 --- a/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts +++ b/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts @@ -26,11 +26,13 @@ export class PdfTemplateApplication { public async createPdfTemplate( tenantId: number, templateName: string, + resource: string, invoiceTemplateDTO: ICreateInvoicePdfTemplateDTO ) { return this.createPdfTemplateService.createPdfTemplate( tenantId, templateName, + resource, invoiceTemplateDTO ); } diff --git a/packages/webapp/src/components/DrawersContainer.tsx b/packages/webapp/src/components/DrawersContainer.tsx index fdcaac652..89c1a13e5 100644 --- a/packages/webapp/src/components/DrawersContainer.tsx +++ b/packages/webapp/src/components/DrawersContainer.tsx @@ -28,7 +28,7 @@ import { EstimateCustomizeDrawer } from '@/containers/Sales/Estimates/EstimateCu import { ReceiptCustomizeDrawer } from '@/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeDrawer'; import { CreditNoteCustomizeDrawer } from '@/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawer'; import { PaymentReceivedCustomizeDrawer } from '@/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeDrawer'; -import { BrandingTemplatesDrawer } from '@/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesDrawer'; +import { BrandingTemplatesDrawer } from '@/containers/BrandingTemplates/BrandingTemplatesDrawer'; import { DRAWERS } from '@/constants/drawers'; diff --git a/packages/webapp/src/containers/AlertsContainer/registered.tsx b/packages/webapp/src/containers/AlertsContainer/registered.tsx index 5094b8783..a4180e25b 100644 --- a/packages/webapp/src/containers/AlertsContainer/registered.tsx +++ b/packages/webapp/src/containers/AlertsContainer/registered.tsx @@ -29,7 +29,7 @@ import { CashflowAlerts } from '../CashFlow/CashflowAlerts'; import { BankRulesAlerts } from '../Banking/Rules/RulesList/BankRulesAlerts'; import { SubscriptionAlerts } from '../Subscriptions/alerts/alerts'; import { BankAccountAlerts } from '@/containers/CashFlow/AccountTransactions/alerts'; -import { BrandingTemplatesAlerts } from '../Sales/Invoices/BrandingTemplates/alerts/BrandingTemplatesAlerts'; +import { BrandingTemplatesAlerts } from '../BrandingTemplates/alerts/BrandingTemplatesAlerts'; export default [ ...AccountsAlerts, diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandTemplates.module.scss b/packages/webapp/src/containers/BrandingTemplates/BrandTemplates.module.scss similarity index 100% rename from packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandTemplates.module.scss rename to packages/webapp/src/containers/BrandingTemplates/BrandTemplates.module.scss diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplateBoot.tsx b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplateBoot.tsx similarity index 100% rename from packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplateBoot.tsx rename to packages/webapp/src/containers/BrandingTemplates/BrandingTemplateBoot.tsx diff --git a/packages/webapp/src/containers/BrandingTemplates/BrandingTemplateForm.tsx b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplateForm.tsx new file mode 100644 index 000000000..7c9920cd8 --- /dev/null +++ b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplateForm.tsx @@ -0,0 +1,87 @@ +import * as Yup from 'yup'; +import { + ElementCustomize, + ElementCustomizeProps, +} from '../ElementCustomize/ElementCustomize'; +import { + transformToEditRequest, + transformToNewRequest, + useBrandingTemplateFormInitialValues, +} from './_utils'; +import { AppToaster } from '@/components'; +import { Intent } from '@blueprintjs/core'; +import { + useCreatePdfTemplate, + useEditPdfTemplate, +} from '@/hooks/query/pdf-templates'; +import { FormikHelpers } from 'formik'; +import { BrandingTemplateValues } from './types'; + +interface BrandingTemplateFormProps extends ElementCustomizeProps { + resource: string; + templateId?: number; + onSuccess?: () => void; + onError?: () => void; + defaultValues?: T; +} + +export function BrandingTemplateForm({ + templateId, + onSuccess, + onError, + defaultValues, + resource, + ...props +}: BrandingTemplateFormProps) { + const { mutateAsync: createPdfTemplate } = useCreatePdfTemplate(); + const { mutateAsync: editPdfTemplate } = useEditPdfTemplate(); + + const initialValues = useBrandingTemplateFormInitialValues(defaultValues); + + const handleFormSubmit = (values: T, { setSubmitting }: FormikHelpers) => { + const handleSuccess = (message: string) => { + AppToaster.show({ intent: Intent.SUCCESS, message }); + setSubmitting(false); + onSuccess && onSuccess(); + }; + const handleError = (message: string) => { + AppToaster.show({ intent: Intent.DANGER, message }); + setSubmitting(false); + onError && onError(); + }; + if (templateId) { + const reqValues = transformToEditRequest(values); + setSubmitting(true); + + // Edit existing template + editPdfTemplate({ templateId, values: reqValues }) + .then(() => handleSuccess('PDF template updated successfully!')) + .catch(() => + handleError('An error occurred while updating the PDF template.'), + ); + } else { + const reqValues = transformToNewRequest(values, resource); + setSubmitting(true); + + // Create new template + createPdfTemplate(reqValues) + .then(() => handleSuccess('PDF template created successfully!')) + .catch(() => + handleError('An error occurred while creating the PDF template.'), + ); + } + }; + + return ( + + initialValues={initialValues} + validationSchema={validationSchema} + onSubmit={handleFormSubmit} + {...props} + /> + ); +} + +export const validationSchema = Yup.object().shape({ + templateName: Yup.string().required('Template Name is required'), +}); diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesActionsBar.tsx b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesActionsBar.tsx similarity index 100% rename from packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesActionsBar.tsx rename to packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesActionsBar.tsx diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesBoot.tsx b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesBoot.tsx similarity index 82% rename from packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesBoot.tsx rename to packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesBoot.tsx index 3a1470253..ee0229f9b 100644 --- a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesBoot.tsx +++ b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesBoot.tsx @@ -1,5 +1,6 @@ import React, { createContext } from 'react'; import { useGetPdfTemplates } from '@/hooks/query/pdf-templates'; +import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; interface BrandingTemplatesBootValues { pdfTemplates: any; @@ -15,8 +16,11 @@ interface BrandingTemplatesBootProps { } function BrandingTemplatesBoot({ ...props }: BrandingTemplatesBootProps) { + const { payload } = useDrawerContext(); + const resource = payload?.resource || null; + const { data: pdfTemplates, isLoading: isPdfTemplatesLoading } = - useGetPdfTemplates(); + useGetPdfTemplates({ resource }); const provider = { pdfTemplates, diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesContent.tsx b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesContent.tsx similarity index 100% rename from packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesContent.tsx rename to packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesContent.tsx diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesDrawer.tsx b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesDrawer.tsx similarity index 95% rename from packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesDrawer.tsx rename to packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesDrawer.tsx index afc51c22c..b4b9679ca 100644 --- a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesDrawer.tsx +++ b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesDrawer.tsx @@ -16,12 +16,13 @@ function BrandingTemplatesDrawerRoot({ name, // #withDrawer isOpen, - payload: {}, + payload, }) { return ( diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesTable.tsx b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesTable.tsx similarity index 100% rename from packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesTable.tsx rename to packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesTable.tsx diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/_components.tsx b/packages/webapp/src/containers/BrandingTemplates/_components.tsx similarity index 100% rename from packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/_components.tsx rename to packages/webapp/src/containers/BrandingTemplates/_components.tsx diff --git a/packages/webapp/src/containers/BrandingTemplates/_utils.ts b/packages/webapp/src/containers/BrandingTemplates/_utils.ts new file mode 100644 index 000000000..416fe7f88 --- /dev/null +++ b/packages/webapp/src/containers/BrandingTemplates/_utils.ts @@ -0,0 +1,52 @@ +import { omit } from 'lodash'; +import { + CreatePdfTemplateValues, + EditPdfTemplateValues, +} from '@/hooks/query/pdf-templates'; +import { useBrandingTemplateBoot } from './BrandingTemplateBoot'; +import { transformToForm } from '@/utils'; +import { BrandingTemplateValues } from './types'; +import { useFormikContext } from 'formik'; + +export const transformToEditRequest = ( + values: T, +): EditPdfTemplateValues => { + return { + templateName: values.templateName, + attributes: omit(values, ['templateName']), + }; +}; + +export const transformToNewRequest = ( + values: T, + resource: string +): CreatePdfTemplateValues => { + return { + resource, + templateName: values.templateName, + attributes: omit(values, ['templateName']), + }; +}; + +export const useIsTemplateNamedFilled = () => { + const { values } = useFormikContext(); + + return values.templateName && values.templateName?.length >= 4; +}; + +export const useBrandingTemplateFormInitialValues = < + T extends BrandingTemplateValues, +>( + initialValues = {}, +) => { + const { pdfTemplate } = useBrandingTemplateBoot(); + + const defaultPdfTemplate = { + templateName: pdfTemplate?.templateName, + ...pdfTemplate?.attributes, + }; + return { + ...initialValues, + ...(transformToForm(defaultPdfTemplate, initialValues) as T), + }; +}; diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/BrandingTemplatesAlerts.ts b/packages/webapp/src/containers/BrandingTemplates/alerts/BrandingTemplatesAlerts.ts similarity index 100% rename from packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/BrandingTemplatesAlerts.ts rename to packages/webapp/src/containers/BrandingTemplates/alerts/BrandingTemplatesAlerts.ts diff --git a/packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/DeleteBrandingTemplateAlert.tsx b/packages/webapp/src/containers/BrandingTemplates/alerts/DeleteBrandingTemplateAlert.tsx similarity index 100% rename from packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/DeleteBrandingTemplateAlert.tsx rename to packages/webapp/src/containers/BrandingTemplates/alerts/DeleteBrandingTemplateAlert.tsx diff --git a/packages/webapp/src/containers/BrandingTemplates/types.ts b/packages/webapp/src/containers/BrandingTemplates/types.ts new file mode 100644 index 000000000..a74109c46 --- /dev/null +++ b/packages/webapp/src/containers/BrandingTemplates/types.ts @@ -0,0 +1,5 @@ + + +export interface BrandingTemplateValues { + templateName: string; +} \ No newline at end of file diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx index 4ff664f89..31b02d8d3 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx @@ -1,39 +1,35 @@ -import { Box } from '@/components'; -import { Classes } from '@blueprintjs/core'; +import { useFormikContext } from 'formik'; import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize'; import { CreditNoteCustomizeGeneralField } from './CreditNoteCustomizeGeneralFields'; import { CreditNoteCustomizeContentFields } from './CreditNoteCutomizeContentFields'; import { CreditNotePaperTemplate } from './CreditNotePaperTemplate'; import { CreditNoteCustomizeValues } from './types'; import { initialValues } from './constants'; -import { useFormikContext } from 'formik'; -export default function CreditNoteCustomizeContent() { +export function CreditNoteCustomizeContent() { const handleFormSubmit = (values: CreditNoteCustomizeValues) => {}; return ( - - - initialValues={initialValues} - onSubmit={handleFormSubmit} - > - - - + + initialValues={initialValues} + onSubmit={handleFormSubmit} + > + + + - - - + + + - - - + + + - - asdfasdfdsaf #3 - - - + + asdfasdfdsaf #3 + + ); } diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawerBody.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawerBody.tsx new file mode 100644 index 000000000..3472833cb --- /dev/null +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawerBody.tsx @@ -0,0 +1,18 @@ +import { Box } from '@/components'; +import { CreditNoteCustomizeContent } from './CreditNoteCustomizeContent'; +import { Classes } from '@blueprintjs/core'; +import { BrandingTemplateBoot } from '@/containers/BrandingTemplates/BrandingTemplateBoot'; +import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; + +export default function CreditNoteCustomizeDrawerBody() { + const { payload } = useDrawerContext(); + const templateId = payload.templateId; + + return ( + + + + + + ); +} diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesActionsBar.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesActionsBar.tsx index 963ae005d..3def3a944 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesActionsBar.tsx +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesActionsBar.tsx @@ -101,7 +101,7 @@ function CreditNotesActionsBar({ }; // Handle the customize button click. const handleCustomizeBtnClick = () => { - openDrawer(DRAWERS.CREDIT_NOTE_CUSTOMIZE); + openDrawer(DRAWERS.BRANDING_TEMPLATES, { resource: 'CreditNote' }); } return ( @@ -174,7 +174,7 @@ function CreditNotesActionsBar({ } diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx index 15250b8e7..82f05c618 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx @@ -1,40 +1,49 @@ -import React from 'react'; -import { Box } from '@/components'; import { Classes } from '@blueprintjs/core'; +import { useFormikContext } from 'formik'; +import { Box } from '@/components'; import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize'; import { EstimateCustomizeGeneralField } from './EstimateCustomizeFieldsGeneral'; import { EstimateCustomizeContentFields } from './EstimateCustomizeFieldsContent'; import { EstimatePaperTemplate } from './EstimatePaperTemplate'; import { EstimateCustomizeValues } from './types'; import { initialValues } from './constants'; -import { useFormikContext } from 'formik'; +import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; +import { useDrawerActions } from '@/hooks/state'; +import { BrandingTemplateForm } from '@/containers/BrandingTemplates/BrandingTemplateForm'; -export default function EstimateCustomizeContent() { - const handleFormSubmit = (values: EstimateCustomizeValues) => {}; +export function EstimateCustomizeContent() { + const { payload, name } = useDrawerContext(); + const { closeDrawer } = useDrawerActions(); + + const templateId = payload?.templateId || null; + + const handleSuccess = () => { + closeDrawer(name); + }; return ( - - - initialValues={initialValues} - onSubmit={handleFormSubmit} - > - - - + + templateId={templateId} + defaultValues={initialValues} + onSuccess={handleSuccess} + resource={'SaleEstimate'} + > + + + - - - + + + - - - + + + - - asdfasdfdsaf #3 - - - + + asdfasdfdsaf #3 + + ); } diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawer.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawer.tsx index 6a6042ba8..54b0ab3f7 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawer.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawer.tsx @@ -4,8 +4,8 @@ import * as R from 'ramda'; import { Drawer, DrawerSuspense } from '@/components'; import withDrawers from '@/containers/Drawer/withDrawers'; -const EstimateCustomizeContent = React.lazy( - () => import('./EstimateCustomizeContent'), +const EstimateCustomizeDrawerBody = React.lazy( + () => import('./EstimateCustomizeDrawerBody'), ); /** @@ -22,7 +22,7 @@ function EstimateCustomizeDrawerRoot({ return ( - + ); diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawerBody.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawerBody.tsx new file mode 100644 index 000000000..49ada83a3 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawerBody.tsx @@ -0,0 +1,18 @@ +import { Box } from '@/components'; +import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; +import { BrandingTemplateBoot } from '@/containers/BrandingTemplates/BrandingTemplateBoot'; +import { Classes } from '@blueprintjs/core'; +import { EstimateCustomizeContent } from './EstimateCustomizeContent'; + +export default function EstimateCustomizeDrawerBody() { + const { payload } = useDrawerContext(); + const templateId = payload?.templateId || null; + + return ( + + + + + + ); +} diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx index b1fcc0d81..837f1be48 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx @@ -1,6 +1,13 @@ // @ts-nocheck import { Classes, Text } from '@blueprintjs/core'; -import { FFormGroup, FSwitch, Group, Stack } from '@/components'; +import { + FFormGroup, + FInputGroup, + FSwitch, + FieldRequiredHint, + Group, + Stack, +} from '@/components'; import { FColorInput } from '@/components/Forms/FColorInput'; // import styles from './InvoiceCustomizeFields.module.scss'; @@ -10,11 +17,21 @@ export function EstimateCustomizeGeneralField() {

General Branding

- Set your invoice details to be automatically applied every time
you + Set your invoice details to be automatically applied every timeyou create a new invoice.

+ } + fastField + style={{ marginBottom: 10 }} + > + + + { - openDrawer(DRAWERS.ESTIMATE_CUSTOMIZE); + openDrawer(DRAWERS.BRANDING_TEMPLATES, { resource: 'SaleEstimate' }); }; return ( @@ -192,7 +192,7 @@ function EstimateActionsBar({ } diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx index 9ce80d618..c45e98c19 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx @@ -1,8 +1,8 @@ // @ts-nocheck import { Classes } from '@blueprintjs/core'; import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; -import { BrandingTemplateBoot } from '../BrandingTemplates/BrandingTemplateBoot'; import { InvoiceCustomizeContent } from './InvoiceCustomizeContent'; +import { BrandingTemplateBoot } from '@/containers/BrandingTemplates/BrandingTemplateBoot'; import { Box } from '@/components'; export default function InvoiceCustomize() { diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx index ec4b4e056..e6bc0dbd6 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx @@ -1,8 +1,6 @@ import React from 'react'; import * as R from 'ramda'; -import { AppToaster } from '@/components'; -import { Intent } from '@blueprintjs/core'; -import { FormikHelpers, useFormikContext } from 'formik'; +import { useFormikContext } from 'formik'; import { InvoicePaperTemplate, InvoicePaperTemplateProps, @@ -11,70 +9,29 @@ import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize'; import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields'; import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields'; import { InvoiceCustomizeValues } from './types'; -import { - useCreatePdfTemplate, - useEditPdfTemplate, -} from '@/hooks/query/pdf-templates'; -import { - transformToEditRequest, - transformToNewRequest, - useInvoiceCustomizeInitialValues, -} from './utils'; import { InvoiceCustomizeSchema } from './InvoiceCustomizeForm.schema'; import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; import { useDrawerActions } from '@/hooks/state'; +import { BrandingTemplateForm } from '@/containers/BrandingTemplates/BrandingTemplateForm'; +import { initialValues } from './constants'; export function InvoiceCustomizeContent() { - const { mutateAsync: createPdfTemplate } = useCreatePdfTemplate(); - const { mutateAsync: editPdfTemplate } = useEditPdfTemplate(); - const { payload, name } = useDrawerContext(); const { closeDrawer } = useDrawerActions(); const templateId = payload?.templateId || null; - const handleFormSubmit = ( - values: InvoiceCustomizeValues, - { setSubmitting }: FormikHelpers, - ) => { - const handleSuccess = (message: string) => { - AppToaster.show({ intent: Intent.SUCCESS, message }); - setSubmitting(false); - closeDrawer(name); - }; - const handleError = (message: string) => { - AppToaster.show({ intent: Intent.DANGER, message }); - setSubmitting(false); - }; - if (templateId) { - const reqValues = transformToEditRequest(values); - setSubmitting(true); - - // Edit existing template - editPdfTemplate({ templateId, values: reqValues }) - .then(() => handleSuccess('PDF template updated successfully!')) - .catch(() => - handleError('An error occurred while updating the PDF template.'), - ); - } else { - const reqValues = transformToNewRequest(values); - setSubmitting(true); - - // Create new template - createPdfTemplate(reqValues) - .then(() => handleSuccess('PDF template created successfully!')) - .catch(() => - handleError('An error occurred while creating the PDF template.'), - ); - } + const handleSuccess = () => { + closeDrawer(name); }; - const initialValues = useInvoiceCustomizeInitialValues(); return ( - - initialValues={initialValues} + + templateId={templateId} + defaultValues={initialValues} validationSchema={InvoiceCustomizeSchema} - onSubmit={handleFormSubmit} + onSuccess={handleSuccess} + resource={'SaleInvoice'} > @@ -91,7 +48,7 @@ export function InvoiceCustomizeContent() { asdfasdfdsaf #3 - + ); } diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/types.ts b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/types.ts index 3e972b020..f7ed6ea5e 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/types.ts +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/types.ts @@ -1,6 +1,6 @@ -export interface InvoiceCustomizeValues { - templateName: string; +import { BrandingTemplateValues } from "@/containers/BrandingTemplates/types"; +export interface InvoiceCustomizeValues extends BrandingTemplateValues { // Colors primaryColor?: string; secondaryColor?: string; diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/utils.ts b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/utils.ts index f3fcd8385..130986261 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/utils.ts +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/utils.ts @@ -5,9 +5,9 @@ import { CreatePdfTemplateValues, EditPdfTemplateValues, } from '@/hooks/query/pdf-templates'; -import { useBrandingTemplateBoot } from '../BrandingTemplates/BrandingTemplateBoot'; import { transformToForm } from '@/utils'; import { initialValues } from './constants'; +import { useBrandingTemplateBoot } from '@/containers/BrandingTemplates/BrandingTemplateBoot'; export const transformToEditRequest = ( values: InvoiceCustomizeValues, diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx index bf8fe5bba..205c64694 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx @@ -109,7 +109,7 @@ function InvoiceActionsBar({ // Handles the invoice customize button click. const handleCustomizeBtnClick = () => { - openDrawer(DRAWERS.BRANDING_TEMPLATES); + openDrawer(DRAWERS.BRANDING_TEMPLATES, { resource: 'SaleInvoice' }); }; return ( @@ -190,7 +190,7 @@ function InvoiceActionsBar({ } diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomize.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomize.tsx new file mode 100644 index 000000000..a2db6c48e --- /dev/null +++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomize.tsx @@ -0,0 +1,18 @@ +import { Box } from '@/components'; +import { Classes } from '@blueprintjs/core'; +import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; +import { PaymentReceivedCustomizeContent } from './PaymentReceivedCustomizeContent'; +import { BrandingTemplateBoot } from '@/containers/BrandingTemplates/BrandingTemplateBoot'; + +export default function PaymentReceivedCustomize() { + const { payload } = useDrawerContext(); + const templateId = payload.templateId; + + return ( + + + + + + ); +} diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx index 67a28fb31..b84bbb168 100644 --- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx +++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx @@ -1,40 +1,47 @@ -import React from 'react'; import { useFormikContext } from 'formik'; -import { Classes } from '@blueprintjs/core'; -import { Box } from '@/components'; import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize'; import { PaymentReceivedCustomizeGeneralField } from './PaymentReceivedCustomizeFieldsGeneral'; import { PaymentReceivedCustomizeContentFields } from './PaymentReceivedCustomizeFieldsContent'; import { PaymentReceivedCustomizeValues } from './types'; import { PaymentReceivedPaperTemplate } from './PaymentReceivedPaperTemplate'; import { initialValues } from './constants'; +import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; +import { useDrawerActions } from '@/hooks/state'; +import { BrandingTemplateForm } from '@/containers/BrandingTemplates/BrandingTemplateForm'; -export default function PaymentReceivedCustomizeContent() { - const handleFormSubmit = (values: PaymentReceivedCustomizeValues) => {}; +export function PaymentReceivedCustomizeContent() { + const { payload, name } = useDrawerContext(); + const { closeDrawer } = useDrawerActions(); + + const templateId = payload?.templateId || null; + + const handleSuccess = () => { + closeDrawer(name); + }; return ( - - - initialValues={initialValues} - onSubmit={handleFormSubmit} - > - - - + + templateId={templateId} + defaultValues={initialValues} + onSuccess={handleSuccess} + resource={'PaymentReceive'} + > + + + - - - + + + - - - + + + - - asdfasdfdsaf #3 - - - + + asdfasdfdsaf #3 + + ); } diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeDrawer.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeDrawer.tsx index 78ba99dba..ccf830905 100644 --- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeDrawer.tsx +++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeDrawer.tsx @@ -4,8 +4,8 @@ import * as R from 'ramda'; import { Drawer, DrawerSuspense } from '@/components'; import withDrawers from '@/containers/Drawer/withDrawers'; -const PaymentReceivedCustomizeContent = React.lazy( - () => import('./PaymentReceivedCustomizeContent'), +const PaymentReceivedCustomize = React.lazy( + () => import('./PaymentReceivedCustomize'), ); /** @@ -16,12 +16,12 @@ function PaymentReceivedCustomizeDrawerRoot({ name, // #withDrawer isOpen, - payload: {}, + payload }) { return ( - + - + ); diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/constants.ts b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/constants.ts index da939b914..4b735653a 100644 --- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/constants.ts +++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/constants.ts @@ -1,4 +1,6 @@ export const initialValues = { + templateName: '', + // Colors primaryColor: '#2c3dd8', secondaryColor: '#2c3dd8', diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/types.ts b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/types.ts index 9e5f89170..1da7073b8 100644 --- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/types.ts +++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/types.ts @@ -1,4 +1,6 @@ -export interface PaymentReceivedCustomizeValues { +import { BrandingTemplateValues } from '@/containers/BrandingTemplates/types'; + +export interface PaymentReceivedCustomizeValues extends BrandingTemplateValues { // Colors primaryColor?: string; secondaryColor?: string; diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentsLanding/PaymentsReceivedActionsBar.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentsLanding/PaymentsReceivedActionsBar.tsx index eca4dc208..412dc094e 100644 --- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentsLanding/PaymentsReceivedActionsBar.tsx +++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentsLanding/PaymentsReceivedActionsBar.tsx @@ -113,7 +113,7 @@ function PaymentsReceivedActionsBar({ }; // Handle the customize button click. const handleCustomizeBtnClick = () => { - openDrawer(DRAWERS.PAYMENT_RECEIVED_CUSTOMIZE); + openDrawer(DRAWERS.BRANDING_TEMPLATES, { resource: 'PaymentReceive' }); }; return ( @@ -195,7 +195,7 @@ function PaymentsReceivedActionsBar({ } diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx index 3b4246101..cc68a1759 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx @@ -1,5 +1,3 @@ -import { Box } from '@/components'; -import { Classes } from '@blueprintjs/core'; import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize'; import { ReceiptCustomizeGeneralField } from './ReceiptCustomizeFieldsGeneral'; import { ReceiptCustomizeFieldsContent } from './ReceiptCustomizeFieldsContent'; @@ -7,33 +5,43 @@ import { ReceiptPaperTemplate } from './ReceiptPaperTemplate'; import { ReceiptCustomizeValues } from './types'; import { initialValues } from './constants'; import { useFormikContext } from 'formik'; +import { BrandingTemplateForm } from '@/containers/BrandingTemplates/BrandingTemplateForm'; +import { useDrawerActions } from '@/hooks/state'; +import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; -export default function ReceiptCustomizeContent() { - const handleFormSubmit = (values: ReceiptCustomizeValues) => {}; +export function ReceiptCustomizeContent() { + const { payload, name } = useDrawerContext(); + const { closeDrawer } = useDrawerActions(); + + const templateId = payload?.templateId || null; + + const handleFormSuccess = () => { + closeDrawer(name); + }; return ( - - - initialValues={initialValues} - onSubmit={handleFormSubmit} - > - - - + + templateId={templateId} + initialValues={initialValues} + onSuccess={handleFormSuccess} + resource={'SaleReceipt'} + > + + + - - - + + + - - - + + + - - asdfasdfdsaf #3 - - - + + asdfasdfdsaf #3 + + ); } diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeDrawer.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeDrawer.tsx index 48dbdaebc..7c54cf61d 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeDrawer.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeDrawer.tsx @@ -4,8 +4,8 @@ import * as R from 'ramda'; import { Drawer, DrawerSuspense } from '@/components'; import withDrawers from '@/containers/Drawer/withDrawers'; -const ReceiptCustomizeContent = React.lazy( - () => import('./ReceiptCustomizeContent'), +const ReceiptCustomizeDrawerBody = React.lazy( + () => import('./ReceiptCustomizeDrawerBody'), ); /** @@ -16,12 +16,12 @@ function ReceiptCustomizeDrawerRoot({ name, // #withDrawer isOpen, - payload: {}, + payload, }) { return ( - + - + ); diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeDrawerBody.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeDrawerBody.tsx new file mode 100644 index 000000000..8b48a57b7 --- /dev/null +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeDrawerBody.tsx @@ -0,0 +1,18 @@ +import { Box } from '@/components'; +import { Classes } from '@blueprintjs/core'; +import { ReceiptCustomizeContent } from './ReceiptCustomizeContent'; +import { BrandingTemplateBoot } from '@/containers/BrandingTemplates/BrandingTemplateBoot'; +import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; + +export default function ReceiptCustomizeDrawerBody() { + const { payload } = useDrawerContext(); + const templateId = payload.templateId; + + return ( + + + + + + ); +} diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/constants.ts b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/constants.ts index b80b50162..241020ce2 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/constants.ts +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/constants.ts @@ -1,4 +1,6 @@ export const initialValues = { + templateName: '', + // Colors primaryColor: '#2c3dd8', secondaryColor: '#2c3dd8', diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/types.ts b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/types.ts index f411639ea..8b9ccc54c 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/types.ts +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/types.ts @@ -1,4 +1,6 @@ -export interface ReceiptCustomizeValues { +import { BrandingTemplateValues } from "@/containers/BrandingTemplates/types"; + +export interface ReceiptCustomizeValues extends BrandingTemplateValues { // Colors primaryColor?: string; secondaryColor?: string; diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.tsx index a8f95d77e..d45554459 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.tsx @@ -115,7 +115,7 @@ function ReceiptActionsBar({ }; // Handle customize button click. const handleCustomizeBtnClick = () => { - openDrawer(DRAWERS.RECEIPT_CUSTOMIZE); + openDrawer(DRAWERS.BRANDING_TEMPLATES, { resource: 'SaleReceipt' }); }; return ( @@ -198,7 +198,7 @@ function ReceiptActionsBar({ } diff --git a/packages/webapp/src/hooks/query/pdf-templates.ts b/packages/webapp/src/hooks/query/pdf-templates.ts index 0e03aa81b..eee064585 100644 --- a/packages/webapp/src/hooks/query/pdf-templates.ts +++ b/packages/webapp/src/hooks/query/pdf-templates.ts @@ -153,12 +153,16 @@ export const useGetPdfTemplate = ( // Hook for getting multiple PDF templates export const useGetPdfTemplates = ( + query?: { resource: string }, options?: UseQueryOptions, ): UseQueryResult => { const apiRequest = useApiRequest(); return useQuery( - PdfTemplatesQueryKey, - () => apiRequest.get('/pdf-templates').then((res) => res.data), + [PdfTemplatesQueryKey, query], + () => + apiRequest + .get('/pdf-templates', { params: query }) + .then((res) => res.data), options, ); }; From 411ac55986cfb633c1c4decd49e7ffe9259fd597 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 12 Sep 2024 17:49:00 +0200 Subject: [PATCH 20/32] feat: templates customize --- .../BrandingTemplatesActionsBar.tsx | 20 +++- .../BrandingTemplatesContent.tsx | 2 +- .../BrandingTemplatesTable.tsx | 8 +- .../containers/BrandingTemplates/_utils.ts | 32 +++++-- .../src/containers/BrandingTemplates/utils.ts | 8 ++ .../CreditNoteCustomizeContent.tsx | 22 ++++- .../CreditNoteCustomizeDrawer.tsx | 10 +- .../CreditNoteCustomizeDrawerBody.tsx | 2 +- .../CreditNoteCustomizeGeneralFields.tsx | 90 +++++++++++------- .../CreditNoteCustomize/constants.ts | 2 + .../CreditNotes/CreditNoteCustomize/types.ts | 4 +- .../EstimateCustomizeDrawer.tsx | 4 +- .../EstimateCustomizeFieldsGeneral.tsx | 73 ++++++++------- .../InvoiceCustomizeGeneralFields.tsx | 2 +- .../Sales/Invoices/InvoiceCustomize/utils.ts | 6 -- .../PaymentReceivedCustomizeFieldsGeneral.tsx | 91 ++++++++++++------- .../ReceiptCustomizeFieldsGeneral.tsx | 90 +++++++++++------- 17 files changed, 293 insertions(+), 173 deletions(-) create mode 100644 packages/webapp/src/containers/BrandingTemplates/utils.ts diff --git a/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesActionsBar.tsx b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesActionsBar.tsx index 65aa94e3e..a7e5ccfbf 100644 --- a/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesActionsBar.tsx +++ b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesActionsBar.tsx @@ -1,20 +1,30 @@ // @ts-nocheck -import React from 'react'; +import React, { useMemo } from 'react'; import { Button, NavbarGroup, Intent } from '@blueprintjs/core'; import { DashboardActionsBar, Icon } from '@/components'; -import { DRAWERS } from '@/constants/drawers'; - import withDrawerActions from '@/containers/Drawer/withDrawerActions'; +import { + getButtonLabelFromResource, + getCustomizeDrawerNameFromResource, +} from './_utils'; import { compose } from '@/utils'; +import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; /** * Account drawer action bar. */ function BrandingTemplateActionsBarRoot({ openDrawer }) { + const { + payload: { resource }, + } = useDrawerContext(); + // Handle new child button click. const handleCreateBtnClick = () => { - openDrawer(DRAWERS.INVOICE_CUSTOMIZE); + const drawerResource = getCustomizeDrawerNameFromResource(resource); + openDrawer(drawerResource); }; + const label = useMemo(() => getButtonLabelFromResource(resource), [resource]); + return ( @@ -24,7 +34,7 @@ function BrandingTemplateActionsBarRoot({ openDrawer }) { onClick={handleCreateBtnClick} minimal > - Create Invoice Branding + {label} diff --git a/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesContent.tsx b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesContent.tsx index cd7fd3e37..8b6f139cf 100644 --- a/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesContent.tsx +++ b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesContent.tsx @@ -5,8 +5,8 @@ import { BrandingTemplatesBoot } from './BrandingTemplatesBoot'; import { Box, Card, DrawerHeaderContent, Group } from '@/components'; import { DRAWERS } from '@/constants/drawers'; import { BrandingTemplatesTable } from './BrandingTemplatesTable'; -import withDrawerActions from '@/containers/Drawer/withDrawerActions'; import { BrandingTemplateActionsBar } from './BrandingTemplatesActionsBar'; +import withDrawerActions from '@/containers/Drawer/withDrawerActions'; export default function BrandingTemplateContent() { return ( diff --git a/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesTable.tsx b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesTable.tsx index 6ae205212..01f358c65 100644 --- a/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesTable.tsx +++ b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesTable.tsx @@ -5,10 +5,11 @@ import clsx from 'classnames'; import { DataTable, Group, TableSkeletonRows } from '@/components'; import { useBrandingTemplatesBoot } from './BrandingTemplatesBoot'; import { ActionsMenu } from './_components'; +import { DRAWERS } from '@/constants/drawers'; import withAlertActions from '@/containers/Alert/withAlertActions'; import withDrawerActions from '@/containers/Drawer/withDrawerActions'; -import { DRAWERS } from '@/constants/drawers'; import styles from './BrandTemplates.module.scss'; +import { getCustomizeDrawerNameFromResource } from './_utils'; interface BrandingTemplatesTableProps {} @@ -35,7 +36,10 @@ function BrandingTemplateTableRoot({ const templateId = cell.row.original.id; const resource = cell.row.original.resource; - openDrawer(DRAWERS.INVOICE_CUSTOMIZE, { templateId, resource }); + // Retrieves the customize drawer name from the given resource name. + const drawerName = getCustomizeDrawerNameFromResource(resource); + + openDrawer(drawerName, { templateId, resource }); }; return ( diff --git a/packages/webapp/src/containers/BrandingTemplates/_utils.ts b/packages/webapp/src/containers/BrandingTemplates/_utils.ts index 416fe7f88..50e5122a8 100644 --- a/packages/webapp/src/containers/BrandingTemplates/_utils.ts +++ b/packages/webapp/src/containers/BrandingTemplates/_utils.ts @@ -1,4 +1,5 @@ import { omit } from 'lodash'; +import * as R from 'ramda'; import { CreatePdfTemplateValues, EditPdfTemplateValues, @@ -7,6 +8,7 @@ import { useBrandingTemplateBoot } from './BrandingTemplateBoot'; import { transformToForm } from '@/utils'; import { BrandingTemplateValues } from './types'; import { useFormikContext } from 'formik'; +import { DRAWERS } from '@/constants/drawers'; export const transformToEditRequest = ( values: T, @@ -19,7 +21,7 @@ export const transformToEditRequest = ( export const transformToNewRequest = ( values: T, - resource: string + resource: string, ): CreatePdfTemplateValues => { return { resource, @@ -28,12 +30,6 @@ export const transformToNewRequest = ( }; }; -export const useIsTemplateNamedFilled = () => { - const { values } = useFormikContext(); - - return values.templateName && values.templateName?.length >= 4; -}; - export const useBrandingTemplateFormInitialValues = < T extends BrandingTemplateValues, >( @@ -50,3 +46,25 @@ export const useBrandingTemplateFormInitialValues = < ...(transformToForm(defaultPdfTemplate, initialValues) as T), }; }; + +export const getCustomizeDrawerNameFromResource = (resource: string) => { + const pairs = { + SaleInvoice: DRAWERS.INVOICE_CUSTOMIZE, + SaleEstimate: DRAWERS.ESTIMATE_CUSTOMIZE, + SaleReceipt: DRAWERS.RECEIPT_CUSTOMIZE, + CreditNote: DRAWERS.CREDIT_NOTE_CUSTOMIZE, + PaymentReceive: DRAWERS.PAYMENT_RECEIVED_CUSTOMIZE, + }; + return R.prop(resource, pairs) || DRAWERS.INVOICE_CUSTOMIZE; +}; + +export const getButtonLabelFromResource = (resource: string) => { + const pairs = { + SaleInvoice: 'Create Invoice Branding', + SaleEstimate: 'Create Estimate Branding', + SaleReceipt: 'Create Receipt Branding', + CreditNote: 'Create Credit Note Branding', + PaymentReceive: 'Create Payment Branding', + }; + return R.prop(resource, pairs) || 'Create Branding Template'; +} \ No newline at end of file diff --git a/packages/webapp/src/containers/BrandingTemplates/utils.ts b/packages/webapp/src/containers/BrandingTemplates/utils.ts new file mode 100644 index 000000000..788d36898 --- /dev/null +++ b/packages/webapp/src/containers/BrandingTemplates/utils.ts @@ -0,0 +1,8 @@ +import { useFormikContext } from 'formik'; +import { BrandingTemplateValues } from './types'; + +export const useIsTemplateNamedFilled = () => { + const { values } = useFormikContext(); + + return values.templateName && values.templateName?.length >= 4; +}; diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx index 31b02d8d3..8a9ca888d 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx @@ -5,14 +5,26 @@ import { CreditNoteCustomizeContentFields } from './CreditNoteCutomizeContentFie import { CreditNotePaperTemplate } from './CreditNotePaperTemplate'; import { CreditNoteCustomizeValues } from './types'; import { initialValues } from './constants'; +import { BrandingTemplateForm } from '@/containers/BrandingTemplates/BrandingTemplateForm'; +import { useDrawerActions } from '@/hooks/state'; +import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; export function CreditNoteCustomizeContent() { - const handleFormSubmit = (values: CreditNoteCustomizeValues) => {}; + const { payload, name } = useDrawerContext(); + const { closeDrawer } = useDrawerActions(); + + const templateId = payload?.templateId || null; + + const handleSuccess = () => { + closeDrawer(name); + }; return ( - - initialValues={initialValues} - onSubmit={handleFormSubmit} + + resource={'CreditNote'} + templateId={templateId} + defaultValues={initialValues} + onSuccess={handleSuccess} > @@ -29,7 +41,7 @@ export function CreditNoteCustomizeContent() { asdfasdfdsaf #3 - + ); } diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawer.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawer.tsx index dc6ad6a19..42159714e 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawer.tsx +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawer.tsx @@ -4,8 +4,8 @@ import * as R from 'ramda'; import { Drawer, DrawerSuspense } from '@/components'; import withDrawers from '@/containers/Drawer/withDrawers'; -const CreditNoteCustomizeContent = React.lazy( - () => import('./CreditNoteCustomizeContent'), +const CreditNoteCustomizeDrawerBody = React.lazy( + () => import('./CreditNoteCustomizeDrawerBody'), ); /** @@ -16,12 +16,12 @@ function CreditNoteCustomizeDrawerRoot({ name, // #withDrawer isOpen, - payload: {}, + payload, }) { return ( - + - + ); diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawerBody.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawerBody.tsx index 3472833cb..c6fb97471 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawerBody.tsx +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawerBody.tsx @@ -6,7 +6,7 @@ import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; export default function CreditNoteCustomizeDrawerBody() { const { payload } = useDrawerContext(); - const templateId = payload.templateId; + const templateId = payload?.templateId || null; return ( diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeGeneralFields.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeGeneralFields.tsx index 9c8b84c7c..9519b4bc5 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeGeneralFields.tsx +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeGeneralFields.tsx @@ -1,9 +1,19 @@ // @ts-nocheck import { Classes } from '@blueprintjs/core'; -import { FFormGroup, FSwitch, Stack } from '@/components'; +import { + FFormGroup, + FieldRequiredHint, + FInputGroup, + FSwitch, + Stack, +} from '@/components'; import { FColorInput } from '@/components/Forms/FColorInput'; +import { Overlay } from '../../Invoices/InvoiceCustomize/Overlay'; +import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils'; export function CreditNoteCustomizeGeneralField() { + const isTemplateNameFilled = useIsTemplateNamedFilled(); + return ( @@ -14,45 +24,57 @@ export function CreditNoteCustomizeGeneralField() {

- - - } + fastField + style={{ marginBottom: 10 }} + > + + + + + + - + > + +
- - - + > + + - - - -
+ + + + + ); } diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/constants.ts b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/constants.ts index 20ecaf45f..987303fa6 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/constants.ts +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/constants.ts @@ -1,4 +1,6 @@ export const initialValues = { + templateName: '', + // Colors primaryColor: '#2c3dd8', secondaryColor: '#2c3dd8', diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/types.ts b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/types.ts index 1d8cfb5f7..5527a0e3c 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/types.ts +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/types.ts @@ -1,4 +1,6 @@ -export interface CreditNoteCustomizeValues { +import { BrandingTemplateValues } from '@/containers/BrandingTemplates/types'; + +export interface CreditNoteCustomizeValues extends BrandingTemplateValues { // Colors primaryColor?: string; secondaryColor?: string; diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawer.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawer.tsx index 54b0ab3f7..35bd969ea 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawer.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawer.tsx @@ -17,10 +17,10 @@ function EstimateCustomizeDrawerRoot({ // #withDrawer isOpen, - payload: {}, + payload, }) { return ( - + diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx index 837f1be48..2a32b4412 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx @@ -9,9 +9,12 @@ import { Stack, } from '@/components'; import { FColorInput } from '@/components/Forms/FColorInput'; -// import styles from './InvoiceCustomizeFields.module.scss'; +import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils'; +import { Overlay } from '../../Invoices/InvoiceCustomize/Overlay'; export function EstimateCustomizeGeneralField() { + const isTemplateNameFilled = useIsTemplateNamedFilled(); + return ( @@ -32,45 +35,47 @@ export function EstimateCustomizeGeneralField() { - - - + + - + > + + - - - + > + + - - - - + + + + + ); } diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx index c9f6d1827..5ec48a7a1 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx @@ -11,7 +11,7 @@ import { import { FColorInput } from '@/components/Forms/FColorInput'; import { CreditCardIcon } from '@/icons/CreditCardIcon'; import { Overlay } from './Overlay'; -import { useIsTemplateNamedFilled } from './utils'; +import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils'; export function InvoiceCustomizeGeneralField() { const isTemplateNameFilled = useIsTemplateNamedFilled(); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/utils.ts b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/utils.ts index 130986261..d9a1979da 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/utils.ts +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/utils.ts @@ -28,12 +28,6 @@ export const transformToNewRequest = ( }; }; -export const useIsTemplateNamedFilled = () => { - const { values } = useFormikContext(); - - return values.templateName && values.templateName?.length >= 4; -}; - export const useInvoiceCustomizeInitialValues = (): InvoiceCustomizeValues => { const { pdfTemplate } = useBrandingTemplateBoot(); diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsGeneral.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsGeneral.tsx index 9dbfd5feb..ed3edaf22 100644 --- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsGeneral.tsx +++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsGeneral.tsx @@ -1,10 +1,19 @@ // @ts-nocheck import { Classes } from '@blueprintjs/core'; -import { FFormGroup, FSwitch, Stack } from '@/components'; +import { + FFormGroup, + FieldRequiredHint, + FInputGroup, + FSwitch, + Stack, +} from '@/components'; import { FColorInput } from '@/components/Forms/FColorInput'; -// import styles from './InvoiceCustomizeFields.module.scss'; +import { Overlay } from '../../Invoices/InvoiceCustomize/Overlay'; +import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils'; export function PaymentReceivedCustomizeGeneralField() { + const isTemplateNameFilled = useIsTemplateNamedFilled(); + return ( @@ -15,45 +24,57 @@ export function PaymentReceivedCustomizeGeneralField() {

- - - } + style={{ marginBottom: 10 }} + fastField + > + + + + + + - + > + + - - - + > + + - - - - + + + + +
); } diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsGeneral.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsGeneral.tsx index 93b76c78b..81e7121a6 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsGeneral.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsGeneral.tsx @@ -1,9 +1,19 @@ // @ts-nocheck import { Classes } from '@blueprintjs/core'; -import { FFormGroup, FSwitch, Stack } from '@/components'; +import { + FFormGroup, + FieldRequiredHint, + FInputGroup, + FSwitch, + Stack, +} from '@/components'; import { FColorInput } from '@/components/Forms/FColorInput'; +import { useIsTemplateNamedFilled } from '@/containers/BrandingTemplates/utils'; +import { Overlay } from '../../Invoices/InvoiceCustomize/Overlay'; export function ReceiptCustomizeGeneralField() { + const isTemplateNameFilled = useIsTemplateNamedFilled(); + return ( @@ -14,45 +24,57 @@ export function ReceiptCustomizeGeneralField() {

- - - } + fastField + style={{ marginBottom: 10 }} + > + + + + + + - + > + + - - - + > + + - - - - + + + + +
); } From df0f73f338ed87e124a551a048b46a542f219b44 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sat, 14 Sep 2024 16:19:06 +0200 Subject: [PATCH 21/32] feat: mark specific template as default --- .../PdfTemplates/PdfTemplatesController.ts | 32 +++++++- .../PdfTemplate/AssignPdfTemplateDefault.ts | 47 ++++++++++++ .../PdfTemplate/PdfTemplateApplication.ts | 14 ++++ packages/server/src/subscribers/events.ts | 3 + .../BrandingTemplatesTable.tsx | 6 ++ .../BrandingTemplates/_components.tsx | 11 ++- .../alerts/BrandingTemplatesAlerts.ts | 8 ++ .../MarkDefaultBrandingTemplateAlert.tsx | 73 +++++++++++++++++++ .../webapp/src/hooks/query/pdf-templates.ts | 37 ++++++++++ 9 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 packages/server/src/services/PdfTemplate/AssignPdfTemplateDefault.ts create mode 100644 packages/webapp/src/containers/BrandingTemplates/alerts/MarkDefaultBrandingTemplateAlert.tsx diff --git a/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts b/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts index 63a854e22..baad0cace 100644 --- a/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts +++ b/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts @@ -21,7 +21,6 @@ export class PdfTemplatesController extends BaseController { this.validationResult, this.deletePdfTemplate.bind(this) ); - router.put( '/:template_id', [ @@ -54,6 +53,12 @@ export class PdfTemplatesController extends BaseController { this.validationResult, this.createPdfInvoiceTemplate.bind(this) ); + router.post( + '/:template_id/assign_default', + [param('template_id').exists().isInt().toInt()], + this.validationResult, + this.assginPdfTemplateAsDefault.bind(this) + ); return router; } @@ -139,4 +144,29 @@ export class PdfTemplatesController extends BaseController { next(error); } } + + async assginPdfTemplateAsDefault( + req: Request, + res: Response, + next: NextFunction + ) { + const { tenantId } = req; + const { template_id: templateId } = req.params; + + try { + await this.pdfTemplateApplication.assignPdfTemplateAsDefault( + tenantId, + Number(templateId) + ); + return res + .status(204) + .send({ + id: templateId, + message: + 'The given pdf template has been assigned as default template', + }); + } catch (error) { + next(error); + } + } } diff --git a/packages/server/src/services/PdfTemplate/AssignPdfTemplateDefault.ts b/packages/server/src/services/PdfTemplate/AssignPdfTemplateDefault.ts new file mode 100644 index 000000000..f4317aec1 --- /dev/null +++ b/packages/server/src/services/PdfTemplate/AssignPdfTemplateDefault.ts @@ -0,0 +1,47 @@ +import { Service, Inject } from 'typedi'; +import HasTenancyService from '../Tenancy/TenancyService'; +import UnitOfWork from '../UnitOfWork'; +import { Knex } from 'knex'; +import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; +import events from '@/subscribers/events'; + +@Service() +export class AssignPdfTemplateDefault { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private uow: UnitOfWork; + + @Inject() + private eventPublisher: EventPublisher; + + public async assignDefaultTemplate(tenantId: number, templateId: number) { + const { PdfTemplate } = this.tenancy.models(tenantId); + + const oldPdfTempalte = await PdfTemplate.query() + .findById(templateId) + .throwIfNotFound(); + + return this.uow.withTransaction( + tenantId, + async (trx?: Knex.Transaction) => { + await PdfTemplate.query(trx) + .where('resource', oldPdfTempalte.resource) + .patch({ default: false }); + + await PdfTemplate.query(trx) + .findById(templateId) + .patch({ default: true }); + + await this.eventPublisher.emitAsync( + events.pdfTemplate.onAssignedDefault, + { + tenantId, + templateId, + } + ); + } + ); + } +} diff --git a/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts b/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts index 2007f9a52..30a08266e 100644 --- a/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts +++ b/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts @@ -5,6 +5,7 @@ import { DeletePdfTemplate } from './DeletePdfTemplate'; import { GetPdfTemplate } from './GetPdfTemplate'; import { GetPdfTemplates } from './GetPdfTemplates'; import { EditPdfTemplate } from './EditPdfTemplate'; +import { AssignPdfTemplateDefault } from './AssignPdfTemplateDefault'; @Service() export class PdfTemplateApplication { @@ -23,6 +24,9 @@ export class PdfTemplateApplication { @Inject() private editPdfTemplateService: EditPdfTemplate; + @Inject() + private assignPdfTemplateDefaultService: AssignPdfTemplateDefault; + public async createPdfTemplate( tenantId: number, templateName: string, @@ -66,4 +70,14 @@ export class PdfTemplateApplication { ) { return this.getPdfTemplatesService.getPdfTemplates(tenantId, query); } + + public async assignPdfTemplateAsDefault( + tenantId: number, + templateId: number + ) { + return this.assignPdfTemplateDefaultService.assignDefaultTemplate( + tenantId, + templateId + ); + } } diff --git a/packages/server/src/subscribers/events.ts b/packages/server/src/subscribers/events.ts index ba2cf7103..9e7d71be7 100644 --- a/packages/server/src/subscribers/events.ts +++ b/packages/server/src/subscribers/events.ts @@ -695,6 +695,9 @@ export default { onDeleting: 'onPdfTemplateDeleting', onDeleted: 'onPdfTemplateDeleted', + onAssignedDefault: 'onPdfTemplateAssignedDefault', + onAssigningDefault: 'onPdfTemplateAssigningDefault', + onInvoiceCreated: 'onInvoicePdfTemplateCreated', }, }; diff --git a/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesTable.tsx b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesTable.tsx index 01f358c65..aeba730dc 100644 --- a/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesTable.tsx +++ b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesTable.tsx @@ -42,6 +42,11 @@ function BrandingTemplateTableRoot({ openDrawer(drawerName, { templateId, resource }); }; + // Handle mark as default button click. + const handleMarkDefaultTemplate = (template) => { + openAlert('branding-template-mark-default', { templateId: template.id }); + }; + return ( + {!original.default && ( + <> + + + + )} import('./DeleteBrandingTemplateAlert'), ); +const MarkDefaultBrandingTemplateAlert = React.lazy( + () => import('./MarkDefaultBrandingTemplateAlert'), +); + export const BrandingTemplatesAlerts = [ { name: 'branding-template-delete', component: DeleteBrandingTemplateAlert }, + { + name: 'branding-template-mark-default', + component: MarkDefaultBrandingTemplateAlert, + }, ]; diff --git a/packages/webapp/src/containers/BrandingTemplates/alerts/MarkDefaultBrandingTemplateAlert.tsx b/packages/webapp/src/containers/BrandingTemplates/alerts/MarkDefaultBrandingTemplateAlert.tsx new file mode 100644 index 000000000..41b9c8244 --- /dev/null +++ b/packages/webapp/src/containers/BrandingTemplates/alerts/MarkDefaultBrandingTemplateAlert.tsx @@ -0,0 +1,73 @@ +// @ts-nocheck +import React from 'react'; +import intl from 'react-intl-universal'; +import { AppToaster } from '@/components'; +import { Alert, Intent } from '@blueprintjs/core'; +import { useAssignPdfTemplateAsDefault } from '@/hooks/query/pdf-templates'; + +import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect'; +import withAlertActions from '@/containers/Alert/withAlertActions'; + +import { compose } from '@/utils'; + +/** + * Mark default branding template alert. + */ +function MarkDefaultBrandingTemplateAlert({ + // #ownProps + name, + + // #withAlertStoreConnect + isOpen, + payload: { templateId }, + + // #withAlertActions + closeAlert, +}) { + const { mutateAsync: assignPdfTemplateAsDefault } = + useAssignPdfTemplateAsDefault(); + + const handleConfirmDelete = () => { + assignPdfTemplateAsDefault({ templateId }) + .then(() => { + AppToaster.show({ + message: + 'The branding template has been marked as default successfully.', + intent: Intent.SUCCESS, + }); + closeAlert(name); + }) + .catch((error) => { + AppToaster.show({ + message: 'Something went wrong.', + intent: Intent.DANGER, + }); + closeAlert(name); + }); + }; + + const handleCancel = () => { + closeAlert(name); + }; + + return ( + +

+ Are you sure want to mark the given branding template as default + template? +

+
+ ); +} + +export default compose( + withAlertStoreConnect(), + withAlertActions, +)(MarkDefaultBrandingTemplateAlert); diff --git a/packages/webapp/src/hooks/query/pdf-templates.ts b/packages/webapp/src/hooks/query/pdf-templates.ts index eee064585..adba19753 100644 --- a/packages/webapp/src/hooks/query/pdf-templates.ts +++ b/packages/webapp/src/hooks/query/pdf-templates.ts @@ -166,3 +166,40 @@ export const useGetPdfTemplates = ( options, ); }; + +export interface AssignPdfTemplateAsDefaultValues { + templateId: number; +} + +export interface AssignPdfTemplateAsDefaultResponse {} + +export const useAssignPdfTemplateAsDefault = ( + options?: UseMutationOptions< + AssignPdfTemplateAsDefaultResponse, + Error, + AssignPdfTemplateAsDefaultValues + >, +): UseMutationResult< + AssignPdfTemplateAsDefaultResponse, + Error, + AssignPdfTemplateAsDefaultValues +> => { + const apiRequest = useApiRequest(); + const queryClient = useQueryClient(); + return useMutation< + AssignPdfTemplateAsDefaultResponse, + Error, + AssignPdfTemplateAsDefaultValues + >( + ({ templateId }) => + apiRequest + .post(`/pdf-templates/${templateId}/assign_default`) + .then((res) => res.data), + { + onSuccess: () => { + queryClient.invalidateQueries([PdfTemplatesQueryKey]); + }, + ...options, + }, + ); +}; From 28319c2cdc949ada42b5456c62f42d20703f499e Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sat, 14 Sep 2024 16:26:34 +0200 Subject: [PATCH 22/32] feat: cannot delete a predefined branding template --- .../services/PdfTemplate/DeletePdfTemplate.ts | 20 ++++++++-- .../server/src/services/PdfTemplate/types.ts | 3 ++ .../alerts/DeleteBrandingTemplateAlert.tsx | 39 ++++++++++++------- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/packages/server/src/services/PdfTemplate/DeletePdfTemplate.ts b/packages/server/src/services/PdfTemplate/DeletePdfTemplate.ts index e15bea1de..246d12636 100644 --- a/packages/server/src/services/PdfTemplate/DeletePdfTemplate.ts +++ b/packages/server/src/services/PdfTemplate/DeletePdfTemplate.ts @@ -1,8 +1,10 @@ import { Inject, Service } from 'typedi'; import HasTenancyService from '../Tenancy/TenancyService'; import UnitOfWork from '../UnitOfWork'; -import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; import events from '@/subscribers/events'; +import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; +import { ServiceError } from '@/exceptions'; +import { ERRORS } from './types'; @Service() export class DeletePdfTemplate { @@ -18,16 +20,26 @@ export class DeletePdfTemplate { /** * Deletes a pdf template. * @param {number} tenantId - * @param {number} templateId + * @param {number} templateId - Pdf template id. */ - public deletePdfTemplate(tenantId: number, templateId: number) { + public async deletePdfTemplate(tenantId: number, templateId: number) { const { PdfTemplate } = this.tenancy.models(tenantId); + const oldPdfTemplate = await PdfTemplate.query() + .findById(templateId) + .throwIfNotFound(); + + // Cannot delete the predefined pdf templates. + if (oldPdfTemplate.predefined) { + throw new ServiceError(ERRORS.CANNOT_DELETE_PREDEFINED_PDF_TEMPLATE); + } return this.uow.withTransaction(tenantId, async (trx) => { // Triggers `onPdfTemplateDeleting` event. await this.eventPublisher.emitAsync(events.pdfTemplate.onDeleting, { tenantId, templateId, + oldPdfTemplate, + trx, }); await PdfTemplate.query(trx).deleteById(templateId); @@ -35,6 +47,8 @@ export class DeletePdfTemplate { await this.eventPublisher.emitAsync(events.pdfTemplate.onDeleted, { tenantId, templateId, + oldPdfTemplate, + trx, }); }); } diff --git a/packages/server/src/services/PdfTemplate/types.ts b/packages/server/src/services/PdfTemplate/types.ts index 98cf1d214..b27c87f0a 100644 --- a/packages/server/src/services/PdfTemplate/types.ts +++ b/packages/server/src/services/PdfTemplate/types.ts @@ -1,3 +1,6 @@ +export enum ERRORS { + CANNOT_DELETE_PREDEFINED_PDF_TEMPLATE = 'CANNOT_DELETE_PREDEFINED_PDF_TEMPLATE', +} export interface ICreateInvoicePdfTemplateDTO { // Colors primaryColor?: string; diff --git a/packages/webapp/src/containers/BrandingTemplates/alerts/DeleteBrandingTemplateAlert.tsx b/packages/webapp/src/containers/BrandingTemplates/alerts/DeleteBrandingTemplateAlert.tsx index a044d5fbe..58358b0de 100644 --- a/packages/webapp/src/containers/BrandingTemplates/alerts/DeleteBrandingTemplateAlert.tsx +++ b/packages/webapp/src/containers/BrandingTemplates/alerts/DeleteBrandingTemplateAlert.tsx @@ -3,7 +3,7 @@ import React from 'react'; import intl from 'react-intl-universal'; import { AppToaster } from '@/components'; import { Alert, Intent } from '@blueprintjs/core'; -import { useDeletePdfTemplate} from '@/hooks/query/pdf-templates'; +import { useDeletePdfTemplate } from '@/hooks/query/pdf-templates'; import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect'; import withAlertActions from '@/containers/Alert/withAlertActions'; @@ -35,13 +35,30 @@ function DeleteBrandingTemplateAlert({ }); closeAlert(name); }) - .catch((error) => { - AppToaster.show({ - message: 'Something went wrong.', - intent: Intent.DANGER, - }); - closeAlert(name); - }); + .catch( + ({ + response: { + data: { errors }, + }, + }) => { + if ( + errors.find( + (error) => error.type === 'CANNOT_DELETE_PREDEFINED_PDF_TEMPLATE', + ) + ) { + AppToaster.show({ + message: 'Cannot delete a predefined branding template.', + intent: Intent.DANGER, + }); + } else { + AppToaster.show({ + message: 'Something went wrong.', + intent: Intent.DANGER, + }); + } + closeAlert(name); + }, + ); }; const handleCancel = () => { @@ -57,9 +74,7 @@ function DeleteBrandingTemplateAlert({ onCancel={handleCancel} onConfirm={handleConfirmDelete} > -

- Are you sure want to delete branding template? -

+

Are you sure want to delete branding template?

); } @@ -68,5 +83,3 @@ export default compose( withAlertStoreConnect(), withAlertActions, )(DeleteBrandingTemplateAlert); - - From d690c6a3fe848727f9ae7cb7cab2490136b5aee1 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sat, 14 Sep 2024 19:32:16 +0200 Subject: [PATCH 23/32] feat: optimize branding templates customiing --- .../ElementCustomizeFieldsGroup.tsx | 2 +- .../CreditNotePaperTemplate.tsx | 9 ++- .../EstimateCustomizeContent.tsx | 3 - .../EstimatePaperTemplate.tsx | 18 ++++-- .../Estimates/EstimateCustomize/constants.ts | 8 +-- .../InvoiceCustomize/InvoicePaperTemplate.tsx | 16 ++++-- .../InvoiceCustomize/PaperTemplate.tsx | 35 ++++++++++-- .../PaymentReceivedPaperTemplate.tsx | 31 ++++++++--- .../ReceiptCustomizeContent.tsx | 3 +- .../ReceiptCustomizeFieldsContent.tsx | 24 +++++++- .../ReceiptCustomize/ReceiptPaperTemplate.tsx | 39 +++++++++++-- .../Receipts/ReceiptCustomize/constants.ts | 55 ++++++++++++++++++- .../Sales/Receipts/ReceiptCustomize/types.ts | 11 ++-- 13 files changed, 209 insertions(+), 45 deletions(-) diff --git a/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFieldsGroup.tsx b/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFieldsGroup.tsx index 0e406f4f2..0ee08b93f 100644 --- a/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFieldsGroup.tsx +++ b/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFieldsGroup.tsx @@ -1,7 +1,7 @@ // @ts-nocheck +import { InputGroupProps, SwitchProps } from '@blueprintjs/core'; import { FInputGroup, FSwitch, Group, Stack } from '@/components'; import { CLASSES } from '@/constants'; -import { InputGroupProps, SwitchProps } from '@blueprintjs/core'; export function ElementCustomizeFieldsGroup({ label, diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNotePaperTemplate.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNotePaperTemplate.tsx index 61de0f25a..d50c7d9ac 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNotePaperTemplate.tsx +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNotePaperTemplate.tsx @@ -1,5 +1,8 @@ import { Group, Stack } from '@/components'; -import { PaperTemplate, PaperTemplateProps } from '../../Invoices/InvoiceCustomize/PaperTemplate'; +import { + PaperTemplate, + PaperTemplateProps, +} from '../../Invoices/InvoiceCustomize/PaperTemplate'; export interface CreditNotePaperTemplateProps extends PaperTemplateProps { billedToAddress?: Array; @@ -131,8 +134,8 @@ export function CreditNotePaperTemplate({ columns={[ { label: 'Item', accessor: 'item' }, { label: 'Description', accessor: 'item' }, - { label: 'Rate', accessor: 'rate' }, - { label: 'Total', accessor: 'total' }, + { label: 'Rate', accessor: 'rate', align: 'right' }, + { label: 'Total', accessor: 'total', align: 'right' }, ]} data={lines} /> diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx index 82f05c618..9d4dd2739 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx @@ -1,6 +1,4 @@ -import { Classes } from '@blueprintjs/core'; import { useFormikContext } from 'formik'; -import { Box } from '@/components'; import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize'; import { EstimateCustomizeGeneralField } from './EstimateCustomizeFieldsGeneral'; import { EstimateCustomizeContentFields } from './EstimateCustomizeFieldsContent'; @@ -14,7 +12,6 @@ import { BrandingTemplateForm } from '@/containers/BrandingTemplates/BrandingTem export function EstimateCustomizeContent() { const { payload, name } = useDrawerContext(); const { closeDrawer } = useDrawerActions(); - const templateId = payload?.templateId || null; const handleSuccess = () => { diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimatePaperTemplate.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimatePaperTemplate.tsx index 5432e5d45..f1375b8d0 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimatePaperTemplate.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimatePaperTemplate.tsx @@ -58,6 +58,7 @@ export function EstimatePaperTemplate({ secondaryColor, showCompanyLogo = true, companyLogo, + companyName, billedToAddress = [ 'Bigcapital Technology, Inc.', @@ -74,6 +75,9 @@ export function EstimatePaperTemplate({ '+1 762-339-5634', 'ahmed@bigcapital.app', ], + showBilledFromAddress = true, + showBilledToAddress = true, + total = '$1000.00', totalLabel = 'Total', showTotal = true, @@ -141,8 +145,14 @@ export function EstimatePaperTemplate({ - - + {showBilledFromAddress && ( + {companyName}, ...billedFromAddress]} + /> + )} + {showBilledToAddress && ( + + )} @@ -150,8 +160,8 @@ export function EstimatePaperTemplate({ columns={[ { label: 'Item', accessor: 'item' }, { label: 'Description', accessor: 'item' }, - { label: 'Rate', accessor: 'rate' }, - { label: 'Total', accessor: 'total' }, + { label: 'Rate', accessor: 'rate', align: 'right' }, + { label: 'Total', accessor: 'total', align: 'right' }, ]} data={lines} /> diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/constants.ts b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/constants.ts index d48e05f3d..14c0c2196 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/constants.ts +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/constants.ts @@ -25,7 +25,7 @@ export const initialValues = { // Addresses showBilledFromAddress: true, - showBillingToAddress: true, + showBilledToAddress: true, billedToLabel: 'Billed To', // Entries @@ -55,7 +55,7 @@ export const fieldsGroups = [ fields: [ { labelKey: 'estimateNumberLabel', - enableKey: 'showEstimateeNumber', + enableKey: 'showEstimateNumber', label: 'Estimate No.', }, { @@ -65,11 +65,11 @@ export const fieldsGroups = [ }, { labelKey: 'expirationDateLabel', - enableKey: 'expirationDueDate', + enableKey: 'showExpirationDate', label: 'Expiration Date', }, { - enableKey: 'showBillingToAddress', + enableKey: 'showBilledToAddress', labelKey: 'billedToLabel', label: 'Bill To', }, diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx index 2fc4041dd..7a24f9685 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { PaperTemplate } from './PaperTemplate'; +import { PaperTemplate, PaperTemplateTotalBorder } from './PaperTemplate'; import { Group, Stack } from '@/components'; interface PapaerLine { @@ -223,8 +223,8 @@ export function InvoicePaperTemplate({ columns={[ { label: lineItemLabel, accessor: 'item' }, { label: lineDescriptionLabel, accessor: 'description' }, - { label: lineRateLabel, accessor: 'rate' }, - { label: lineTotalLabel, accessor: 'total' }, + { label: lineRateLabel, accessor: 'rate', align: 'right' }, + { label: lineTotalLabel, accessor: 'total', align: 'right' }, ]} data={lines} /> @@ -233,6 +233,7 @@ export function InvoicePaperTemplate({ )} {showDiscount && ( @@ -253,7 +254,12 @@ export function InvoicePaperTemplate({ )} {showTotal && ( - + )} {showPaymentMade && ( )} diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx index fde94244d..571f3c753 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx @@ -10,6 +10,7 @@ export interface PaperTemplateProps { showCompanyLogo?: boolean; companyLogo?: string; + companyName?: string; bigtitle?: string; @@ -44,7 +45,11 @@ export function PaperTemplate({ } interface PaperTemplateTableProps { - columns: Array<{ accessor: string; label: string }>; + columns: Array<{ + accessor: string; + label: string; + align?: 'left' | 'center' | 'right'; + }>; data: Array>; } @@ -54,7 +59,9 @@ PaperTemplate.Table = ({ columns, data }: PaperTemplateTableProps) => { {columns.map((col, index) => ( - {col.label} + + {col.label} + ))} @@ -63,7 +70,9 @@ PaperTemplate.Table = ({ columns, data }: PaperTemplateTableProps) => { {data.map((_data: any) => ( {columns.map((column, index) => ( - {get(_data, column.accessor)} + + {get(_data, column.accessor)} + ))} ))} @@ -72,18 +81,34 @@ PaperTemplate.Table = ({ columns, data }: PaperTemplateTableProps) => { ); }; +export enum PaperTemplateTotalBorder { + Gray = 'gray', + Dark = 'dark', +} + PaperTemplate.Totals = ({ children }: { children: React.ReactNode }) => { - return
{children}
; + return
{children}
; }; PaperTemplate.TotalLine = ({ label, amount, + border, + style, }: { label: string; amount: string; + border?: PaperTemplateTotalBorder; + style?: any; }) => { return ( -
+
{label}
{amount}
diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedPaperTemplate.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedPaperTemplate.tsx index ac0779d5a..52318be2e 100644 --- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedPaperTemplate.tsx +++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedPaperTemplate.tsx @@ -2,6 +2,7 @@ import { Group, Stack } from '@/components'; import { PaperTemplate, PaperTemplateProps, + PaperTemplateTotalBorder, } from '../../Invoices/InvoiceCustomize/PaperTemplate'; export interface PaymentReceivedPaperTemplateProps extends PaperTemplateProps { @@ -44,6 +45,7 @@ export function PaymentReceivedPaperTemplate({ secondaryColor, showCompanyLogo = true, companyLogo, + companyName = 'Bigcapital Technology, Inc.', billedToAddress = [ 'Bigcapital Technology, Inc.', @@ -62,6 +64,7 @@ export function PaymentReceivedPaperTemplate({ ], showBilledFromAddress, showBillingToAddress, + billedToLabel = 'Billed To', total = '$1000.00', totalLabel = 'Total', @@ -110,11 +113,15 @@ export function PaymentReceivedPaperTemplate({ - {showBillingToAddress && ( - - )} {showBilledFromAddress && ( - + {companyName}, ...billedFromAddress]} + /> + )} + {showBillingToAddress && ( + {billedToLabel}, ...billedToAddress]} + /> )} @@ -122,8 +129,12 @@ export function PaymentReceivedPaperTemplate({ @@ -132,10 +143,16 @@ export function PaymentReceivedPaperTemplate({ )} {showTotal && ( - + )} diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx index cc68a1759..abcffae35 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx @@ -1,10 +1,10 @@ +import { useFormikContext } from 'formik'; import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize'; import { ReceiptCustomizeGeneralField } from './ReceiptCustomizeFieldsGeneral'; import { ReceiptCustomizeFieldsContent } from './ReceiptCustomizeFieldsContent'; import { ReceiptPaperTemplate } from './ReceiptPaperTemplate'; import { ReceiptCustomizeValues } from './types'; import { initialValues } from './constants'; -import { useFormikContext } from 'formik'; import { BrandingTemplateForm } from '@/containers/BrandingTemplates/BrandingTemplateForm'; import { useDrawerActions } from '@/hooks/state'; import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; @@ -12,7 +12,6 @@ import { useDrawerContext } from '@/components/Drawer/DrawerProvider'; export function ReceiptCustomizeContent() { const { payload, name } = useDrawerContext(); const { closeDrawer } = useDrawerActions(); - const templateId = payload?.templateId || null; const handleFormSuccess = () => { diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsContent.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsContent.tsx index ed73871e5..f7107e20b 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsContent.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsContent.tsx @@ -1,6 +1,11 @@ // @ts-nocheck import { Stack } from '@/components'; import { Classes } from '@blueprintjs/core'; +import { + ElementCustomizeContentItemFieldGroup, + ElementCustomizeFieldsGroup, +} from '@/containers/ElementCustomize/ElementCustomizeFieldsGroup'; +import { fieldsGroups } from './constants'; export function ReceiptCustomizeFieldsContent() { return ( @@ -16,7 +21,24 @@ export function ReceiptCustomizeFieldsContent() {

- + + {fieldsGroups.map((group) => ( + + {group.fields.map((item, index) => ( + + ))} + + ))} + ); } diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptPaperTemplate.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptPaperTemplate.tsx index 148e34d5d..62631c155 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptPaperTemplate.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptPaperTemplate.tsx @@ -1,26 +1,38 @@ import { Group, Stack } from '@/components'; -import { PaperTemplate, PaperTemplateProps } from '../../Invoices/InvoiceCustomize/PaperTemplate'; +import { + PaperTemplate, + PaperTemplateProps, +} from '../../Invoices/InvoiceCustomize/PaperTemplate'; export interface ReceiptPaperTemplateProps extends PaperTemplateProps { + // Addresses billedToAddress?: Array; billedFromAddress?: Array; + showBilledFromAddress?: boolean; + showBilledToAddress?: boolean; + billedToLabel?: string; + // Total total?: string; showTotal?: boolean; totalLabel?: string; + // Subtotal subtotal?: string; showSubtotal?: boolean; subtotalLabel?: string; +// Customer Note showCustomerNote?: boolean; customerNote?: string; customerNoteLabel?: string; + // Terms & Conditions showTermsConditions?: boolean; termsConditions?: string; termsConditionsLabel?: string; + // Lines lines?: Array<{ item: string; description: string; @@ -29,10 +41,12 @@ export interface ReceiptPaperTemplateProps extends PaperTemplateProps { total: string; }>; + // Receipt Date. receiptDateLabel?: string; showReceiptDate?: boolean; receiptDate?: string; + // Receipt Number receiptNumebr?: string; receiptNumberLabel?: string; showReceiptNumber?: boolean; @@ -43,7 +57,9 @@ export function ReceiptPaperTemplate({ secondaryColor, showCompanyLogo = true, companyLogo, + companyName = 'Bigcapital Technology, Inc.', + // # Address billedToAddress = [ 'Bigcapital Technology, Inc.', '131 Continental Dr Suite 305 Newark,', @@ -59,6 +75,10 @@ export function ReceiptPaperTemplate({ '+1 762-339-5634', 'ahmed@bigcapital.app', ], + showBilledFromAddress = true, + showBilledToAddress = true, + billedToLabel = 'Billed To', + total = '$1000.00', totalLabel = 'Total', showTotal = true, @@ -107,7 +127,6 @@ export function ReceiptPaperTemplate({ {receiptNumebr} )} - {showReceiptDate && ( {receiptDate} @@ -116,8 +135,16 @@ export function ReceiptPaperTemplate({ - - + {showBilledFromAddress && ( + {companyName}, ...billedFromAddress]} + /> + )} + {showBilledToAddress && ( + {billedToLabel}, ...billedToAddress]} + /> + )} @@ -125,8 +152,8 @@ export function ReceiptPaperTemplate({ columns={[ { label: 'Item', accessor: 'item' }, { label: 'Description', accessor: 'item' }, - { label: 'Rate', accessor: 'rate' }, - { label: 'Total', accessor: 'total' }, + { label: 'Rate', accessor: 'rate', align: 'right' }, + { label: 'Total', accessor: 'total', align: 'right' }, ]} data={lines} /> diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/constants.ts b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/constants.ts index 241020ce2..cffb4cd49 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/constants.ts +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/constants.ts @@ -23,7 +23,7 @@ export const initialValues = { // Addresses showBilledFromAddress: true, - showBillingToAddress: true, + showBilledToAddress: true, billedToLabel: 'Billed To', // Entries @@ -48,3 +48,56 @@ export const initialValues = { customerNoteLabel: 'Customer Note', showCustomerNote: true, }; + +export const fieldsGroups = [ + { + label: 'Header', + fields: [ + { + labelKey: 'receiptNumberLabel', + enableKey: 'showReceiptNumber', + label: 'Receipt Number', + }, + { + labelKey: 'receiptDateLabel', + enableKey: 'showReceiptDate', + label: 'Receipt Date', + }, + { + enableKey: 'showBilledToAddress', + labelKey: 'billedToLabel', + label: 'Bill To', + }, + { + enableKey: 'showBilledFromAddress', + label: 'Billed From', + }, + ], + }, + { + label: 'Totals', + fields: [ + { + labelKey: 'subtotalLabel', + enableKey: 'showSubtotal', + label: 'Subtotal', + }, + { labelKey: 'totalLabel', enableKey: 'showTotal', label: 'Total' }, + ], + }, + { + label: 'Statements', + fields: [ + { + enableKey: 'showCustomerNote', + labelKey: 'customerNoteLabel', + label: 'Customer Note', + }, + { + enableKey: 'showTermsConditions', + labelKey: 'termsConditionsLabel', + label: 'Terms & Conditions', + }, + ], + }, +]; diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/types.ts b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/types.ts index 8b9ccc54c..d9257b0a8 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/types.ts +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/types.ts @@ -9,10 +9,11 @@ export interface ReceiptCustomizeValues extends BrandingTemplateValues { showCompanyLogo?: boolean; companyLogo?: string; - // Top details. + // Receipt Number showReceiptNumber?: boolean; receiptNumberLabel?: string; + // Receipt Date. showReceiptDate?: boolean; receiptDateLabel?: string; @@ -21,7 +22,7 @@ export interface ReceiptCustomizeValues extends BrandingTemplateValues { // Addresses showBilledFromAddress?: boolean; - showBillingToAddress?: boolean; + showBilledToAddress?: boolean; billedToLabel?: string; // Entries @@ -30,17 +31,19 @@ export interface ReceiptCustomizeValues extends BrandingTemplateValues { itemRateLabel?: string; itemTotalLabel?: string; - // Totals + // Subtotal showSubtotal?: boolean; subtotalLabel?: string; + // Total showTotal?: boolean; totalLabel?: string; - // Statements + // Terms & Conditions termsConditionsLabel?: string; showTermsConditions?: boolean; + // Statement customerNoteLabel?: string; showCustomerNote?: boolean; } From 70551bee308ef905a23bdb5832011679c7661213 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sat, 14 Sep 2024 20:18:03 +0200 Subject: [PATCH 24/32] feat: the element customize submit button --- .../src/containers/ElementCustomize/ElementCustomizeFields.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFields.tsx b/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFields.tsx index c9846ecf6..2e0ec0c94 100644 --- a/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFields.tsx +++ b/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFields.tsx @@ -63,6 +63,7 @@ function ElementCustomizeFooterActionsRoot({ closeDrawer }) { intent={Intent.PRIMARY} style={{ minWidth: 75 }} loading={isSubmitting} + type={'submit'} > Save From 8566422ce3a88a08749665d7339886cbe821eaf3 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sat, 14 Sep 2024 22:52:37 +0200 Subject: [PATCH 25/32] fix: Add mising address in branding templates customize --- .../CreditNotePaperTemplate.tsx | 30 +++++++++++++++++-- .../CreditNoteCustomize/constants.ts | 14 +++++++++ .../EstimatePaperTemplate.tsx | 6 +++- .../InvoiceCustomizeContent.tsx | 1 - .../InvoiceCustomize/InvoicePaperTemplate.tsx | 4 ++- 5 files changed, 49 insertions(+), 6 deletions(-) diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNotePaperTemplate.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNotePaperTemplate.tsx index d50c7d9ac..f594ba551 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNotePaperTemplate.tsx +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNotePaperTemplate.tsx @@ -5,8 +5,12 @@ import { } from '../../Invoices/InvoiceCustomize/PaperTemplate'; export interface CreditNotePaperTemplateProps extends PaperTemplateProps { + // Address billedToAddress?: Array; billedFromAddress?: Array; + showBilledToAddress?: boolean; + showBilledFromAddress?: boolean; + billedToLabel?: string; // Total total?: string; @@ -28,6 +32,7 @@ export interface CreditNotePaperTemplateProps extends PaperTemplateProps { termsConditions?: string; termsConditionsLabel?: string; + // Lines lines?: Array<{ item: string; description: string; @@ -52,7 +57,9 @@ export function CreditNotePaperTemplate({ secondaryColor, showCompanyLogo = true, companyLogo, + companyName = 'Bigcapital Technology, Inc.', + // Address billedToAddress = [ 'Bigcapital Technology, Inc.', '131 Continental Dr Suite 305 Newark,', @@ -68,18 +75,26 @@ export function CreditNotePaperTemplate({ '+1 762-339-5634', 'ahmed@bigcapital.app', ], + showBilledToAddress = true, + showBilledFromAddress = true, + billedToLabel = 'Billed To', + + // Total total = '$1000.00', totalLabel = 'Total', showTotal = true, + // Subtotal subtotal = '1000/00', subtotalLabel = 'Subtotal', showSubtotal = true, + // Customer note showCustomerNote = true, customerNote = 'It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.', customerNoteLabel = 'Customer Note', + // Terms & conditions showTermsConditions = true, termsConditions = 'It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.', termsConditionsLabel = 'Terms & Conditions', @@ -93,10 +108,12 @@ export function CreditNotePaperTemplate({ total: '$1000.00', }, ], + // Credit note number. showCreditNoteNumber = true, creditNoteNumberLabel = 'Credit Note Number', creditNoteNumebr = '346D3D40-0001', + // Credit note date. creditNoteDate = 'September 3, 2024', showCreditNoteDate = true, creditNoteDateLabel = 'Credit Note Date', @@ -116,7 +133,6 @@ export function CreditNotePaperTemplate({ {creditNoteNumebr} )} - {showCreditNoteDate && ( {creditNoteDate} @@ -125,8 +141,16 @@ export function CreditNotePaperTemplate({ - - + {showBilledFromAddress && ( + {companyName}, ...billedFromAddress]} + /> + )} + {showBilledToAddress && ( + {billedToLabel}, ...billedToAddress]} + /> + )} diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/constants.ts b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/constants.ts index 987303fa6..e1beac48f 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/constants.ts +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/constants.ts @@ -10,6 +10,11 @@ export const initialValues = { companyLogo: 'https://cdn-development.mercury.com/demo-assets/avatars/mercury-demo-dark.png', + // Address + showBilledToAddress: true, + showBilledFromAddress: true, + billedToLabel: 'Bill To', + // Entries itemNameLabel: 'Item', itemDescriptionLabel: 'Description', @@ -55,6 +60,15 @@ export const fieldsGroups = [ enableKey: 'showCreditNoteNumber', label: 'Credit Note #', }, + { + enableKey: 'showBilledToAddress', + labelKey: 'billedToLabel', + label: 'Bill To', + }, + { + enableKey: 'showBilledFromAddress', + label: 'Billed From', + }, ], }, { diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimatePaperTemplate.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimatePaperTemplate.tsx index f1375b8d0..09517d495 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimatePaperTemplate.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimatePaperTemplate.tsx @@ -25,6 +25,7 @@ export interface EstimatePaperTemplateProps extends PaperTemplateProps { showBilledFromAddress?: boolean; billedFromAddress?: Array; + billedToLabel?: string; // Totals total?: string; @@ -77,6 +78,7 @@ export function EstimatePaperTemplate({ ], showBilledFromAddress = true, showBilledToAddress = true, + billedToLabel = 'Billed To', total = '$1000.00', totalLabel = 'Total', @@ -151,7 +153,9 @@ export function EstimatePaperTemplate({ /> )} {showBilledToAddress && ( - + {billedToLabel}, ...billedToAddress]} + /> )} diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx index e6bc0dbd6..0346b989c 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx @@ -18,7 +18,6 @@ import { initialValues } from './constants'; export function InvoiceCustomizeContent() { const { payload, name } = useDrawerContext(); const { closeDrawer } = useDrawerActions(); - const templateId = payload?.templateId || null; const handleSuccess = () => { diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx index 7a24f9685..b191b0875 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx @@ -214,7 +214,9 @@ export function InvoicePaperTemplate({ /> )} {showBillingToAddress && ( - + {billedToLabel}, ...billedToAddress]} + /> )} From ef4beaa5647086f66b9b94eae659c79b258c089e Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 15 Sep 2024 22:01:11 +0200 Subject: [PATCH 26/32] feat: seed initial standard branding templates --- ...240911112147_create_pdf_templates_table.js | 2 + ...40915195024_seed_standard_pdf_templates.js | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 packages/server/src/database/migrations/20240915195024_seed_standard_pdf_templates.js diff --git a/packages/server/src/database/migrations/20240911112147_create_pdf_templates_table.js b/packages/server/src/database/migrations/20240911112147_create_pdf_templates_table.js index f48f28498..be880c262 100644 --- a/packages/server/src/database/migrations/20240911112147_create_pdf_templates_table.js +++ b/packages/server/src/database/migrations/20240911112147_create_pdf_templates_table.js @@ -9,6 +9,8 @@ exports.up = function (knex) { table.text('resource'); table.text('template_name'); table.json('attributes'); + table.boolean('predefined').defaultTo(false); + table.boolean('default').defaultTo(false); table.timestamps(); }) .table('sales_invoices', (table) => { diff --git a/packages/server/src/database/migrations/20240915195024_seed_standard_pdf_templates.js b/packages/server/src/database/migrations/20240915195024_seed_standard_pdf_templates.js new file mode 100644 index 000000000..74c87bb14 --- /dev/null +++ b/packages/server/src/database/migrations/20240915195024_seed_standard_pdf_templates.js @@ -0,0 +1,44 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function (knex) { + return knex('pdf_templates').insert([ + { + resource: 'SaleInvoice', + templateName: 'Standard Template', + predefined: true, + default: true, + }, + { + resource: 'SaleEstimate', + templateName: 'Standard Template', + predefined: true, + default: true, + }, + { + resource: 'SaleReceipt', + templateName: 'Standard Template', + predefined: true, + default: true, + }, + { + resource: 'CreditNote', + templateName: 'Standard Template', + predefined: true, + default: true, + }, + { + resource: 'PaymentReceive', + templateName: 'Standard Template', + predefined: true, + default: true, + }, + ]); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function (knex) {}; From 94c08f0b9e5369084d14eb48aaa43b8c88a29e5c Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 15 Sep 2024 22:55:39 +0200 Subject: [PATCH 27/32] chore: clean pdf templates code --- .../PdfTemplate/AssignPdfTemplateDefault.ts | 9 ++++- .../services/PdfTemplate/EditPdfTemplate.ts | 24 +++++++---- .../PdfTemplate/GetPdfTemplatesTransformer.ts | 14 ++++++- .../PdfTemplate/PdfTemplateApplication.ts | 40 +++++++++++++++++++ packages/server/src/subscribers/events.ts | 3 +- .../BrandingTemplatesTable.tsx | 29 ++------------ .../containers/BrandingTemplates/_hooks.tsx | 25 ++++++++++++ 7 files changed, 106 insertions(+), 38 deletions(-) create mode 100644 packages/webapp/src/containers/BrandingTemplates/_hooks.tsx diff --git a/packages/server/src/services/PdfTemplate/AssignPdfTemplateDefault.ts b/packages/server/src/services/PdfTemplate/AssignPdfTemplateDefault.ts index f4317aec1..ac038c79d 100644 --- a/packages/server/src/services/PdfTemplate/AssignPdfTemplateDefault.ts +++ b/packages/server/src/services/PdfTemplate/AssignPdfTemplateDefault.ts @@ -1,7 +1,7 @@ import { Service, Inject } from 'typedi'; +import { Knex } from 'knex'; import HasTenancyService from '../Tenancy/TenancyService'; import UnitOfWork from '../UnitOfWork'; -import { Knex } from 'knex'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; import events from '@/subscribers/events'; @@ -16,6 +16,13 @@ export class AssignPdfTemplateDefault { @Inject() private eventPublisher: EventPublisher; + /** + * Assigns a default PDF template for a specific tenant. + * @param {number} tenantId - The ID of the tenant for whom the default template is being assigned. + * @param {number} templateId - The ID of the template to be set as the default. + * @returns {Promise} A promise that resolves when the operation is complete. + * @throws {Error} Throws ddan error if the specified template is not found. + */ public async assignDefaultTemplate(tenantId: number, templateId: number) { const { PdfTemplate } = this.tenancy.models(tenantId); diff --git a/packages/server/src/services/PdfTemplate/EditPdfTemplate.ts b/packages/server/src/services/PdfTemplate/EditPdfTemplate.ts index 0e94a2c61..1436e4af5 100644 --- a/packages/server/src/services/PdfTemplate/EditPdfTemplate.ts +++ b/packages/server/src/services/PdfTemplate/EditPdfTemplate.ts @@ -1,4 +1,5 @@ import { Inject, Service } from 'typedi'; +import { Knex } from 'knex'; import { IEditPdfTemplateDTO } from './types'; import HasTenancyService from '../Tenancy/TenancyService'; import UnitOfWork from '../UnitOfWork'; @@ -19,30 +20,39 @@ export class EditPdfTemplate { /** * Edits an existing pdf template. * @param {number} tenantId - * @param {number} templateId + * @param {number} templateId - Template id. * @param {IEditPdfTemplateDTO} editTemplateDTO */ - public editPdfTemplate( + public async editPdfTemplate( tenantId: number, templateId: number, editTemplateDTO: IEditPdfTemplateDTO ) { const { PdfTemplate } = this.tenancy.models(tenantId); - return this.uow.withTransaction(tenantId, async (trx) => { + const oldPdfTemplate = await PdfTemplate.query() + .findById(templateId) + .throwIfNotFound(); + + return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { + // Triggers `onPdfTemplateEditing` event. await this.eventPublisher.emitAsync(events.pdfTemplate.onEditing, { tenantId, templateId, }); - await PdfTemplate.query(trx).where('id', templateId).update({ - templateName: editTemplateDTO.templateName, - attributes: editTemplateDTO.attributes, - }); + const pdfTemplate = await PdfTemplate.query(trx) + .where('id', templateId) + .update({ + templateName: editTemplateDTO.templateName, + attributes: editTemplateDTO.attributes, + }); + // Triggers `onPdfTemplatedEdited` event. await this.eventPublisher.emitAsync(events.pdfTemplate.onEdited, { tenantId, templateId, }); + return pdfTemplate; }); } } diff --git a/packages/server/src/services/PdfTemplate/GetPdfTemplatesTransformer.ts b/packages/server/src/services/PdfTemplate/GetPdfTemplatesTransformer.ts index 181b3f029..1b6904752 100644 --- a/packages/server/src/services/PdfTemplate/GetPdfTemplatesTransformer.ts +++ b/packages/server/src/services/PdfTemplate/GetPdfTemplatesTransformer.ts @@ -18,11 +18,21 @@ export class GetPdfTemplatesTransformer extends Transformer { return ['createdAtFormatted', 'resourceFormatted']; }; - private createdAtFormatted = (template) => { + /** + * Formats the creation date of the PDF template. + * @param {Object} template + * @returns {string} A formatted string representing the creation date of the template. + */ + protected createdAtFormatted = (template) => { return this.formatDate(template.createdAt); }; - private resourceFormatted = (template) => { + /** + * Formats the creation date of the PDF template. + * @param {Object} template - + * @returns {string} A formatted string representing the creation date of the template. + */ + protected resourceFormatted = (template) => { return getTransactionTypeLabel(template.resource); }; } diff --git a/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts b/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts index 30a08266e..3f0d66be6 100644 --- a/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts +++ b/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts @@ -27,6 +27,14 @@ export class PdfTemplateApplication { @Inject() private assignPdfTemplateDefaultService: AssignPdfTemplateDefault; + /** + * Creates a new PDF template. + * @param {number} tenantId - + * @param {string} templateName - The name of the PDF template to create. + * @param {string} resource - The resource type associated with the PDF template. + * @param {ICreateInvoicePdfTemplateDTO} invoiceTemplateDTO - The data transfer object containing the details for the new PDF template. + * @returns {Promise} + */ public async createPdfTemplate( tenantId: number, templateName: string, @@ -41,6 +49,13 @@ export class PdfTemplateApplication { ); } + /** + * Edits an existing PDF template. + * @param {number} tenantId - The ID of the tenant. + * @param {number} templateId - The ID of the PDF template to edit. + * @param {IEditPdfTemplateDTO} editTemplateDTO - The data transfer object containing the updated details for the PDF template. + * @returns {Promise} + */ public async editPdfTemplate( tenantId: number, templateId: number, @@ -53,6 +68,13 @@ export class PdfTemplateApplication { ); } + /** + * Deletes a PDF template. + * @param {number} tenantId - The ID of the tenant. + * @param {number} templateId - The ID of the PDF template to delete. + * @returns {Promise} + */ + public async deletePdfTemplate(tenantId: number, templateId: number) { return this.deletePdfTemplateService.deletePdfTemplate( tenantId, @@ -60,10 +82,22 @@ export class PdfTemplateApplication { ); } + /** + * Retrieves a PDF template by its ID for a specified tenant. + * @param {number} tenantId - + * @param {number} templateId - The ID of the PDF template to retrieve. + * @returns {Promise} + */ public async getPdfTemplate(tenantId: number, templateId: number) { return this.getPdfTemplateService.getPdfTemplate(tenantId, templateId); } + /** + * Retrieves a list of PDF templates. + * @param {number} tenantId - The ID of the tenant for which to retrieve templates. + * @param {Object} query + * @returns {Promise} + */ public async getPdfTemplates( tenantId: number, query?: { resource?: string } @@ -71,6 +105,12 @@ export class PdfTemplateApplication { return this.getPdfTemplatesService.getPdfTemplates(tenantId, query); } + /** + * Assigns a PDF template as the default template. + * @param {number} tenantId + * @param {number} templateId - The ID of the PDF template to assign as default. + * @returns {Promise} + */ public async assignPdfTemplateAsDefault( tenantId: number, templateId: number diff --git a/packages/server/src/subscribers/events.ts b/packages/server/src/subscribers/events.ts index 9e7d71be7..01f828124 100644 --- a/packages/server/src/subscribers/events.ts +++ b/packages/server/src/subscribers/events.ts @@ -685,6 +685,7 @@ export default { onImportCommitted: 'onImportFileCommitted', }, + // Branding templates pdfTemplate: { onCreating: 'onPdfTemplateCreating', onCreated: 'onPdfTemplateCreated', @@ -697,7 +698,5 @@ export default { onAssignedDefault: 'onPdfTemplateAssignedDefault', onAssigningDefault: 'onPdfTemplateAssigningDefault', - - onInvoiceCreated: 'onInvoicePdfTemplateCreated', }, }; diff --git a/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesTable.tsx b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesTable.tsx index aeba730dc..0b2d57985 100644 --- a/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesTable.tsx +++ b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesTable.tsx @@ -1,15 +1,14 @@ // @ts-nocheck import * as R from 'ramda'; -import { Classes, Tag } from '@blueprintjs/core'; -import clsx from 'classnames'; -import { DataTable, Group, TableSkeletonRows } from '@/components'; +import { DataTable, TableSkeletonRows } from '@/components'; import { useBrandingTemplatesBoot } from './BrandingTemplatesBoot'; import { ActionsMenu } from './_components'; import { DRAWERS } from '@/constants/drawers'; import withAlertActions from '@/containers/Alert/withAlertActions'; import withDrawerActions from '@/containers/Drawer/withDrawerActions'; -import styles from './BrandTemplates.module.scss'; import { getCustomizeDrawerNameFromResource } from './_utils'; +import { useBrandingTemplatesColumns } from './_hooks'; +import styles from './BrandTemplates.module.scss'; interface BrandingTemplatesTableProps {} @@ -72,25 +71,3 @@ export const BrandingTemplatesTable = R.compose( withAlertActions, withDrawerActions, )(BrandingTemplateTableRoot); - -const useBrandingTemplatesColumns = () => { - return [ - { - Header: 'Template Name', - accessor: (row) => ( - - {row.template_name} {row.default && Default} - - ), - width: 65, - clickable: true, - }, - { - Header: 'Created At', - accessor: 'created_at_formatted', - width: 35, - className: clsx(Classes.TEXT_MUTED), - clickable: true, - }, - ]; -}; diff --git a/packages/webapp/src/containers/BrandingTemplates/_hooks.tsx b/packages/webapp/src/containers/BrandingTemplates/_hooks.tsx new file mode 100644 index 000000000..eb5f32251 --- /dev/null +++ b/packages/webapp/src/containers/BrandingTemplates/_hooks.tsx @@ -0,0 +1,25 @@ +import clsx from 'classnames'; +import { Classes, Tag } from '@blueprintjs/core'; +import { Group } from '@/components'; + +export const useBrandingTemplatesColumns = () => { + return [ + { + Header: 'Template Name', + accessor: (row: any) => ( + + {row.template_name} {row.default && Default} + + ), + width: 65, + clickable: true, + }, + { + Header: 'Created At', + accessor: 'created_at_formatted', + width: 35, + className: clsx(Classes.TEXT_MUTED), + clickable: true, + }, + ]; +}; From 4f59b27d702851769f5fcce35d6b43a7d867530c Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 16 Sep 2024 20:02:02 +0200 Subject: [PATCH 28/32] feat: hook up branding templates to invoices --- packages/server/resources/scss/base.css | 42 ++ packages/server/resources/scss/base.scss | 34 -- packages/server/resources/scss/normalize.css | 379 ++++++++++++++++++ .../resources/views/PaperTemplateLayout.pug | 3 + .../views/modules/invoice-regular.pug | 92 ----- .../views/modules/invoice-standard.pug | 244 +++++++++++ packages/server/src/interfaces/SaleInvoice.ts | 85 ++++ packages/server/src/models/PdfTemplate.ts | 3 + .../ChromiumlyTenancy/ChromiumlyTenancy.ts | 6 +- .../src/services/Items/ItemsEntriesService.ts | 2 +- .../PdfTemplate/AssignPdfTemplateDefault.ts | 9 + .../BrandingTemplateDTOTransformer.ts | 39 ++ .../CommandSaleInvoiceDTOTransformer.ts | 14 +- .../services/Sales/Invoices/SaleInvoicePdf.ts | 61 ++- .../Sales/Invoices/SaleInvoicePdfTemplate.ts | 31 ++ .../src/services/Sales/Invoices/constants.ts | 85 ++++ .../src/services/Sales/Invoices/utils.ts | 43 ++ .../TemplateInjectable/TemplateInjectable.ts | 2 +- .../webapp/src/components/Forms/Select.tsx | 18 +- .../BrandingTemplatesSelectFields.tsx | 46 +++ .../InvoiceForm/InvoiceFloatingActions.tsx | 37 +- .../InvoiceForm/InvoiceFormProvider.tsx | 9 +- .../Sales/Invoices/InvoiceForm/utils.tsx | 11 + 23 files changed, 1141 insertions(+), 154 deletions(-) create mode 100644 packages/server/resources/scss/base.css create mode 100644 packages/server/resources/scss/normalize.css delete mode 100644 packages/server/resources/views/modules/invoice-regular.pug create mode 100644 packages/server/resources/views/modules/invoice-standard.pug create mode 100644 packages/server/src/services/PdfTemplate/BrandingTemplateDTOTransformer.ts create mode 100644 packages/server/src/services/Sales/Invoices/SaleInvoicePdfTemplate.ts create mode 100644 packages/server/src/services/Sales/Invoices/utils.ts create mode 100644 packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesSelectFields.tsx diff --git a/packages/server/resources/scss/base.css b/packages/server/resources/scss/base.css new file mode 100644 index 000000000..d7d148cab --- /dev/null +++ b/packages/server/resources/scss/base.css @@ -0,0 +1,42 @@ +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap'); + +*, +*::before, +*::after { + box-sizing: border-box; +} + +th { + text-align: inherit; + text-align: -webkit-match-parent; +} + +thead, +tbody, +tfoot, +tr, +td, +th { + border-color: inherit; + border-style: solid; + border-width: 0; +} + +body{ + margin: 0; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #000; + background-color: #fff; + direction: ltr; + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: transparent; + + font-family: "Noto Sans", sans-serif; + font-optical-sizing: auto; + /* font-weight: ; */ + font-style: normal; + /* font-variation-settings: + "wdth" 100; */ +} \ No newline at end of file diff --git a/packages/server/resources/scss/base.scss b/packages/server/resources/scss/base.scss index bf7b32cc6..f7ed229d8 100644 --- a/packages/server/resources/scss/base.scss +++ b/packages/server/resources/scss/base.scss @@ -1,35 +1 @@ @import "./normalize.scss"; - -*, -*::before, -*::after { - box-sizing: border-box; -} - -th { - text-align: inherit; // 2 - text-align: -webkit-match-parent; // 3 -} - -thead, -tbody, -tfoot, -tr, -td, -th { - border-color: inherit; - border-style: solid; - border-width: 0; -} - -body{ - margin: 0; - font-size: 1rem; - font-weight: 400; - line-height: 1.5; - color: #212529; - background-color: #fff; - direction: ltr; - -webkit-text-size-adjust: 100%; - -webkit-tap-highlight-color: transparent; -} diff --git a/packages/server/resources/scss/normalize.css b/packages/server/resources/scss/normalize.css new file mode 100644 index 000000000..631be2581 --- /dev/null +++ b/packages/server/resources/scss/normalize.css @@ -0,0 +1,379 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + */ + + html { + line-height: 1.15; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers. + */ + +body { + margin: 0; +} + +/** + * Render the `main` element consistently in IE. + */ + +main { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; + /* 1 */ + height: 0; + /* 1 */ + overflow: visible; + /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace, monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Remove the gray background on active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + border-bottom: none; + /* 1 */ + text-decoration: underline; + /* 2 */ + text-decoration: underline dotted; + /* 2 */ +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace, monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove the border on images inside links in IE 10. + */ + +img { + border-style: none; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + line-height: 1.15; + /* 1 */ + margin: 0; + /* 2 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { + /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { + /* 1 */ + text-transform: none; +} + +/** + * Correct the inability to style clickable types in iOS and Safari. + */ + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; + /* 1 */ + color: inherit; + /* 2 */ + display: table; + /* 1 */ + max-width: 100%; + /* 1 */ + padding: 0; + /* 3 */ + white-space: normal; + /* 1 */ +} + +/** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + vertical-align: baseline; +} + +/** + * Remove the default vertical scrollbar in IE 10+. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10. + * 2. Remove the padding in IE 10. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; + /* 1 */ + padding: 0; + /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +[type="search"] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/** + * Remove the inner padding in Chrome and Safari on macOS. + */ + +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + +details { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Misc + ========================================================================== */ + +/** + * Add the correct display in IE 10+. + */ + +template { + display: none; +} + +/** + * Add the correct display in IE 10. + */ + +[hidden] { + display: none; +} diff --git a/packages/server/resources/views/PaperTemplateLayout.pug b/packages/server/resources/views/PaperTemplateLayout.pug index 4678ae166..b93bc7388 100644 --- a/packages/server/resources/views/PaperTemplateLayout.pug +++ b/packages/server/resources/views/PaperTemplateLayout.pug @@ -1,6 +1,9 @@ html(lang=locale) head title My Site - #{title} + style + include ../scss/normalize.css + include ../scss/base.css block head body div.paper-template diff --git a/packages/server/resources/views/modules/invoice-regular.pug b/packages/server/resources/views/modules/invoice-regular.pug deleted file mode 100644 index edddcaf9c..000000000 --- a/packages/server/resources/views/modules/invoice-regular.pug +++ /dev/null @@ -1,92 +0,0 @@ -extends ../PaperTemplateLayout.pug - -block head - style - if (isRtl) - include ../../css/modules/invoice-rtl.css - else - include ../../css/modules/invoice.css - -block content - div.invoice - div.invoice__header - div.paper - h1.title #{__("invoice.paper.invoice")} - if saleInvoice.invoiceNo - span.invoiceNo #{saleInvoice.invoiceNo} - - div.organization - h3.title #{organizationName} - if organizationEmail - span.email #{organizationEmail} - - div.invoice__due-amount - div.label #{__('invoice.paper.invoice_amount')} - div.amount #{saleInvoice.totalFormatted} - - div.invoice__meta - div.invoice__meta-item.invoice__meta-item--amount - span.label #{__('invoice.paper.due_amount')} - span.value #{saleInvoice.dueAmountFormatted} - - div.invoice__meta-item.invoice__meta-item--billed-to - span.label #{__("invoice.paper.billed_to")} - span.value #{saleInvoice.customer.displayName} - - div.invoice__meta-item.invoice__meta-item--invoice-date - span.label #{__("invoice.paper.invoice_date")} - span.value #{saleInvoice.invoiceDateFormatted} - - div.invoice__meta-item.invoice__meta-item--due-date - span.label #{__("invoice.paper.due_date")} - span.value #{saleInvoice.dueDateFormatted} - - div.invoice__table - table - thead - tr - th.item #{__("item_entry.paper.item_name")} - th.rate #{__("item_entry.paper.rate")} - th.quantity #{__("item_entry.paper.quantity")} - th.total #{__("item_entry.paper.total")} - tbody - each entry in saleInvoice.entries - tr - td.item - div.title=entry.item.name - span.description=entry.description - td.rate=entry.rate - td.quantity=entry.quantity - td.total=entry.amount - - div.invoice__table-after - div.invoice__table-total - table - tbody - tr.subtotal - td #{__('invoice.paper.subtotal')} - td #{saleInvoice.subtotalFormatted} - each tax in saleInvoice.taxes - tr.tax_line - td #{tax.name} [#{tax.taxRate}%] - td #{tax.taxRateAmountFormatted} - tr.total - td #{__('invoice.paper.total')} - td #{saleInvoice.totalFormatted} - tr.payment-amount - td #{__('invoice.paper.payment_amount')} - td #{saleInvoice.paymentAmountFormatted} - tr.blanace-due - td #{__('invoice.paper.balance_due')} - td #{saleInvoice.dueAmountFormatted} - - div.invoice__footer - if saleInvoice.termsConditions - div.invoice__conditions - h3 #{__("invoice.paper.conditions_title")} - p #{saleInvoice.termsConditions} - - if saleInvoice.invoiceMessage - div.invoice__notes - h3 #{__("invoice.paper.notes_title")} - p #{saleInvoice.invoiceMessage} \ No newline at end of file diff --git a/packages/server/resources/views/modules/invoice-standard.pug b/packages/server/resources/views/modules/invoice-standard.pug new file mode 100644 index 000000000..d5b172a8d --- /dev/null +++ b/packages/server/resources/views/modules/invoice-standard.pug @@ -0,0 +1,244 @@ +extends ../PaperTemplateLayout.pug + +block head + - var prefix = 'bc' + style. + .#{prefix}-root { + background-color: #fff; + color: #111; + padding: 24px 30px; + font-size: 12px; + position: relative; + box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color); + } + .#{prefix}-big-title { + font-size: 60px; + margin: 0; + line-height: 1; + margin-bottom: 25px; + font-weight: 500; + color: #333; + } + .#{prefix}-logo-wrap { + height: 120px; + width: 120px; + position: absolute; + right: 26px; + top: 26px; + border-radius: 5px; + overflow: hidden; + } + .#{prefix}-details { + display: flex; + flex-direction: column; + gap: 4px; + margin-bottom: 24px; + } + .#{prefix}-detail { + display: flex; + flex-direction: row; + gap: 12px; + } + .#{prefix}-detail__label { + min-width: 120px; + color: #333; + } + .#{prefix}-detail__value { + /* Styles for detail values */ + } + .#{prefix}-address-root { + box-sizing: border-box; + display: flex; + flex-flow: wrap; + -webkit-box-align: center; + align-items: center; + -webkit-box-pack: start; + justify-content: flex-start; + gap: 10px; + margin-bottom: 24px; + } + .#{prefix}-address-from { + flex: 1; + } + .#{prefix}-address-from__item { + /* Styles for items in the billed-from address */ + } + .#{prefix}-address-to { + flex: 1; + } + .#{prefix}-address-to__item { + /* Styles for items in the billed-to address */ + } + .#{prefix}-table { + width: 100%; + border-collapse: collapse; + text-align: left; + font-size: inherit; + } + .#{prefix}-table__header { + font-weight: 400; + border-bottom: 1px solid #000; + padding: 2px 10px; + color: #333; + } + .#{prefix}-table__header:first-of-type{ + padding-left: 0; + } + .#{prefix}-table__header:last-of-type{ + padding-right: 0; + } + .#{prefix}-table__header--right { + text-align: right; + } + .#{prefix}-table__cell { + border-bottom: 1px solid #F6F6F6; + padding: 12px 10px; + } + .#{prefix}-table__cell:first-of-type{ + padding-left: 0; + } + .#{prefix}-table__cell:last-of-type { + padding-right: 0; + } + .#{prefix}-table__cell--right { + text-align: right; + } + .#{prefix}-totals { + display: flex; + flex-direction: column; + margin-left: auto; + width: 300px; + margin-bottom: 24px; + } + .#{prefix}-totals__item { + display: flex; + padding: 4px 0; + } + .#{prefix}-totals__item--border-gray { + border-bottom: 1px solid #DADADA; + } + .#{prefix}-totals__item--border-dark { + border-bottom: 1px solid #000; + } + .#{prefix}-totals__item--font-weight-bold { + font-weight: bold; + /* Additional styles for total items with bold font weight */ + } + .#{prefix}-totals__item-label { + min-width: 160px; + } + .#{prefix}-totals__item-amount { + flex: 1 1 auto; + text-align: right; + } + .#{prefix}-paragraph { + margin-bottom: 20px; + } + .#{prefix}-paragraph__label { + color: #666; + } + .#{prefix}-paragraph__value { + /* Styles for values within the paragraph section */ + } +block content + //- block head + div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`) + //- Title and company logo + h1(class=`${prefix}-big-title`) Invoice + + if showCompanyLogo + div(class=`${prefix}-logo-wrap`) + img(alt="", src=companyLogo) + + //- Invoice details + div(class=`${prefix}-details`) + if showInvoiceNumber + div(class=`${prefix}-detail`) + div(class=`${prefix}-detail__label`) #{invoiceNumberLabel} + div(class=`${prefix}-detail__value`) #{invoiceNumber} + + if showDateIssue + div(class=`${prefix}-detail`) + div(class=`${prefix}-detail__label`) #{dateIssueLabel} + div(class=`${prefix}-detail__value`) #{dateIssue} + + if showDueDate + div(class=`${prefix}-detail`) + div(class=`${prefix}-detail__label`) #{dueDateLabel} + div(class=`${prefix}-detail__value`) #{dueDate} + + //- Address section + div(class=`${prefix}-address-root`) + if showBilledFromAddress + div(class=`${prefix}-address-from`) + strong #{companyName} + each item in billedFromAddres + div(class=`${prefix}-address-from__item`) #{item} + + if showBillingToAddress + div(class=`${prefix}-address-to`) + strong #{billedToLabel} + each item in billedToAddress + div(class=`${prefix}-address-to__item`) #{item} + + //- Invoice table + table(class=`${prefix}-table`) + thead + tr + th(class=`${prefix}-table__header`) #{lineItemLabel} + th(class=`${prefix}-table__header`) #{lineDescriptionLabel} + th(class=`${prefix}-table__header ${prefix}-table__header--right`) #{lineRateLabel} + th(class=`${prefix}-table__header ${prefix}-table__header--right`) #{lineTotalLabel} + tbody + each line in lines + tr + td(class=`${prefix}-table__cell`) #{line.item} + td(class=`${prefix}-table__cell`) #{line.description} + td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.rate} + td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.total} + + //- Totals section + div(class=`${prefix}-totals`) + if showSubtotal + div(class=`${prefix}-totals__item ${prefix}-totals__item--border-gray`) + div(class=`${prefix}-totals__item-label`) #{subtotalLabel} + div(class=`${prefix}-totals__item-amount`) #{subtotal} + + if showDiscount + div(class=`${prefix}-totals__item`) + div(class=`${prefix}-totals__item-label`) #{discountLabel} + div(class=`${prefix}-totals__item-amount`) #{discount} + + if showTaxes + each tax in taxes + div(class=`${prefix}-totals__item`) + div(class=`${prefix}-totals__item-label`) #{tax.label} + div(class=`${prefix}-totals__item-amount`) #{tax.amount} + + if showTotal + div(class=`${prefix}-totals__item ${prefix}-totals__item--border-dark ${prefix}-totals__item--font-weight-bold`) + div(class=`${prefix}-totals__item-label`) #{totalLabel} + div(class=`${prefix}-totals__item-amount`) #{total} + + if showPaymentMade + div(class=`${prefix}-totals__item`) + div(class=`${prefix}-totals__item-label`) #{paymentMadeLabel} + div(class=`${prefix}-totals__item-amount`) #{paymentMade} + + if showBalanceDue + div(class=`${prefix}-totals__item ${prefix}-totals__item--border-dark ${prefix}-totals__item--font-weight-bold`) + div(class=`${prefix}-totals__item-label`) #{balanceDueLabel} + div(class=`${prefix}-totals__item-amount`) #{balanceDue} + + //- Footer section + if showTermsConditions + div(class=`${prefix}-paragraph`) + if termsConditionsLabel + div(class=`${prefix}-paragraph__label`) #{termsConditionsLabel} + div(class=`${prefix}-paragraph__value`) #{termsConditions} + + if showStatement + div(class=`${prefix}-paragraph`) + if statementLabel + div(class=`${prefix}-paragraph__label`) #{statementLabel} + div(class=`${prefix}-paragraph__value`) #{statement} diff --git a/packages/server/src/interfaces/SaleInvoice.ts b/packages/server/src/interfaces/SaleInvoice.ts index 03827d229..4493782b8 100644 --- a/packages/server/src/interfaces/SaleInvoice.ts +++ b/packages/server/src/interfaces/SaleInvoice.ts @@ -45,6 +45,11 @@ export interface ISaleInvoice { subtotal: number; subtotalLocal: number; subtotalExludingTax: number; + + termsConditions: string; + invoiceMessage: string; + + pdfTemplateId?: number; } export interface ISaleInvoiceDTO { @@ -217,3 +222,83 @@ export interface ISaleInvoiceMailSent { saleInvoiceId: number; messageOptions: SendInvoiceMailDTO; } + + +// Invoice Pdf Document +export interface InvoicePdfLine { + item: string; + description: string; + rate: string; + quantity: string; + total: string; +} + +export interface InvoicePdfTax { + label: string; + amount: string; +} + +export interface InvoicePdfTemplateAttributes { + primaryColor: string; + secondaryColor: string; + + companyName: string; + + showCompanyLogo: boolean; + companyLogo: string; + + dueDate: string; + dueDateLabel: string; + showDueDate: boolean; + + dateIssue: string; + dateIssueLabel: string; + showDateIssue: boolean; + + invoiceNumberLabel: string; + invoiceNumber: string; + showInvoiceNumber: boolean; + + showBillingToAddress: boolean; + showBilledFromAddress: boolean; + billedToLabel: string; + + lineItemLabel: string; + lineDescriptionLabel: string; + lineRateLabel: string; + lineTotalLabel: string; + + totalLabel: string; + subtotalLabel: string; + discountLabel: string; + paymentMadeLabel: string; + balanceDueLabel: string; + + showTotal: boolean; + showSubtotal: boolean; + showDiscount: boolean; + showTaxes: boolean; + showPaymentMade: boolean; + showDueAmount: boolean; + showBalanceDue: boolean; + + total: string; + subtotal: string; + discount: string; + paymentMade: string; + balanceDue: string; + + termsConditionsLabel: string; + showTermsConditions: boolean; + termsConditions: string; + + lines: InvoicePdfLine[]; + taxes: InvoicePdfTax[]; + + statementLabel: string; + showStatement: boolean; + statement: string; + + billedToAddress: string[]; + billedFromAddres: string[]; +} \ No newline at end of file diff --git a/packages/server/src/models/PdfTemplate.ts b/packages/server/src/models/PdfTemplate.ts index 67f53ac9d..91920a4dd 100644 --- a/packages/server/src/models/PdfTemplate.ts +++ b/packages/server/src/models/PdfTemplate.ts @@ -15,6 +15,9 @@ export class PdfTemplate extends TenantModel { return ['createdAt', 'updatedAt']; } + /** + * Json schema. + */ static get jsonSchema() { return { type: 'object', diff --git a/packages/server/src/services/ChromiumlyTenancy/ChromiumlyTenancy.ts b/packages/server/src/services/ChromiumlyTenancy/ChromiumlyTenancy.ts index 21df50a53..3e8a37daf 100644 --- a/packages/server/src/services/ChromiumlyTenancy/ChromiumlyTenancy.ts +++ b/packages/server/src/services/ChromiumlyTenancy/ChromiumlyTenancy.ts @@ -20,6 +20,10 @@ export class ChromiumlyTenancy { properties?: PageProperties, pdfFormat?: PdfFormat ) { - return this.htmlConvert.convert(tenantId, content, properties, pdfFormat); + const parsedProperties = { + margins: { top: 0, bottom: 0, left: 0, right: 0 }, + ...properties, + } + return this.htmlConvert.convert(tenantId, content, parsedProperties, pdfFormat); } } diff --git a/packages/server/src/services/Items/ItemsEntriesService.ts b/packages/server/src/services/Items/ItemsEntriesService.ts index f307087b2..bab37c367 100644 --- a/packages/server/src/services/Items/ItemsEntriesService.ts +++ b/packages/server/src/services/Items/ItemsEntriesService.ts @@ -238,7 +238,7 @@ export default class ItemsEntriesService { * Sets the cost/sell accounts to the invoice entries. */ public setItemsEntriesDefaultAccounts(tenantId: number) { - return async (entries: IItemEntry[]) => { + return async (entries: IItemEntry[]) => { const { Item } = this.tenancy.models(tenantId); const entriesItemsIds = entries.map((e) => e.itemId); diff --git a/packages/server/src/services/PdfTemplate/AssignPdfTemplateDefault.ts b/packages/server/src/services/PdfTemplate/AssignPdfTemplateDefault.ts index ac038c79d..04a7c5e4f 100644 --- a/packages/server/src/services/PdfTemplate/AssignPdfTemplateDefault.ts +++ b/packages/server/src/services/PdfTemplate/AssignPdfTemplateDefault.ts @@ -33,6 +33,14 @@ export class AssignPdfTemplateDefault { return this.uow.withTransaction( tenantId, async (trx?: Knex.Transaction) => { + // Triggers `onPdfTemplateAssigningDefault` event. + await this.eventPublisher.emitAsync( + events.pdfTemplate.onAssigningDefault, + { + tenantId, + templateId, + } + ); await PdfTemplate.query(trx) .where('resource', oldPdfTempalte.resource) .patch({ default: false }); @@ -41,6 +49,7 @@ export class AssignPdfTemplateDefault { .findById(templateId) .patch({ default: true }); + // Triggers `onPdfTemplateAssignedDefault` event. await this.eventPublisher.emitAsync( events.pdfTemplate.onAssignedDefault, { diff --git a/packages/server/src/services/PdfTemplate/BrandingTemplateDTOTransformer.ts b/packages/server/src/services/PdfTemplate/BrandingTemplateDTOTransformer.ts new file mode 100644 index 000000000..379f097ca --- /dev/null +++ b/packages/server/src/services/PdfTemplate/BrandingTemplateDTOTransformer.ts @@ -0,0 +1,39 @@ +import * as R from 'ramda'; +import HasTenancyService from '../Tenancy/TenancyService'; +import { Inject, Service } from 'typedi'; + +@Service() +export class BrandingTemplateDTOTransformer { + @Inject() + private tenancy: HasTenancyService; + + /** + * Associates the default branding template id. + * @param {number} tenantId + * @param {string} resource + * @param {Record} object + * @param {string} attributeName + * @returns + */ + public assocDefaultBrandingTemplate = ( + tenantId: number, + resource: string, + ) => async (object: Record) => { + const { PdfTemplate } = this.tenancy.models(tenantId); + const attributeName = 'pdfTemplateId'; + + const defaultTemplate = await PdfTemplate.query().findOne({ + resource, + default: true, + }); + console.log(defaultTemplate); + + if (!defaultTemplate) { + return object; + } + return { + ...object, + [attributeName]: defaultTemplate.id, + }; + }, +} diff --git a/packages/server/src/services/Sales/Invoices/CommandSaleInvoiceDTOTransformer.ts b/packages/server/src/services/Sales/Invoices/CommandSaleInvoiceDTOTransformer.ts index 8013fe7af..e6a080c04 100644 --- a/packages/server/src/services/Sales/Invoices/CommandSaleInvoiceDTOTransformer.ts +++ b/packages/server/src/services/Sales/Invoices/CommandSaleInvoiceDTOTransformer.ts @@ -19,6 +19,7 @@ import { formatDateFields } from 'utils'; import { ItemEntriesTaxTransactions } from '@/services/TaxRates/ItemEntriesTaxTransactions'; import { assocItemEntriesDefaultIndex } from '@/services/Items/utils'; import { ItemEntry } from '@/models'; +import { BrandingTemplateDTOTransformer } from '@/services/PdfTemplate/BrandingTemplateDTOTransformer'; @Service() export class CommandSaleInvoiceDTOTransformer { @@ -40,6 +41,9 @@ export class CommandSaleInvoiceDTOTransformer { @Inject() private taxDTOTransformer: ItemEntriesTaxTransactions; + @Inject() + private brandingTemplatesTransformer: BrandingTemplateDTOTransformer; + /** * Transformes the create DTO to invoice object model. * @param {ISaleInvoiceCreateDTO} saleInvoiceDTO - Sale invoice DTO. @@ -113,11 +117,19 @@ export class CommandSaleInvoiceDTOTransformer { userId: authorizedUser.id, } as ISaleInvoice; + const initialAsyncDTO = await composeAsync( + // Assigns the default branding template id to the invoice DTO. + this.brandingTemplatesTransformer.assocDefaultBrandingTemplate( + tenantId, + 'SaleInvoice' + ) + )(initialDTO); + return R.compose( this.taxDTOTransformer.assocTaxAmountWithheldFromEntries, this.branchDTOTransform.transformDTO(tenantId), this.warehouseDTOTransform.transformDTO(tenantId) - )(initialDTO); + )(initialAsyncDTO); } /** diff --git a/packages/server/src/services/Sales/Invoices/SaleInvoicePdf.ts b/packages/server/src/services/Sales/Invoices/SaleInvoicePdf.ts index 2bbe7e003..0146478b3 100644 --- a/packages/server/src/services/Sales/Invoices/SaleInvoicePdf.ts +++ b/packages/server/src/services/Sales/Invoices/SaleInvoicePdf.ts @@ -2,9 +2,16 @@ import { Inject, Service } from 'typedi'; import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy'; import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable'; import { GetSaleInvoice } from './GetSaleInvoice'; +import HasTenancyService from '@/services/Tenancy/TenancyService'; +import { transformInvoiceToPdfTemplate } from './utils'; +import { InvoicePdfTemplateAttributes } from '@/interfaces'; +import { SaleInvoicePdfTemplate } from './SaleInvoicePdfTemplate'; @Service() export class SaleInvoicePdf { + @Inject() + private tenancy: HasTenancyService; + @Inject() private chromiumlyTenancy: ChromiumlyTenancy; @@ -14,6 +21,9 @@ export class SaleInvoicePdf { @Inject() private getInvoiceService: GetSaleInvoice; + @Inject() + private invoiceBrandingTemplateService: SaleInvoicePdfTemplate; + /** * Retrieve sale invoice pdf content. * @param {number} tenantId - Tenant Id. @@ -24,19 +34,54 @@ export class SaleInvoicePdf { tenantId: number, invoiceId: number ): Promise { - const saleInvoice = await this.getInvoiceService.getSaleInvoice( + const brandingAttributes = await this.getInvoiceBrandingAttributes( tenantId, invoiceId ); const htmlContent = await this.templateInjectable.render( tenantId, - 'modules/invoice-regular', - { - saleInvoice, - } + 'modules/invoice-standard', + brandingAttributes ); - return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent, { - margins: { top: 0, bottom: 0, left: 0, right: 0 }, - }); + // Converts the given html content to pdf document. + return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent); + } + + /** + * Retrieves the branding attributes of the given sale invoice. + * @param {number} tenantId + * @param {number} invoiceId + * @returns {Promise} + */ + async getInvoiceBrandingAttributes( + tenantId: number, + invoiceId: number + ): Promise { + const { PdfTemplate } = this.tenancy.models(tenantId); + + const invoice = await this.getInvoiceService.getSaleInvoice( + tenantId, + invoiceId + ); + // Retrieve the invoice template id of not found get the default template id. + const templateId = + invoice.pdfTemplateId ?? + ( + await PdfTemplate.query().findOne({ + resource: 'SaleInvoice', + default: true, + }) + )?.id; + // Getting the branding template attributes. + const brandingTemplate = + await this.invoiceBrandingTemplateService.getInvoicePdfTemplate( + tenantId, + templateId + ); + // Merge the branding template attributes with the invoice. + return { + ...brandingTemplate.attributes, + ...transformInvoiceToPdfTemplate(invoice), + }; } } diff --git a/packages/server/src/services/Sales/Invoices/SaleInvoicePdfTemplate.ts b/packages/server/src/services/Sales/Invoices/SaleInvoicePdfTemplate.ts new file mode 100644 index 000000000..563fd3a20 --- /dev/null +++ b/packages/server/src/services/Sales/Invoices/SaleInvoicePdfTemplate.ts @@ -0,0 +1,31 @@ +import { Inject, Service } from 'typedi'; +import { mergePdfTemplateWithDefaultAttributes } from './utils'; +import { GetPdfTemplate } from '@/services/PdfTemplate/GetPdfTemplate'; +import { defaultInvoicePdfTemplateAttributes } from './constants'; + +@Service() +export class SaleInvoicePdfTemplate { + @Inject() + private getPdfTemplateService: GetPdfTemplate; + + /** + * Retrieves the invoice pdf template. + * @param {number} tenantId + * @param {number} invoiceTemplateId + * @returns + */ + async getInvoicePdfTemplate(tenantId: number, invoiceTemplateId: number){ + const template = await this.getPdfTemplateService.getPdfTemplate( + tenantId, + invoiceTemplateId + ); + const attributes = mergePdfTemplateWithDefaultAttributes( + template.attributes, + defaultInvoicePdfTemplateAttributes + ); + return { + ...template, + attributes, + }; + } +} diff --git a/packages/server/src/services/Sales/Invoices/constants.ts b/packages/server/src/services/Sales/Invoices/constants.ts index 4ed0e6bbd..ffb1db409 100644 --- a/packages/server/src/services/Sales/Invoices/constants.ts +++ b/packages/server/src/services/Sales/Invoices/constants.ts @@ -158,3 +158,88 @@ export const SaleInvoicesSampleData = [ Description: 'Description', }, ]; + +export const defaultInvoicePdfTemplateAttributes = { + primaryColor: 'red', + secondaryColor: 'red', + + companyName: 'Bigcapital Technology, Inc.', + + showCompanyLogo: true, + companyLogo: '', + + dueDateLabel: 'Date due', + showDueDate: true, + + dateIssueLabel: 'Date of issue', + showDateIssue: true, + + // dateIssue, + invoiceNumberLabel: 'Invoice number', + showInvoiceNumber: true, + + // Address + showBillingToAddress: true, + showBilledFromAddress: true, + billedToLabel: 'Billed To', + + // Entries + lineItemLabel: 'Item', + lineDescriptionLabel: 'Description', + lineRateLabel: 'Rate', + lineTotalLabel: '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, + + discount: '0.00', + + // Footer paragraphs. + termsConditionsLabel: 'Terms & Conditions', + showTermsConditions: true, + + lines: [ + { + item: 'Simply dummy text', + description: 'Simply dummy text of the printing and typesetting', + rate: '1', + quantity: '1000', + total: '$1000.00', + }, + ], + taxes: [ + { label: 'Sample Tax1 (4.70%)', amount: '11.75' }, + { label: 'Sample Tax2 (7.00%)', amount: '21.74' }, + ], + + statementLabel: 'Statement', + showStatement: true, + billedToAddress: [ + 'Bigcapital Technology, Inc.', + '131 Continental Dr Suite 305 Newark,', + 'Delaware 19713', + 'United States', + '+1 762-339-5634', + 'ahmed@bigcapital.app', + ], + billedFromAddres: [ + '131 Continental Dr Suite 305 Newark,', + 'Delaware 19713', + 'United States', + '+1 762-339-5634', + 'ahmed@bigcapital.app', + ], +} + diff --git a/packages/server/src/services/Sales/Invoices/utils.ts b/packages/server/src/services/Sales/Invoices/utils.ts new file mode 100644 index 000000000..ed8235678 --- /dev/null +++ b/packages/server/src/services/Sales/Invoices/utils.ts @@ -0,0 +1,43 @@ +import { pickBy } from 'lodash'; +import { InvoicePdfTemplateAttributes, ISaleInvoice } from '@/interfaces'; + +export const mergePdfTemplateWithDefaultAttributes = ( + brandingTemplate?: Record, + defaultAttributes: Record = {} +) => { + const brandingAttributes = pickBy( + brandingTemplate, + (val, key) => val !== null && Object.keys(defaultAttributes).includes(key) + ); + + return { + ...defaultAttributes, + ...brandingAttributes, + }; +}; + +export const transformInvoiceToPdfTemplate = ( + invoice: ISaleInvoice +): Partial => { + return { + dueDate: invoice.dueDateFormatted, + dateIssue: invoice.invoiceDateFormatted, + invoiceNumber: invoice.invoiceNo, + + total: invoice.totalFormatted, + subtotal: invoice.subtotalFormatted, + paymentMade: invoice.paymentAmountFormatted, + balanceDue: invoice.balanceAmountFormatted, + + termsConditions: invoice.termsConditions, + statement: invoice.invoiceMessage, + + lines: invoice.entries.map((entry) => ({ + item: entry.item.name, + description: entry.description, + rate: entry.rateFormatted, + quantity: entry.quantityFormatted, + total: entry.totalFormatted, + })), + }; +}; diff --git a/packages/server/src/services/TemplateInjectable/TemplateInjectable.ts b/packages/server/src/services/TemplateInjectable/TemplateInjectable.ts index fd2c965a8..31e059ae3 100644 --- a/packages/server/src/services/TemplateInjectable/TemplateInjectable.ts +++ b/packages/server/src/services/TemplateInjectable/TemplateInjectable.ts @@ -17,7 +17,7 @@ export class TemplateInjectable { public async render( tenantId: number, filename: string, - options: Record + options: Record ) { const i18n = this.tenancy.i18n(tenantId); diff --git a/packages/webapp/src/components/Forms/Select.tsx b/packages/webapp/src/components/Forms/Select.tsx index c77c51ce2..93d6fc8ed 100644 --- a/packages/webapp/src/components/Forms/Select.tsx +++ b/packages/webapp/src/components/Forms/Select.tsx @@ -6,16 +6,14 @@ import styled from 'styled-components'; import clsx from 'classnames'; export function FSelect({ ...props }) { - const input = ({ activeItem, text, label, value }) => { - return ( - - ); - }; + const input = ({ activeItem, text, label, value }) => ( + + ); return