mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2026-05-08 09:38:55 -04:00
Update README and improve mobile responsiveness for invoicing UI
- README: fix auth (better-auth), database (PostgreSQL), env vars, Docker setup, and feature list to reflect actual implementation - InvoicesDataTable: show status badge + amount inline on mobile (previously hidden behind sm: breakpoint, leaving mobile users with no financial or status info at a glance) - InvoiceItemsTable: hide Date/Hours/Rate columns on mobile and fold that info into the Description cell as secondary text - invoice-view.tsx header card: wrap to column layout on mobile so status/amount/button don't overflow narrow screens; also improve item rows to show date, hours, and rate as subtext https://claude.ai/code/session_012sqEgNQpx676isepeoX4Mi
This commit is contained in:
@@ -40,13 +40,32 @@ const columns: ColumnDef<InvoiceItem>[] = [
|
||||
accessorKey: "date",
|
||||
header: "Date",
|
||||
cell: ({ row }) => formatDate(row.getValue("date")),
|
||||
meta: {
|
||||
headerClassName: "hidden sm:table-cell",
|
||||
cellClassName: "hidden sm:table-cell",
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "Description",
|
||||
cell: ({ row }) => (
|
||||
<div className="font-medium">{row.getValue("description")}</div>
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const item = row.original;
|
||||
return (
|
||||
<>
|
||||
{/* Desktop: plain description */}
|
||||
<div className="hidden font-medium sm:block">
|
||||
{item.description}
|
||||
</div>
|
||||
{/* Mobile: description + date + hours @ rate stacked */}
|
||||
<div className="sm:hidden">
|
||||
<p className="font-medium">{item.description}</p>
|
||||
<p className="text-muted-foreground mt-0.5 text-xs">
|
||||
{formatDate(item.date)} · {item.hours}h @ {formatCurrency(item.rate)}/hr
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "hours",
|
||||
@@ -54,6 +73,10 @@ const columns: ColumnDef<InvoiceItem>[] = [
|
||||
cell: ({ row }) => (
|
||||
<div className="text-right">{row.getValue("hours")}</div>
|
||||
),
|
||||
meta: {
|
||||
headerClassName: "hidden sm:table-cell",
|
||||
cellClassName: "hidden sm:table-cell",
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "rate",
|
||||
@@ -61,6 +84,10 @@ const columns: ColumnDef<InvoiceItem>[] = [
|
||||
cell: ({ row }) => (
|
||||
<div className="text-right">{formatCurrency(row.getValue("rate"))}</div>
|
||||
),
|
||||
meta: {
|
||||
headerClassName: "hidden sm:table-cell",
|
||||
cellClassName: "hidden sm:table-cell",
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "amount",
|
||||
|
||||
@@ -134,13 +134,23 @@ export function InvoicesDataTable({ invoices }: InvoicesDataTableProps) {
|
||||
<div className="bg-primary/10 hidden p-2 sm:flex">
|
||||
<FileText className="text-primary h-4 w-4" />
|
||||
</div>
|
||||
<div className="max-w-[80px] min-w-0 sm:max-w-[200px] lg:max-w-[300px]">
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate font-medium">
|
||||
{invoice.client?.name ?? "—"}
|
||||
</p>
|
||||
<p className="text-muted-foreground truncate text-xs sm:text-sm">
|
||||
{invoice.invoiceNumber}
|
||||
</p>
|
||||
{/* Show status + amount inline on mobile only */}
|
||||
<div className="mt-1 flex items-center gap-2 sm:hidden">
|
||||
<StatusBadge
|
||||
status={getStatusType(invoice)}
|
||||
className="text-xs"
|
||||
/>
|
||||
<span className="text-foreground text-xs font-semibold">
|
||||
{formatCurrency(invoice.totalAmount)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -185,14 +185,14 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
||||
{/* Invoice Header Card */}
|
||||
<Card className="bg-card border-border border">
|
||||
<CardContent>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div className="min-w-0 flex-1 space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="bg-primary/10 p-2">
|
||||
<div className="bg-primary/10 flex-shrink-0 p-2">
|
||||
<FileText className="text-primary h-6 w-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-foreground text-2xl font-bold">
|
||||
<div className="min-w-0">
|
||||
<h2 className="text-foreground truncate text-2xl font-bold">
|
||||
{invoice.invoiceNumber}
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
@@ -217,21 +217,23 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 text-right">
|
||||
<StatusBadge
|
||||
status={invoice.status as StatusType}
|
||||
className="px-3 py-1 text-sm font-medium"
|
||||
>
|
||||
<StatusIcon className="mr-1 h-3 w-3" />
|
||||
</StatusBadge>
|
||||
<div className="text-primary text-3xl font-bold">
|
||||
{formatCurrency(invoice.totalAmount)}
|
||||
<div className="flex flex-row items-center justify-between gap-3 sm:flex-col sm:items-end sm:text-right">
|
||||
<div>
|
||||
<StatusBadge
|
||||
status={invoice.status as StatusType}
|
||||
className="px-3 py-1 text-sm font-medium"
|
||||
>
|
||||
<StatusIcon className="mr-1 h-3 w-3" />
|
||||
</StatusBadge>
|
||||
<div className="text-primary mt-1 text-2xl font-bold sm:text-3xl">
|
||||
{formatCurrency(invoice.totalAmount)}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handlePDFExport}
|
||||
disabled={isExportingPDF}
|
||||
variant="default"
|
||||
className="transform-none"
|
||||
className="transform-none flex-shrink-0"
|
||||
>
|
||||
{isExportingPDF ? (
|
||||
<>
|
||||
@@ -326,17 +328,18 @@ export function InvoiceView({ invoiceId }: InvoiceViewProps) {
|
||||
{invoice.items?.map((item, index) => (
|
||||
<div
|
||||
key={item.id || index}
|
||||
className="bg-background flex items-center justify-between rounded-lg p-4"
|
||||
className="bg-background flex flex-col gap-1 rounded-lg p-4 sm:flex-row sm:items-center sm:justify-between"
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-muted-foreground text-sm">
|
||||
{formatDate(item.date)}
|
||||
</div>
|
||||
<div className="text-foreground font-medium">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-foreground font-medium break-words">
|
||||
{item.description}
|
||||
</div>
|
||||
<div className="text-muted-foreground mt-0.5 text-sm">
|
||||
{formatDate(item.date)} · {item.hours}h @{" "}
|
||||
{formatCurrency(item.rate)}/hr
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-foreground text-right font-medium">
|
||||
<div className="text-foreground flex-shrink-0 font-medium sm:text-right">
|
||||
{formatCurrency(item.amount)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user