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 && (
+ }
+ minimal={true}
+ onClick={handleClose}
+ style={{ marginLeft: 'auto' }}
+ />
+ )}
+
+ );
+}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreview.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreview.tsx
new file mode 100644
index 000000000..8ae9a84a0
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreview.tsx
@@ -0,0 +1,12 @@
+import { Stack } from '@/components';
+import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader';
+import { InvoiceCustomizePreviewContent } from './InvoiceCustomizePreviewContent';
+
+export function InvoiceCustomizePreview() {
+ return (
+
+
+
+
+ );
+}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx
new file mode 100644
index 000000000..91df2434c
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx
@@ -0,0 +1,10 @@
+import { Box } from '@/components';
+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
new file mode 100644
index 000000000..6ca316672
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.module.scss
@@ -0,0 +1,14 @@
+
+.root {
+ flex: 1;
+ min-width: 165px;
+ max-width: 185px;
+}
+
+.content{
+ padding: 5px;
+}
+
+.tabsList{
+ width: 100%;
+}
\ 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
new file mode 100644
index 000000000..c5bcabcb0
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.tsx
@@ -0,0 +1,29 @@
+import { Box } from '@/components';
+import { Tab, Tabs } from '@blueprintjs/core';
+import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader';
+import styles from './InvoiceCustomizeTabs.module.scss';
+import {
+ InvoiceCustomizeTabsEnum,
+ useInvoiceCustomizeTabsController,
+} from './InvoiceCustomizeTabsController';
+
+export function InvoiceCustomizeTabs() {
+ const { setCurrentTabId } = useInvoiceCustomizeTabsController();
+
+ const handleChange = (value: InvoiceCustomizeTabsEnum) => {
+ setCurrentTabId(value);
+ };
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabsController.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabsController.tsx
new file mode 100644
index 000000000..d58fa2a1a
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabsController.tsx
@@ -0,0 +1,46 @@
+import React, { createContext, useContext, useState } from 'react';
+
+export enum InvoiceCustomizeTabsEnum {
+ General = 'general',
+ Items = 'items',
+ Totals = 'totals'
+}
+
+const DEFAULT_TAB_ID = InvoiceCustomizeTabsEnum.General;
+
+interface InvoiceCustomizeTabsControllerValue {
+ currentTabId: InvoiceCustomizeTabsEnum;
+ setCurrentTabId: React.Dispatch<
+ React.SetStateAction
+ >;
+}
+
+const InvoiceCustomizeTabsController = createContext(
+ {} as InvoiceCustomizeTabsControllerValue,
+);
+
+export const useInvoiceCustomizeTabsController = () => {
+ return useContext(InvoiceCustomizeTabsController);
+};
+
+interface InvoiceCustomizeTabsControllerProps {
+ children: React.ReactNode;
+}
+
+export const InvoiceCustomizeTabsControllerProvider = ({
+ children,
+}: InvoiceCustomizeTabsControllerProps) => {
+ const [currentTabId, setCurrentTabId] =
+ useState(DEFAULT_TAB_ID);
+
+ const value = {
+ currentTabId,
+ setCurrentTabId,
+ };
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizerForm.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizerForm.tsx
new file mode 100644
index 000000000..c99bcfb2a
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizerForm.tsx
@@ -0,0 +1,27 @@
+import { Formik, Form } from 'formik';
+import * as Yup from 'yup';
+import React from 'react';
+
+const validationSchema = Yup.object().shape({
+ invoiceNumber: Yup.string().required('Invoice number is required'),
+ customerName: Yup.string().required('Customer name is required'),
+ amount: Yup.number()
+ .required('Amount is required')
+ .positive('Amount must be positive'),
+});
+
+interface InvoiceCustomizeFormProps {
+ children: React.ReactNode;
+}
+
+export function InvoiceCustomizeForm({ children }: InvoiceCustomizeFormProps) {
+ 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
new file mode 100644
index 000000000..4989f86f2
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.module.scss
@@ -0,0 +1,105 @@
+
+.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;
+ position: relative;
+}
+
+.bigTitle{
+ font-size: 60px;
+ margin: 0;
+ LINE-HEIGHT: 1;
+ MARGIN-BOTTOM: 20px;
+}
+
+.details {
+ margin-bottom: 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+.detail {
+ display: flex;
+ flex-direction: row;
+ gap: 10px;
+}
+.detailLabel {
+ min-width: 120px;
+}
+
+.addressRoot{
+ display: flex;
+ flex-direction: row;
+}
+
+.addressBillTo{
+ flex: 1;
+}
+
+.addressFrom{
+ flex: 1;
+}
+
+.table :global {
+ margin-top: 40px;
+ width: 100%;
+ border-collapse: collapse;
+ text-align: left;
+
+ thead th{
+ font-weight: 400;
+ border-bottom: 1px solid #000;
+ }
+
+ tbody{
+
+ tr {
+
+ }
+ td{
+ border: 1px solid #F6F6F6;
+ padding: 10px 0;
+ }
+ }
+}
+
+.totals{
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 40px;
+ margin-left: auto;
+}
+.totalsItem{
+ display: flex;
+ padding: 6px 0;
+}
+.totalsItemLabel{
+ min-width: 160px;
+}
+
+.logoWrap{
+ height: 120px;
+ width: 120px;
+ position: absolute;
+ right: 20px;
+ top: 20px;
+
+ img{
+ max-width: 100%;
+ }
+}
+
+
+.footer{
+
+}
+
+.paragraph{
+ margin-bottom: 20px;
+}
+
+.paragraphLabel{
+ color: #333333;
+}
\ 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
new file mode 100644
index 000000000..eac6baec6
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx
@@ -0,0 +1,135 @@
+import styles from './PaperTemplate.module.scss';
+
+export function PaperTemplate() {
+ return (
+
+
+
Invoice
+
+
+
+
+
+
+
+
+
Invoice number
+
346D3D40-0001
+
+
+
+
Date of issue
+
September 3, 2024
+
+
+
+
Date due
+
October 3, 2024
+
+
+
+
+
+ Bigcapital Technology, Inc.
+ 131 Continental Dr Suite 305 Newark,
+
+ Delaware 19713
+
+ United States
+
+ +1 762-339-5634
+
+ ahmed@bigcapital.app
+
+
+
+ Billed To
+ Bigcapital Technology, Inc.
+ 131 Continental Dr Suite 305 Newark,
+
+ Delaware 19713
+
+ United States
+
+ +1 762-339-5634
+
+ ahmed@bigcapital.app
+
+
+
+
+
+
+ Item
+ Description
+ Rate
+ Total
+
+
+
+
+
+ Simply dummy text
+ Simply dummy text of the printing and typesetting
+ 1 X $100,00
+ $100,00
+
+
+
+
+
+
+
+
+
+
+
+
Sample Tax1 (4.70%)
+
11.75
+
+
+
+
Sample Tax2 (7.00%)
+
21.00
+
+
+
+
+
+
Payment Made
+
100.00
+
+
+
+
Balance Due
+
$562.75
+
+
+
+
+
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.
+
+
+
+
+
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.
+
+
+
+ );
+}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx
index 6f5ce73ce..be63b7637 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx
@@ -7,6 +7,11 @@ import {
NavbarGroup,
Intent,
Alignment,
+ Menu,
+ MenuItem,
+ Popover,
+ PopoverInteractionKind,
+ Position,
} from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import {
@@ -32,6 +37,8 @@ import withSettingsActions from '@/containers/Settings/withSettingsActions';
import { compose } from '@/utils';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import { DialogsName } from '@/constants/dialogs';
+import withDrawerActions from '@/containers/Drawer/withDrawerActions';
+import { DRAWERS } from '@/constants/drawers';
/**
* Invoices table actions bar.
@@ -51,6 +58,9 @@ function InvoiceActionsBar({
// #withDialogsActions
openDialog,
+
+ // #withDrawerActions
+ openDrawer,
}) {
const history = useHistory();
@@ -97,6 +107,11 @@ function InvoiceActionsBar({
downloadExportPdf({ resource: 'SaleInvoice' });
};
+ // Handles the invoice customize button click.
+ const handleCustomizeBtnClick = () => {
+ openDrawer(DRAWERS.INVOICE_CUSTOMIZE);
+ };
+
return (
@@ -164,6 +179,25 @@ function InvoiceActionsBar({
+
+
+
+ }
+ >
+ } minimal={true} />
+
+
}
@@ -184,4 +218,5 @@ export default compose(
invoicesTableSize: invoiceSettings?.tableSize,
})),
withDialogActions,
+ withDrawerActions,
)(InvoiceActionsBar);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f26282a66..354ba01bb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -687,6 +687,9 @@ importers:
react-body-classname:
specifier: ^1.3.1
version: 1.3.1(react@18.3.1)
+ react-colorful:
+ specifier: ^5.6.1
+ version: 5.6.1(react-dom@18.3.1)(react@18.3.1)
react-content-loader:
specifier: ^6.0.1
version: 6.2.1(react@18.3.1)
@@ -21277,6 +21280,16 @@ packages:
- react
dev: false
+ /react-colorful@5.6.1(react-dom@18.3.1)(react@18.3.1):
+ resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+ dependencies:
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ dev: false
+
/react-content-loader@6.2.1(react@18.3.1):
resolution: {integrity: sha512-6ONbFX+Hi3SHuP66JB8CPvJn372pj+qwltJV0J8z/8MFrq98I1cbFdZuhDWeQXu3CFxiiDTXJn7DFxx2ZvrO7g==}
engines: {node: '>=10'}
From f5e9485a12e3638d0c845bbe400790b4b98ed74f Mon Sep 17 00:00:00 2001
From: Ahmed Bouhuolia
Date: Sun, 8 Sep 2024 17:34:19 +0200
Subject: [PATCH 02/32] feat: wip invoice customize
---
.../InvoiceCustomize/ColorField.module.scss | 5 +-
.../Invoices/InvoiceCustomize/ColorField.tsx | 62 ++++++++++++++++--
.../Invoices/InvoiceCustomize/FColorField.tsx | 65 +++++++++++++++++--
.../InvoiceCustomizeContent.tsx | 2 +-
.../InvoiceCustomizeDrawer.tsx | 2 +-
.../InvoiceCustomizeFields.module.scss | 12 ++++
.../InvoiceCustomizeFields.tsx | 45 +++++++++++--
.../InvoiceCustomizeGeneralFields.tsx | 44 ++++++++++---
.../InvoiceCustomizeHeader.module.scss | 1 +
.../InvoiceCustomizeHeader.tsx | 2 +-
.../InvoiceCustomizePreview.tsx | 23 ++++++-
.../InvoiceCustomizePreviewContent.tsx | 2 +-
.../InvoiceCustomizeTabs.module.scss | 7 ++
.../InvoiceCustomize/InvoiceCustomizeTabs.tsx | 14 ++--
.../InvoiceCutomizeContentFields.tsx | 37 +++++++++++
15 files changed, 287 insertions(+), 36 deletions(-)
create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.module.scss
index a018fb5c9..f0f737129 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.module.scss
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.module.scss
@@ -8,7 +8,8 @@
.colorPicker{
background-color: rgb(103, 114, 229);
border-radius: 3px;
- height: 14px;
- width: 14px;
+ height: 16px;
+ width: 16px;
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
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
index 3f18db7e1..719fc714c 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.tsx
@@ -1,29 +1,79 @@
+import { useState } from 'react';
+import clsx from 'classnames';
import {
+ IInputGroupProps,
InputGroup,
+ IPopoverProps,
Popover,
PopoverInteractionKind,
Position,
} from '@blueprintjs/core';
-import { useState } from 'react';
import { HexColorPicker } from 'react-colorful';
+import { useUncontrolled } from '@/hooks/useUncontrolled';
+import { Box, BoxProps } from '@/components';
import styles from './ColorField.module.scss';
-export function ColorField() {
- const [color, setColor] = useState('#aabbcc');
+export interface ColorFieldProps {
+ value?: string;
+ initialValue?: string;
+ onChange?: (value: string) => void;
+ popoverProps?: Partial;
+ inputProps?: Partial;
+ pickerProps?: Partial;
+ pickerWrapProps?: Partial;
+}
+
+export function ColorField({
+ value,
+ initialValue,
+ onChange,
+ popoverProps,
+ inputProps,
+ pickerWrapProps,
+ pickerProps,
+}: ColorFieldProps) {
+ const [_value, handleChange] = useUncontrolled({
+ value,
+ initialValue,
+ onChange,
+ finalValue: '',
+ });
+ const [isOpen, setIsOpen] = useState(false);
+
+ const handleClose = () => {
+ setIsOpen(false);
+ };
return (
}
+ content={ }
position={Position.BOTTOM}
interactionKind={PopoverInteractionKind.CLICK}
modifiers={{
offset: { offset: '0, 4' },
}}
+ onClose={handleClose}
+ isOpen={isOpen}
minimal
+ {...popoverProps}
>
}
- className={styles.field}
+ value={_value}
+ leftElement={
+
+ setIsOpen((oldValue) => !oldValue)}
+ style={{ backgroundColor: _value }}
+ className={clsx(styles.colorPicker, pickerProps?.className)}
+ {...pickerProps}
+ />
+
+ }
+ {...inputProps}
+ className={clsx(styles.field, inputProps?.className)}
/>
);
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/FColorField.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/FColorField.tsx
index fc2008a77..35840f7cb 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/FColorField.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/FColorField.tsx
@@ -1,9 +1,64 @@
-import { ColorField } from './ColorField';
+import React from 'react';
+import { getIn, FieldConfig, FieldProps } from 'formik';
+import { Intent } from '@blueprintjs/core';
+import { Field } from '@blueprintjs-formik/core';
+import { ColorField, ColorFieldProps } from './ColorField';
-interface FColorFieldProps {
- name: string;
+interface ColorFieldInputGroupProps
+ extends Omit,
+ ColorFieldProps {}
+
+export interface ColorFieldToInputProps
+ extends Omit,
+ ColorFieldProps {}
+
+/**
+ * Transforms field props to input group props for ColorField.
+ * @param {ColorFieldToInputProps}
+ * @returns {ColorFieldProps}
+ */
+function fieldToColorFieldInputGroup({
+ field: { onBlur: onFieldBlur, onChange: onFieldChange, value, ...field },
+ form: { touched, errors, setFieldValue },
+ onChange,
+ ...props
+}: ColorFieldToInputProps): ColorFieldProps {
+ const fieldError = getIn(errors, field.name);
+ const showError = getIn(touched, field.name) && !!fieldError;
+
+ return {
+ inputProps: {
+ intent: showError ? Intent.DANGER : Intent.NONE,
+ },
+ value,
+ onChange:
+ onChange ??
+ function (value: string) {
+ setFieldValue(field.name, value);
+ },
+ ...field,
+ ...props,
+ };
}
-export function FColorField({ name }: FColorFieldProps) {
- return ;
+/**
+ * Transforms field props to input group props for ColorField.
+ * @param {ColorFieldToInputProps} props -
+ * @returns {JSX.Element}
+ */
+function ColorFieldToInputGroup({
+ ...props
+}: ColorFieldToInputProps): JSX.Element {
+ return ;
+}
+
+/**
+ * Input group Blueprint component binded with Formik for ColorField.
+ * @param {ColorFieldInputGroupProps}
+ * @returns {JSX.Element}
+ */
+export function FColorInput({
+ ...props
+}: ColorFieldInputGroupProps): JSX.Element {
+ return ;
}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx
index 00f1933b2..31ce04ad7 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx
@@ -9,7 +9,7 @@ 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
index e4e8dbe2c..04aa9143a 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer.tsx
@@ -1,7 +1,6 @@
// @ts-nocheck
import React from 'react';
import * as R from 'ramda';
-
import { Drawer, DrawerSuspense } from '@/components';
import withDrawers from '@/containers/Drawer/withDrawers';
@@ -25,6 +24,7 @@ function InvoiceCustomizeDrawerRoot({
name={name}
size={'100%'}
>
+
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 4159c65a9..7222b3187 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.module.scss
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.module.scss
@@ -4,4 +4,16 @@
.mainFields{
flex: 1;
+}
+.fieldGroup {
+
+ :global .bp4-form-content{
+ margin-left: auto;
+ }
+}
+
+.footerActions{
+ padding: 10px 16px;
+ border-top: 1px solid #d9d9d9;
+ flex-flow: row-reverse;
}
\ 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
index a50011509..094e308dc 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx
@@ -1,9 +1,16 @@
-import { Box, Group } from '@/components';
+// @ts-nocheck
+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';
+import withDrawerActions from '@/containers/Drawer/withDrawerActions';
+import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
+import { useFormikContext } from 'formik';
+import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields';
export function InvoiceCustomizeFields() {
return (
@@ -17,10 +24,40 @@ export function InvoiceCustomizeFields() {
export function InvoiceCustomizeFieldsMain() {
const { currentTabId } = useInvoiceCustomizeTabsController();
return (
-
+
- {currentTabId === 'general' && }
-
+
+ {currentTabId === 'general' && }
+ {currentTabId === 'content' && }
+
+
+
+
);
}
+
+function InvoiceCustomizeFooterActionsRoot({ closeDrawer }) {
+ const { name } = useDrawerContext();
+ const { submitForm } = useFormikContext();
+
+ const handleSubmitBtnClick = () => {
+ submitForm();
+ };
+ const handleCancelBtnClick = () => {
+ closeDrawer(name);
+ };
+
+ return (
+
+
+ Save
+
+ Cancel
+
+ );
+}
+
+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
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 (
-
+
Save
Cancel
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() {
-
+
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({
+
+
+
+ }
+ >
+ } minimal={true} />
+
+
}
@@ -187,4 +220,5 @@ export default compose(
estimatesTableSize: estimatesSettings?.tableSize,
})),
withDialogActions,
+ withDrawerActions,
)(EstimateActionsBar);
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx
new file mode 100644
index 000000000..d88336974
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx
@@ -0,0 +1,74 @@
+import React from 'react';
+import { Box, Group } from '@/components';
+import { InvoiceCustomizeProvider } from './InvoiceCustomizeProvider';
+import {
+ InvoiceCustomizeForm,
+ InvoiceCustomizeFormProps,
+} from './InvoiceCustomizerForm';
+import { InvoiceCustomizeTabsControllerProvider } from './InvoiceCustomizeTabsController';
+import { InvoiceCustomizeFields } from './InvoiceCustomizeFields';
+import { InvoiceCustomizePreview } from './InvoiceCustomizePreview';
+import { extractChildren } from '@/utils/extract-children';
+
+export interface InvoiceCustomizeProps extends InvoiceCustomizeFormProps {
+ children?: React.ReactNode;
+}
+
+export function InvoiceCustomize({
+ initialValues,
+ validationSchema,
+ onSubmit,
+ children,
+}: InvoiceCustomizeProps) {
+ const PaperTemplate = React.useMemo(
+ () => extractChildren(children, InvoiceCustomize.PaperTemplate),
+ [children],
+ );
+ const CustomizeTabs = React.useMemo(
+ () => extractChildren(children, InvoiceCustomize.FieldsTab),
+ [children],
+ );
+
+ const value = { PaperTemplate, CustomizeTabs };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export interface InvoiceCustomizePaperTemplateProps {
+ children?: React.ReactNode;
+}
+
+InvoiceCustomize.PaperTemplate = ({
+ children,
+}: InvoiceCustomizePaperTemplateProps) => {
+ return {children} ;
+};
+
+export interface InvoiceCustomizeContentProps {
+ id: string;
+ label: string;
+ children?: React.ReactNode;
+}
+
+InvoiceCustomize.FieldsTab = ({
+ id,
+ label,
+ children,
+}: InvoiceCustomizeContentProps) => {
+ return {children} ;
+};
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx
index 31ce04ad7..e17963eb9 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx
@@ -1,21 +1,65 @@
-import { Box, Group } from '@/components';
-import { InvoiceCustomizePreview } from './InvoiceCustomizePreview';
-import { InvoiceCustomizeFields } from './InvoiceCustomizeFields';
-import { InvoiceCustomizeForm } from './InvoiceCustomizerForm';
+import React from 'react';
+import { Box } from '@/components';
import { Classes } from '@blueprintjs/core';
-import { InvoiceCustomizeTabsControllerProvider } from './InvoiceCustomizeTabsController';
+import { InvoicePaperTemplate } from './InvoicePaperTemplate';
+import { InvoiceCustomize } from './InvoiceCustomize';
+import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields';
+import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields';
+
+interface InvoiceCustomizeValues {
+ invoiceNumber?: string;
+ invoiceNumberLabel?: string;
+
+ dateIssue?: string;
+ dateIssueLabel?: string;
+
+ dueDate?: string;
+ dueDateLabel?: string;
+
+ companyName?: string;
+
+ bigtitle?: string;
+
+ itemRateLabel?: string;
+ itemQuantityLabel?: string;
+ itemTotalLabel?: string;
+
+ // Totals
+ showDueAmount?: boolean;
+ showDiscount?: boolean;
+ showPaymentMade?: boolean;
+ showTaxes?: boolean;
+ showSubtotal?: boolean;
+ showTotal?: boolean;
+ showBalanceDue?: boolean;
+
+ paymentMadeLabel?: string;
+ discountLabel?: string;
+ subtotalLabel?: string;
+ totalLabel?: string;
+ balanceDueLabel?: string;
+}
export default function InvoiceCustomizeContent() {
return (
-
-
-
-
-
-
-
-
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ asdfasdfdsaf #3
+
+
);
}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer.tsx
index 04aa9143a..3a9956143 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer.tsx
@@ -9,8 +9,8 @@ const InvoiceCustomizeContent = React.lazy(
);
/**
- * Refund credit note detail.
- * @returns
+ * Invoice customize drawer.
+ * @returns {React.ReactNode}
*/
function InvoiceCustomizeDrawerRoot({
name,
@@ -19,12 +19,7 @@ function InvoiceCustomizeDrawerRoot({
payload: {},
}) {
return (
-
-
+
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx
index b0453e94e..98fd8d396 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx
@@ -1,16 +1,16 @@
// @ts-nocheck
+import React from 'react';
import * as R from 'ramda';
-import { Box, Group, Stack } from '@/components';
+import { Button, Intent } from '@blueprintjs/core';
+import { Group, Stack } from '@/components';
import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader';
import { InvoiceCustomizeTabs } from './InvoiceCustomizeTabs';
-import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields';
import { useInvoiceCustomizeTabsController } from './InvoiceCustomizeTabsController';
-import { Button, Intent } from '@blueprintjs/core';
-import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { useFormikContext } from 'formik';
-import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields';
+import { useInvoiceCustomizeContext } from './InvoiceCustomizeProvider';
import styles from './InvoiceCustomizeFields.module.scss';
+import withDrawerActions from '@/containers/Drawer/withDrawerActions';
export function InvoiceCustomizeFields() {
return (
@@ -23,14 +23,22 @@ export function InvoiceCustomizeFields() {
export function InvoiceCustomizeFieldsMain() {
const { currentTabId } = useInvoiceCustomizeTabsController();
+ const { CustomizeTabs } = useInvoiceCustomizeContext();
+
+ const CustomizeTabPanel = React.useMemo(
+ () =>
+ React.Children.map(CustomizeTabs, (tab) => {
+ return tab.props.id === currentTabId ? tab : null;
+ }).filter(Boolean),
+ [CustomizeTabs, currentTabId],
+ );
+
return (
- {currentTabId === 'general' && }
- {currentTabId === 'content' && }
-
+ {CustomizeTabPanel}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx
index e8e789f4b..f78f2d4e5 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx
@@ -1,17 +1,19 @@
import { Box } from '@/components';
-import { PaperTemplate } from './PaperTemplate';
+import { useInvoiceCustomizeContext } from './InvoiceCustomizeProvider';
export function InvoiceCustomizePreviewContent() {
+ const { PaperTemplate } = useInvoiceCustomizeContext();
+
return (
-
+ {PaperTemplate}
);
}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeProvider.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeProvider.tsx
new file mode 100644
index 000000000..3049a9994
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeProvider.tsx
@@ -0,0 +1,32 @@
+import React, { createContext, useContext } from 'react';
+
+interface InvoiceCustomizeValue {
+ PaperTemplate?: React.ReactNode;
+ CustomizeTabs: React.ReactNode;
+}
+
+const InvoiceCustomizeContext = createContext(
+ {} as InvoiceCustomizeValue,
+);
+
+export const InvoiceCustomizeProvider: React.FC<{
+ value: InvoiceCustomizeValue;
+ children: React.ReactNode;
+}> = ({ value, children }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export const useInvoiceCustomizeContext = (): InvoiceCustomizeValue => {
+ const context = useContext(InvoiceCustomizeContext);
+
+ if (!context) {
+ throw new Error(
+ 'useInvoiceCustomize must be used within an InvoiceCustomizeProvider',
+ );
+ }
+ return context;
+};
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.tsx
index 4298af017..425312d48 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.tsx
@@ -6,10 +6,17 @@ import {
useInvoiceCustomizeTabsController,
} from './InvoiceCustomizeTabsController';
import styles from './InvoiceCustomizeTabs.module.scss';
+import { useInvoiceCustomizeContext } from './InvoiceCustomizeProvider';
+import React from 'react';
export function InvoiceCustomizeTabs() {
const { setCurrentTabId } = useInvoiceCustomizeTabsController();
+ const { CustomizeTabs } = useInvoiceCustomizeContext();
+
+ const tabItems = React.Children.map(CustomizeTabs, (node) => ({
+ ...(React.isValidElement(node) ? node.props : {}),
+ }));
const handleChange = (value: InvoiceCustomizeTabsEnum) => {
setCurrentTabId(value);
};
@@ -25,9 +32,9 @@ export function InvoiceCustomizeTabs() {
onChange={handleChange}
className={styles.tabsList}
>
-
-
-
+ {tabItems?.map(({ id, label }: { id: string; label: string }) => (
+
+ ))}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizerForm.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizerForm.tsx
index c99bcfb2a..a245b5a5d 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizerForm.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizerForm.tsx
@@ -1,25 +1,25 @@
-import { Formik, Form } from 'formik';
-import * as Yup from 'yup';
+// @ts-nocheck
import React from 'react';
+import { Formik, Form, FormikHelpers } from 'formik';
-const validationSchema = Yup.object().shape({
- invoiceNumber: Yup.string().required('Invoice number is required'),
- customerName: Yup.string().required('Customer name is required'),
- amount: Yup.number()
- .required('Amount is required')
- .positive('Amount must be positive'),
-});
-
-interface InvoiceCustomizeFormProps {
- children: React.ReactNode;
+export interface InvoiceCustomizeFormProps {
+ initialValues?: T;
+ validationSchema?: any;
+ onSubmit?: (values: T, formikHelpers: FormikHelpers) => void;
+ children?: React.ReactNode;
}
-export function InvoiceCustomizeForm({ children }: InvoiceCustomizeFormProps) {
+export function InvoiceCustomizeForm({
+ initialValues,
+ validationSchema,
+ onSubmit,
+ children,
+}: InvoiceCustomizeFormProps) {
return (
-
+ initialValues={{ ...initialValues }}
validationSchema={validationSchema}
- onSubmit={(values) => {}}
+ onSubmit={(value, helpers) => onSubmit && onSubmit(value, helpers)}
>
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx
new file mode 100644
index 000000000..49333844c
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx
@@ -0,0 +1,235 @@
+import clsx from 'classnames';
+import styles from './PaperTemplate.module.scss';
+
+interface PaperTemplateProps {
+ invoiceNumber?: string;
+ invoiceNumberLabel?: string;
+
+ dateIssue?: string;
+ dateIssueLabel?: string;
+
+ dueDate?: string;
+ dueDateLabel?: string;
+
+ companyName?: string;
+
+ bigtitle?: string;
+
+ itemRateLabel?: string;
+ itemQuantityLabel?: string;
+ itemTotalLabel?: string;
+
+ // Totals
+ showDueAmount?: boolean;
+ showDiscount?: boolean;
+ showPaymentMade?: boolean;
+ showTaxes?: boolean;
+ showSubtotal?: boolean;
+ showTotal?: boolean;
+ showBalanceDue?: boolean;
+
+ paymentMadeLabel?: string;
+ discountLabel?: string;
+ subtotalLabel?: string;
+ totalLabel?: string;
+ balanceDueLabel?: string;
+}
+
+export function InvoicePaperTemplate({
+ bigtitle = 'Invoice',
+
+ companyName = 'Bigcapital Technology, Inc.',
+ // dueDateLabel,
+
+ dueDate = 'September 3, 2024',
+ dueDateLabel = 'Date due',
+
+ dateIssue = 'September 3, 2024',
+ dateIssueLabel = 'Date of issue',
+
+ // dateIssue,
+ invoiceNumberLabel = 'Invoice number',
+ invoiceNumber = '346D3D40-0001',
+
+ // Entries
+ itemQuantityLabel = 'Quantity',
+ itemRateLabel = 'Rate',
+ itemTotalLabel = 'Total',
+
+ totalLabel = 'Total',
+ subtotalLabel = 'Subtotal',
+ discountLabel = 'Discount',
+ paymentMadeLabel = 'Payment Made',
+ balanceDueLabel = 'Balance Due',
+
+ // Totals
+ showTotal = true,
+ showSubtotal = true,
+ showDiscount = true,
+ showTaxes = true,
+ showPaymentMade = true,
+ showDueAmount = true,
+ showBalanceDue = true,
+}: PaperTemplateProps) {
+ return (
+
+
+
{bigtitle}
+
+
+
+
+
+
+
+
{invoiceNumberLabel}
+
{invoiceNumber}
+
+
+
+
{dateIssueLabel}
+
{dateIssue}
+
+
+
+
{dueDateLabel}
+
{dueDate}
+
+
+
+
+
+ {companyName}
+ 131 Continental Dr Suite 305 Newark,
+
+ Delaware 19713
+
+ United States
+
+ +1 762-339-5634
+
+ ahmed@bigcapital.app
+
+
+
+ Billed To
+ Bigcapital Technology, Inc.
+ 131 Continental Dr Suite 305 Newark,
+
+ Delaware 19713
+
+ United States
+
+ +1 762-339-5634
+
+ ahmed@bigcapital.app
+
+
+
+
+
+
+ Item
+ Description
+ {itemRateLabel}
+ {itemTotalLabel}
+
+
+
+
+
+ Simply dummy text
+ Simply dummy text of the printing and typesetting
+ 1 X $100,00
+ $100,00
+
+
+
+
+
+
+ {showSubtotal && (
+
+
{subtotalLabel}
+
630.00
+
+ )}
+
+ {showDiscount && (
+
+
{discountLabel}
+
0.00
+
+ )}
+
+ {showTaxes && (
+ <>
+
+
+ Sample Tax1 (4.70%)
+
+
11.75
+
+
+
+
+ Sample Tax2 (7.00%)
+
+
21.00
+
+ >
+ )}
+
+ {showTotal && (
+
+
{totalLabel}
+
$662.75
+
+ )}
+
+ {showPaymentMade && (
+
+
{paymentMadeLabel}
+
100.00
+
+ )}
+
+ {showBalanceDue && (
+
+
{balanceDueLabel}
+
$562.75
+
+ )}
+
+
+
+
+
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.
+
+
+
+
+
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.
+
+
+
+ );
+}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx
deleted file mode 100644
index 8c83a94b4..000000000
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import clsx from 'classnames';
-import styles from './PaperTemplate.module.scss';
-
-export function PaperTemplate() {
- return (
-
-
-
Invoice
-
-
-
-
-
-
-
-
-
Invoice number
-
346D3D40-0001
-
-
-
-
Date of issue
-
September 3, 2024
-
-
-
-
Date due
-
October 3, 2024
-
-
-
-
-
- Bigcapital Technology, Inc.
- 131 Continental Dr Suite 305 Newark,
-
- Delaware 19713
-
- United States
-
- +1 762-339-5634
-
- ahmed@bigcapital.app
-
-
-
- Billed To
- Bigcapital Technology, Inc.
- 131 Continental Dr Suite 305 Newark,
-
- Delaware 19713
-
- United States
-
- +1 762-339-5634
-
- ahmed@bigcapital.app
-
-
-
-
-
-
- Item
- Description
- Rate
- Total
-
-
-
-
-
- Simply dummy text
- Simply dummy text of the printing and typesetting
- 1 X $100,00
- $100,00
-
-
-
-
-
-
-
-
-
-
-
-
Sample Tax1 (4.70%)
-
11.75
-
-
-
-
Sample Tax2 (7.00%)
-
21.00
-
-
-
-
-
-
Payment Made
-
100.00
-
-
-
-
Balance Due
-
$562.75
-
-
-
-
-
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.
-
-
-
-
-
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.
-
-
-
- );
-}
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx
new file mode 100644
index 000000000..684c5bda7
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx
@@ -0,0 +1,3 @@
+export default function ReceiptCustomizeContent() {
+ return asdasdasd ;
+}
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeDrawer.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeDrawer.tsx
new file mode 100644
index 000000000..48dbdaebc
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeDrawer.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 ReceiptCustomizeContent = React.lazy(
+ () => import('./ReceiptCustomizeContent'),
+);
+
+/**
+ * Receipt customize drawer.
+ * @returns {React.ReactNode}
+ */
+function ReceiptCustomizeDrawerRoot({
+ name,
+ // #withDrawer
+ isOpen,
+ payload: {},
+}) {
+ return (
+
+
+
+
+
+ );
+}
+
+export const ReceiptCustomizeDrawer = R.compose(withDrawers())(
+ ReceiptCustomizeDrawerRoot,
+);
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.tsx
index e5fef4dd0..a8f95d77e 100644
--- a/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.tsx
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.tsx
@@ -7,6 +7,11 @@ import {
NavbarGroup,
Intent,
Alignment,
+ Popover,
+ PopoverInteractionKind,
+ Position,
+ Menu,
+ MenuItem,
} from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
@@ -38,6 +43,8 @@ import { SaleReceiptAction, AbilitySubject } from '@/constants/abilityOption';
import { DialogsName } from '@/constants/dialogs';
import { compose } from '@/utils';
+import withDrawerActions from '@/containers/Drawer/withDrawerActions';
+import { DRAWERS } from '@/constants/drawers';
/**
* Receipts actions bar.
@@ -55,6 +62,9 @@ function ReceiptActionsBar({
// #withDialogActions
openDialog,
+ // #withDrawerActions
+ openDrawer,
+
// #withSettingsActions
addSetting,
}) {
@@ -103,6 +113,10 @@ function ReceiptActionsBar({
const handlePrintButtonClick = () => {
downloadExportPdf({ resource: 'SaleReceipt' });
};
+ // Handle customize button click.
+ const handleCustomizeBtnClick = () => {
+ openDrawer(DRAWERS.RECEIPT_CUSTOMIZE);
+ };
return (
@@ -173,6 +187,25 @@ function ReceiptActionsBar({
+
+
+
+ }
+ >
+ } minimal={true} />
+
+
}
@@ -193,4 +226,5 @@ export default compose(
receiptsTableSize: receiptSettings?.tableSize,
})),
withDialogActions,
+ withDrawerActions,
)(ReceiptActionsBar);
diff --git a/packages/webapp/src/utils/extract-children.ts b/packages/webapp/src/utils/extract-children.ts
new file mode 100644
index 000000000..f70d3fbef
--- /dev/null
+++ b/packages/webapp/src/utils/extract-children.ts
@@ -0,0 +1,10 @@
+import React from 'react';
+
+export const extractChildren = (
+ children: React.ReactNode,
+ type: React.ElementType,
+): T[] => {
+ return React.Children.toArray(children).filter(
+ (node) => React.isValidElement(node) && node.type === type,
+ ) as T[];
+};
From f644ed670874dc1ee731112b86ad5ef34c60b3fe Mon Sep 17 00:00:00 2001
From: Ahmed Bouhuolia
Date: Mon, 9 Sep 2024 21:07:22 +0200
Subject: [PATCH 07/32] feat: element customize component
---
.../Forms/ColorInput.module.scss} | 0
.../Forms/ColorInput.tsx} | 8 +-
.../Forms/FColorInput.tsx} | 38 +++---
.../ElementCustomize.module.scss | 24 ++++
.../ElementCustomize/ElementCustomize.tsx | 74 +++++++++++
.../ElementCustomizeFields.tsx} | 38 +++---
.../ElementCustomizeHeader.module.scss} | 0
.../ElementCustomizeHeader.tsx} | 10 +-
.../ElementCustomizePreview.tsx} | 19 +--
.../ElementCustomizePreviewContent.tsx} | 6 +-
.../ElementCustomizeProvider.tsx | 32 +++++
.../ElementCustomizeTabs.module.scss} | 0
.../ElementCustomizeTabs.tsx} | 24 ++--
.../ElementCustomizeTabsController.tsx | 46 +++++++
.../ElementCustomizerForm.tsx} | 6 +-
.../InvoiceCustomize/InvoiceCustomize.tsx | 125 ++++++++----------
.../InvoiceCustomizeContent.tsx | 65 ---------
.../InvoiceCustomizeDrawer.tsx | 6 +-
.../InvoiceCustomizeGeneralFields.tsx | 8 +-
.../InvoiceCustomizeProvider.tsx | 32 -----
.../InvoiceCustomizeTabsController.tsx | 46 -------
.../InvoiceCutomizeContentFields.tsx | 2 +-
....scss => InvoicePaperTemplate.module.scss} | 0
.../InvoiceCustomize/InvoicePaperTemplate.tsx | 2 +-
.../CreditCardIcon.tsx | 0
25 files changed, 319 insertions(+), 292 deletions(-)
rename packages/webapp/src/{containers/Sales/Invoices/InvoiceCustomize/ColorField.module.scss => components/Forms/ColorInput.module.scss} (100%)
rename packages/webapp/src/{containers/Sales/Invoices/InvoiceCustomize/ColorField.tsx => components/Forms/ColorInput.tsx} (93%)
rename packages/webapp/src/{containers/Sales/Invoices/InvoiceCustomize/FColorField.tsx => components/Forms/FColorInput.tsx} (52%)
create mode 100644 packages/webapp/src/containers/ElementCustomize/ElementCustomize.module.scss
create mode 100644 packages/webapp/src/containers/ElementCustomize/ElementCustomize.tsx
rename packages/webapp/src/containers/{Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx => ElementCustomize/ElementCustomizeFields.tsx} (60%)
rename packages/webapp/src/containers/{Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss => ElementCustomize/ElementCustomizeHeader.module.scss} (100%)
rename packages/webapp/src/containers/{Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.tsx => ElementCustomize/ElementCustomizeHeader.tsx} (80%)
rename packages/webapp/src/containers/{Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreview.tsx => ElementCustomize/ElementCustomizePreview.tsx} (51%)
rename packages/webapp/src/containers/{Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx => ElementCustomize/ElementCustomizePreviewContent.tsx} (59%)
create mode 100644 packages/webapp/src/containers/ElementCustomize/ElementCustomizeProvider.tsx
rename packages/webapp/src/containers/{Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.module.scss => ElementCustomize/ElementCustomizeTabs.module.scss} (100%)
rename packages/webapp/src/containers/{Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.tsx => ElementCustomize/ElementCustomizeTabs.tsx} (56%)
create mode 100644 packages/webapp/src/containers/ElementCustomize/ElementCustomizeTabsController.tsx
rename packages/webapp/src/containers/{Sales/Invoices/InvoiceCustomize/InvoiceCustomizerForm.tsx => ElementCustomize/ElementCustomizerForm.tsx} (81%)
delete mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx
delete mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeProvider.tsx
delete mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabsController.tsx
rename packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/{PaperTemplate.module.scss => InvoicePaperTemplate.module.scss} (100%)
rename packages/webapp/src/{containers/Sales/Invoices/InvoiceCustomize => icons}/CreditCardIcon.tsx (100%)
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.module.scss b/packages/webapp/src/components/Forms/ColorInput.module.scss
similarity index 100%
rename from packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.module.scss
rename to packages/webapp/src/components/Forms/ColorInput.module.scss
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.tsx b/packages/webapp/src/components/Forms/ColorInput.tsx
similarity index 93%
rename from packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.tsx
rename to packages/webapp/src/components/Forms/ColorInput.tsx
index 719fc714c..e986e0892 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/ColorField.tsx
+++ b/packages/webapp/src/components/Forms/ColorInput.tsx
@@ -11,9 +11,9 @@ import {
import { HexColorPicker } from 'react-colorful';
import { useUncontrolled } from '@/hooks/useUncontrolled';
import { Box, BoxProps } from '@/components';
-import styles from './ColorField.module.scss';
+import styles from './ColorInput.module.scss';
-export interface ColorFieldProps {
+export interface ColorInputProps {
value?: string;
initialValue?: string;
onChange?: (value: string) => void;
@@ -23,7 +23,7 @@ export interface ColorFieldProps {
pickerWrapProps?: Partial;
}
-export function ColorField({
+export function ColorInput({
value,
initialValue,
onChange,
@@ -31,7 +31,7 @@ export function ColorField({
inputProps,
pickerWrapProps,
pickerProps,
-}: ColorFieldProps) {
+}: ColorInputProps) {
const [_value, handleChange] = useUncontrolled({
value,
initialValue,
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/FColorField.tsx b/packages/webapp/src/components/Forms/FColorInput.tsx
similarity index 52%
rename from packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/FColorField.tsx
rename to packages/webapp/src/components/Forms/FColorInput.tsx
index 35840f7cb..0622f129b 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/FColorField.tsx
+++ b/packages/webapp/src/components/Forms/FColorInput.tsx
@@ -2,27 +2,27 @@ import React from 'react';
import { getIn, FieldConfig, FieldProps } from 'formik';
import { Intent } from '@blueprintjs/core';
import { Field } from '@blueprintjs-formik/core';
-import { ColorField, ColorFieldProps } from './ColorField';
+import { ColorInput, ColorInputProps } from './ColorInput';
-interface ColorFieldInputGroupProps
+interface ColorInputInputGroupProps
extends Omit,
- ColorFieldProps {}
+ ColorInputProps {}
-export interface ColorFieldToInputProps
+export interface ColorInputToInputProps
extends Omit,
- ColorFieldProps {}
+ ColorInputProps {}
/**
- * Transforms field props to input group props for ColorField.
- * @param {ColorFieldToInputProps}
- * @returns {ColorFieldProps}
+ * Transforms field props to input group props for ColorInput.
+ * @param {ColorInputToInputProps}
+ * @returns {ColorInputProps}
*/
-function fieldToColorFieldInputGroup({
+function fieldToColorInputInputGroup({
field: { onBlur: onFieldBlur, onChange: onFieldChange, value, ...field },
form: { touched, errors, setFieldValue },
onChange,
...props
-}: ColorFieldToInputProps): ColorFieldProps {
+}: ColorInputToInputProps): ColorInputProps {
const fieldError = getIn(errors, field.name);
const showError = getIn(touched, field.name) && !!fieldError;
@@ -42,23 +42,23 @@ function fieldToColorFieldInputGroup({
}
/**
- * Transforms field props to input group props for ColorField.
- * @param {ColorFieldToInputProps} props -
+ * Transforms field props to input group props for ColorInput.
+ * @param {ColorInputToInputProps} props -
* @returns {JSX.Element}
*/
-function ColorFieldToInputGroup({
+function ColorInputToInputGroup({
...props
-}: ColorFieldToInputProps): JSX.Element {
- return ;
+}: ColorInputToInputProps): JSX.Element {
+ return ;
}
/**
- * Input group Blueprint component binded with Formik for ColorField.
- * @param {ColorFieldInputGroupProps}
+ * Input group Blueprint component binded with Formik for ColorInput.
+ * @param {ColorInputInputGroupProps}
* @returns {JSX.Element}
*/
export function FColorInput({
...props
-}: ColorFieldInputGroupProps): JSX.Element {
- return ;
+}: ColorInputInputGroupProps): JSX.Element {
+ return ;
}
diff --git a/packages/webapp/src/containers/ElementCustomize/ElementCustomize.module.scss b/packages/webapp/src/containers/ElementCustomize/ElementCustomize.module.scss
new file mode 100644
index 000000000..8fd643511
--- /dev/null
+++ b/packages/webapp/src/containers/ElementCustomize/ElementCustomize.module.scss
@@ -0,0 +1,24 @@
+.root {
+ background: #fff;
+}
+
+.mainFields{
+ width: 400px;
+ height: 100vh;
+}
+.fieldGroup {
+
+ :global .bp4-form-content{
+ margin-left: auto;
+ }
+}
+
+.footerActions{
+ 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/ElementCustomize/ElementCustomize.tsx b/packages/webapp/src/containers/ElementCustomize/ElementCustomize.tsx
new file mode 100644
index 000000000..2ba5e0998
--- /dev/null
+++ b/packages/webapp/src/containers/ElementCustomize/ElementCustomize.tsx
@@ -0,0 +1,74 @@
+import React from 'react';
+import { Box, Group } from '@/components';
+import { ElementCustomizeProvider } from './ElementCustomizeProvider';
+import {
+ ElementCustomizeForm,
+ ElementCustomizeFormProps,
+} from './ElementCustomizerForm';
+import { ElementCustomizeTabsControllerProvider } from './ElementCustomizeTabsController';
+import { ElementCustomizeFields } from './ElementCustomizeFields';
+import { ElementCustomizePreview } from './ElementCustomizePreview';
+import { extractChildren } from '@/utils/extract-children';
+
+export interface ElementCustomizeProps extends ElementCustomizeFormProps {
+ children?: React.ReactNode;
+}
+
+export function ElementCustomize({
+ initialValues,
+ validationSchema,
+ onSubmit,
+ children,
+}: ElementCustomizeProps) {
+ const PaperTemplate = React.useMemo(
+ () => extractChildren(children, ElementCustomize.PaperTemplate),
+ [children],
+ );
+ const CustomizeTabs = React.useMemo(
+ () => extractChildren(children, ElementCustomize.FieldsTab),
+ [children],
+ );
+
+ const value = { PaperTemplate, CustomizeTabs };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export interface ElementCustomizePaperTemplateProps {
+ children?: React.ReactNode;
+}
+
+ElementCustomize.PaperTemplate = ({
+ children,
+}: ElementCustomizePaperTemplateProps) => {
+ return {children} ;
+};
+
+export interface ElementCustomizeContentProps {
+ id: string;
+ label: string;
+ children?: React.ReactNode;
+}
+
+ElementCustomize.FieldsTab = ({
+ id,
+ label,
+ children,
+}: ElementCustomizeContentProps) => {
+ return {children} ;
+};
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx b/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFields.tsx
similarity index 60%
rename from packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx
rename to packages/webapp/src/containers/ElementCustomize/ElementCustomizeFields.tsx
index 98fd8d396..b55132c74 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeFields.tsx
+++ b/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFields.tsx
@@ -2,28 +2,28 @@
import React from 'react';
import * as R from 'ramda';
import { Button, Intent } from '@blueprintjs/core';
-import { Group, Stack } from '@/components';
-import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader';
-import { InvoiceCustomizeTabs } from './InvoiceCustomizeTabs';
-import { useInvoiceCustomizeTabsController } from './InvoiceCustomizeTabsController';
-import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import { useFormikContext } from 'formik';
-import { useInvoiceCustomizeContext } from './InvoiceCustomizeProvider';
-import styles from './InvoiceCustomizeFields.module.scss';
+import { Group, Stack } from '@/components';
+import { ElementCustomizeHeader } from './ElementCustomizeHeader';
+import { ElementCustomizeTabs } from './ElementCustomizeTabs';
+import { useElementCustomizeTabsController } from './ElementCustomizeTabsController';
+import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
+import { useElementCustomizeContext } from './ElementCustomizeProvider';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
+import styles from './ElementCustomize.module.scss';
-export function InvoiceCustomizeFields() {
+export function ElementCustomizeFields() {
return (
-
-
-
+
+
+
);
}
-export function InvoiceCustomizeFieldsMain() {
- const { currentTabId } = useInvoiceCustomizeTabsController();
- const { CustomizeTabs } = useInvoiceCustomizeContext();
+export function ElementCustomizeFieldsMain() {
+ const { currentTabId } = useElementCustomizeTabsController();
+ const { CustomizeTabs } = useElementCustomizeContext();
const CustomizeTabPanel = React.useMemo(
() =>
@@ -35,17 +35,17 @@ export function InvoiceCustomizeFieldsMain() {
return (
-
+
{CustomizeTabPanel}
-
+
);
}
-function InvoiceCustomizeFooterActionsRoot({ closeDrawer }) {
+function ElementCustomizeFooterActionsRoot({ closeDrawer }) {
const { name } = useDrawerContext();
const { submitForm } = useFormikContext();
@@ -70,6 +70,6 @@ function InvoiceCustomizeFooterActionsRoot({ closeDrawer }) {
);
}
-const InvoiceCustomizeFooterActions = R.compose(withDrawerActions)(
- InvoiceCustomizeFooterActionsRoot,
+const ElementCustomizeFooterActions = R.compose(withDrawerActions)(
+ ElementCustomizeFooterActionsRoot,
);
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss b/packages/webapp/src/containers/ElementCustomize/ElementCustomizeHeader.module.scss
similarity index 100%
rename from packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.module.scss
rename to packages/webapp/src/containers/ElementCustomize/ElementCustomizeHeader.module.scss
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.tsx b/packages/webapp/src/containers/ElementCustomize/ElementCustomizeHeader.tsx
similarity index 80%
rename from packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.tsx
rename to packages/webapp/src/containers/ElementCustomize/ElementCustomizeHeader.tsx
index 0dc6e209c..1800ca78f 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeHeader.tsx
+++ b/packages/webapp/src/containers/ElementCustomize/ElementCustomizeHeader.tsx
@@ -1,20 +1,20 @@
-import { Group, Icon } from '@/components';
import { Button, Classes } from '@blueprintjs/core';
-import styles from './InvoiceCustomizeHeader.module.scss';
+import { Group, Icon } from '@/components';
+import styles from './ElementCustomizeHeader.module.scss';
-interface InvoiceCustomizeHeaderProps {
+interface ElementCustomizeHeaderProps {
label?: string;
children?: React.ReactNode;
closeButton?: boolean;
onClose?: () => void;
}
-export function InvoiceCustomizeHeader({
+export function ElementCustomizeHeader({
label,
closeButton,
onClose,
children,
-}: InvoiceCustomizeHeaderProps) {
+}: ElementCustomizeHeaderProps) {
const handleClose = () => {
onClose && onClose();
};
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreview.tsx b/packages/webapp/src/containers/ElementCustomize/ElementCustomizePreview.tsx
similarity index 51%
rename from packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreview.tsx
rename to packages/webapp/src/containers/ElementCustomize/ElementCustomizePreview.tsx
index 6b74bf581..975d820b0 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreview.tsx
+++ b/packages/webapp/src/containers/ElementCustomize/ElementCustomizePreview.tsx
@@ -1,29 +1,32 @@
// @ts-nocheck
import * as R from 'ramda';
import { Stack } from '@/components';
-import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader';
-import { InvoiceCustomizePreviewContent } from './InvoiceCustomizePreviewContent';
+import { ElementCustomizeHeader } from './ElementCustomizeHeader';
+import { ElementCustomizePreviewContent } from './ElementCustomizePreviewContent';
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
-function InvoiceCustomizePreviewRoot({ closeDrawer }) {
+function ElementCustomizePreviewRoot({ closeDrawer }) {
const { name } = useDrawerContext();
const handleCloseBtnClick = () => {
closeDrawer(name);
};
return (
-
-
+
-
+
);
}
-export const InvoiceCustomizePreview = R.compose(withDrawerActions)(
- InvoiceCustomizePreviewRoot,
+export const ElementCustomizePreview = R.compose(withDrawerActions)(
+ ElementCustomizePreviewRoot,
);
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx b/packages/webapp/src/containers/ElementCustomize/ElementCustomizePreviewContent.tsx
similarity index 59%
rename from packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx
rename to packages/webapp/src/containers/ElementCustomize/ElementCustomizePreviewContent.tsx
index f78f2d4e5..6f643141d 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizePreviewContent.tsx
+++ b/packages/webapp/src/containers/ElementCustomize/ElementCustomizePreviewContent.tsx
@@ -1,8 +1,8 @@
import { Box } from '@/components';
-import { useInvoiceCustomizeContext } from './InvoiceCustomizeProvider';
+import { useElementCustomizeContext } from './ElementCustomizeProvider';
-export function InvoiceCustomizePreviewContent() {
- const { PaperTemplate } = useInvoiceCustomizeContext();
+export function ElementCustomizePreviewContent() {
+ const { PaperTemplate } = useElementCustomizeContext();
return (
(
+ {} as ElementCustomizeValue,
+);
+
+export const ElementCustomizeProvider: React.FC<{
+ value: ElementCustomizeValue;
+ children: React.ReactNode;
+}> = ({ value, children }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export const useElementCustomizeContext = (): ElementCustomizeValue => {
+ const context = useContext(ElementCustomizeContext);
+
+ if (!context) {
+ throw new Error(
+ 'useElementCustomize must be used within an ElementCustomizeProvider',
+ );
+ }
+ return context;
+};
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.module.scss b/packages/webapp/src/containers/ElementCustomize/ElementCustomizeTabs.module.scss
similarity index 100%
rename from packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.module.scss
rename to packages/webapp/src/containers/ElementCustomize/ElementCustomizeTabs.module.scss
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.tsx b/packages/webapp/src/containers/ElementCustomize/ElementCustomizeTabs.tsx
similarity index 56%
rename from packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.tsx
rename to packages/webapp/src/containers/ElementCustomize/ElementCustomizeTabs.tsx
index 425312d48..9ab3ae39f 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabs.tsx
+++ b/packages/webapp/src/containers/ElementCustomize/ElementCustomizeTabs.tsx
@@ -1,28 +1,28 @@
+import React from 'react';
import { Box, Stack } from '@/components';
import { Tab, Tabs } from '@blueprintjs/core';
-import { InvoiceCustomizeHeader } from './InvoiceCustomizeHeader';
+import { ElementCustomizeHeader } from './ElementCustomizeHeader';
import {
- InvoiceCustomizeTabsEnum,
- useInvoiceCustomizeTabsController,
-} from './InvoiceCustomizeTabsController';
-import styles from './InvoiceCustomizeTabs.module.scss';
-import { useInvoiceCustomizeContext } from './InvoiceCustomizeProvider';
-import React from 'react';
+ ElementCustomizeTabsEnum,
+ useElementCustomizeTabsController,
+} from './ElementCustomizeTabsController';
+import { useElementCustomizeContext } from './ElementCustomizeProvider';
+import styles from './ElementCustomizeTabs.module.scss';
-export function InvoiceCustomizeTabs() {
- const { setCurrentTabId } = useInvoiceCustomizeTabsController();
+export function ElementCustomizeTabs() {
+ const { setCurrentTabId } = useElementCustomizeTabsController();
- const { CustomizeTabs } = useInvoiceCustomizeContext();
+ const { CustomizeTabs } = useElementCustomizeContext();
const tabItems = React.Children.map(CustomizeTabs, (node) => ({
...(React.isValidElement(node) ? node.props : {}),
}));
- const handleChange = (value: InvoiceCustomizeTabsEnum) => {
+ const handleChange = (value: ElementCustomizeTabsEnum) => {
setCurrentTabId(value);
};
return (
-
+
+ >;
+}
+
+const ElementCustomizeTabsController = createContext(
+ {} as ElementCustomizeTabsControllerValue,
+);
+
+export const useElementCustomizeTabsController = () => {
+ return useContext(ElementCustomizeTabsController);
+};
+
+interface ElementCustomizeTabsControllerProps {
+ children: React.ReactNode;
+}
+
+export const ElementCustomizeTabsControllerProvider = ({
+ children,
+}: ElementCustomizeTabsControllerProps) => {
+ const [currentTabId, setCurrentTabId] =
+ useState(DEFAULT_TAB_ID);
+
+ const value = {
+ currentTabId,
+ setCurrentTabId,
+ };
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizerForm.tsx b/packages/webapp/src/containers/ElementCustomize/ElementCustomizerForm.tsx
similarity index 81%
rename from packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizerForm.tsx
rename to packages/webapp/src/containers/ElementCustomize/ElementCustomizerForm.tsx
index a245b5a5d..114e4f66e 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizerForm.tsx
+++ b/packages/webapp/src/containers/ElementCustomize/ElementCustomizerForm.tsx
@@ -2,19 +2,19 @@
import React from 'react';
import { Formik, Form, FormikHelpers } from 'formik';
-export interface InvoiceCustomizeFormProps {
+export interface ElementCustomizeFormProps {
initialValues?: T;
validationSchema?: any;
onSubmit?: (values: T, formikHelpers: FormikHelpers) => void;
children?: React.ReactNode;
}
-export function InvoiceCustomizeForm({
+export function ElementCustomizeForm({
initialValues,
validationSchema,
onSubmit,
children,
-}: InvoiceCustomizeFormProps) {
+}: ElementCustomizeFormProps) {
return (
initialValues={{ ...initialValues }}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx
index d88336974..d84fb170b 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx
@@ -1,74 +1,65 @@
import React from 'react';
-import { Box, Group } from '@/components';
-import { InvoiceCustomizeProvider } from './InvoiceCustomizeProvider';
-import {
- InvoiceCustomizeForm,
- InvoiceCustomizeFormProps,
-} from './InvoiceCustomizerForm';
-import { InvoiceCustomizeTabsControllerProvider } from './InvoiceCustomizeTabsController';
-import { InvoiceCustomizeFields } from './InvoiceCustomizeFields';
-import { InvoiceCustomizePreview } from './InvoiceCustomizePreview';
-import { extractChildren } from '@/utils/extract-children';
+import { Box } from '@/components';
+import { Classes } from '@blueprintjs/core';
+import { InvoicePaperTemplate } from './InvoicePaperTemplate';
+import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize';
+import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields';
+import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields';
-export interface InvoiceCustomizeProps extends InvoiceCustomizeFormProps {
- children?: React.ReactNode;
+interface InvoiceCustomizeValues {
+ invoiceNumber?: string;
+ invoiceNumberLabel?: string;
+
+ dateIssue?: string;
+ dateIssueLabel?: string;
+
+ dueDate?: string;
+ dueDateLabel?: string;
+
+ companyName?: string;
+
+ bigtitle?: string;
+
+ itemRateLabel?: string;
+ itemQuantityLabel?: string;
+ itemTotalLabel?: string;
+
+ // Totals
+ showDueAmount?: boolean;
+ showDiscount?: boolean;
+ showPaymentMade?: boolean;
+ showTaxes?: boolean;
+ showSubtotal?: boolean;
+ showTotal?: boolean;
+ showBalanceDue?: boolean;
+
+ paymentMadeLabel?: string;
+ discountLabel?: string;
+ subtotalLabel?: string;
+ totalLabel?: string;
+ balanceDueLabel?: string;
}
-export function InvoiceCustomize({
- initialValues,
- validationSchema,
- onSubmit,
- children,
-}: InvoiceCustomizeProps) {
- const PaperTemplate = React.useMemo(
- () => extractChildren(children, InvoiceCustomize.PaperTemplate),
- [children],
- );
- const CustomizeTabs = React.useMemo(
- () => extractChildren(children, InvoiceCustomize.FieldsTab),
- [children],
- );
-
- const value = { PaperTemplate, CustomizeTabs };
-
+export default function InvoiceCustomizeContent() {
return (
-
-
-
-
-
-
-
-
-
-
+
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ asdfasdfdsaf #3
+
+
+
);
}
-
-export interface InvoiceCustomizePaperTemplateProps {
- children?: React.ReactNode;
-}
-
-InvoiceCustomize.PaperTemplate = ({
- children,
-}: InvoiceCustomizePaperTemplateProps) => {
- return {children} ;
-};
-
-export interface InvoiceCustomizeContentProps {
- id: string;
- label: string;
- children?: React.ReactNode;
-}
-
-InvoiceCustomize.FieldsTab = ({
- id,
- label,
- children,
-}: InvoiceCustomizeContentProps) => {
- return {children} ;
-};
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx
deleted file mode 100644
index e17963eb9..000000000
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import React from 'react';
-import { Box } from '@/components';
-import { Classes } from '@blueprintjs/core';
-import { InvoicePaperTemplate } from './InvoicePaperTemplate';
-import { InvoiceCustomize } from './InvoiceCustomize';
-import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields';
-import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields';
-
-interface InvoiceCustomizeValues {
- invoiceNumber?: string;
- invoiceNumberLabel?: string;
-
- dateIssue?: string;
- dateIssueLabel?: string;
-
- dueDate?: string;
- dueDateLabel?: string;
-
- companyName?: string;
-
- bigtitle?: string;
-
- itemRateLabel?: string;
- itemQuantityLabel?: string;
- itemTotalLabel?: string;
-
- // Totals
- showDueAmount?: boolean;
- showDiscount?: boolean;
- showPaymentMade?: boolean;
- showTaxes?: boolean;
- showSubtotal?: boolean;
- showTotal?: boolean;
- showBalanceDue?: boolean;
-
- paymentMadeLabel?: string;
- discountLabel?: string;
- subtotalLabel?: string;
- totalLabel?: string;
- balanceDueLabel?: string;
-}
-
-export default function InvoiceCustomizeContent() {
- return (
-
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
- asdfasdfdsaf #3
-
-
-
- );
-}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer.tsx
index 3a9956143..167022bec 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeDrawer.tsx
@@ -4,8 +4,8 @@ import * as R from 'ramda';
import { Drawer, DrawerSuspense } from '@/components';
import withDrawers from '@/containers/Drawer/withDrawers';
-const InvoiceCustomizeContent = React.lazy(
- () => import('./InvoiceCustomizeContent'),
+const InvoiceCustomize = React.lazy(
+ () => import('./InvoiceCustomize'),
);
/**
@@ -21,7 +21,7 @@ function InvoiceCustomizeDrawerRoot({
return (
-
+
);
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx
index bfe44e947..2636deb1e 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx
@@ -1,9 +1,9 @@
// @ts-nocheck
-import { Box, FFormGroup, FSwitch, Group, Stack } from '@/components';
-import { FColorInput } from './FColorField';
-import styles from './InvoiceCustomizeFields.module.scss';
import { Classes, Text } from '@blueprintjs/core';
-import { CreditCardIcon } from './CreditCardIcon';
+import { FFormGroup, FSwitch, Group, Stack } from '@/components';
+import { FColorInput } from '@/components/Forms/FColorInput';
+import { CreditCardIcon } from '@/icons/CreditCardIcon';
+import styles from './InvoiceCustomizeFields.module.scss';
export function InvoiceCustomizeGeneralField() {
return (
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeProvider.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeProvider.tsx
deleted file mode 100644
index 3049a9994..000000000
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeProvider.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import React, { createContext, useContext } from 'react';
-
-interface InvoiceCustomizeValue {
- PaperTemplate?: React.ReactNode;
- CustomizeTabs: React.ReactNode;
-}
-
-const InvoiceCustomizeContext = createContext(
- {} as InvoiceCustomizeValue,
-);
-
-export const InvoiceCustomizeProvider: React.FC<{
- value: InvoiceCustomizeValue;
- children: React.ReactNode;
-}> = ({ value, children }) => {
- return (
-
- {children}
-
- );
-};
-
-export const useInvoiceCustomizeContext = (): InvoiceCustomizeValue => {
- const context = useContext(InvoiceCustomizeContext);
-
- if (!context) {
- throw new Error(
- 'useInvoiceCustomize must be used within an InvoiceCustomizeProvider',
- );
- }
- return context;
-};
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabsController.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabsController.tsx
deleted file mode 100644
index d58fa2a1a..000000000
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeTabsController.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import React, { createContext, useContext, useState } from 'react';
-
-export enum InvoiceCustomizeTabsEnum {
- General = 'general',
- Items = 'items',
- Totals = 'totals'
-}
-
-const DEFAULT_TAB_ID = InvoiceCustomizeTabsEnum.General;
-
-interface InvoiceCustomizeTabsControllerValue {
- currentTabId: InvoiceCustomizeTabsEnum;
- setCurrentTabId: React.Dispatch<
- React.SetStateAction
- >;
-}
-
-const InvoiceCustomizeTabsController = createContext(
- {} as InvoiceCustomizeTabsControllerValue,
-);
-
-export const useInvoiceCustomizeTabsController = () => {
- return useContext(InvoiceCustomizeTabsController);
-};
-
-interface InvoiceCustomizeTabsControllerProps {
- children: React.ReactNode;
-}
-
-export const InvoiceCustomizeTabsControllerProvider = ({
- children,
-}: InvoiceCustomizeTabsControllerProps) => {
- const [currentTabId, setCurrentTabId] =
- useState(DEFAULT_TAB_ID);
-
- const value = {
- currentTabId,
- setCurrentTabId,
- };
-
- return (
-
- {children}
-
- );
-};
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx
index ba58df5a3..29ca74c3e 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx
@@ -1,5 +1,5 @@
-import { FInputGroup, FSwitch, Group, Stack } from '@/components';
import { Classes } from '@blueprintjs/core';
+import { FInputGroup, FSwitch, Group, Stack } from '@/components';
const items = [
{ key: 'dueAmount', label: 'Due Amount' },
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.module.scss
similarity index 100%
rename from packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.module.scss
rename to packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.module.scss
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx
index 49333844c..84c213627 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 clsx from 'classnames';
-import styles from './PaperTemplate.module.scss';
+import styles from './InvoicePaperTemplate.module.scss';
interface PaperTemplateProps {
invoiceNumber?: string;
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/CreditCardIcon.tsx b/packages/webapp/src/icons/CreditCardIcon.tsx
similarity index 100%
rename from packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/CreditCardIcon.tsx
rename to packages/webapp/src/icons/CreditCardIcon.tsx
From 67904f52af8c26807d863603fd4dcfba19898b89 Mon Sep 17 00:00:00 2001
From: Ahmed Bouhuolia
Date: Mon, 9 Sep 2024 21:31:14 +0200
Subject: [PATCH 08/32] feat: add more customize drawers
---
.../src/components/DrawersContainer.tsx | 2 ++
packages/webapp/src/constants/drawers.ts | 1 +
.../VendorsCreditNoteActionsBar.tsx | 34 +++++++++++++++++++
.../CreditNoteCustomizeContent.tsx | 3 ++
.../CreditNoteCustomizeDrawer.tsx | 32 +++++++++++++++++
.../CreditNotesActionsBar.tsx | 34 +++++++++++++++++++
6 files changed, 106 insertions(+)
create mode 100644 packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx
create mode 100644 packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawer.tsx
diff --git a/packages/webapp/src/components/DrawersContainer.tsx b/packages/webapp/src/components/DrawersContainer.tsx
index 88fc83397..344c4712d 100644
--- a/packages/webapp/src/components/DrawersContainer.tsx
+++ b/packages/webapp/src/components/DrawersContainer.tsx
@@ -26,6 +26,7 @@ import ChangeSubscriptionPlanDrawer from '@/containers/Subscriptions/drawers/Cha
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 { CreditNoteCustomizeDrawer } from '@/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawer';
import { DRAWERS } from '@/constants/drawers';
/**
@@ -70,6 +71,7 @@ export default function DrawersContainer() {
+
);
}
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({
+
+
+
+ }
+ >
+ } minimal={true} />
+
+
}
@@ -173,4 +206,5 @@ export default compose(
creditNoteTableSize: vendorsCreditNoteSetting?.tableSize,
})),
withDialogActions,
+ withDrawerActions,
)(VendorsCreditNoteActionsBar);
diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx
new file mode 100644
index 000000000..8065e7479
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx
@@ -0,0 +1,3 @@
+export default function CreditNoteCustomizeContent() {
+ return asdasd ;
+}
diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawer.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawer.tsx
new file mode 100644
index 000000000..dc6ad6a19
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeDrawer.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 CreditNoteCustomizeContent = React.lazy(
+ () => import('./CreditNoteCustomizeContent'),
+);
+
+/**
+ * Invoice customize drawer.
+ * @returns {React.ReactNode}
+ */
+function CreditNoteCustomizeDrawerRoot({
+ name,
+ // #withDrawer
+ isOpen,
+ payload: {},
+}) {
+ return (
+
+
+
+
+
+ );
+}
+
+export const CreditNoteCustomizeDrawer = R.compose(withDrawers())(
+ CreditNoteCustomizeDrawerRoot,
+);
diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesActionsBar.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesActionsBar.tsx
index 332ad9352..963ae005d 100644
--- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesActionsBar.tsx
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesActionsBar.tsx
@@ -6,6 +6,11 @@ import {
NavbarDivider,
NavbarGroup,
Alignment,
+ Menu,
+ MenuItem,
+ Popover,
+ PopoverInteractionKind,
+ Position,
} from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import {
@@ -28,9 +33,11 @@ import withCreditNotesActions from './withCreditNotesActions';
import withSettings from '@/containers/Settings/withSettings';
import withSettingsActions from '@/containers/Settings/withSettingsActions';
import withDialogActions from '@/containers/Dialog/withDialogActions';
+import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { DialogsName } from '@/constants/dialogs';
import { compose } from '@/utils';
+import { DRAWERS } from '@/constants/drawers';
/**
* Credit note table actions bar.
@@ -50,6 +57,9 @@ function CreditNotesActionsBar({
// #withDialogActions
openDialog,
+
+ // #withDrawerActions
+ openDrawer
}) {
const history = useHistory();
@@ -89,6 +99,10 @@ function CreditNotesActionsBar({
const handlePrintBtnClick = () => {
downloadExportPdf({ resource: 'CreditNote' });
};
+ // Handle the customize button click.
+ const handleCustomizeBtnClick = () => {
+ openDrawer(DRAWERS.CREDIT_NOTE_CUSTOMIZE);
+ }
return (
@@ -149,6 +163,25 @@ function CreditNotesActionsBar({
+
+
+
+ }
+ >
+ } minimal={true} />
+
+
}
@@ -169,4 +202,5 @@ export default compose(
creditNoteTableSize: creditNoteSettings?.tableSize,
})),
withDialogActions,
+ withDrawerActions
)(CreditNotesActionsBar);
From f0dfc3d1b047b47c7b10f82e9f8b3e23e1464156 Mon Sep 17 00:00:00 2001
From: Ahmed Bouhuolia
Date: Tue, 10 Sep 2024 13:19:11 +0200
Subject: [PATCH 09/32] feat: invoice customize paper preview
---
.../src/components/Forms/ColorInput.tsx | 5 +
.../ElementCustomize/ElementCustomize.tsx | 4 +-
.../ElementCustomizeFields.tsx | 6 +-
.../InvoiceCustomize/InvoiceCustomize.tsx | 43 +--
.../InvoiceCustomizeGeneralFields.tsx | 13 +-
.../InvoiceCutomizeContentFields.tsx | 51 +--
.../InvoicePaperTemplate.module.scss | 2 +-
.../InvoiceCustomize/InvoicePaperTemplate.tsx | 326 ++++++++++++------
.../Invoices/InvoiceCustomize/constants.ts | 134 +++++++
.../Sales/Invoices/InvoiceCustomize/types.ts | 58 ++++
.../webapp/src/utils/sanitize-hex-color.ts | 3 +
11 files changed, 474 insertions(+), 171 deletions(-)
create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/constants.ts
create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/types.ts
create mode 100644 packages/webapp/src/utils/sanitize-hex-color.ts
diff --git a/packages/webapp/src/components/Forms/ColorInput.tsx b/packages/webapp/src/components/Forms/ColorInput.tsx
index e986e0892..d06f2fdc8 100644
--- a/packages/webapp/src/components/Forms/ColorInput.tsx
+++ b/packages/webapp/src/components/Forms/ColorInput.tsx
@@ -11,6 +11,7 @@ import {
import { HexColorPicker } from 'react-colorful';
import { useUncontrolled } from '@/hooks/useUncontrolled';
import { Box, BoxProps } from '@/components';
+import { sanitizeToHexColor } from '@/utils/sanitize-hex-color';
import styles from './ColorInput.module.scss';
export interface ColorInputProps {
@@ -72,6 +73,10 @@ export function ColorInput({
/>
}
+ onChange={(e) => {
+ const value = sanitizeToHexColor(e.currentTarget.value);
+ handleChange(value);
+ }}
{...inputProps}
className={clsx(styles.field, inputProps?.className)}
/>
diff --git a/packages/webapp/src/containers/ElementCustomize/ElementCustomize.tsx b/packages/webapp/src/containers/ElementCustomize/ElementCustomize.tsx
index 2ba5e0998..8c55d16bc 100644
--- a/packages/webapp/src/containers/ElementCustomize/ElementCustomize.tsx
+++ b/packages/webapp/src/containers/ElementCustomize/ElementCustomize.tsx
@@ -56,7 +56,7 @@ export interface ElementCustomizePaperTemplateProps {
ElementCustomize.PaperTemplate = ({
children,
}: ElementCustomizePaperTemplateProps) => {
- return {children} ;
+ return <>{children}>;
};
export interface ElementCustomizeContentProps {
@@ -70,5 +70,5 @@ ElementCustomize.FieldsTab = ({
label,
children,
}: ElementCustomizeContentProps) => {
- return {children} ;
+ return <>{children}>;
};
diff --git a/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFields.tsx b/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFields.tsx
index b55132c74..25294d3b1 100644
--- a/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFields.tsx
+++ b/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFields.tsx
@@ -3,7 +3,7 @@ import React from 'react';
import * as R from 'ramda';
import { Button, Intent } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
-import { Group, Stack } from '@/components';
+import { Box, Group, Stack } from '@/components';
import { ElementCustomizeHeader } from './ElementCustomizeHeader';
import { ElementCustomizeTabs } from './ElementCustomizeTabs';
import { useElementCustomizeTabsController } from './ElementCustomizeTabsController';
@@ -14,7 +14,7 @@ import styles from './ElementCustomize.module.scss';
export function ElementCustomizeFields() {
return (
-
+
@@ -38,7 +38,7 @@ export function ElementCustomizeFieldsMain() {
- {CustomizeTabPanel}
+ {CustomizeTabPanel}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx
index d84fb170b..3eefb60f6 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx
@@ -5,45 +5,18 @@ import { InvoicePaperTemplate } from './InvoicePaperTemplate';
import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize';
import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields';
import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields';
-
-interface InvoiceCustomizeValues {
- invoiceNumber?: string;
- invoiceNumberLabel?: string;
-
- dateIssue?: string;
- dateIssueLabel?: string;
-
- dueDate?: string;
- dueDateLabel?: string;
-
- companyName?: string;
-
- bigtitle?: string;
-
- itemRateLabel?: string;
- itemQuantityLabel?: string;
- itemTotalLabel?: string;
-
- // Totals
- showDueAmount?: boolean;
- showDiscount?: boolean;
- showPaymentMade?: boolean;
- showTaxes?: boolean;
- showSubtotal?: boolean;
- showTotal?: boolean;
- showBalanceDue?: boolean;
-
- paymentMadeLabel?: string;
- discountLabel?: string;
- subtotalLabel?: string;
- totalLabel?: string;
- balanceDueLabel?: string;
-}
+import { InvoiceCustomizeValues } from './types';
+import { initialValues } from './constants';
export default function InvoiceCustomizeContent() {
+ const handleFormSubmit = (values: InvoiceCustomizeValues) => {};
+
return (
- >
+
+ initialValues={initialValues}
+ onSubmit={handleFormSubmit}
+ >
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx
index 2636deb1e..43397bd64 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx
@@ -20,33 +20,38 @@ export function InvoiceCustomizeGeneralField() {
-
+
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx
index 29ca74c3e..fd1188d20 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx
@@ -1,35 +1,44 @@
-import { Classes } from '@blueprintjs/core';
+// @ts-nocheck
import { FInputGroup, FSwitch, Group, Stack } from '@/components';
-
-const items = [
- { key: 'dueAmount', label: 'Due Amount' },
- { key: 'billedTo', label: 'Billed To' },
- { key: 'balanceDue', label: 'Balance Due' },
- { key: 'termsConditions', label: 'Terms & Conditions' },
-];
+import { CLASSES } from '@/constants';
+import { Classes } from '@blueprintjs/core';
+import { fieldsGroups } from './constants';
export function InvoiceCustomizeContentFields() {
return (
-
-
- General Branding
+
+
+ General Branding
Set your invoice details to be automatically applied every time
you
create a new invoice.
- Header
-
- {items.map((item, index) => (
-
-
-
-
+ {fieldsGroups.map((group) => (
+ <>
+
+ {group.label}
+
+
+ {group.fields.map((item, index) => (
+
+
+ {item.labelKey && (
+
+ )}
+
+ ))}
+
+ >
))}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.module.scss
index b042dd270..9cc71dcb1 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.module.scss
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.module.scss
@@ -3,7 +3,7 @@
border-radius: 5px;
background-color: #fff;
color: #111;
- box-shadow: inset 0 4px 0px 0 #002762, 0 10px 15px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color), 0 10px 15px rgba(0, 0, 0, 0.05);
padding: 24px 30px;
font-size: 12px;
position: relative;
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx
index 84c213627..7fb69d58b 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx
@@ -1,60 +1,127 @@
import clsx from 'classnames';
+import * as R from 'ramda';
+import { useFormikContext } from 'formik';
import styles from './InvoicePaperTemplate.module.scss';
+interface PapaerLine {
+ item?: string;
+ description?: string;
+ quantity?: string;
+ rate?: string;
+ total?: string;
+}
+
+interface PaperTax {
+ label: string;
+ amount: string;
+}
+
interface PaperTemplateProps {
+ primaryColor?: string;
+ secondaryColor?: string;
+
+ showCompanyLogo?: boolean;
+ companyLogo?: string;
+
+ showInvoiceNumber?: boolean;
invoiceNumber?: string;
invoiceNumberLabel?: string;
+ showDateIssue?: boolean;
dateIssue?: string;
dateIssueLabel?: string;
+ showDueDate?: boolean;
dueDate?: string;
dueDateLabel?: string;
companyName?: string;
-
bigtitle?: string;
- itemRateLabel?: string;
- itemQuantityLabel?: string;
- itemTotalLabel?: string;
+ // Address
+ showBillingToAddress?: boolean;
+ showBilledFromAddress?: boolean;
+ billedToLabel?: string;
+
+ // Entries
+ lineItemLabel?: string;
+ lineDescriptionLabel?: string;
+ lineRateLabel?: string;
+ lineTotalLabel?: string;
// Totals
- showDueAmount?: boolean;
- showDiscount?: boolean;
- showPaymentMade?: boolean;
- showTaxes?: boolean;
- showSubtotal?: boolean;
showTotal?: boolean;
- showBalanceDue?: boolean;
-
- paymentMadeLabel?: string;
- discountLabel?: string;
- subtotalLabel?: string;
totalLabel?: string;
+ total?: string;
+
+ showDiscount?: boolean;
+ discountLabel?: string;
+ discount?: string;
+
+ showSubtotal?: boolean;
+ subtotalLabel?: string;
+ subtotal?: string;
+
+ showPaymentMade?: boolean;
+ paymentMadeLabel?: string;
+ paymentMade?: string;
+
+ showTaxes?: boolean;
+
+ showDueAmount?: boolean;
+ showBalanceDue?: boolean;
balanceDueLabel?: string;
+ balanceDue?: string;
+
+ // Footer
+ termsConditionsLabel: string;
+ showTermsConditions: boolean;
+ termsConditions: string;
+
+ statementLabel: string;
+ showStatement: boolean;
+ statement: string;
+
+ lines?: Array;
+ taxes?: Array;
+
+ billedFromAddres?: Array;
+ billedToAddress?: Array;
}
-export function InvoicePaperTemplate({
- bigtitle = 'Invoice',
+function InvoicePaperTemplateRoot({
+ primaryColor,
+ secondaryColor,
+ bigtitle = 'Invoice',
companyName = 'Bigcapital Technology, Inc.',
- // dueDateLabel,
+
+ showCompanyLogo = true,
+ companyLogo,
dueDate = 'September 3, 2024',
dueDateLabel = 'Date due',
+ showDueDate,
dateIssue = 'September 3, 2024',
dateIssueLabel = 'Date of issue',
+ showDateIssue,
// dateIssue,
invoiceNumberLabel = 'Invoice number',
invoiceNumber = '346D3D40-0001',
+ showInvoiceNumber,
+
+ // Address
+ showBillingToAddress = true,
+ showBilledFromAddress = true,
+ billedToLabel = 'Billed To',
// Entries
- itemQuantityLabel = 'Quantity',
- itemRateLabel = 'Rate',
- itemTotalLabel = 'Total',
+ lineItemLabel = 'Item',
+ lineDescriptionLabel = 'Description',
+ lineRateLabel = 'Rate',
+ lineTotalLabel = 'Total',
totalLabel = 'Total',
subtotalLabel = 'Subtotal',
@@ -70,82 +137,127 @@ export function InvoicePaperTemplate({
showPaymentMade = true,
showDueAmount = true,
showBalanceDue = true,
+
+ total = '$662.75',
+ subtotal = '630.00',
+ discount = '0.00',
+ paymentMade = '100.00',
+ balanceDue = '$562.75',
+
+ // Footer paragraphs.
+ termsConditionsLabel = '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.',
+
+ 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,
+ 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.',
+ 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',
+ ],
}: PaperTemplateProps) {
return (
+
+
{bigtitle}
-
-
-
+
+ {showCompanyLogo && (
+
+
+
+ )}
-
-
{invoiceNumberLabel}
-
{invoiceNumber}
-
-
-
-
{dateIssueLabel}
-
{dateIssue}
-
-
-
-
{dueDateLabel}
-
{dueDate}
-
+ {showInvoiceNumber && (
+
+
{invoiceNumberLabel}
+
{invoiceNumber}
+
+ )}
+ {showDateIssue && (
+
+
{dateIssueLabel}
+
{dateIssue}
+
+ )}
+ {showDueDate && (
+
+
{dueDateLabel}
+
{dueDate}
+
+ )}
-
- {companyName}
- 131 Continental Dr Suite 305 Newark,
-
- Delaware 19713
-
- United States
-
- +1 762-339-5634
-
- ahmed@bigcapital.app
-
+ {showBilledFromAddress && (
+
+
{companyName}
+ {billedFromAddres.map((text, index) => (
+
{text}
+ ))}
+
+ )}
-
- Billed To
- Bigcapital Technology, Inc.
- 131 Continental Dr Suite 305 Newark,
-
- Delaware 19713
-
- United States
-
- +1 762-339-5634
-
- ahmed@bigcapital.app
-
+ {showBillingToAddress && (
+
+
{billedToLabel}
+ {billedToAddress.map((text, index) => (
+
{text}
+ ))}
+
+ )}
- Item
- Description
- {itemRateLabel}
- {itemTotalLabel}
+ {lineItemLabel}
+ {lineDescriptionLabel}
+ {lineRateLabel}
+ {lineTotalLabel}
-
- Simply dummy text
- Simply dummy text of the printing and typesetting
- 1 X $100,00
- $100,00
-
+ {lines.map((line, index) => (
+
+ {line.item}
+ {line.description}
+
+ {line.quantity} X {line.rate}
+
+ {line.total}
+
+ ))}
@@ -159,32 +271,25 @@ export function InvoicePaperTemplate({
)}
>
{subtotalLabel}
-
630.00
+
{subtotal}
)}
{showDiscount && (
{discountLabel}
-
0.00
+
{discount}
)}
{showTaxes && (
<>
-
-
- Sample Tax1 (4.70%)
+ {taxes.map((tax, index) => (
+
+
{tax.label}
+
{tax.amount}
-
11.75
-
-
-
-
- Sample Tax2 (7.00%)
-
-
21.00
-
+ ))}
>
)}
@@ -193,14 +298,14 @@ export function InvoicePaperTemplate({
className={clsx(styles.totalsItem, styles.totalBottomBordered)}
>
{totalLabel}
-
$662.75
+
{total}
)}
{showPaymentMade && (
{paymentMadeLabel}
-
100.00
+
{paymentMade}
)}
@@ -209,27 +314,38 @@ export function InvoicePaperTemplate({
className={clsx(styles.totalsItem, styles.totalBottomBordered)}
>
{balanceDueLabel}
- $562.75
+ {balanceDue}
)}
-
-
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({
+
+
+
+ }
+ >
+ } minimal={true} />
+
+
}
@@ -191,4 +224,5 @@ export default compose(
paymentReceivesTableSize: paymentReceiveSettings?.tableSize,
})),
withDialogActions,
+ withDrawerActions,
)(PaymentsReceivedActionsBar);
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx
index 684c5bda7..30f41182e 100644
--- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx
@@ -1,3 +1,37 @@
+import React from 'react';
+import { Box } from '@/components';
+import { Classes } from '@blueprintjs/core';
+import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize';
+import { ReceiptCustomizeGeneralField } from './ReceiptCustomizeFieldsGeneral';
+import { ReceiptCustomizeFieldsContent } from './ReceiptCustomizeFieldsContent';
+import { ReceiptCustomizeValues } from './types';
+import { initialValues } from './constants';
+
export default function ReceiptCustomizeContent() {
- return asdasdasd ;
+ const handleFormSubmit = (values: ReceiptCustomizeValues) => {};
+
+ return (
+
+
+ initialValues={initialValues}
+ onSubmit={handleFormSubmit}
+ >
+ {/*
+
+ */}
+
+
+
+
+
+
+
+
+
+
+ asdfasdfdsaf #3
+
+
+
+ );
}
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsContent.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsContent.tsx
new file mode 100644
index 000000000..ed73871e5
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsContent.tsx
@@ -0,0 +1,22 @@
+// @ts-nocheck
+import { Stack } from '@/components';
+import { Classes } from '@blueprintjs/core';
+
+export function ReceiptCustomizeFieldsContent() {
+ 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/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsGeneral.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsGeneral.tsx
new file mode 100644
index 000000000..dede57674
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsGeneral.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 ReceiptCustomizeGeneralField() {
+ 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/Receipts/ReceiptCustomize/constants.ts b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/constants.ts
new file mode 100644
index 000000000..4a9bb97dd
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/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.
+ showReceiptNumber: true,
+ receiptNumberLabel: '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/Receipts/ReceiptCustomize/types.ts b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/types.ts
new file mode 100644
index 000000000..712682506
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/types.ts
@@ -0,0 +1,58 @@
+export interface ReceiptCustomizeValues {
+ // 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;
+}
From 77a1e35ff43d6d3785d264857bb1da894e4f366e Mon Sep 17 00:00:00 2001
From: Ahmed Bouhuolia
Date: Tue, 10 Sep 2024 18:37:38 +0200
Subject: [PATCH 11/32] feat: Paper template reusable
---
.../CreditNoteCustomizeContent.tsx | 3 +-
.../CreditNotePaperTemplate.tsx | 161 ++++++++++++++++
.../EstimateCustomizeContent.tsx | 7 +-
.../EstimatePaperTemplate.tsx | 175 ++++++++++++++++++
.../InvoiceCustomize/PaperTemplate.tsx | 139 ++++++++++++++
.../PaymentReceivedCustomizeContent.tsx | 7 +-
.../PaymentReceivedPaperTemplate.tsx | 127 +++++++++++++
.../ReceiptCustomizeContent.tsx | 8 +-
.../ReceiptCustomize/ReceiptPaperTemplate.tsx | 161 ++++++++++++++++
9 files changed, 777 insertions(+), 11 deletions(-)
create mode 100644 packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNotePaperTemplate.tsx
create mode 100644 packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimatePaperTemplate.tsx
create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx
create mode 100644 packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedPaperTemplate.tsx
create mode 100644 packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptPaperTemplate.tsx
diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx
index 6d26b5bd5..f2893cd60 100644
--- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx
@@ -5,6 +5,7 @@ import { Classes } from '@blueprintjs/core';
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';
@@ -18,7 +19,7 @@ export default function CreditNoteCustomizeContent() {
onSubmit={handleFormSubmit}
>
- {/* */}
+
diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNotePaperTemplate.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNotePaperTemplate.tsx
new file mode 100644
index 000000000..895d74eda
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNotePaperTemplate.tsx
@@ -0,0 +1,161 @@
+import { Group, Stack } from '@/components';
+import { PaperTemplate, PaperTemplateProps } from '../../Invoices/InvoiceCustomize/PaperTemplate';
+
+export interface CreditNotePaperTemplateProps extends PaperTemplateProps {
+ billedToAddress?: Array;
+ billedFromAddress?: Array;
+
+ total?: string;
+ showTotal?: boolean;
+ totalLabel?: string;
+
+ subtotal?: string;
+ showSubtotal?: boolean;
+ subtotalLabel?: string;
+
+ showCustomerNote?: boolean;
+ customerNote?: string;
+ customerNoteLabel?: string;
+
+ showTermsConditions?: boolean;
+ termsConditions?: string;
+ termsConditionsLabel?: string;
+
+ lines?: Array<{
+ item: string;
+ description: string;
+ rate: string;
+ quantity: string;
+ total: string;
+ }>;
+
+ creditNoteDateLabel?: string;
+ showCreditNoteDate?: boolean;
+ creditNoteDate?: string;
+
+ creditNoteNumebr?: string;
+ creditNoteNumberLabel?: string;
+ showCreditNoteNumber?: boolean;
+}
+
+export function CreditNotePaperTemplate({
+ primaryColor,
+ secondaryColor,
+ showCompanyLogo = true,
+ companyLogo,
+
+ billedToAddress = [
+ 'Bigcapital Technology, Inc.',
+ '131 Continental Dr Suite 305 Newark,',
+ 'Delaware 19713',
+ 'United States',
+ '+1 762-339-5634',
+ 'ahmed@bigcapital.app',
+ ],
+ billedFromAddress = [
+ '131 Continental Dr Suite 305 Newark,',
+ 'Delaware 19713',
+ 'United States',
+ '+1 762-339-5634',
+ 'ahmed@bigcapital.app',
+ ],
+ total = '$1000.00',
+ totalLabel = 'Total',
+ showTotal = true,
+
+ subtotal = '1000/00',
+ subtotalLabel = 'Subtotal',
+ showSubtotal = true,
+
+ 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',
+
+ 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',
+
+ lines = [
+ {
+ item: 'Simply dummy text',
+ description: 'Simply dummy text of the printing and typesetting',
+ rate: '1',
+ quantity: '1000',
+ total: '$1000.00',
+ },
+ ],
+ showCreditNoteNumber = true,
+ creditNoteNumberLabel = 'Credit Note Number',
+ creditNoteNumebr = '346D3D40-0001',
+
+ creditNoteDate = 'September 3, 2024',
+ showCreditNoteDate = true,
+ creditNoteDateLabel = 'Credit Note Date',
+}: CreditNotePaperTemplateProps) {
+ return (
+
+
+
+ {showCreditNoteNumber && (
+
+ {creditNoteNumebr}
+
+ )}
+
+ {showCreditNoteDate && (
+
+ {creditNoteDate}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {showSubtotal && (
+
+ )}
+ {showTotal && (
+
+ )}
+
+
+
+
+ {showCustomerNote && (
+
+ {customerNote}
+
+ )}
+ {showTermsConditions && (
+
+ {termsConditions}
+
+ )}
+
+
+
+ );
+}
diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx
index 38f8fbe43..dc11aa27a 100644
--- a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx
+++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx
@@ -4,6 +4,7 @@ import { Classes } from '@blueprintjs/core';
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';
@@ -16,9 +17,9 @@ export default function EstimateCustomizeContent() {
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
- {/*
-
- */}
+
+
+
diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimatePaperTemplate.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimatePaperTemplate.tsx
new file mode 100644
index 000000000..3e76564bd
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimatePaperTemplate.tsx
@@ -0,0 +1,175 @@
+import { Group, Stack } from '@/components';
+import { PaperTemplate, PaperTemplateProps } from '../../Invoices/InvoiceCustomize/PaperTemplate';
+
+export interface EstimatePaperTemplateProps extends PaperTemplateProps {
+ billedToAddress?: Array;
+ billedFromAddress?: Array;
+
+ total?: string;
+ showTotal?: boolean;
+ totalLabel?: string;
+
+ subtotal?: string;
+ showSubtotal?: boolean;
+ subtotalLabel?: string;
+
+ showCustomerNote?: boolean;
+ customerNote?: string;
+ customerNoteLabel?: string;
+
+ showTermsConditions?: boolean;
+ termsConditions?: string;
+ termsConditionsLabel?: string;
+
+ lines?: Array<{
+ item: string;
+ description: string;
+ rate: string;
+ quantity: string;
+ total: string;
+ }>;
+
+ expirationDate?: string;
+ showExpirationDate?: boolean;
+ expirationDateLabel?: string;
+
+ estimateDateLabel?: string;
+ showEstimateDate?: boolean;
+ estimateDate?: string;
+
+ estimateNumebr?: string;
+ estimateNumberLabel?: string;
+ showEstimateNumber?: boolean;
+}
+
+export function EstimatePaperTemplate({
+ primaryColor,
+ secondaryColor,
+ showCompanyLogo = true,
+ companyLogo,
+
+ billedToAddress = [
+ 'Bigcapital Technology, Inc.',
+ '131 Continental Dr Suite 305 Newark,',
+ 'Delaware 19713',
+ 'United States',
+ '+1 762-339-5634',
+ 'ahmed@bigcapital.app',
+ ],
+ billedFromAddress = [
+ '131 Continental Dr Suite 305 Newark,',
+ 'Delaware 19713',
+ 'United States',
+ '+1 762-339-5634',
+ 'ahmed@bigcapital.app',
+ ],
+ total = '$1000.00',
+ totalLabel = 'Total',
+ showTotal = true,
+
+ subtotal = '1000/00',
+ subtotalLabel = 'Subtotal',
+ showSubtotal = true,
+
+ 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',
+
+ 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',
+
+ lines = [
+ {
+ item: 'Simply dummy text',
+ description: 'Simply dummy text of the printing and typesetting',
+ rate: '1',
+ quantity: '1000',
+ total: '$1000.00',
+ },
+ ],
+ showEstimateNumber = true,
+ estimateNumberLabel = 'Estimate Number',
+ estimateNumebr = '346D3D40-0001',
+
+ estimateDate = 'September 3, 2024',
+ showEstimateDate = true,
+ estimateDateLabel = 'Estimate Date',
+
+ expirationDateLabel = 'Expiration Date',
+ showExpirationDate = true,
+ expirationDate = 'September 3, 2024',
+}: EstimatePaperTemplateProps) {
+ return (
+
+
+
+ {showEstimateNumber && (
+
+ {estimateNumebr}
+
+ )}
+
+ {showEstimateDate && (
+
+ {estimateDate}
+
+ )}
+
+ {showExpirationDate && (
+
+ {expirationDate}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {showSubtotal && (
+
+ )}
+ {showTotal && (
+
+ )}
+
+
+
+
+ {showCustomerNote && (
+
+ {customerNote}
+
+ )}
+ {showTermsConditions && (
+
+ {termsConditions}
+
+ )}
+
+
+
+ );
+}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx
new file mode 100644
index 000000000..aa29523e9
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx
@@ -0,0 +1,139 @@
+import React from 'react';
+import clsx from 'classnames';
+import { get } from 'lodash';
+import { Stack } from '@/components';
+import styles from './InvoicePaperTemplate.module.scss';
+
+export interface PaperTemplateProps {
+ primaryColor?: string;
+ secondaryColor?: string;
+
+ showCompanyLogo?: boolean;
+ companyLogo?: string;
+
+ bigtitle?: string;
+
+ children?: React.ReactNode;
+}
+
+export function PaperTemplate({
+ primaryColor,
+ secondaryColor,
+ showCompanyLogo,
+ companyLogo,
+ bigtitle = 'Invoice',
+ children,
+}: PaperTemplateProps) {
+ return (
+
+
+
+
+
{bigtitle}
+
+ {showCompanyLogo && (
+
+
+
+ )}
+
+
+ {children}
+
+ );
+}
+
+interface PaperTemplateTableProps {
+ columns: Array<{ accessor: string; label: string }>;
+ data: Array>;
+}
+
+PaperTemplate.Table = ({ columns, data }: PaperTemplateTableProps) => {
+ return (
+
+
+
+ {columns.map((col, index) => (
+ {col.label}
+ ))}
+
+
+
+
+ {data.map((_data: any) => (
+
+ {columns.map((column, index) => (
+ {get(_data, column.accessor)}
+ ))}
+
+ ))}
+
+
+ );
+};
+
+PaperTemplate.Totals = ({ children }: { children: React.ReactNode }) => {
+ return {children}
;
+};
+PaperTemplate.TotalLine = ({
+ label,
+ amount,
+}: {
+ label: string;
+ amount: string;
+}) => {
+ return (
+
+ );
+};
+
+PaperTemplate.MutedText = () => {};
+
+PaperTemplate.Text = () => {};
+
+PaperTemplate.Address = ({ items }: { items: Array }) => {
+ return (
+
+ {items.map((item, index) => (
+ {item}
+ ))}
+
+ );
+};
+
+PaperTemplate.Statement = ({
+ label,
+ children,
+}: {
+ label: string;
+ children: React.ReactNode;
+}) => {
+ return (
+
+ {label &&
{label}
}
+
{children}
+
+ );
+};
+
+PaperTemplate.TermsList = ({ children }: { children: React.ReactNode }) => {
+ return {children}
;
+};
+
+PaperTemplate.TermsItem = ({
+ label,
+ children,
+}: {
+ label: string;
+ children: React.ReactNode;
+}) => {
+ return (
+
+ );
+};
diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx
index 75afcdf12..b2308e619 100644
--- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx
+++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx
@@ -5,6 +5,7 @@ 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';
export default function PaymentReceivedCustomizeContent() {
@@ -16,9 +17,9 @@ export default function PaymentReceivedCustomizeContent() {
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
- {/*
-
- */}
+
+
+
diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedPaperTemplate.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedPaperTemplate.tsx
new file mode 100644
index 000000000..d43e9010a
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedPaperTemplate.tsx
@@ -0,0 +1,127 @@
+import { Group, Stack } from '@/components';
+import { PaperTemplate, PaperTemplateProps } from '../../Invoices/InvoiceCustomize/PaperTemplate';
+
+export interface PaymentReceivedPaperTemplateProps extends PaperTemplateProps {
+ billedToAddress?: Array;
+ billedFromAddress?: Array;
+
+ total?: string;
+ showTotal?: boolean;
+ totalLabel?: string;
+
+ subtotal?: string;
+ showSubtotal?: boolean;
+ subtotalLabel?: string;
+
+ lines?: Array<{
+ paidAmount: string;
+ invoiceAmount: string;
+ invoiceNumber: string;
+ }>;
+
+ paymentReceivedDateLabel?: string;
+ showPaymentReceivedDate?: boolean;
+ paymentReceivedDate?: string;
+
+ paymentReceivedNumebr?: string;
+ paymentReceivedNumberLabel?: string;
+ showPaymentReceivedNumber?: boolean;
+}
+
+export function PaymentReceivedPaperTemplate({
+ primaryColor,
+ secondaryColor,
+ showCompanyLogo = true,
+ companyLogo,
+
+ billedToAddress = [
+ 'Bigcapital Technology, Inc.',
+ '131 Continental Dr Suite 305 Newark,',
+ 'Delaware 19713',
+ 'United States',
+ '+1 762-339-5634',
+ 'ahmed@bigcapital.app',
+ ],
+ billedFromAddress = [
+ '131 Continental Dr Suite 305 Newark,',
+ 'Delaware 19713',
+ 'United States',
+ '+1 762-339-5634',
+ 'ahmed@bigcapital.app',
+ ],
+ total = '$1000.00',
+ totalLabel = 'Total',
+ showTotal = true,
+
+ subtotal = '1000/00',
+ subtotalLabel = 'Subtotal',
+ showSubtotal = true,
+
+ lines = [
+ {
+ invoiceNumber: 'INV-00001',
+ invoiceAmount: '$1000.00',
+ paidAmount: '$1000.00',
+ },
+ ],
+ showPaymentReceivedNumber = true,
+ paymentReceivedNumberLabel = 'Payment Number',
+ paymentReceivedNumebr = '346D3D40-0001',
+
+ paymentReceivedDate = 'September 3, 2024',
+ showPaymentReceivedDate = true,
+ paymentReceivedDateLabel = 'Payment Date',
+}: PaymentReceivedPaperTemplateProps) {
+ return (
+
+
+
+ {showPaymentReceivedNumber && (
+
+ {paymentReceivedNumebr}
+
+ )}
+
+ {showPaymentReceivedDate && (
+
+ {paymentReceivedDate}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {showSubtotal && (
+
+ )}
+ {showTotal && (
+
+ )}
+
+
+
+
+ );
+}
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx
index 30f41182e..439f0d7dc 100644
--- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx
@@ -1,9 +1,9 @@
-import React from 'react';
import { Box } from '@/components';
import { Classes } from '@blueprintjs/core';
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';
@@ -16,9 +16,9 @@ export default function ReceiptCustomizeContent() {
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
- {/*
-
- */}
+
+
+
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptPaperTemplate.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptPaperTemplate.tsx
new file mode 100644
index 000000000..148e34d5d
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptPaperTemplate.tsx
@@ -0,0 +1,161 @@
+import { Group, Stack } from '@/components';
+import { PaperTemplate, PaperTemplateProps } from '../../Invoices/InvoiceCustomize/PaperTemplate';
+
+export interface ReceiptPaperTemplateProps extends PaperTemplateProps {
+ billedToAddress?: Array;
+ billedFromAddress?: Array;
+
+ total?: string;
+ showTotal?: boolean;
+ totalLabel?: string;
+
+ subtotal?: string;
+ showSubtotal?: boolean;
+ subtotalLabel?: string;
+
+ showCustomerNote?: boolean;
+ customerNote?: string;
+ customerNoteLabel?: string;
+
+ showTermsConditions?: boolean;
+ termsConditions?: string;
+ termsConditionsLabel?: string;
+
+ lines?: Array<{
+ item: string;
+ description: string;
+ rate: string;
+ quantity: string;
+ total: string;
+ }>;
+
+ receiptDateLabel?: string;
+ showReceiptDate?: boolean;
+ receiptDate?: string;
+
+ receiptNumebr?: string;
+ receiptNumberLabel?: string;
+ showReceiptNumber?: boolean;
+}
+
+export function ReceiptPaperTemplate({
+ primaryColor,
+ secondaryColor,
+ showCompanyLogo = true,
+ companyLogo,
+
+ billedToAddress = [
+ 'Bigcapital Technology, Inc.',
+ '131 Continental Dr Suite 305 Newark,',
+ 'Delaware 19713',
+ 'United States',
+ '+1 762-339-5634',
+ 'ahmed@bigcapital.app',
+ ],
+ billedFromAddress = [
+ '131 Continental Dr Suite 305 Newark,',
+ 'Delaware 19713',
+ 'United States',
+ '+1 762-339-5634',
+ 'ahmed@bigcapital.app',
+ ],
+ total = '$1000.00',
+ totalLabel = 'Total',
+ showTotal = true,
+
+ subtotal = '1000/00',
+ subtotalLabel = 'Subtotal',
+ showSubtotal = true,
+
+ 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',
+
+ 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',
+
+ lines = [
+ {
+ item: 'Simply dummy text',
+ description: 'Simply dummy text of the printing and typesetting',
+ rate: '1',
+ quantity: '1000',
+ total: '$1000.00',
+ },
+ ],
+ showReceiptNumber = true,
+ receiptNumberLabel = 'Receipt Number',
+ receiptNumebr = '346D3D40-0001',
+
+ receiptDate = 'September 3, 2024',
+ showReceiptDate = true,
+ receiptDateLabel = 'Receipt Date',
+}: ReceiptPaperTemplateProps) {
+ return (
+
+
+
+ {showReceiptNumber && (
+
+ {receiptNumebr}
+
+ )}
+
+ {showReceiptDate && (
+
+ {receiptDate}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ {showSubtotal && (
+
+ )}
+ {showTotal && (
+
+ )}
+
+
+
+
+ {showCustomerNote && (
+
+ {customerNote}
+
+ )}
+ {showTermsConditions && (
+
+ {termsConditions}
+
+ )}
+
+
+
+ );
+}
From 716dec799a30711b20e1ddce3cfecaeba4ede8d7 Mon Sep 17 00:00:00 2001
From: Ahmed Bouhuolia
Date: Tue, 10 Sep 2024 21:54:37 +0200
Subject: [PATCH 12/32] feat: paper template customize
---
.../ElementCustomizeFieldsGroup.tsx | 40 +++
.../CreditNoteCustomizeContent.tsx | 11 +-
.../CreditNoteCustomizeGeneralFields.tsx | 6 +-
.../EstimateCustomizeContent.tsx | 9 +-
.../EstimateCustomizeFieldsGeneral.tsx | 6 +-
.../EstimatePaperTemplate.tsx | 37 ++-
.../Estimates/EstimateCustomize/constants.ts | 36 +--
.../Estimates/EstimateCustomize/types.ts | 31 +--
.../InvoiceCustomizeGeneralFields.tsx | 7 +-
.../InvoiceCutomizeContentFields.tsx | 40 ++-
.../InvoicePaperTemplate.module.scss | 3 -
.../InvoiceCustomize/InvoicePaperTemplate.tsx | 245 +++++++-----------
.../InvoiceCustomize/PaperTemplate.tsx | 6 +-
.../PaymentReceivedCustomizeContent.tsx | 11 +-
.../PaymentReceivedCustomizeFieldsGeneral.tsx | 6 +-
.../ReceiptCustomizeContent.tsx | 9 +-
.../ReceiptCustomizeFieldsGeneral.tsx | 7 +-
.../Receipts/ReceiptCustomize/constants.ts | 24 +-
.../Sales/Receipts/ReceiptCustomize/types.ts | 28 +-
19 files changed, 269 insertions(+), 293 deletions(-)
create mode 100644 packages/webapp/src/containers/ElementCustomize/ElementCustomizeFieldsGroup.tsx
diff --git a/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFieldsGroup.tsx b/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFieldsGroup.tsx
new file mode 100644
index 000000000..0e406f4f2
--- /dev/null
+++ b/packages/webapp/src/containers/ElementCustomize/ElementCustomizeFieldsGroup.tsx
@@ -0,0 +1,40 @@
+// @ts-nocheck
+import { FInputGroup, FSwitch, Group, Stack } from '@/components';
+import { CLASSES } from '@/constants';
+import { InputGroupProps, SwitchProps } from '@blueprintjs/core';
+
+export function ElementCustomizeFieldsGroup({
+ label,
+ children,
+}: {
+ label: string;
+ children: React.ReactNode;
+}) {
+ return (
+
+
+ {label}
+
+
+ {children}
+
+ );
+}
+
+export function ElementCustomizeContentItemFieldGroup({
+ inputGroupProps,
+ switchProps,
+}: {
+ inputGroupProps: InputGroupProps;
+ switchProps?: SwitchProps;
+}) {
+ return (
+
+
+
+ {switchProps?.name && (
+
+ )}
+
+ );
+}
diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx
index f2893cd60..4ff664f89 100644
--- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx
@@ -1,13 +1,12 @@
-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 { CreditNotePaperTemplate } from './CreditNotePaperTemplate';
import { CreditNoteCustomizeValues } from './types';
import { initialValues } from './constants';
+import { useFormikContext } from 'formik';
export default function CreditNoteCustomizeContent() {
const handleFormSubmit = (values: CreditNoteCustomizeValues) => {};
@@ -19,7 +18,7 @@ export default function CreditNoteCustomizeContent() {
onSubmit={handleFormSubmit}
>
-
+
@@ -37,3 +36,9 @@ export default function CreditNoteCustomizeContent() {
);
}
+
+function CreditNotePaperTemplateFormConnected() {
+ const { values } = useFormikContext();
+
+ return ;
+}
diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeGeneralFields.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeGeneralFields.tsx
index 41f06a231..9c8b84c7c 100644
--- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeGeneralFields.tsx
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeGeneralFields.tsx
@@ -18,7 +18,7 @@ export function CreditNoteCustomizeGeneralField() {
@@ -32,7 +32,7 @@ export function CreditNoteCustomizeGeneralField() {
@@ -47,7 +47,7 @@ export function CreditNoteCustomizeGeneralField() {
diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx
index dc11aa27a..15250b8e7 100644
--- a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx
+++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx
@@ -7,6 +7,7 @@ import { EstimateCustomizeContentFields } from './EstimateCustomizeFieldsContent
import { EstimatePaperTemplate } from './EstimatePaperTemplate';
import { EstimateCustomizeValues } from './types';
import { initialValues } from './constants';
+import { useFormikContext } from 'formik';
export default function EstimateCustomizeContent() {
const handleFormSubmit = (values: EstimateCustomizeValues) => {};
@@ -18,7 +19,7 @@ export default function EstimateCustomizeContent() {
onSubmit={handleFormSubmit}
>
-
+
@@ -36,3 +37,9 @@ export default function EstimateCustomizeContent() {
);
}
+
+function EstimatePaperTemplateFormConnected() {
+ const { values } = useFormikContext();
+
+ return ;
+}
diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx
index 707a0b44f..b1fcc0d81 100644
--- a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx
+++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeFieldsGeneral.tsx
@@ -19,7 +19,7 @@ export function EstimateCustomizeGeneralField() {
@@ -33,7 +33,7 @@ export function EstimateCustomizeGeneralField() {
@@ -48,7 +48,7 @@ export function EstimateCustomizeGeneralField() {
diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimatePaperTemplate.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimatePaperTemplate.tsx
index 3e76564bd..5432e5d45 100644
--- a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimatePaperTemplate.tsx
+++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimatePaperTemplate.tsx
@@ -1,10 +1,32 @@
import { Group, Stack } from '@/components';
-import { PaperTemplate, PaperTemplateProps } from '../../Invoices/InvoiceCustomize/PaperTemplate';
+import {
+ PaperTemplate,
+ PaperTemplateProps,
+} from '../../Invoices/InvoiceCustomize/PaperTemplate';
export interface EstimatePaperTemplateProps extends PaperTemplateProps {
+ estimateNumebr?: string;
+ estimateNumberLabel?: string;
+ showEstimateNumber?: boolean;
+
+ expirationDate?: string;
+ showExpirationDate?: boolean;
+ expirationDateLabel?: string;
+
+ estimateDateLabel?: string;
+ showEstimateDate?: boolean;
+ estimateDate?: string;
+
+ companyName?: string;
+
+ // Address
+ showBilledToAddress?: boolean;
billedToAddress?: Array;
+
+ showBilledFromAddress?: boolean;
billedFromAddress?: Array;
+ // Totals
total?: string;
showTotal?: boolean;
totalLabel?: string;
@@ -13,6 +35,7 @@ export interface EstimatePaperTemplateProps extends PaperTemplateProps {
showSubtotal?: boolean;
subtotalLabel?: string;
+ // Statements
showCustomerNote?: boolean;
customerNote?: string;
customerNoteLabel?: string;
@@ -28,18 +51,6 @@ export interface EstimatePaperTemplateProps extends PaperTemplateProps {
quantity: string;
total: string;
}>;
-
- expirationDate?: string;
- showExpirationDate?: boolean;
- expirationDateLabel?: string;
-
- estimateDateLabel?: string;
- showEstimateDate?: boolean;
- estimateDate?: string;
-
- estimateNumebr?: string;
- estimateNumberLabel?: string;
- showEstimateNumber?: boolean;
}
export function EstimatePaperTemplate({
diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/constants.ts b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/constants.ts
index ad6b6f8d4..3f63a4685 100644
--- a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/constants.ts
+++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/constants.ts
@@ -1,4 +1,3 @@
-
export const initialValues = {
// Colors
primaryColor: '#2c3dd8',
@@ -13,8 +12,8 @@ export const initialValues = {
showEstimateNumber: true,
estimateNumberLabel: 'Estimate number',
- showDateIssue: true,
- dateIssueLabel: 'Date of Issue',
+ estimateDateLabel: 'Date of Issue',
+ showEstimateDate: true,
showExpirationDate: true,
expirationDateLabel: 'Expiration Date',
@@ -37,24 +36,17 @@ export const initialValues = {
showSubtotal: true,
subtotalLabel: 'Subtotal',
- showDiscount: true,
- discountLabel: 'Discount',
-
- showTaxes: true,
-
showTotal: true,
totalLabel: 'Total',
- // Footer paragraphs.
- termsConditionsLabel: 'Terms & Conditions',
+ // Statements
+ showCustomerNote: true,
+ customerNoteLabel: 'Customer Note',
+
showTermsConditions: true,
-
- statementLabel: 'Statement',
- showStatement: true,
+ termsConditionsLabel: 'Terms & Conditions',
};
-
-
export const fieldsGroups = [
{
label: 'Header',
@@ -65,8 +57,8 @@ export const fieldsGroups = [
label: 'Estimate No.',
},
{
- labelKey: 'dateIssueLabel',
- enableKey: 'showDateIssue',
+ labelKey: 'estimateDateLabel',
+ enableKey: 'showEstimateDate',
label: 'Issue Date',
},
{
@@ -93,12 +85,6 @@ export const fieldsGroups = [
enableKey: 'showSubtotal',
label: 'Subtotal',
},
- {
- labelKey: 'discountLabel',
- enableKey: 'showDiscount',
- label: 'Discount',
- },
- { enableKey: 'showTaxes', label: 'Taxes' },
{ labelKey: 'totalLabel', enableKey: 'showTotal', label: 'Total' },
],
},
@@ -111,8 +97,8 @@ export const fieldsGroups = [
label: 'Terms & Conditions',
},
{
- labelKey: 'statementLabel',
- enableKey: 'showStatement',
+ labelKey: 'customerNoteLabel',
+ enableKey: 'showCustomerNote',
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
index 7dbf7182a..d51cc8304 100644
--- a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/types.ts
+++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/types.ts
@@ -8,14 +8,14 @@ export interface EstimateCustomizeValues {
companyLogo?: string;
// Top details.
- showInvoiceNumber?: boolean;
- invoiceNumberLabel?: string;
+ estimateNumberLabel?: string;
+ showEstimateNumber?: boolean;
- showDateIssue?: boolean;
- dateIssueLabel?: string;
+ showExpirationDate?: boolean;
+ expirationDateLabel?: string;
- showDueDate?: boolean;
- dueDateLabel?: string;
+ estimateDateLabel?: string;
+ showEstimateDate?: boolean;
// Company name
companyName?: string;
@@ -35,24 +35,13 @@ export interface EstimateCustomizeValues {
showSubtotal?: boolean;
subtotalLabel?: string;
- showDiscount?: boolean;
- discountLabel?: string;
-
- showTaxes?: boolean;
-
showTotal?: boolean;
totalLabel?: string;
- paymentMadeLabel?: string;
- showPaymentMade?: boolean;
+ // Statements
+ showCustomerNote?: boolean;
+ customerNoteLabel?: string;
- dueAmountLabel?: string;
- showDueAmount?: boolean;
-
- // Footer paragraphs.
- termsConditionsLabel?: string;
showTermsConditions?: boolean;
-
- statementLabel?: string;
- showStatement?: boolean;
+ termsConditionsLabel?: string;
}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx
index 43397bd64..39c0a3c3f 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeGeneralFields.tsx
@@ -3,7 +3,6 @@ import { Classes, Text } from '@blueprintjs/core';
import { FFormGroup, FSwitch, Group, Stack } from '@/components';
import { FColorInput } from '@/components/Forms/FColorInput';
import { CreditCardIcon } from '@/icons/CreditCardIcon';
-import styles from './InvoiceCustomizeFields.module.scss';
export function InvoiceCustomizeGeneralField() {
return (
@@ -20,7 +19,7 @@ export function InvoiceCustomizeGeneralField() {
@@ -34,7 +33,7 @@ export function InvoiceCustomizeGeneralField() {
@@ -49,7 +48,7 @@ export function InvoiceCustomizeGeneralField() {
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx
index fd1188d20..98c4cbfe9 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCutomizeContentFields.tsx
@@ -1,8 +1,11 @@
// @ts-nocheck
-import { FInputGroup, FSwitch, Group, Stack } from '@/components';
-import { CLASSES } from '@/constants';
+import { Stack } from '@/components';
import { Classes } from '@blueprintjs/core';
import { fieldsGroups } from './constants';
+import {
+ ElementCustomizeFieldsGroup,
+ ElementCustomizeContentItemFieldGroup,
+} from '@/containers/ElementCustomize/ElementCustomizeFieldsGroup';
export function InvoiceCustomizeContentFields() {
return (
@@ -20,25 +23,20 @@ export function InvoiceCustomizeContentFields() {
{fieldsGroups.map((group) => (
- <>
-
- {group.label}
-
-
- {group.fields.map((item, index) => (
-
-
- {item.labelKey && (
-
- )}
-
- ))}
-
- >
+
+ {group.fields.map((item, index) => (
+
+ ))}
+
))}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.module.scss b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.module.scss
index 9cc71dcb1..2d562e31a 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.module.scss
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.module.scss
@@ -22,7 +22,6 @@
}
.details {
- margin-bottom: 25px;
display: flex;
flex-direction: column;
gap: 4px;
@@ -40,7 +39,6 @@
.addressRoot{
display: flex;
flex-direction: row;
- margin-bottom: 25px;
}
.addressBillTo{
@@ -101,7 +99,6 @@
.totals{
display: flex;
flex-direction: column;
- margin-bottom: 40px;
margin-left: auto;
width: 300px;
}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx
index 7fb69d58b..4eae081a5 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx
@@ -2,6 +2,9 @@ import clsx from 'classnames';
import * as R from 'ramda';
import { useFormikContext } from 'formik';
import styles from './InvoicePaperTemplate.module.scss';
+import { PaperTemplate } from './PaperTemplate';
+import { Group, Stack } from '@/components';
+import React from 'react';
interface PapaerLine {
item?: string;
@@ -85,15 +88,14 @@ interface PaperTemplateProps {
lines?: Array;
taxes?: Array;
- billedFromAddres?: Array;
- billedToAddress?: Array;
+ billedFromAddres?: Array;
+ billedToAddress?: Array;
}
function InvoicePaperTemplateRoot({
primaryColor,
secondaryColor,
- bigtitle = 'Invoice',
companyName = 'Bigcapital Technology, Inc.',
showCompanyLogo = true,
@@ -183,156 +185,109 @@ function InvoicePaperTemplateRoot({
],
}: PaperTemplateProps) {
return (
-
-
-
-
-
{bigtitle}
-
- {showCompanyLogo && (
-
-
-
- )}
-
-
-
- {showInvoiceNumber && (
-
-
{invoiceNumberLabel}
-
{invoiceNumber}
-
- )}
- {showDateIssue && (
-
-
{dateIssueLabel}
-
{dateIssue}
-
- )}
- {showDueDate && (
-
-
{dueDateLabel}
-
{dueDate}
-
- )}
-
-
-
- {showBilledFromAddress && (
-
-
{companyName}
- {billedFromAddres.map((text, index) => (
-
{text}
- ))}
-
- )}
-
- {showBillingToAddress && (
-
-
{billedToLabel}
- {billedToAddress.map((text, index) => (
-
{text}
- ))}
-
- )}
-
-
-
-
-
- {lineItemLabel}
- {lineDescriptionLabel}
- {lineRateLabel}
- {lineTotalLabel}
-
-
-
-
- {lines.map((line, index) => (
-
- {line.item}
- {line.description}
-
- {line.quantity} X {line.rate}
-
- {line.total}
-
- ))}
-
-
-
-
-
- {showSubtotal && (
-
-
{subtotalLabel}
-
{subtotal}
-
+
+
+
+ {showInvoiceNumber && (
+
+ {invoiceNumber}
+
)}
-
- {showDiscount && (
-
-
{discountLabel}
-
{discount}
-
+ {showDateIssue && (
+
+ {dateIssue}
+
)}
-
- {showTaxes && (
- <>
- {taxes.map((tax, index) => (
-
-
{tax.label}
-
{tax.amount}
-
- ))}
- >
+ {showDueDate && (
+
+ {dueDate}
+
)}
+
- {showTotal && (
-
-
{totalLabel}
-
{total}
-
+
+ {showBilledFromAddress && (
+ {companyName}, ...billedFromAddres]}
+ />
)}
-
- {showPaymentMade && (
-
-
{paymentMadeLabel}
-
{paymentMade}
-
+ {showBillingToAddress && (
+
)}
+
- {showBalanceDue && (
-
-
{balanceDueLabel}
-
{balanceDue}
-
+
+
+
+ {showSubtotal && (
+
+ )}
+ {showDiscount && (
+
+ )}
+ {showTaxes && (
+ <>
+ {taxes.map((tax, index) => (
+
+ ))}
+ >
+ )}
+ {showTotal && (
+
+ )}
+ {showPaymentMade && (
+
+ )}
+ {showBalanceDue && (
+
+ )}
+
+
+
+
+ {showTermsConditions && (
+
+ {termsConditions}
+
)}
-
-
-
- {showTermsConditions && (
-
-
{termsConditionsLabel}
-
{termsConditions}
-
- )}
- {showStatement && (
-
-
{statementLabel}
-
{statement}
-
- )}
-
+ {showStatement && (
+
+ {statement}
+
+ )}
+
+
+
);
}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx
index aa29523e9..fde94244d 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/PaperTemplate.tsx
@@ -94,7 +94,11 @@ PaperTemplate.MutedText = () => {};
PaperTemplate.Text = () => {};
-PaperTemplate.Address = ({ items }: { items: Array }) => {
+PaperTemplate.Address = ({
+ items,
+}: {
+ items: Array;
+}) => {
return (
{items.map((item, index) => (
diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx
index b2308e619..67a28fb31 100644
--- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx
+++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx
@@ -1,6 +1,7 @@
import React from 'react';
-import { Box } from '@/components';
+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';
@@ -18,7 +19,7 @@ export default function PaymentReceivedCustomizeContent() {
onSubmit={handleFormSubmit}
>
-
+
@@ -36,3 +37,9 @@ export default function PaymentReceivedCustomizeContent() {
);
}
+
+function PaymentReceivedPaperTemplateFormConnected() {
+ const { values } = useFormikContext();
+
+ return ;
+}
diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsGeneral.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsGeneral.tsx
index 8f3158263..9dbfd5feb 100644
--- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsGeneral.tsx
+++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsGeneral.tsx
@@ -19,7 +19,7 @@ export function PaymentReceivedCustomizeGeneralField() {
@@ -33,7 +33,7 @@ export function PaymentReceivedCustomizeGeneralField() {
@@ -48,7 +48,7 @@ export function PaymentReceivedCustomizeGeneralField() {
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx
index 439f0d7dc..3b4246101 100644
--- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx
@@ -6,6 +6,7 @@ import { ReceiptCustomizeFieldsContent } from './ReceiptCustomizeFieldsContent';
import { ReceiptPaperTemplate } from './ReceiptPaperTemplate';
import { ReceiptCustomizeValues } from './types';
import { initialValues } from './constants';
+import { useFormikContext } from 'formik';
export default function ReceiptCustomizeContent() {
const handleFormSubmit = (values: ReceiptCustomizeValues) => {};
@@ -17,7 +18,7 @@ export default function ReceiptCustomizeContent() {
onSubmit={handleFormSubmit}
>
-
+
@@ -35,3 +36,9 @@ export default function ReceiptCustomizeContent() {
);
}
+
+function ReceiptPaperTemplateFormConnected() {
+ const { values } = useFormikContext();
+
+ return ;
+}
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsGeneral.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsGeneral.tsx
index dede57674..93b76c78b 100644
--- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsGeneral.tsx
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeFieldsGeneral.tsx
@@ -2,7 +2,6 @@
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 ReceiptCustomizeGeneralField() {
return (
@@ -19,7 +18,7 @@ export function ReceiptCustomizeGeneralField() {
@@ -33,7 +32,7 @@ export function ReceiptCustomizeGeneralField() {
@@ -48,7 +47,7 @@ export function ReceiptCustomizeGeneralField() {
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/constants.ts b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/constants.ts
index 4a9bb97dd..45930f6ef 100644
--- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/constants.ts
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/constants.ts
@@ -10,13 +10,10 @@ export const initialValues = {
// Top details.
showReceiptNumber: true,
- receiptNumberLabel: 'Invoice number',
+ receiptNumberLabel: 'Receipt number',
- showDateIssue: true,
- dateIssueLabel: 'Date of Issue',
-
- showDueDate: true,
- dueDateLabel: 'Due Date',
+ showReceiptDate: true,
+ receiptDateLabel: 'Date of Issue',
// Company name
companyName: 'Bigcapital Technology, Inc.',
@@ -36,24 +33,13 @@ export const initialValues = {
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,
+ customerNoteLabel: 'Customer Note',
+ showCustomerNote: true,
};
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/types.ts b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/types.ts
index 712682506..f411639ea 100644
--- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/types.ts
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/types.ts
@@ -8,14 +8,11 @@ export interface ReceiptCustomizeValues {
companyLogo?: string;
// Top details.
- showInvoiceNumber?: boolean;
- invoiceNumberLabel?: string;
+ showReceiptNumber?: boolean;
+ receiptNumberLabel?: string;
- showDateIssue?: boolean;
- dateIssueLabel?: string;
-
- showDueDate?: boolean;
- dueDateLabel?: string;
+ showReceiptDate?: boolean;
+ receiptDateLabel?: string;
// Company name
companyName?: string;
@@ -35,24 +32,13 @@ export interface ReceiptCustomizeValues {
showSubtotal?: boolean;
subtotalLabel?: string;
- showDiscount?: boolean;
- discountLabel?: string;
-
- showTaxes?: boolean;
-
showTotal?: boolean;
totalLabel?: string;
- paymentMadeLabel?: string;
- showPaymentMade?: boolean;
-
- dueAmountLabel?: string;
- showDueAmount?: boolean;
-
- // Footer paragraphs.
+ // Statements
termsConditionsLabel?: string;
showTermsConditions?: boolean;
- statementLabel?: string;
- showStatement?: boolean;
+ customerNoteLabel?: string;
+ showCustomerNote?: boolean;
}
From 4541d28b681a4d234e6dc9ca26c898a8bbd648f2 Mon Sep 17 00:00:00 2001
From: Ahmed Bouhuolia
Date: Tue, 10 Sep 2024 22:25:16 +0200
Subject: [PATCH 13/32] chore: dump CHANGELOG
---
CHANGELOG.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 80 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9949feb54..785bea9ab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,77 @@
All notable changes to Bigcapital server-side will be in this file.
-## [0.19.4] - 18-08-2024
+# [0.19.17]
+
+* fix: Un-categorize bank transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/663
+
+# [0.19.16]
+
+* feat: Tracking more Posthog events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/653
+* fix: Expense cannot accept credit card as payment account by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/654
+* fix: Suspense the lazy loaded components in banking pages by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/657
+* feat: Add help dropdown menu by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/656
+* feat: Bank pages layout breaking by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/658
+* feat: Datatable UI improvements by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/655
+* fix: Array cast of recognize function rule ids by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/660
+* fix: Payment made filling the form full amount field by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/661
+* feat: Tabular number of all money columns by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/659
+* refactor: The expense G/L writer by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/662
+
+## [0.19.15] -
+
+* fix: Bank transactions infinity scrolling by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/648
+* feat: Integrate multiple branches and warehouses to resource importing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/645
+* fix: Integrate multiple branches with expense resource by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/649
+* feat: Cover more tracking events. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/650
+* feat: Track banking service events by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/651
+
+## [0.19.14]
+
+* fix: Import bugs by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/643
+* fix: Set default index to transaction entries by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/644
+* feat(server): Events tracking using Posthog by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/646
+
+## [0.19.13]
+
+* fix: Subscription middleware by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/624
+* fix: Getting the sheet columns in import sheet by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/641
+
+## [0.19.12]
+
+* fix: Typo one-click demo page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/640
+
+## [0.19.11]
+
+* fix: Avoid running the cost job in import preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/635
+* fix: Debounce scheduling calculating items cost by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/634
+* fix: Expand the resources export page size limitation by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/636
+* feat: Optimize loading perf. by splitting big chunks and lazy loading them by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/632
+* fix: Use standard ISO 8601 format for exported data by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/638
+* fix: Add customer type to customers resource by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/639
+
+## [0.19.10]
+
+* fix: Add subscription plans offer text by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/629
+
+## [0.19.9]
+
+* fix: Make webapp package env variables dynamic by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/628
+
+## [v0.19.8]
+
+* fix: Cannot import items income and cost accounts by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/617
+* fix: Some bank account details hidden by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/618
+* feat(ee): One-click demo account by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/616
+* feat: change banking service language by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/619
+* feat(banking): Filter uncategorized bank transactions by date by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/590
+* Fix: Syntax error caused error by @wolone in https://github.com/bigcapitalhq/bigcapital/pull/622
+* fix: Listen to payment webhooks by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/623
+* fix: Add prefix J-00001 to manual journals increments by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/625
+* fix: Disable sms service until Twilo integration by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/626
+* fix: Style tweaks in onboarding page by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/627
+
+## [0.19.5] - 18-08-2024
* fix: Allow multi-lines to statements transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/594
* feat: Add amount comparators to amount bank rule field by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/595
@@ -23,6 +93,15 @@ All notable changes to Bigcapital server-side will be in this file.
* fix: Delete bank account with uncategorized transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/614
* feat: activate/inactivate account from drawer details by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/615
+## [v0.19.4]
+
+* feat: Import and export tax rates by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/591
+* feat: Un-categorize bank transactions in bulk by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/587
+* feat: Pending bank transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/589
+* fix: Update `dev` Script in `package.json` to Use `cross-env` by @Champetaman in https://github.com/bigcapitalhq/bigcapital/pull/588
+* fix: Should not load branches on reconcile matching form if the branches not enabled by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/592
+* fix: Rounding the total amount the pending and matched transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/593
+
## [v0.18.0] - 10-08-2024
* feat: Bank rules for automated categorization by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/511
From 5b6270a184186eafffbd73631845838874f8df06 Mon Sep 17 00:00:00 2001
From: Ahmed Bouhuolia
Date: Tue, 10 Sep 2024 23:32:34 +0200
Subject: [PATCH 14/32] feat: invoice pdf customize
---
.../CreditNoteCutomizeContentFields.tsx | 24 ++++-
.../CreditNotePaperTemplate.tsx | 6 ++
.../CreditNoteCustomize/constants.ts | 96 ++++++++++++-------
.../CreditNotes/CreditNoteCustomize/types.ts | 42 +++++++-
.../InvoiceCustomize/InvoiceCustomize.tsx | 22 ++++-
.../InvoiceCustomize/InvoicePaperTemplate.tsx | 26 +----
.../PaymentReceivedCustomizeFieldsContent.tsx | 24 ++++-
.../PaymentReceivedPaperTemplate.tsx | 26 ++++-
.../PaymentReceivedCustomize/constants.ts | 78 ++-------------
.../Receipts/ReceiptCustomize/constants.ts | 9 +-
10 files changed, 213 insertions(+), 140 deletions(-)
diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCutomizeContentFields.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCutomizeContentFields.tsx
index 9bd40caf6..923b59098 100644
--- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCutomizeContentFields.tsx
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCutomizeContentFields.tsx
@@ -1,6 +1,11 @@
// @ts-nocheck
import { Stack } from '@/components';
+import {
+ ElementCustomizeContentItemFieldGroup,
+ ElementCustomizeFieldsGroup,
+} from '@/containers/ElementCustomize/ElementCustomizeFieldsGroup';
import { Classes } from '@blueprintjs/core';
+import { fieldsGroups } from './constants';
export function CreditNoteCustomizeContentFields() {
return (
@@ -16,7 +21,24 @@ export function CreditNoteCustomizeContentFields() {
-
+
+ {fieldsGroups.map((group) => (
+
+ {group.fields.map((item, index) => (
+
+ ))}
+
+ ))}
+
);
}
diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNotePaperTemplate.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNotePaperTemplate.tsx
index 895d74eda..61de0f25a 100644
--- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNotePaperTemplate.tsx
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNotePaperTemplate.tsx
@@ -5,18 +5,22 @@ export interface CreditNotePaperTemplateProps extends PaperTemplateProps {
billedToAddress?: Array;
billedFromAddress?: Array;
+ // 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;
@@ -29,10 +33,12 @@ export interface CreditNotePaperTemplateProps extends PaperTemplateProps {
total: string;
}>;
+ // Date issue.
creditNoteDateLabel?: string;
showCreditNoteDate?: boolean;
creditNoteDate?: string;
+ // Credit Number.
creditNoteNumebr?: string;
creditNoteNumberLabel?: string;
showCreditNoteNumber?: boolean;
diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/constants.ts b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/constants.ts
index 2524d1e88..20ecaf45f 100644
--- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/constants.ts
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/constants.ts
@@ -8,52 +8,78 @@ export const initialValues = {
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,
-
+ // Total
showTotal: true,
totalLabel: 'Total',
- paymentMadeLabel: 'Payment Made',
- showPaymentMade: true,
+ // Subtotal
+ showSubtotal: true,
+ subtotalLabel: 'Subtotal',
- dueAmountLabel: 'Due Amount',
- showDueAmount: true,
+ // Customer Note.
+ showCustomerNote: true,
+ customerNoteLabel: 'Customer Note',
- // Footer paragraphs.
- termsConditionsLabel: 'Terms & Conditions',
+ // Terms & Conditions
showTermsConditions: true,
+ termsConditionsLabel: 'Terms & Conditions',
- statementLabel: 'Statement',
- showStatement: true,
+ // Date issue.
+ creditNoteDateLabel: 'Issue of Date',
+ showCreditNoteDate: true,
+
+ // Credit Number.
+ creditNoteNumberLabel: 'Credit Note #',
+ showCreditNoteNumber: true,
};
+
+export const fieldsGroups = [
+ {
+ label: 'Header',
+ fields: [
+ {
+ labelKey: 'creditNoteDateLabel',
+ enableKey: 'showCreditNoteDate',
+ label: 'Issue of Date',
+ },
+ {
+ labelKey: 'creditNoteNumberLabel',
+ enableKey: 'showCreditNoteNumber',
+ label: 'Credit Note #',
+ },
+ ],
+ },
+ {
+ label: 'Totals',
+ fields: [
+ {
+ labelKey: 'subtotalLabel',
+ enableKey: 'showSubtotal',
+ label: 'Subtotal',
+ },
+ { labelKey: 'totalLabel', enableKey: 'showTotal', label: 'Total' },
+ ],
+ },
+ {
+ label: 'Footer',
+ fields: [
+ {
+ labelKey: 'termsConditionsLabel',
+ enableKey: 'showTermsConditions',
+ label: 'Terms & Conditions',
+ },
+ {
+ labelKey: 'customerNoteLabel',
+ enableKey: 'showCustomerNote',
+ label: 'Customer Note',
+ labelPlaceholder: 'Customer Note',
+ },
+ ],
+ },
+];
diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/types.ts b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/types.ts
index 22067f1ce..1d8cfb5f7 100644
--- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/types.ts
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/types.ts
@@ -1,5 +1,39 @@
-
-
export interface CreditNoteCustomizeValues {
-
-}
\ No newline at end of file
+ // Colors
+ primaryColor?: string;
+ secondaryColor?: string;
+
+ // Company Logo
+ showCompanyLogo?: boolean;
+ companyLogo?: string;
+
+ // Entries
+ itemNameLabel?: string;
+ itemDescriptionLabel?: string;
+ itemRateLabel?: string;
+ itemTotalLabel?: string;
+
+ // Total
+ showTotal?: boolean;
+ totalLabel?: string;
+
+ // Subtotal
+ showSubtotal?: boolean;
+ subtotalLabel?: string;
+
+ // Customer Note.
+ showCustomerNote?: boolean;
+ customerNoteLabel?: string;
+
+ // Terms & Conditions
+ showTermsConditions?: boolean;
+ termsConditionsLabel?: string;
+
+ // Date issue.
+ creditNoteDateLabel?: string;
+ showCreditNoteDate?: boolean;
+
+ // Credit Number.
+ creditNoteNumberLabel?: string;
+ showCreditNoteNumber?: boolean;
+}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx
index 3eefb60f6..090cadc88 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx
@@ -1,7 +1,12 @@
import React from 'react';
+import * as R from 'ramda';
import { Box } from '@/components';
import { Classes } from '@blueprintjs/core';
-import { InvoicePaperTemplate } from './InvoicePaperTemplate';
+import { useFormikContext } from 'formik';
+import {
+ InvoicePaperTemplate,
+ InvoicePaperTemplateProps,
+} from './InvoicePaperTemplate';
import { ElementCustomize } from '../../../ElementCustomize/ElementCustomize';
import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields';
import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields';
@@ -18,7 +23,7 @@ export default function InvoiceCustomizeContent() {
onSubmit={handleFormSubmit}
>
-
+
@@ -36,3 +41,16 @@ export default function InvoiceCustomizeContent() {
);
}
+
+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/InvoicePaperTemplate.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx
index 4eae081a5..2fc4041dd 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoicePaperTemplate.tsx
@@ -1,10 +1,6 @@
-import clsx from 'classnames';
-import * as R from 'ramda';
-import { useFormikContext } from 'formik';
-import styles from './InvoicePaperTemplate.module.scss';
+import React from 'react';
import { PaperTemplate } from './PaperTemplate';
import { Group, Stack } from '@/components';
-import React from 'react';
interface PapaerLine {
item?: string;
@@ -19,7 +15,7 @@ interface PaperTax {
amount: string;
}
-interface PaperTemplateProps {
+export interface InvoicePaperTemplateProps {
primaryColor?: string;
secondaryColor?: string;
@@ -92,7 +88,7 @@ interface PaperTemplateProps {
billedToAddress?: Array;
}
-function InvoicePaperTemplateRoot({
+export function InvoicePaperTemplate({
primaryColor,
secondaryColor,
@@ -183,7 +179,7 @@ function InvoicePaperTemplateRoot({
'+1 762-339-5634',
'ahmed@bigcapital.app',
],
-}: PaperTemplateProps) {
+}: InvoicePaperTemplateProps) {
return (
);
}
-
-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/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsContent.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsContent.tsx
index 94519a9b6..fd284f437 100644
--- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsContent.tsx
+++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeFieldsContent.tsx
@@ -1,6 +1,11 @@
// @ts-nocheck
import { Stack } from '@/components';
import { Classes } from '@blueprintjs/core';
+import { fieldsGroups } from './constants';
+import {
+ ElementCustomizeContentItemFieldGroup,
+ ElementCustomizeFieldsGroup,
+} from '@/containers/ElementCustomize/ElementCustomizeFieldsGroup';
export function PaymentReceivedCustomizeContentFields() {
return (
@@ -16,7 +21,24 @@ export function PaymentReceivedCustomizeContentFields() {
-
+
+ {fieldsGroups.map((group) => (
+
+ {group.fields.map((item, index) => (
+
+ ))}
+
+ ))}
+
);
}
diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedPaperTemplate.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedPaperTemplate.tsx
index d43e9010a..ac0779d5a 100644
--- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedPaperTemplate.tsx
+++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedPaperTemplate.tsx
@@ -1,14 +1,23 @@
import { Group, Stack } from '@/components';
-import { PaperTemplate, PaperTemplateProps } from '../../Invoices/InvoiceCustomize/PaperTemplate';
+import {
+ PaperTemplate,
+ PaperTemplateProps,
+} from '../../Invoices/InvoiceCustomize/PaperTemplate';
export interface PaymentReceivedPaperTemplateProps extends PaperTemplateProps {
billedToAddress?: Array;
- billedFromAddress?: Array;
+ showBillingToAddress?: boolean;
+ billedFromAddress?: Array;
+ showBilledFromAddress?: boolean;
+ billedToLabel?: string;
+
+ // Total.
total?: string;
showTotal?: boolean;
totalLabel?: string;
+ // Subtotal.
subtotal?: string;
showSubtotal?: boolean;
subtotalLabel?: string;
@@ -19,10 +28,12 @@ export interface PaymentReceivedPaperTemplateProps extends PaperTemplateProps {
invoiceNumber: string;
}>;
+ // Issue date.
paymentReceivedDateLabel?: string;
showPaymentReceivedDate?: boolean;
paymentReceivedDate?: string;
+ // Payment received number.
paymentReceivedNumebr?: string;
paymentReceivedNumberLabel?: string;
showPaymentReceivedNumber?: boolean;
@@ -49,6 +60,9 @@ export function PaymentReceivedPaperTemplate({
'+1 762-339-5634',
'ahmed@bigcapital.app',
],
+ showBilledFromAddress,
+ showBillingToAddress,
+
total = '$1000.00',
totalLabel = 'Total',
showTotal = true,
@@ -96,8 +110,12 @@ export function PaymentReceivedPaperTemplate({
-
-
+ {showBillingToAddress && (
+
+ )}
+ {showBilledFromAddress && (
+
+ )}
diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/constants.ts b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/constants.ts
index ac4a03dae..da939b914 100644
--- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/constants.ts
+++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/constants.ts
@@ -9,14 +9,11 @@ export const initialValues = {
'https://cdn-development.mercury.com/demo-assets/avatars/mercury-demo-dark.png',
// Top details.
- showInvoiceNumber: true,
- invoiceNumberLabel: 'Invoice number',
+ showPaymentReceivedNumber: true,
+ paymentReceivedNumberLabel: 'Payment number',
- showDateIssue: true,
- dateIssueLabel: 'Date of Issue',
-
- showDueDate: true,
- dueDateLabel: 'Due Date',
+ showPaymentReceivedDate: true,
+ paymentReceivedDateLabel: 'Date of Issue',
// Company name
companyName: 'Bigcapital Technology, Inc.',
@@ -36,26 +33,8 @@ export const initialValues = {
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 = [
@@ -63,19 +42,14 @@ export const fieldsGroups = [
label: 'Header',
fields: [
{
- labelKey: 'invoiceNumberLabel',
- enableKey: 'showInvoiceNumber',
- label: 'Invoice No.',
+ labelKey: 'paymentReceivedNumberLabel',
+ enableKey: 'showPaymentReceivedNumber',
+ label: 'Payment No.',
},
{
- labelKey: 'dateIssueLabel',
- enableKey: 'showDateIssue',
- label: 'Issue Date',
- },
- {
- labelKey: 'dueDateLabel',
- enableKey: 'showDueDate',
- label: 'Due Date',
+ labelKey: 'paymentReceivedDateLabel',
+ enableKey: 'showPaymentReceivedDate',
+ label: 'Payment Date',
},
{
enableKey: 'showBillingToAddress',
@@ -96,39 +70,7 @@ export const fieldsGroups = [
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/Receipts/ReceiptCustomize/constants.ts b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/constants.ts
index 45930f6ef..b80b50162 100644
--- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/constants.ts
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/constants.ts
@@ -8,10 +8,11 @@ export const initialValues = {
companyLogo:
'https://cdn-development.mercury.com/demo-assets/avatars/mercury-demo-dark.png',
- // Top details.
+ // Receipt Number
showReceiptNumber: true,
receiptNumberLabel: 'Receipt number',
+ // Receipt Date
showReceiptDate: true,
receiptDateLabel: 'Date of Issue',
@@ -29,17 +30,19 @@ export const initialValues = {
itemRateLabel: 'Rate',
itemTotalLabel: 'Total',
- // Totals
+ // Subtotal
showSubtotal: true,
subtotalLabel: 'Subtotal',
+ // Total
showTotal: true,
totalLabel: 'Total',
- // Footer paragraphs.
+ // Terms & Conditions
termsConditionsLabel: 'Terms & Conditions',
showTermsConditions: true,
+ // Customer Note
customerNoteLabel: 'Customer Note',
showCustomerNote: true,
};
From c0769662bd61f4b488a10f731e439e11fd2f81fe Mon Sep 17 00:00:00 2001
From: Ahmed Bouhuolia
Date: Wed, 11 Sep 2024 14:54:13 +0200
Subject: [PATCH 15/32] feat(server): add pdf template crud endpoints
---
.../PdfTemplates/PdfTemplatesController.ts | 129 ++++++++++++++++++
packages/server/src/api/index.ts | 7 +-
...240911112147_create_pdf_templates_table.js | 21 +++
packages/server/src/loaders/tenantModels.ts | 2 +
packages/server/src/models/PdfTemplate.ts | 42 ++++++
.../services/PdfTemplate/CreatePdfTemplate.ts | 45 ++++++
.../services/PdfTemplate/DeletePdfTemplate.ts | 35 +++++
.../services/PdfTemplate/EditPdfTemplate.ts | 45 ++++++
.../services/PdfTemplate/GetPdfTemplate.ts | 29 ++++
.../services/PdfTemplate/GetPdfTemplates.ts | 37 +++++
.../PdfTemplate/GetPdfTemplatesTransformer.ts | 13 ++
.../PdfTemplate/PdfTemplateApplication.ts | 67 +++++++++
.../server/src/services/PdfTemplate/types.ts | 58 ++++++++
packages/server/src/subscribers/events.ts | 14 +-
14 files changed, 542 insertions(+), 2 deletions(-)
create mode 100644 packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts
create mode 100644 packages/server/src/database/migrations/20240911112147_create_pdf_templates_table.js
create mode 100644 packages/server/src/models/PdfTemplate.ts
create mode 100644 packages/server/src/services/PdfTemplate/CreatePdfTemplate.ts
create mode 100644 packages/server/src/services/PdfTemplate/DeletePdfTemplate.ts
create mode 100644 packages/server/src/services/PdfTemplate/EditPdfTemplate.ts
create mode 100644 packages/server/src/services/PdfTemplate/GetPdfTemplate.ts
create mode 100644 packages/server/src/services/PdfTemplate/GetPdfTemplates.ts
create mode 100644 packages/server/src/services/PdfTemplate/GetPdfTemplatesTransformer.ts
create mode 100644 packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts
create mode 100644 packages/server/src/services/PdfTemplate/types.ts
diff --git a/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts b/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts
new file mode 100644
index 000000000..2e6821bdb
--- /dev/null
+++ b/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts
@@ -0,0 +1,129 @@
+import { Router, Request, Response, NextFunction } from 'express';
+import { check, param, query } from 'express-validator';
+import { Service, Inject } from 'typedi';
+import BaseController from '@/api/controllers/BaseController';
+import { PdfTemplateApplication } from '@/services/PdfTemplate/PdfTemplateApplication';
+
+@Service()
+export class PdfTemplatesController extends BaseController {
+ @Inject()
+ public pdfTemplateApplication: PdfTemplateApplication;
+
+ /**
+ * Router constructor method.
+ */
+ public router() {
+ const router = Router();
+
+ router.delete(
+ '/:template_id',
+ [param('template_id').exists().isInt().toInt()],
+ this.validationResult,
+ this.deletePdfTemplate.bind(this)
+ );
+
+ router.put(
+ '/:template_id',
+ [
+ param('template_id').exists().isInt().toInt(),
+ check('attributes').exists(),
+ ],
+ this.validationResult,
+ this.editPdfTemplate.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)
+ );
+ return router;
+ }
+
+ async createPdfInvoiceTemplate(
+ req: Request,
+ res: Response,
+ next: NextFunction
+ ) {
+ const { tenantId } = req;
+ const { templateName, attributes } = this.matchedBodyData(req);
+
+ try {
+ const result = await this.pdfTemplateApplication.createPdfTemplate(
+ tenantId,
+ templateName,
+ attributes
+ );
+ return res.status(201).send(result);
+ } catch (error) {
+ next(error);
+ }
+ }
+
+ async editPdfTemplate(req: Request, res: Response, next: NextFunction) {
+ const { tenantId } = req;
+ const { template_id: templateId } = req.params;
+ const { attributes } = this.matchedBodyData(req);
+
+ try {
+ const result = await this.pdfTemplateApplication.editPdfTemplate(
+ tenantId,
+ Number(templateId),
+ attributes
+ );
+ return res.status(200).send(result);
+ } catch (error) {
+ next(error);
+ }
+ }
+
+ async deletePdfTemplate(req: Request, res: Response, next: NextFunction) {
+ const { tenantId } = req;
+ const { template_id: templateId } = req.params;
+
+ try {
+ await this.pdfTemplateApplication.deletePdfTemplate(
+ tenantId,
+ Number(templateId)
+ );
+ return res.status(204).send();
+ } catch (error) {
+ next(error);
+ }
+ }
+
+ async getPdfTemplate(req: Request, res: Response, next: NextFunction) {
+ const { tenantId } = req;
+ const { template_id: templateId } = req.params;
+
+ try {
+ const template = await this.pdfTemplateApplication.getPdfTemplate(
+ tenantId,
+ Number(templateId)
+ );
+ return res.status(200).send(template);
+ } catch (error) {
+ next(error);
+ }
+ }
+
+ async getPdfTemplates(req: Request, res: Response, next: NextFunction) {
+ const { tenantId } = req;
+
+ try {
+ const templates = await this.pdfTemplateApplication.getPdfTemplates(
+ tenantId
+ );
+ return res.status(200).send(templates);
+ } catch (error) {
+ next(error);
+ }
+ }
+}
diff --git a/packages/server/src/api/index.ts b/packages/server/src/api/index.ts
index ce4d2093f..c0fc2ef80 100644
--- a/packages/server/src/api/index.ts
+++ b/packages/server/src/api/index.ts
@@ -64,6 +64,7 @@ import { Webhooks } from './controllers/Webhooks/Webhooks';
import { ExportController } from './controllers/Export/ExportController';
import { AttachmentsController } from './controllers/Attachments/AttachmentsController';
import { OneClickDemoController } from './controllers/OneClickDemo/OneClickDemoController';
+import { PdfTemplatesController } from './controllers/PdfTemplates/PdfTemplatesController';
export default () => {
const app = Router();
@@ -81,7 +82,7 @@ export default () => {
app.use('/jobs', Container.get(Jobs).router());
app.use('/account', Container.get(Account).router());
app.use('/webhooks', Container.get(Webhooks).router());
- app.use('/demo', Container.get(OneClickDemoController).router())
+ app.use('/demo', Container.get(OneClickDemoController).router());
// - Dashboard routes.
// ---------------------------
@@ -147,6 +148,10 @@ export default () => {
dashboard.use('/import', Container.get(ImportController).router());
dashboard.use('/export', Container.get(ExportController).router());
dashboard.use('/attachments', Container.get(AttachmentsController).router());
+ dashboard.use(
+ '/pdf_templates',
+ Container.get(PdfTemplatesController).router()
+ );
dashboard.use('/', Container.get(ProjectTasksController).router());
dashboard.use('/', Container.get(ProjectTimesController).router());
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
new file mode 100644
index 000000000..ce4c96f59
--- /dev/null
+++ b/packages/server/src/database/migrations/20240911112147_create_pdf_templates_table.js
@@ -0,0 +1,21 @@
+/**
+ * @param { import("knex").Knex } knex
+ * @returns { Promise }
+ */
+exports.up = function (knex) {
+ return knex.schema.createTable('pdf_templates', (table) => {
+ table.increments('id').primary();
+ table.text('resource');
+ table.text('template_name');
+ table.json('attributes');
+ table.timestamps();
+ });
+};
+
+/**
+ * @param { import("knex").Knex } knex
+ * @returns { Promise }
+ */
+exports.down = function (knex) {
+ return knex.schema.dropTableIfExists('pdf_templates');
+};
diff --git a/packages/server/src/loaders/tenantModels.ts b/packages/server/src/loaders/tenantModels.ts
index 02877491a..3d349f81e 100644
--- a/packages/server/src/loaders/tenantModels.ts
+++ b/packages/server/src/loaders/tenantModels.ts
@@ -68,6 +68,7 @@ import { BankRule } from '@/models/BankRule';
import { BankRuleCondition } from '@/models/BankRuleCondition';
import { RecognizedBankTransaction } from '@/models/RecognizedBankTransaction';
import { MatchedBankTransaction } from '@/models/MatchedBankTransaction';
+import { PdfTemplate } from '@/models/PdfTemplate';
export default (knex) => {
const models = {
@@ -139,6 +140,7 @@ export default (knex) => {
BankRuleCondition,
RecognizedBankTransaction,
MatchedBankTransaction,
+ PdfTemplate
};
return mapValues(models, (model) => model.bindKnex(knex));
};
diff --git a/packages/server/src/models/PdfTemplate.ts b/packages/server/src/models/PdfTemplate.ts
new file mode 100644
index 000000000..67f53ac9d
--- /dev/null
+++ b/packages/server/src/models/PdfTemplate.ts
@@ -0,0 +1,42 @@
+import TenantModel from 'models/TenantModel';
+
+export class PdfTemplate extends TenantModel {
+ /**
+ * Table name.
+ */
+ static get tableName() {
+ return 'pdf_templates';
+ }
+
+ /**
+ * Timestamps columns.
+ */
+ get timestamps() {
+ return ['createdAt', 'updatedAt'];
+ }
+
+ static get jsonSchema() {
+ return {
+ type: 'object',
+ properties: {
+ id: { type: 'integer' },
+ templateName: { type: 'string' },
+ attributes: { type: 'object' }, // JSON field definition
+ },
+ };
+ }
+
+ /**
+ * Virtual attributes.
+ */
+ static get virtualAttributes() {
+ return [];
+ }
+
+ /**
+ * Relationship mapping.
+ */
+ static get relationMappings() {
+ return {};
+ }
+}
diff --git a/packages/server/src/services/PdfTemplate/CreatePdfTemplate.ts b/packages/server/src/services/PdfTemplate/CreatePdfTemplate.ts
new file mode 100644
index 000000000..251e67980
--- /dev/null
+++ b/packages/server/src/services/PdfTemplate/CreatePdfTemplate.ts
@@ -0,0 +1,45 @@
+import { Inject, Service } from 'typedi';
+import { ICreateInvoicePdfTemplateDTO } from './types';
+import HasTenancyService from '../Tenancy/TenancyService';
+import UnitOfWork from '../UnitOfWork';
+import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
+import events from '@/subscribers/events';
+
+@Service()
+export class CreatePdfTemplate {
+ @Inject()
+ private tennacy: HasTenancyService;
+
+ @Inject()
+ private uow: UnitOfWork;
+
+ @Inject()
+ private eventPublisher: EventPublisher;
+
+ /**
+ * Creates a new pdf template.
+ * @param {number} tenantId
+ * @param {ICreateInvoicePdfTemplateDTO} invoiceTemplateDTO
+ */
+ public createPdfTemplate(
+ tenantId: number,
+ templateName: string,
+ invoiceTemplateDTO: ICreateInvoicePdfTemplateDTO
+ ) {
+ const { PdfTemplate } = this.tennacy.models(tenantId);
+ const resource = 'SaleInvoice';
+ const attributes = invoiceTemplateDTO;
+
+ return this.uow.withTransaction(tenantId, async (trx) => {
+ await PdfTemplate.query(trx).insert({
+ templateName,
+ resource,
+ attributes,
+ });
+
+ await this.eventPublisher.emitAsync(events.pdfTemplate.onInvoiceCreated, {
+ tenantId,
+ });
+ });
+ }
+}
diff --git a/packages/server/src/services/PdfTemplate/DeletePdfTemplate.ts b/packages/server/src/services/PdfTemplate/DeletePdfTemplate.ts
new file mode 100644
index 000000000..eaf4391a5
--- /dev/null
+++ b/packages/server/src/services/PdfTemplate/DeletePdfTemplate.ts
@@ -0,0 +1,35 @@
+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';
+
+@Service()
+export class DeletePdfTemplate {
+ @Inject()
+ private tenancy: HasTenancyService;
+
+ @Inject()
+ private uow: UnitOfWork;
+
+ @Inject()
+ private eventPublisher: EventPublisher;
+
+ /**
+ * Deletes a pdf template.
+ * @param {number} tenantId
+ * @param {number} templateId
+ */
+ public deletePdfTemplate(tenantId: number, templateId: number) {
+ const { PdfTemplate } = this.tenancy.models(tenantId);
+
+ return this.uow.withTransaction(tenantId, async (trx) => {
+ await PdfTemplate.query(trx).deleteById(templateId);
+
+ await this.eventPublisher.emitAsync(events.pdfTemplate.onDeleted, {
+ tenantId,
+ templateId,
+ });
+ });
+ }
+}
diff --git a/packages/server/src/services/PdfTemplate/EditPdfTemplate.ts b/packages/server/src/services/PdfTemplate/EditPdfTemplate.ts
new file mode 100644
index 000000000..27a57b543
--- /dev/null
+++ b/packages/server/src/services/PdfTemplate/EditPdfTemplate.ts
@@ -0,0 +1,45 @@
+import { Inject, Service } from 'typedi';
+import { IEditPdfTemplateDTO } from './types';
+import HasTenancyService from '../Tenancy/TenancyService';
+import UnitOfWork from '../UnitOfWork';
+import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
+import events from '@/subscribers/events';
+
+@Service()
+export class EditPdfTemplate {
+ @Inject()
+ private tenancy: HasTenancyService;
+
+ @Inject()
+ private uow: UnitOfWork;
+
+ @Inject()
+ private eventPublisher: EventPublisher;
+
+ /**
+ * Edits an existing pdf template.
+ * @param {number} tenantId
+ * @param {number} templateId
+ * @param {IEditPdfTemplateDTO} editTemplateDTO
+ */
+ public editPdfTemplate(
+ tenantId: number,
+ templateId: number,
+ editTemplateDTO: IEditPdfTemplateDTO
+ ) {
+ 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.onEdited, {
+ tenantId,
+ templateId,
+ });
+ });
+ }
+}
diff --git a/packages/server/src/services/PdfTemplate/GetPdfTemplate.ts b/packages/server/src/services/PdfTemplate/GetPdfTemplate.ts
new file mode 100644
index 000000000..c41fe651d
--- /dev/null
+++ b/packages/server/src/services/PdfTemplate/GetPdfTemplate.ts
@@ -0,0 +1,29 @@
+import { Knex } from 'knex';
+import { Inject, Service } from 'typedi';
+import HasTenancyService from '../Tenancy/TenancyService';
+
+@Service()
+export class GetPdfTemplate {
+ @Inject()
+ private tenancy: HasTenancyService;
+
+ /**
+ * Retrieves a pdf template by its ID.
+ * @param {number} tenantId - The ID of the tenant.
+ * @param {number} templateId - The ID of the pdf template to retrieve.
+ * @return {Promise} - The retrieved pdf template.
+ */
+ async getPdfTemplate(
+ tenantId: number,
+ templateId: number,
+ trx?: Knex.Transaction
+ ): Promise {
+ const { PdfTemplate } = this.tenancy.models(tenantId);
+
+ const template = await PdfTemplate.query(trx)
+ .findById(templateId)
+ .throwIfNotFound();
+
+ return template;
+ }
+}
diff --git a/packages/server/src/services/PdfTemplate/GetPdfTemplates.ts b/packages/server/src/services/PdfTemplate/GetPdfTemplates.ts
new file mode 100644
index 000000000..ba6d7cdaa
--- /dev/null
+++ b/packages/server/src/services/PdfTemplate/GetPdfTemplates.ts
@@ -0,0 +1,37 @@
+import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
+import HasTenancyService from '../Tenancy/TenancyService';
+import { GetPdfTemplatesTransformer } from './GetPdfTemplatesTransformer';
+import { Inject, Service } from 'typedi';
+
+@Service()
+export class GetPdfTemplates {
+ @Inject()
+ private tenancy: HasTenancyService;
+
+ @Inject()
+ private transformInjectable: TransformerInjectable;
+
+ /**
+ * Retrieves a list of PDF templates for a specified tenant.
+ * @param {number} tenantId - The ID of the tenant for which to retrieve templates.
+ * @param {Object} [query] - Optional query parameters to filter the templates.
+ * @param {string} [query.resource] - The resource type to filter the templates by.
+ * @returns {Promise} - A promise that resolves to the transformed list of PDF templates.
+ */
+ async getPdfTemplates(tenantId: number, query?: { resource?: string }) {
+ const { PdfTemplate } = this.tenancy.models(tenantId);
+
+ const templates = await PdfTemplate.query().onBuild((q) => {
+ if (query?.resource) {
+ q.where('resource', query?.resource);
+ }
+ q.orderBy('createdAt', 'ASC');
+ });
+
+ return this.transformInjectable.transform(
+ tenantId,
+ templates,
+ new GetPdfTemplatesTransformer()
+ );
+ }
+}
diff --git a/packages/server/src/services/PdfTemplate/GetPdfTemplatesTransformer.ts b/packages/server/src/services/PdfTemplate/GetPdfTemplatesTransformer.ts
new file mode 100644
index 000000000..9863281cb
--- /dev/null
+++ b/packages/server/src/services/PdfTemplate/GetPdfTemplatesTransformer.ts
@@ -0,0 +1,13 @@
+import { Transformer } from '@/lib/Transformer/Transformer';
+
+export class GetPdfTemplatesTransformer extends Transformer {
+ // Empty transformer with no additional methods or attributes
+
+ /**
+ * Exclude attributes.
+ * @returns {string[]}
+ */
+ public excludeAttributes = (): string[] => {
+ return ['attributes'];
+ };
+}
diff --git a/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts b/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts
new file mode 100644
index 000000000..9f06006d7
--- /dev/null
+++ b/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts
@@ -0,0 +1,67 @@
+import { Inject, Service } from 'typedi';
+import { ICreateInvoicePdfTemplateDTO } from './types';
+import { CreatePdfTemplate } from './CreatePdfTemplate';
+import { DeletePdfTemplate } from './DeletePdfTemplate';
+import { GetPdfTemplate } from './GetPdfTemplate';
+import { GetPdfTemplates } from './GetPdfTemplates';
+import { EditPdfTemplate } from './EditPdfTemplate';
+
+@Service()
+export class PdfTemplateApplication {
+ @Inject()
+ private createPdfTemplateService: CreatePdfTemplate;
+
+ @Inject()
+ private deletePdfTemplateService: DeletePdfTemplate;
+
+ @Inject()
+ private getPdfTemplateService: GetPdfTemplate;
+
+ @Inject()
+ private getPdfTemplatesService: GetPdfTemplates;
+
+ @Inject()
+ private editPdfTemplateService: EditPdfTemplate;
+
+ public async createPdfTemplate(
+ tenantId: number,
+ templateName: string,
+ invoiceTemplateDTO: ICreateInvoicePdfTemplateDTO
+ ) {
+ return this.createPdfTemplateService.createPdfTemplate(
+ tenantId,
+ templateName,
+ invoiceTemplateDTO
+ );
+ }
+
+ public async editPdfTemplate(
+ tenantId: number,
+ templateId: number,
+ editTemplateDTO: IEditPdfTemplateDTO
+ ) {
+ return this.editPdfTemplateService.editPdfTemplate(
+ tenantId,
+ templateId,
+ editTemplateDTO
+ );
+ }
+
+ public async deletePdfTemplate(tenantId: number, templateId: number) {
+ return this.deletePdfTemplateService.deletePdfTemplate(
+ tenantId,
+ templateId
+ );
+ }
+
+ public async getPdfTemplate(tenantId: number, templateId: number) {
+ return this.getPdfTemplateService.getPdfTemplate(tenantId, templateId);
+ }
+
+ public async getPdfTemplates(
+ tenantId: number,
+ query?: { resource?: string }
+ ) {
+ return this.getPdfTemplatesService.getPdfTemplates(tenantId, query);
+ }
+}
diff --git a/packages/server/src/services/PdfTemplate/types.ts b/packages/server/src/services/PdfTemplate/types.ts
new file mode 100644
index 000000000..98cf1d214
--- /dev/null
+++ b/packages/server/src/services/PdfTemplate/types.ts
@@ -0,0 +1,58 @@
+export interface ICreateInvoicePdfTemplateDTO {
+ // 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/server/src/subscribers/events.ts b/packages/server/src/subscribers/events.ts
index d21ffac98..2a70570cb 100644
--- a/packages/server/src/subscribers/events.ts
+++ b/packages/server/src/subscribers/events.ts
@@ -58,7 +58,7 @@ export default {
onSubscriptionSubscribed: 'onSubscriptionSubscribed',
onSubscriptionPaymentSucceed: 'onSubscriptionPaymentSucceed',
- onSubscriptionPaymentFailed: 'onSubscriptionPaymentFailed'
+ onSubscriptionPaymentFailed: 'onSubscriptionPaymentFailed',
},
/**
@@ -684,4 +684,16 @@ export default {
import: {
onImportCommitted: 'onImportFileCommitted',
},
+
+ pdfTemplate: {
+ onCreating: 'onPdfTemplateCreating',
+ onCreated: 'onPdfTemplateCreated',
+
+ onEditing: 'onPdfTemplateEditing',
+ onEdited: 'onPdfTemplatedEdited',
+
+ onDeleted: 'onPdfTemplateDeleted',
+
+ onInvoiceCreated: 'onInvoicePdfTemplateCreated',
+ },
};
From ef74e250f170b09831a85ec617b38a28fb70b62e Mon Sep 17 00:00:00 2001
From: Ahmed Bouhuolia
Date: Wed, 11 Sep 2024 16:49:44 +0200
Subject: [PATCH 16/32] feat: link pdf template to sales transactions
---
.../src/api/controllers/Sales/CreditNotes.ts | 3 +
.../api/controllers/Sales/PaymentReceives.ts | 3 +
.../api/controllers/Sales/SalesEstimates.ts | 7 +-
.../api/controllers/Sales/SalesInvoices.ts | 7 +-
.../api/controllers/Sales/SalesReceipts.ts | 9 +-
...240911112147_create_pdf_templates_table.js | 68 +++++++--
.../services/PdfTemplate/CreatePdfTemplate.ts | 9 +-
.../services/PdfTemplate/DeletePdfTemplate.ts | 6 +
packages/server/src/subscribers/events.ts | 1 +
.../InvoiceCustomize/InvoiceCustomize.tsx | 48 ++++++-
.../Sales/Invoices/InvoiceCustomize/utils.ts | 24 ++++
.../webapp/src/hooks/query/pdf-templates.ts | 131 ++++++++++++++++++
12 files changed, 294 insertions(+), 22 deletions(-)
create mode 100644 packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/utils.ts
create mode 100644 packages/webapp/src/hooks/query/pdf-templates.ts
diff --git a/packages/server/src/api/controllers/Sales/CreditNotes.ts b/packages/server/src/api/controllers/Sales/CreditNotes.ts
index 95d18b553..e26c26a0d 100644
--- a/packages/server/src/api/controllers/Sales/CreditNotes.ts
+++ b/packages/server/src/api/controllers/Sales/CreditNotes.ts
@@ -236,6 +236,9 @@ export default class PaymentReceivesController extends BaseController {
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
+
+ // Pdf template id.
+ check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
];
}
diff --git a/packages/server/src/api/controllers/Sales/PaymentReceives.ts b/packages/server/src/api/controllers/Sales/PaymentReceives.ts
index 10cbb0c28..2adeb1a84 100644
--- a/packages/server/src/api/controllers/Sales/PaymentReceives.ts
+++ b/packages/server/src/api/controllers/Sales/PaymentReceives.ts
@@ -167,6 +167,9 @@ export default class PaymentReceivesController extends BaseController {
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
+
+ // Pdf template id.
+ check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
];
}
diff --git a/packages/server/src/api/controllers/Sales/SalesEstimates.ts b/packages/server/src/api/controllers/Sales/SalesEstimates.ts
index b1808006f..c19632ce1 100644
--- a/packages/server/src/api/controllers/Sales/SalesEstimates.ts
+++ b/packages/server/src/api/controllers/Sales/SalesEstimates.ts
@@ -168,9 +168,7 @@ export default class SalesEstimatesController extends BaseController {
check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.quantity').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(),
- check('entries.*.description')
- .optional({ nullable: true })
- .trim(),
+ check('entries.*.description').optional({ nullable: true }).trim(),
check('entries.*.discount')
.optional({ nullable: true })
.isNumeric()
@@ -186,6 +184,9 @@ export default class SalesEstimatesController extends BaseController {
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
+
+ // Pdf template id.
+ check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
];
}
diff --git a/packages/server/src/api/controllers/Sales/SalesInvoices.ts b/packages/server/src/api/controllers/Sales/SalesInvoices.ts
index 0b2bc948f..012b7f041 100644
--- a/packages/server/src/api/controllers/Sales/SalesInvoices.ts
+++ b/packages/server/src/api/controllers/Sales/SalesInvoices.ts
@@ -224,9 +224,7 @@ export default class SaleInvoicesController extends BaseController {
.optional({ nullable: true })
.isNumeric()
.toFloat(),
- check('entries.*.description')
- .optional({ nullable: true })
- .trim(),
+ check('entries.*.description').optional({ nullable: true }).trim(),
check('entries.*.tax_code')
.optional({ nullable: true })
.trim()
@@ -257,6 +255,9 @@ export default class SaleInvoicesController extends BaseController {
.optional({ nullable: true })
.isNumeric()
.toFloat(),
+
+ // Pdf template id.
+ check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
];
}
diff --git a/packages/server/src/api/controllers/Sales/SalesReceipts.ts b/packages/server/src/api/controllers/Sales/SalesReceipts.ts
index ba2376568..5330c5d26 100644
--- a/packages/server/src/api/controllers/Sales/SalesReceipts.ts
+++ b/packages/server/src/api/controllers/Sales/SalesReceipts.ts
@@ -148,17 +148,20 @@ export default class SalesReceiptsController extends BaseController {
.optional({ nullable: true })
.isNumeric()
.toInt(),
- check('entries.*.description')
- .optional({ nullable: true })
- .trim(),
+ check('entries.*.description').optional({ nullable: true }).trim(),
check('entries.*.warehouse_id')
.optional({ nullable: true })
.isNumeric()
.toInt(),
+
check('receipt_message').optional().trim(),
+
check('statement').optional().trim(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
+
+ // Pdf template id.
+ check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
];
}
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 ce4c96f59..f48f28498 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
@@ -3,13 +3,49 @@
* @returns { Promise }
*/
exports.up = function (knex) {
- return knex.schema.createTable('pdf_templates', (table) => {
- table.increments('id').primary();
- table.text('resource');
- table.text('template_name');
- table.json('attributes');
- table.timestamps();
- });
+ return knex.schema
+ .createTable('pdf_templates', (table) => {
+ table.increments('id').primary();
+ table.text('resource');
+ table.text('template_name');
+ table.json('attributes');
+ table.timestamps();
+ })
+ .table('sales_invoices', (table) => {
+ table
+ .integer('pdf_template_id')
+ .unsigned()
+ .references('id')
+ .inTable('pdf_templates');
+ })
+ .table('sales_estimates', (table) => {
+ table
+ .integer('pdf_template_id')
+ .unsigned()
+ .references('id')
+ .inTable('pdf_templates');
+ })
+ .table('sales_receipts', (table) => {
+ table
+ .integer('pdf_template_id')
+ .unsigned()
+ .references('id')
+ .inTable('pdf_templates');
+ })
+ .table('credit_notes', (table) => {
+ table
+ .integer('pdf_template_id')
+ .unsigned()
+ .references('id')
+ .inTable('pdf_templates');
+ })
+ .table('payment_receives', (table) => {
+ table
+ .integer('pdf_template_id')
+ .unsigned()
+ .references('id')
+ .inTable('pdf_templates');
+ });
};
/**
@@ -17,5 +53,21 @@ exports.up = function (knex) {
* @returns { Promise }
*/
exports.down = function (knex) {
- return knex.schema.dropTableIfExists('pdf_templates');
+ return knex.schema
+ .table('payment_receives', (table) => {
+ table.dropColumn('pdf_template_id');
+ })
+ .table('credit_notes', (table) => {
+ table.dropColumn('pdf_template_id');
+ })
+ .table('sales_receipts', (table) => {
+ table.dropColumn('pdf_template_id');
+ })
+ .table('sales_estimates', (table) => {
+ table.dropColumn('pdf_template_id');
+ })
+ .table('sales_invoices', (table) => {
+ table.dropColumn('pdf_template_id');
+ })
+ .dropTableIfExists('pdf_templates');
};
diff --git a/packages/server/src/services/PdfTemplate/CreatePdfTemplate.ts b/packages/server/src/services/PdfTemplate/CreatePdfTemplate.ts
index 251e67980..feab657c6 100644
--- a/packages/server/src/services/PdfTemplate/CreatePdfTemplate.ts
+++ b/packages/server/src/services/PdfTemplate/CreatePdfTemplate.ts
@@ -31,13 +31,18 @@ export class CreatePdfTemplate {
const attributes = invoiceTemplateDTO;
return this.uow.withTransaction(tenantId, async (trx) => {
+ // Triggers `onPdfTemplateCreating` event.
+ await this.eventPublisher.emitAsync(events.pdfTemplate.onCreating, {
+ tenantId,
+ });
+
await PdfTemplate.query(trx).insert({
templateName,
resource,
attributes,
});
-
- await this.eventPublisher.emitAsync(events.pdfTemplate.onInvoiceCreated, {
+ // Triggers `onPdfTemplateCreated` event.
+ await this.eventPublisher.emitAsync(events.pdfTemplate.onCreated, {
tenantId,
});
});
diff --git a/packages/server/src/services/PdfTemplate/DeletePdfTemplate.ts b/packages/server/src/services/PdfTemplate/DeletePdfTemplate.ts
index eaf4391a5..e15bea1de 100644
--- a/packages/server/src/services/PdfTemplate/DeletePdfTemplate.ts
+++ b/packages/server/src/services/PdfTemplate/DeletePdfTemplate.ts
@@ -24,8 +24,14 @@ export class DeletePdfTemplate {
const { PdfTemplate } = this.tenancy.models(tenantId);
return this.uow.withTransaction(tenantId, async (trx) => {
+ // Triggers `onPdfTemplateDeleting` event.
+ await this.eventPublisher.emitAsync(events.pdfTemplate.onDeleting, {
+ tenantId,
+ templateId,
+ });
await PdfTemplate.query(trx).deleteById(templateId);
+ // Triggers `onPdfTemplateDeleted` event.
await this.eventPublisher.emitAsync(events.pdfTemplate.onDeleted, {
tenantId,
templateId,
diff --git a/packages/server/src/subscribers/events.ts b/packages/server/src/subscribers/events.ts
index 2a70570cb..ba2cf7103 100644
--- a/packages/server/src/subscribers/events.ts
+++ b/packages/server/src/subscribers/events.ts
@@ -692,6 +692,7 @@ export default {
onEditing: 'onPdfTemplateEditing',
onEdited: 'onPdfTemplatedEdited',
+ onDeleting: 'onPdfTemplateDeleting',
onDeleted: 'onPdfTemplateDeleted',
onInvoiceCreated: 'onInvoicePdfTemplateCreated',
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx
index 090cadc88..c42dc2e44 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomize.tsx
@@ -1,7 +1,7 @@
import React from 'react';
import * as R from 'ramda';
-import { Box } from '@/components';
-import { Classes } from '@blueprintjs/core';
+import { AppToaster, Box } from '@/components';
+import { Classes, Intent } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import {
InvoicePaperTemplate,
@@ -12,9 +12,51 @@ 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';
export default function InvoiceCustomizeContent() {
- const handleFormSubmit = (values: InvoiceCustomizeValues) => {};
+ 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({ ...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.'),
+ );
+ }
+ };
return (
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/utils.ts b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/utils.ts
new file mode 100644
index 000000000..a063b9ee2
--- /dev/null
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/utils.ts
@@ -0,0 +1,24 @@
+import { omit } from 'lodash';
+import { InvoiceCustomizeValues } from './types';
+import { CreatePdfTemplateValues, EditPdfTemplateValues } from '@/hooks/query/pdf-templates';
+
+export const transformToEditRequest = (
+ values: InvoiceCustomizeValues,
+ templateId: number,
+): EditPdfTemplateValues => {
+ return {
+ templateId,
+ templateName: 'Template Name',
+ attributes: omit(values, ['templateName']),
+ };
+};
+
+export const transformToNewRequest = (
+ values: InvoiceCustomizeValues,
+): CreatePdfTemplateValues => {
+ return {
+ resource: 'SaleInvoice',
+ templateName: 'Template Name',
+ attributes: omit(values, ['templateName']),
+ };
+};
diff --git a/packages/webapp/src/hooks/query/pdf-templates.ts b/packages/webapp/src/hooks/query/pdf-templates.ts
new file mode 100644
index 000000000..2045c0fb3
--- /dev/null
+++ b/packages/webapp/src/hooks/query/pdf-templates.ts
@@ -0,0 +1,131 @@
+import {
+ useMutation,
+ useQuery,
+ UseMutationOptions,
+ UseQueryOptions,
+ UseMutationResult,
+ UseQueryResult,
+} from 'react-query';
+import useApiRequest from '../useRequest';
+
+export interface CreatePdfTemplateValues {
+ templateName: string;
+ resource: string;
+ attributes: Record;
+}
+
+export interface CreatePdfTemplateResponse {}
+
+export interface EditPdfTemplateValues {
+ templateId: string | number;
+ templateName: string;
+ attributes: Record;
+}
+
+export interface EditPdfTemplateResponse {}
+
+export interface DeletePdfTemplateValues {
+ templateId: number | string;
+}
+
+export interface DeletePdfTemplateResponse {}
+
+export interface GetPdfTemplateValues {}
+
+export interface GetPdfTemplateResponse {}
+
+export interface GetPdfTemplatesValues {}
+
+export interface GetPdfTemplatesResponse {}
+
+// Hook for creating a PDF template
+export const useCreatePdfTemplate = (
+ options?: UseMutationOptions<
+ CreatePdfTemplateResponse,
+ Error,
+ CreatePdfTemplateValues
+ >,
+): UseMutationResult<
+ CreatePdfTemplateResponse,
+ Error,
+ CreatePdfTemplateValues
+> => {
+ const apiRequest = useApiRequest();
+ return useMutation(
+ (values) =>
+ apiRequest.post('/pdf-templates', values).then((res) => res.data),
+ options,
+ );
+};
+
+// Hook for editing a PDF template
+export const useEditPdfTemplate = (
+ options?: UseMutationOptions<
+ EditPdfTemplateResponse,
+ Error,
+ { templateId: number; values: EditPdfTemplateValues }
+ >,
+): UseMutationResult<
+ EditPdfTemplateResponse,
+ Error,
+ { templateId: number; values: EditPdfTemplateValues }
+> => {
+ const apiRequest = useApiRequest();
+ return useMutation<
+ EditPdfTemplateResponse,
+ Error,
+ { templateId: number; values: EditPdfTemplateValues }
+ >(
+ ({ templateId, values }) =>
+ apiRequest
+ .put(`/pdf-templates/${templateId}`, values)
+ .then((res) => res.data),
+ options,
+ );
+};
+
+// Hook for deleting a PDF template
+export const useDeletePdfTemplate = (
+ options?: UseMutationOptions<
+ DeletePdfTemplateResponse,
+ Error,
+ { templateId: number }
+ >,
+): UseMutationResult<
+ DeletePdfTemplateResponse,
+ Error,
+ { templateId: number }
+> => {
+ const apiRequest = useApiRequest();
+ return useMutation(
+ ({ templateId }) =>
+ apiRequest.delete(`/pdf-templates/${templateId}`).then((res) => res.data),
+ options,
+ );
+};
+
+// Hook for getting a single PDF template
+export const useGetPdfTemplate = (
+ templateId: number,
+ options?: UseQueryOptions,
+): UseQueryResult => {
+ const apiRequest = useApiRequest();
+ return useQuery(
+ ['pdfTemplate', templateId],
+ () =>
+ apiRequest.get(`/pdf-templates/${templateId}`).then((res) => res.data),
+ options,
+ );
+};
+
+// Hook for getting multiple PDF templates
+export const useGetPdfTemplates = (
+ options?: UseQueryOptions,
+): UseQueryResult => {
+ const apiRequest = useApiRequest();
+ return useQuery(
+ 'pdfTemplates',
+ () => apiRequest.get('/pdf-templates').then((res) => res.data),
+ options,
+ );
+};
From a7df23cebcdb698b807f36314e218e350f84b8d6 Mon Sep 17 00:00:00 2001
From: Ahmed Bouhuolia
Date: Wed, 11 Sep 2024 21:16:21 +0200
Subject: [PATCH 17/32] feat: branding templates table
---
packages/server/src/api/index.ts | 2 +-
.../PdfTemplate/GetPdfTemplatesTransformer.ts | 19 +++-
packages/webapp/src/components/Card/index.tsx | 8 +-
.../src/components/DrawersContainer.tsx | 9 +-
packages/webapp/src/constants/drawers.ts | 4 +-
.../containers/AlertsContainer/registered.tsx | 2 +
.../BrandTemplates.module.scss | 15 ++++
.../BrandingTemplatesActionsBar.tsx | 35 ++++++++
.../BrandingTemplatesBoot.tsx | 32 +++++++
.../BrandingTemplatesContent.tsx | 52 +++++++++++
.../BrandingTemplatesDrawer.tsx | 37 ++++++++
.../BrandingTemplatesTable.tsx | 87 +++++++++++++++++++
.../BrandingTemplates/_components.tsx | 26 ++++++
.../alerts/BrandingTemplatesAlerts.ts | 10 +++
.../alerts/DeleteBrandingTemplateAlert.tsx | 72 +++++++++++++++
.../InvoiceCustomize/InvoiceCustomize.tsx | 10 +--
.../InvoicesLanding/InvoicesActionsBar.tsx | 2 +-
.../webapp/src/hooks/query/pdf-templates.ts | 1 +
.../style/components/DataTable/DataTable.scss | 2 +-
.../src/style/pages/Dashboard/Dashboard.scss | 9 ++
20 files changed, 418 insertions(+), 16 deletions(-)
create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandTemplates.module.scss
create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesActionsBar.tsx
create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesBoot.tsx
create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesContent.tsx
create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesDrawer.tsx
create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/BrandingTemplatesTable.tsx
create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/_components.tsx
create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/BrandingTemplatesAlerts.ts
create mode 100644 packages/webapp/src/containers/Sales/Invoices/BrandingTemplates/alerts/DeleteBrandingTemplateAlert.tsx
diff --git a/packages/server/src/api/index.ts b/packages/server/src/api/index.ts
index c0fc2ef80..9dc3b5d07 100644
--- a/packages/server/src/api/index.ts
+++ b/packages/server/src/api/index.ts
@@ -149,7 +149,7 @@ export default () => {
dashboard.use('/export', Container.get(ExportController).router());
dashboard.use('/attachments', Container.get(AttachmentsController).router());
dashboard.use(
- '/pdf_templates',
+ '/pdf-templates',
Container.get(PdfTemplatesController).router()
);
diff --git a/packages/server/src/services/PdfTemplate/GetPdfTemplatesTransformer.ts b/packages/server/src/services/PdfTemplate/GetPdfTemplatesTransformer.ts
index 9863281cb..181b3f029 100644
--- a/packages/server/src/services/PdfTemplate/GetPdfTemplatesTransformer.ts
+++ b/packages/server/src/services/PdfTemplate/GetPdfTemplatesTransformer.ts
@@ -1,8 +1,7 @@
import { Transformer } from '@/lib/Transformer/Transformer';
+import { getTransactionTypeLabel } from '@/utils/transactions-types';
export class GetPdfTemplatesTransformer extends Transformer {
- // Empty transformer with no additional methods or attributes
-
/**
* Exclude attributes.
* @returns {string[]}
@@ -10,4 +9,20 @@ export class GetPdfTemplatesTransformer extends Transformer {
public excludeAttributes = (): string[] => {
return ['attributes'];
};
+
+ /**
+ * Includeded attributes.
+ * @returns {string[]}
+ */
+ public includeAttributes = (): string[] => {
+ return ['createdAtFormatted', 'resourceFormatted'];
+ };
+
+ private createdAtFormatted = (template) => {
+ return this.formatDate(template.createdAt);
+ };
+
+ private resourceFormatted = (template) => {
+ return getTransactionTypeLabel(template.resource);
+ };
}
diff --git a/packages/webapp/src/components/Card/index.tsx b/packages/webapp/src/components/Card/index.tsx
index 8bd314db6..da2c64b43 100644
--- a/packages/webapp/src/components/Card/index.tsx
+++ b/packages/webapp/src/components/Card/index.tsx
@@ -2,8 +2,12 @@
import React from 'react';
import styled from 'styled-components';
-export function Card({ className, children }) {
- return {children} ;
+export function Card({ className, style, children }) {
+ return (
+
+ {children}
+
+ );
}
const CardRoot = styled.div`
diff --git a/packages/webapp/src/components/DrawersContainer.tsx b/packages/webapp/src/components/DrawersContainer.tsx
index 3e28d06af..fdcaac652 100644
--- a/packages/webapp/src/components/DrawersContainer.tsx
+++ b/packages/webapp/src/components/DrawersContainer.tsx
@@ -27,9 +27,11 @@ import { InvoiceCustomizeDrawer } from '@/containers/Sales/Invoices/InvoiceCusto
import { EstimateCustomizeDrawer } from '@/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeDrawer';
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 { DRAWERS } from '@/constants/drawers';
-import { PaymentReceivedCustomizeDrawer } from '@/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeDrawer';
+
/**
* Drawers container of the dashboard.
*/
@@ -73,7 +75,10 @@ export default function DrawersContainer() {
-
+
+
);
}
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 (
+
+
+ }
+ onClick={handleCreateBtnClick}
+ minimal
+ >
+ Create Invoice Branding
+
+
+
+ );
+}
+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 (
+
+
+ Create Invoice Branding
+
+
+ );
+ },
+);
+
+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 (
-
+
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 ;
}
diff --git a/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesSelectFields.tsx b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesSelectFields.tsx
new file mode 100644
index 000000000..34b0ada06
--- /dev/null
+++ b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesSelectFields.tsx
@@ -0,0 +1,46 @@
+import { Button } from '@blueprintjs/core';
+import styled from 'styled-components';
+import { FFormGroup } from '@/components';
+
+export const BrandingThemeFormGroup = styled(FFormGroup)`
+ margin-bottom: 0;
+
+ .bp4-label {
+ color: #7a8492;
+ }
+
+ &.bp4-inline label.bp4-label {
+ margin-right: 0;
+ }
+`;
+
+export const BrandingThemeSelectButton = styled(Button)`
+ position: relative;
+ padding-right: 26px;
+
+ &::after {
+ content: '';
+ display: inline-block;
+ width: 0;
+ height: 0;
+
+ border-left: 4px solid transparent;
+ border-right: 4px solid transparent;
+ border-top: 5px solid #98a1ae;
+
+ position: absolute;
+ right: -2px;
+ top: 50%;
+ margin-top: -2px;
+ margin-right: 12px;
+ border-radius: 1px;
+ }
+`;
+
+
+export const convertBrandingTemplatesToOptions = (brandingTemplates: Array) => {
+ return brandingTemplates?.map(
+ (template) =>
+ ({ text: template.template_name, value: template.id } || []),
+ )
+}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFloatingActions.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFloatingActions.tsx
index e3d88daf5..44e6b6716 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFloatingActions.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFloatingActions.tsx
@@ -1,5 +1,5 @@
// @ts-nocheck
-import React from 'react';
+import React, { useMemo } from 'react';
import {
Intent,
Button,
@@ -10,12 +10,17 @@ import {
Menu,
MenuItem,
} from '@blueprintjs/core';
-import classNames from 'classnames';
-import { CLASSES } from '@/constants/classes';
-import { useFormikContext } from 'formik';
-import { If, Icon, FormattedMessage as T, Group } from '@/components';
import { useHistory } from 'react-router-dom';
+import classNames from 'classnames';
+import { useFormikContext } from 'formik';
+import { CLASSES } from '@/constants/classes';
+import { If, Icon, FormattedMessage as T, Group, FSelect } from '@/components';
import { useInvoiceFormContext } from './InvoiceFormProvider';
+import { useInvoiceFormBrandingTemplatesOptions } from './utils';
+import {
+ BrandingThemeFormGroup,
+ BrandingThemeSelectButton,
+} from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
/**
* Invoice floating actions bar.
@@ -75,6 +80,8 @@ export default function InvoiceFloatingActions() {
resetForm();
};
+ const brandingTemplatesOptions = useInvoiceFormBrandingTemplatesOptions();
+
return (
}
/>
+
+ {/* ----------- Branding Template Select ----------- */}
+
+ (
+
+ )}
+ filterable={false}
+ popoverProps={{ minimal: true }}
+ />
+
);
}
+
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormProvider.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormProvider.tsx
index fd1b3b0b2..fc6a41f6f 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormProvider.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceFormProvider.tsx
@@ -19,6 +19,7 @@ import {
} from '@/hooks/query';
import { useProjects } from '@/containers/Projects/hooks';
import { useTaxRates } from '@/hooks/query/taxRates';
+import { useGetPdfTemplates } from '@/hooks/query/pdf-templates';
const InvoiceFormContext = createContext();
@@ -55,6 +56,10 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) {
{ enabled: !!estimateId },
);
+ // Fetches branding templates of invoice.
+ const { data: brandingTemplates, isLoading: isBrandingTemplatesLoading } =
+ useGetPdfTemplates({ resource: 'SaleInvoice' });
+
const newInvoice = !isEmpty(estimate)
? transformToEditForm({
...pick(estimate, ['customer_id', 'currency_code', 'entries']),
@@ -105,7 +110,7 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) {
// Determines whether the warehouse and branches are loading.
const isFeatureLoading =
- isWarehouesLoading || isBranchesLoading || isProjectsLoading;
+ isWarehouesLoading || isBranchesLoading || isProjectsLoading || isBrandingTemplatesLoading;
const provider = {
invoice,
@@ -119,6 +124,7 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) {
warehouses,
projects,
taxRates,
+ brandingTemplates,
isInvoiceLoading,
isItemsLoading,
@@ -130,6 +136,7 @@ function InvoiceFormProvider({ invoiceId, baseCurrency, ...props }) {
isBranchesSuccess,
isWarehousesSuccess,
isTaxRatesLoading,
+ isBrandingTemplatesLoading,
createInvoiceMutate,
editInvoiceMutate,
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx
index c031ce74c..edc3c91de 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx
@@ -31,6 +31,7 @@ import {
transformAttachmentsToForm,
transformAttachmentsToRequest,
} from '@/containers/Attachments/utils';
+import { convertBrandingTemplatesToOptions } from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
export const MIN_LINES_NUMBER = 1;
@@ -66,6 +67,7 @@ export const defaultInvoice = {
branch_id: '',
warehouse_id: '',
project_id: '',
+ pdf_template_id: '',
entries: [...repeatValue(defaultInvoiceEntry, MIN_LINES_NUMBER)],
attachments: [],
};
@@ -406,3 +408,12 @@ export const useInvoiceCurrencyCode = () => {
return values.currency_code;
};
+
+export const useInvoiceFormBrandingTemplatesOptions = () => {
+ const { brandingTemplates } = useInvoiceFormContext();
+
+ return React.useMemo(
+ () => convertBrandingTemplatesToOptions(brandingTemplates),
+ [brandingTemplates],
+ );
+};
From 2c790427faf2b2ab77a88746e628cf9eed7df28a Mon Sep 17 00:00:00 2001
From: Ahmed Bouhuolia
Date: Tue, 17 Sep 2024 13:53:57 +0200
Subject: [PATCH 29/32] feat: rendering pdf templates on the server-side
---
.../views/modules/credit-note-standard.pug | 260 +++++++++++++-----
.../views/modules/estimate-regular.pug | 258 ++++++++++++-----
.../views/modules/invoice-standard.pug | 4 +-
.../modules/payment-receive-standard.pug | 230 ++++++++++++----
.../views/modules/receipt-regular.pug | 241 +++++++++++-----
packages/server/src/interfaces/CreditNote.ts | 46 ++++
.../server/src/interfaces/PaymentReceive.ts | 68 +++++
.../server/src/interfaces/SaleEstimate.ts | 1 +
.../services/CreditNotes/CreateCreditNote.ts | 2 +-
.../CreditNotes/CreditNoteBrandingTemplate.ts | 30 ++
.../src/services/CreditNotes/CreditNotes.ts | 19 +-
.../services/CreditNotes/EditCreditNote.ts | 2 +-
.../services/CreditNotes/GetCreditNotePdf.ts | 66 ++++-
.../src/services/CreditNotes/constants.ts | 71 ++++-
.../server/src/services/CreditNotes/utils.ts | 9 +
.../BrandingTemplateDTOTransformer.ts | 46 ++--
.../Estimates/SaleEstimateDTOTransformer.ts | 15 +-
.../Sales/Estimates/SaleEstimatesPdf.ts | 54 +++-
.../src/services/Sales/Estimates/constants.ts | 119 ++++++++
.../src/services/Sales/Estimates/utils.ts | 7 +
.../Sales/Invoices/SaleEstimatePdfTemplate.ts | 31 +++
.../PaymentReceived/GetPaymentReceivedPdf.ts | 57 +++-
.../PaymentReceivedBrandingTemplate.ts | 35 +++
.../PaymentReceivedDTOTransformer.ts | 15 +-
.../Sales/PaymentReceived/constants.ts | 50 ++++
.../services/Sales/PaymentReceived/utils.ts | 12 +
.../Receipts/SaleReceiptBrandingTemplate.ts | 35 +++
.../Receipts/SaleReceiptDTOTransformer.ts | 14 +-
.../Sales/Receipts/SaleReceiptsPdfService.ts | 60 +++-
.../src/services/Sales/Receipts/constants.ts | 98 +++++--
.../src/services/Sales/Receipts/utils.ts | 6 +
.../CreditNoteFloatingActions.tsx | 28 +-
.../CreditNoteForm/CreditNoteFormProvider.tsx | 12 +-
.../CreditNotes/CreditNoteForm/utils.tsx | 14 +-
.../EstimateForm/EstimateFloatingActions.tsx | 28 +-
.../EstimateForm/EstimateFormProvider.tsx | 21 +-
.../Sales/Estimates/EstimateForm/utils.tsx | 13 +-
.../EstimatesLanding/EstimatesActionsBar.tsx | 5 +
.../PaymentReceiveFloatingActions.tsx | 29 +-
.../PaymentReceiveFormProvider.tsx | 20 +-
.../PaymentReceiveForm/utils.tsx | 13 +-
.../ReceiptFormFloatingActions.tsx | 28 +-
.../ReceiptForm/ReceiptFormProvider.tsx | 13 +-
.../Sales/Receipts/ReceiptForm/utils.tsx | 11 +
44 files changed, 1833 insertions(+), 363 deletions(-)
create mode 100644 packages/server/src/services/CreditNotes/CreditNoteBrandingTemplate.ts
create mode 100644 packages/server/src/services/CreditNotes/utils.ts
create mode 100644 packages/server/src/services/Sales/Estimates/utils.ts
create mode 100644 packages/server/src/services/Sales/Invoices/SaleEstimatePdfTemplate.ts
create mode 100644 packages/server/src/services/Sales/PaymentReceived/PaymentReceivedBrandingTemplate.ts
create mode 100644 packages/server/src/services/Sales/PaymentReceived/utils.ts
create mode 100644 packages/server/src/services/Sales/Receipts/SaleReceiptBrandingTemplate.ts
create mode 100644 packages/server/src/services/Sales/Receipts/utils.ts
diff --git a/packages/server/resources/views/modules/credit-note-standard.pug b/packages/server/resources/views/modules/credit-note-standard.pug
index 8f9367a6b..16b1c8834 100644
--- a/packages/server/resources/views/modules/credit-note-standard.pug
+++ b/packages/server/resources/views/modules/credit-note-standard.pug
@@ -1,81 +1,205 @@
extends ../PaperTemplateLayout.pug
block head
- style
- if (isRtl)
- include ../../css/modules/credit-rtl.css
- else
- include ../../css/modules/credit.css
+ - var prefix = 'bc'
+ style.
+ .#{prefix}-root {
+ 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;
+ overflow: hidden;
+ }
+ .#{prefix}-terms-list {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ margin-bottom: 24px;
+ }
+ .#{prefix}-terms-item {
+ display: flex;
+ flex-direction: row;
+ gap: 12px;
+ }
+ .#{prefix}-terms-item__label {
+ min-width: 120px;
+ color: #333;
+ }
+ .#{prefix}-terms-item__value {
+ /* Styles for the term value */
+ }
+
+ .#{prefix}-group {
+ 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 {
+ /* Styles for each address block */
+ }
+
+ .#{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;
+ }
+ .#{prefix}-totals__item-label {
+ min-width: 160px;
+ }
+ .#{prefix}-totals__item-amount {
+ flex: 1 1 auto;
+ text-align: right;
+ }
+
+ .#{prefix}-statement {
+ /* Styles for customer note/terms statement section */
+ }
+
+ .#{prefix}-statement__label {
+ /* Styles for statement label */
+ }
+
+ .#{prefix}-statement__value {
+ /* Styles for statement value */
+ }
block content
- div.credit
- div.credit__header
- div.paper
- h1.title #{__('credit.paper.credit_note')}
- if creditNote.creditNoteNumber
- span.creditNoteNumber #{creditNote.creditNoteNumber}
+ div(class=`${prefix}-root`)
+ div(class=`${prefix}-big-title`) Credit Note
- div.organization
- h3.title #{organizationName}
- if organizationEmail
- span.email #{organizationEmail}
+ if showCompanyLogo
+ div(class=`${prefix}-logo-wrap`)
+ img(src=companyLogo alt=`Company Logo`)
+
+ div(class=`${prefix}-terms-list`)
+ if showCreditNoteNumber
+ div(class=`${prefix}-terms-item`)
+ div(class=`${prefix}-terms-item__label`) #{creditNoteNumberLabel}:
+ div(class=`${prefix}-terms-item__value`) #{creditNoteNumebr}
- div.credit__full-amount
- div.label #{__('credit.paper.amount')}
- div.amount #{creditNote.formattedAmount}
+ if showCreditNoteDate
+ div(class=`${prefix}-terms-item`)
+ div(class=`${prefix}-terms-item__label`) #{creditNoteDateLabel}:
+ div(class=`${prefix}-terms-item__value`) #{creditNoteDate}
- div.credit__meta
- div.credit__meta-item.credit__meta-item--amount
- span.label #{__('credit.paper.remaining')}
- span.value #{creditNote.formattedCreditsRemaining}
+ div(class=`${prefix}-group`)
+ if showBilledFromAddress
+ div(class=`${prefix}-address`)
+ strong #{companyName}
+ each address in billedFromAddress
+ div #{address}
+ if showBilledToAddress
+ div(class=`${prefix}-address`)
+ strong #{billedToLabel}
+ each address in billedToAddress
+ div #{address}
- div.credit__meta-item.credit__meta-item--billed-to
- span.label #{__("credit.paper.billed_to")}
- span.value #{creditNote.customer.displayName}
-
- div.credit__meta-item.credit__meta-item--credit-date
- span.label #{__("credit.paper.credit_date")}
- span.value #{creditNote.formattedCreditNoteDate}
-
- div.credit__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")}
+ table(class=`${prefix}-table`)
+ thead(class=`${prefix}-table__header`)
+ tr
+ th #{'Item'}
+ th #{'Description'}
+ th #{'Rate'}
+ th #{'Total'}
tbody
- each entry in creditNote.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
+ each line in lines
+ tr(class=`${prefix}-table__row`)
+ td #{line.item}
+ td #{line.description}
+ td(class=`${prefix}-table__column--right`) #{line.rate}
+ td(class=`${prefix}-table__column--right`) #{line.total}
- div.credit__table-after
- div.credit__table-total
- table
- tbody
- tr.total
- td #{__('credit.paper.total')}
- td #{creditNote.formattedAmount}
- tr.payment-amount
- td #{__('credit.paper.credits_used')}
- td #{creditNote.formattedCreditsUsed}
- tr.blanace-due
- td #{__('credit.paper.credits_remaining')}
- td #{creditNote.formattedCreditsRemaining}
+ div(class=`${prefix}-totals`)
+ if showSubtotal
+ div(class=`${prefix}-totals__item ${prefix}-totals__item--border-gray`)
+ div #{subtotalLabel}:
+ div #{subtotal}
- div.credit__footer
- if creditNote.termsConditions
- div.credit__conditions
- h3 #{__("credit.paper.terms_conditions")}
- p #{creditNote.termsConditions}
+ if showTotal
+ div(class=`${prefix}-totals__item ${prefix}-totals__item--border-dark`)
+ div #{totalLabel}:
+ div #{total}
- if creditNote.note
- div.credit__notes
- h3 #{__("credit.paper.notes")}
- p #{creditNote.note}
\ No newline at end of file
+ if showCustomerNote
+ div(class=`${prefix}-statement`)
+ div(class=`${prefix}-statement__label`) #{customerNoteLabel}:
+ div(class=`${prefix}-statement__value`) #{customerNote}
+
+ if showTermsConditions
+ div(class=`${prefix}-statement`)
+ div(class=`${prefix}-statement__label`) #{termsConditionsLabel}:
+ div(class=`${prefix}-statement__value`) #{termsConditions}
diff --git a/packages/server/resources/views/modules/estimate-regular.pug b/packages/server/resources/views/modules/estimate-regular.pug
index 37cf85bfa..393cbceaf 100644
--- a/packages/server/resources/views/modules/estimate-regular.pug
+++ b/packages/server/resources/views/modules/estimate-regular.pug
@@ -1,82 +1,202 @@
extends ../PaperTemplateLayout.pug
-block head
- style
- if (isRtl)
- include ../../css/modules/estimate-rtl.css
- else
- include ../../css/modules/estimate.css
+block head
+ - var prefix = 'bc'
+ style.
+ .#{prefix}-root {
+ 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;
+ overflow: hidden;
+ }
+ .#{prefix}-terms-list {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ margin-bottom: 24px;
+ }
+ .#{prefix}-terms-item {
+ display: flex;
+ flex-direction: row;
+ gap: 12px;
+ }
+ .#{prefix}-terms-item__label {
+ min-width: 120px;
+ color: #333;
+ }
+ .#{prefix}-terms-item__value {
+ }
+ .#{prefix}-address-section{
+ 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 {
+ }
+ .#{prefix}-address__item {
+ }
+ .#{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--right{
+ text-align: right;
+ }
+ .#{prefix}-table__cell:first-of-type{
+ padding-left: 0;
+ }
+ .#{prefix}-table__cell:last-of-type {
+ padding-right: 0;
+ }
+ .#{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;
+ }
+ .#{prefix}-totals__item-label {
+ min-width: 160px;
+ }
+ .#{prefix}-totals__item-amount {
+ flex: 1 1 auto;
+ text-align: right;
+ }
+ .#{prefix}-statement {
+ }
+ .#{prefix}-statement__label {
+ }
+ .#{prefix}-statement__value {
+ }
block content
- div.estimate
- div.estimate__header
- div.paper
- h1.title #{__("estimate.paper.estimate")}
- span.email #{saleEstimate.estimateNumber}
+ div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
+ h1(class=`${prefix}-big-title`) Estimate
- div.organization
- h3.title #{organizationName}
- if organizationEmail
- span.email #{organizationEmail}
+ if showCompanyLogo
+ div(class=`${prefix}-logo-wrap`)
+ img(alt="", src=companyLogo)
- div.estimate__estimate-amount
- div.label #{__('estimate.paper.estimate_amount')}
- div.amount #{saleEstimate.formattedAmount}
+ //- Terms List
+ div(class=`${prefix}-terms`)
+ if showEstimateNumber
+ div(class=`${prefix}-terms-item`)
+ div(class=`${prefix}-terms-item__label`) #{estimateNumberLabel}
+ div(class=`${prefix}-terms-item__value`) #{estimateNumebr}
+ if showEstimateDate
+ div(class=`${prefix}-terms-item`)
+ div(class=`${prefix}-terms-item__label`) #{estimateDateLabel}
+ div(class=`${prefix}-terms-item__value`) #{estimateDate}
+ if showExpirationDate
+ div(class=`${prefix}-terms-item`)
+ div(class=`${prefix}-terms-item__label`) #{expirationDateLabel}
+ div(class=`${prefix}-terms-item__value`) #{expirationDate}
- div.estimate__meta
- if saleEstimate.estimateNumber
- div.estimate__meta-item.estimate__meta-item--estimate-number
- span.label #{__("estimate.paper.estimate_number")}
- span.value #{saleEstimate.estimateNumber}
+ //- Addresses (Group section)
+ div(class=`${prefix}-address-section`)
+ if showBilledFromAddress
+ div(class=`${prefix}-address`)
+ strong #{companyName}
+ each item in billedFromAddress
+ div(class=`${prefix}-address__item`) #{item}
- div.estimate__meta-item.estimate__meta-item--billed-to
- span.label #{__("estimate.paper.billed_to")}
- span.value #{saleEstimate.customer.displayName}
+ if showBilledToAddress
+ div(class=`${prefix}-address`)
+ strong #{billedToLabel}
+ each item in billedToAddress
+ div(class=`${prefix}-address__item`) #{item}
- div.estimate__meta-item.estimate__meta-item--estimate-date
- span.label #{__("estimate.paper.estimate_date")}
- span.value #{saleEstimate.formattedEstimateDate}
-
- div.estimate__meta-item.estimate__meta-item--due-date
- span.label #{__("estimate.paper.expiration_date")}
- span.value #{saleEstimate.formattedExpirationDate}
-
- div.estimate__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")}
+ //- Table section (Line items)
+ table(class=`${prefix}-table`)
+ thead
+ tr
+ th(class=`${prefix}-table__header`) Item
+ th(class=`${prefix}-table__header`) Description
+ th(class=`${prefix}-table__header ${prefix}-table__header--right`) Rate
+ th(class=`${prefix}-table__header ${prefix}-table__header--right`) Total
tbody
- each entry in saleEstimate.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
+ 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}
- div.estimate__table-after
- div.estimate__table-total
- table
- tbody
- tr.subtotal
- td #{__('estimate.paper.subtotal')}
- td #{saleEstimate.formattedAmount}
- tr.total
- td #{__('estimate.paper.total')}
- td #{saleEstimate.formattedAmount}
+ //- Totals section
+ div(class=`${prefix}-totals`)
+ if showSubtotal
+ div(class=`${prefix}-totals__item`)
+ div(class=`${prefix}-totals__item-label`) #{subtotalLabel}
+ div(class=`${prefix}-totals__item-amount`) #{subtotal}
+ if showTotal
+ div(class=`${prefix}-totals__item`)
+ div(class=`${prefix}-totals__item-label`) #{totalLabel}
+ div(class=`${prefix}-totals__item-amount`) #{total}
- div.estimate__footer
- if saleEstimate.termsConditions
- div.estimate__conditions
- h3 #{__("estimate.paper.conditions_title")}
- p #{saleEstimate.termsConditions}
+ //- Statements section
+ if showCustomerNote
+ div(class=`${prefix}-statement`)
+ div(class=`${prefix}-statement__label`) #{customerNoteLabel}
+ div(class=`${prefix}-statement__value`) #{customerNote}
- if saleEstimate.note
- div.estimate__notes
- h3 #{__("estimate.paper.notes_title")}
- p #{saleEstimate.note}
\ No newline at end of file
+ if showTermsConditions
+ div(class=`${prefix}-statement`)
+ div(class=`${prefix}-statement__label`) #{termsConditionsLabel}
+ div(class=`${prefix}-statement__value`) #{termsConditions}
\ 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
index d5b172a8d..006e33569 100644
--- a/packages/server/resources/views/modules/invoice-standard.pug
+++ b/packages/server/resources/views/modules/invoice-standard.pug
@@ -4,7 +4,6 @@ block head
- var prefix = 'bc'
style.
.#{prefix}-root {
- background-color: #fff;
color: #111;
padding: 24px 30px;
font-size: 12px;
@@ -25,7 +24,6 @@ block head
position: absolute;
right: 26px;
top: 26px;
- border-radius: 5px;
overflow: hidden;
}
.#{prefix}-details {
@@ -122,7 +120,6 @@ block head
}
.#{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;
@@ -143,6 +140,7 @@ block head
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
diff --git a/packages/server/resources/views/modules/payment-receive-standard.pug b/packages/server/resources/views/modules/payment-receive-standard.pug
index ee415b768..2c6e417cd 100644
--- a/packages/server/resources/views/modules/payment-receive-standard.pug
+++ b/packages/server/resources/views/modules/payment-receive-standard.pug
@@ -1,67 +1,187 @@
extends ../PaperTemplateLayout.pug
block head
- style
- if (isRtl)
- include ../../css/modules/payment-rtl.css
- else
- include ../../css/modules/payment.css
+ - var prefix = 'bp3';
+
+ style.
+ .#{prefix}-root{
+ 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;
+ overflow: hidden;
+ }
+ .#{prefix}-terms-list{
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ margin-bottom: 24px;
+ }
+ .#{prefix}-terms-item{
+ display: flex;
+ flex-direction: row;
+ gap: 12px;
+ }
+ .#{prefix}-terms-item__label{
+ min-width: 120px;
+ color: #333;
+ }
+ .#{prefix}-group{
+ }
+ .#{prefix}-addresses{
+ 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}-addresses > * {
+ flex: 1 1;
+ }
+ .#{prefix}-address__label{
+
+ }
+ .#{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}-table__column{
+
+ }
+ .#{prefix}-table__column--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--gray-border {
+ border-bottom: 1px solid #DADADA;
+ }
+ .#{prefix}-totals__item--dark-border {
+ border-bottom: 1px solid #000;
+ }
+ .#{prefix}-totals__item--bold {
+ font-weight: bold;
+ }
+ .#{prefix}-totals__item-label {
+ min-width: 160px;
+ }
+ .#{prefix}-totals__item-amount {
+ flex: 1 1 auto;
+ text-align: right;
+ }
block content
- div.payment
- div.payment__header
- div.paper
- h1.title #{__("payment.paper.payment_receipt")}
- if paymentReceive.paymentReceiveNo
- span.paymentNumber #{paymentReceive.paymentReceiveNo}
+ div(class=`${prefix}-root`)
+ div(class=`${prefix}-big-title`) Payment
- div.organization
- h3.title #{organizationName}
- if organizationEmail
- span.email #{organizationEmail}
+ if showCompanyLogo
+ div(class=`${prefix}-logo-wrap`)
+ img(src=companyLogo alt="Company Logo")
+
+ div(class=`${prefix}-terms-list`)
+ if showPaymentReceivedNumber
+ div(class=`${prefix}-terms-item`)
+ div(class=`${prefix}-terms-item__label`) #{paymentReceivedNumberLabel}
+ div(class=`${prefix}-terms-item__value`) #{paymentReceivedNumebr}
- div.payment__received-amount
- div.label #{__('payment.paper.amount_received')}
- div.amount #{paymentReceive.formattedAmount}
+ if showPaymentReceivedDate
+ div(class=`${prefix}-terms-item`)
+ div(class=`${prefix}-terms-item__label`) #{paymentReceivedDateLabel}
+ div(class=`${prefix}-terms-item__value`) #{paymentReceivedDate}
+
+ div(class=`${prefix}-addresses`)
+ if showBilledFromAddress
+ div(class=`${prefix}-address`)
+ strong(class=`${prefix}-address__item`) #{companyName}
+ each addressLine in billedFromAddress
+ div(class=`${prefix}-address__item`) #{addressLine}
- div.payment__meta
- div.payment__meta-item.payment__meta-item--billed-to
- span.label #{__("payment.paper.billed_to")}
- span.value #{paymentReceive.customer.displayName}
+ if showBillingToAddress
+ div(class=`${prefix}-address`)
+ strong(class=`${prefix}-address__item`) #{billedToLabel}
+ each addressLine in billedToAddress
+ div(class=`${prefix}-address__item`) #{addressLine}
- div.payment__meta-item.payment__meta-item--payment-date
- span.label #{__("payment.paper.payment_date")}
- span.value #{paymentReceive.formattedPaymentDate}
+ table(class=`${prefix}-table`)
+ thead
+ tr
+ th(class=`${prefix}-table__header`) Invoice #
+ th(class=`${prefix}-table__header ${prefix}-table__header--right`) Invoice Amount
+ th(class=`${prefix}-table__header ${prefix}-table__header--right`) Paid Amount
- div.payment__table
- table
- thead
- tr
- th.item #{__("payment.paper.invoice_number")}
- th.date #{__("payment.paper.invoice_date")}
- th.invoiceAmount #{__("payment.paper.invoice_amount")}
- th.paymentAmount #{__("payment.paper.payment_amount")}
- tbody
- each entry in paymentReceive.entries
- tr
- td.item=entry.invoice.invoiceNo
- td.date=entry.invoice.invoiceDateFormatted
- td.invoiceAmount=entry.invoice.totalFormatted
- td.paymentAmount=entry.invoice.paymentAmountFormatted
+ tbody
+ each line in lines
+ tr
+ td(class=`${prefix}-table__cell`) #{line.invoiceNumber}
+ td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.invoiceAmount}
+ td(class=`${prefix}-table__cell ${prefix}-table__cell--right`) #{line.paidAmount}
- div.payment__table-after
- div.payment__table-total
- table
- tbody
- tr.payment-amount
- td #{__('payment.paper.payment_amount')}
- td #{paymentReceive.formattedAmount}
- tr.blanace-due
- td #{__('payment.paper.balance_due')}
- td #{paymentReceive.customer.closingBalance}
+ div(class=`${prefix}-totals`)
+ if showSubtotal
+ div(class=`${prefix}-totals__item`)
+ div(class=`${prefix}-totals__item-label`) #{subtotalLabel}
+ div(class=`${prefix}-totals__item-amount`) #{subtotal}
- div.payment__footer
- if paymentReceive.statement
- div.payment__notes
- h3 #{__("payment.paper.statement")}
- p #{paymentReceive.statement}
\ No newline at end of file
+ if showTotal
+ div(class=`${prefix}-totals__item`)
+ div(class=`${prefix}-totals__item-label`) #{totalLabel}
+ div(class=`${prefix}-totals__item-amount`) #{total}
diff --git a/packages/server/resources/views/modules/receipt-regular.pug b/packages/server/resources/views/modules/receipt-regular.pug
index a0cbb1f61..f9ccd1a72 100644
--- a/packages/server/resources/views/modules/receipt-regular.pug
+++ b/packages/server/resources/views/modules/receipt-regular.pug
@@ -1,77 +1,190 @@
extends ../PaperTemplateLayout.pug
block head
- style
- if (isRtl)
- include ../../css/modules/receipt-rtl.css
- else
- include ../../css/modules/receipt.css
+ - var prefix = 'bc'
+ style.
+ .#{prefix}-root {
+ color: #111;
+ padding: 24px 30px;
+ font-size: 12px;
+ position: relative;
+ box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
+ }
+ .#{prefix}-logo-wrap {
+ font-size: 60px;
+ margin: 0;
+ line-height: 1;
+ margin-bottom: 25px;
+ font-weight: 500;
+ color: #333;
+ }
+ .#{prefix}-big-title {
+ height: 120px;
+ width: 120px;
+ position: absolute;
+ right: 26px;
+ top: 26px;
+ overflow: hidden;
+ }
+ .#{prefix}-terms-list {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ margin-bottom: 24px;
+ }
+ .#{prefix}-terms-item {
+ display: flex;
+ flex-direction: row;
+ gap: 12px;
+ }
+ .#{prefix}-terms-item__label {
+ min-width: 120px;
+ color: #333;
+ }
+ .#{prefix}-terms-item__value {}
+ .#{prefix}-address-section {
+ 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 {}
+ .#{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__line {
+ display: flex;
+ padding: 4px 0;
+ }
+ .#{prefix}-totals__line--gray-border {}
+ .#{prefix}-totals__line--dark-border {}
+ .#{prefix}-totals__line__label {
+ min-width: 160px;
+ }
+ .#{prefix}-totals__line__amount {
+ flex: 1 1 auto;
+ text-align: right;
+ }
+ .#{prefix}-statement {
+ margin-bottom: 20px;
+ }
+ .#{prefix}-statement__label {}
+ .#{prefix}-statement__value {}
block content
- div.receipt
- div.receipt__header
- div.paper
- h1.title #{__("receipt.paper.receipt")}
- span.receiptNumber #{saleReceipt.receiptNumber}
+ //- block head
+ div(class=`${prefix}-root`, style=`--invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor};`)
+
+ //- Title and company logo
+ h1(class=`${prefix}-big-title`) Receipt
- div.organization
- h3.title #{organizationName}
+ if showCompanyLogo
+ div(class=`${prefix}-logo-wrap`)
+ img(src=companyLogo alt=`Company Logo`)
- div.receipt__receipt-amount
- div.label #{__('receipt.paper.receipt_amount')}
- div.amount #{saleReceipt.formattedAmount}
+ //- Terms List
+ div(class=`${prefix}-terms-list`)
+ if showReceiptNumber
+ div(class=`${prefix}-terms-item`)
+ span(class=`${prefix}-terms-item__label`)= receiptNumberLabel
+ span(class=`${prefix}-terms-item__value`)= receiptNumber
+ if showReceiptDate
+ div(class=`${prefix}-terms-item`)
+ span(class=`${prefix}-terms-item__label`)= receiptDateLabel
+ span(class=`${prefix}-terms-item__value`)= receiptDate
- div.receipt__meta
- div.receipt__meta-item.receipt__meta-item--billed-to
- span.label #{__("receipt.paper.billed_to")}
- span.value #{saleReceipt.customer.displayName}
+ //- Address Section
+ div(class=`${prefix}-address-section`)
+ if showBilledFromAddress
+ div(class=`${prefix}-address`)
+ strong= companyName
+ each addressLine in billedFromAddress
+ div= addressLine
- div.receipt__meta-item.receipt__meta-item--invoice-date
- span.label #{__("receipt.paper.receipt_date")}
- span.value #{saleReceipt.formattedReceiptDate}
+ if showBilledToAddress
+ div(class=`${prefix}-address`)
+ strong= billedToLabel
+ each addressLine in billedToAddress
+ div= addressLine
- if saleReceipt.receiptNumber
- div.receipt__meta-item.receipt__meta-item--invoice-number
- span.label #{__("receipt.paper.receipt_number")}
- span.value #{saleReceipt.receiptNumber}
+ //- Table Section
+ table(class=`${prefix}-table`)
+ thead(class=`${prefix}-table__header`)
+ tr
+ th(class=`${prefix}-table__header`) Item
+ th(class=`${prefix}-table__header`) Description
+ th(class=`${prefix}-table__header ${prefix}-table__header--right`) Rate
+ th(class=`${prefix}-table__header ${prefix}-table__header--right`) Total
+ tbody
+ each line in lines
+ tr(class=`${prefix}-table__row`)
+ td(class=`${prefix}-table__column`)= line.item
+ td(class=`${prefix}-table__column`)= line.description
+ td(class=`${prefix}-table__column ${prefix}-table__column--right`)= line.rate
+ td(class=`${prefix}-table__column ${prefix}-table__column--right`)= line.total
- div.receipt__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 saleReceipt.entries
- tr
- td.item=entry.item.name
- td.rate=entry.rate
- td.quantity=entry.quantity
- td.total=entry.amount
-
- div.receipt__table-after
- div.receipt__table-total
- table
- tbody
- tr.total
- td #{__('receipt.paper.total')}
- td #{saleReceipt.formattedAmount}
- tr.payment-amount
- td #{__('receipt.paper.payment_amount')}
- td #{saleReceipt.formattedAmount}
- tr.blanace-due
- td #{__('receipt.paper.balance_due')}
- td #{'$0'}
+ //- Totals Section
+ div(class=`${prefix}-totals`)
+ if showSubtotal
+ div(class=`${prefix}-totals__line #{prefix}-totals__line--gray-border`)
+ span(class=`${prefix}-totals__line__label`)= subtotalLabel
+ span(class=`${prefix}-totals__line__amount`)= subtotal
+ if showTotal
+ div(class=`${prefix}-totals__line #{prefix}-totals__line--dark-border`)
+ span(class=`${prefix}-totals__line__label`)= totalLabel
+ span(class=`${prefix}-totals__line__amount`)= total
- div.receipt__footer
- if saleReceipt.statement
- div.receipt__conditions
- h3 #{__("receipt.paper.statement")}
- p #{saleReceipt.statement}
+ //- Customer Note Section
+ if showCustomerNote
+ div(class=`${prefix}-statement`)
+ span(class=`${prefix}-statement__label`)= customerNoteLabel
+ p(class=`${prefix}-statement__value`)= customerNote
- if saleReceipt.receiptMessage
- div.receipt__notes
- h3 #{__("receipt.paper.notes")}
- p #{saleReceipt.receiptMessage}
\ No newline at end of file
+ //- Terms & Conditions Section
+ if showTermsConditions
+ div(class=`${prefix}-statement`)
+ span(class=`${prefix}-statement__label`)= termsConditionsLabel
+ p(class=`${prefix}-statement__value`)= termsConditions
diff --git a/packages/server/src/interfaces/CreditNote.ts b/packages/server/src/interfaces/CreditNote.ts
index 22ff521d7..136425818 100644
--- a/packages/server/src/interfaces/CreditNote.ts
+++ b/packages/server/src/interfaces/CreditNote.ts
@@ -258,3 +258,49 @@ export type ICreditNoteGLCommonEntry = Pick<
| 'debit'
| 'branchId'
>;
+
+export interface CreditNotePdfTemplateAttributes {
+ primaryColor: string;
+ secondaryColor: string;
+ showCompanyLogo: boolean;
+ companyLogo: string;
+ companyName: string;
+
+ billedToAddress: string[];
+ billedFromAddress: string[];
+ showBilledToAddress: boolean;
+ showBilledFromAddress: boolean;
+ billedToLabel: string;
+
+ total: string;
+ totalLabel: string;
+ showTotal: boolean;
+
+ subtotal: string;
+ subtotalLabel: string;
+ showSubtotal: boolean;
+
+ showCustomerNote: boolean;
+ customerNote: string;
+ customerNoteLabel: string;
+
+ showTermsConditions: boolean;
+ termsConditions: string;
+ termsConditionsLabel: string;
+
+ lines: Array<{
+ item: string;
+ description: string;
+ rate: string;
+ quantity: string;
+ total: string;
+ }>;
+
+ showCreditNoteNumber: boolean;
+ creditNoteNumberLabel: string;
+ creditNoteNumebr: string;
+
+ creditNoteDate: string;
+ showCreditNoteDate: boolean;
+ creditNoteDateLabel: string;
+}
diff --git a/packages/server/src/interfaces/PaymentReceive.ts b/packages/server/src/interfaces/PaymentReceive.ts
index ea90e58af..fbc24ba43 100644
--- a/packages/server/src/interfaces/PaymentReceive.ts
+++ b/packages/server/src/interfaces/PaymentReceive.ts
@@ -25,6 +25,7 @@ export interface IPaymentReceived {
updatedAt: Date;
localAmount?: number;
branchId?: number;
+ pdfTemplateId?: number;
}
export interface IPaymentReceivedCreateDTO {
customerId: number;
@@ -185,3 +186,70 @@ export interface PaymentReceiveMailPresendEvent {
paymentReceiveId: number;
messageOptions: PaymentReceiveMailOptsDTO;
}
+
+export interface PaymentReceivedPdfLineItem {
+ item: string;
+ description: string;
+ rate: string;
+ quantity: string;
+ total: string;
+}
+
+export interface PaymentReceivedPdfTax {
+ label: string;
+ amount: string;
+}
+
+export interface PaymentReceivedPdfTemplateAttributes {
+ primaryColor: string;
+ secondaryColor: string;
+ companyName: string;
+
+ showCompanyLogo: boolean;
+ companyLogo: string;
+
+ dueDateLabel: string;
+ showDueDate: boolean;
+
+ dateIssueLabel: string;
+ showDateIssue: boolean;
+
+ invoiceNumberLabel: 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;
+
+ discount: string;
+
+ termsConditionsLabel: string;
+ showTermsConditions: boolean;
+
+ lines: PaymentReceivedPdfLineItem[];
+ taxes: PaymentReceivedPdfTax[];
+
+ statementLabel: string;
+ showStatement: boolean;
+ billedToAddress: string[];
+ billedFromAddress: string[];
+}
diff --git a/packages/server/src/interfaces/SaleEstimate.ts b/packages/server/src/interfaces/SaleEstimate.ts
index bc6231fc4..f53693b52 100644
--- a/packages/server/src/interfaces/SaleEstimate.ts
+++ b/packages/server/src/interfaces/SaleEstimate.ts
@@ -143,3 +143,4 @@ export interface ISaleEstimateMailPresendEvent {
saleEstimateId: number;
messageOptions: SaleEstimateMailOptionsDTO;
}
+
diff --git a/packages/server/src/services/CreditNotes/CreateCreditNote.ts b/packages/server/src/services/CreditNotes/CreateCreditNote.ts
index c47587fbe..d48a1371f 100644
--- a/packages/server/src/services/CreditNotes/CreateCreditNote.ts
+++ b/packages/server/src/services/CreditNotes/CreateCreditNote.ts
@@ -59,7 +59,7 @@ export default class CreateCreditNote extends BaseCreditNotes {
creditNoteDTO.entries
);
// Transformes the given DTO to storage layer data.
- const creditNoteModel = this.transformCreateEditDTOToModel(
+ const creditNoteModel = await this.transformCreateEditDTOToModel(
tenantId,
creditNoteDTO,
customer.currencyCode
diff --git a/packages/server/src/services/CreditNotes/CreditNoteBrandingTemplate.ts b/packages/server/src/services/CreditNotes/CreditNoteBrandingTemplate.ts
new file mode 100644
index 000000000..1b35dc29b
--- /dev/null
+++ b/packages/server/src/services/CreditNotes/CreditNoteBrandingTemplate.ts
@@ -0,0 +1,30 @@
+import { Inject } from "typedi";
+import { GetPdfTemplate } from "../PdfTemplate/GetPdfTemplate";
+import { defaultCreditNoteBrandingAttributes } from "./constants";
+import { mergePdfTemplateWithDefaultAttributes } from "../Sales/Invoices/utils";
+
+export class CreditNoteBrandingTemplate {
+ @Inject()
+ private getPdfTemplateService: GetPdfTemplate;
+
+ /**
+ * Retrieves the credit note branding template.
+ * @param {number} tenantId
+ * @param {number} templateId
+ * @returns {}
+ */
+ public async getCreditNoteBrandingTemplate(tenantId: number, templateId: number) {
+ const template = await this.getPdfTemplateService.getPdfTemplate(
+ tenantId,
+ templateId
+ );
+ const attributes = mergePdfTemplateWithDefaultAttributes(
+ template.attributes,
+ defaultCreditNoteBrandingAttributes
+ );
+ return {
+ ...template,
+ attributes,
+ };
+ }
+}
diff --git a/packages/server/src/services/CreditNotes/CreditNotes.ts b/packages/server/src/services/CreditNotes/CreditNotes.ts
index 414be0e2a..5f3a57a0d 100644
--- a/packages/server/src/services/CreditNotes/CreditNotes.ts
+++ b/packages/server/src/services/CreditNotes/CreditNotes.ts
@@ -2,6 +2,7 @@ import { Service, Inject } from 'typedi';
import moment from 'moment';
import { omit } from 'lodash';
import * as R from 'ramda';
+import composeAsync from 'async/compose';
import { ServiceError } from '@/exceptions';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { ERRORS } from './constants';
@@ -16,6 +17,7 @@ import AutoIncrementOrdersService from '@/services/Sales/AutoIncrementOrdersServ
import { WarehouseTransactionDTOTransform } from '@/services/Warehouses/Integrations/WarehouseTransactionDTOTransform';
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
import { assocItemEntriesDefaultIndex } from '../Items/utils';
+import { BrandingTemplateDTOTransformer } from '../PdfTemplate/BrandingTemplateDTOTransformer';
@Service()
export default class BaseCreditNotes {
@@ -34,17 +36,20 @@ export default class BaseCreditNotes {
@Inject()
private warehouseDTOTransform: WarehouseTransactionDTOTransform;
+ @Inject()
+ private brandingTemplatesTransformer: BrandingTemplateDTOTransformer;
+
/**
* Transformes the credit/edit DTO to model.
* @param {ICreditNoteNewDTO | ICreditNoteEditDTO} creditNoteDTO
* @param {string} customerCurrencyCode -
*/
- protected transformCreateEditDTOToModel = (
+ protected transformCreateEditDTOToModel = async (
tenantId: number,
creditNoteDTO: ICreditNoteNewDTO | ICreditNoteEditDTO,
customerCurrencyCode: string,
oldCreditNote?: ICreditNote
- ): ICreditNote => {
+ ): Promise => {
// Retrieve the total amount of the given items entries.
const amount = this.itemsEntriesService.getTotalItemsEntries(
creditNoteDTO.entries
@@ -83,10 +88,18 @@ export default class BaseCreditNotes {
refundedAmount: 0,
invoicesAmount: 0,
};
+ const initialAsyncDTO = await composeAsync(
+ // Assigns the default branding template id to the invoice DTO.
+ this.brandingTemplatesTransformer.assocDefaultBrandingTemplate(
+ tenantId,
+ 'CreditNote'
+ )
+ )(initialDTO);
+
return R.compose(
this.branchDTOTransform.transformDTO(tenantId),
this.warehouseDTOTransform.transformDTO(tenantId)
- )(initialDTO);
+ )(initialAsyncDTO);
};
/**
diff --git a/packages/server/src/services/CreditNotes/EditCreditNote.ts b/packages/server/src/services/CreditNotes/EditCreditNote.ts
index 0e045227d..cbeed9ab2 100644
--- a/packages/server/src/services/CreditNotes/EditCreditNote.ts
+++ b/packages/server/src/services/CreditNotes/EditCreditNote.ts
@@ -63,7 +63,7 @@ export default class EditCreditNote extends BaseCreditNotes {
creditNoteEditDTO.entries
);
// Transformes the given DTO to storage layer data.
- const creditNoteModel = this.transformCreateEditDTOToModel(
+ const creditNoteModel = await this.transformCreateEditDTOToModel(
tenantId,
creditNoteEditDTO,
customer.currencyCode,
diff --git a/packages/server/src/services/CreditNotes/GetCreditNotePdf.ts b/packages/server/src/services/CreditNotes/GetCreditNotePdf.ts
index c54c8096e..5a1ea0faf 100644
--- a/packages/server/src/services/CreditNotes/GetCreditNotePdf.ts
+++ b/packages/server/src/services/CreditNotes/GetCreditNotePdf.ts
@@ -2,9 +2,16 @@ import { Inject, Service } from 'typedi';
import { ChromiumlyTenancy } from '../ChromiumlyTenancy/ChromiumlyTenancy';
import { TemplateInjectable } from '../TemplateInjectable/TemplateInjectable';
import GetCreditNote from './GetCreditNote';
+import { CreditNoteBrandingTemplate } from './CreditNoteBrandingTemplate';
+import { CreditNotePdfTemplateAttributes } from '@/interfaces';
+import HasTenancyService from '../Tenancy/TenancyService';
+import { transformCreditNoteToPdfTemplate } from './utils';
@Service()
export default class GetCreditNotePdf {
+ @Inject()
+ private tenancy: HasTenancyService;
+
@Inject()
private chromiumlyTenancy: ChromiumlyTenancy;
@@ -14,25 +21,62 @@ export default class GetCreditNotePdf {
@Inject()
private getCreditNoteService: GetCreditNote;
+ @Inject()
+ private creditNoteBrandingTemplate: CreditNoteBrandingTemplate;
+
/**
- * Retrieve sale invoice pdf content.
+ * Retrieves sale invoice pdf content.
* @param {number} tenantId - Tenant id.
* @param {number} creditNoteId - Credit note id.
*/
public async getCreditNotePdf(tenantId: number, creditNoteId: number) {
+ const brandingAttributes = await this.getCreditNoteBrandingAttributes(
+ tenantId,
+ creditNoteId
+ );
+ console.log(brandingAttributes, 'brandingAttributes');
+
+ const htmlContent = await this.templateInjectable.render(
+ tenantId,
+ 'modules/credit-note-standard',
+ brandingAttributes
+ );
+ return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent);
+ }
+
+ /**
+ * Retrieves credit note branding attributes.
+ * @param {number} tenantId - The ID of the tenant.
+ * @param {number} creditNoteId - The ID of the credit note.
+ * @returns {Promise} The credit note branding attributes.
+ */
+ public async getCreditNoteBrandingAttributes(
+ tenantId: number,
+ creditNoteId: number
+ ): Promise {
+ const { PdfTemplate } = this.tenancy.models(tenantId);
const creditNote = await this.getCreditNoteService.getCreditNote(
tenantId,
creditNoteId
);
- const htmlContent = await this.templateInjectable.render(
- tenantId,
- 'modules/credit-note-standard',
- {
- creditNote,
- }
- );
- return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent, {
- margins: { top: 0, bottom: 0, left: 0, right: 0 },
- });
+ // Retrieve the invoice template id of not found get the default template id.
+ const templateId =
+ creditNote.pdfTemplateId ??
+ (
+ await PdfTemplate.query().findOne({
+ resource: 'CreditNote',
+ default: true,
+ })
+ )?.id;
+ // Retrieves the credit note branding template.
+ const brandingTemplate =
+ await this.creditNoteBrandingTemplate.getCreditNoteBrandingTemplate(
+ tenantId,
+ templateId
+ );
+ return {
+ ...brandingTemplate.attributes,
+ ...transformCreditNoteToPdfTemplate(creditNote),
+ };
}
}
diff --git a/packages/server/src/services/CreditNotes/constants.ts b/packages/server/src/services/CreditNotes/constants.ts
index 9d0060075..9691a4b77 100644
--- a/packages/server/src/services/CreditNotes/constants.ts
+++ b/packages/server/src/services/CreditNotes/constants.ts
@@ -9,7 +9,7 @@ export const ERRORS = {
'CREDIT_NOTE_APPLY_TO_INVOICES_NOT_FOUND',
CREDIT_NOTE_HAS_REFUNDS_TRANSACTIONS: 'CREDIT_NOTE_HAS_REFUNDS_TRANSACTIONS',
CREDIT_NOTE_HAS_APPLIED_INVOICES: 'CREDIT_NOTE_HAS_APPLIED_INVOICES',
- CUSTOMER_HAS_LINKED_CREDIT_NOTES: 'CUSTOMER_HAS_LINKED_CREDIT_NOTES'
+ CUSTOMER_HAS_LINKED_CREDIT_NOTES: 'CUSTOMER_HAS_LINKED_CREDIT_NOTES',
};
export const DEFAULT_VIEW_COLUMNS = [];
@@ -66,3 +66,72 @@ export const DEFAULT_VIEWS = [
columns: DEFAULT_VIEW_COLUMNS,
},
];
+
+export const defaultCreditNoteBrandingAttributes = {
+ primaryColor: '',
+ secondaryColor: '',
+ showCompanyLogo: true,
+ companyLogo: '',
+ companyName: 'Bigcapital Technology, Inc.',
+
+ // Address
+ billedToAddress: [
+ 'Bigcapital Technology, Inc.',
+ '131 Continental Dr Suite 305 Newark,',
+ 'Delaware 19713',
+ 'United States',
+ '+1 762-339-5634',
+ 'ahmed@bigcapital.app',
+ ],
+ billedFromAddress: [
+ '131 Continental Dr Suite 305 Newark,',
+ 'Delaware 19713',
+ 'United States',
+ '+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',
+
+ lines: [
+ {
+ item: 'Simply dummy text',
+ description: 'Simply dummy text of the printing and typesetting',
+ rate: '1',
+ quantity: '1000',
+ 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',
+};
diff --git a/packages/server/src/services/CreditNotes/utils.ts b/packages/server/src/services/CreditNotes/utils.ts
new file mode 100644
index 000000000..e81de43f2
--- /dev/null
+++ b/packages/server/src/services/CreditNotes/utils.ts
@@ -0,0 +1,9 @@
+import { CreditNotePdfTemplateAttributes } from "@/interfaces";
+import CreditNote from "@/models/CreditNote";
+
+
+export const transformCreditNoteToPdfTemplate = (creditNote: CreditNote): Partial {
+ return {
+
+ };
+}
\ No newline at end of file
diff --git a/packages/server/src/services/PdfTemplate/BrandingTemplateDTOTransformer.ts b/packages/server/src/services/PdfTemplate/BrandingTemplateDTOTransformer.ts
index 379f097ca..88aea05fb 100644
--- a/packages/server/src/services/PdfTemplate/BrandingTemplateDTOTransformer.ts
+++ b/packages/server/src/services/PdfTemplate/BrandingTemplateDTOTransformer.ts
@@ -1,6 +1,7 @@
import * as R from 'ramda';
import HasTenancyService from '../Tenancy/TenancyService';
import { Inject, Service } from 'typedi';
+import { isEmpty } from 'lodash';
@Service()
export class BrandingTemplateDTOTransformer {
@@ -9,31 +10,28 @@ export class BrandingTemplateDTOTransformer {
/**
* Associates the default branding template id.
- * @param {number} tenantId
- * @param {string} resource
- * @param {Record} object
- * @param {string} attributeName
- * @returns
+ * @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';
+ 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,
+ const defaultTemplate = await PdfTemplate.query().findOne({
+ resource,
+ default: true,
+ });
+ if (!defaultTemplate || !isEmpty(object[attributeName])) {
+ return object;
+ }
+ return {
+ ...object,
+ [attributeName]: defaultTemplate.id,
+ };
};
- },
}
diff --git a/packages/server/src/services/Sales/Estimates/SaleEstimateDTOTransformer.ts b/packages/server/src/services/Sales/Estimates/SaleEstimateDTOTransformer.ts
index 80879af7f..32b1245bd 100644
--- a/packages/server/src/services/Sales/Estimates/SaleEstimateDTOTransformer.ts
+++ b/packages/server/src/services/Sales/Estimates/SaleEstimateDTOTransformer.ts
@@ -1,6 +1,7 @@
import * as R from 'ramda';
import { Inject, Service } from 'typedi';
import { omit, sumBy } from 'lodash';
+import composeAsync from 'async/compose';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { ICustomer, ISaleEstimate, ISaleEstimateDTO } from '@/interfaces';
import { SaleEstimateValidators } from './SaleEstimateValidators';
@@ -10,6 +11,7 @@ import { formatDateFields } from '@/utils';
import moment from 'moment';
import { SaleEstimateIncrement } from './SaleEstimateIncrement';
import { assocItemEntriesDefaultIndex } from '@/services/Items/utils';
+import { BrandingTemplateDTOTransformer } from '@/services/PdfTemplate/BrandingTemplateDTOTransformer';
@Service()
export class SaleEstimateDTOTransformer {
@@ -28,6 +30,9 @@ export class SaleEstimateDTOTransformer {
@Inject()
private estimateIncrement: SaleEstimateIncrement;
+ @Inject()
+ private brandingTemplatesTransformer: BrandingTemplateDTOTransformer;
+
/**
* Transform create DTO object ot model object.
* @param {number} tenantId
@@ -81,10 +86,18 @@ export class SaleEstimateDTOTransformer {
deliveredAt: moment().toMySqlDateTime(),
}),
};
+ const initialAsyncDTO = await composeAsync(
+ // Assigns the default branding template id to the invoice DTO.
+ this.brandingTemplatesTransformer.assocDefaultBrandingTemplate(
+ tenantId,
+ 'SaleEstimate'
+ )
+ )(initialDTO);
+
return R.compose(
this.branchDTOTransform.transformDTO(tenantId),
this.warehouseDTOTransform.transformDTO(tenantId)
- )(initialDTO);
+ )(initialAsyncDTO);
}
/**
diff --git a/packages/server/src/services/Sales/Estimates/SaleEstimatesPdf.ts b/packages/server/src/services/Sales/Estimates/SaleEstimatesPdf.ts
index af1d2098c..ef89fc919 100644
--- a/packages/server/src/services/Sales/Estimates/SaleEstimatesPdf.ts
+++ b/packages/server/src/services/Sales/Estimates/SaleEstimatesPdf.ts
@@ -2,9 +2,16 @@ import { Inject, Service } from 'typedi';
import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy';
import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable';
import { GetSaleEstimate } from './GetSaleEstimate';
+import HasTenancyService from '@/services/Tenancy/TenancyService';
+import { SaleEstimatePdfTemplate } from '../Invoices/SaleEstimatePdfTemplate';
+import { transformEstimateToPdfTemplate } from './utils';
+import { EstimatePdfBrandingAttributes } from './constants';
@Service()
export class SaleEstimatesPdf {
+ @Inject()
+ private tenancy: HasTenancyService;
+
@Inject()
private chromiumlyTenancy: ChromiumlyTenancy;
@@ -14,25 +21,58 @@ export class SaleEstimatesPdf {
@Inject()
private getSaleEstimate: GetSaleEstimate;
+ @Inject()
+ private estimatePdfTemplate: SaleEstimatePdfTemplate;
+
/**
* Retrieve sale invoice pdf content.
* @param {number} tenantId -
* @param {ISaleInvoice} saleInvoice -
*/
public async getSaleEstimatePdf(tenantId: number, saleEstimateId: number) {
- const saleEstimate = await this.getSaleEstimate.getEstimate(
+ const brandingAttributes = await this.getEstimateBrandingAttributes(
tenantId,
saleEstimateId
);
const htmlContent = await this.templateInjectable.render(
tenantId,
'modules/estimate-regular',
- {
- saleEstimate,
- }
+ brandingAttributes
);
- return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent, {
- margins: { top: 0, bottom: 0, left: 0, right: 0 },
- });
+ return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent);
+ }
+
+ /**
+ *
+ * @param {number} tenantId
+ * @param {number} estimateId
+ */
+ async getEstimateBrandingAttributes(
+ tenantId: number,
+ estimateId: number
+ ): Promise {
+ const { PdfTemplate } = this.tenancy.models(tenantId);
+ const saleEstimate = await this.getSaleEstimate.getEstimate(
+ tenantId,
+ estimateId
+ );
+ // Retrieve the invoice template id of not found get the default template id.
+ const templateId =
+ saleEstimate.pdfTemplateId ??
+ (
+ await PdfTemplate.query().findOne({
+ resource: 'SaleEstimate',
+ default: true,
+ })
+ )?.id;
+ const brandingTemplate =
+ await this.estimatePdfTemplate.getEstimatePdfTemplate(
+ tenantId,
+ templateId
+ );
+ return {
+ ...brandingTemplate.attributes,
+ ...transformEstimateToPdfTemplate(saleEstimate),
+ };
}
}
diff --git a/packages/server/src/services/Sales/Estimates/constants.ts b/packages/server/src/services/Sales/Estimates/constants.ts
index 1870c01d2..a5f9a9520 100644
--- a/packages/server/src/services/Sales/Estimates/constants.ts
+++ b/packages/server/src/services/Sales/Estimates/constants.ts
@@ -173,3 +173,122 @@ export const SaleEstimatesSampleData = [
'Line Description': 'Qui suscipit ducimus qui qui.',
},
];
+
+export const defaultEstimatePdfBrandingAttributes = {
+ primaryColor: '#000',
+ secondaryColor: '#000',
+ showCompanyLogo: true,
+ companyLogo: '',
+ companyName: '',
+
+ billedToAddress: [
+ 'Bigcapital Technology, Inc.',
+ '131 Continental Dr Suite 305 Newark,',
+ 'Delaware 19713',
+ 'United States',
+ '+1 762-339-5634',
+ 'ahmed@bigcapital.app',
+ ],
+ billedFromAddress: [
+ '131 Continental Dr Suite 305 Newark,',
+ 'Delaware 19713',
+ 'United States',
+ '+1 762-339-5634',
+ 'ahmed@bigcapital.app',
+ ],
+ showBilledFromAddress: true,
+ showBilledToAddress: true,
+ billedToLabel: 'Billed To',
+
+ total: '$1000.00',
+ totalLabel: 'Total',
+ showTotal: true,
+
+ subtotal: '1000/00',
+ subtotalLabel: 'Subtotal',
+ showSubtotal: true,
+
+ 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',
+
+ 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',
+
+ lines: [
+ {
+ item: 'Simply dummy text',
+ description: 'Simply dummy text of the printing and typesetting',
+ rate: '1',
+ quantity: '1000',
+ total: '$1000.00',
+ },
+ ],
+ showEstimateNumber: true,
+ estimateNumberLabel: 'Estimate Number',
+ estimateNumebr: '346D3D40-0001',
+
+ estimateDate: 'September 3, 2024',
+ showEstimateDate: true,
+ estimateDateLabel: 'Estimate Date',
+
+ expirationDateLabel: 'Expiration Date',
+ showExpirationDate: true,
+ expirationDate: 'September 3, 2024',
+};
+
+
+interface EstimatePdfBrandingLineItem {
+ item: string;
+ description: string;
+ rate: string;
+ quantity: string;
+ total: string;
+}
+
+export interface EstimatePdfBrandingAttributes {
+ primaryColor: string;
+ secondaryColor: string;
+ showCompanyLogo: boolean;
+ companyLogo: string;
+ companyName: string;
+
+ billedToAddress: string[];
+ billedFromAddress: string[];
+ showBilledFromAddress: boolean;
+ showBilledToAddress: boolean;
+ billedToLabel: string;
+
+ total: string;
+ totalLabel: string;
+ showTotal: boolean;
+
+ subtotal: string;
+ subtotalLabel: string;
+ showSubtotal: boolean;
+
+ showCustomerNote: boolean;
+ customerNote: string;
+ customerNoteLabel: string;
+
+ showTermsConditions: boolean;
+ termsConditions: string;
+ termsConditionsLabel: string;
+
+ lines: EstimatePdfBrandingLineItem[];
+
+ showEstimateNumber: boolean;
+ estimateNumberLabel: string;
+ estimateNumebr: string;
+
+ estimateDate: string;
+ showEstimateDate: boolean;
+ estimateDateLabel: string;
+
+ expirationDateLabel: string;
+ showExpirationDate: boolean;
+ expirationDate: string;
+}
\ No newline at end of file
diff --git a/packages/server/src/services/Sales/Estimates/utils.ts b/packages/server/src/services/Sales/Estimates/utils.ts
new file mode 100644
index 000000000..2b7c7fd1b
--- /dev/null
+++ b/packages/server/src/services/Sales/Estimates/utils.ts
@@ -0,0 +1,7 @@
+import { EstimatePdfBrandingAttributes } from './constants';
+
+export const transformEstimateToPdfTemplate = (
+ estimate
+): Partial => {
+ return {};
+};
diff --git a/packages/server/src/services/Sales/Invoices/SaleEstimatePdfTemplate.ts b/packages/server/src/services/Sales/Invoices/SaleEstimatePdfTemplate.ts
new file mode 100644
index 000000000..de324ea90
--- /dev/null
+++ b/packages/server/src/services/Sales/Invoices/SaleEstimatePdfTemplate.ts
@@ -0,0 +1,31 @@
+import { Inject, Service } from 'typedi';
+import { mergePdfTemplateWithDefaultAttributes } from './utils';
+import { GetPdfTemplate } from '@/services/PdfTemplate/GetPdfTemplate';
+import { defaultEstimatePdfBrandingAttributes } from '../Estimates/constants';
+
+@Service()
+export class SaleEstimatePdfTemplate {
+ @Inject()
+ private getPdfTemplateService: GetPdfTemplate;
+
+ /**
+ * Retrieves the estimate pdf template.
+ * @param {number} tenantId
+ * @param {number} invoiceTemplateId
+ * @returns
+ */
+ async getEstimatePdfTemplate(tenantId: number, estimateTemplateId: number) {
+ const template = await this.getPdfTemplateService.getPdfTemplate(
+ tenantId,
+ estimateTemplateId
+ );
+ const attributes = mergePdfTemplateWithDefaultAttributes(
+ template.attributes,
+ defaultEstimatePdfBrandingAttributes
+ );
+ return {
+ ...template,
+ attributes,
+ };
+ }
+}
diff --git a/packages/server/src/services/Sales/PaymentReceived/GetPaymentReceivedPdf.ts b/packages/server/src/services/Sales/PaymentReceived/GetPaymentReceivedPdf.ts
index 0a357e5ea..afae67359 100644
--- a/packages/server/src/services/Sales/PaymentReceived/GetPaymentReceivedPdf.ts
+++ b/packages/server/src/services/Sales/PaymentReceived/GetPaymentReceivedPdf.ts
@@ -2,9 +2,16 @@ import { Inject, Service } from 'typedi';
import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy';
import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable';
import { GetPaymentReceived } from './GetPaymentReceived';
+import HasTenancyService from '@/services/Tenancy/TenancyService';
+import { PaymentReceivedBrandingTemplate } from './PaymentReceivedBrandingTemplate';
+import { transformPaymentReceivedToPdfTemplate } from './utils';
+import { PaymentReceivedPdfTemplateAttributes } from '@/interfaces';
@Service()
export default class GetPaymentReceivedPdf {
+ @Inject()
+ private tenancy: HasTenancyService;
+
@Inject()
private chromiumlyTenancy: ChromiumlyTenancy;
@@ -14,6 +21,9 @@ export default class GetPaymentReceivedPdf {
@Inject()
private getPaymentService: GetPaymentReceived;
+ @Inject()
+ private paymentBrandingTemplateService: PaymentReceivedBrandingTemplate;
+
/**
* Retrieve sale invoice pdf content.
* @param {number} tenantId -
@@ -24,19 +34,52 @@ export default class GetPaymentReceivedPdf {
tenantId: number,
paymentReceiveId: number
): Promise {
- const paymentReceive = await this.getPaymentService.getPaymentReceive(
+ const brandingAttributes = await this.getPaymentBrandingAttributes(
tenantId,
paymentReceiveId
);
const htmlContent = await this.templateInjectable.render(
tenantId,
'modules/payment-receive-standard',
- {
- paymentReceive,
- }
+ 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 given payment received branding attributes.
+ * @param {number} tenantId
+ * @param {number} paymentReceivedId
+ * @returns {Promise}
+ */
+ async getPaymentBrandingAttributes(
+ tenantId: number,
+ paymentReceivedId: number
+ ): Promise {
+ const { PdfTemplate } = this.tenancy.models(tenantId);
+
+ const paymentReceived = await this.getPaymentService.getPaymentReceive(
+ tenantId,
+ paymentReceivedId
+ );
+ const templateId =
+ paymentReceived?.pdfTemplateId ??
+ (
+ await PdfTemplate.query().findOne({
+ resource: 'PaymentReceive',
+ default: true,
+ })
+ )?.id;
+
+ const brandingTemplate =
+ await this.paymentBrandingTemplateService.getPaymentReceivedPdfTemplate(
+ tenantId,
+ templateId
+ );
+ return {
+ ...brandingTemplate.attributes,
+ ...transformPaymentReceivedToPdfTemplate(paymentReceived),
+ };
}
}
diff --git a/packages/server/src/services/Sales/PaymentReceived/PaymentReceivedBrandingTemplate.ts b/packages/server/src/services/Sales/PaymentReceived/PaymentReceivedBrandingTemplate.ts
new file mode 100644
index 000000000..20e01d7e6
--- /dev/null
+++ b/packages/server/src/services/Sales/PaymentReceived/PaymentReceivedBrandingTemplate.ts
@@ -0,0 +1,35 @@
+import { GetPdfTemplate } from '@/services/PdfTemplate/GetPdfTemplate';
+import { Inject, Service } from 'typedi';
+import { mergePdfTemplateWithDefaultAttributes } from '../Invoices/utils';
+import { defaultPaymentReceivedPdfTemplateAttributes } from './constants';
+import { PdfTemplate } from '@/models/PdfTemplate';
+
+@Service()
+export class PaymentReceivedBrandingTemplate {
+ @Inject()
+ private getPdfTemplateService: GetPdfTemplate;
+
+ /**
+ * Retrieves the payment received pdf template.
+ * @param {number} tenantId
+ * @param {number} paymentTemplateId
+ * @returns
+ */
+ public async getPaymentReceivedPdfTemplate(
+ tenantId: number,
+ paymentTemplateId: number
+ ): Promise {
+ const template = await this.getPdfTemplateService.getPdfTemplate(
+ tenantId,
+ paymentTemplateId
+ );
+ const attributes = mergePdfTemplateWithDefaultAttributes(
+ template.attributes,
+ defaultPaymentReceivedPdfTemplateAttributes
+ );
+ return {
+ ...template,
+ attributes,
+ };
+ }
+}
diff --git a/packages/server/src/services/Sales/PaymentReceived/PaymentReceivedDTOTransformer.ts b/packages/server/src/services/Sales/PaymentReceived/PaymentReceivedDTOTransformer.ts
index 24f0b0737..047607df4 100644
--- a/packages/server/src/services/Sales/PaymentReceived/PaymentReceivedDTOTransformer.ts
+++ b/packages/server/src/services/Sales/PaymentReceived/PaymentReceivedDTOTransformer.ts
@@ -1,6 +1,7 @@
import * as R from 'ramda';
import { Inject, Service } from 'typedi';
import { omit, sumBy } from 'lodash';
+import composeAsync from 'async/compose';
import {
ICustomer,
IPaymentReceived,
@@ -12,6 +13,7 @@ import { PaymentReceivedIncrement } from './PaymentReceivedIncrement';
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
import { formatDateFields } from '@/utils';
import { assocItemEntriesDefaultIndex } from '@/services/Items/utils';
+import { BrandingTemplateDTOTransformer } from '@/services/PdfTemplate/BrandingTemplateDTOTransformer';
@Service()
export class PaymentReceiveDTOTransformer {
@@ -24,6 +26,9 @@ export class PaymentReceiveDTOTransformer {
@Inject()
private branchDTOTransform: BranchTransactionDTOTransform;
+ @Inject()
+ private brandingTemplatesTransformer: BrandingTemplateDTOTransformer;
+
/**
* Transformes the create payment receive DTO to model object.
* @param {number} tenantId
@@ -68,8 +73,16 @@ export class PaymentReceiveDTOTransformer {
exchangeRate: paymentReceiveDTO.exchangeRate || 1,
entries,
};
+ const initialAsyncDTO = await composeAsync(
+ // Assigns the default branding template id to the invoice DTO.
+ this.brandingTemplatesTransformer.assocDefaultBrandingTemplate(
+ tenantId,
+ 'SaleInvoice'
+ )
+ )(initialDTO);
+
return R.compose(
this.branchDTOTransform.transformDTO(tenantId)
- )(initialDTO);
+ )(initialAsyncDTO);
}
}
diff --git a/packages/server/src/services/Sales/PaymentReceived/constants.ts b/packages/server/src/services/Sales/PaymentReceived/constants.ts
index 0f48e39fe..3d9b6af88 100644
--- a/packages/server/src/services/Sales/PaymentReceived/constants.ts
+++ b/packages/server/src/services/Sales/PaymentReceived/constants.ts
@@ -45,3 +45,53 @@ export const PaymentsReceiveSampleData = [
'Payment Amount': 850,
},
];
+
+export const defaultPaymentReceivedPdfTemplateAttributes = {
+ primaryColor: '#000',
+ secondaryColor: '#000',
+ showCompanyLogo: true,
+ companyLogo: '',
+ companyName: 'Bigcapital Technology, Inc.',
+
+ billedToAddress: [
+ 'Bigcapital Technology, Inc.',
+ '131 Continental Dr Suite 305 Newark,',
+ 'Delaware 19713',
+ 'United States',
+ '+1 762-339-5634',
+ 'ahmed@bigcapital.app',
+ ],
+ billedFromAddress: [
+ '131 Continental Dr Suite 305 Newark,',
+ 'Delaware 19713',
+ 'United States',
+ '+1 762-339-5634',
+ 'ahmed@bigcapital.app',
+ ],
+ showBilledFromAddress: true,
+ showBillingToAddress: true,
+ billedToLabel: 'Billed To',
+
+ total: '$1000.00',
+ totalLabel: 'Total',
+ showTotal: true,
+
+ subtotal: '1000/00',
+ subtotalLabel: 'Subtotal',
+ showSubtotal: true,
+
+ lines: [
+ {
+ invoiceNumber: 'INV-00001',
+ invoiceAmount: '$1000.00',
+ paidAmount: '$1000.00',
+ },
+ ],
+ showPaymentReceivedNumber: true,
+ paymentReceivedNumberLabel: 'Payment Number',
+ paymentReceivedNumebr: '346D3D40-0001',
+
+ paymentReceivedDate: 'September 3, 2024',
+ showPaymentReceivedDate: true,
+ paymentReceivedDateLabel: 'Payment Date',
+};
diff --git a/packages/server/src/services/Sales/PaymentReceived/utils.ts b/packages/server/src/services/Sales/PaymentReceived/utils.ts
new file mode 100644
index 000000000..beda5e0b9
--- /dev/null
+++ b/packages/server/src/services/Sales/PaymentReceived/utils.ts
@@ -0,0 +1,12 @@
+import {
+ IPaymentReceived,
+ PaymentReceivedPdfTemplateAttributes,
+} from '@/interfaces';
+
+export const transformPaymentReceivedToPdfTemplate = (
+ payment: IPaymentReceived
+): Partial => {
+ return {
+ // ...payment
+ };
+};
diff --git a/packages/server/src/services/Sales/Receipts/SaleReceiptBrandingTemplate.ts b/packages/server/src/services/Sales/Receipts/SaleReceiptBrandingTemplate.ts
new file mode 100644
index 000000000..5d7794421
--- /dev/null
+++ b/packages/server/src/services/Sales/Receipts/SaleReceiptBrandingTemplate.ts
@@ -0,0 +1,35 @@
+import { GetPdfTemplate } from '@/services/PdfTemplate/GetPdfTemplate';
+import { Inject, Service } from 'typedi';
+import { defaultSaleReceiptBrandingAttributes } from './constants';
+import { mergePdfTemplateWithDefaultAttributes } from '../Invoices/utils';
+
+@Service()
+export class SaleReceiptBrandingTemplate {
+ @Inject()
+ private getPdfTemplateService: GetPdfTemplate;
+
+
+ /**
+ * Retrieves the sale receipt branding template.
+ * @param {number} tenantId - The ID of the tenant.
+ * @param {number} templateId - The ID of the PDF template.
+ * @returns {Promise} The sale receipt branding template with merged attributes.
+ */
+ public async getSaleReceiptBrandingTemplate(
+ tenantId: number,
+ templateId: number
+ ) {
+ const template = await this.getPdfTemplateService.getPdfTemplate(
+ tenantId,
+ templateId
+ );
+ const attributes = mergePdfTemplateWithDefaultAttributes(
+ template.attributes,
+ defaultSaleReceiptBrandingAttributes
+ );
+ return {
+ ...template,
+ attributes,
+ };
+ }
+}
diff --git a/packages/server/src/services/Sales/Receipts/SaleReceiptDTOTransformer.ts b/packages/server/src/services/Sales/Receipts/SaleReceiptDTOTransformer.ts
index ae492099a..78d14b3b8 100644
--- a/packages/server/src/services/Sales/Receipts/SaleReceiptDTOTransformer.ts
+++ b/packages/server/src/services/Sales/Receipts/SaleReceiptDTOTransformer.ts
@@ -12,6 +12,7 @@ import { formatDateFields } from '@/utils';
import { SaleReceiptIncrement } from './SaleReceiptIncrement';
import { ItemEntry } from '@/models';
import { assocItemEntriesDefaultIndex } from '@/services/Items/utils';
+import { BrandingTemplateDTOTransformer } from '@/services/PdfTemplate/BrandingTemplateDTOTransformer';
@Service()
export class SaleReceiptDTOTransformer {
@@ -30,6 +31,9 @@ export class SaleReceiptDTOTransformer {
@Inject()
private receiptIncrement: SaleReceiptIncrement;
+ @Inject()
+ private brandingTemplatesTransformer: BrandingTemplateDTOTransformer;
+
/**
* Transform create DTO object to model object.
* @param {ISaleReceiptDTO} saleReceiptDTO -
@@ -88,9 +92,17 @@ export class SaleReceiptDTOTransformer {
}),
entries,
};
+ const initialAsyncDTO = await composeAsync(
+ // Assigns the default branding template id to the invoice DTO.
+ this.brandingTemplatesTransformer.assocDefaultBrandingTemplate(
+ tenantId,
+ 'SaleReceipt'
+ )
+ )(initialDTO);
+
return R.compose(
this.branchDTOTransform.transformDTO(tenantId),
this.warehouseDTOTransform.transformDTO(tenantId)
- )(initialDTO);
+ )(initialAsyncDTO);
}
}
diff --git a/packages/server/src/services/Sales/Receipts/SaleReceiptsPdfService.ts b/packages/server/src/services/Sales/Receipts/SaleReceiptsPdfService.ts
index cad2b5f93..c72c9b0c0 100644
--- a/packages/server/src/services/Sales/Receipts/SaleReceiptsPdfService.ts
+++ b/packages/server/src/services/Sales/Receipts/SaleReceiptsPdfService.ts
@@ -2,9 +2,15 @@ import { Inject, Service } from 'typedi';
import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable';
import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy';
import { GetSaleReceipt } from './GetSaleReceipt';
+import HasTenancyService from '@/services/Tenancy/TenancyService';
+import { SaleReceiptBrandingTemplate } from './SaleReceiptBrandingTemplate';
+import { transformReceiptToBrandingTemplateAttributes } from './utils';
@Service()
export class SaleReceiptsPdf {
+ @Inject()
+ private tenancy: HasTenancyService;
+
@Inject()
private chromiumlyTenancy: ChromiumlyTenancy;
@@ -14,26 +20,64 @@ export class SaleReceiptsPdf {
@Inject()
private getSaleReceiptService: GetSaleReceipt;
+ @Inject()
+ private saleReceiptBrandingTemplate: SaleReceiptBrandingTemplate;
+
/**
* Retrieves sale invoice pdf content.
- * @param {number} tenantId -
+ * @param {number} tenantId -
* @param {number} saleInvoiceId -
* @returns {Promise}
*/
public async saleReceiptPdf(tenantId: number, saleReceiptId: number) {
- const saleReceipt = await this.getSaleReceiptService.getSaleReceipt(
+ const brandingAttributes = await this.getReceiptBrandingAttributes(
tenantId,
saleReceiptId
);
+ console.log(brandingAttributes, 'attributes');
+
const htmlContent = await this.templateInjectable.render(
tenantId,
'modules/receipt-regular',
- {
- saleReceipt,
- }
+ brandingAttributes
);
- return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent, {
- margins: { top: 0, bottom: 0, left: 0, right: 0 },
- });
+ return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent);
+ }
+
+ /**
+ * Retrieves receipt branding attributes.
+ * @param {number} tenantId
+ * @param {number] receiptId
+ * @returns
+ */
+ public async getReceiptBrandingAttributes(
+ tenantId: number,
+ receiptId: number
+ ) {
+ const { PdfTemplate } = this.tenancy.models(tenantId);
+
+ const saleReceipt = await this.getSaleReceiptService.getSaleReceipt(
+ tenantId,
+ receiptId
+ );
+ // Retrieve the invoice template id of not found get the default template id.
+ const templateId =
+ saleReceipt.pdfTemplateId ??
+ (
+ await PdfTemplate.query().findOne({
+ resource: 'SaleReceipt',
+ default: true,
+ })
+ )?.id;
+ // Retrieves the receipt branding template.
+ const brandingTemplate =
+ await this.saleReceiptBrandingTemplate.getSaleReceiptBrandingTemplate(
+ tenantId,
+ templateId
+ );
+ return {
+ ...brandingTemplate.attributes,
+ ...transformReceiptToBrandingTemplateAttributes(saleReceipt),
+ };
}
}
diff --git a/packages/server/src/services/Sales/Receipts/constants.ts b/packages/server/src/services/Sales/Receipts/constants.ts
index 06df9a615..491369c74 100644
--- a/packages/server/src/services/Sales/Receipts/constants.ts
+++ b/packages/server/src/services/Sales/Receipts/constants.ts
@@ -22,7 +22,7 @@ export const ERRORS = {
SALE_RECEIPT_IS_ALREADY_CLOSED: 'SALE_RECEIPT_IS_ALREADY_CLOSED',
SALE_RECEIPT_NO_IS_REQUIRED: 'SALE_RECEIPT_NO_IS_REQUIRED',
CUSTOMER_HAS_SALES_INVOICES: 'CUSTOMER_HAS_SALES_INVOICES',
- NO_INVOICE_CUSTOMER_EMAIL_ADDR: 'NO_INVOICE_CUSTOMER_EMAIL_ADDR'
+ NO_INVOICE_CUSTOMER_EMAIL_ADDR: 'NO_INVOICE_CUSTOMER_EMAIL_ADDR',
};
export const DEFAULT_VIEW_COLUMNS = [];
@@ -47,22 +47,84 @@ export const DEFAULT_VIEWS = [
},
];
-
export const SaleReceiptsSampleData = [
{
- "Receipt Date": "2023-01-01",
- "Customer": "Randall Kohler",
- "Deposit Account": "Petty Cash",
- "Exchange Rate": "",
- "Receipt Number": "REC-00001",
- "Reference No.": "REF-0001",
- "Statement": "Delectus unde aut soluta et accusamus placeat.",
- "Receipt Message": "Vitae asperiores dicta.",
- "Closed": "T",
- "Item": "Schmitt Group",
- "Quantity": 100,
- "Rate": 200,
- "Line Description": "Distinctio distinctio sit veritatis consequatur iste quod veritatis."
- }
-
-]
\ No newline at end of file
+ 'Receipt Date': '2023-01-01',
+ Customer: 'Randall Kohler',
+ 'Deposit Account': 'Petty Cash',
+ 'Exchange Rate': '',
+ 'Receipt Number': 'REC-00001',
+ 'Reference No.': 'REF-0001',
+ Statement: 'Delectus unde aut soluta et accusamus placeat.',
+ 'Receipt Message': 'Vitae asperiores dicta.',
+ Closed: 'T',
+ Item: 'Schmitt Group',
+ Quantity: 100,
+ Rate: 200,
+ 'Line Description':
+ 'Distinctio distinctio sit veritatis consequatur iste quod veritatis.',
+ },
+];
+
+export const defaultSaleReceiptBrandingAttributes = {
+ primaryColor: '',
+ secondaryColor: '',
+ showCompanyLogo: true,
+ companyLogo: '',
+ companyName: 'Bigcapital Technology, Inc.',
+
+ // # Address
+ billedToAddress: [
+ 'Bigcapital Technology, Inc.',
+ '131 Continental Dr Suite 305 Newark,',
+ 'Delaware 19713',
+ 'United States',
+ '+1 762-339-5634',
+ 'ahmed@bigcapital.app',
+ ],
+ billedFromAddress: [
+ '131 Continental Dr Suite 305 Newark,',
+ 'Delaware 19713',
+ 'United States',
+ '+1 762-339-5634',
+ 'ahmed@bigcapital.app',
+ ],
+ showBilledFromAddress: true,
+ showBilledToAddress: true,
+ billedToLabel: 'Billed To',
+
+ total: '$1000.00',
+ totalLabel: 'Total',
+ showTotal: true,
+
+ subtotal: '1000/00',
+ subtotalLabel: 'Subtotal',
+ showSubtotal: true,
+
+ 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',
+
+ 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',
+
+ lines: [
+ {
+ item: 'Simply dummy text',
+ description: 'Simply dummy text of the printing and typesetting',
+ rate: '1',
+ quantity: '1000',
+ total: '$1000.00',
+ },
+ ],
+ showReceiptNumber: true,
+ receiptNumberLabel: 'Receipt Number',
+ receiptNumebr: '346D3D40-0001',
+
+ receiptDate: 'September 3, 2024',
+ showReceiptDate: true,
+ receiptDateLabel: 'Receipt Date',
+};
diff --git a/packages/server/src/services/Sales/Receipts/utils.ts b/packages/server/src/services/Sales/Receipts/utils.ts
new file mode 100644
index 000000000..548ad7001
--- /dev/null
+++ b/packages/server/src/services/Sales/Receipts/utils.ts
@@ -0,0 +1,6 @@
+
+
+
+export const transformReceiptToBrandingTemplateAttributes = () => {
+ return {};
+}
\ No newline at end of file
diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFloatingActions.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFloatingActions.tsx
index bfdc6c57f..85bd40d93 100644
--- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFloatingActions.tsx
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFloatingActions.tsx
@@ -12,10 +12,15 @@ import {
Menu,
MenuItem,
} from '@blueprintjs/core';
-import { If, Icon, FormattedMessage as T, Group } from '@/components';
+import { If, Icon, FormattedMessage as T, Group, FSelect } from '@/components';
import { CLASSES } from '@/constants/classes';
import classNames from 'classnames';
import { useCreditNoteFormContext } from './CreditNoteFormProvider';
+import {
+ BrandingThemeFormGroup,
+ BrandingThemeSelectButton,
+} from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
+import { useCreditNoteFormBrandingTemplatesOptions } from './utils';
/**
* Credit note floating actions.
@@ -74,6 +79,8 @@ export default function CreditNoteFloatingActions() {
resetForm();
};
+ const brandingTemplatesOptions = useCreditNoteFormBrandingTemplatesOptions();
+
return (
}
/>
+
+ {/* ----------- Branding Template Select ----------- */}
+
+ (
+
+ )}
+ filterable={false}
+ popoverProps={{ minimal: true }}
+ />
+
);
}
diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormProvider.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormProvider.tsx
index 3e4d95261..19c02eb61 100644
--- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormProvider.tsx
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormProvider.tsx
@@ -18,6 +18,7 @@ import {
useSettingsCreditNotes,
useInvoice,
} from '@/hooks/query';
+import { useGetPdfTemplates } from '@/hooks/query/pdf-templates';
const CreditNoteFormContext = React.createContext();
@@ -73,6 +74,10 @@ function CreditNoteFormProvider({ creditNoteId, ...props }) {
isSuccess: isBranchesSuccess,
} = useBranches({}, { enabled: isBranchFeatureCan });
+ // Fetches branding templates of invoice.
+ const { data: brandingTemplates, isLoading: isBrandingTemplatesLoading } =
+ useGetPdfTemplates({ resource: 'PaymentReceive' });
+
// Handle fetching settings.
useSettingsCreditNotes();
@@ -115,13 +120,18 @@ function CreditNoteFormProvider({ creditNoteId, ...props }) {
createCreditNoteMutate,
editCreditNoteMutate,
setSubmitPayload,
+
+ // Branding templates.
+ brandingTemplates,
+ isBrandingTemplatesLoading,
};
const isLoading =
isItemsLoading ||
isCustomersLoading ||
isCreditNoteLoading ||
- isInvoiceLoading;
+ isInvoiceLoading ||
+ isBrandingTemplatesLoading;
return (
diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/utils.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/utils.tsx
index 52af20853..fad59288f 100644
--- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/utils.tsx
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/utils.tsx
@@ -24,6 +24,7 @@ import {
transformAttachmentsToForm,
transformAttachmentsToRequest,
} from '@/containers/Attachments/utils';
+import { convertBrandingTemplatesToOptions } from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
export const MIN_LINES_NUMBER = 1;
@@ -54,7 +55,8 @@ export const defaultCreditNote = {
exchange_rate: 1,
currency_code: '',
entries: [...repeatValue(defaultCreditNoteEntry, MIN_LINES_NUMBER)],
- attachments: []
+ attachments: [],
+ pdf_template_id: '',
};
/**
@@ -214,3 +216,13 @@ export const useCreditNoteIsForeignCustomer = () => {
);
return isForeignCustomer;
};
+
+
+export const useCreditNoteFormBrandingTemplatesOptions = () => {
+ const { brandingTemplates } = useCreditNoteFormContext();
+
+ return React.useMemo(
+ () => convertBrandingTemplatesToOptions(brandingTemplates),
+ [brandingTemplates],
+ );
+};
diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFloatingActions.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFloatingActions.tsx
index 906d703d9..31e7cf8dc 100644
--- a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFloatingActions.tsx
+++ b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFloatingActions.tsx
@@ -11,11 +11,16 @@ import {
Menu,
MenuItem,
} from '@blueprintjs/core';
-import { If, Icon, FormattedMessage as T, Group } from '@/components';
+import { If, Icon, FormattedMessage as T, Group, FSelect } from '@/components';
import { CLASSES } from '@/constants/classes';
import { useHistory } from 'react-router-dom';
import { useFormikContext } from 'formik';
import { useEstimateFormContext } from './EstimateFormProvider';
+import { useEstimateFormBrandingTemplatesOptions } from './utils';
+import {
+ BrandingThemeFormGroup,
+ BrandingThemeSelectButton,
+} from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
/**
* Estimate floating actions bar.
@@ -73,6 +78,8 @@ export default function EstimateFloatingActions() {
resetForm();
};
+ const brandingTemplatesOptions = useEstimateFormBrandingTemplatesOptions();
+
return (
}
/>
+
+ {/* ----------- Branding Template Select ----------- */}
+
+ (
+
+ )}
+ filterable={false}
+ popoverProps={{ minimal: true }}
+ />
+
);
}
diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormProvider.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormProvider.tsx
index 87196dc24..6cba0125e 100644
--- a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormProvider.tsx
+++ b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateFormProvider.tsx
@@ -12,8 +12,9 @@ import {
useCreateEstimate,
useEditEstimate,
} from '@/hooks/query';
-import { Features } from '@/constants';
import { useProjects } from '@/containers/Projects/hooks';
+import { useGetPdfTemplates } from '@/hooks/query/pdf-templates';
+import { Features } from '@/constants';
import { useFeatureCan } from '@/hooks/state';
import { ITEMS_FILTER_ROLES } from './utils';
@@ -71,6 +72,10 @@ function EstimateFormProvider({ query, estimateId, ...props }) {
isLoading: isProjectsLoading,
} = useProjects({}, { enabled: !!isProjectsFeatureCan });
+ // Fetches branding templates of invoice.
+ const { data: brandingTemplates, isLoading: isBrandingTemplatesLoading } =
+ useGetPdfTemplates({ resource: 'SaleEstimate' });
+
// Handle fetch settings.
useSettingsEstimates();
@@ -112,13 +117,19 @@ function EstimateFormProvider({ query, estimateId, ...props }) {
createEstimateMutate,
editEstimateMutate,
+
+ brandingTemplates,
+ isBrandingTemplatesLoading,
};
+ const isLoading =
+ isCustomersLoading ||
+ isItemsLoading ||
+ isEstimateLoading ||
+ isBrandingTemplatesLoading;
+
return (
-
+
);
diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/utils.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/utils.tsx
index 48088a524..355c7619e 100644
--- a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/utils.tsx
+++ b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/utils.tsx
@@ -22,6 +22,7 @@ import {
transformAttachmentsToForm,
transformAttachmentsToRequest,
} from '@/containers/Attachments/utils';
+import { convertBrandingTemplatesToOptions } from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
export const MIN_LINES_NUMBER = 1;
@@ -60,7 +61,8 @@ export const defaultEstimate = {
exchange_rate: 1,
currency_code: '',
entries: [...repeatValue(defaultEstimateEntry, MIN_LINES_NUMBER)],
- attachments: []
+ attachments: [],
+ pdf_template_id: '',
};
const ERRORS = {
@@ -262,3 +264,12 @@ export const resetFormState = ({ initialValues, values, resetForm }) => {
},
});
};
+
+export const useEstimateFormBrandingTemplatesOptions = () => {
+ const { brandingTemplates } = useEstimateFormContext();
+
+ return React.useMemo(
+ () => convertBrandingTemplatesToOptions(brandingTemplates),
+ [brandingTemplates],
+ );
+};
diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.tsx
index 3f6d874dc..e403822b4 100644
--- a/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.tsx
+++ b/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.tsx
@@ -25,6 +25,7 @@ import {
DashboardFilterButton,
DashboardRowsHeightButton,
DashboardActionsBar,
+ FSelect,
} from '@/components';
import withEstimates from './withEstimates';
@@ -42,6 +43,10 @@ import { compose } from '@/utils';
import { DialogsName } from '@/constants/dialogs';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { DRAWERS } from '@/constants/drawers';
+import {
+ BrandingThemeFormGroup,
+ BrandingThemeSelectButton,
+} from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
/**
* Estimates list actions bar.
diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceiveForm/PaymentReceiveFloatingActions.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceiveForm/PaymentReceiveFloatingActions.tsx
index 351b88a88..53f4e3b4b 100644
--- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceiveForm/PaymentReceiveFloatingActions.tsx
+++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceiveForm/PaymentReceiveFloatingActions.tsx
@@ -12,10 +12,15 @@ import {
MenuItem,
} from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
-import { Group, Icon, FormattedMessage as T } from '@/components';
+import { FSelect, Group, Icon, FormattedMessage as T } from '@/components';
import { useFormikContext } from 'formik';
import { usePaymentReceiveFormContext } from './PaymentReceiveFormProvider';
import { CLASSES } from '@/constants/classes';
+import {
+ BrandingThemeFormGroup,
+ BrandingThemeSelectButton,
+} from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
+import { usePaymentReceivedFormBrandingTemplatesOptions } from './utils';
/**
* Payment receive floating actions bar.
@@ -53,6 +58,9 @@ export default function PaymentReceiveFormFloatingActions() {
submitForm();
};
+ const brandingTemplatesOpts =
+ usePaymentReceivedFormBrandingTemplatesOptions();
+
return (
}
/>
+
+ {/* ----------- Branding Template Select ----------- */}
+
+ (
+
+ )}
+ filterable={false}
+ popoverProps={{ minimal: true }}
+ />
+
);
}
diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceiveForm/PaymentReceiveFormProvider.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceiveForm/PaymentReceiveFormProvider.tsx
index c6f5a4729..d98e26b4d 100644
--- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceiveForm/PaymentReceiveFormProvider.tsx
+++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceiveForm/PaymentReceiveFormProvider.tsx
@@ -13,6 +13,7 @@ import {
useCreatePaymentReceive,
useEditPaymentReceive,
} from '@/hooks/query';
+import { useGetPdfTemplates } from '@/hooks/query/pdf-templates';
// Payment receive form context.
const PaymentReceiveFormContext = createContext();
@@ -65,6 +66,10 @@ function PaymentReceiveFormProvider({ query, paymentReceiveId, ...props }) {
isLoading: isProjectsLoading,
} = useProjects({}, { enabled: !!isProjectsFeatureCan });
+ // Fetches branding templates of payment received module.
+ const { data: brandingTemplates, isLoading: isBrandingTemplatesLoading } =
+ useGetPdfTemplates({ resource: 'PaymentReceive' });
+
// Detarmines whether the new mode.
const isNewMode = !paymentReceiveId;
@@ -102,13 +107,20 @@ function PaymentReceiveFormProvider({ query, paymentReceiveId, ...props }) {
isExcessConfirmed,
setIsExcessConfirmed,
+
+ // Branding templates
+ brandingTemplates,
+ isBrandingTemplatesLoading,
};
+ const isLoading =
+ isPaymentLoading ||
+ isAccountsLoading ||
+ isCustomersLoading ||
+ isBrandingTemplatesLoading;
+
return (
-
+
);
diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceiveForm/utils.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceiveForm/utils.tsx
index bbd416248..4464b8ea9 100644
--- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceiveForm/utils.tsx
+++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceiveForm/utils.tsx
@@ -19,6 +19,7 @@ import {
transformAttachmentsToForm,
transformAttachmentsToRequest,
} from '@/containers/Attachments/utils';
+import { convertBrandingTemplatesToOptions } from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
// Default payment receive entry.
export const defaultPaymentReceiveEntry = {
@@ -44,10 +45,11 @@ export const defaultPaymentReceive = {
statement: '',
amount: '',
currency_code: '',
- branch_id: '',
exchange_rate: 1,
entries: [],
attachments: [],
+ branch_id: '',
+ pdf_template_id: '',
};
export const defaultRequestPaymentEntry = {
@@ -303,3 +305,12 @@ export const getExceededAmountFromValues = (values) => {
return totalAmount - totalApplied;
};
+
+export const usePaymentReceivedFormBrandingTemplatesOptions = () => {
+ const { brandingTemplates } = usePaymentReceiveFormContext();
+
+ return React.useMemo(
+ () => convertBrandingTemplatesToOptions(brandingTemplates),
+ [brandingTemplates],
+ );
+};
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFloatingActions.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFloatingActions.tsx
index aa156618d..8e198ea27 100644
--- a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFloatingActions.tsx
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormFloatingActions.tsx
@@ -11,12 +11,17 @@ import {
Menu,
MenuItem,
} from '@blueprintjs/core';
-import { Group, FormattedMessage as T } from '@/components';
+import { FSelect, Group, FormattedMessage as T } from '@/components';
import { useFormikContext } from 'formik';
import { useHistory } from 'react-router-dom';
import { CLASSES } from '@/constants/classes';
import { If, Icon } from '@/components';
import { useReceiptFormContext } from './ReceiptFormProvider';
+import { useReceiptFormBrandingTemplatesOptions } from './utils';
+import {
+ BrandingThemeFormGroup,
+ BrandingThemeSelectButton,
+} from '@/containers/BrandingTemplates/BrandingTemplatesSelectFields';
/**
* Receipt floating actions bar.
@@ -76,6 +81,8 @@ export default function ReceiptFormFloatingActions() {
resetForm();
};
+ const brandingTemplatesOptions = useReceiptFormBrandingTemplatesOptions();
+
return (
}
/>
+
+ {/* ----------- Branding Template Select ----------- */}
+
+ (
+
+ )}
+ filterable={false}
+ popoverProps={{ minimal: true }}
+ />
+
);
}
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormProvider.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormProvider.tsx
index 23f432115..d51877c01 100644
--- a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormProvider.tsx
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormProvider.tsx
@@ -15,6 +15,7 @@ import {
useEditReceipt,
} from '@/hooks/query';
import { useProjects } from '@/containers/Projects/hooks';
+import { useGetPdfTemplates } from '@/hooks/query/pdf-templates';
const ReceiptFormContext = createContext();
@@ -77,7 +78,7 @@ function ReceiptFormProvider({ receiptId, ...props }) {
[],
);
- // Handle fetch Items data table or list
+ // Handle fetch Items data table or list.
const {
data: { items },
isLoading: isItemsLoading,
@@ -85,13 +86,16 @@ function ReceiptFormProvider({ receiptId, ...props }) {
page_size: 10000,
stringified_filter_roles: stringifiedFilterRoles,
});
-
// Fetch project list.
const {
data: { projects },
isLoading: isProjectsLoading,
} = useProjects({}, { enabled: !!isProjectsFeatureCan });
+ // Fetches branding templates of receipt.
+ const { data: brandingTemplates, isLoading: isBrandingTemplatesLoading } =
+ useGetPdfTemplates({ resource: 'SaleReceipt' });
+
// Fetch receipt settings.
const { isLoading: isSettingLoading } = useSettingsReceipts();
@@ -101,7 +105,6 @@ function ReceiptFormProvider({ receiptId, ...props }) {
const [submitPayload, setSubmitPayload] = useState({});
const isNewMode = !receiptId;
-
const isFeatureLoading = isWarehouesLoading || isBranchesLoading;
const provider = {
@@ -130,6 +133,10 @@ function ReceiptFormProvider({ receiptId, ...props }) {
createReceiptMutate,
editReceiptMutate,
setSubmitPayload,
+
+ // Branding templates
+ brandingTemplates,
+ isBrandingTemplatesLoading
};
return (
{
},
});
};
+
+export const useReceiptFormBrandingTemplatesOptions = () => {
+ const { brandingTemplates } = useReceiptFormContext();
+
+ return React.useMemo(
+ () => convertBrandingTemplatesToOptions(brandingTemplates),
+ [brandingTemplates],
+ );
+};
From bb0d91a9cb49a641392b5ed25a638e6534b7bc3f Mon Sep 17 00:00:00 2001
From: Ahmed Bouhuolia
Date: Tue, 17 Sep 2024 17:46:56 +0200
Subject: [PATCH 30/32] fix: pdf templates
---
packages/server/resources/scss/base.css | 6 +-
.../views/modules/credit-note-standard.pug | 45 +++++-------
.../views/modules/estimate-regular.pug | 19 +++--
.../views/modules/invoice-standard.pug | 4 +-
.../modules/payment-receive-standard.pug | 13 +---
.../views/modules/receipt-regular.pug | 50 +++++++------
packages/server/src/interfaces/CreditNote.ts | 2 +
.../server/src/interfaces/PaymentReceive.ts | 70 +++++++------------
packages/server/src/interfaces/SaleReceipt.ts | 54 ++++++++++++++
.../server/src/services/CreditNotes/utils.ts | 24 +++++--
.../ReceiptForm/ReceiptFormProvider.tsx | 21 +++---
11 files changed, 177 insertions(+), 131 deletions(-)
diff --git a/packages/server/resources/scss/base.css b/packages/server/resources/scss/base.css
index d7d148cab..9c11398a2 100644
--- a/packages/server/resources/scss/base.css
+++ b/packages/server/resources/scss/base.css
@@ -29,14 +29,12 @@ body{
line-height: 1.5;
color: #000;
background-color: #fff;
- direction: ltr;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: transparent;
+}
+body, h1, h2, h3, h4, h5, h6{
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/views/modules/credit-note-standard.pug b/packages/server/resources/views/modules/credit-note-standard.pug
index 16b1c8834..f580b8fc9 100644
--- a/packages/server/resources/views/modules/credit-note-standard.pug
+++ b/packages/server/resources/views/modules/credit-note-standard.pug
@@ -44,8 +44,7 @@ block head
.#{prefix}-terms-item__value {
/* Styles for the term value */
}
-
- .#{prefix}-group {
+ .#{prefix}-address-section{
box-sizing: border-box;
display: flex;
flex-flow: wrap;
@@ -56,12 +55,9 @@ block head
gap: 10px;
margin-bottom: 24px;
}
-
-
- .#{prefix}-address {
- /* Styles for each address block */
+ .#{prefix}-address-section > * {
+ flex: 1 1;
}
-
.#{prefix}-table {
width: 100%;
border-collapse: collapse;
@@ -124,15 +120,12 @@ block head
flex: 1 1 auto;
text-align: right;
}
-
.#{prefix}-statement {
- /* Styles for customer note/terms statement section */
+ margin-bottom: 20px;
}
-
.#{prefix}-statement__label {
- /* Styles for statement label */
+ color: #666;
}
-
.#{prefix}-statement__value {
/* Styles for statement value */
}
@@ -156,7 +149,7 @@ block content
div(class=`${prefix}-terms-item__label`) #{creditNoteDateLabel}:
div(class=`${prefix}-terms-item__value`) #{creditNoteDate}
- div(class=`${prefix}-group`)
+ div(class=`${prefix}-address-section`)
if showBilledFromAddress
div(class=`${prefix}-address`)
strong #{companyName}
@@ -169,30 +162,30 @@ block content
div #{address}
table(class=`${prefix}-table`)
- thead(class=`${prefix}-table__header`)
+ thead
tr
- th #{'Item'}
- th #{'Description'}
- th #{'Rate'}
- th #{'Total'}
+ th(class=`${prefix}-table__header`) #{'Item'}
+ th(class=`${prefix}-table__header`) #{'Description'}
+ th(class=`${prefix}-table__header`) #{'Rate'}
+ th(class=`${prefix}-table__header`) #{'Total'}
tbody
each line in lines
tr(class=`${prefix}-table__row`)
- td #{line.item}
- td #{line.description}
- td(class=`${prefix}-table__column--right`) #{line.rate}
- td(class=`${prefix}-table__column--right`) #{line.total}
+ td(class=`${prefix}-table__cell`) #{line.item}
+ td(class=`${prefix}-table__cell`) #{line.description}
+ td(class=`${prefix}-table__cell--right`) #{line.rate}
+ td(class=`${prefix}-table__cell--right`) #{line.total}
div(class=`${prefix}-totals`)
if showSubtotal
div(class=`${prefix}-totals__item ${prefix}-totals__item--border-gray`)
- div #{subtotalLabel}:
- div #{subtotal}
+ div(class=`${prefix}-totals__item-label`) #{subtotallabel}
+ div(class=`${prefix}-totals__item-amount`) #{subtotal}
if showTotal
div(class=`${prefix}-totals__item ${prefix}-totals__item--border-dark`)
- div #{totalLabel}:
- div #{total}
+ div(class=`${prefix}-totals__item-amount`) #{totalLabel}:
+ div(class=`${prefix}-totals__item-label`) #{total}
if showCustomerNote
div(class=`${prefix}-statement`)
diff --git a/packages/server/resources/views/modules/estimate-regular.pug b/packages/server/resources/views/modules/estimate-regular.pug
index 393cbceaf..a908cb6f1 100644
--- a/packages/server/resources/views/modules/estimate-regular.pug
+++ b/packages/server/resources/views/modules/estimate-regular.pug
@@ -26,7 +26,7 @@ block head
top: 26px;
overflow: hidden;
}
- .#{prefix}-terms-list {
+ .#{prefix}-terms {
display: flex;
flex-direction: column;
gap: 4px;
@@ -43,7 +43,7 @@ block head
}
.#{prefix}-terms-item__value {
}
- .#{prefix}-address-section{
+ .#{prefix}-addresses{
box-sizing: border-box;
display: flex;
flex-flow: wrap;
@@ -54,6 +54,9 @@ block head
gap: 10px;
margin-bottom: 24px;
}
+ .#{prefix}-addresses > * {
+ flex: 1 1;
+ }
.#{prefix}-address {
}
.#{prefix}-address__item {
@@ -120,8 +123,10 @@ block head
text-align: right;
}
.#{prefix}-statement {
+ margin-bottom: 20px;
}
.#{prefix}-statement__label {
+ color: #666;
}
.#{prefix}-statement__value {
}
@@ -150,7 +155,7 @@ block content
div(class=`${prefix}-terms-item__value`) #{expirationDate}
//- Addresses (Group section)
- div(class=`${prefix}-address-section`)
+ div(class=`${prefix}-addresses`)
if showBilledFromAddress
div(class=`${prefix}-address`)
strong #{companyName}
@@ -182,21 +187,21 @@ block content
//- Totals section
div(class=`${prefix}-totals`)
if showSubtotal
- div(class=`${prefix}-totals__item`)
+ 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 showTotal
- div(class=`${prefix}-totals__item`)
+ 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}
//- Statements section
- if showCustomerNote
+ if showCustomerNote && customerNote
div(class=`${prefix}-statement`)
div(class=`${prefix}-statement__label`) #{customerNoteLabel}
div(class=`${prefix}-statement__value`) #{customerNote}
- if showTermsConditions
+ if showTermsConditions && termsConditions
div(class=`${prefix}-statement`)
div(class=`${prefix}-statement__label`) #{termsConditionsLabel}
div(class=`${prefix}-statement__value`) #{termsConditions}
\ 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
index 006e33569..ba9339637 100644
--- a/packages/server/resources/views/modules/invoice-standard.pug
+++ b/packages/server/resources/views/modules/invoice-standard.pug
@@ -229,13 +229,13 @@ block content
div(class=`${prefix}-totals__item-amount`) #{balanceDue}
//- Footer section
- if showTermsConditions
+ if showTermsConditions && termsConditions
div(class=`${prefix}-paragraph`)
if termsConditionsLabel
div(class=`${prefix}-paragraph__label`) #{termsConditionsLabel}
div(class=`${prefix}-paragraph__value`) #{termsConditions}
- if showStatement
+ if showStatement && statement
div(class=`${prefix}-paragraph`)
if statementLabel
div(class=`${prefix}-paragraph__label`) #{statementLabel}
diff --git a/packages/server/resources/views/modules/payment-receive-standard.pug b/packages/server/resources/views/modules/payment-receive-standard.pug
index 2c6e417cd..36ba86ed2 100644
--- a/packages/server/resources/views/modules/payment-receive-standard.pug
+++ b/packages/server/resources/views/modules/payment-receive-standard.pug
@@ -41,9 +41,6 @@ block head
.#{prefix}-terms-item__label{
min-width: 120px;
color: #333;
- }
- .#{prefix}-group{
-
}
.#{prefix}-addresses{
box-sizing: border-box;
@@ -95,12 +92,6 @@ block head
}
.#{prefix}-table__cell--right {
text-align: right;
- }
- .#{prefix}-table__column{
-
- }
- .#{prefix}-table__column--right{
-
}
.#{prefix}-totals {
display: flex;
@@ -177,11 +168,11 @@ block content
div(class=`${prefix}-totals`)
if showSubtotal
- div(class=`${prefix}-totals__item`)
+ div(class=`${prefix}-totals__item ${prefix}-totals__item--gray-border`)
div(class=`${prefix}-totals__item-label`) #{subtotalLabel}
div(class=`${prefix}-totals__item-amount`) #{subtotal}
if showTotal
- div(class=`${prefix}-totals__item`)
+ div(class=`${prefix}-totals__item ${prefix}-totals__item--dark-border`)
div(class=`${prefix}-totals__item-label`) #{totalLabel}
div(class=`${prefix}-totals__item-amount`) #{total}
diff --git a/packages/server/resources/views/modules/receipt-regular.pug b/packages/server/resources/views/modules/receipt-regular.pug
index f9ccd1a72..2aa504031 100644
--- a/packages/server/resources/views/modules/receipt-regular.pug
+++ b/packages/server/resources/views/modules/receipt-regular.pug
@@ -4,21 +4,13 @@ block head
- var prefix = 'bc'
style.
.#{prefix}-root {
- color: #111;
+ color: #000;
padding: 24px 30px;
font-size: 12px;
position: relative;
box-shadow: inset 0 4px 0px 0 var(--invoice-primary-color);
}
.#{prefix}-logo-wrap {
- font-size: 60px;
- margin: 0;
- line-height: 1;
- margin-bottom: 25px;
- font-weight: 500;
- color: #333;
- }
- .#{prefix}-big-title {
height: 120px;
width: 120px;
position: absolute;
@@ -26,6 +18,14 @@ block head
top: 26px;
overflow: hidden;
}
+ .#{prefix}-big-title {
+ font-size: 60px;
+ margin: 0;
+ line-height: 1;
+ margin-bottom: 25px;
+ font-weight: 500;
+ color: #333;
+ }
.#{prefix}-terms-list {
display: flex;
flex-direction: column;
@@ -53,6 +53,9 @@ block head
gap: 10px;
margin-bottom: 24px;
}
+ .#{prefix}-address-section > * {
+ flex: 1 1 auto;
+ }
.#{prefix}-address {}
.#{prefix}-table {
width: 100%;
@@ -99,8 +102,12 @@ block head
display: flex;
padding: 4px 0;
}
- .#{prefix}-totals__line--gray-border {}
- .#{prefix}-totals__line--dark-border {}
+ .#{prefix}-totals__line--gray-border {
+ border-bottom: 1px solid #DADADA;
+ }
+ .#{prefix}-totals__line--dark-border {
+ border-bottom: 1px solid #000;
+ }
.#{prefix}-totals__line__label {
min-width: 160px;
}
@@ -161,30 +168,31 @@ block content
tbody
each line in lines
tr(class=`${prefix}-table__row`)
- td(class=`${prefix}-table__column`)= line.item
- td(class=`${prefix}-table__column`)= line.description
- td(class=`${prefix}-table__column ${prefix}-table__column--right`)= line.rate
- td(class=`${prefix}-table__column ${prefix}-table__column--right`)= line.total
+ 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__line #{prefix}-totals__line--gray-border`)
+ div(class=`${prefix}-totals__line ${prefix}-totals__line--gray-border`)
span(class=`${prefix}-totals__line__label`)= subtotalLabel
span(class=`${prefix}-totals__line__amount`)= subtotal
+
if showTotal
- div(class=`${prefix}-totals__line #{prefix}-totals__line--dark-border`)
+ div(class=`${prefix}-totals__line ${prefix}-totals__line--dark-border`)
span(class=`${prefix}-totals__line__label`)= totalLabel
span(class=`${prefix}-totals__line__amount`)= total
//- Customer Note Section
if showCustomerNote
div(class=`${prefix}-statement`)
- span(class=`${prefix}-statement__label`)= customerNoteLabel
- p(class=`${prefix}-statement__value`)= customerNote
+ div(class=`${prefix}-statement__label`)= customerNoteLabel
+ div(class=`${prefix}-statement__value`)= customerNote
//- Terms & Conditions Section
if showTermsConditions
div(class=`${prefix}-statement`)
- span(class=`${prefix}-statement__label`)= termsConditionsLabel
- p(class=`${prefix}-statement__value`)= termsConditions
+ div(class=`${prefix}-statement__label`)= termsConditionsLabel
+ div(class=`${prefix}-statement__value`)= termsConditions
diff --git a/packages/server/src/interfaces/CreditNote.ts b/packages/server/src/interfaces/CreditNote.ts
index 136425818..ffb4c26ba 100644
--- a/packages/server/src/interfaces/CreditNote.ts
+++ b/packages/server/src/interfaces/CreditNote.ts
@@ -62,6 +62,8 @@ export interface ICreditNote {
branchId?: number;
warehouseId: number;
createdAt?: Date;
+ termsConditions: string;
+ note: string;
}
export enum CreditNoteAction {
diff --git a/packages/server/src/interfaces/PaymentReceive.ts b/packages/server/src/interfaces/PaymentReceive.ts
index fbc24ba43..8dd880872 100644
--- a/packages/server/src/interfaces/PaymentReceive.ts
+++ b/packages/server/src/interfaces/PaymentReceive.ts
@@ -203,53 +203,35 @@ export interface PaymentReceivedPdfTax {
export interface PaymentReceivedPdfTemplateAttributes {
primaryColor: string;
secondaryColor: string;
- companyName: string;
-
showCompanyLogo: boolean;
companyLogo: string;
+ companyName: string;
- dueDateLabel: string;
- showDueDate: boolean;
-
- dateIssueLabel: string;
- showDateIssue: boolean;
-
- invoiceNumberLabel: 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;
-
- discount: string;
-
- termsConditionsLabel: string;
- showTermsConditions: boolean;
-
- lines: PaymentReceivedPdfLineItem[];
- taxes: PaymentReceivedPdfTax[];
-
- statementLabel: string;
- showStatement: boolean;
billedToAddress: string[];
billedFromAddress: string[];
+ showBilledFromAddress: boolean;
+ showBillingToAddress: boolean;
+ billedToLabel: string;
+
+ total: string;
+ totalLabel: string;
+ showTotal: boolean;
+
+ subtotal: string;
+ subtotalLabel: string;
+ showSubtotal: boolean;
+
+ lines: Array<{
+ invoiceNumber: string;
+ invoiceAmount: string;
+ paidAmount: string;
+ }>;
+
+ showPaymentReceivedNumber: boolean;
+ paymentReceivedNumberLabel: string;
+ paymentReceivedNumebr: string;
+
+ paymentReceivedDate: string;
+ showPaymentReceivedDate: boolean;
+ paymentReceivedDateLabel: string;
}
diff --git a/packages/server/src/interfaces/SaleReceipt.ts b/packages/server/src/interfaces/SaleReceipt.ts
index 14f451dda..d3e458313 100644
--- a/packages/server/src/interfaces/SaleReceipt.ts
+++ b/packages/server/src/interfaces/SaleReceipt.ts
@@ -155,3 +155,57 @@ export interface ISaleReceiptMailPresend {
saleReceiptId: number;
messageOptions: SaleReceiptMailOptsDTO;
}
+
+export interface ISaleReceiptBrandingTemplateAttributes {
+ primaryColor: string;
+ secondaryColor: string;
+ showCompanyLogo: boolean;
+ companyLogo: string;
+ companyName: string;
+
+ // Address
+ billedToAddress: string[];
+ billedFromAddress: string[];
+ showBilledFromAddress: boolean;
+ showBilledToAddress: boolean;
+ billedToLabel: string;
+
+ // Total
+ total: string;
+ totalLabel: string;
+ showTotal: boolean;
+
+ // Subtotal
+ subtotal: string;
+ subtotalLabel: string;
+ showSubtotal: boolean;
+
+ // Customer Note
+ showCustomerNote: boolean;
+ customerNote: string;
+ customerNoteLabel: string;
+
+ // Terms & Conditions
+ showTermsConditions: boolean;
+ termsConditions: string;
+ termsConditionsLabel: string;
+
+ // Lines
+ lines: Array<{
+ item: string;
+ description: string;
+ rate: string;
+ quantity: string;
+ total: string;
+ }>;
+
+ // Receipt Number
+ showReceiptNumber: boolean;
+ receiptNumberLabel: string;
+ receiptNumebr: string;
+
+ // Receipt Date
+ receiptDate: string;
+ showReceiptDate: boolean;
+ receiptDateLabel: string;
+}
diff --git a/packages/server/src/services/CreditNotes/utils.ts b/packages/server/src/services/CreditNotes/utils.ts
index e81de43f2..6b94d955e 100644
--- a/packages/server/src/services/CreditNotes/utils.ts
+++ b/packages/server/src/services/CreditNotes/utils.ts
@@ -1,9 +1,23 @@
-import { CreditNotePdfTemplateAttributes } from "@/interfaces";
-import CreditNote from "@/models/CreditNote";
+import { CreditNotePdfTemplateAttributes, ICreditNote } from '@/interfaces';
-
-export const transformCreditNoteToPdfTemplate = (creditNote: CreditNote): Partial {
+export const transformCreditNoteToPdfTemplate = (
+ creditNote: ICreditNote
+): Partial => {
return {
+ creditNoteDate: creditNote.formattedCreditNoteDate,
+ creditNoteNumebr: creditNote.creditNoteNumber,
+ total: creditNote.formattedAmount,
+ subtotal: creditNote.formattedSubtotal,
+
+ lines: creditNote.entries?.map((entry) => ({
+ item: entry.item.name,
+ description: entry.description,
+ rate: entry.rateFormatted,
+ quantity: entry.quantityFormatted,
+ total: entry.totalFormatted,
+ })),
+ customerNote: creditNote.note,
+ termsConditions: creditNote.termsConditions,
};
-}
\ No newline at end of file
+};
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormProvider.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormProvider.tsx
index d51877c01..e4a8a1095 100644
--- a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormProvider.tsx
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptFormProvider.tsx
@@ -136,19 +136,18 @@ function ReceiptFormProvider({ receiptId, ...props }) {
// Branding templates
brandingTemplates,
- isBrandingTemplatesLoading
+ isBrandingTemplatesLoading,
};
+ const isLoading =
+ isReceiptLoading ||
+ isAccountsLoading ||
+ isCustomersLoading ||
+ isItemsLoading ||
+ isSettingLoading ||
+ isBrandingTemplatesLoading;
+
return (
-
+
);
From 5f40d508527df1f4ba7b30fcc5dc101a732d5f0f Mon Sep 17 00:00:00 2001
From: Ahmed Bouhuolia
Date: Tue, 17 Sep 2024 18:19:28 +0200
Subject: [PATCH 31/32] fix: pdf template customization
---
.../PdfTemplates/PdfTemplatesController.ts | 28 +++++++++++--------
.../services/PdfTemplate/CreatePdfTemplate.ts | 3 +-
.../PdfTemplate/PdfTemplateApplication.ts | 2 +-
.../server/src/services/PdfTemplate/types.ts | 6 ++++
.../src/services/Sales/Estimates/utils.ts | 17 ++++++++++-
.../src/services/Sales/Invoices/utils.ts | 5 +++-
.../PaymentReceivedBrandingTemplate.ts | 2 +-
.../services/Sales/PaymentReceived/utils.ts | 11 +++++++-
.../src/services/Sales/Receipts/utils.ts | 18 ++++++++++--
.../CreditNoteCustomizeContent.tsx | 4 ---
.../EstimateCustomizeContent.tsx | 4 ---
.../InvoiceCustomizeContent.tsx | 4 ---
.../Sales/Invoices/InvoiceForm/utils.tsx | 1 -
.../PaymentReceivedCustomizeContent.tsx | 4 ---
.../ReceiptCustomizeContent.tsx | 4 ---
.../webapp/src/hooks/query/pdf-templates.ts | 2 +-
16 files changed, 74 insertions(+), 41 deletions(-)
diff --git a/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts b/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts
index baad0cace..c3758250d 100644
--- a/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts
+++ b/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts
@@ -21,7 +21,7 @@ export class PdfTemplatesController extends BaseController {
this.validationResult,
this.deletePdfTemplate.bind(this)
);
- router.put(
+ router.post(
'/:template_id',
[
param('template_id').exists().isInt().toInt(),
@@ -77,7 +77,10 @@ export class PdfTemplatesController extends BaseController {
resource,
attributes
);
- return res.status(201).send(result);
+ return res.status(201).send({
+ id: result.id,
+ message: 'The PDF template has been created successfully.',
+ });
} catch (error) {
next(error);
}
@@ -94,7 +97,10 @@ export class PdfTemplatesController extends BaseController {
Number(templateId),
editTemplateDTO
);
- return res.status(200).send(result);
+ return res.status(200).send({
+ id: result.id,
+ message: 'The PDF template has been updated successfully.',
+ });
} catch (error) {
next(error);
}
@@ -109,7 +115,10 @@ export class PdfTemplatesController extends BaseController {
tenantId,
Number(templateId)
);
- return res.status(204).send();
+ return res.status(204).send({
+ id: templateId,
+ message: 'The PDF template has been deleted successfully.',
+ });
} catch (error) {
next(error);
}
@@ -158,13 +167,10 @@ export class PdfTemplatesController extends BaseController {
tenantId,
Number(templateId)
);
- return res
- .status(204)
- .send({
- id: templateId,
- message:
- 'The given pdf template has been assigned as default template',
- });
+ 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/CreatePdfTemplate.ts b/packages/server/src/services/PdfTemplate/CreatePdfTemplate.ts
index 02efc83f2..a437b163c 100644
--- a/packages/server/src/services/PdfTemplate/CreatePdfTemplate.ts
+++ b/packages/server/src/services/PdfTemplate/CreatePdfTemplate.ts
@@ -36,7 +36,7 @@ export class CreatePdfTemplate {
tenantId,
});
- await PdfTemplate.query(trx).insert({
+ const pdfTemplate = await PdfTemplate.query(trx).insert({
templateName,
resource,
attributes,
@@ -45,6 +45,7 @@ export class CreatePdfTemplate {
await this.eventPublisher.emitAsync(events.pdfTemplate.onCreated, {
tenantId,
});
+ return pdfTemplate;
});
}
}
diff --git a/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts b/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts
index 3f0d66be6..5f77475ff 100644
--- a/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts
+++ b/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts
@@ -1,5 +1,5 @@
import { Inject, Service } from 'typedi';
-import { ICreateInvoicePdfTemplateDTO } from './types';
+import { ICreateInvoicePdfTemplateDTO, IEditPdfTemplateDTO } from './types';
import { CreatePdfTemplate } from './CreatePdfTemplate';
import { DeletePdfTemplate } from './DeletePdfTemplate';
import { GetPdfTemplate } from './GetPdfTemplate';
diff --git a/packages/server/src/services/PdfTemplate/types.ts b/packages/server/src/services/PdfTemplate/types.ts
index b27c87f0a..abe9345a0 100644
--- a/packages/server/src/services/PdfTemplate/types.ts
+++ b/packages/server/src/services/PdfTemplate/types.ts
@@ -1,6 +1,12 @@
export enum ERRORS {
CANNOT_DELETE_PREDEFINED_PDF_TEMPLATE = 'CANNOT_DELETE_PREDEFINED_PDF_TEMPLATE',
}
+
+export interface IEditPdfTemplateDTO {
+ templateName: string;
+ attributes: Record;
+}
+
export interface ICreateInvoicePdfTemplateDTO {
// Colors
primaryColor?: string;
diff --git a/packages/server/src/services/Sales/Estimates/utils.ts b/packages/server/src/services/Sales/Estimates/utils.ts
index 2b7c7fd1b..879667893 100644
--- a/packages/server/src/services/Sales/Estimates/utils.ts
+++ b/packages/server/src/services/Sales/Estimates/utils.ts
@@ -3,5 +3,20 @@ import { EstimatePdfBrandingAttributes } from './constants';
export const transformEstimateToPdfTemplate = (
estimate
): Partial => {
- return {};
+ return {
+ expirationDate: estimate.formattedExpirationDate,
+ estimateNumebr: estimate.estimateNumber,
+ estimateDate: estimate.formattedEstimateDate,
+ lines: estimate.entries.map((entry) => ({
+ item: entry.item.name,
+ description: entry.description,
+ rate: entry.rateFormatted,
+ quantity: entry.quantityFormatted,
+ total: entry.totalFormatted,
+ })),
+ total: estimate.formattedSubtotal,
+ subtotal: estimate.formattedSubtotal,
+ customerNote: estimate.customerNote,
+ termsConditions: estimate.termsConditions,
+ };
};
diff --git a/packages/server/src/services/Sales/Invoices/utils.ts b/packages/server/src/services/Sales/Invoices/utils.ts
index ed8235678..8131ea2bd 100644
--- a/packages/server/src/services/Sales/Invoices/utils.ts
+++ b/packages/server/src/services/Sales/Invoices/utils.ts
@@ -9,7 +9,6 @@ export const mergePdfTemplateWithDefaultAttributes = (
brandingTemplate,
(val, key) => val !== null && Object.keys(defaultAttributes).includes(key)
);
-
return {
...defaultAttributes,
...brandingAttributes,
@@ -39,5 +38,9 @@ export const transformInvoiceToPdfTemplate = (
quantity: entry.quantityFormatted,
total: entry.totalFormatted,
})),
+ taxes: invoice.taxes.map((tax) => ({
+ label: tax.name,
+ amount: tax.taxRateAmountFormatted,
+ })),
};
};
diff --git a/packages/server/src/services/Sales/PaymentReceived/PaymentReceivedBrandingTemplate.ts b/packages/server/src/services/Sales/PaymentReceived/PaymentReceivedBrandingTemplate.ts
index 20e01d7e6..625b24d95 100644
--- a/packages/server/src/services/Sales/PaymentReceived/PaymentReceivedBrandingTemplate.ts
+++ b/packages/server/src/services/Sales/PaymentReceived/PaymentReceivedBrandingTemplate.ts
@@ -18,7 +18,7 @@ export class PaymentReceivedBrandingTemplate {
public async getPaymentReceivedPdfTemplate(
tenantId: number,
paymentTemplateId: number
- ): Promise {
+ ) {
const template = await this.getPdfTemplateService.getPdfTemplate(
tenantId,
paymentTemplateId
diff --git a/packages/server/src/services/Sales/PaymentReceived/utils.ts b/packages/server/src/services/Sales/PaymentReceived/utils.ts
index beda5e0b9..540aab9ed 100644
--- a/packages/server/src/services/Sales/PaymentReceived/utils.ts
+++ b/packages/server/src/services/Sales/PaymentReceived/utils.ts
@@ -7,6 +7,15 @@ export const transformPaymentReceivedToPdfTemplate = (
payment: IPaymentReceived
): Partial => {
return {
- // ...payment
+ total: payment.formattedAmount,
+ subtotal: payment.subtotalFormatted,
+ paymentReceivedNumebr: payment.paymentReceiveNo,
+ paymentReceivedDate: payment.formattedPaymentDate,
+ customerName: payment.customer.displayName,
+ lines: payment.entries.map((entry) => ({
+ invoiceNumber: entry.invoice.invoiceNo,
+ invoiceAmount: entry.invoice.totalFormatted,
+ paidAmount: entry.paymentAmountFormatted,
+ })),
};
};
diff --git a/packages/server/src/services/Sales/Receipts/utils.ts b/packages/server/src/services/Sales/Receipts/utils.ts
index 548ad7001..e1d9ac9d9 100644
--- a/packages/server/src/services/Sales/Receipts/utils.ts
+++ b/packages/server/src/services/Sales/Receipts/utils.ts
@@ -1,6 +1,20 @@
+import { ISaleReceipt, ISaleReceiptBrandingTemplateAttributes } from "@/interfaces";
-export const transformReceiptToBrandingTemplateAttributes = () => {
- return {};
+export const transformReceiptToBrandingTemplateAttributes = (saleReceipt: ISaleReceipt): Partial => {
+ return {
+ total: saleReceipt.formattedAmount,
+ subtotal: saleReceipt.formattedSubtotal,
+ lines: saleReceipt.entries?.map((entry) => ({
+ item: entry.item.name,
+ description: entry.description,
+ rate: entry.rateFormatted,
+ quantity: entry.quantityFormatted,
+ total: entry.totalFormatted,
+ })),
+
+ receiptNumber: saleReceipt.receiptNumber,
+ receiptDate: saleReceipt.formattedReceiptDate,
+ };
}
\ 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 8a9ca888d..a5e437754 100644
--- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx
+++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteCustomize/CreditNoteCustomizeContent.tsx
@@ -37,10 +37,6 @@ export function CreditNoteCustomizeContent() {
-
-
- asdfasdfdsaf #3
-
);
}
diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx
index 9d4dd2739..23a67e011 100644
--- a/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx
+++ b/packages/webapp/src/containers/Sales/Estimates/EstimateCustomize/EstimateCustomizeContent.tsx
@@ -36,10 +36,6 @@ export function EstimateCustomizeContent() {
-
-
- asdfasdfdsaf #3
-
);
}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx
index 0346b989c..988198b57 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceCustomize/InvoiceCustomizeContent.tsx
@@ -43,10 +43,6 @@ export function InvoiceCustomizeContent() {
-
-
- asdfasdfdsaf #3
-
);
}
diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx
index edc3c91de..659b0d5f3 100644
--- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx
+++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/utils.tsx
@@ -10,7 +10,6 @@ import {
compose,
transformToForm,
repeatValue,
- formattedAmount,
defaultFastFieldShouldUpdate,
} from '@/utils';
import { ERROR } from '@/constants/errors';
diff --git a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx
index b84bbb168..b08b5979e 100644
--- a/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx
+++ b/packages/webapp/src/containers/Sales/PaymentsReceived/PaymentReceivedCustomize/PaymentReceivedCustomizeContent.tsx
@@ -37,10 +37,6 @@ export function PaymentReceivedCustomizeContent() {
-
-
- asdfasdfdsaf #3
-
);
}
diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx
index abcffae35..fb1a0e06c 100644
--- a/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx
+++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptCustomize/ReceiptCustomizeContent.tsx
@@ -36,10 +36,6 @@ export function ReceiptCustomizeContent() {
-
-
- asdfasdfdsaf #3
-
);
}
diff --git a/packages/webapp/src/hooks/query/pdf-templates.ts b/packages/webapp/src/hooks/query/pdf-templates.ts
index adba19753..6823b9cee 100644
--- a/packages/webapp/src/hooks/query/pdf-templates.ts
+++ b/packages/webapp/src/hooks/query/pdf-templates.ts
@@ -98,7 +98,7 @@ export const useEditPdfTemplate = (
>(
({ templateId, values }) =>
apiRequest
- .put(`/pdf-templates/${templateId}`, transfromToSnakeCase(values))
+ .post(`/pdf-templates/${templateId}`, transfromToSnakeCase(values))
.then((res) => res.data),
{
onSuccess: () => {
From 34e781b4a273eb43719d88f8f43b9c68309c05ef Mon Sep 17 00:00:00 2001
From: Ahmed Bouhuolia
Date: Tue, 17 Sep 2024 19:18:22 +0200
Subject: [PATCH 32/32] fix: typo in invoice customize drawer
---
packages/server/src/constants/event-tracker.ts | 2 +-
.../src/services/Sales/Estimates/SaleEstimatesPdf.ts | 7 ++++---
.../services/Sales/Receipts/SaleReceiptsPdfService.ts | 11 ++++++-----
.../alerts/MarkDefaultBrandingTemplateAlert.tsx | 5 ++---
.../src/style/components/DataTable/DataTable.scss | 2 +-
5 files changed, 14 insertions(+), 13 deletions(-)
diff --git a/packages/server/src/constants/event-tracker.ts b/packages/server/src/constants/event-tracker.ts
index 3fafc76a6..d5d21a508 100644
--- a/packages/server/src/constants/event-tracker.ts
+++ b/packages/server/src/constants/event-tracker.ts
@@ -1,5 +1,5 @@
export const SALE_INVOICE_CREATED = 'Sale invoice created';
-export const SALE_INVOICE_EDITED = 'Sale invoice d';
+export const SALE_INVOICE_EDITED = 'Sale invoice edited';
export const SALE_INVOICE_DELETED = 'Sale invoice deleted';
export const SALE_INVOICE_MAIL_DELIVERED = 'Sale invoice mail delivered';
diff --git a/packages/server/src/services/Sales/Estimates/SaleEstimatesPdf.ts b/packages/server/src/services/Sales/Estimates/SaleEstimatesPdf.ts
index ef89fc919..36e4bf8f0 100644
--- a/packages/server/src/services/Sales/Estimates/SaleEstimatesPdf.ts
+++ b/packages/server/src/services/Sales/Estimates/SaleEstimatesPdf.ts
@@ -43,9 +43,10 @@ export class SaleEstimatesPdf {
}
/**
- *
- * @param {number} tenantId
- * @param {number} estimateId
+ * Retrieves the given estimate branding attributes.
+ * @param {number} tenantId - Tenant id.
+ * @param {number} estimateId - Estimate id.
+ * @returns {Promise}
*/
async getEstimateBrandingAttributes(
tenantId: number,
diff --git a/packages/server/src/services/Sales/Receipts/SaleReceiptsPdfService.ts b/packages/server/src/services/Sales/Receipts/SaleReceiptsPdfService.ts
index c72c9b0c0..7b056c1c5 100644
--- a/packages/server/src/services/Sales/Receipts/SaleReceiptsPdfService.ts
+++ b/packages/server/src/services/Sales/Receipts/SaleReceiptsPdfService.ts
@@ -5,6 +5,7 @@ import { GetSaleReceipt } from './GetSaleReceipt';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { SaleReceiptBrandingTemplate } from './SaleReceiptBrandingTemplate';
import { transformReceiptToBrandingTemplateAttributes } from './utils';
+import { ISaleReceiptBrandingTemplateAttributes } from '@/interfaces';
@Service()
export class SaleReceiptsPdf {
@@ -34,26 +35,26 @@ export class SaleReceiptsPdf {
tenantId,
saleReceiptId
);
- console.log(brandingAttributes, 'attributes');
-
+ // Converts the receipt template to html content.
const htmlContent = await this.templateInjectable.render(
tenantId,
'modules/receipt-regular',
brandingAttributes
);
+ // Renders the html content to pdf document.
return this.chromiumlyTenancy.convertHtmlContent(tenantId, htmlContent);
}
/**
* Retrieves receipt branding attributes.
* @param {number} tenantId
- * @param {number] receiptId
- * @returns
+ * @param {number} receiptId
+ * @returns {Promise}
*/
public async getReceiptBrandingAttributes(
tenantId: number,
receiptId: number
- ) {
+ ): Promise {
const { PdfTemplate } = this.tenancy.models(tenantId);
const saleReceipt = await this.getSaleReceiptService.getSaleReceipt(
diff --git a/packages/webapp/src/containers/BrandingTemplates/alerts/MarkDefaultBrandingTemplateAlert.tsx b/packages/webapp/src/containers/BrandingTemplates/alerts/MarkDefaultBrandingTemplateAlert.tsx
index 41b9c8244..8329c0f02 100644
--- a/packages/webapp/src/containers/BrandingTemplates/alerts/MarkDefaultBrandingTemplateAlert.tsx
+++ b/packages/webapp/src/containers/BrandingTemplates/alerts/MarkDefaultBrandingTemplateAlert.tsx
@@ -32,7 +32,7 @@ function MarkDefaultBrandingTemplateAlert({
.then(() => {
AppToaster.show({
message:
- 'The branding template has been marked as default successfully.',
+ 'The branding template has been marked as a default template.',
intent: Intent.SUCCESS,
});
closeAlert(name);
@@ -60,8 +60,7 @@ function MarkDefaultBrandingTemplateAlert({
onConfirm={handleConfirmDelete}
>
- Are you sure want to mark the given branding template as default
- template?
+ Are you sure want to mark the given branding template as a default template?
);
diff --git a/packages/webapp/src/style/components/DataTable/DataTable.scss b/packages/webapp/src/style/components/DataTable/DataTable.scss
index 3d6387028..a96d7a003 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: #f6f7f9;
+ background: #f5f5f5;
font-size: 14px;
color: #424853;
font-weight: 400;