#
tokens: 47247/50000 85/85 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── components.json
├── eslint.config.mjs
├── jsconfig.json
├── middleware.js
├── next.config.mjs
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── public
│   ├── file.svg
│   ├── globe.svg
│   ├── logo.png
│   ├── next.svg
│   ├── qris.png
│   ├── vercel.svg
│   └── window.svg
├── README.md
└── src
    ├── app
    │   ├── _components
    │   │   └── LoginForm.jsx
    │   ├── api
    │   │   └── logout
    │   │       └── route.js
    │   ├── dashboard
    │   │   ├── invoices
    │   │   │   ├── _components
    │   │   │   │   ├── DeleteInvoiceModal.jsx
    │   │   │   │   ├── InvoiceDownloadModal.jsx
    │   │   │   │   ├── InvoicePreview.jsx
    │   │   │   │   └── StatusCombobox.jsx
    │   │   │   ├── [invoiceNumber]
    │   │   │   │   └── page.jsx
    │   │   │   ├── create
    │   │   │   │   ├── _components
    │   │   │   │   │   ├── ProductsCombobox.jsx
    │   │   │   │   │   └── SizeCombobox.jsx
    │   │   │   │   ├── CreateInvoicePage.jsx
    │   │   │   │   └── page.js
    │   │   │   ├── InvoicePage.jsx
    │   │   │   ├── page.js
    │   │   │   ├── Table.jsx
    │   │   │   └── UpdateInvoiceForm.jsx
    │   │   ├── layout.jsx
    │   │   ├── page.jsx
    │   │   ├── products
    │   │   │   ├── _components
    │   │   │   │   ├── ProductDeleteModal.jsx
    │   │   │   │   ├── ProductEditModal.jsx
    │   │   │   │   ├── ProductModal.jsx
    │   │   │   │   ├── ProductModalButton.jsx
    │   │   │   │   └── ProductTable.jsx
    │   │   │   ├── page.js
    │   │   │   └── ProductPage.jsx
    │   │   └── size-pricing
    │   │       ├── _components
    │   │       │   ├── AddSizeButton.jsx
    │   │       │   ├── AddSizeModal.jsx
    │   │       │   ├── DeleteSizeModal.jsx
    │   │       │   ├── EditSizeModal.jsx
    │   │       │   └── Table.jsx
    │   │       ├── page.js
    │   │       └── SizePage.jsx
    │   ├── favicon.ico
    │   ├── globals.css
    │   ├── layout.js
    │   ├── not-found.js
    │   ├── page.js
    │   └── unauthorized.js
    ├── components
    │   ├── dashboard
    │   │   ├── DatePicker.jsx
    │   │   ├── Modal.jsx
    │   │   └── Sidebar.jsx
    │   └── ui
    │       ├── badge.jsx
    │       ├── button.jsx
    │       ├── calendar.jsx
    │       ├── card.jsx
    │       ├── command.jsx
    │       ├── dialog.jsx
    │       ├── drawer.jsx
    │       ├── input.jsx
    │       ├── label.jsx
    │       ├── popover.jsx
    │       ├── radio-group.jsx
    │       ├── select.jsx
    │       └── sonner.jsx
    └── lib
        ├── actions
        │   ├── invoice
        │   │   ├── deleteInvoice.js
        │   │   ├── getAll.js
        │   │   ├── getInvoiceByNumber.js
        │   │   ├── getInvoiceWithItem.js
        │   │   ├── submitInvoice.js
        │   │   └── updateInvoice.js
        │   ├── products
        │   │   ├── addProduct.js
        │   │   ├── deleteProduct.js
        │   │   ├── getAllProducts.js
        │   │   └── updateProduct.js
        │   └── size-price
        │       ├── addSize.js
        │       ├── deleteSize.js
        │       ├── getAll.js
        │       └── updateSize.js
        ├── exportToPng.js
        ├── supabaseBrowser.js
        ├── supabaseServer.js
        ├── utils.js
        └── verifySession.js
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

/src/generated/prisma

prisma

db_backups
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
Tech stack:

- Next.js
- shadcn + tailwind
- supabase + prisma

