diff --git a/app/(app)/apps/invoices/components/invoice-generator.tsx b/app/(app)/apps/invoices/components/invoice-generator.tsx
index 4ee985d..049798b 100644
--- a/app/(app)/apps/invoices/components/invoice-generator.tsx
+++ b/app/(app)/apps/invoices/components/invoice-generator.tsx
@@ -25,7 +25,13 @@ function invoiceFormReducer(state: InvoiceFormData, action: any): InvoiceFormDat
case "UPDATE_FIELD":
return { ...state, [action.field]: action.value }
case "ADD_ITEM":
- return { ...state, items: [...state.items, { description: "", quantity: 1, unitPrice: 0, subtotal: 0 }] }
+ return {
+ ...state,
+ items: [
+ ...state.items,
+ { name: "", subtitle: "", showSubtitle: false, quantity: 1, unitPrice: 0, subtotal: 0 },
+ ],
+ }
case "UPDATE_ITEM": {
const items = [...state.items]
items[action.index] = { ...items[action.index], [action.field]: action.value }
@@ -146,7 +152,6 @@ export function InvoiceGenerator({
}
try {
- // Get existing templates
const result = await addNewTemplateAction(user, {
id: `tmpl_${Math.random().toString(36).substring(2, 15)}`,
name: newTemplateName,
diff --git a/app/(app)/apps/invoices/components/invoice-page.tsx b/app/(app)/apps/invoices/components/invoice-page.tsx
index c204b75..bd77a9e 100644
--- a/app/(app)/apps/invoices/components/invoice-page.tsx
+++ b/app/(app)/apps/invoices/components/invoice-page.tsx
@@ -7,7 +7,9 @@ import { X } from "lucide-react"
import { InputHTMLAttributes, memo, useCallback, useMemo } from "react"
export interface InvoiceItem {
- description: string
+ name: string
+ subtitle: string
+ showSubtitle: boolean
quantity: number
unitPrice: number
subtotal: number
@@ -67,49 +69,92 @@ const ItemRow = memo(function ItemRow({
}: {
item: InvoiceItem
index: number
- onChange: (index: number, field: keyof InvoiceItem, value: string | number) => void
+ onChange: (index: number, field: keyof InvoiceItem, value: string | number | boolean) => void
onRemove: (index: number) => void
currency: string
}) {
return (
-
- |
- onChange(index, "description", e.target.value)}
- className="w-full min-w-64"
- placeholder="Item description"
- required
- />
- |
-
- onChange(index, "quantity", Number(e.target.value))}
- className="w-20 text-right"
- required
- />
- |
-
- onChange(index, "unitPrice", Number(e.target.value))}
- className="w-24 text-right"
- required
- />
- |
- {formatCurrency(item.subtotal * 100, currency)} |
-
+
+ {/* Mobile view label (visible only on small screens) */}
+
+ Item
- |
-
+
+
+ {/* Item name and subtitle */}
+
+
+
onChange(index, "name", e.target.value)}
+ className="w-full min-w-0 font-semibold"
+ placeholder="Item name"
+ required
+ />
+
+ {!item.showSubtitle ? (
+
+ ) : (
+ onChange(index, "subtitle", e.target.value)}
+ className="w-full mt-1 text-xs text-muted-foreground"
+ placeholder="Detailed description (optional)"
+ />
+ )}
+
+
+
+
+ {/* Mobile labels for small screens */}
+
+
Quantity
+
Unit Price
+
Subtotal
+
+
+ {/* Quantity, Unit Price, Subtotal, and Remove button */}
+
+
+ onChange(index, "quantity", Number(e.target.value))}
+ className="w-full text-right"
+ required
+ />
+
+
+ onChange(index, "unitPrice", Number(e.target.value))}
+ className="w-full text-right"
+ required
+ />
+
+
+ {formatCurrency(item.subtotal * 100, currency)}
+
+
+
+
+
+
)
})
@@ -196,7 +241,7 @@ export function InvoicePage({ invoiceData, dispatch, currencies }: InvoicePagePr
const addItem = useCallback(() => dispatch({ type: "ADD_ITEM" }), [dispatch])
const removeItem = useCallback((index: number) => dispatch({ type: "REMOVE_ITEM", index }), [dispatch])
const updateItem = useCallback(
- (index: number, field: keyof InvoiceItem, value: string | number) =>
+ (index: number, field: keyof InvoiceItem, value: string | number | boolean) =>
dispatch({ type: "UPDATE_ITEM", index, field, value }),
[dispatch]
)
@@ -350,125 +395,60 @@ export function InvoicePage({ invoiceData, dispatch, currencies }: InvoicePagePr
- {/* Items Table */}
+ {/* Items Section - Refactored to use only flex divs */}
- {/* Table for desktop/tablet */}
-
-
-
-
- |
- dispatch({ type: "UPDATE_FIELD", field: "itemLabel", value: e.target.value })}
- className="text-xs font-medium text-gray-500 uppercase tracking-wider"
- />
- |
-
-
- dispatch({ type: "UPDATE_FIELD", field: "quantityLabel", value: e.target.value })
- }
- className="text-xs font-medium text-gray-500 uppercase tracking-wider"
- />
- |
-
-
- dispatch({ type: "UPDATE_FIELD", field: "unitPriceLabel", value: e.target.value })
- }
- className="text-xs font-medium text-gray-500 uppercase tracking-wider"
- />
- |
-
-
- dispatch({ type: "UPDATE_FIELD", field: "subtotalLabel", value: e.target.value })
- }
- className="text-xs font-medium text-gray-500 uppercase tracking-wider"
- />
- |
- |
-
-
-
- {invoiceData.items.map((item, index) => (
-
- ))}
-
-
+ {/* Header row for column titles */}
+
+
+ dispatch({ type: "UPDATE_FIELD", field: "itemLabel", value: e.target.value })}
+ className="text-xs font-medium text-gray-500 uppercase tracking-wider"
+ />
+
+
+ dispatch({ type: "UPDATE_FIELD", field: "quantityLabel", value: e.target.value })}
+ className="text-xs font-medium text-gray-500 uppercase tracking-wider text-right w-full"
+ />
+
+
+ dispatch({ type: "UPDATE_FIELD", field: "unitPriceLabel", value: e.target.value })}
+ className="text-xs font-medium text-gray-500 uppercase tracking-wider text-right w-full"
+ />
+
+
+ dispatch({ type: "UPDATE_FIELD", field: "subtotalLabel", value: e.target.value })}
+ className="text-xs font-medium text-gray-500 uppercase tracking-wider text-right w-full"
+ />
+
+
- {/* Flex list for mobile */}
-
+
+ {/* Invoice items */}
+
{invoiceData.items.map((item, index) => (
-
-
-
-
- updateItem(index, "description", e.target.value)}
- className="w-full min-w-0"
- placeholder="Item description"
- required
- />
-
-
-
-
- updateItem(index, "quantity", Number(e.target.value))}
- className="w-full text-right"
- required
- />
-
-
-
- updateItem(index, "unitPrice", Number(e.target.value))}
- className="w-full text-right"
- required
- />
-
-
-
-
- {formatCurrency(item.subtotal * 100, invoiceData.currency)}
-
-
-
-
+
))}
+
diff --git a/app/(app)/apps/invoices/components/invoice-pdf.tsx b/app/(app)/apps/invoices/components/invoice-pdf.tsx
index b409656..dc618f9 100644
--- a/app/(app)/apps/invoices/components/invoice-pdf.tsx
+++ b/app/(app)/apps/invoices/components/invoice-pdf.tsx
@@ -210,6 +210,14 @@ const styles = StyleSheet.create({
fontSize: 12,
color: "#000000",
},
+ colName: {
+ fontWeight: "semibold",
+ },
+ itemSubtitle: {
+ fontSize: 10,
+ color: "#6B7280",
+ marginTop: 2,
+ },
notes: {
marginBottom: 30,
fontSize: 12,
@@ -342,7 +350,10 @@ export function InvoicePDF({ data }: { data: InvoiceFormData }): ReactElement {
{data.items.map((item: InvoiceItem, index: number) => (
- {item.description}
+
+ {item.name}
+ {item.showSubtitle && item.subtitle && {item.subtitle}}
+
{item.quantity}
{formatCurrency(item.unitPrice * 100, data.currency)}
diff --git a/app/(app)/apps/invoices/default-templates.ts b/app/(app)/apps/invoices/default-templates.ts
index fcbacf2..a473852 100644
--- a/app/(app)/apps/invoices/default-templates.ts
+++ b/app/(app)/apps/invoices/default-templates.ts
@@ -21,7 +21,7 @@ export default function defaultTemplates(user: User, settings: SettingsMap): Inv
companyDetailsLabel: "Bill From",
billTo: "",
billToLabel: "Bill To",
- items: [{ description: "", quantity: 1, unitPrice: 0, subtotal: 0 }],
+ items: [{ name: "", subtitle: "", showSubtitle: false, quantity: 1, unitPrice: 0, subtotal: 0 }],
taxIncluded: true,
additionalTaxes: [{ name: "VAT", rate: 0, amount: 0 }],
additionalFees: [],
@@ -48,7 +48,7 @@ export default function defaultTemplates(user: User, settings: SettingsMap): Inv
companyDetailsLabel: "Rechnungssteller",
billTo: "",
billToLabel: "Rechnungsempfänger",
- items: [{ description: "", quantity: 1, unitPrice: 0, subtotal: 0 }],
+ items: [{ name: "", subtitle: "", showSubtitle: false, quantity: 1, unitPrice: 0, subtotal: 0 }],
taxIncluded: true,
additionalTaxes: [{ name: "MwSt", rate: 19, amount: 0 }],
additionalFees: [],