1 | import { useMemo } from 'react';
|
---|
2 | import { Page, View, Text, Image, Document, Font, StyleSheet } from '@react-pdf/renderer';
|
---|
3 | // utils
|
---|
4 | import { fDate } from 'src/utils/format-time';
|
---|
5 | import { fCurrency } from 'src/utils/format-number';
|
---|
6 | // types
|
---|
7 | import { Invoice } from 'src/schemas';
|
---|
8 | import { createFullAddress } from 'src/utils/create-full-address';
|
---|
9 | import { getQuantityType } from 'src/utils/get-invoice-quantity-type';
|
---|
10 | // import signatureImage from 'src/assets/png/signature.png';
|
---|
11 |
|
---|
12 | // ----------------------------------------------------------------------
|
---|
13 |
|
---|
14 | Font.register({
|
---|
15 | family: 'Roboto',
|
---|
16 | fonts: [{ src: '/fonts/Roboto-Regular.ttf' }, { src: '/fonts/Roboto-Bold.ttf' }],
|
---|
17 | });
|
---|
18 |
|
---|
19 | const useStyles = () =>
|
---|
20 | useMemo(
|
---|
21 | () =>
|
---|
22 | StyleSheet.create({
|
---|
23 | col4: { width: '25%' },
|
---|
24 | col8: { width: '75%' },
|
---|
25 | col6: { width: '50%' },
|
---|
26 | mb4: { marginBottom: 4 },
|
---|
27 | mb8: { marginBottom: 8 },
|
---|
28 | mb40: { marginBottom: 40 },
|
---|
29 | h3: { fontSize: 16, fontWeight: 700 },
|
---|
30 | h4: { fontSize: 13, fontWeight: 700 },
|
---|
31 | body1: { fontSize: 11 },
|
---|
32 | body2: { fontSize: 10 },
|
---|
33 | subtitle1: { fontSize: 11, fontWeight: 700 },
|
---|
34 | subtitle2: { fontSize: 10, fontWeight: 700 },
|
---|
35 | alignRight: { textAlign: 'right' },
|
---|
36 | page: {
|
---|
37 | fontSize: 9,
|
---|
38 | lineHeight: 1.6,
|
---|
39 | fontFamily: 'Roboto',
|
---|
40 | backgroundColor: '#FFFFFF',
|
---|
41 | textTransform: 'capitalize',
|
---|
42 | padding: '40px 24px 120px 24px',
|
---|
43 | },
|
---|
44 | footer: {
|
---|
45 | left: 0,
|
---|
46 | right: 0,
|
---|
47 | bottom: 0,
|
---|
48 | padding: 24,
|
---|
49 | margin: 'auto',
|
---|
50 | borderTopWidth: 1,
|
---|
51 | borderStyle: 'solid',
|
---|
52 | position: 'absolute',
|
---|
53 | borderColor: '#DFE3E8',
|
---|
54 | },
|
---|
55 | signatures: {
|
---|
56 | left: 0,
|
---|
57 | right: 0,
|
---|
58 | bottom: 0,
|
---|
59 | padding: 24,
|
---|
60 | margin: 'auto',
|
---|
61 | borderStyle: 'solid',
|
---|
62 | position: 'absolute',
|
---|
63 | },
|
---|
64 | gridContainer: {
|
---|
65 | flexDirection: 'row',
|
---|
66 | justifyContent: 'space-between',
|
---|
67 | },
|
---|
68 | table: {
|
---|
69 | display: 'flex',
|
---|
70 | width: 'auto',
|
---|
71 | },
|
---|
72 | tableHead: {
|
---|
73 | padding: '5px 5px',
|
---|
74 | flexDirection: 'row',
|
---|
75 | },
|
---|
76 | tableRow: {
|
---|
77 | padding: '5px 5px',
|
---|
78 | flexDirection: 'row',
|
---|
79 | borderBottomWidth: 1,
|
---|
80 | borderStyle: 'solid',
|
---|
81 | borderColor: '#DFE3E8',
|
---|
82 | },
|
---|
83 | noBorder: {
|
---|
84 | paddingTop: 8,
|
---|
85 | paddingBottom: 0,
|
---|
86 | borderBottomWidth: 0,
|
---|
87 | },
|
---|
88 | tableCell_1: {
|
---|
89 | width: '5%',
|
---|
90 | },
|
---|
91 | tableCell_2: {
|
---|
92 | width: '50%',
|
---|
93 | paddingRight: 16,
|
---|
94 | },
|
---|
95 | tableCell_3: {
|
---|
96 | width: '15%',
|
---|
97 | },
|
---|
98 | }),
|
---|
99 | []
|
---|
100 | );
|
---|
101 |
|
---|
102 | // ----------------------------------------------------------------------
|
---|
103 |
|
---|
104 | type Props = {
|
---|
105 | invoice: Invoice;
|
---|
106 | currentStatus: string;
|
---|
107 | };
|
---|
108 |
|
---|
109 | export default function InvoiceMKPDF({ invoice, currentStatus }: Props) {
|
---|
110 | const {
|
---|
111 | items,
|
---|
112 | dueDate,
|
---|
113 | discount,
|
---|
114 | invoiceTo,
|
---|
115 | issueDate,
|
---|
116 | totalAmount,
|
---|
117 | invoiceFrom,
|
---|
118 | invoiceNumber,
|
---|
119 | subTotal,
|
---|
120 | currency,
|
---|
121 | quantityType,
|
---|
122 | } = invoice;
|
---|
123 |
|
---|
124 | const styles = useStyles();
|
---|
125 |
|
---|
126 | return (
|
---|
127 | <Document>
|
---|
128 | <Page size="A4" style={styles.page}>
|
---|
129 | <View style={[styles.gridContainer, styles.mb40]}>
|
---|
130 | <Image source="/logo/logo_single.png" style={{ width: 48, height: 48 }} />
|
---|
131 |
|
---|
132 | <View style={{ alignItems: 'flex-end', flexDirection: 'column' }}>
|
---|
133 | <Text style={styles.h3}>Invoice No. {invoiceNumber}</Text>
|
---|
134 | <Text>{invoiceFrom.name}</Text>
|
---|
135 | <Text>VAT Number: 4057020552553</Text>
|
---|
136 | <Text>IBAN: MK07210701001535321</Text>
|
---|
137 | <Text>SWIFT: TUTNMK22</Text>
|
---|
138 | </View>
|
---|
139 | </View>
|
---|
140 |
|
---|
141 | <View style={[styles.gridContainer, styles.mb40, { gap: 16 }]}>
|
---|
142 | <View
|
---|
143 | style={[
|
---|
144 | styles.col6,
|
---|
145 | { border: 1, borderColor: '#DFE3E8', borderRadius: 5, padding: 4 },
|
---|
146 | ]}
|
---|
147 | >
|
---|
148 | <Text style={[styles.subtitle1, styles.mb4]}>Invoice from</Text>
|
---|
149 | <Text style={[styles.body2, { fontWeight: 700 }]}>{invoiceFrom.name}</Text>
|
---|
150 | <Text style={styles.body2}>{createFullAddress(invoiceFrom.address)}</Text>
|
---|
151 | <Text style={styles.body2}>{invoiceFrom.phoneNumber}</Text>
|
---|
152 | <View style={{ marginTop: 5 }} />
|
---|
153 | <Text style={{ ...styles.body2, textTransform: 'lowercase' }}>{invoiceFrom.email}</Text>
|
---|
154 | </View>
|
---|
155 |
|
---|
156 | <View
|
---|
157 | style={[
|
---|
158 | styles.col6,
|
---|
159 | { border: 1, borderColor: '#DFE3E8', borderRadius: 5, padding: 4 },
|
---|
160 | ]}
|
---|
161 | >
|
---|
162 | <Text style={[styles.subtitle1, styles.mb4]}>Invoice to</Text>
|
---|
163 | <Text style={[styles.body2, { fontWeight: 700 }]}>{invoiceTo.name}</Text>
|
---|
164 | {invoiceTo.companyId && (
|
---|
165 | <Text style={[styles.body2]}>Company ID: {invoiceTo.companyId}</Text>
|
---|
166 | )}
|
---|
167 | <Text style={styles.body2}>{createFullAddress(invoiceTo.address)}</Text>
|
---|
168 | <Text style={styles.body2}>{invoiceTo.phoneNumber}</Text>
|
---|
169 | <View style={{ marginTop: 5 }} />
|
---|
170 | <Text style={{ ...styles.body2, textTransform: 'lowercase' }}>{invoiceTo.email}</Text>
|
---|
171 | </View>
|
---|
172 | </View>
|
---|
173 |
|
---|
174 | <View style={[styles.gridContainer, styles.mb40]}>
|
---|
175 | <View style={styles.col6}>
|
---|
176 | <Text style={[styles.subtitle1, styles.mb4]}>Invoice Number</Text>
|
---|
177 | <Text style={styles.body2}>{invoiceNumber}</Text>
|
---|
178 | </View>
|
---|
179 | <View style={styles.col6}>
|
---|
180 | <Text style={[styles.subtitle1, styles.mb4]}>Date Issued</Text>
|
---|
181 | <Text style={styles.body2}>{fDate(issueDate)}</Text>
|
---|
182 | </View>
|
---|
183 | <View style={styles.col6}>
|
---|
184 | <Text style={[styles.subtitle1, styles.mb4]}>Due date</Text>
|
---|
185 | <Text style={styles.body2}>{fDate(dueDate)}</Text>
|
---|
186 | </View>
|
---|
187 | </View>
|
---|
188 |
|
---|
189 | <Text style={[styles.subtitle1, styles.mb8]}>Invoice Details</Text>
|
---|
190 |
|
---|
191 | <View style={styles.table}>
|
---|
192 | <View>
|
---|
193 | <View style={[styles.tableHead, { backgroundColor: 'black', color: 'white' }]}>
|
---|
194 | <View style={styles.tableCell_1}>
|
---|
195 | <Text style={styles.subtitle1}>#</Text>
|
---|
196 | </View>
|
---|
197 |
|
---|
198 | <View style={styles.tableCell_2}>
|
---|
199 | <Text style={styles.subtitle1}>Description</Text>
|
---|
200 | </View>
|
---|
201 |
|
---|
202 | <View style={styles.tableCell_3}>
|
---|
203 | <Text style={styles.subtitle1}>{getQuantityType(invoice)}</Text>
|
---|
204 | </View>
|
---|
205 |
|
---|
206 | <View style={styles.tableCell_3}>
|
---|
207 | <Text style={styles.subtitle1}>Unit price</Text>
|
---|
208 | </View>
|
---|
209 |
|
---|
210 | <View style={[styles.tableCell_3, styles.alignRight]}>
|
---|
211 | <Text style={styles.subtitle1}>Total</Text>
|
---|
212 | </View>
|
---|
213 | </View>
|
---|
214 | </View>
|
---|
215 |
|
---|
216 | <View>
|
---|
217 | {items.map((item, index) => (
|
---|
218 | <View style={styles.tableRow} key={item.title}>
|
---|
219 | <View style={styles.tableCell_1}>
|
---|
220 | <Text>{index + 1}</Text>
|
---|
221 | </View>
|
---|
222 |
|
---|
223 | <View style={styles.tableCell_2}>
|
---|
224 | <Text style={styles.subtitle2}>{item.title}</Text>
|
---|
225 | <Text>{item.description}</Text>
|
---|
226 | </View>
|
---|
227 |
|
---|
228 | <View style={styles.tableCell_3}>
|
---|
229 | <Text>{item.quantity}</Text>
|
---|
230 | </View>
|
---|
231 |
|
---|
232 | <View style={styles.tableCell_3}>
|
---|
233 | <Text>{item.price}</Text>
|
---|
234 | </View>
|
---|
235 |
|
---|
236 | <View style={[styles.tableCell_3, styles.alignRight]}>
|
---|
237 | <Text>{fCurrency(item.price * item.quantity, currency)}</Text>
|
---|
238 | </View>
|
---|
239 | </View>
|
---|
240 | ))}
|
---|
241 |
|
---|
242 | <View style={[styles.tableRow, styles.noBorder]}>
|
---|
243 | <View style={styles.tableCell_1} />
|
---|
244 | <View style={styles.tableCell_2} />
|
---|
245 | <View style={styles.tableCell_3} />
|
---|
246 | <View style={styles.tableCell_3}>
|
---|
247 | <Text>Subtotal</Text>
|
---|
248 | </View>
|
---|
249 | <View style={[styles.tableCell_3, styles.alignRight]}>
|
---|
250 | <Text>{fCurrency(subTotal, currency)}</Text>
|
---|
251 | </View>
|
---|
252 | </View>
|
---|
253 |
|
---|
254 | {!!discount && (
|
---|
255 | <View style={[styles.tableRow, styles.noBorder]}>
|
---|
256 | <View style={styles.tableCell_1} />
|
---|
257 | <View style={styles.tableCell_2} />
|
---|
258 | <View style={styles.tableCell_3} />
|
---|
259 | <View style={styles.tableCell_3}>
|
---|
260 | <Text>Discount</Text>
|
---|
261 | </View>
|
---|
262 | <View style={[styles.tableCell_3, styles.alignRight]}>
|
---|
263 | <Text>{fCurrency(-discount, currency)}</Text>
|
---|
264 | </View>
|
---|
265 | </View>
|
---|
266 | )}
|
---|
267 |
|
---|
268 | {/* <View style={[styles.tableRow, styles.noBorder]}>
|
---|
269 | <View style={styles.tableCell_1} />
|
---|
270 | <View style={styles.tableCell_2} />
|
---|
271 | <View style={styles.tableCell_3} />
|
---|
272 | <View style={styles.tableCell_3}>
|
---|
273 | <Text>Taxes</Text>
|
---|
274 | </View>
|
---|
275 | <View style={[styles.tableCell_3, styles.alignRight]}>
|
---|
276 | <Text>{fCurrency(taxes)}</Text>
|
---|
277 | </View>
|
---|
278 | </View> */}
|
---|
279 |
|
---|
280 | <View style={[styles.tableRow, styles.noBorder]}>
|
---|
281 | <View style={styles.tableCell_1} />
|
---|
282 | <View style={styles.tableCell_2} />
|
---|
283 | <View style={styles.tableCell_3} />
|
---|
284 | <View style={styles.tableCell_3}>
|
---|
285 | <Text style={styles.h4}>Total</Text>
|
---|
286 | </View>
|
---|
287 | <View style={[styles.tableCell_3, styles.alignRight]}>
|
---|
288 | <Text style={styles.h4}>{fCurrency(totalAmount, currency)}</Text>
|
---|
289 | </View>
|
---|
290 | </View>
|
---|
291 | </View>
|
---|
292 | </View>
|
---|
293 |
|
---|
294 | {/* Add this block for signature lines */}
|
---|
295 | <View style={[styles.gridContainer, styles.signatures, { marginBottom: 75 }]} fixed>
|
---|
296 | <View style={[styles.gridContainer, { justifyContent: 'space-between' }]}>
|
---|
297 | <View style={{ alignItems: 'flex-start', width: '50%' }}>
|
---|
298 | {/* <Image
|
---|
299 | source={signatureImage.src}
|
---|
300 | style={{ width: 100, position: 'absolute', bottom: 20 }}
|
---|
301 | /> */}
|
---|
302 | <View
|
---|
303 | style={{
|
---|
304 | marginTop: 4,
|
---|
305 | borderTopWidth: 1,
|
---|
306 | borderStyle: 'solid',
|
---|
307 | borderColor: '#000',
|
---|
308 | width: '65%',
|
---|
309 | }}
|
---|
310 | />
|
---|
311 | <Text style={{ marginTop: 16, fontSize: 11 }}>{invoiceFrom.representative}</Text>
|
---|
312 | <Text style={{ fontSize: 11, fontWeight: 700 }}>{invoiceFrom.name}</Text>
|
---|
313 | </View>
|
---|
314 | <View style={{ alignItems: 'flex-end', width: '50%' }}>
|
---|
315 | <View
|
---|
316 | style={{
|
---|
317 | marginTop: 4,
|
---|
318 | borderTopWidth: 1,
|
---|
319 | borderStyle: 'solid',
|
---|
320 | borderColor: '#000',
|
---|
321 | width: '65%',
|
---|
322 | }}
|
---|
323 | />
|
---|
324 | <Text style={{ marginTop: 16, fontSize: 11 }}>{invoiceTo.representative}</Text>
|
---|
325 | <Text style={{ fontSize: 11, fontWeight: 700 }}>{invoiceTo.name}</Text>
|
---|
326 | </View>
|
---|
327 | </View>
|
---|
328 | </View>
|
---|
329 |
|
---|
330 | <View style={[styles.gridContainer, styles.footer]} fixed>
|
---|
331 | <View style={styles.col8}>
|
---|
332 | <Text style={styles.subtitle2}>NOTES</Text>
|
---|
333 | <Text>
|
---|
334 | VAT is not calculated according to article 14, paragraph 3, point 5 from the VAT act.
|
---|
335 | </Text>
|
---|
336 | </View>
|
---|
337 | <View style={[styles.col4, styles.alignRight]}>
|
---|
338 | <Text style={[styles.subtitle2]}>Have a question?</Text>
|
---|
339 | <Text style={{ textTransform: 'lowercase' }}>{invoiceFrom.email}</Text>
|
---|
340 | </View>
|
---|
341 | </View>
|
---|
342 | </Page>
|
---|
343 | </Document>
|
---|
344 | );
|
---|
345 | }
|
---|