Please visit [Cheese Stick Koe](https://cheesestick-koe.my.id). Thanks ♥️

```

--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

```

--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------

```
const config = {
  plugins: ["@tailwindcss/postcss"],
};

export default config;

```

--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------

```
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
```

--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------

```
/** @type {import('next').NextConfig} */
const nextConfig = {
	experimental: {
		authInterrupts: true,
	},
};

export default nextConfig;

```

--------------------------------------------------------------------------------
/src/lib/supabaseBrowser.js:
--------------------------------------------------------------------------------

```javascript
import { createBrowserClient } from "@supabase/ssr";

export function supabaseBrowser() {
	return createBrowserClient(
		process.env.NEXT_PUBLIC_SUPABASE_URL,
		process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
	);
}

```

--------------------------------------------------------------------------------
/src/lib/verifySession.js:
--------------------------------------------------------------------------------

```javascript
import { supabaseServer } from "@/lib/supabaseServer";

export async function verifySession() {
	const supabase = await supabaseServer();
	const {
		data: { session },
	} = await supabase.auth.getSession();

	return session;
}

```

--------------------------------------------------------------------------------
/src/app/dashboard/invoices/page.js:
--------------------------------------------------------------------------------

```javascript
import { unauthorized } from "next/navigation";

import { verifySession } from "@/lib/verifySession";

import InvoicePage from "./InvoicePage";

export default async function InvoicesPage() {
	const session = await verifySession();

	if (!session) {
		unauthorized();
	}

	return <InvoicePage />;
}

```

--------------------------------------------------------------------------------
/src/app/dashboard/invoices/create/page.js:
--------------------------------------------------------------------------------

```javascript
import { unauthorized } from "next/navigation";

import { verifySession } from "@/lib/verifySession";

import CreateInvoicePage from "./CreateInvoicePage";

export default async function Page() {
	const session = await verifySession();

	if (!session) {
		unauthorized();
	}

	return <CreateInvoicePage />;
}

```

--------------------------------------------------------------------------------
/src/lib/actions/size-price/addSize.js:
--------------------------------------------------------------------------------

```javascript
"use server";

import { supabaseServer } from "@/lib/supabaseServer";

export async function addSize({ size, price }) {
	const supabase = await supabaseServer();
	const { data, error } = await supabase
		.from("ProductSizePrice")
		.insert([{ size, price }])
		.select()
		.single();
	return { data, error };
}

```

--------------------------------------------------------------------------------
/src/app/api/logout/route.js:
--------------------------------------------------------------------------------

```javascript
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";

export async function POST() {
	const supabase = createRouteHandlerClient({ cookies });

	await supabase.auth.signOut();

	return NextResponse.json({ message: "Logged out" });
}

```

--------------------------------------------------------------------------------
/src/lib/actions/products/addProduct.js:
--------------------------------------------------------------------------------

```javascript
"use server";

import { supabaseServer } from "@/lib/supabaseServer";

export async function addProduct({ name, description }) {
	const supabase = await supabaseServer();
	const { data, error } = await supabase
		.from("Product")
		.insert([{ name, description: description || null }])
		.select()
		.single();
	return { data, error };
}

```

--------------------------------------------------------------------------------
/src/app/dashboard/layout.jsx:
--------------------------------------------------------------------------------

```javascript
import { Sidebar } from "@/components/dashboard/Sidebar";

export default function DashboardLayout({ children }) {
	return (
		<div className="min-h-screen flex flex-col md:flex-row bg-[#fffaf0]">
			<Sidebar />
			<main className="flex-1 p-4 md:p-6 bg-white rounded-tl-xl md:rounded-tl-none shadow-inner">
				{children}
			</main>
		</div>
	);
}

```

--------------------------------------------------------------------------------
/src/lib/actions/products/getAllProducts.js:
--------------------------------------------------------------------------------

```javascript
"use server";

import { supabaseServer } from "@/lib/supabaseServer";

export async function getAllProducts(sortOrder = "asc") {
	const supabase = await supabaseServer();

	const { data, error } = await supabase
		.from("Product")
		.select("id, name, description, createdAt")
		.order("name", { ascending: sortOrder === "asc" });

	return { data, error };
}

```

--------------------------------------------------------------------------------
/src/lib/actions/size-price/getAll.js:
--------------------------------------------------------------------------------

```javascript
"use server";

import { supabaseServer } from "@/lib/supabaseServer";

export async function getAllSizePrice(sortOrder = "asc") {
	const supabase = await supabaseServer();

	const { data, error } = await supabase
		.from("ProductSizePrice")
		.select("id, size, price, createdAt")
		.order("size", { ascending: sortOrder === "asc" });

	return { data, error };
}

```

--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------

```
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const compat = new FlatCompat({
  baseDirectory: __dirname,
});

const eslintConfig = [...compat.extends("next/core-web-vitals")];

export default eslintConfig;

```

--------------------------------------------------------------------------------
/src/app/dashboard/size-pricing/page.js:
--------------------------------------------------------------------------------

```javascript
import { unauthorized } from "next/navigation";

import { getPageTitle } from "@/lib/utils";
import { verifySession } from "@/lib/verifySession";

import SizePage from "./SizePage";

export const metadata = {
	title: getPageTitle("Size"),
};

export default function page() {
	const session = verifySession();

	if (!session) {
		unauthorized();
	}

	return <SizePage />;
}

```

--------------------------------------------------------------------------------
/src/app/page.js:
--------------------------------------------------------------------------------

```javascript
import { redirect } from "next/navigation";

import { supabaseServer } from "@/lib/supabaseServer";

import LoginForm from "./_components/LoginForm";

export default async function LoginPage() {
	const supabase = await supabaseServer();

	const {
		data: { session },
	} = await supabase.auth.getSession();

	if (session) {
		redirect("/dashboard");
	}

	return <LoginForm />;
}

```

--------------------------------------------------------------------------------
/public/window.svg:
--------------------------------------------------------------------------------

```
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
```

--------------------------------------------------------------------------------
/src/app/dashboard/products/page.js:
--------------------------------------------------------------------------------

```javascript
import { unauthorized } from "next/navigation";

import { getPageTitle } from "@/lib/utils";
import { verifySession } from "@/lib/verifySession";

import ProductPage from "./ProductPage";

export const metadata = {
	title: getPageTitle("Products"),
};

export default function Page() {
	const session = verifySession();

	if (!session) {
		unauthorized();
	}

	return <ProductPage />;
}

```

--------------------------------------------------------------------------------
/public/file.svg:
--------------------------------------------------------------------------------

```
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
```

--------------------------------------------------------------------------------
/src/lib/actions/invoice/getAll.js:
--------------------------------------------------------------------------------

```javascript
"use server";

import { supabaseServer } from "@/lib/supabaseServer";

export async function getAllInvoice(sortOrder = "asc") {
	const supabase = await supabaseServer();

	const { data, error } = await supabase
		.from("Invoice")
		.select("id, invoiceNumber, buyerName, totalPrice, invoiceDate, status, createdAt")
		.order("invoiceNumber", { ascending: sortOrder === "asc" });

	return { data, error };
}

```

--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------

```json
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": true,
  "tsx": false,
  "tailwind": {
    "config": "",
    "css": "src/app/globals.css",
    "baseColor": "neutral",
    "cssVariables": true,
    "prefix": ""
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  },
  "iconLibrary": "lucide"
}
```

--------------------------------------------------------------------------------
/src/app/not-found.js:
--------------------------------------------------------------------------------

```javascript
export default function NotFound() {
	return (
		<main className="grid min-h-screen place-items-center bg-white px-6 py-24 sm:py-32 lg:px-8">
			<div className="text-center">
				<p className="text-9xl font-semibold text-[#6d2315]">404</p>
				<h1 className="mt-4 text-2xl font-bold tracking-tight text-gray-500 sm:text-5xl">
					Not Found
				</h1>
				<p className="mt-6 text-lg leading-8 text-gray-400">Could not find requested resource</p>
			</div>
		</main>
	);
}

```

--------------------------------------------------------------------------------
/src/lib/actions/invoice/getInvoiceWithItem.js:
--------------------------------------------------------------------------------

```javascript
import { supabaseBrowser } from "@/lib/supabaseBrowser";

export async function getInvoiceWithItems(invoiceId) {
	const supabase = supabaseBrowser();

	const { data: invoice, error: err1 } = await supabase
		.from("Invoice")
		.select("*")
		.eq("id", invoiceId)
		.single();

	const { data: items, error: err2 } = await supabase
		.from("InvoiceItem")
		.select("*, product:Product(name), size:ProductSizePrice(size)")
		.eq("invoiceId", invoiceId);

	return { invoice, items };
}

```

--------------------------------------------------------------------------------
/src/components/ui/sonner.jsx:
--------------------------------------------------------------------------------

```javascript
"use client"

import { useTheme } from "next-themes"
import { Toaster as Sonner } from "sonner";

const Toaster = ({
  ...props
}) => {
  const { theme = "system" } = useTheme()

  return (
    <Sonner
      theme={theme}
      className="toaster group"
      style={
        {
          "--normal-bg": "var(--popover)",
          "--normal-text": "var(--popover-foreground)",
          "--normal-border": "var(--border)"
        }
      }
      {...props} />
  );
}

export { Toaster }

```

--------------------------------------------------------------------------------
/src/app/unauthorized.js:
--------------------------------------------------------------------------------

```javascript
export default function UnauthorizedPage() {
	return (
		<main className="grid min-h-screen place-items-center bg-white px-6 py-24 sm:py-32 lg:px-8">
			<div className="text-center">
				<p className="text-9xl font-semibold text-[#6d2315]">401</p>
				<h1 className="mt-4 text-2xl font-bold tracking-tight text-gray-500 sm:text-5xl">
					Unauthorized Access
				</h1>
				<p className="mt-6 text-lg leading-8 text-gray-400">
					Sorry, you don&apos;t have permission to access this page. account.
				</p>
			</div>
		</main>
	);
}

```

--------------------------------------------------------------------------------
/src/app/dashboard/size-pricing/_components/AddSizeButton.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { useState } from "react";

import { Button } from "@/components/ui/button";
import AddSizeModal from "./AddSizeModal";

export default function AddSizeButton({ onSizeAdded }) {
	const [open, setOpen] = useState(false);

	return (
		<>
			<Button
				onClick={() => setOpen(true)}
				className="bg-[#6D2315] hover:bg-[#591c10] text-white px-4 py-2 rounded-md"
			>
				Add Size
			</Button>
			<AddSizeModal
				open={open}
				setOpen={setOpen}
				onSuccess={() => {
					onSizeAdded?.();
				}}
			/>
		</>
	);
}

```

--------------------------------------------------------------------------------
/src/lib/actions/invoice/getInvoiceByNumber.js:
--------------------------------------------------------------------------------

```javascript
import { supabaseServer } from "@/lib/supabaseServer";

export const getInvoiceByNumber = async (invoiceNumber) => {
	const supabase = await supabaseServer();

	const { data: invoice, error } = await supabase
		.from("Invoice")
		.select(
			`
			*,
			items:InvoiceItem(
				id,
				productId,
				sizePriceId,
				quantity,
				discountAmount,
				product:Product(id, name),
				sizePrice:ProductSizePrice(id, price, size)
			)
		`
		)
		.eq("invoiceNumber", invoiceNumber)
		.single();

	if (error) return { error };
	return { data: invoice };
};

```

--------------------------------------------------------------------------------
/src/app/dashboard/products/_components/ProductModalButton.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { useState } from "react";

import { Button } from "@/components/ui/button";

import ProductModal from "./ProductModal";

export default function ProductModalButton({ onProductAdded }) {
	const [open, setOpen] = useState(false);

	return (
		<>
			<Button
				onClick={() => setOpen(true)}
				className="bg-[#6D2315] hover:bg-[#591c10] text-white px-4 py-2 rounded-md"
			>
				Add Product
			</Button>
			<ProductModal
				open={open}
				setOpen={setOpen}
				onSuccess={() => {
					onProductAdded?.();
				}}
			/>
		</>
	);
}

```

--------------------------------------------------------------------------------
/src/components/ui/label.jsx:
--------------------------------------------------------------------------------

```javascript
"use client"

import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"

import { cn } from "@/lib/utils"

function Label({
  className,
  ...props
}) {
  return (
    <LabelPrimitive.Root
      data-slot="label"
      className={cn(
        "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
        className
      )}
      {...props} />
  );
}

export { Label }

```

--------------------------------------------------------------------------------
/src/app/dashboard/size-pricing/SizePage.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { useRef } from "react";

import SizePriceTable from "./_components/Table";
import AddSizeButton from "./_components/AddSizeButton";

export default function SizePage() {
	const tableRef = useRef();

	return (
		<section className="p-4 space-y-4">
			<div className="flex justify-between items-center mb-2">
				<h1 className="text-2xl font-bold text-[#6D2315] tracking-tight">Size & Price List</h1>
				<AddSizeButton
					onSizeAdded={() => {
						tableRef.current.refetch();
					}}
				/>
			</div>

			<SizePriceTable ref={tableRef} />
		</section>
	);
}

```

--------------------------------------------------------------------------------
/src/lib/supabaseServer.js:
--------------------------------------------------------------------------------

```javascript
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";

export const supabaseServer = async () => {
	const cookieStore = await cookies();

	return createServerClient(
		process.env.NEXT_PUBLIC_SUPABASE_URL,
		process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
		{
			cookies: {
				getAll: async () => cookieStore.getAll(),
				setAll: async (cookiesToSet) => {
					cookiesToSet.forEach(({ name, value, options }) => cookieStore.set(name, value, options));
				},
				remove: async (name, options) => cookieStore.delete(name, options),
			},
		}
	);
};

```

--------------------------------------------------------------------------------
/src/lib/exportToPng.js:
--------------------------------------------------------------------------------

```javascript
import html2canvas from "html2canvas-pro";

export async function exportInvoiceToPng(element, filename = "Invoice.png") {
	if (!element) return;

	const width = element.scrollWidth;
	const height = element.scrollHeight;

	const canvas = await html2canvas(element, {
		scale: 2,
		scrollX: 0,
		scrollY: 0,
		width,
		height,
		windowWidth: width,
		windowHeight: height,
		backgroundColor: "#ffffff",
	});

	const link = document.createElement("a");
	link.download = filename;
	link.href = canvas.toDataURL("image/png");

	return new Promise((resolve) => {
		link.click();
		resolve();
	});
}

```

--------------------------------------------------------------------------------
/src/app/dashboard/products/ProductPage.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { useRef } from "react";

import ProductModalButton from "./_components/ProductModalButton";
import ProductTable from "./_components/ProductTable";

export default function ProductPage() {
	const tableRef = useRef();

	return (
		<section className="p-4 space-y-4">
			<div className="flex justify-between items-center mb-2">
				<h1 className="text-2xl font-bold text-[#6D2315] tracking-tight">Product List</h1>
				<ProductModalButton
					onProductAdded={() => {
						tableRef.current?.refetch();
					}}
				/>
			</div>

			<ProductTable ref={tableRef} />
		</section>
	);
}

```

--------------------------------------------------------------------------------
/src/lib/actions/size-price/deleteSize.js:
--------------------------------------------------------------------------------

```javascript
"use server";

import { supabaseServer } from "@/lib/supabaseServer";
import { revalidatePath } from "next/cache";

export async function deleteSize(sizeId) {
	const supabase = await supabaseServer();

	try {
		const { error } = await supabase.from("ProductSizePrice").delete().match({ id: sizeId });

		if (error) {
			console.error("❌ Supabase delete error:", error);
			return { success: false, message: "Failed to delete size" };
		}

		revalidatePath("/dashboard/size-pricing");

		return { success: true };
	} catch (err) {
		console.log(err);
		return { success: false, message: "Failed to delete size" };
	}
}

```

--------------------------------------------------------------------------------
/src/lib/actions/invoice/deleteInvoice.js:
--------------------------------------------------------------------------------

```javascript
"use server";

import { supabaseServer } from "@/lib/supabaseServer";
import { revalidatePath } from "next/cache";

export async function deleteInvoice(invoiceId) {
	const supabase = await supabaseServer();

	try {
		const { error } = await supabase.from("Invoice").delete().match({ id: invoiceId });

		if (error) {
			console.error("❌ Supabase delete error:", error);
			return { success: false, message: "Failed to delete invoice" };
		}

		revalidatePath("/dashboard/invoices");

		return { success: true };
	} catch (err) {
		console.log(err);
		return { success: false, message: "Failed to delete invoice" };
	}
}

```

--------------------------------------------------------------------------------
/src/lib/actions/products/deleteProduct.js:
--------------------------------------------------------------------------------

```javascript
"use server";

import { supabaseServer } from "@/lib/supabaseServer";
import { revalidatePath } from "next/cache";

export async function deleteProduct(productId) {
	const supabase = await supabaseServer();

	try {
		const { error } = await supabase.from("Product").delete().match({ id: productId });

		if (error) {
			console.error("❌ Supabase delete error:", error);
			return { success: false, message: "Failed to delete product" };
		}

		revalidatePath("/dashboard/products");

		return { success: true };
	} catch (err) {
		console.log(err);
		return { success: false, message: "Failed to delete product" };
	}
}

```

--------------------------------------------------------------------------------
/src/lib/actions/size-price/updateSize.js:
--------------------------------------------------------------------------------

```javascript
"use server";

import { supabaseServer } from "@/lib/supabaseServer";
import { revalidatePath } from "next/cache";

export async function updateSize(id, { size, price }) {
	const supabase = await supabaseServer();

	try {
		const { error } = await supabase.from("ProductSizePrice").update({ size, price }).eq("id", id);

		if (error) {
			console.error("❌ Supabase delete error:", error);
			return { success: false, message: "Failed to update size" };
		}

		revalidatePath("/dashboard/size-pricing");

		return { success: true };
	} catch (err) {
		console.log(err);
		return { success: false, message: "Failed to update size" };
	}
}

```

--------------------------------------------------------------------------------
/src/lib/actions/products/updateProduct.js:
--------------------------------------------------------------------------------

```javascript
"use server";

import { supabaseServer } from "@/lib/supabaseServer";
import { revalidatePath } from "next/cache";

export async function updateProduct(id, { name, description }) {
	const supabase = await supabaseServer();

	try {
		const { error } = await supabase.from("Product").update({ name, description }).eq("id", id);

		if (error) {
			console.error("❌ Supabase delete error:", error);
			return { success: false, message: "Failed to update product" };
		}

		revalidatePath("/dashboard/products");

		return { success: true };
	} catch (err) {
		console.log(err);
		return { success: false, message: "Failed to update product" };
	}
}

```

--------------------------------------------------------------------------------
/src/app/layout.js:
--------------------------------------------------------------------------------

```javascript
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

import { Toaster } from "sonner";

const geistSans = Geist({
	variable: "--font-geist-sans",
	subsets: ["latin"],
});

const geistMono = Geist_Mono({
	variable: "--font-geist-mono",
	subsets: ["latin"],
});

export const metadata = {
	title: "Cheese Stick Koe",
	description: "Invoice generator for Cheese Stick Koe company",
};

export default function RootLayout({ children }) {
	return (
		<html lang="en">
			<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
				{children}
				<Toaster position="top-center" richColors />
			</body>
		</html>
	);
}

```

--------------------------------------------------------------------------------
/src/app/dashboard/invoices/InvoicePage.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { useRef } from "react";
import Link from "next/link";

import { Button } from "@/components/ui/button";

import InvoicesTable from "./Table";

export default function InvoicePage() {
	const tableRef = useRef();

	return (
		<section className="p-4 space-y-4">
			<div className="flex justify-between items-center mb-2">
				<h1 className="text-2xl font-bold text-[#6D2315] tracking-tight">Invoices List</h1>
				<Button asChild className="bg-[#6D2315] hover:bg-[#591c10] text-white px-4 py-2 rounded-md">
					<Link href="/dashboard/invoices/create">Create Invoice</Link>
				</Button>
			</div>

			<InvoicesTable ref={tableRef} />
		</section>
	);
}

```

--------------------------------------------------------------------------------
/middleware.js:
--------------------------------------------------------------------------------

```javascript
import { createMiddlewareClient } from "@supabase/auth-helpers-nextjs";
import { NextResponse } from "next/server";

export async function middleware(req) {
	const res = NextResponse.next();
	const supabase = createMiddlewareClient({ req, res });

	const {
		data: { session },
	} = await supabase.auth.getSession();

	const { pathname } = req.nextUrl;

	if (session && pathname === "/") {
		return NextResponse.redirect(new URL("/dashboard", req.url));
	}

	// trying to access protected route
	if (!session && pathname.startsWith("/dashboard")) {
		return NextResponse.redirect(new URL("/", req.url));
	}

	return res;
}

export const config = {
	matcher: ["/", "/dashboard/:path*"],
};

```

--------------------------------------------------------------------------------
/src/app/dashboard/invoices/[invoiceNumber]/page.jsx:
--------------------------------------------------------------------------------

```javascript
import { unauthorized } from "next/navigation";
import { verifySession } from "@/lib/verifySession";

import UpdateInvoiceForm from "../UpdateInvoiceForm";

import { getInvoiceByNumber } from "@/lib/actions/invoice/getInvoiceByNumber";

export default async function UpdateInvoicePage(props) {
	const session = await verifySession();

	if (!session) {
		unauthorized();
	}

	const { invoiceNumber } = await props.params;

	if (!invoiceNumber) return <div className="text-red-500">Invoice number not found</div>;

	const { data: invoice, error } = await getInvoiceByNumber(invoiceNumber);

	if (error || !invoice) {
		return <div className="text-red-500">Invoice not found</div>;
	}

	return <UpdateInvoiceForm invoice={invoice} />;
}

```

--------------------------------------------------------------------------------
/src/components/ui/input.jsx:
--------------------------------------------------------------------------------

```javascript
import * as React from "react"

import { cn } from "@/lib/utils"

function Input({
  className,
  type,
  ...props
}) {
  return (
    <input
      type={type}
      data-slot="input"
      className={cn(
        "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
        "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
        "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
        className
      )}
      {...props} />
  );
}

export { Input }

```

--------------------------------------------------------------------------------
/src/app/dashboard/size-pricing/_components/DeleteSizeModal.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { toast } from "sonner";

import { DialogFooter } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";

import Modal from "@/components/dashboard/Modal";

import { deleteSize } from "@/lib/actions/size-price/deleteSize";

export default function DeleteSizeModal({ open, onOpenChange, sizeId, onSuccess }) {
	const handleDelete = async () => {
		const result = await deleteSize(sizeId);

		if (result?.success) {
			toast.success("Size has been deleted");
			onSuccess?.();
			onOpenChange(false);
		} else {
			toast.error(result?.message || "Failed to delete size");
		}
	};

	return (
		<Modal
			open={open}
			onOpenChange={onOpenChange}
			title="Delete Size"
			color="red"
			submitLabel="Delete"
			showCancel={false}
		>
			<p>Are you sure want to delete this size?</p>
			<DialogFooter>
				<Button
					variant="destructive"
					onClick={handleDelete}
					className={"bg-rose-600 hover:bg-red-600 w-24"}
				>
					Delete
				</Button>
			</DialogFooter>
		</Modal>
	);
}

```

--------------------------------------------------------------------------------
/public/globe.svg:
--------------------------------------------------------------------------------

```
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
```

--------------------------------------------------------------------------------
/src/app/dashboard/invoices/_components/DeleteInvoiceModal.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { DialogFooter } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";

import Modal from "@/components/dashboard/Modal";

import { deleteInvoice } from "@/lib/actions/invoice/deleteInvoice";

import { toast } from "sonner";

export default function DeleteInvoiceModal({ open, onOpenChange, invoiceId, onSuccess }) {
	const handleDelete = async () => {
		if (!invoiceId) {
			toast.error("Invoice ID not found");
			return;
		}

		const result = await deleteInvoice(invoiceId);

		if (result?.success) {
			toast.success("Invoice has been deleted");
			onSuccess?.();
			onOpenChange(false);
		} else {
			toast.error(result?.message || "Failed to delete invoice");
		}
	};

	return (
		<Modal
			open={open}
			onOpenChange={onOpenChange}
			title="Delete Invoice"
			color="red"
			submitLabel="Delete"
			showCancel={false}
		>
			<p>Are you sure want to delete this invoice?</p>
			<DialogFooter>
				<Button
					variant="destructive"
					onClick={handleDelete}
					className={"bg-rose-600 hover:bg-red-600 w-24"}
				>
					Delete
				</Button>
			</DialogFooter>
		</Modal>
	);
}

```

--------------------------------------------------------------------------------
/src/app/dashboard/products/_components/ProductModal.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { useState } from "react";

import Modal from "@/components/dashboard/Modal";

import { addProduct } from "@/lib/actions/products/addProduct";

import { toast } from "sonner";

export default function ProductModal({ open, setOpen, onSuccess }) {
	const [name, setName] = useState("");
	const [description, setDescription] = useState("");

	const handleSubmit = async (e) => {
		e.preventDefault();

		const { data, error } = await addProduct({ name, description });

		if (error && error.message) {
			toast.error("Failed to add product: " + error.message);
		} else {
			toast.success("Product has been added");
			onSuccess?.(data);
			setOpen(false);
			setName("");
			setDescription("");
		}
	};

	return (
		<Modal
			open={open}
			onOpenChange={setOpen}
			title="Add Product"
			color="default"
			fields={[
				{
					label: "Product Name",
					value: name,
					onChange: (e) => setName(e.target.value),
					required: true,
				},
				{
					label: "Description",
					value: description,
					onChange: (e) => setDescription(e.target.value),
				},
			]}
			onSubmit={handleSubmit}
			submitLabel="Add"
			showCancel={false}
			buttonStyling="bg-primary"
		/>
	);
}

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
	"name": "invoice-generator",
	"version": "0.1.0",
	"private": true,
	"scripts": {
		"dev": "next dev --turbopack",
		"build": "next build",
		"start": "next start",
		"lint": "next lint",
		"seed": "node prisma/seed.js"
	},
	"dependencies": {
		"@prisma/client": "^6.11.1",
		"@radix-ui/react-dialog": "^1.1.14",
		"@radix-ui/react-label": "^2.1.7",
		"@radix-ui/react-popover": "^1.1.14",
		"@radix-ui/react-radio-group": "^1.3.8",
		"@radix-ui/react-select": "^2.2.5",
		"@radix-ui/react-slot": "^1.2.3",
		"@supabase/auth-helpers-nextjs": "^0.10.0",
		"@supabase/ssr": "^0.6.1",
		"class-variance-authority": "^0.7.1",
		"clsx": "^2.1.1",
		"cmdk": "^1.1.1",
		"date-fns": "^4.1.0",
		"dotenv": "^17.2.0",
		"html2canvas-pro": "^1.5.11",
		"lucide-react": "^0.525.0",
		"next": "15.4.1",
		"next-themes": "^0.4.6",
		"prisma": "^6.11.1",
		"react": "19.1.0",
		"react-day-picker": "^9.8.0",
		"react-dom": "19.1.0",
		"react-hook-form": "^7.62.0",
		"sonner": "^2.0.6",
		"tailwind-merge": "^3.3.1",
		"vaul": "^1.1.2"
	},
	"devDependencies": {
		"@eslint/eslintrc": "^3",
		"@tailwindcss/postcss": "^4",
		"autoprefixer": "^10.4.21",
		"eslint": "^9",
		"eslint-config-next": "15.4.1",
		"postcss": "^8.5.6",
		"tailwindcss": "^4.1.11",
		"tw-animate-css": "^1.3.5"
	}
}

```

--------------------------------------------------------------------------------
/src/app/dashboard/size-pricing/_components/AddSizeModal.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { useState } from "react";

import Modal from "@/components/dashboard/Modal";

import { addSize } from "@/lib/actions/size-price/addSize";

import { toast } from "sonner";

export default function AddSizeModal({ open, setOpen, onSuccess }) {
	const [size, setSize] = useState("");
	const [price, setPrice] = useState("");

	const handleSubmit = async (e) => {
		e.preventDefault();

		const { data, error } = await addSize({ size, price });

		if (error && error.message) {
			toast.error("Failed to add size: " + error.message);
		} else {
			toast.success("Size has been added");
			onSuccess?.(data);
			setOpen(false);
			setSize("");
			setPrice("");
		}
	};

	return (
		<Modal
			open={open}
			onOpenChange={setOpen}
			title="Add Size"
			color="default"
			fields={[
				{
					label: "Size name",
					value: size,
					onChange: (e) => setSize(e.target.value),
					required: true,
				},
				{
					type: "number",
					label: "Price",
					value: price === "" ? "" : Number(price),
					onChange: (e) => {
						const val = e.target.value;
						setPrice(val === "" ? "" : parseInt(val));
					},
					required: true,
				},
			]}
			onSubmit={handleSubmit}
			submitLabel="Add"
			showCancel={false}
			buttonStyling="bg-primary"
		/>
	);
}

```

--------------------------------------------------------------------------------
/src/components/ui/radio-group.jsx:
--------------------------------------------------------------------------------

```javascript
"use client"

import * as React from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { CircleIcon } from "lucide-react"

import { cn } from "@/lib/utils"

function RadioGroup({
  className,
  ...props
}) {
  return (
    <RadioGroupPrimitive.Root
      data-slot="radio-group"
      className={cn("grid gap-3", className)}
      {...props} />
  );
}

function RadioGroupItem({
  className,
  ...props
}) {
  return (
    <RadioGroupPrimitive.Item
      data-slot="radio-group-item"
      className={cn(
        "border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
        className
      )}
      {...props}>
      <RadioGroupPrimitive.Indicator
        data-slot="radio-group-indicator"
        className="relative flex items-center justify-center">
        <CircleIcon
          className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
      </RadioGroupPrimitive.Indicator>
    </RadioGroupPrimitive.Item>
  );
}

export { RadioGroup, RadioGroupItem }

```

--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------

```
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
```

--------------------------------------------------------------------------------
/src/components/ui/popover.jsx:
--------------------------------------------------------------------------------

```javascript
"use client"

import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"

import { cn } from "@/lib/utils"

function Popover({
  ...props
}) {
  return <PopoverPrimitive.Root data-slot="popover" {...props} />;
}

function PopoverTrigger({
  ...props
}) {
  return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
}

function PopoverContent({
  className,
  align = "center",
  sideOffset = 4,
  ...props
}) {
  return (
    <PopoverPrimitive.Portal>
      <PopoverPrimitive.Content
        data-slot="popover-content"
        align={align}
        sideOffset={sideOffset}
        className={cn(
          "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
          className
        )}
        {...props} />
    </PopoverPrimitive.Portal>
  );
}

function PopoverAnchor({
  ...props
}) {
  return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
}

export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }

```

--------------------------------------------------------------------------------
/src/components/ui/badge.jsx:
--------------------------------------------------------------------------------

```javascript
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva } from "class-variance-authority";

import { cn } from "@/lib/utils"

