From 0438c30c97bba2110fd536bf0a7af5e66a326a14 Mon Sep 17 00:00:00 2001 From: Suvodip Date: Mon, 31 Mar 2025 13:55:35 +0530 Subject: [PATCH] last work on invoice generation --- package-lock.json | 107 ++++++++++++ package.json | 3 + src/components/PrintInvoice.tsx | 273 +++++++++++++++++++++++++++++++ src/components/UserProfile.tsx | 72 +------- src/components/ui/container.tsx | 16 ++ src/components/ui/table.tsx | 38 +++++ src/components/ui/typography.tsx | 42 +++++ src/pages/print-invoice.astro | 7 + yarn.lock | 59 ++++++- 9 files changed, 545 insertions(+), 72 deletions(-) create mode 100644 src/components/PrintInvoice.tsx create mode 100644 src/components/ui/container.tsx create mode 100644 src/components/ui/table.tsx create mode 100644 src/components/ui/typography.tsx create mode 100644 src/pages/print-invoice.astro diff --git a/package-lock.json b/package-lock.json index 3f13ceb..4f37710 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@astrojs/tailwind": "^6.0.0", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-select": "^2.1.6", + "@radix-ui/react-toast": "^1.2.6", "@shadcn/ui": "^0.0.4", "@types/react": "^19.0.12", "astro": "^5.5.2", @@ -21,6 +22,8 @@ "lucide-react": "^0.484.0", "pocketbase": "^0.25.2", "postcss": "^8.5.3", + "react-router-dom": "^7.4.1", + "react-to-print": "^3.0.5", "tailwind-merge": "^3.0.2", "tailwindcss": "^3.4.17", "tailwindcss-animate": "^1.0.7" @@ -1794,6 +1797,40 @@ } } }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.6.tgz", + "integrity": "sha512-gN4dpuIVKEgpLn1z5FhzT9mYRUitbfZq9XqN/7kkBMUgFTzTG8x/KszWJugJXHcwxckY8xcKDZPz7kG3o6DsUA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.5", + "@radix-ui/react-portal": "1.1.4", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", @@ -6007,6 +6044,55 @@ } } }, + "node_modules/react-router": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.4.1.tgz", + "integrity": "sha512-Vmizn9ZNzxfh3cumddqv3kLOKvc7AskUT0dC1prTabhiEi0U4A33LmkDOJ79tXaeSqCqMBXBU/ySX88W85+EUg==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.4.1.tgz", + "integrity": "sha512-L3/4tig0Lvs6m6THK0HRV4eHUdpx0dlJasgCxXKnavwhh4tKYgpuZk75HRYNoRKDyDWi9QgzGXsQ1oQSBlWpAA==", + "license": "MIT", + "dependencies": { + "react-router": "7.4.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/react-style-singleton": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", @@ -6029,6 +6115,15 @@ } } }, + "node_modules/react-to-print": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/react-to-print/-/react-to-print-3.0.5.tgz", + "integrity": "sha512-Z15MwMOzYCHWi26CZeFNwflAg7Nr8uWD6FTj+EkfIOjYyjr0MXGbI0c7rF4Fgrbj3XG9hFndb1ourxpPz2RAiA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ~19" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -6463,6 +6558,12 @@ "node": ">=10" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/sharp": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", @@ -7005,6 +7106,12 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, "node_modules/type-fest": { "version": "4.37.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.37.0.tgz", diff --git a/package.json b/package.json index aba8c08..303d8c6 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@astrojs/tailwind": "^6.0.0", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-select": "^2.1.6", + "@radix-ui/react-toast": "^1.2.6", "@shadcn/ui": "^0.0.4", "@types/react": "^19.0.12", "astro": "^5.5.2", @@ -23,6 +24,8 @@ "lucide-react": "^0.484.0", "pocketbase": "^0.25.2", "postcss": "^8.5.3", + "react-router-dom": "^7.4.1", + "react-to-print": "^3.0.5", "tailwind-merge": "^3.0.2", "tailwindcss": "^3.4.17", "tailwindcss-animate": "^1.0.7" diff --git a/src/components/PrintInvoice.tsx b/src/components/PrintInvoice.tsx new file mode 100644 index 0000000..0ec8055 --- /dev/null +++ b/src/components/PrintInvoice.tsx @@ -0,0 +1,273 @@ +import React, { useEffect, useState } from "react"; + +interface Invoice { + invoice_id: number; + invoice_number: string; + customer_id: string; + invoice_date: string; + due_date: string; + subtotal: number; + tax_amount: number; + total_amount: number; + status: string; + payment_terms: string | null; + notes: string; + created_at: string; + updated_at: string; +} + +export default function PrintInvoice() { + const [invoiceList, setInvoiceList] = useState([]); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(true); + const [selectedInvoice, setSelectedInvoice] = useState(null); + + useEffect(() => { + const getInvoiceListData = async () => { + try { + const response = await fetch('http://localhost:2058/host-api/v1/invoice/invoice-info/', { + method: 'GET', + credentials: 'include', + headers: { 'Accept': 'application/json' } + }); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const data = await response.json(); + + if (!data.success) { + throw new Error(data.message || 'Session fetch failed'); + } + + setInvoiceList(data.data); + } catch (error: any) { + console.error('Fetch error:', error); + setError(error.message); + } finally { + setLoading(false); + } + }; + + getInvoiceListData(); + }, []); + + const handlePrint = () => { + window.print(); + } + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + }); + } + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD' + }).format(amount); + } + + const viewInvoiceDetails = (invoice: Invoice) => { + setSelectedInvoice(invoice); + } + + const closeInvoiceDetails = () => { + setSelectedInvoice(null); + } + + if (error) { + return
Error: {error}
; + } + + if (loading) { + return
Loading invoice data...
; + } + + return ( +
+ {/* Invoice List */} +
+

Invoices

+
+ + + + + + + + + + + + + {invoiceList.map((invoice) => ( + + + + + + + + + ))} + +
Invoice #DateDue DateAmountStatusActions
{invoice.invoice_number}{formatDate(invoice.invoice_date)}{formatDate(invoice.due_date)}{formatCurrency(invoice.total_amount)} + + {invoice.status} + + + + +
+
+
+ + {/* Invoice Detail Modal */} + {selectedInvoice && ( +
+
+ {/* Printable Invoice */} +
+
+
+

INVOICE

+

{selectedInvoice.invoice_number}

+
+
+

Status: + {selectedInvoice.status.toUpperCase()} +

+

Date: {formatDate(selectedInvoice.invoice_date)}

+

Due: {formatDate(selectedInvoice.due_date)}

+
+
+ +
+

From

+

Your Company Name

+

123 Business Street

+

City, State 12345

+

contact@yourcompany.com

+
+ +
+

Bill To

+

Customer ID: {selectedInvoice.customer_id}

+

[Customer Name]

+

[Customer Address]

+

[Customer Email]

+
+ +
+
+
Description
+
Subtotal
+
Tax
+
Total
+
+
+ +
+
+
[Service/Product Description]
+
{formatCurrency(selectedInvoice.subtotal)}
+
{formatCurrency(selectedInvoice.tax_amount)}
+
{formatCurrency(selectedInvoice.total_amount)}
+
+
+ +
+
+
+ Subtotal: + {formatCurrency(selectedInvoice.subtotal)} +
+
+ Tax: + {formatCurrency(selectedInvoice.tax_amount)} +
+
+ Total: + {formatCurrency(selectedInvoice.total_amount)} +
+
+
+ + {selectedInvoice.notes && ( +
+

Notes

+

{selectedInvoice.notes}

+
+ )} + +
+

Thank you for your business!

+
+
+ +
+ + +
+
+
+ )} + + {/* Print Styles */} + +
+ ); +} \ No newline at end of file diff --git a/src/components/UserProfile.tsx b/src/components/UserProfile.tsx index 95ff65f..ec1d211 100644 --- a/src/components/UserProfile.tsx +++ b/src/components/UserProfile.tsx @@ -88,10 +88,6 @@ export default function ProfilePage() { } return (
- {/*
-

Profile

-

Update your profile settings.

-
*/}
@@ -128,15 +124,6 @@ export default function ProfilePage() {
- {/*
- -