15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Invoice Generator
2 |
3 | Quick and free invoice generator created with Svelte.
4 |
5 | See the live version at .
6 |
7 | ## Building
8 |
9 | Invoice Generator uses npm scripts for building and testing, so first, make sure npm is installed. To start a local development server with hot reloading run `npm run dev`. To create a production version ready for deployment run `npm run build`.
10 |
11 | ## Code Checking
12 |
13 | Code checking is performed with `npm run check` and code formatting with `npm run clean`.
14 |
--------------------------------------------------------------------------------
/src/lib/InvoiceInfoSection.ts:
--------------------------------------------------------------------------------
1 | import type PDFSection from './PDFSection';
2 | import PDFWriter from './PDFWriter';
3 |
4 | export default class InvoiceInfoSection implements PDFSection {
5 | number = 'INV-001';
6 | date = new Date().toISOString().split('T')[0];
7 | due = '';
8 |
9 | static labels = {
10 | number: 'Invoice #',
11 | date: 'Invoice Date',
12 | due: 'Due Date'
13 | };
14 |
15 | addTo(writer: PDFWriter, x: number) {
16 | (['number', 'date', 'due'] as const).forEach((key) => {
17 | if (!this[key]) return;
18 |
19 | writer
20 | .addTextCell(InvoiceInfoSection.labels[key], x)
21 | .addTextCell(this[key], x + 30)
22 | .finishTextRow()
23 | .moveDown(3);
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/lib/Address.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "invoice-generator",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview",
10 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
11 | "format": "prettier --write ."
12 | },
13 | "devDependencies": {
14 | "@sveltejs/adapter-static": "^3.0.1",
15 | "@sveltejs/kit": "^2.5.5",
16 | "@sveltejs/vite-plugin-svelte": "^3.0.1",
17 | "@tsconfig/svelte": "^5.0.2",
18 | "prettier": "^3.1.1",
19 | "prettier-plugin-svelte": "^3.1.2",
20 | "svelte": "^4.2.8",
21 | "svelte-check": "^3.6.2",
22 | "tslib": "^2.5.0",
23 | "typescript": "^5.3.3",
24 | "vite": "^5.0.11"
25 | },
26 | "dependencies": {
27 | "jspdf": "^2.5.1"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from '@sveltejs/adapter-static';
2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | export default {
6 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess
7 | // for more information about preprocessors
8 | preprocess: [vitePreprocess({})],
9 |
10 |
11 | kit: {
12 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
13 | // If your environment is not supported or you settled on a specific environment, switch out the adapter.
14 | // See https://kit.svelte.dev/docs/adapters for more information about adapters.
15 | adapter: adapter({
16 | // default options are shown. On some platforms
17 | // these options are set automatically — see below
18 | pages: 'dist',
19 | assets: 'dist',
20 | fallback: undefined,
21 | precompress: false,
22 | strict: true
23 | })
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/src/lib/ItemTableTotals.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
Sub-Total
11 |
12 |
13 |
14 |
15 |
16 | Tax (%)
22 |
23 |
24 |
25 |
26 |
27 |
Total
28 |
29 |
30 |
31 |
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Brandon Fowler
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/lib/AddressSection.ts:
--------------------------------------------------------------------------------
1 | import type PDFSection from './PDFSection';
2 | import PDFWriter from './PDFWriter';
3 |
4 | export default class AddressSection implements PDFSection {
5 | static ORDER = ['name', 'street', 'street2', 'city'] as const;
6 |
7 | name = '';
8 | street = '';
9 | street2 = '';
10 | city = '';
11 | storageKey?: string;
12 |
13 | constructor(
14 | public title: string,
15 | public save = false
16 | ) {
17 | this.storageKey = this.save
18 | ? this.title.toLowerCase().replace(/ /g, '-')
19 | : undefined;
20 |
21 | AddressSection.ORDER.forEach((key) => {
22 | if (!this.storageKey) this[key] = '';
23 | this[key] =
24 | globalThis.localStorage?.getItem(`invoice-${this.storageKey}-${key}`) ?? '';
25 | });
26 | }
27 |
28 | onChange(key: (typeof AddressSection.ORDER)[number]): void {
29 | if (this.storageKey)
30 | globalThis.localStorage?.setItem(`invoice-${this.storageKey}-${key}`, this[key]);
31 | }
32 |
33 | addTo(writer: PDFWriter, x: number) {
34 | const entries = AddressSection.ORDER.map((key) => this[key]).filter(
35 | (str) => str
36 | );
37 |
38 | if (entries.length) {
39 | writer.addText(this.title, x, { bold: true }).moveDown(3);
40 | }
41 |
42 | entries.forEach((str) => {
43 | writer.addText(str, x).moveDown(3);
44 | });
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/lib/ItemTable.svelte:
--------------------------------------------------------------------------------
1 |
20 |
21 |