const badgeVariants = cva(
  "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
  {
    variants: {
      variant: {
        default:
          "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
        secondary:
          "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
        destructive:
          "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
        outline:
          "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
)

function Badge({
  className,
  variant,
  asChild = false,
  ...props
}) {
  const Comp = asChild ? Slot : "span"

  return (
    <Comp
      data-slot="badge"
      className={cn(badgeVariants({ variant }), className)}
      {...props} />
  );
}

export { Badge, badgeVariants }

```

--------------------------------------------------------------------------------
/src/app/dashboard/invoices/create/_components/SizeCombobox.jsx:
--------------------------------------------------------------------------------

```javascript
import { useState } from "react";

import { Button } from "@/components/ui/button";
import {
	Command,
	CommandEmpty,
	CommandGroup,
	CommandInput,
	CommandItem,
	CommandList,
} from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";

import { Check, ChevronsUpDown } from "lucide-react";

import { cn } from "@/lib/utils";

export default function SizeCombobox({ sizes, value, onChange }) {
	const [open, setOpen] = useState(false);

	return (
		<Popover open={open} onOpenChange={setOpen}>
			<PopoverTrigger asChild>
				<Button
					variant="outline"
					role="combobox"
					aria-expanded={open}
					className="w-full justify-between"
				>
					{value ? sizes.find((s) => s.id === value)?.size : "Choose size..."}
					<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
				</Button>
			</PopoverTrigger>
			<PopoverContent className="w-full p-0">
				<Command>
					<CommandInput placeholder="Search size..." />
					<CommandList>
						<CommandEmpty>Size not found</CommandEmpty>
						<CommandGroup>
							{sizes.map((s) => (
								<CommandItem
									key={s.id}
									value={s.size}
									onSelect={() => {
										onChange(s.id, s.price);
										setOpen(false);
									}}
								>
									{s.size}
									<Check
										className={cn("ml-auto h-4 w-4", value === s.id ? "opacity-100" : "opacity-0")}
									/>
								</CommandItem>
							))}
						</CommandGroup>
					</CommandList>
				</Command>
			</PopoverContent>
		</Popover>
	);
}

```

--------------------------------------------------------------------------------
/src/app/dashboard/size-pricing/_components/EditSizeModal.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { useState, useEffect } from "react";

import { DialogFooter } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";

import Modal from "@/components/dashboard/Modal";

import { updateSize } from "@/lib/actions/size-price/updateSize";

import { toast } from "sonner";

export default function EditSizeModal({ open, onOpenChange, data, onSuccess }) {
	const [size, setSize] = useState("");
	const [price, setPrice] = useState("");

	useEffect(() => {
		if (data) {
			setSize(data.size || "");
			setPrice(data.price || "");
		}
	}, [data]);

	const handleUpdate = async () => {
		const result = await updateSize(data.id, { size, price });

		if (result?.success) {
			toast.success("Product has been updated");
			onSuccess?.({ ...data, size, price });
			onOpenChange(false);
		} else {
			toast.error(result?.message || "Failed to update size");
		}
	};

	return (
		<Modal
			open={open}
			onOpenChange={onOpenChange}
			title="Edit Size"
			color="blue"
			submitLabel="Update"
			showCancel={false}
		>
			<div className="space-y-2">
				<Input placeholder="Size name" value={size} onChange={(e) => setSize(e.target.value)} />
				<Input
					placeholder="Price"
					type="number"
					value={price}
					onChange={(e) => {
						const val = e.target.value;
						setPrice(val === "" ? "" : parseInt(val));
					}}
				/>
			</div>

			<DialogFooter className="pt-4">
				<Button onClick={handleUpdate} className="bg-sky-500 hover:bg-blue-500 w-full">
					Update
				</Button>
			</DialogFooter>
		</Modal>
	);
}

```

--------------------------------------------------------------------------------
/src/app/dashboard/products/_components/ProductEditModal.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { useState, useEffect } from "react";

import { DialogFooter } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";

import Modal from "@/components/dashboard/Modal";

import { updateProduct } from "@/lib/actions/products/updateProduct";

import { toast } from "sonner";

export default function ProductEditModal({ open, onOpenChange, product, onSuccess }) {
	const [name, setName] = useState("");
	const [description, setDescription] = useState("");

	useEffect(() => {
		if (product) {
			setName(product.name || "");
			setDescription(product.description || "");
		}
	}, [product]);

	const handleUpdate = async () => {
		const result = await updateProduct(product.id, { name, description });

		if (result?.success) {
			toast.success("Product has been updated");
			onSuccess?.({ ...product, name, description });
			onOpenChange(false);
		} else {
			toast.error(result?.message || "Failed to update product");
		}
	};

	return (
		<Modal
			open={open}
			onOpenChange={onOpenChange}
			title="Edit Product"
			color="blue"
			submitLabel="Update"
			showCancel={false}
		>
			<div className="space-y-2">
				<Input placeholder="Product name" value={name} onChange={(e) => setName(e.target.value)} />
				<Input
					placeholder="Description"
					value={description}
					onChange={(e) => setDescription(e.target.value)}
				/>
			</div>

			<DialogFooter className="pt-4">
				<Button onClick={handleUpdate} className="bg-sky-500 hover:bg-blue-500 w-full">
					Update
				</Button>
			</DialogFooter>
		</Modal>
	);
}

```

--------------------------------------------------------------------------------
/src/app/dashboard/products/_components/ProductDeleteModal.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { DialogFooter } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";

import Modal from "@/components/dashboard/Modal";

import { deleteProduct } from "@/lib/actions/products/deleteProduct";

import { toast } from "sonner";

export default function ProductDeleteModal({ open, onOpenChange, productId, onSuccess }) {
	const handleDelete = async () => {
		const result = await deleteProduct(productId);

		if (result?.success) {
			toast.success("Product has been deleted");
			onSuccess?.();
			onOpenChange(false);
		} else {
			toast.error(result?.message || "Failed to delete product");
		}
	};

	return (
		<Modal
			open={open}
			onOpenChange={onOpenChange}
			title="Delete Product"
			color="red"
			submitLabel="Delete"
			showCancel={false}
		>
			<p>Are you sure want to delete this product?</p>
			<DialogFooter>
				<Button
					variant="destructive"
					onClick={handleDelete}
					className={"bg-rose-600 hover:bg-red-600 w-24"}
				>
					Delete
				</Button>
			</DialogFooter>
		</Modal>

		// <Dialog open={open} onOpenChange={onOpenChange}>
		// 	<DialogContent>
		// 		<DialogHeader>
		// 			<DialogTitle className={"text-red-500"}>Delete Product</DialogTitle>
		// 		</DialogHeader>
		// 		<p>Are you sure want to delete this product?</p>
		// 		<DialogFooter>
		// 			<Button variant="outline" onClick={() => onOpenChange(false)}>
		// 				Cancel
		// 			</Button>
		// 			<Button
		// 				variant="destructive"
		// 				onClick={handleDelete}
		// 				className={"bg-rose-600 hover:bg-red-600"}
		// 			>
		// 				Delete
		// 			</Button>
		// 		</DialogFooter>
		// 	</DialogContent>
		// </Dialog>
	);
}

```

--------------------------------------------------------------------------------
/src/app/dashboard/invoices/create/_components/ProductsCombobox.jsx:
--------------------------------------------------------------------------------

```javascript
import { useState } from "react";

import { Button } from "@/components/ui/button";
import {
	Command,
	CommandEmpty,
	CommandGroup,
	CommandInput,
	CommandItem,
	CommandList,
} from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";

import { Check, ChevronsUpDown } from "lucide-react";

import { cn } from "@/lib/utils";

export default function ProductCombobox({ products, value, onChange }) {
	const [open, setOpen] = useState(false);

	return (
		<Popover open={open} onOpenChange={setOpen}>
			<PopoverTrigger asChild>
				<Button
					variant="outline"
					role="combobox"
					aria-expanded={open}
					className="w-full justify-between"
				>
					{value ? products.find((p) => p.id === value)?.name : "Choose item..."}
					<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
				</Button>
			</PopoverTrigger>
			<PopoverContent className="w-full p-0">
				<Command>
					<CommandInput placeholder="Search item..." />
					<CommandList>
						<CommandEmpty>Item not found</CommandEmpty>
						<CommandGroup>
							{products.map((product) => (
								<CommandItem
									key={product.id}
									value={product.name}
									onSelect={(currentValue) => {
										const selected = products.find((p) => p.name === currentValue);
										if (selected) {
											onChange(selected.id);
											setOpen(false);
										}
									}}
								>
									{product.name}
									<Check
										className={cn(
											"ml-auto h-4 w-4",
											value === product.id ? "opacity-100" : "opacity-0"
										)}
									/>
								</CommandItem>
							))}
						</CommandGroup>
					</CommandList>
				</Command>
			</PopoverContent>
		</Popover>
	);
}

```

--------------------------------------------------------------------------------
/src/lib/actions/invoice/updateInvoice.js:
--------------------------------------------------------------------------------

```javascript
"use server";

import { supabaseServer } from "@/lib/supabaseServer";

export async function updateInvoice({ invoiceId, invoiceData, items }) {
	const supabase = await supabaseServer();

	try {
		// is invoice number already exist?
		const { data: existing, error: checkError } = await supabase
			.from("Invoice")
			.select("id")
			.eq("invoiceNumber", invoiceData.invoiceNumber)
			.neq("id", invoiceId) // exclude current invoice number
			.maybeSingle();

		if (checkError) throw checkError;
		if (existing) return { error: "Invoice number already existed!" };

		const { error: invoiceError } = await supabase
			.from("Invoice")
			.update({
				invoiceNumber: invoiceData.invoiceNumber,
				buyerName: invoiceData.buyerName,
				invoiceDate: invoiceData.invoiceDate,
				totalPrice: invoiceData.totalPrice,
				discount: invoiceData.discount,
				shipping: parseInt(invoiceData.shipping) || 0,
				status: invoiceData.status,
			})
			.eq("id", invoiceId);

		if (invoiceError) {
			throw invoiceError;
		}

		const { error: deleteError } = await supabase
			.from("InvoiceItem")
			.delete()
			.eq("invoiceId", invoiceId);

		if (deleteError) {
			throw deleteError;
		}

		// insert new items
		const itemsToInsert = items.map((item) => ({
			invoiceId,
			productId: item.productId,
			sizePriceId: item.sizePriceId,
			quantity: item.quantity,
			subtotal: item.quantity * (item.price || 0) - (item.discountAmount || 0),
			discountAmount: item.discountAmount || 0,
		}));

		const { error: insertError } = await supabase.from("InvoiceItem").insert(itemsToInsert);

		if (insertError) {
			throw insertError;
		}

		return { success: true };
	} catch (err) {
		console.error("Error:", err);
		return { success: false, error: err.message };
	}
}

```

--------------------------------------------------------------------------------
/src/components/ui/card.jsx:
--------------------------------------------------------------------------------

```javascript
import * as React from "react"

import { cn } from "@/lib/utils"

function Card({
  className,
  ...props
}) {
  return (
    <div
      data-slot="card"
      className={cn(
        "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
        className
      )}
      {...props} />
  );
}

function CardHeader({
  className,
  ...props
}) {
  return (
    <div
      data-slot="card-header"
      className={cn(
        "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
        className
      )}
      {...props} />
  );
}

function CardTitle({
  className,
  ...props
}) {
  return (
    <div
      data-slot="card-title"
      className={cn("leading-none font-semibold", className)}
      {...props} />
  );
}

function CardDescription({
  className,
  ...props
}) {
  return (
    <div
      data-slot="card-description"
      className={cn("text-muted-foreground text-sm", className)}
      {...props} />
  );
}

function CardAction({
  className,
  ...props
}) {
  return (
    <div
      data-slot="card-action"
      className={cn(
        "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
        className
      )}
      {...props} />
  );
}

function CardContent({
  className,
  ...props
}) {
  return (<div data-slot="card-content" className={cn("px-6", className)} {...props} />);
}

function CardFooter({
  className,
  ...props
}) {
  return (
    <div
      data-slot="card-footer"
      className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
      {...props} />
  );
}

export {
  Card,
  CardHeader,
  CardFooter,
  CardTitle,
  CardAction,
  CardDescription,
  CardContent,
}

```

--------------------------------------------------------------------------------
/src/app/dashboard/invoices/_components/StatusCombobox.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { useState } from "react";

import { Button } from "@/components/ui/button";
import {
	Command,
	CommandEmpty,
	CommandGroup,
	CommandInput,
	CommandItem,
	CommandList,
} from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";

import { Check, ChevronsUpDown } from "lucide-react";

import { cn } from "@/lib/utils";

const statuses = [
	{ label: "Pending", value: "pending" },
	{ label: "Success", value: "success" },
	{ label: "Canceled", value: "canceled" },
];

export default function StatusCombobox({ value, onChange }) {
	const [open, setOpen] = useState(false);

	const selected = statuses.find((s) => s.value === value);

	return (
		<Popover open={open} onOpenChange={setOpen}>
			<PopoverTrigger asChild>
				<Button
					variant="outline"
					role="combobox"
					aria-expanded={open}
					className="w-full justify-between"
				>
					{selected ? selected.label : "Choose status..."}
					<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
				</Button>
			</PopoverTrigger>
			<PopoverContent className="w-full p-0">
				<Command>
					<CommandInput placeholder="Search status..." />
					<CommandList>
						<CommandEmpty>Status not found</CommandEmpty>
						<CommandGroup>
							{statuses.map((s) => (
								<CommandItem
									key={s.value}
									value={s.value}
									onSelect={(currentVal) => {
										onChange(currentVal);
										setOpen(false);
									}}
								>
									{s.label}
									<Check
										className={cn(
											"ml-auto h-4 w-4",
											value === s.value ? "opacity-100" : "opacity-0"
										)}
									/>
								</CommandItem>
							))}
						</CommandGroup>
					</CommandList>
				</Command>
			</PopoverContent>
		</Popover>
	);
}

```

--------------------------------------------------------------------------------
/src/components/dashboard/Modal.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import {
	Dialog,
	DialogContent,
	DialogHeader,
	DialogTitle,
	DialogFooter,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";

export default function Modal({
	open,
	onOpenChange,
	title,
	color = "default", // 'default' | 'red' | 'blue' | 'green' (styling title/button)
	fields = [], // [{ label, placeholder, value, onChange, required }]
	onSubmit,
	submitLabel = "Submit",
	footerButtons, // override footer
	children, // optional custom content
	buttonStyling,
}) {
	const getColorClass = () => {
		switch (color) {
			case "red":
				return "text-red-500";
			case "blue":
				return "text-blue-500";
			case "green":
				return "text-green-500";
			default:
				return "";
		}
	};

	return (
		<Dialog open={open} onOpenChange={onOpenChange}>
			<DialogContent>
				<DialogHeader>
					<DialogTitle className={getColorClass()}>{title}</DialogTitle>
				</DialogHeader>

				{/* CASE 1:  children custom (non-form) */}
				{children && children}

				{!children && fields.length > 0 && (
					<form onSubmit={onSubmit} className="space-y-4 mt-4">
						{fields.map((field) => (
							<div className="space-y-1" key={field.label || field.placeholder || field.value}>
								{field.label && <Label>{field.label}</Label>}
								<Input
									type={field.type || "text"}
									placeholder={field.placeholder}
									value={field.value}
									onChange={field.onChange}
									required={field.required}
								/>
							</div>
						))}
						<DialogFooter>
							<Button type="submit" className={`w-full ${buttonStyling}`}>
								{submitLabel}
							</Button>
						</DialogFooter>
					</form>
				)}

				{footerButtons && <DialogFooter>{footerButtons}</DialogFooter>}
			</DialogContent>
		</Dialog>
	);
}

```

--------------------------------------------------------------------------------
/src/components/ui/button.jsx:
--------------------------------------------------------------------------------

```javascript
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva } from "class-variance-authority";

import { cn } from "@/lib/utils"

const buttonVariants = cva(
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
  {
    variants: {
      variant: {
        default:
          "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
        destructive:
          "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
        outline:
          "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
        secondary:
          "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
        ghost:
          "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-9 px-4 py-2 has-[>svg]:px-3",
        sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
        lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
        icon: "size-9",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

function Button({
  className,
  variant,
  size,
  asChild = false,
  ...props
}) {
  const Comp = asChild ? Slot : "button"

  return (
    <Comp
      data-slot="button"
      className={cn(buttonVariants({ variant, size, className }))}
      {...props} />
  );
}

export { Button, buttonVariants }

```

--------------------------------------------------------------------------------
/src/lib/actions/invoice/submitInvoice.js:
--------------------------------------------------------------------------------

```javascript
"use server";

import { supabaseServer } from "@/lib/supabaseServer";

export const submitInvoice = async ({
	invoiceNumber,
	buyerName,
	invoiceDate,
	shippingPrice,
	discountAmount = 0,
	totalPrice,
	items,
	user,
}) => {
	if (!user) {
		return { error: "User is not login!" };
	}

	if (!invoiceNumber.trim() || !buyerName.trim() || items.length === 0) {
		return { error: "Invoice number, buyer name, and at least one item are required!" };
	}

	const supabase = await supabaseServer();

	// validate number inputs
	const shipping = Number.isNaN(parseInt(shippingPrice)) ? 0 : parseInt(shippingPrice);
	const discount = Number.isNaN(parseInt(discountAmount)) ? 0 : parseInt(discountAmount);
	const total = Number.isNaN(parseInt(totalPrice)) ? 0 : parseInt(totalPrice);

	try {
		// is invoice number already exist?
		const { data: existing, error: checkError } = await supabase
			.from("Invoice")
			.select("id")
			.eq("invoiceNumber", invoiceNumber)
			.maybeSingle();

		if (checkError) throw checkError;
		if (existing) return { error: "Invoice number already existed!" };

		// insert invoice
		const { data: invoice, error: invoiceError } = await supabase
			.from("Invoice")
			.insert([
				{
					invoiceNumber,
					buyerName,
					invoiceDate: new Date(invoiceDate),
					shipping,
					discount,
					totalPrice: total,
					status: "pending",
					userId: user.id,
				},
			])
			.select()
			.single();

		if (invoiceError || !invoice) throw invoiceError || new Error("Failed to insert invoice!");

		// insert invoice items
		const invoiceItems = items.map((item) => ({
			invoiceId: invoice.id,
			productId: item.productId,
			sizePriceId: item.sizePriceId,
			quantity: item.quantity,
			subtotal: item.total,
			discountAmount: item.discountAmount || 0,
		}));

		const { error: itemError } = await supabase.from("InvoiceItem").insert(invoiceItems);

		if (itemError) throw itemError;

		return { success: true, message: "Invoice has been created", invoice };
	} catch (err) {
		return { error: "Something went wrong while saving invoice!" };
	}
};

```

--------------------------------------------------------------------------------
/src/components/dashboard/DatePicker.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { useState } from "react";
import { CalendarIcon } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";

function formatDate(date) {
	if (!date) {
		return "";
	}

	return date.toLocaleDateString("id-ID", {
		day: "2-digit",
		month: "long",
		year: "numeric",
	});
}

function isValidDate(date) {
	if (!date) {
		return false;
	}
	return !isNaN(date.getTime());
}

export default function DatePicker({ invoiceDate, setInvoiceDate }) {
	const [open, setOpen] = useState(false);
	const [selectedDate, setSelectedDate] = useState(
		invoiceDate ? new Date(invoiceDate) : new Date()
	);

	return (
		<div className="flex flex-col gap-3">
			<div className="relative flex gap-2">
				<Input
					id="date"
					value={formatDate(new Date(invoiceDate))}
					placeholder="June 01, 2025"
					className="bg-background pr-10"
					onChange={(e) => {
						const date = new Date(e.target.value);
						if (!isNaN(date)) {
							date.setHours(12, 0, 0, 0);
							setSelectedDate(date);
							setInvoiceDate(date.toISOString());
						}
					}}
					onKeyDown={(e) => {
						if (e.key === "ArrowDown") {
							e.preventDefault();
							setOpen(true);
						}
					}}
				/>
				<Popover open={open} onOpenChange={setOpen}>
					<PopoverTrigger asChild>
						<Button
							id="date-picker"
							variant="ghost"
							className="absolute top-1/2 right-2 size-6 -translate-y-1/2"
						>
							<CalendarIcon className="size-3.5" />
							<span className="sr-only">Select date</span>
						</Button>
					</PopoverTrigger>
					<PopoverContent
						className="w-auto overflow-hidden p-0"
						align="end"
						alignOffset={-8}
						sideOffset={10}
					>
						<Calendar
							mode="single"
							selected={selectedDate}
							captionLayout="dropdown"
							month={selectedDate}
							onMonthChange={(month) => setSelectedDate(month)}
							onSelect={(date) => {
								if (date) {
									setSelectedDate(date);

									const withNoon = new Date(date);
									withNoon.setHours(12, 0, 0, 0);
									setInvoiceDate(withNoon.toISOString());

									setOpen(false);
								}
							}}
						/>
					</PopoverContent>
				</Popover>
			</div>
		</div>
	);
}

```

--------------------------------------------------------------------------------
/src/lib/utils.js:
--------------------------------------------------------------------------------

```javascript
import { clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs) {
	return twMerge(clsx(inputs));
}

export function getStatusVariant(status) {
	switch (status) {
		case "success":
			return "bg-green-100 text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-full dark:bg-green-900 dark:text-green-300";
		case "pending":
			return "bg-gray-100 text-gray-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-full dark:bg-gray-700 dark:text-gray-300";
		case "canceled":
			return "bg-red-100 text-red-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-full dark:bg-red-900 dark:text-red-300";
		default:
			return "bg-purple-100 text-purple-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-full dark:bg-purple-900 dark:text-purple-300";
	}
}

export function toTitleCase(str) {
	return str.replace(
		/\w\S*/g,
		(text) => text.charAt(0).toUpperCase() + text.substring(1).toLowerCase()
	);
}

export function formatInvoiceDateTime(dateStr, timeStr) {
	const date = new Date(dateStr);
	const time = new Date(timeStr);

	const day = date.getDate().toString().padStart(2, "0");
	const month = (date.getMonth() + 1).toString().padStart(2, "0");
	const year = date.getFullYear();

	const hours = time.getHours().toString().padStart(2, "0");
	const minutes = time.getMinutes().toString().padStart(2, "0");

	return `${day}/${month}/${year} ${hours}:${minutes}`;
}

export function formatDateFilename(date) {
	if (!date) return "";

	const dates = new Date(date);
	const day = String(dates.getDate()).padStart(2, "0");
	const month = String(dates.getMonth() + 1).padStart(2, "0");
	const year = dates.getFullYear();

	return `${day}/${month}/${year}`;
}

export function getPageTitle(subTitle) {
	return `Cheese Stick Koe - ${subTitle}`;
}

export function calculateDiscountPercent({
	quantity,
	price,
	discountMode,
	discountInput,
	discountAmount,
}) {
	const rawTotal = (quantity || 0) * (price || 0);

	if (!rawTotal) return "0";

	const amount =
		discountMode === "percent"
			? (parseFloat(discountInput) / 100) * rawTotal
			: parseInt(discountInput) || discountAmount || 0;

	const percent = (amount / rawTotal) * 100;

	return isNaN(percent) ? "0" : percent.toFixed(2);
}

export function calculateDiscountAmount({ quantity, price, discountInput, discountMode }) {
	const rawTotal = (quantity || 0) * (price || 0);
	if (discountMode === "percent") {
		return Math.round(((parseFloat(discountInput) || 0) / 100) * rawTotal);
	}
	return parseInt(discountInput) || 0;
}

```

--------------------------------------------------------------------------------
/src/components/dashboard/Sidebar.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { useState } from "react";
import { Menu, X } from "lucide-react";

const navItems = [
	{ name: "Dashboard", href: "/dashboard" },
	{ name: "Products", href: "/dashboard/products" },
	{ name: "Size & Price", href: "/dashboard/size-pricing" },
	{
		name: "Invoices",
		href: "/dashboard/invoices",
		children: [
			{ name: "List Invoices", href: "/dashboard/invoices" },
			{ name: "Create Invoice", href: "/dashboard/invoices/create" },
		],
	},
];

export function Sidebar() {
	const pathname = usePathname();
	const router = useRouter();
	const [open, setOpen] = useState(false);

	const handleLogout = async () => {
		try {
			const res = await fetch("/api/logout", {
				method: "POST",
			});
			if (res.ok) {
				router.push("/");
			} else {
				console.error("Logout failed");
			}
		} catch (err) {
			console.error("Logout error:", err);
		}
	};

	return (
		<>
			{/* Burger Icon */}
			<button onClick={() => setOpen(!open)} className="md:hidden p-4 focus:outline-none">
				{open ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
			</button>

			{/* Sidebar */}
			<aside
				className={`${
					open ? "block" : "hidden"
				} md:block w-full md:w-64 bg-[#fff7f3] border-r border-[#f1e3db] shadow-sm fixed md:static z-50 top-0 left-0 h-full md:h-auto`}
			>
				{/* Header */}
				<div className="p-4 border-b border-[#f1e3db] flex justify-between items-center bg-[#fff2ea]">
					<h1 className="text-lg font-bold text-[#6D2315] tracking-wide">Invoice App</h1>
					<button onClick={() => setOpen(false)} className="md:hidden">
						<X className="w-5 h-5 text-[#6D2315]" />
					</button>
				</div>

				{/* Nav Items */}
				<nav className="flex flex-col p-4 space-y-2 text-sm text-gray-700">
					{navItems.map((item) => {
						const isActive = pathname === item.href || pathname.startsWith(item.href + "/");

						if (item.children) {
							return (
								<div key={item.href} className="space-y-1">
									<div
										className={`px-3 py-2 rounded-md font-semibold ${
											isActive ? "bg-[#f9e0cd] text-[#6D2315]" : "text-gray-700 hover:bg-[#fceee4]"
										}`}
									>
										{item.name}
									</div>
									<div className="ml-4 space-y-1">
										{item.children.map((child) => (
											<Link
												key={child.href}
												href={child.href}
												className={`block px-3 py-2 rounded-md text-sm ${
													pathname === child.href
														? "bg-[#f9e0cd] text-[#6D2315] font-semibold"
														: "hover:bg-[#fceee4] text-gray-700"
												}`}
												onClick={() => setOpen(false)}
											>
												{child.name}
											</Link>
										))}
									</div>
								</div>
							);
						}

						return (
							<Link
								key={item.href}
								href={item.href}
								className={`px-3 py-2 rounded-md ${
									pathname === item.href
										? "bg-[#f9e0cd] text-[#6D2315] font-semibold"
										: "hover:bg-[#fceee4] text-gray-700"
								}`}
								onClick={() => setOpen(false)}
							>
								{item.name}
							</Link>
						);
					})}

					<button
						onClick={handleLogout}
						className="mt-4 text-sm text-left text-red-500 hover:bg-red-100 px-3 py-2 rounded-md"
					>
						Logout
					</button>
				</nav>
			</aside>
		</>
	);
}

```

--------------------------------------------------------------------------------
/src/components/ui/dialog.jsx:
--------------------------------------------------------------------------------

```javascript
"use client"

import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { XIcon } from "lucide-react"

import { cn } from "@/lib/utils"

function Dialog({
  ...props
}) {
  return <DialogPrimitive.Root data-slot="dialog" {...props} />;
}

function DialogTrigger({
  ...props
}) {
  return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
}

function DialogPortal({
  ...props
}) {
  return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
}

function DialogClose({
  ...props
}) {
  return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
}

function DialogOverlay({
  className,
  ...props
}) {
  return (
    <DialogPrimitive.Overlay
      data-slot="dialog-overlay"
      className={cn(
        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
        className
      )}
      {...props} />
  );
}

function DialogContent({
  className,
  children,
  showCloseButton = true,
  ...props
}) {
  return (
    <DialogPortal data-slot="dialog-portal">
      <DialogOverlay />
      <DialogPrimitive.Content
        data-slot="dialog-content"
        className={cn(
          "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
          className
        )}
        {...props}>
        {children}
        {showCloseButton && (
          <DialogPrimitive.Close
            data-slot="dialog-close"
            className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
            <XIcon />
            <span className="sr-only">Close</span>
          </DialogPrimitive.Close>
        )}
      </DialogPrimitive.Content>
    </DialogPortal>
  );
}

function DialogHeader({
  className,
  ...props
}) {
  return (
    <div
      data-slot="dialog-header"
      className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
      {...props} />
  );
}

function DialogFooter({
  className,
  ...props
}) {
  return (
    <div
      data-slot="dialog-footer"
      className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
      {...props} />
  );
}

function DialogTitle({
  className,
  ...props
}) {
  return (
    <DialogPrimitive.Title
      data-slot="dialog-title"
      className={cn("text-lg leading-none font-semibold", className)}
      {...props} />
  );
}

function DialogDescription({
  className,
  ...props
}) {
  return (
    <DialogPrimitive.Description
      data-slot="dialog-description"
      className={cn("text-muted-foreground text-sm", className)}
      {...props} />
  );
}

export {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogOverlay,
  DialogPortal,
  DialogTitle,
  DialogTrigger,
}

```

--------------------------------------------------------------------------------
/src/components/ui/drawer.jsx:
--------------------------------------------------------------------------------

```javascript
"use client"

import * as React from "react"
import { Drawer as DrawerPrimitive } from "vaul"

import { cn } from "@/lib/utils"

function Drawer({
  ...props
}) {
  return <DrawerPrimitive.Root data-slot="drawer" {...props} />;
}

function DrawerTrigger({
  ...props
}) {
  return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />;
}

function DrawerPortal({
  ...props
}) {
  return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />;
}

function DrawerClose({
  ...props
}) {
  return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />;
}

function DrawerOverlay({
  className,
  ...props
}) {
  return (
    <DrawerPrimitive.Overlay
      data-slot="drawer-overlay"
      className={cn(
        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
        className
      )}
      {...props} />
  );
}

function DrawerContent({
  className,
  children,
  ...props
}) {
  return (
    <DrawerPortal data-slot="drawer-portal">
      <DrawerOverlay />
      <DrawerPrimitive.Content
        data-slot="drawer-content"
        className={cn(
          "group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
          "data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
          "data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
          "data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
          "data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
          className
        )}
        {...props}>
        <div
          className="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
        {children}
      </DrawerPrimitive.Content>
    </DrawerPortal>
  );
}

function DrawerHeader({
  className,
  ...props
}) {
  return (
    <div
      data-slot="drawer-header"
      className={cn(
        "flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left",
        className
      )}
      {...props} />
  );
}

function DrawerFooter({
  className,
  ...props
}) {
  return (
    <div
      data-slot="drawer-footer"
      className={cn("mt-auto flex flex-col gap-2 p-4", className)}
      {...props} />
  );
}

function DrawerTitle({
  className,
  ...props
}) {
  return (
    <DrawerPrimitive.Title
      data-slot="drawer-title"
      className={cn("text-foreground font-semibold", className)}
      {...props} />
  );
}

function DrawerDescription({
  className,
  ...props
}) {
  return (
    <DrawerPrimitive.Description
      data-slot="drawer-description"
      className={cn("text-muted-foreground text-sm", className)}
      {...props} />
  );
}

export {
  Drawer,
  DrawerPortal,
  DrawerOverlay,
  DrawerTrigger,
  DrawerClose,
  DrawerContent,
  DrawerHeader,
  DrawerFooter,
  DrawerTitle,
  DrawerDescription,
}

```

--------------------------------------------------------------------------------
/src/app/_components/LoginForm.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";

import { supabaseBrowser } from "@/lib/supabaseBrowser";

import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";

import { toast } from "sonner";
import { Loader2Icon, LockIcon } from "lucide-react";

import { Controller, useForm } from "react-hook-form";

export default function LoginForm() {
	const router = useRouter();
	const [loading, setLoading] = useState(false);

	useEffect(() => {
		// set loading state false when route change complete
		const handleComplete = () => setLoading(false);

		// Listen to route change events
		router.events?.on("routeChangeComplete", handleComplete);
		router.events?.on("routeChangeError", handleComplete);

		return () => {
			router.events?.off("routeChangeComplete", handleComplete);
			router.events?.off("routeChangeError", handleComplete);
		};
	}, [router]);

	const {
		control,
		handleSubmit,
		formState: { errors },
	} = useForm({
		defaultValues: {
			email: "",
			password: "",
		},
	});

	const onSubmit = async (data) => {
		try {
			setLoading(true);
			const supabase = supabaseBrowser();
			const { error } = await supabase.auth.signInWithPassword({
				email: data.email,
				password: data.password,
			});

			if (error) throw error;

			await router.push("/dashboard");
		} catch (err) {
			toast.error(err.message);
			setLoading(false);
		}
	};

	return (
		<div className="min-h-screen flex items-center justify-center px-4 bg-gradient-to-br from-[#fef6f3] to-[#f1f5f9]">
			<Card className="w-full max-w-md shadow-xl border border-gray-200">
				<CardHeader className="text-center space-y-2">
					<div className="flex justify-center">
						<div className="w-14 h-14 rounded-full bg-[#6d2315] text-white flex items-center justify-center text-2xl font-bold">
							<LockIcon />
						</div>
					</div>
					<CardTitle className="text-2xl font-bold text-[#6d2315]">Welcome Back</CardTitle>
					<p className="text-sm text-gray-500">Please login to your account</p>
				</CardHeader>

				<CardContent>
					<form onSubmit={handleSubmit(onSubmit)} className="space-y-5">
						<div className="space-y-1">
							<label htmlFor="email" className="text-sm font-medium text-gray-700">
								Email
							</label>
							<Controller
								name="email"
								control={control}
								rules={{ required: "Email is required!" }}
								render={({ field }) => (
									<Input
										{...field}
										id="email"
										type="email"
										placeholder="[email protected]"
										required
									/>
								)}
							/>
							{errors.email && (
								<p role="alert" className="text-sm text-red-500">
									{errors.email.message}
								</p>
							)}
						</div>

						<div className="space-y-1">
							<label htmlFor="password" className="text-sm font-medium text-gray-700">
								Password
							</label>
							<Controller
								name="password"
								control={control}
								rules={{ required: "Password is required!" }}
								render={({ field }) => (
									<Input {...field} id="password" type="password" placeholder="••••••••" required />
								)}
							/>
							{errors.password && (
								<p role="alert" className="text-sm text-red-500">
									{errors.password.message}
								</p>
							)}
						</div>

						{/* {error && <p className="text-sm text-red-500 text-center">{error}</p>} */}

						<Button
							type="submit"
							className="w-full bg-[#6d2315] hover:bg-[#591c10]"
							disabled={loading}
						>
							{loading ? (
								<>
									<Loader2Icon className="mr-2 h-4 w-4 animate-spin" />
									Logging in...
								</>
							) : (
								"Login"
							)}
						</Button>
					</form>
				</CardContent>
			</Card>
		</div>
	);
}

```

--------------------------------------------------------------------------------
/src/app/globals.css:
--------------------------------------------------------------------------------

```css
@import "tailwindcss";
@import "tw-animate-css";

@custom-variant dark (&:is(.dark *));

@theme inline {
	--color-background: var(--background);
	--color-foreground: var(--foreground);
	--font-sans: var(--font-geist-sans);
	--font-mono: var(--font-geist-mono);
	--color-sidebar-ring: var(--sidebar-ring);
	--color-sidebar-border: var(--sidebar-border);
	--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
	--color-sidebar-accent: var(--sidebar-accent);
	--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
	--color-sidebar-primary: var(--sidebar-primary);
	--color-sidebar-foreground: var(--sidebar-foreground);
	--color-sidebar: var(--sidebar);
	--color-chart-5: var(--chart-5);
	--color-chart-4: var(--chart-4);
	--color-chart-3: var(--chart-3);
	--color-chart-2: var(--chart-2);
	--color-chart-1: var(--chart-1);
	--color-ring: var(--ring);
	--color-input: var(--input);
	--color-border: var(--border);
	--color-destructive: var(--destructive);
	--color-accent-foreground: var(--accent-foreground);
	--color-accent: var(--accent);
	--color-muted-foreground: var(--muted-foreground);
	--color-muted: var(--muted);
	--color-secondary-foreground: var(--secondary-foreground);
	--color-secondary: var(--secondary);
	--color-primary-foreground: var(--primary-foreground);
	--color-primary: var(--primary);
	--color-popover-foreground: var(--popover-foreground);
	--color-popover: var(--popover);
	--color-card-foreground: var(--card-foreground);
	--color-card: var(--card);
	--radius-sm: calc(var(--radius) - 4px);
	--radius-md: calc(var(--radius) - 2px);
	--radius-lg: var(--radius);
	--radius-xl: calc(var(--radius) + 4px);
}

:root {
	--radius: 0.625rem;
	--background: oklch(1 0 0);
	--foreground: oklch(0.145 0 0);
	--card: oklch(1 0 0);
	--card-foreground: oklch(0.145 0 0);
	--popover: oklch(1 0 0);
	--popover-foreground: oklch(0.145 0 0);
	--primary: oklch(0.205 0 0);
	--primary-foreground: oklch(0.985 0 0);
	--secondary: oklch(0.97 0 0);
	--secondary-foreground: oklch(0.205 0 0);
	--muted: oklch(0.97 0 0);
	--muted-foreground: oklch(0.556 0 0);
	--accent: oklch(0.97 0 0);
	--accent-foreground: oklch(0.205 0 0);
	--destructive: oklch(0.577 0.245 27.325);
	--border: oklch(0.922 0 0);
	--input: oklch(0.922 0 0);
	--ring: oklch(0.708 0 0);
	--chart-1: oklch(0.646 0.222 41.116);
	--chart-2: oklch(0.6 0.118 184.704);
	--chart-3: oklch(0.398 0.07 227.392);
	--chart-4: oklch(0.828 0.189 84.429);
	--chart-5: oklch(0.769 0.188 70.08);
	--sidebar: oklch(0.985 0 0);
	--sidebar-foreground: oklch(0.145 0 0);
	--sidebar-primary: oklch(0.205 0 0);
	--sidebar-primary-foreground: oklch(0.985 0 0);
	--sidebar-accent: oklch(0.97 0 0);
	--sidebar-accent-foreground: oklch(0.205 0 0);
	--sidebar-border: oklch(0.922 0 0);
	--sidebar-ring: oklch(0.708 0 0);
}

.dark {
	--background: oklch(0.145 0 0);
	--foreground: oklch(0.985 0 0);
	--card: oklch(0.205 0 0);
	--card-foreground: oklch(0.985 0 0);
	--popover: oklch(0.205 0 0);
	--popover-foreground: oklch(0.985 0 0);
	--primary: oklch(0.922 0 0);
	--primary-foreground: oklch(0.205 0 0);
	--secondary: oklch(0.269 0 0);
	--secondary-foreground: oklch(0.985 0 0);
	--muted: oklch(0.269 0 0);
	--muted-foreground: oklch(0.708 0 0);
	--accent: oklch(0.269 0 0);
	--accent-foreground: oklch(0.985 0 0);
	--destructive: oklch(0.704 0.191 22.216);
	--border: oklch(1 0 0 / 10%);
	--input: oklch(1 0 0 / 15%);
	--ring: oklch(0.556 0 0);
	--chart-1: oklch(0.488 0.243 264.376);
	--chart-2: oklch(0.696 0.17 162.48);
	--chart-3: oklch(0.769 0.188 70.08);
	--chart-4: oklch(0.627 0.265 303.9);
	--chart-5: oklch(0.645 0.246 16.439);
	--sidebar: oklch(0.205 0 0);
	--sidebar-foreground: oklch(0.985 0 0);
	--sidebar-primary: oklch(0.488 0.243 264.376);
	--sidebar-primary-foreground: oklch(0.985 0 0);
	--sidebar-accent: oklch(0.269 0 0);
	--sidebar-accent-foreground: oklch(0.985 0 0);
	--sidebar-border: oklch(1 0 0 / 10%);
	--sidebar-ring: oklch(0.556 0 0);
}

@layer base {
	* {
		@apply border-border outline-ring/50;
	}
	body {
		@apply bg-background text-foreground;
	}
}

button {
	cursor: pointer;
}

```

--------------------------------------------------------------------------------
/src/app/dashboard/invoices/_components/InvoiceDownloadModal.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { useRef, useState } from "react";

import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";

import InvoicePreview from "./InvoicePreview";

import { exportInvoiceToPng } from "@/lib/exportToPng";
import { formatDateFilename } from "@/lib/utils";

import { toast } from "sonner";

export default function InvoiceDownloadModal({ open, onOpenChange, invoice, invoiceItems }) {
	const invoiceRef = useRef(null);
	const hiddenRef = useRef(null);

	const [dataReady, setDataReady] = useState(false);
	const [isInvoiceReady, setIsInvoiceReady] = useState(false);
	const [isDownloading, setIsDownloading] = useState(false);

	const [shippingType, setShippingType] = useState("");

	const handleDownload = async () => {
		if (isDownloading) {
			toast.error("Wait until download complete");
			return;
		}

		if (!isInvoiceReady || !dataReady) {
			toast.error("Wait a second. Invoice is loading ..");
			return;
		}

		setIsDownloading(true);

		try {
			await document.fonts.ready;
			await new Promise((r) => setTimeout(r, 200));

			const formattedName = `Invoice-${invoice.invoiceNumber}_${
				invoice.buyerName
			}_${formatDateFilename(invoice?.invoiceDate).replaceAll("/", "")}.png`
				.replace(/\s+/g, "-")
				.toLowerCase();

			await exportInvoiceToPng(hiddenRef.current, formattedName);
		} catch (error) {
			console.error("Download failed:", error);
			toast.error("Failed to export invoice");
		} finally {
			setTimeout(() => setIsDownloading(false), 3000);
		}
	};

	return (
		<Dialog open={open} onOpenChange={onOpenChange}>
			<DialogHeader>
				<DialogTitle className="sr-only">Preview Invoice</DialogTitle>
			</DialogHeader>
			<DialogContent className="w-full md:max-w-7xl max-h-[90vh] overflow-y-auto p-0 mx-auto">
				<div
					ref={hiddenRef}
					className="absolute -left-[9999px] top-0 bg-white p-2"
					style={{
						width: "max-content",
						display: "inline-block",
						overflow: "visible",
					}}
				>
					<InvoicePreview
						invoice={invoice}
						invoiceItems={invoiceItems}
						onDataReady={setDataReady}
						oonReady={() => setIsInvoiceReady(true)}
						shippingType={shippingType}
						isDownloadVersion
					/>
				</div>

				<div className="flex flex-col md:flex-row gap-0">
					{/* main content */}
					<div ref={invoiceRef} className="bg-white p-6 flex-1">
						<InvoicePreview
							invoice={invoice}
							invoiceItems={invoiceItems}
							onDataReady={setDataReady}
							shippingType={shippingType}
							onReady={() => setIsInvoiceReady(true)}
						/>
					</div>

					{/* sidebar */}
					<div className="md:w-64 bg-white pt-5 flex flex-col items-center md:items-start text-center md:text-left">
						<p className="mb-2 font-medium">Pilih Ongkir</p>
						<RadioGroup
							value={shippingType}
							onValueChange={setShippingType}
							className="flex flex-col gap-3"
						>
							{["", "sameday", "instan", "jne", "j&t"].map((value) => (
								<div key={value} className="flex items-center space-x-2">
									<RadioGroupItem value={value} id={value || "default"} />
									<Label htmlFor={value || "default"} className="text-xs">
										{value === "" ? "Default (tanpa opsi)" : value}
									</Label>
								</div>
							))}
						</RadioGroup>
					</div>
				</div>

				{/* desktop view */}
				<div className="hidden md:block sticky bottom-0 z-10 bg-white p-4 border-t border-gray-200 shadow-sm text-center">
					<Button onClick={handleDownload} disabled={isDownloading}>
						{isDownloading ? "Please wait ..." : "Download PNG"}
					</Button>
				</div>

				{/* mobile view */}
				<div className="md:hidden sticky bottom-0 z-10 bg-white p-4 border-t border-gray-200">
					<Button className="w-full" onClick={handleDownload} disabled={isDownloading}>
						{isDownloading ? "Please wait ..." : "Download PNG"}
					</Button>
				</div>
			</DialogContent>
		</Dialog>
	);
}

```

--------------------------------------------------------------------------------
/src/components/ui/command.jsx:
--------------------------------------------------------------------------------

```javascript
"use client"

import * as React from "react"
import { Command as CommandPrimitive } from "cmdk"
import { SearchIcon } from "lucide-react"

import { cn } from "@/lib/utils"
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog"

function Command({
  className,
  ...props
}) {
  return (
    <CommandPrimitive
      data-slot="command"
      className={cn(
        "bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
        className
      )}
      {...props} />
  );
}

function CommandDialog({
  title = "Command Palette",
  description = "Search for a command to run...",
  children,
  className,
  showCloseButton = true,
  ...props
}) {
  return (
    <Dialog {...props}>
      <DialogHeader className="sr-only">
        <DialogTitle>{title}</DialogTitle>
        <DialogDescription>{description}</DialogDescription>
      </DialogHeader>
      <DialogContent
        className={cn("overflow-hidden p-0", className)}
        showCloseButton={showCloseButton}>
        <Command
          className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
          {children}
        </Command>
      </DialogContent>
    </Dialog>
  );
}

function CommandInput({
  className,
  ...props
}) {
  return (
    <div
      data-slot="command-input-wrapper"
      className="flex h-9 items-center gap-2 border-b px-3">
      <SearchIcon className="size-4 shrink-0 opacity-50" />
      <CommandPrimitive.Input
        data-slot="command-input"
        className={cn(
          "placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
          className
        )}
        {...props} />
    </div>
  );
}

function CommandList({
  className,
  ...props
}) {
  return (
    <CommandPrimitive.List
      data-slot="command-list"
      className={cn("max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto", className)}
      {...props} />
  );
}

function CommandEmpty({
  ...props
}) {
  return (<CommandPrimitive.Empty data-slot="command-empty" className="py-6 text-center text-sm" {...props} />);
}

function CommandGroup({
  className,
  ...props
}) {
  return (
    <CommandPrimitive.Group
      data-slot="command-group"
      className={cn(
        "text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
        className
      )}
      {...props} />
  );
}

function CommandSeparator({
  className,
  ...props
}) {
  return (
    <CommandPrimitive.Separator
      data-slot="command-separator"
      className={cn("bg-border -mx-1 h-px", className)}
      {...props} />
  );
}

function CommandItem({
  className,
  ...props
}) {
  return (
    <CommandPrimitive.Item
      data-slot="command-item"
      className={cn(
        "data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className
      )}
      {...props} />
  );
}

function CommandShortcut({
  className,
  ...props
}) {
  return (
    <span
      data-slot="command-shortcut"
      className={cn("text-muted-foreground ml-auto text-xs tracking-widest", className)}
      {...props} />
  );
}

export {
  Command,
  CommandDialog,
  CommandInput,
  CommandList,
  CommandEmpty,
  CommandGroup,
  CommandItem,
  CommandShortcut,
  CommandSeparator,
}

```

--------------------------------------------------------------------------------
/src/app/dashboard/size-pricing/_components/Table.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { useState, useEffect, forwardRef, useImperativeHandle } from "react";

import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";

import EditSizeModal from "./EditSizeModal";
import DeleteSizeModal from "./DeleteSizeModal";

import { ArrowUp, ArrowDown, Pencil, Trash2 } from "lucide-react";

import { getAllSizePrice } from "@/lib/actions/size-price/getAll";

const ITEMS_PER_PAGE = 10;

const SizePriceTable = forwardRef(function SizePriceTable(props, ref) {
	const [size, setSize] = useState([]);
	const [sortOrder, setSortOrder] = useState("asc");

	const [error, setError] = useState(null);
	const [currentPage, setCurrentPage] = useState(1);

	const [selectedSize, setSelectedSize] = useState(null);
	const [deleteModalOpen, setDeleteModalOpen] = useState(false);
	const [editModalOpen, setEditModalOpen] = useState(false);

	if (error) return <p className="text-red-500">Failed to fetch data: {error}</p>;

	const fetchData = async () => {
		const { data, error } = await getAllSizePrice(sortOrder);
		if (error) setError(error.message);
		else setSize(data);
	};

	useEffect(() => {
		fetchData();
	}, [sortOrder]);

	useImperativeHandle(ref, () => ({
		refetch: fetchData,
	}));

	// pagination
	const totalPages = Math.ceil(size.length / ITEMS_PER_PAGE);
	const paginatedData = size.slice(
		(currentPage - 1) * ITEMS_PER_PAGE,
		currentPage * ITEMS_PER_PAGE
	);

	return (
		<Card className="p-4 bg-[#fffaf0] border border-[#f4e3d3] shadow-sm">
			<div className="overflow-x-auto rounded-lg border border-[#fceee4]">
				<table className="w-full text-sm">
					<thead className="bg-[#fdf2e9] text-[#6D2315]">
						<tr>
							<th
								className="px-4 py-2 text-left font-semibold cursor-pointer"
								onClick={() => {
									setSortOrder((prev) => (prev === "asc" ? "desc" : "asc"));
									setCurrentPage(1); // reset ke page 1 tiap kali sorting
								}}
							>
								<div className="flex items-center gap-1">
									Size
									{sortOrder === "asc" ? (
										<ArrowUp className="w-4 h-4" />
									) : (
										<ArrowDown className="w-4 h-4" />
									)}
								</div>
							</th>

							<th className="px-4 py-2 text-left font-semibold">Price</th>
							<th className="px-4 py-2 text-left font-semibold">Created At</th>
							<th className="px-4 py-2 text-left font-semibold">Action</th>
						</tr>
					</thead>
					<tbody>
						{paginatedData.map((data) => (
							<tr
								key={data.id}
								className="border-t border-[#fceee4] hover:bg-[#fff3ec] transition-colors"
							>
								<td className="px-4 py-2 text-gray-800">{data.size}</td>
								<td className="px-4 py-2 text-gray-800">
									Rp. {(data.price || 0).toLocaleString("id-ID")}
								</td>
								<td className="px-4 py-2 text-gray-800">
									{new Date(data.createdAt).toLocaleString()}
								</td>
								<td className="px-4 py-2">
									<div className="flex gap-2">
										<Button
											onClick={() => {
												setSelectedSize(data);
												setEditModalOpen(true);
											}}
											variant="ghost"
											size="icon"
											className="text-blue-500 hover:text-blue-600"
										>
											<Pencil className="h-4 w-4" />
										</Button>
										<Button
											onClick={() => {
												setSelectedSize(data);
												setDeleteModalOpen(true);
											}}
											variant="ghost"
											size="icon"
											className="text-red-500 hover:text-red-600"
										>
											<Trash2 className="h-4 w-4" />
										</Button>
									</div>
								</td>
							</tr>
						))}
					</tbody>
				</table>
			</div>

			{/* Pagination */}
			<div className="mt-4 flex justify-end flex-wrap gap-2">
				{Array.from({ length: totalPages }).map((_, idx) => {
					const page = idx + 1;
					return (
						<Button
							key={page}
							onClick={() => setCurrentPage(page)}
							variant={page === currentPage ? "default" : "outline"}
							size="sm"
							className={page === currentPage ? "bg-[#6D2315] text-white" : ""}
						>
							{page}
						</Button>
					);
				})}
			</div>

			<EditSizeModal
				open={editModalOpen}
				onOpenChange={setEditModalOpen}
				data={selectedSize}
				onSuccess={(updatedSize) => {
					setSize((prev) => prev.map((p) => (p.id === updatedSize.id ? updatedSize : p)));
					setSelectedSize(null);
				}}
			/>

			<DeleteSizeModal
				open={deleteModalOpen}
				onOpenChange={setDeleteModalOpen}
				sizeId={selectedSize?.id}
				onSuccess={() => {
					setSize((prev) => prev.filter((p) => p.id !== selectedSize?.id));
					setSelectedSize(null);
				}}
			/>
		</Card>
	);
});

export default SizePriceTable;

```

--------------------------------------------------------------------------------
/src/app/dashboard/products/_components/ProductTable.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { useState, useEffect, forwardRef, useImperativeHandle } from "react";

import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";

import ProductDeleteModal from "./ProductDeleteModal";
import ProductEditModal from "./ProductEditModal";

import { ArrowUp, ArrowDown, Pencil, Trash2 } from "lucide-react";

import { getAllProducts } from "@/lib/actions/products/getAllProducts";

const ITEMS_PER_PAGE = 10;

const ProductTable = forwardRef(function ProductTable(props, ref) {
	const [products, setProducts] = useState([]);
	const [sortOrder, setSortOrder] = useState("asc");

	const [error, setError] = useState(null);
	const [currentPage, setCurrentPage] = useState(1);

	const [selectedProduct, setSelectedProduct] = useState(null);
	const [deleteModalOpen, setDeleteModalOpen] = useState(false);
	const [editModalOpen, setEditModalOpen] = useState(false);

	const [searchQuery, setSearchQuery] = useState("");

	if (error) return <p className="text-red-500">Failed to fetch data: {error}</p>;

	const fetchData = async () => {
		const { data, error } = await getAllProducts(sortOrder);
		if (error) setError(error.message);
		else setProducts(data);
	};

	useEffect(() => {
		fetchData();
	}, [sortOrder]);

	useImperativeHandle(ref, () => ({
		refetch: fetchData,
	}));

	const filteredData = products.filter((product) => {
		const query = searchQuery.toLowerCase();
		return product.name.toLowerCase().includes(query);
	});

	// pagination
	const totalPages = Math.ceil(filteredData.length / ITEMS_PER_PAGE);
	const paginatedData = filteredData.slice(
		(currentPage - 1) * ITEMS_PER_PAGE,
		currentPage * ITEMS_PER_PAGE
	);

	return (
		<Card className="p-4 bg-[#fffaf0] border border-[#f4e3d3] shadow-sm">
			{/* Search Field */}
			<Input
				type="text"
				placeholder="Search product..."
				value={searchQuery}
				onChange={(e) => {
					setSearchQuery(e.target.value);
					setCurrentPage(1);
				}}
				className="mb-4 w-full sm:w-64 px-3 py-2 text-sm border border-[#6D2315] rounded-md focus:outline-none focus:ring-2 focus:ring-[#6D2315]"
			/>

			{/* Table */}
			<div className="overflow-x-auto rounded-lg border border-[#fceee4]">
				<table className="w-full text-sm">
					<thead className="bg-[#fdf2e9] text-[#6D2315]">
						<tr>
							<th
								className="px-4 py-2 text-left font-semibold cursor-pointer"
								onClick={() => {
									setSortOrder((prev) => (prev === "asc" ? "desc" : "asc"));
									setCurrentPage(1);
								}}
							>
								<div className="flex items-center gap-1">
									Product Name
									{sortOrder === "asc" ? (
										<ArrowUp className="w-4 h-4" />
									) : (
										<ArrowDown className="w-4 h-4" />
									)}
								</div>
							</th>
							<th className="px-4 py-2 text-left font-semibold">Description</th>
							<th className="px-4 py-2 text-left font-semibold">Created At</th>
							<th className="px-4 py-2 text-left font-semibold">Action</th>
						</tr>
					</thead>

					<tbody>
						{paginatedData.map((product) => (
							<tr
								key={product.id}
								className="border-t border-[#fceee4] hover:bg-[#fff3ec] transition-colors"
							>
								<td className="px-4 py-2 text-gray-800">{product.name}</td>
								<td className="px-4 py-2 text-gray-600">{product.description || "-"}</td>
								<td className="px-4 py-2 text-gray-500">
									{new Date(product.createdAt).toLocaleString()}
								</td>
								<td className="px-4 py-2">
									<div className="flex gap-2">
										<Button
											onClick={() => {
												setSelectedProduct(product);
												setEditModalOpen(true);
											}}
											variant="ghost"
											size="icon"
											className="text-blue-500 hover:text-blue-600"
										>
											<Pencil className="h-4 w-4" />
										</Button>
										<Button
											onClick={() => {
												setSelectedProduct(product);
												setDeleteModalOpen(true);
											}}
											variant="ghost"
											size="icon"
											className="text-red-500 hover:text-red-600"
										>
											<Trash2 className="h-4 w-4" />
										</Button>
									</div>
								</td>
							</tr>
						))}
					</tbody>
				</table>
			</div>

			{/* Pagination */}
			<div className="mt-4 flex justify-end flex-wrap gap-2">
				{Array.from({ length: totalPages }).map((_, idx) => {
					const page = idx + 1;
					return (
						<Button
							key={page}
							onClick={() => setCurrentPage(page)}
							variant={page === currentPage ? "default" : "outline"}
							size="sm"
							className={page === currentPage ? "bg-[#6D2315] text-white" : ""}
						>
							{page}
						</Button>
					);
				})}
			</div>

			{/* Modals */}
			<ProductDeleteModal
				open={deleteModalOpen}
				onOpenChange={setDeleteModalOpen}
				productId={selectedProduct?.id}
				onSuccess={() => {
					setProducts((prev) => prev.filter((p) => p.id !== selectedProduct?.id));
					setSelectedProduct(null);
				}}
			/>

			<ProductEditModal
				open={editModalOpen}
				onOpenChange={setEditModalOpen}
				product={selectedProduct}
				onSuccess={(updatedProduct) => {
					setProducts((prev) => prev.map((p) => (p.id === updatedProduct.id ? updatedProduct : p)));
					setSelectedProduct(null);
				}}
			/>
		</Card>
	);
});

export default ProductTable;

```

--------------------------------------------------------------------------------
/src/components/ui/select.jsx:
--------------------------------------------------------------------------------

```javascript
"use client"

import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"

import { cn } from "@/lib/utils"

function Select({
  ...props
}) {
  return <SelectPrimitive.Root data-slot="select" {...props} />;
}

function SelectGroup({
  ...props
}) {
  return <SelectPrimitive.Group data-slot="select-group" {...props} />;
}

function SelectValue({
  ...props
}) {
  return <SelectPrimitive.Value data-slot="select-value" {...props} />;
}

function SelectTrigger({
  className,
  size = "default",
  children,
  ...props
}) {
  return (
    <SelectPrimitive.Trigger
      data-slot="select-trigger"
      data-size={size}
      className={cn(
        "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className
      )}
      {...props}>
      {children}
      <SelectPrimitive.Icon asChild>
        <ChevronDownIcon className="size-4 opacity-50" />
      </SelectPrimitive.Icon>
    </SelectPrimitive.Trigger>
  );
}

function SelectContent({
  className,
  children,
  position = "popper",
  ...props
}) {
  return (
    <SelectPrimitive.Portal>
      <SelectPrimitive.Content
        data-slot="select-content"
        className={cn(
          "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
          position === "popper" &&
            "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
          className
        )}
        position={position}
        {...props}>
        <SelectScrollUpButton />
        <SelectPrimitive.Viewport
          className={cn("p-1", position === "popper" &&
            "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1")}>
          {children}
        </SelectPrimitive.Viewport>
        <SelectScrollDownButton />
      </SelectPrimitive.Content>
    </SelectPrimitive.Portal>
  );
}

function SelectLabel({
  className,
  ...props
}) {
  return (
    <SelectPrimitive.Label
      data-slot="select-label"
      className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
      {...props} />
  );
}

function SelectItem({
  className,
  children,
  ...props
}) {
  return (
    <SelectPrimitive.Item
      data-slot="select-item"
      className={cn(
        "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
        className
      )}
      {...props}>
      <span className="absolute right-2 flex size-3.5 items-center justify-center">
        <SelectPrimitive.ItemIndicator>
          <CheckIcon className="size-4" />
        </SelectPrimitive.ItemIndicator>
      </span>
      <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
    </SelectPrimitive.Item>
  );
}

function SelectSeparator({
  className,
  ...props
}) {
  return (
    <SelectPrimitive.Separator
      data-slot="select-separator"
      className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
      {...props} />
  );
}

function SelectScrollUpButton({
  className,
  ...props
}) {
  return (
    <SelectPrimitive.ScrollUpButton
      data-slot="select-scroll-up-button"
      className={cn("flex cursor-default items-center justify-center py-1", className)}
      {...props}>
      <ChevronUpIcon className="size-4" />
    </SelectPrimitive.ScrollUpButton>
  );
}

function SelectScrollDownButton({
  className,
  ...props
}) {
  return (
    <SelectPrimitive.ScrollDownButton
      data-slot="select-scroll-down-button"
      className={cn("flex cursor-default items-center justify-center py-1", className)}
      {...props}>
      <ChevronDownIcon className="size-4" />
    </SelectPrimitive.ScrollDownButton>
  );
}

export {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectScrollDownButton,
  SelectScrollUpButton,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
}

```

--------------------------------------------------------------------------------
/src/app/dashboard/page.jsx:
--------------------------------------------------------------------------------

```javascript
import Link from "next/link";
import { redirect, unauthorized } from "next/navigation";

import { supabaseServer } from "@/lib/supabaseServer";

import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";

import { getPageTitle, getStatusVariant, toTitleCase } from "@/lib/utils";
import { AlertTriangle, CheckCircle, FileText, Package, Users, Wallet } from "lucide-react";

export const metadata = {
	title: getPageTitle("Dashboard"),
};

export default async function Dashboard() {
	const supabase = await supabaseServer();

	const {
		data: { session },
	} = await supabase.auth.getSession();

	if (!session) {
		unauthorized();
	}

	// const user = session.user;

	// get all invoices
	const { data: invoices } = await supabase
		.from("Invoice")
		.select("*")
		.order("invoiceDate", { ascending: false });

	// get total invoice
	const totalInvoices = invoices.length;

	// get latest invoice data
	const latestInvoices = invoices.slice(0, 5);

	// count paid invoices
	const invoicesSuccess = invoices.filter((inv) => inv.status === "success").length;

	// count unpaid invoices
	const invoicesUnpaid = invoices.filter((inv) => inv.status === "pending").length;

	// count total customers (unique)
	const { data } = await supabase.from("Invoice").select("buyerName");
	const uniqueCustomers = new Set(data.map((d) => d.buyerName.trim().toLowerCase()));
	const totalCustomers = uniqueCustomers.size;

	const totalAmount =
		invoices
			?.filter((inv) => inv.status === "success")
			.reduce((acc, curr) => acc + curr.totalPrice, 0) || 0;

	// get total products
	const { count: totalProducts } = await supabase
		.from("Product")
		.select("*", { count: "exact", head: true });

	return (
		<div className="grid gap-6">
			{/* Welcome Card */}
			<Card className="bg-[#fffaf0] border border-[#f4e3d3] shadow-sm">
				<CardHeader>
					<CardTitle className="text-xl text-[#6D2315] font-bold">Welcome back, Admin 👋</CardTitle>
				</CardHeader>
				<CardContent>
					<p className="text-sm text-gray-600">Manage your dashboard here.</p>
					<div className="mt-4">
						<Link href="/dashboard/invoices/create">
							<Button className="bg-[#6D2315] hover:bg-[#591c10] text-white">
								+ Create Invoice
							</Button>
						</Link>
					</div>
				</CardContent>
			</Card>

			{/* Statistik Ringkas */}
			<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
				{/* Total Invoices */}
				<Card className="bg-[#fef6f3] border-0 shadow-sm text-[#6D2315]">
					<CardHeader>
						<CardTitle className="text-sm font-medium flex items-center gap-2">
							<FileText className="w-4 h-4 text-[#6D2315]" />
							Total Invoices
						</CardTitle>
					</CardHeader>
					<CardContent>
						<p className="text-3xl font-bold">{totalInvoices || 0}</p>
					</CardContent>
				</Card>

				{/* Total Income */}
				<Card className="bg-[#fdf2e9] border-0 shadow-sm text-[#92400e]">
					<CardHeader>
						<CardTitle className="text-sm font-medium flex items-center gap-2">
							<Wallet className="w-4 h-4 text-[#92400e]" />
							Total Income
						</CardTitle>
					</CardHeader>
					<CardContent>
						<p className="text-3xl font-bold">Rp {totalAmount.toLocaleString("id-ID")}</p>
					</CardContent>
				</Card>

				{/* Total Products */}
				<Card className="bg-[#fef9e7] border-0 shadow-sm text-[#92400e]">
					<CardHeader>
						<CardTitle className="text-sm font-medium flex items-center gap-2">
							<Package className="w-4 h-4 text-[#92400e]" />
							Total Products
						</CardTitle>
					</CardHeader>
					<CardContent>
						<p className="text-3xl font-bold">{totalProducts}</p>
					</CardContent>
				</Card>

				{/* Paid Invoices */}
				<Card className="bg-[#f0f9f5] border-0 shadow-sm text-[#065f46]">
					<CardHeader>
						<CardTitle className="text-sm font-medium flex items-center gap-2">
							<CheckCircle className="w-4 h-4 text-[#065f46]" />
							Paid Invoices
						</CardTitle>
					</CardHeader>
					<CardContent>
						<p className="text-3xl font-bold">{invoicesSuccess}</p>
					</CardContent>
				</Card>

				{/* Unpaid Invoices */}
				<Card className="bg-[#fef2f2] border-0 shadow-sm text-[#991b1b]">
					<CardHeader>
						<CardTitle className="text-sm font-medium flex items-center gap-2">
							<AlertTriangle className="w-4 h-4 text-[#991b1b]" />
							Unpaid Invoices
						</CardTitle>
					</CardHeader>
					<CardContent>
						<p className="text-3xl font-bold">{invoicesUnpaid}</p>
					</CardContent>
				</Card>

				{/* Total Customers */}
				<Card className="bg-[#e8f0fe] border-0 shadow-sm text-[#1e3a8a]">
					<CardHeader>
						<CardTitle className="text-sm font-medium flex items-center gap-2">
							<Users className="w-4 h-4 text-[#1e3a8a]" />
							Total Customers
						</CardTitle>
					</CardHeader>
					<CardContent>
						<p className="text-3xl font-bold">{totalCustomers}</p>
					</CardContent>
				</Card>
			</div>

			{/* Latest Invoices */}
			<Card className="bg-white border border-[#f4e3d3] shadow-sm">
				<CardHeader>
					<CardTitle className="text-[#6D2315] font-medium">Latest Invoices</CardTitle>
				</CardHeader>

				<CardContent className="space-y-3">
					{latestInvoices && latestInvoices.length > 0 ? (
						latestInvoices.map((inv) => (
							<div
								key={inv.id}
								className="flex items-center justify-between bg-[#fefaf7] hover:bg-[#fff3ec] transition-colors duration-150 border border-[#fceee4] rounded-md p-3"
							>
								{/* Left Section: Icon + Info */}
								<div className="flex items-center gap-3">
									<div className="bg-[#fdf0e6] text-[#6D2315] p-2 rounded-md">
										<FileText className="w-5 h-5" />
									</div>
									<div>
										<p className="font-medium text-gray-800">Invoice #{inv.invoiceNumber}</p>
										<p className="text-sm text-gray-500">{toTitleCase(inv.buyerName)}</p>
									</div>
								</div>

								{/* Right Section: Amount + Status */}
								<div className="text-right space-y-1">
									<p className="font-semibold text-gray-800">
										Rp {inv.totalPrice.toLocaleString("id-ID")}
									</p>
									<span className={getStatusVariant(inv.status)}>{toTitleCase(inv.status)}</span>
								</div>
							</div>
						))
					) : (
						<p className="text-sm text-gray-500">No invoice data.</p>
					)}
				</CardContent>
			</Card>
		</div>
	);
}

```

--------------------------------------------------------------------------------
/src/app/dashboard/invoices/Table.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { useState, useEffect, forwardRef, useImperativeHandle } from "react";
import { useRouter } from "next/navigation";

import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";

import DeleteInvoiceModal from "./_components/DeleteInvoiceModal";
import InvoiceDownloadModal from "./_components/InvoiceDownloadModal";

import { getAllInvoice } from "@/lib/actions/invoice/getAll";
import { getInvoiceWithItems } from "@/lib/actions/invoice/getInvoiceWithItem";

import { formatInvoiceDateTime, getStatusVariant, toTitleCase } from "@/lib/utils";

import { ArrowUp, ArrowDown, Pencil, Trash2, Download } from "lucide-react";

const ITEMS_PER_PAGE = 10;

const InvoicesTable = forwardRef(function InvoicesTable(props, ref) {
	const router = useRouter();

	const [invoice, setInvoice] = useState([]);
	const [sortOrder, setSortOrder] = useState("desc");

	const [error, setError] = useState(null);
	const [currentPage, setCurrentPage] = useState(1);

	const [selectedInvoice, setSelectedInvoice] = useState(null);
	const [deleteModalOpen, setDeleteModalOpen] = useState(false);

	const [searchQuery, setSearchQuery] = useState("");

	const [downloadModalOpen, setDownloadModalOpen] = useState(false);
	const [invoiceItems, setInvoiceItems] = useState([]);

	if (error) return <p className="text-red-500">Failed to fetch data: {error}</p>;

	const fetchData = async () => {
		const { data, error } = await getAllInvoice(sortOrder);
		if (error) setError(error.message);
		else setInvoice(data);
	};

	useEffect(() => {
		fetchData();
	}, [sortOrder]);

	useImperativeHandle(ref, () => ({
		refetch: fetchData,
	}));

	const filteredData = invoice.filter((inv) => {
		const query = searchQuery.toLowerCase();
		return (
			inv.invoiceNumber.toLowerCase().includes(query) || inv.buyerName.toLowerCase().includes(query)
		);
	});

	// pagination
	const totalPages = Math.ceil(filteredData.length / ITEMS_PER_PAGE);
	const paginatedData = filteredData.slice(
		(currentPage - 1) * ITEMS_PER_PAGE,
		currentPage * ITEMS_PER_PAGE
	);

	const handleDownload = async (invoiceId) => {
		const { invoice, items } = await getInvoiceWithItems(invoiceId);
		setSelectedInvoice(invoice);
		setInvoiceItems(items);
		setDownloadModalOpen(true);
	};

	return (
		<Card className="p-4 bg-[#fffaf0] border border-[#f4e3d3] shadow-sm">
			<Input
				type="text"
				placeholder="Search by name/inv.number"
				value={searchQuery}
				onChange={(e) => {
					setSearchQuery(e.target.value);
					setCurrentPage(1);
				}}
				className="mb-4 w-full sm:w-64 px-3 py-2 text-sm border border-[#6D2315] rounded-md focus:outline-none focus:ring-2 focus:ring-[#6D2315]"
			/>
			<div className="overflow-x-auto rounded-lg border border-[#fceee4]">
				<table className="w-full text-sm">
					<thead className="bg-[#fdf2e9] text-[#6D2315]">
						<tr>
							<th
								className="px-4 py-2 text-left font-semibold cursor-pointer"
								onClick={() => {
									setSortOrder((prev) => (prev === "asc" ? "desc" : "asc"));
									setCurrentPage(1); // reset ke page 1 tiap kali sorting
								}}
							>
								<div className="flex items-center gap-1">
									Invoice Number
									{sortOrder === "desc" ? (
										<ArrowDown className="w-4 h-4" />
									) : (
										<ArrowUp className="w-4 h-4" />
									)}
								</div>
							</th>

							<th className="px-4 py-2 text-left font-semibold">Customer</th>
							<th className="px-4 py-2 text-left font-semibold">Total Price</th>
							<th className="px-4 py-2 text-left font-semibold">Date</th>
							<th className="px-4 py-2 text-left font-semibold">Status</th>
							<th className="px-4 py-2 text-left font-semibold">Action</th>
						</tr>
					</thead>
					<tbody>
						{paginatedData && paginatedData.length > 0 ? (
							paginatedData.map((data) => (
								<tr
									key={data.id}
									className="border-t border-[#fceee4] hover:bg-[#fff3ec] transition-colors"
								>
									<td className="px-4 py-2 text-gray-800">{data.invoiceNumber}</td>
									<td className="px-4 py-2 text-gray-800">{toTitleCase(data.buyerName)}</td>
									<td className="px-4 py-2 text-gray-800">
										Rp. {(data.totalPrice || 0).toLocaleString("id-ID")}
									</td>
									<td className="px-4 py-2 text-gray-800">
										{formatInvoiceDateTime(data.invoiceDate, data.createdAt)}
									</td>
									<td className="px-4 py-2 text-gray-800">
										<span className={getStatusVariant(data.status)}>
											{toTitleCase(data.status)}
										</span>
									</td>
									<td className="px-4 py-2">
										<div className="flex gap-2">
											<Button
												onClick={() => {
													router.push(`/dashboard/invoices/${data.invoiceNumber}`);
												}}
												variant="ghost"
												size="icon"
												className="text-blue-500 hover:text-blue-600"
											>
												<Pencil className="h-4 w-4" />
											</Button>
											<Button
												onClick={() => {
													setSelectedInvoice(data);
													setDeleteModalOpen(true);
												}}
												variant="ghost"
												size="icon"
												className="text-red-500 hover:text-red-600"
											>
												<Trash2 className="h-4 w-4" />
											</Button>
											<Button
												onClick={() => handleDownload(data.id)}
												variant="ghost"
												size="icon"
												className="text-green-500 hover:text-green-600"
											>
												<Download className="h-4 w-4" />
											</Button>
										</div>
									</td>
								</tr>
							))
						) : (
							<tr>
								<td colSpan="6" className="px-4 py-6 text-center text-gray-500">
									No invoice data
								</td>
							</tr>
						)}
					</tbody>
				</table>
			</div>

			{/* Pagination */}
			<div className="mt-4 flex justify-end flex-wrap gap-2">
				{Array.from({ length: totalPages }).map((_, idx) => {
					const page = idx + 1;
					return (
						<Button
							key={page}
							onClick={() => setCurrentPage(page)}
							variant={page === currentPage ? "default" : "outline"}
							size="sm"
							className={page === currentPage ? "bg-[#6D2315] hover:bg-[#8d2e1c] text-white" : ""}
						>
							{page}
						</Button>
					);
				})}
			</div>

			<DeleteInvoiceModal
				open={deleteModalOpen}
				onOpenChange={setDeleteModalOpen}
				invoiceId={selectedInvoice?.id}
				onSuccess={() => {
					setInvoice((prev) => prev.filter((p) => p.id !== selectedInvoice?.id));
					setSelectedInvoice(null);
				}}
			/>

			<InvoiceDownloadModal
				open={downloadModalOpen}
				onOpenChange={setDownloadModalOpen}
				invoice={selectedInvoice}
				invoiceItems={invoiceItems}
			/>
		</Card>
	);
});

export default InvoicesTable;

```

--------------------------------------------------------------------------------
/src/components/ui/calendar.jsx:
--------------------------------------------------------------------------------

```javascript
"use client"

import * as React from "react"
import {
  ChevronDownIcon,
  ChevronLeftIcon,
  ChevronRightIcon,
} from "lucide-react"
import { DayPicker, getDefaultClassNames } from "react-day-picker";

import { cn } from "@/lib/utils"
import { Button, buttonVariants } from "@/components/ui/button"

function Calendar({
  className,
  classNames,
  showOutsideDays = true,
  captionLayout = "label",
  buttonVariant = "ghost",
  formatters,
  components,
  ...props
}) {
  const defaultClassNames = getDefaultClassNames()

  return (
    <DayPicker
      showOutsideDays={showOutsideDays}
      className={cn(
        "bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
        String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
        String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
        className
      )}
      captionLayout={captionLayout}
      formatters={{
        formatMonthDropdown: (date) =>
          date.toLocaleString("default", { month: "short" }),
        ...formatters,
      }}
      classNames={{
        root: cn("w-fit", defaultClassNames.root),
        months: cn("flex gap-4 flex-col md:flex-row relative", defaultClassNames.months),
        month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
        nav: cn(
          "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
          defaultClassNames.nav
        ),
        button_previous: cn(
          buttonVariants({ variant: buttonVariant }),
          "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
          defaultClassNames.button_previous
        ),
        button_next: cn(
          buttonVariants({ variant: buttonVariant }),
          "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
          defaultClassNames.button_next
        ),
        month_caption: cn(
          "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
          defaultClassNames.month_caption
        ),
        dropdowns: cn(
          "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
          defaultClassNames.dropdowns
        ),
        dropdown_root: cn(
          "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
          defaultClassNames.dropdown_root
        ),
        dropdown: cn("absolute bg-popover inset-0 opacity-0", defaultClassNames.dropdown),
        caption_label: cn("select-none font-medium", captionLayout === "label"
          ? "text-sm"
          : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5", defaultClassNames.caption_label),
        table: "w-full border-collapse",
        weekdays: cn("flex", defaultClassNames.weekdays),
        weekday: cn(
          "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
          defaultClassNames.weekday
        ),
        week: cn("flex w-full mt-2", defaultClassNames.week),
        week_number_header: cn("select-none w-(--cell-size)", defaultClassNames.week_number_header),
        week_number: cn(
          "text-[0.8rem] select-none text-muted-foreground",
          defaultClassNames.week_number
        ),
        day: cn(
          "relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
          defaultClassNames.day
        ),
        range_start: cn("rounded-l-md bg-accent", defaultClassNames.range_start),
        range_middle: cn("rounded-none", defaultClassNames.range_middle),
        range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
        today: cn(
          "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
          defaultClassNames.today
        ),
        outside: cn(
          "text-muted-foreground aria-selected:text-muted-foreground",
          defaultClassNames.outside
        ),
        disabled: cn("text-muted-foreground opacity-50", defaultClassNames.disabled),
        hidden: cn("invisible", defaultClassNames.hidden),
        ...classNames,
      }}
      components={{
        Root: ({ className, rootRef, ...props }) => {
          return (<div data-slot="calendar" ref={rootRef} className={cn(className)} {...props} />);
        },
        Chevron: ({ className, orientation, ...props }) => {
          if (orientation === "left") {
            return (<ChevronLeftIcon className={cn("size-4", className)} {...props} />);
          }

          if (orientation === "right") {
            return (<ChevronRightIcon className={cn("size-4", className)} {...props} />);
          }

          return (<ChevronDownIcon className={cn("size-4", className)} {...props} />);
        },
        DayButton: CalendarDayButton,
        WeekNumber: ({ children, ...props }) => {
          return (
            <td {...props}>
              <div
                className="flex size-(--cell-size) items-center justify-center text-center">
                {children}
              </div>
            </td>
          );
        },
        ...components,
      }}
      {...props} />
  );
}

function CalendarDayButton({
  className,
  day,
  modifiers,
  ...props
}) {
  const defaultClassNames = getDefaultClassNames()

  const ref = React.useRef(null)
  React.useEffect(() => {
    if (modifiers.focused) ref.current?.focus()
  }, [modifiers.focused])

  return (
    <Button
      ref={ref}
      variant="ghost"
      size="icon"
      data-day={day.date.toLocaleDateString()}
      data-selected-single={
        modifiers.selected &&
        !modifiers.range_start &&
        !modifiers.range_end &&
        !modifiers.range_middle
      }
      data-range-start={modifiers.range_start}
      data-range-end={modifiers.range_end}
      data-range-middle={modifiers.range_middle}
      className={cn(
        "data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70",
        defaultClassNames.day,
        className
      )}
      {...props} />
  );
}

export { Calendar, CalendarDayButton }

```

--------------------------------------------------------------------------------
/src/app/dashboard/invoices/_components/InvoicePreview.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { forwardRef, useEffect, useState } from "react";

import Image from "next/image";

import { getAllProducts } from "@/lib/actions/products/getAllProducts";
import { getAllSizePrice } from "@/lib/actions/size-price/getAll";

import { cn, formatDateFilename, toTitleCase } from "@/lib/utils";

const InvoicePreview = forwardRef(
	(
		{ invoice, invoiceItems, shippingType, onReady, onDataReady, isDownloadVersion = false },
		ref
	) => {
		const [products, setProducts] = useState([]);
		const [sizes, setSizes] = useState([]);
		const [items, setItems] = useState([]);

		useEffect(() => {
			const fetchData = async () => {
				const { data: productsData } = await getAllProducts();
				const { data: sizeData } = await getAllSizePrice();

				setProducts(productsData || []);
				setSizes(sizeData || []);
			};

			fetchData();
		}, []);

		useEffect(() => {
			if (invoiceItems?.length && products.length && sizes.length) {
				const mappedItems = invoiceItems.map((item) => {
					const product = products.find((p) => p.id === item.productId);
					const size = sizes.find((s) => s.id === item.sizePriceId);
					const price = size?.price || 0;
					const discount = item.discountAmount || 0;
					const quantity = item.quantity;

					return {
						productName: product?.name || "Unknown",
						sizeName: size?.size || "Unknown",
						quantity,
						price,
						discountAmount: discount,
						total: quantity * price - discount,
					};
				});

				setItems(mappedItems);

				onDataReady?.(true);
			} else {
				onDataReady?.(false);
			}
		}, [invoiceItems, products, sizes]);

		useEffect(() => {
			if (!items.length) return;

			const timer = setTimeout(() => {
				onReady?.();
			}, 0);

			return () => clearTimeout(timer);
		}, [items]);

		const subtotal = items.reduce((acc, item) => acc + item.total, 0);
		const discount = invoice.discount || 0;

		const discountPercent = subtotal > 0 ? (discount / subtotal) * 100 : 0;

		const TOTAL_GAP_ROWS = 10;
		const gapRows = Math.max(0, TOTAL_GAP_ROWS - items.length);

		return (
			<>
				<style jsx global>{`
					@import url("https://fonts.googleapis.com/css2?family=Pattaya&family=Poppins:wght@400;700&display=swap");

					.invoice-content {
						font-family: "Poppins", sans-serif;
					}

					.thanks-msg {
						font-family: "Pattaya", cursive;
					}

					.invoice-content * {
						box-sizing: border-box;
					}

					.invoice-content table td,
					.invoice-content table th {
						padding: 8px 12px; /* ~px-3 py-2 */
						white-space: nowrap;
					}
				`}</style>

				<div
					ref={ref}
					className={cn(
						"border border-[#6D2315] font-sans text-sm text-gray-900 invoice-content",
						isDownloadVersion
							? "w-[1080px] p-3 overflow-visible"
							: "w-full md:w-[1080px] overflow-x-auto p-4"
					)}
				>
					<div
						className={cn("flex w-full", isDownloadVersion ? "flex-row" : "flex-col md:flex-row")}
					>
						{/* LEFT SECTION */}
						<div
							className={cn(
								"pr-4 border-r border-[#6D2315] space-y-6",
								isDownloadVersion ? "w-1/3" : "w-full md:w-1/3"
							)}
						>
							<div className="flex justify-center mb-4">
								<div className="w-24 h-24 relative">
									<Image src="/logo.png" alt="Logo" fill className="object-cover rounded-full" />
								</div>
							</div>

							<h2 className="text-center font-bold text-[#6b1d1d] uppercase text-lg pb-7">
								cheese stick koe
							</h2>

							<div>
								<h4 className="text-[#6b1d1d] font-semibold text-xs mb-1 uppercase">
									diberikan kepada
								</h4>
								<p className="text-sm">{toTitleCase(invoice?.buyerName)}</p>
							</div>

							<div>
								<h4 className="text-[#6b1d1d] font-semibold text-xs mb-1 uppercase">detail bank</h4>
								<div className="text-sm space-y-2">
									<div>
										<span className="font-semibold uppercase">bri</span>
										<br />
										Ermi Sayekti Endahwati
										<br />
										0122-01-012734-53-8
									</div>
									<div>
										<span className="font-semibold uppercase">bca</span>
										<br />
										Ermi Sayekti Endahwati
										<br />
										524-5031-928
									</div>
								</div>
							</div>

							<figure className="space-y-2">
								{/* Image */}
								<figcaption className="text-xs text-left font-bold italic">
									QRIS a.n Cheese Stick Koe
								</figcaption>

								<div className="flex justify-center mt-5">
									<div className="relative w-38 h-38">
										<Image src="/qris.png" alt="Icon" fill className="object-contain" />
									</div>
								</div>

								{/* Caption */}
							</figure>

							{/* Thank You Text */}
							<div className="text-center pt-5 font-bold text-2xl text-[#6b1d1d] thanks-msg uppercase">
								terima kasih
							</div>
						</div>

						{/* RIGHT SECTION */}
						<div className="md:w-2/3 w-full pl-6 space-y-6 mt-6 md:mt-0">
							<div className="text-center border-b border-[#6D2315] pb-2">
								<h1 className="font-bold text-lg uppercase">invoice pembayaran</h1>
								<div className="flex justify-between text-xs mt-1">
									<span>Invoice No. {invoice?.invoiceNumber}</span>
									<span className="whitespace-nowrap">
										{formatDateFilename(invoice?.invoiceDate)}
									</span>
								</div>
							</div>

							<div
								className={cn(
									!isDownloadVersion && "overflow-x-auto max-w-full",
									isDownloadVersion && "overflow-hidden"
								)}
							>
								<table
									className="text-left text-sm"
									style={{
										border: "1.5px solid #6D2315",
										tableLayout: "auto",
										width: "100%",
									}}
								>
									<thead
										style={{ backgroundColor: "white", borderBottom: "1.5px solid #6D2315" }}
										className="uppercase"
									>
										<tr>
											<th className="px-2.5 py-2 whitespace-nowrap">item</th>
											<th className="px-2.5 py-2 whitespace-nowrap">ukuran</th>
											<th className="px-2.5 py-2 whitespace-nowrap">jml</th>
											<th className="px-2.5 py-2 whitespace-nowrap">harga</th>
											<th className="px-2.5 py-2 whitespace-nowrap">diskon</th>
											<th className="px-2.5 py-2 whitespace-nowrap">total</th>
										</tr>
									</thead>
									<tbody>
										{items.map((item, i) => (
											<tr key={i}>
												<td className="px-2.5 py-2 whitespace-nowrap">{item.productName}</td>
												<td className="px-2.5 py-2 whitespace-nowrap text-center">
													{item.sizeName}
												</td>
												<td className="px-2.5 py-2 whitespace-nowrap text-center">
													{item.quantity}
												</td>
												<td className="px-2.5 py-2 whitespace-nowrap">{`Rp ${item.price.toLocaleString(
													"id-ID"
												)}`}</td>
												<td
													className={`px-2.5 py-2 whitespace-nowrap text-center ${
														item.discountAmount > 0 ? "text-green-600" : ""
													}`}
												>
													{item.discountAmount
														? `-Rp ${item.discountAmount.toLocaleString("id-ID")}`
														: "-"}
												</td>
												<td className="p-1 whitespace-nowrap">
													{item.total ? `Rp ${item.total.toLocaleString("id-ID")}` : "-"}
												</td>
											</tr>
										))}

										{Array.from({ length: gapRows }).map((_, idx) => (
											<tr key={`gap-${idx}`}>
												<td>&nbsp;</td>
												<td></td>
												<td></td>
												<td></td>
												<td></td>
												<td></td>
												<td></td>
											</tr>
										))}

										{/* Subtotal */}
										<tr style={{ borderTop: "1.5px solid #6D2315" }}>
											<td colSpan="5" className="px-2.5 py-2 text-right uppercase">
												Sub Total :
											</td>
											<td className="px-2.5 py-2 whitespace-nowrap">
												Rp {subtotal.toLocaleString("id-ID")}
											</td>
										</tr>

										{/* Diskon */}
										{invoice?.discount > 0 && (
											<tr className="text-green-500">
												<td colSpan="5" className="px-2.5 py-2 text-right uppercase">
													diskon ({discountPercent.toFixed(2)}%) :
												</td>
												<td className="px-2.5 py-2 whitespace-nowrap">
													-Rp{" "}
													{typeof invoice.discount === "number"
														? invoice.discount.toLocaleString("id-ID")
														: "0"}
												</td>
											</tr>
										)}

										{/* Ongkir */}
										<tr>
											<td colSpan="5" className="px-2.5 py-2 text-right uppercase">
												{shippingType ? `ongkir (${shippingType}) :` : "ongkir :"}
											</td>
											<td className="px-2.5 py-2 whitespace-nowrap">
												Rp {invoice?.shipping?.toLocaleString("id-ID")}
											</td>
										</tr>

										{/* Total */}
										<tr style={{ borderBottom: "1.5px solid #6D2315" }}>
											<td
												colSpan="5"
												className="px-2.5 py-2 text-right font-bold text-red-700 uppercase"
											>
												jumlah yang harus dibayar :
											</td>
											<td className="px-2.5 py-2 font-bold text-red-700 whitespace-nowrap">
												Rp{" "}
												{typeof invoice.totalPrice === "number"
													? invoice.totalPrice.toLocaleString("id-ID")
													: "0"}
											</td>
										</tr>
									</tbody>
								</table>
							</div>

							{/* Disclaimer */}
							<div className="text-red-600 pt-4 border-t border-gray-200">
								<span className="font-semibold text-sm">*Disclaimer</span>
								<br />
								<span className="text-black text-sm">
									Segala kerusakan yang terjadi selama pengiriman menjadi tanggung jawab pihak
									ekspedisi. Namun, kami siap membantu proses klaim ke pihak ekspedisi apabila
									terjadi kendala selama pengiriman.
								</span>
							</div>
						</div>
					</div>
				</div>
			</>
		);
	}
);

export default InvoicePreview;

```

--------------------------------------------------------------------------------
/src/app/dashboard/invoices/create/CreateInvoicePage.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { useEffect, useState } from "react";

import Link from "next/link";

import { Controller, useForm } from "react-hook-form";

import { supabaseBrowser } from "@/lib/supabaseBrowser";

import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";

import DatePicker from "@/components/dashboard/DatePicker";

import ProductCombobox from "./_components/ProductsCombobox";
import SizeCombobox from "./_components/SizeCombobox";

import { ChevronRight, Trash2 } from "lucide-react";
import { toast } from "sonner";

import { getAllProducts } from "@/lib/actions/products/getAllProducts";
import { getAllSizePrice } from "@/lib/actions/size-price/getAll";
import { submitInvoice } from "@/lib/actions/invoice/submitInvoice";
import { calculateDiscountAmount, calculateDiscountPercent } from "@/lib/utils";

export default function CreateInvoicePage() {
	const supabase = supabaseBrowser();
	const [user, setUser] = useState(null);

	const [products, setProducts] = useState([]);
	const [sizes, setSizes] = useState([]);

	const [invoiceDate, setInvoiceDate] = useState(new Date().toISOString());

	const [lastInvoiceNumber, setLastInvoiceNumber] = useState(null);

	const [shippingPrice, setShippingPrice] = useState(0);

	const {
		control,
		handleSubmit,
		formState: { errors },
		reset,
	} = useForm({
		defaultValues: {
			invoiceNumber: "",
			buyerName: "",
		},
		mode: "onChange",
	});

	const resetForm = () => {
		setInvoiceDate(new Date().toISOString());
		setShippingPrice(0);
		setDiscountInput(0);
		setItems([createEmptyItem()]);
	};

	const createEmptyItem = () => ({
		productId: "",
		sizePriceId: "",
		quantity: 1,
		price: 0,
		discountMode: "amount" | "percent",
		discountInput: "",
		discountAmount: 0,
		total: 0,
	});

	const [items, setItems] = useState([createEmptyItem()]);

	// general discount
	const [discountMode, setDiscountMode] = useState("amount");
	const [discountInput, setDiscountInput] = useState(0);

	useEffect(() => {
		const fetchInitialData = async () => {
			const [{ data: userData }, { data: lastInvoice }] = await Promise.all([
				supabase.auth.getUser(),
				supabase
					.from("Invoice")
					.select("invoiceNumber, invoiceDate")
					.order("invoiceDate", { ascending: false })
					.limit(1)
					.single(),
			]);

			if (userData?.user) setUser(userData.user);
			if (lastInvoice) setLastInvoiceNumber(lastInvoice.invoiceNumber);
		};

		fetchInitialData();
	}, []);

	const addItem = () => {
		setItems([...items, createEmptyItem()]);
	};

	const removeItem = (index) => {
		setItems((prev) => prev.filter((_, i) => i !== index));
	};

	useEffect(() => {
		const fetchOptions = async () => {
			const [{ data: prods }, { data: szs }] = await Promise.all([
				getAllProducts(),
				getAllSizePrice(),
			]);

			setProducts(prods || []);
			setSizes(szs || []);
		};

		fetchOptions();
	}, []);

	// calculate each item total and subtotal
	const updateItemField = (item, field, value, mode = null) => {
		if (field === "sizePriceId") {
			const selectedSize = sizes.find((s) => s.id === value);
			item.sizePriceId = value;
			item.price = selectedSize?.price || 0;
		} else if (field === "quantity") {
			const parsed = parseInt(value, 10);
			item.quantity = isNaN(parsed) || parsed < 1 ? 1 : parsed;
		} else if (field === "price") {
			const parsed = parseInt(value, 10);
			item.price = isNaN(parsed) ? 0 : parsed;
		} else if (field === "discountMode") {
			item.discountMode = value;
		} else if (field === "discountInput") {
			item.discountInput = value;
			item.discountMode = mode;
		} else {
			item[field] = value;
		}

		const qty = item.quantity || 0;
		const price = item.price || 0;
		const rawTotal = qty * price;

		const discountAmount = calculateDiscountAmount({
			quantity: item.quantity,
			price: item.price,
			discountInput: item.discountInput,
			discountMode: item.discountMode,
		});

		item.discountAmount = discountAmount;
		item.total = rawTotal - discountAmount;
	};

	const handleItemChange = (index, field, value, mode = null) => {
		const updatedItems = [...items];
		const item = updatedItems[index];

		updateItemField(item, field, value, mode);
		setItems(updatedItems);
	};

	const subtotal = items.reduce((sum, item) => sum + item.total, 0);

	const discountAmount =
		discountMode === "percent"
			? Math.round(((parseFloat(discountInput) || 0) / 100) * subtotal)
			: parseInt(discountInput) || 0;

	const discountPercent =
		discountMode === "amount"
			? ((parseInt(discountInput) || 0) / subtotal) * 100
			: parseFloat(discountInput) || 0;

	const totalPrice = subtotal + (parseInt(shippingPrice) || 0) - discountAmount;

	const onSubmit = async (data) => {
		if (!user) {
			toast.error("User not log in");
			return;
		}

		const isInvalid = items.some((item) => !item.productId || !item.sizePriceId);

		if (isInvalid) {
			toast.error("You must add product and size before submitting!");
			return;
		}

		const res = await submitInvoice({
			invoiceNumber: data.invoiceNumber,
			buyerName: data.buyerName.trim().toLowerCase(),
			invoiceDate,
			shippingPrice,
			discountAmount,
			totalPrice,
			items,
			user,
		});

		if (res.error) {
			toast.error(res.error);
		} else {
			toast.success(res.message);

			reset({
				invoiceNumber: "",
				buyerName: "",
			});

			resetForm();
		}
	};

	return (
		<section className="w-full px-4 py-6 bg-[#fffaf0]">
			<div className="bg-white rounded-xl shadow-md p-6 space-y-6 border border-[#f4e3d3]">
				{/* Breadcrumbs (Mobile Only) */}
				<div className="block md:hidden text-sm text-gray-500 mb-4">
					<nav className="flex items-center space-x-1">
						<Link className="text-gray-400" href="/dashboard/invoices">
							List Invoice
						</Link>
						<ChevronRight className="w-4 h-4 text-gray-400" />
						<span className="text-gray-700 font-medium">Create Invoice</span>
					</nav>
				</div>

				<Card className="border-0 shadow-none">
					<CardHeader className="text-center mb-2">
						<CardTitle className="font-bold text-3xl text-[#6D2315]">INVOICE</CardTitle>
					</CardHeader>

					<CardContent>
						<form onSubmit={handleSubmit(onSubmit)} className="space-y-8">
							{/* Basic Info */}
							<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
								<div>
									<Label className="py-2 block text-sm text-gray-700">Invoice Number</Label>
									<Controller
										name="invoiceNumber"
										control={control}
										rules={{
											required: "Invoice Number is required!",
											pattern: {
												value: /^\d{4}$/,
												message: "Invoice Number must be exactly 4 digits (0-9)",
											},
										}}
										render={({ field }) => (
											<Input
												{...field}
												placeholder={`Nomor invoice terakhir: ${lastInvoiceNumber || "0000"}`}
												maxLength={4}
												required
											/>
										)}
									/>
									{errors.invoiceNumber && (
										<p role="alert" className="text-sm text-red-500">
											{errors.invoiceNumber.message}
										</p>
									)}
								</div>
								<div>
									<Label className="py-2 block text-sm text-gray-700">Buyer Name</Label>
									<Controller
										name="buyerName"
										control={control}
										rules={{
											required: "Buyer Name is required!",
											pattern: {
												value: /^[A-Za-z\s]+$/,
												message: "Buyer Name must contain only letters and spaces",
											},
										}}
										render={({ field }) => <Input {...field} placeholder="Nama pembeli" required />}
									/>
									{errors.buyerName && (
										<p role="alert" className="text-sm text-red-500">
											{errors.buyerName.message}
										</p>
									)}
								</div>
								<div>
									<Label className="py-2 block text-sm text-gray-700">Invoice Date</Label>
									<DatePicker invoiceDate={invoiceDate} setInvoiceDate={setInvoiceDate} />
								</div>
							</div>

							{/* Item List */}
							<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
								{items.map((item, index) => (
									<div key={index}>
										<h3 className="text-sm font-medium text-[#6D2315] mb-1">Item {index + 1}</h3>

										<div className="bg-[#fffefb] border border-[#fceee4] rounded-md p-4 space-y-3">
											{/* Item Select */}
											<div>
												<Label className="text-sm text-gray-700 mb-1 block">Item</Label>
												<ProductCombobox
													products={products}
													value={item.productId}
													onChange={(val) => handleItemChange(index, "productId", val)}
												/>
											</div>

											{/* Size & Qty */}
											<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
												<div>
													<Label className="text-sm text-gray-700 mb-1 block">Size</Label>
													<SizeCombobox
														sizes={sizes}
														value={item.sizePriceId}
														onChange={(val, price) => {
															handleItemChange(index, "sizePriceId", val);
															handleItemChange(index, "price", price);
														}}
													/>
												</div>
												<div>
													<Label className="text-sm text-gray-700 mb-1 block">Qty</Label>
													{/* desktop */}
													<div className="hidden md:block">
														<Input
															type="number"
															value={item.quantity}
															onChange={(e) => handleItemChange(index, "quantity", e.target.value)}
															required
														/>
													</div>

													<div className="flex items-center gap-2 md:hidden">
														<Input
															type="number"
															value={item.quantity}
															onChange={(e) => handleItemChange(index, "quantity", e.target.value)}
															className="w-15 text-center"
														/>
														<button
															type="button"
															className="w-15 px-2 py-1 border rounded bg-rose-500 text-white"
															onClick={() =>
																handleItemChange(
																	index,
																	"quantity",
																	Math.max(1, Number(item.quantity) - 1)
																)
															}
														>
															-
														</button>
														<button
															type="button"
															className="w-15 px-2 py-1 border rounded bg-emerald-500 text-white"
															onClick={() =>
																handleItemChange(index, "quantity", Number(item.quantity) + 1)
															}
														>
															+
														</button>
													</div>
												</div>
											</div>

											{/* Price & Total */}
											<div className="grid grid-cols-2 gap-2">
												<div>
													<Label className="text-sm text-gray-700 mb-1 block">Price</Label>
													<Input
														type="number"
														value={item.price}
														disabled
														className="bg-gray-100"
													/>
												</div>
												<div>
													<Label className="text-sm text-gray-700 mb-1 block">Total</Label>
													<Input
														value={item.total.toLocaleString("id-ID")}
														disabled
														className="bg-gray-100"
													/>
												</div>
											</div>

											{/* Discount Each Item*/}
											<div>
												<Label className="text-sm text-gray-700 mb-1 block">
													Discount (Optional)
												</Label>
												<div className="grid grid-cols-2 gap-2">
													<div>
														<Label className="text-xs text-gray-500">Percent (%)</Label>
														<Input
															type="number"
															placeholder="%"
															min={0}
															max={100}
															step="any"
															value={
																item.discountMode === "percent"
																	? item.discountInput
																	: calculateDiscountPercent(item)
															}
															onChange={(e) =>
																handleItemChange(index, "discountInput", e.target.value, "percent")
															}
														/>
													</div>
													<div>
														<Label className="text-xs text-gray-500">Amount (Rp)</Label>
														<Input
															type="number"
															placeholder="Rp"
															min={0}
															value={
																item.discountMode === "amount"
																	? item.discountInput
																	: item.discountAmount
															}
															onChange={(e) =>
																handleItemChange(index, "discountInput", e.target.value, "amount")
															}
														/>
													</div>
												</div>
											</div>

											{/* Delete button */}
											<div className="flex justify-end">
												<Button
													type="button"
													variant="destructive"
													onClick={() => removeItem(index)}
													className="h-9 px-3"
												>
													<Trash2 className="w-4 h-4" />
												</Button>
											</div>
										</div>
									</div>
								))}

								{/* Add Item Button */}
								<div className="md:col-span-3">
									<Button
										type="button"
										onClick={addItem}
										className="mt-2 bg-[#6D2315] hover:bg-[#591c10] text-white"
									>
										+ Add Item
									</Button>
								</div>
							</div>

							<div className="grid grid-cols-1 md:grid-cols-12 gap-4">
								{/* Discount General */}
								<div className="md:col-span-4">
									<div className="bg-[#fffaf0] border border-[#f4e3d3] rounded-md px-4 py-3 h-full">
										<Label className="block text-sm text-gray-700 mb-2">Discount (Optional)</Label>
										<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
											<div>
												<Label className="text-xs text-gray-500 mb-1 block">Percent (%)</Label>
												<Input
													type="number"
													min={0}
													max={100}
													step="any"
													value={
														discountMode === "percent"
															? discountInput
															: discountPercent.toFixed(2) || 0
													}
													onChange={(e) => {
														setDiscountMode("percent");
														setDiscountInput(e.target.value);
													}}
												/>
											</div>

											<div>
												<Label className="text-xs text-gray-500 mb-1 block">Amount (Rp)</Label>
												<Input
													type="number"
													min={0}
													value={discountMode === "amount" ? discountInput : discountAmount}
													onChange={(e) => {
														setDiscountMode("amount");
														setDiscountInput(e.target.value);
													}}
												/>
											</div>
										</div>
									</div>
								</div>

								<div className="md:col-span-8 hidden md:block"></div>

								{/* Subtotal */}
								<div className="md:col-span-4">
									<Label className="py-2 block text-sm text-gray-700">Subtotal</Label>
									<Input
										value={subtotal.toLocaleString("id-ID")}
										disabled
										className="bg-gray-100"
									/>
								</div>

								{/* Shipping */}
								<div className="md:col-span-4">
									<Label className="py-2 block text-sm text-gray-700">Shipping Price</Label>
									<Input
										type="number"
										value={shippingPrice}
										onChange={(e) => setShippingPrice(e.target.value)}
									/>
								</div>

								{/* Total */}
								<div className="md:col-span-4">
									<Label className="py-2 block text-sm text-gray-700">Total Price</Label>
									<Input
										value={totalPrice.toLocaleString("id-ID")}
										disabled
										className="bg-gray-100"
									/>
								</div>
							</div>

							<Button type="submit" className="w-full bg-[#6D2315] hover:bg-[#591c10] text-white">
								Create Invoice
							</Button>
						</form>
					</CardContent>
				</Card>
			</div>
		</section>
	);
}

```

--------------------------------------------------------------------------------
/src/app/dashboard/invoices/UpdateInvoiceForm.jsx:
--------------------------------------------------------------------------------

```javascript
"use client";

import { useState, useEffect } from "react";

import { useRouter } from "next/navigation";
import Link from "next/link";

import { Controller, useForm } from "react-hook-form";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label";

import DatePicker from "@/components/dashboard/DatePicker";

import ProductCombobox from "./create/_components/ProductsCombobox";
import SizeCombobox from "./create/_components/SizeCombobox";
import StatusCombobox from "./_components/StatusCombobox";

import { getAllProducts } from "@/lib/actions/products/getAllProducts";
import { getAllSizePrice } from "@/lib/actions/size-price/getAll";
import { updateInvoice } from "@/lib/actions/invoice/updateInvoice";

import { calculateDiscountAmount, calculateDiscountPercent, getPageTitle } from "@/lib/utils";
import { ChevronRight, Trash2 } from "lucide-react";

import { toast } from "sonner";

export const metadata = {
	title: getPageTitle("Invoice Edit"),
};

export default function UpdateInvoiceForm({ invoice }) {
	const router = useRouter();

	const [products, setProducts] = useState([]);
	const [sizes, setSizes] = useState([]);

	const [invoiceDate, setInvoiceDate] = useState(invoice.invoiceDate?.split("T")[0] || "");
	const [items, setItems] = useState([]);
	const [shippingPrice, setShippingPrice] = useState(invoice.shipping || 0);
	const [status, setStatus] = useState(invoice.status || "pending");

	const [discountMode, setDiscountMode] = useState("amount");
	const [discountInput, setDiscountInput] = useState(0);

	const {
		control,
		handleSubmit,
		formState: { errors },
	} = useForm({
		defaultValues: {
			invoiceNumber: invoice.invoiceNumber || "",
			buyerName: invoice.buyerName || "",
		},
		mode: "onChange",
	});

	useEffect(() => {
		const fetchData = async () => {
			const { data: productsData } = await getAllProducts();
			const { data: sizeData } = await getAllSizePrice();

			setProducts(productsData || []);
			setSizes(sizeData || []);
		};

		fetchData();
	}, []);

	useEffect(() => {
		if (invoice?.items?.length) {
			const mappedItems = invoice.items.map((item) => {
				const quantity = item.quantity || 0;
				const price = item.sizePrice?.price || 0;
				const subtotal = quantity * price;

				const discountAmount = item.discountAmount || 0;

				return {
					productId: item.productId,
					sizePriceId: item.sizePriceId,
					quantity,
					price,
					discountAmount,
					discountInput: String(discountAmount),
					discountMode: "amount",
					total: subtotal - discountAmount,
				};
			});

			setItems(mappedItems);
		}

		if (invoice?.discount !== undefined) {
			setDiscountInput(String(invoice.discount));
			setDiscountMode("amount");
		}
	}, [invoice]);

	const onUpdate = async (data) => {
		const isInvalid = items.some((item) => !item.productId || !item.sizePriceId);

		if (isInvalid) {
			toast.error("You must add product and size before submitting!");
			return;
		}

		const result = await updateInvoice({
			invoiceId: invoice.id,
			invoiceData: {
				invoiceNumber: data.invoiceNumber,
				buyerName: data.buyerName.trim().toLowerCase(),
				invoiceDate,
				totalPrice,
				discount: discountAmount,
				shipping: parseInt(shippingPrice),
				status,
			},
			items,
		});

		if (!result.success) {
			toast.error(result.error || "Failed to update invoice");
			return;
		}

		toast.success("Invoice has been updated!");
		router.push("/dashboard/invoices");
	};

	const handleItemChange = (index, field, value, mode = null) => {
		const updatedItems = [...items];
		const item = updatedItems[index];

		if (field === "sizePriceId") {
			const selectedSize = sizes.find((s) => s.id === value);
			item.sizePriceId = value;
			item.price = selectedSize?.price || 0;
		} else if (field === "quantity") {
			const parsed = parseInt(value, 10);
			item.quantity = isNaN(parsed) || parsed < 1 ? 1 : parsed;
		} else if (field === "price") {
			const parsed = parseInt(value, 10);
			item.price = isNaN(parsed) ? 0 : parsed;
		} else if (field === "discountMode") {
			item.discountMode = value;
		} else if (field === "discountInput") {
			item.discountInput = value;
			item.discountMode = mode;
		} else {
			item[field] = value;
		}

		const qty = item.quantity || 0;
		const price = item.price || 0;

		const rawTotal = qty * price;

		const discountAmount = calculateDiscountAmount({
			quantity: item.quantity,
			price: item.price,
			discountInput: item.discountInput,
			discountMode: item.discountMode,
		});

		item.discountAmount = discountAmount;
		item.total = rawTotal - discountAmount;

		setItems(updatedItems);
	};

	const subtotal = items.reduce((sum, item) => sum + item.total, 0);

	const discountAmount =
		discountMode === "percent"
			? Math.round(((parseFloat(discountInput) || 0) / 100) * subtotal)
			: parseInt(discountInput) || 0;

	const discountPercent =
		discountMode === "amount"
			? ((parseInt(discountInput) || 0) / subtotal) * 100
			: parseFloat(discountInput) || 0;

	const totalPrice = subtotal + (parseInt(shippingPrice) || 0) - discountAmount;

	const addItem = () => {
		setItems([
			...items,
			{
				productId: "",
				sizePriceId: "",
				price: 0,
				quantity: 1,
				discountAmount: 0,
				discountInput: "0",
				discountMode: "amount",
				total: 0,
			},
		]);
	};

	const removeItem = (index) => {
		const updated = [...items];
		updated.splice(index, 1);
		setItems(updated);
	};

	return (
		<section className="w-full px-4 py-6 bg-[#fffaf0]">
			<div className="bg-white rounded-xl shadow-md p-6 space-y-6 border border-[#f4e3d3]">
				{/* Breadcrumbs (Mobile Only) */}
				<div className="block md:hidden text-sm text-gray-500 mb-4">
					<nav className="flex items-center space-x-1">
						<Link className="text-gray-400" href="/dashboard/invoices">
							List Invoice
						</Link>
						<ChevronRight className="w-4 h-4 text-gray-400" />
						<span className="text-gray-700 font-medium">Edit Invoice</span>
					</nav>
				</div>

				<Card className="border-0 shadow-none">
					<CardHeader className="text-center mb-2">
						<CardTitle className="font-bold text-3xl text-[#6D2315]">EDIT INVOICE</CardTitle>
					</CardHeader>

					<CardContent>
						<form onSubmit={handleSubmit(onUpdate)} className="space-y-8">
							{/* Basic Info */}
							<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
								<div>
									<Label className="py-2 block text-sm text-gray-700">Invoice Number</Label>
									<Controller
										name="invoiceNumber"
										control={control}
										rules={{
											required: "Invoice Number is required!",
											pattern: {
												value: /^\d{4}$/,
												message: "Invoice Number must be exactly 4 digits (0-9)",
											},
										}}
										render={({ field }) => <Input {...field} maxLength={4} required />}
									/>
									{errors.invoiceNumber && (
										<p role="alert" className="text-sm text-red-500">
											{errors.invoiceNumber.message}
										</p>
									)}
								</div>
								<div>
									<Label className="py-2 block text-sm text-gray-700">Buyer Name</Label>
									<Controller
										name="buyerName"
										control={control}
										rules={{
											required: "Buyer Name is required!",
											pattern: {
												value: /^[A-Za-z\s]+$/,
												message: "Buyer Name must contain only letters and spaces",
											},
										}}
										render={({ field }) => <Input {...field} placeholder="Nama pembeli" required />}
									/>
									{errors.buyerName && (
										<p role="alert" className="text-sm text-red-500">
											{errors.buyerName.message}
										</p>
									)}
								</div>
								<div>
									<Label className="py-2 block text-sm text-gray-700">Invoice Date</Label>
									<DatePicker invoiceDate={invoiceDate} setInvoiceDate={setInvoiceDate} />
								</div>
								<div className="">
									<Label className="py-2 block text-sm text-gray-700">Status</Label>
									<StatusCombobox value={status} onChange={setStatus} required />
								</div>
							</div>

							{/* Items */}
							<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
								{items.map((item, index) => (
									<div key={index}>
										<h3 className="text-sm font-medium text-[#6D2315] mb-1">Item {index + 1}</h3>

										<div className="bg-[#fffefb] border border-[#fceee4] rounded-md p-4 space-y-3">
											{/* Item Select */}
											<div>
												<Label className="text-sm text-gray-700 mb-1 block">Item</Label>
												<ProductCombobox
													products={products}
													value={item.productId}
													onChange={(val) => handleItemChange(index, "productId", val)}
												/>
											</div>

											{/* Size & Qty */}
											<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
												<div>
													<Label className="text-sm text-gray-700 mb-1 block">Size</Label>
													<SizeCombobox
														sizes={sizes}
														value={item.sizePriceId}
														onChange={(val, price) => {
															handleItemChange(index, "sizePriceId", val);
															handleItemChange(index, "price", price);
														}}
													/>
												</div>
												<div>
													<Label className="text-sm text-gray-700 mb-1 block">Qty</Label>
													{/* desktop */}
													<div className="hidden md:block">
														<Input
															type="number"
															value={item.quantity}
															onChange={(e) => handleItemChange(index, "quantity", e.target.value)}
															required
														/>
													</div>

													{/* mobile */}
													<div className="flex items-center gap-2 md:hidden">
														<Input
															type="number"
															value={item.quantity}
															onChange={(e) => handleItemChange(index, "quantity", e.target.value)}
															className="w-15 text-center"
														/>
														<button
															type="button"
															className="w-15 px-2 py-1 border rounded bg-rose-500 text-white"
															onClick={() =>
																handleItemChange(
																	index,
																	"quantity",
																	Math.max(1, Number(item.quantity) - 1)
																)
															}
														>
															-
														</button>
														<button
															type="button"
															className="w-15 px-2 py-1 border rounded bg-emerald-500 text-white"
															onClick={() =>
																handleItemChange(index, "quantity", Number(item.quantity) + 1)
															}
														>
															+
														</button>
													</div>
												</div>
											</div>

											{/* Price & Total */}
											<div className="grid grid-cols-2 gap-2">
												<div>
													<Label className="text-sm text-gray-700 mb-1 block">Price</Label>
													<Input
														type="number"
														value={item.price}
														disabled
														className="bg-gray-100"
													/>
												</div>
												<div>
													<Label className="text-sm text-gray-700 mb-1 block">Total</Label>
													<Input
														value={item.total.toLocaleString("id-ID")}
														disabled
														className="bg-gray-100"
													/>
												</div>
											</div>

											{/* Discount Each Item */}
											<div>
												<Label className="text-sm text-gray-700 mb-1 block">
													Discount (Optional)
												</Label>
												<div className="grid grid-cols-2 gap-2">
													<div>
														<Label className="text-xs text-gray-500">Percent (%)</Label>
														<Input
															type="number"
															placeholder="%"
															min={0}
															max={100}
															step="any"
															value={
																item.discountMode === "percent"
																	? item.discountInput
																	: calculateDiscountPercent(item)
															}
															onChange={(e) =>
																handleItemChange(index, "discountInput", e.target.value, "percent")
															}
														/>
													</div>
													<div>
														<Label className="text-xs text-gray-500">Amount (Rp)</Label>
														<Input
															type="number"
															placeholder="Rp"
															min={0}
															value={
																item.discountMode === "amount"
																	? item.discountInput
																	: item.discountAmount
															}
															onChange={(e) =>
																handleItemChange(index, "discountInput", e.target.value, "amount")
															}
														/>
													</div>
												</div>
											</div>

											{/* Delete button */}
											<div className="flex justify-end">
												<Button
													type="button"
													variant="destructive"
													onClick={() => removeItem(index)}
													className="h-9 px-3"
												>
													<Trash2 className="w-4 h-4" />
												</Button>
											</div>
										</div>
									</div>
								))}

								{/* Add Item Button */}
								<div className="md:col-span-3">
									<Button
										type="button"
										onClick={addItem}
										className="mt-2 bg-[#6D2315] hover:bg-[#591c10] text-white"
									>
										+ Add Item
									</Button>
								</div>
							</div>

							{/* Shipping & Total */}
							<div className="grid grid-cols-1 md:grid-cols-12 gap-4">
								{/* Discount General */}
								<div className="md:col-span-4">
									<div className="bg-[#fffaf0] border border-[#f4e3d3] rounded-md px-4 py-3 h-full">
										<Label className="block text-sm text-gray-700 mb-2">Discount (Optional)</Label>
										<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
											<div>
												<Label className="text-xs text-gray-500 mb-1 block">Percent (%)</Label>
												<Input
													type="number"
													min={0}
													max={100}
													step="any"
													value={
														discountMode === "percent"
															? discountInput
															: discountPercent.toFixed(2) || 0
													}
													onChange={(e) => {
														setDiscountMode("percent");
														setDiscountInput(e.target.value);
													}}
												/>
											</div>

											<div>
												<Label className="text-xs text-gray-500 mb-1 block">Amount (Rp)</Label>
												<Input
													type="number"
													min={0}
													value={discountMode === "amount" ? discountInput : discountAmount}
													onChange={(e) => {
														setDiscountMode("amount");
														setDiscountInput(e.target.value);
													}}
												/>
											</div>
										</div>
									</div>
								</div>

								<div className="md:col-span-8 hidden md:block"></div>

								{/* Subtotal */}
								<div className="md:col-span-4">
									<Label className="py-2 block text-sm text-gray-700">Subtotal</Label>
									<Input
										value={subtotal.toLocaleString("id-ID")}
										disabled
										className="bg-gray-100"
									/>
								</div>

								{/* Shipping */}
								<div className="md:col-span-4">
									<Label className="py-2 block text-sm text-gray-700">Shipping Price</Label>
									<Input
										type="number"
										value={shippingPrice}
										onChange={(e) => setShippingPrice(e.target.value)}
									/>
								</div>

								{/* Total */}
								<div className="md:col-span-4">
									<Label className="py-2 block text-sm text-gray-700">Total Price</Label>
									<Input
										value={totalPrice.toLocaleString("id-ID")}
										disabled
										className="bg-gray-100"
									/>
								</div>
							</div>

							<Button type="submit" className="w-full bg-[#6D2315] hover:bg-[#591c10] text-white">
								Save
							</Button>
						</form>
					</CardContent>
				</Card>
			</div>
		</section>
	);
}

```