From 94c9c0e87db6d878d1488ebeff60d728a89fc12d Mon Sep 17 00:00:00 2001 From: "mihailo.vojinovic" Date: Wed, 4 Feb 2026 17:45:38 +0100 Subject: Add invoice generator component --- src/components/invoice-generator.tsx | 131 +++++++++++++++++++++++++++++++++++ src/components/ui/button.tsx | 13 ++++ src/components/ui/input.tsx | 13 ++++ 3 files changed, 157 insertions(+) create mode 100644 src/components/invoice-generator.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/input.tsx (limited to 'src/components') diff --git a/src/components/invoice-generator.tsx b/src/components/invoice-generator.tsx new file mode 100644 index 0000000..d1552c1 --- /dev/null +++ b/src/components/invoice-generator.tsx @@ -0,0 +1,131 @@ +"use client"; + +import { Invoice, InvoiceSchema } from "@/models/invoice"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useFieldArray, useForm, useWatch } from "react-hook-form"; +import { Input } from "./ui/input"; +import React from "react"; +import { cn } from "@/utils/cn"; +import { Button } from "./ui/button"; + +const defaultItem = { + description: "", + quantity: 1, + rate: 0, +}; + +const defaultValues: Invoice = { + invoice_number: 0, + from: "", + line_items: [defaultItem], + currency: "USD", +}; + +export const InvoiceGenerator = () => { + const { + register, + handleSubmit, + control, + watch, + formState: { errors }, + } = useForm({ + resolver: zodResolver(InvoiceSchema), + defaultValues, + }); + + const { fields, append, remove } = useFieldArray({ + control, + name: "line_items", + }); + const lineItems = useWatch({ control, name: "line_items" }); + const currency = watch("currency", "USD"); + + return ( +
+
+
+ + {errors["from"] && ( +

{errors["from"].message ?? "Error"}

+ )} +
+ + +
+
+ + +
+
+
+ + + + + + + + +
+
+

Your Items

+
+

Item

+

Quantity

+

Rate

+

Amount

+

Actions

+
+
+ {fields.map((f, ind) => ( + + + + +

+ {(lineItems[ind]?.rate ?? 0) * (lineItems[ind]?.quantity ?? 0)}{" "} + {currency} +

+ +
+ ))} + +
+
+ Subtotal:{" "} + {lineItems.reduce( + (acc, cur) => acc + (cur.rate ?? 0) * (cur.quantity ?? 0), + 0, + )}{" "} + {currency} +
+
+ ); +}; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..32b9aac --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,13 @@ +import { cn } from "@/utils/cn"; + +export const Button = ({ + className, + ...props +}: React.ComponentPropsWithRef<"button">) => { + return ( +