mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
feat: wip invoice paper template server-side
This commit is contained in:
@@ -1,10 +1,8 @@
|
|||||||
import { renderToString } from 'react-dom/server';
|
|
||||||
import {
|
import {
|
||||||
PaperTemplate,
|
PaperTemplate,
|
||||||
PaperTemplateProps,
|
PaperTemplateProps,
|
||||||
PaperTemplateTotalBorder,
|
PaperTemplateTotalBorder,
|
||||||
} from './PaperTemplate';
|
} from './PaperTemplate';
|
||||||
import { x } from '@xstyled/emotion';
|
|
||||||
import { Box } from '../lib/layout/Box';
|
import { Box } from '../lib/layout/Box';
|
||||||
import { Text } from '../lib/text/Text';
|
import { Text } from '../lib/text/Text';
|
||||||
import { Stack } from '../lib/layout/Stack';
|
import { Stack } from '../lib/layout/Stack';
|
||||||
@@ -17,8 +15,6 @@ import {
|
|||||||
DefaultPdfTemplateAddressBilledTo,
|
DefaultPdfTemplateAddressBilledTo,
|
||||||
DefaultPdfTemplateAddressBilledFrom,
|
DefaultPdfTemplateAddressBilledFrom,
|
||||||
} from './_constants';
|
} from './_constants';
|
||||||
import { PaperTemplateLayout } from './PaperTemplateLayout';
|
|
||||||
import createCache from '@emotion/cache';
|
|
||||||
|
|
||||||
interface PapaerLine {
|
interface PapaerLine {
|
||||||
item?: string;
|
item?: string;
|
||||||
@@ -328,16 +324,3 @@ export function InvoicePaperTemplate({
|
|||||||
</PaperTemplate>
|
</PaperTemplate>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const renderInvoicePaperTemplateHtml = (
|
|
||||||
props: InvoicePaperTemplateProps
|
|
||||||
) => {
|
|
||||||
const key = 'custom';
|
|
||||||
const cache = createCache({ key });
|
|
||||||
|
|
||||||
return renderToString(
|
|
||||||
<PaperTemplateLayout cache={cache}>
|
|
||||||
<InvoicePaperTemplate {...props} />
|
|
||||||
</PaperTemplateLayout>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -2,30 +2,10 @@ import React from 'react';
|
|||||||
import clsx from 'classnames';
|
import clsx from 'classnames';
|
||||||
import { get, isFunction } from 'lodash';
|
import { get, isFunction } from 'lodash';
|
||||||
import { x } from '@xstyled/emotion';
|
import { x } from '@xstyled/emotion';
|
||||||
|
import { css } from '@emotion/css';
|
||||||
import { Box, BoxProps } from '../lib/layout/Box';
|
import { Box, BoxProps } from '../lib/layout/Box';
|
||||||
import { Group, GroupProps } from '../lib/layout/Group';
|
import { Group, GroupProps } from '../lib/layout/Group';
|
||||||
|
|
||||||
const styles = {
|
|
||||||
root: 'root',
|
|
||||||
bigTitle: 'bigTitle',
|
|
||||||
logoWrap: 'logoWrap',
|
|
||||||
logoImg: 'logoImg',
|
|
||||||
table: 'table',
|
|
||||||
tableBody: 'tableBody',
|
|
||||||
totals: 'totals',
|
|
||||||
totalsItem: 'totalsItem',
|
|
||||||
totalBottomBordered: 'totalBottomBordered',
|
|
||||||
totalBottomGrayBordered: 'totalBottomGrayBordered',
|
|
||||||
totalsItemLabel: 'totalsItemLabel',
|
|
||||||
totalsItemAmount: 'totalsItemAmount',
|
|
||||||
addressRoot: 'addressRoot',
|
|
||||||
paragraph: 'paragraph',
|
|
||||||
paragraphLabel: 'paragraphLabel',
|
|
||||||
details: 'details',
|
|
||||||
detail: 'detail',
|
|
||||||
detailLabel: 'detailLabel',
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface PaperTemplateProps extends BoxProps {
|
export interface PaperTemplateProps extends BoxProps {
|
||||||
primaryColor?: string;
|
primaryColor?: string;
|
||||||
secondaryColor?: string;
|
secondaryColor?: string;
|
||||||
@@ -51,7 +31,7 @@ export function PaperTemplate({
|
|||||||
h="1123px"
|
h="1123px"
|
||||||
w="794px"
|
w="794px"
|
||||||
{...restProps}
|
{...restProps}
|
||||||
className={clsx(styles.root, restProps?.className)}
|
className={restProps?.className}
|
||||||
>
|
>
|
||||||
<style>{`:root { --invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor}; }`}</style>
|
<style>{`:root { --invoice-primary-color: ${primaryColor}; --invoice-secondary-color: ${secondaryColor}; }`}</style>
|
||||||
{children}
|
{children}
|
||||||
@@ -76,14 +56,11 @@ interface PaperTemplateBigTitleProps {
|
|||||||
PaperTemplate.BigTitle = ({ title }: PaperTemplateBigTitleProps) => {
|
PaperTemplate.BigTitle = ({ title }: PaperTemplateBigTitleProps) => {
|
||||||
return (
|
return (
|
||||||
<x.h1
|
<x.h1
|
||||||
style={{
|
fontSize={'30px'}
|
||||||
fontSize: '30px',
|
margin={0}
|
||||||
margin: 0,
|
lineHeight={1}
|
||||||
lineHeight: 1,
|
fontWeight={500}
|
||||||
fontWeight: 500,
|
color={'#333'}
|
||||||
color: '#333',
|
|
||||||
}}
|
|
||||||
className={styles.bigTitle}
|
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</x.h1>
|
</x.h1>
|
||||||
@@ -96,15 +73,63 @@ interface PaperTemplateLogoProps {
|
|||||||
|
|
||||||
PaperTemplate.Logo = ({ logoUri }: PaperTemplateLogoProps) => {
|
PaperTemplate.Logo = ({ logoUri }: PaperTemplateLogoProps) => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.logoWrap}>
|
<x.div overflow={'hidden'}>
|
||||||
<img className={styles.logoImg} alt="" src={logoUri} />
|
<x.img
|
||||||
</div>
|
width={'100%'}
|
||||||
|
height={'100%'}
|
||||||
|
maxWidth={'260px'}
|
||||||
|
maxHeight={'100px'}
|
||||||
|
alt=""
|
||||||
|
src={logoUri}
|
||||||
|
/>
|
||||||
|
</x.div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
PaperTemplate.Table = ({ columns, data }: PaperTemplateTableProps) => {
|
PaperTemplate.Table = ({ columns, data }: PaperTemplateTableProps) => {
|
||||||
return (
|
return (
|
||||||
<table className={styles.table}>
|
<table
|
||||||
|
className={css`
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
thead th {
|
||||||
|
font-weight: 400;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
padding: 2px 10px;
|
||||||
|
color: #333;
|
||||||
|
|
||||||
|
&.rate,
|
||||||
|
&.total {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
&:first-of-type {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
&:last-of-type {
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
td {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{columns.map((col, index) => (
|
{columns.map((col, index) => (
|
||||||
@@ -115,7 +140,7 @@ PaperTemplate.Table = ({ columns, data }: PaperTemplateTableProps) => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody className={styles.tableBody}>
|
<tbody>
|
||||||
{data.map((_data: any) => (
|
{data.map((_data: any) => (
|
||||||
<tr>
|
<tr>
|
||||||
{columns.map((column, index) => (
|
{columns.map((column, index) => (
|
||||||
@@ -148,8 +173,16 @@ PaperTemplate.Totals = ({ children }: { children: React.ReactNode }) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</x.div>);
|
</x.div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const totalBottomBordered = css`
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
`;
|
||||||
|
const totalBottomGrayBordered = css`
|
||||||
|
border-bottom: 1px solid #dadada;
|
||||||
|
`;
|
||||||
PaperTemplate.TotalLine = ({
|
PaperTemplate.TotalLine = ({
|
||||||
label,
|
label,
|
||||||
amount,
|
amount,
|
||||||
@@ -165,34 +198,34 @@ PaperTemplate.TotalLine = ({
|
|||||||
<x.div
|
<x.div
|
||||||
display={'flex'}
|
display={'flex'}
|
||||||
padding={'4px 0'}
|
padding={'4px 0'}
|
||||||
|
className={clsx({
|
||||||
className={clsx(styles.totalsItem, {
|
[totalBottomBordered]: border === PaperTemplateTotalBorder.Dark,
|
||||||
[styles.totalBottomBordered]: border === PaperTemplateTotalBorder.Dark,
|
[totalBottomGrayBordered]: border === PaperTemplateTotalBorder.Gray,
|
||||||
[styles.totalBottomGrayBordered]:
|
|
||||||
border === PaperTemplateTotalBorder.Gray,
|
|
||||||
})}
|
})}
|
||||||
style={style}
|
|
||||||
>
|
>
|
||||||
<x.div min-w="160px">{label}</x.div>
|
<x.div min-w="160px">{label}</x.div>
|
||||||
<x.div flex={'1 1 auto'} textAlign={'right'}>{amount}</x.div>
|
<x.div flex={'1 1 auto'} textAlign={'right'}>
|
||||||
|
{amount}
|
||||||
|
</x.div>
|
||||||
</x.div>
|
</x.div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
PaperTemplate.MutedText = () => { };
|
|
||||||
|
|
||||||
PaperTemplate.Text = () => { };
|
|
||||||
|
|
||||||
PaperTemplate.AddressesGroup = (props: GroupProps) => {
|
PaperTemplate.AddressesGroup = (props: GroupProps) => {
|
||||||
return (
|
return (
|
||||||
<Group
|
<Group
|
||||||
spacing={10}
|
spacing={10}
|
||||||
align={'flex-start'}
|
align={'flex-start'}
|
||||||
{...props}
|
{...props}
|
||||||
className={styles.addressRoot}
|
className={css`
|
||||||
|
> div {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
PaperTemplate.Address = ({ children }: { children: React.ReactNode }) => {
|
PaperTemplate.Address = ({ children }: { children: React.ReactNode }) => {
|
||||||
return <Box>{children}</Box>;
|
return <Box>{children}</Box>;
|
||||||
};
|
};
|
||||||
@@ -205,22 +238,16 @@ PaperTemplate.Statement = ({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.paragraph}>
|
<x.div mb={'20px'}>
|
||||||
{label && <div className={styles.paragraphLabel}>{label}</div>}
|
{label && <x.div color={'#666'}>{label}</x.div>}
|
||||||
<div>{children}</div>
|
<x.div>{children}</x.div>
|
||||||
</div>
|
</x.div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
PaperTemplate.TermsList = ({ children }: { children: React.ReactNode }) => {
|
PaperTemplate.TermsList = ({ children }: { children: React.ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
<x.div
|
<x.div display={'flex'} flexDirection={'column'} gap={'4px'}>
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: '4px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</x.div>
|
</x.div>
|
||||||
);
|
);
|
||||||
@@ -234,9 +261,9 @@ PaperTemplate.TermsItem = ({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<x.div style={{ display: 'flex', flexDirection: 'row', gap: '12px' }}>
|
<Group spacing={12}>
|
||||||
<x.div style={{ minWidth: '120px', color: '#333' }}>{label}</x.div>
|
<x.div minWidth={'12px'} color={'#333'}>{label}</x.div>
|
||||||
<x.div>{children}</x.div>
|
<x.div>{children}</x.div>
|
||||||
</x.div>
|
</Group>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
import { CacheProvider, ThemeProvider } from '@emotion/react';
|
import { CacheProvider, ThemeProvider } from '@emotion/react';
|
||||||
import { EmotionCache } from '@emotion/cache';
|
import { EmotionCache } from '@emotion/cache';
|
||||||
import { defaultTheme } from '@xstyled/system';
|
import { defaultTheme } from '@xstyled/system';
|
||||||
|
import { Preflight } from '@xstyled/emotion';
|
||||||
|
|
||||||
const theme = {
|
const theme = {
|
||||||
...defaultTheme,
|
...defaultTheme,
|
||||||
};
|
};
|
||||||
export function PaperTemplateLayout({ cache, children }: {
|
export function PaperTemplateLayout({
|
||||||
|
cache,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
cache: EmotionCache;
|
cache: EmotionCache;
|
||||||
}) {
|
}) {
|
||||||
const html = (
|
return (
|
||||||
<CacheProvider value={cache}>
|
<CacheProvider value={cache}>
|
||||||
<ThemeProvider theme={theme}>{children}</ThemeProvider>
|
<ThemeProvider theme={theme}>
|
||||||
|
<Preflight />
|
||||||
|
{children}
|
||||||
|
</ThemeProvider>
|
||||||
</CacheProvider>
|
</CacheProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
|
||||||
<html lang="en">
|
|
||||||
<body>
|
|
||||||
<div id="root">{html}</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './components/PaperTemplate';
|
export * from './components/PaperTemplate';
|
||||||
export * from './components/InvoicePaperTemplate';
|
export * from './components/InvoicePaperTemplate';
|
||||||
|
export * from './renders/render-invoice-paper-template';
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { renderToString } from 'react-dom/server';
|
||||||
|
import createCache from '@emotion/cache';
|
||||||
|
import {
|
||||||
|
InvoicePaperTemplate,
|
||||||
|
InvoicePaperTemplateProps,
|
||||||
|
} from '../components/InvoicePaperTemplate';
|
||||||
|
import { PaperTemplateLayout } from '../components/PaperTemplateLayout';
|
||||||
|
import { extractCritical } from '@emotion/server';
|
||||||
|
|
||||||
|
export const renderInvoicePaperTemplateHtml = (
|
||||||
|
props: InvoicePaperTemplateProps
|
||||||
|
) => {
|
||||||
|
const key = 'invoice-paper-template';
|
||||||
|
const cache = createCache({ key });
|
||||||
|
|
||||||
|
const renderedHtml = renderToString(
|
||||||
|
<PaperTemplateLayout cache={cache}>
|
||||||
|
<InvoicePaperTemplate {...props} />
|
||||||
|
</PaperTemplateLayout>
|
||||||
|
);
|
||||||
|
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>`;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user