feat: add style to SSR invoice paper template

This commit is contained in:
Ahmed Bouhuolia
2024-11-05 17:09:47 +02:00
parent 22ea557337
commit d23f33bae4
9 changed files with 131 additions and 61 deletions

View File

@@ -450,15 +450,15 @@ export default class SaleInvoicesController extends BaseController {
ACCEPT_TYPE.APPLICATION_JSON,
ACCEPT_TYPE.APPLICATION_PDF,
]);
// Retrieves invoice in pdf format.
if (true) {
// Retrieves invoice in PDF format.
if (ACCEPT_TYPE.APPLICATION_PDF === acceptType) {
const [pdfContent, filename] =
await this.saleInvoiceApplication.saleInvoicePdf(
tenantId,
saleInvoiceId
);
res.set({
'Content-Type': 'text/html',
'Content-Type': 'application/pdf',
'Content-Length': pdfContent.length,
'Content-Disposition': `attachment; filename="${filename}"`,
});

View File

@@ -47,16 +47,9 @@ export class SaleInvoicePdf {
tenantId,
invoiceId
);
// const htmlContent = await this.templateInjectable.render(
// tenantId,
// 'modules/invoice-standard',
// brandingAttributes
// );
const htmlContent = renderInvoicePaperTemplateHtml({});
console.log(htmlContent);
const htmlContent = renderInvoicePaperTemplateHtml({
...brandingAttributes,
});
// Converts the given html content to pdf document.
const buffer = await this.chromiumlyTenancy.convertHtmlContent(
tenantId,
@@ -69,7 +62,7 @@ export class SaleInvoicePdf {
events.saleInvoice.onPdfViewed,
eventPayload
);
return [htmlContent, filename];
return [buffer, filename];
}
/**

View File

@@ -243,15 +243,15 @@ export function InvoicePaperTemplate({
<Stack spacing={2}>
<Text>{data.item}</Text>
<Text
// variant={'muted'}
// style={{ fontSize: 12 }}
color={'#5f6b7c'}
fontSize={12}
>
{data.description}
</Text>
</Stack>
),
},
{ label: lineQuantityLabel, accessor: 'quantity' },
{ label: lineQuantityLabel, accessor: 'quantity', align: 'right' },
{ label: lineRateLabel, accessor: 'rate', align: 'right' },
{ label: lineTotalLabel, accessor: 'total', align: 'right' },
]}

View File

@@ -20,10 +20,9 @@ export function PaperTemplate({
}: PaperTemplateProps) {
return (
<Box
borderRadius="5px"
backgroundColor="#fff"
color="#111"
boxShadow="inset 0 4px 0px 0 var(--invoice-primary-color), 0 10px 15px rgba(0, 0, 0, 0.05)"
boxShadow="inset 0 4px 0px 0 var(--invoice-primary-color)"
padding="30px 30px"
fontSize="12px"
position="relative"
@@ -31,7 +30,15 @@ export function PaperTemplate({
h="1123px"
w="794px"
{...restProps}
className={restProps?.className}
className={clsx(
restProps?.className,
css`
@media print {
width: auto !important;
height: auto !important;
}
`
)}
>
<style>{`:root { --invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor}; }`}</style>
{children}
@@ -39,16 +46,6 @@ export function PaperTemplate({
);
}
interface PaperTemplateTableProps {
columns: Array<{
accessor: string | ((data: Record<string, any>) => JSX.Element);
label: string;
value?: JSX.Element;
align?: 'left' | 'center' | 'right';
}>;
data: Array<Record<string, any>>;
}
interface PaperTemplateBigTitleProps {
title: string;
}
@@ -86,6 +83,16 @@ PaperTemplate.Logo = ({ logoUri }: PaperTemplateLogoProps) => {
);
};
interface PaperTemplateTableProps {
columns: Array<{
accessor: string | ((data: Record<string, any>) => JSX.Element);
label: string;
value?: JSX.Element;
align?: 'left' | 'center' | 'right';
}>;
data: Array<Record<string, any>>;
}
PaperTemplate.Table = ({ columns, data }: PaperTemplateTableProps) => {
return (
<table
@@ -133,9 +140,9 @@ PaperTemplate.Table = ({ columns, data }: PaperTemplateTableProps) => {
<thead>
<tr>
{columns.map((col, index) => (
<th key={index} align={col.align}>
<x.th key={index} textAlign={col.align}>
{col.label}
</th>
</x.th>
))}
</tr>
</thead>
@@ -144,11 +151,11 @@ PaperTemplate.Table = ({ columns, data }: PaperTemplateTableProps) => {
{data.map((_data: any) => (
<tr>
{columns.map((column, index) => (
<td align={column.align} key={index}>
<x.td textAlign={column.align} key={index}>
{isFunction(column?.accessor)
? column?.accessor(_data)
: get(_data, column.accessor)}
</td>
</x.td>
))}
</tr>
))}
@@ -183,6 +190,7 @@ const totalBottomBordered = css`
const totalBottomGrayBordered = css`
border-bottom: 1px solid #dadada;
`;
PaperTemplate.TotalLine = ({
label,
amount,
@@ -262,7 +270,9 @@ PaperTemplate.TermsItem = ({
}) => {
return (
<Group spacing={12}>
<x.div minWidth={'12px'} color={'#333'}>{label}</x.div>
<x.div minWidth={'120px'} color={'#333'}>
{label}
</x.div>
<x.div>{children}</x.div>
</Group>
);

View File

@@ -1,7 +1,7 @@
import { CacheProvider, ThemeProvider } from '@emotion/react';
import { EmotionCache } from '@emotion/cache';
import { defaultTheme } from '@xstyled/system';
import { Preflight } from '@xstyled/emotion';
import { createGlobalStyle, Preflight } from '@xstyled/emotion';
const theme = {
...defaultTheme,
@@ -17,8 +17,50 @@ export function PaperTemplateLayout({
<CacheProvider value={cache}>
<ThemeProvider theme={theme}>
<Preflight />
<GlobalStyles />
{children}
</ThemeProvider>
</CacheProvider>
);
}
// Create global styles to set the body font
const GlobalStyles = createGlobalStyle`
*,
*::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;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: transparent;
}
body, h1, h2, h3, h4, h5, h6{
font-family: "Open Sans", sans-serif;
font-optical-sizing: auto;
font-style: normal;
}
`;

View File

@@ -0,0 +1,5 @@
export const OpenSansFontLink = `
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
`;

View File

@@ -1,5 +1,9 @@
import { x } from '@xstyled/emotion';
import { SystemProps, x } from '@xstyled/emotion';
export const Text = ({ children }: { children: React.ReactNode }) => {
return <x.div>{children}</x.div>;
export interface TextProps extends SystemProps {
children?: React.ReactNode;
}
export const Text = ({ children, ...restProps }: TextProps) => {
return <x.div {...restProps}>{children}</x.div>;
};

View File

@@ -1,36 +1,21 @@
import { renderToString } from 'react-dom/server';
import createCache from '@emotion/cache';
import { css } from '@emotion/css';
import {
InvoicePaperTemplate,
InvoicePaperTemplateProps,
} from '../components/InvoicePaperTemplate';
import { PaperTemplateLayout } from '../components/PaperTemplateLayout';
import { extractCritical } from '@emotion/server';
import { OpenSansFontLink } from '../constants';
import { renderSSR } from './render-ssr';
export const renderInvoicePaperTemplateHtml = (
props: InvoicePaperTemplateProps
) => {
const key = 'invoice-paper-template';
const cache = createCache({ key });
const renderedHtml = renderToString(
<PaperTemplateLayout cache={cache}>
<InvoicePaperTemplate {...props} />
</PaperTemplateLayout>
return renderSSR(
<InvoicePaperTemplate
{...props}
/>
);
const { html, css, ids } = extractCritical(renderedHtml);
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Invoice</title>
<style data-emotion="${key} ${ids.join(' ')}">${css}</style>
</head>
<body>
<div id="root">${html}</div>
</body>
</html>`;
};

View File

@@ -0,0 +1,31 @@
import { renderToString } from 'react-dom/server';
import createCache from '@emotion/cache';
import { extractCritical } from '@emotion/server';
import { OpenSansFontLink } from '../constants';
import { PaperTemplateLayout } from '../components/PaperTemplateLayout';
export const renderSSR = (children: React.ReactNode) => {
const key = 'invoice-paper-template';
const cache = createCache({ key });
const renderedHtml = renderToString(
<PaperTemplateLayout cache={cache}>{children}</PaperTemplateLayout>
);
const extractedHtml = extractCritical(renderedHtml);
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Invoice</title>
${OpenSansFontLink}
<style data-emotion="${key} ${extractedHtml.ids.join(' ')}">${extractedHtml.css
}</style>
</head>
<body>
<div id="root">${extractedHtml.html}</div>
</body>
</html>`;
};