[5d6f37a] | 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
|
---|
[057453c] | 7 | import { Invoice } from 'src/schemas';
|
---|
[5d6f37a] | 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,
|
---|
[87c9f1e] | 115 | issueDate,
|
---|
[5d6f37a] | 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>
|
---|
[87c9f1e] | 181 | <Text style={styles.body2}>{fDate(issueDate)}</Text>
|
---|
[5d6f37a] | 182 | </View>
|
---|
| 183 | <View style={styles.col6}>
|
---|
| 184 | <Text style={[styles.subtitle1, styles.mb4]}>Due date</Text>
|
---|
[057453c] | 185 | <Text style={styles.body2}>{fDate(dueDate)}</Text>
|
---|
[5d6f37a] | 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 | }
|
---|