feat: polish invoice editor and viewer UI with custom NumberInput

component

- Create custom NumberInput component with increment/decrement buttons
- Add 0.25 step increments for hours and rates in invoice forms
- Implement emerald-themed styling with hover states and accessibility
- Add keyboard navigation (arrow keys) and proper ARIA support
- Condense invoice editor tax/totals section into efficient grid layout
- Update client dropdown to single-line format (name + email)
- Add fixed footer with floating action bar pattern matching business
  forms
- Redesign invoice viewer with better space utilization and visual
  hierarchy
- Maintain professional appearance and consistent design system
- Fix Next.js 15 params Promise handling across all invoice pages
- Resolve TypeScript compilation errors and type-only imports
This commit is contained in:
2025-07-15 00:29:02 -04:00
parent 89de059501
commit f331136090
79 changed files with 9944 additions and 4223 deletions
+202
View File
@@ -0,0 +1,202 @@
"use client";
import { useState } from "react";
import { DataTable } from "~/components/ui/data-table";
import { PageHeader } from "~/components/page-header";
import { Button } from "~/components/ui/button";
import { Plus } from "lucide-react";
import type { ColumnDef } from "@tanstack/react-table";
import { DataTableColumnHeader } from "~/components/ui/data-table";
import { DashboardBreadcrumbs } from "~/components/dashboard-breadcrumbs";
import Link from "next/link";
// Sample data type
interface DemoItem {
id: string;
name: string;
email: string;
phone: string;
status: string;
createdAt: Date;
}
// Generate sample data
const sampleData: DemoItem[] = Array.from({ length: 50 }, (_, i) => ({
id: `item-${i + 1}`,
name: `Item ${i + 1}`,
email: `item${i + 1}@example.com`,
phone: `555-${String(Math.floor(Math.random() * 9000) + 1000)}`,
status: ["active", "pending", "inactive"][
Math.floor(Math.random() * 3)
] as string,
createdAt: new Date(Date.now() - Math.random() * 10000000000),
}));
// Define columns with responsive behavior
const columns: ColumnDef<DemoItem>[] = [
{
accessorKey: "name",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Name" />
),
cell: ({ row }) => (
<div>
<p className="font-medium">{row.original.name}</p>
<p className="text-muted-foreground text-xs">{row.original.email}</p>
</div>
),
},
{
accessorKey: "phone",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Phone" />
),
cell: ({ row }) => row.original.phone,
meta: {
headerClassName: "hidden md:table-cell",
cellClassName: "hidden md:table-cell",
},
},
{
accessorKey: "status",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Status" />
),
cell: ({ row }) => (
<span
className={`inline-flex items-center rounded-full px-2 py-1 text-xs font-medium ${
row.original.status === "active"
? "bg-green-100 text-green-700"
: row.original.status === "pending"
? "bg-yellow-100 text-yellow-700"
: "bg-gray-100 text-gray-700"
}`}
>
{row.original.status}
</span>
),
},
{
accessorKey: "createdAt",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Created" />
),
cell: ({ row }) => {
const date = row.getValue("createdAt") as Date;
return new Intl.DateTimeFormat("en-US", {
month: "short",
day: "2-digit",
year: "numeric",
}).format(date);
},
meta: {
headerClassName: "hidden lg:table-cell",
cellClassName: "hidden lg:table-cell",
},
},
];
export default function TableLayoutDemo() {
const [data] = useState(sampleData);
const filterableColumns = [
{
id: "status",
title: "Status",
options: [
{ label: "Active", value: "active" },
{ label: "Pending", value: "pending" },
{ label: "Inactive", value: "inactive" },
],
},
];
return (
<div className="container mx-auto py-10">
<DashboardBreadcrumbs />
<PageHeader
title="Table Layout & Breadcrumb Demo"
description="This demo showcases the improved responsive layouts and dynamic breadcrumbs. The breadcrumbs automatically handle pluralization and capitalization. Navigate to different pages to see how they adapt."
variant="gradient"
>
<Button variant="brand" size="lg">
<Plus className="mr-2 h-5 w-5" />
Add Item
</Button>
</PageHeader>
<div className="mt-8">
<DataTable
columns={columns}
data={data}
searchPlaceholder="Search items..."
filterableColumns={filterableColumns}
showColumnVisibility={true}
showPagination={true}
showSearch={true}
pageSize={10}
/>
</div>
<div className="mt-16 space-y-4">
<h2 className="text-2xl font-bold">Layout Improvements</h2>
<div className="text-muted-foreground space-y-2 text-sm">
<p>
Page header: Description text wraps below the title and action
buttons
</p>
<p>
Filter bar: Search and filters stay inline on mobile with proper
wrapping
</p>
<p>
Pagination bar: Entry count and controls remain on the same line
on mobile
</p>
<p>
Columns: Responsive hiding with both headers and cells hidden
together
</p>
<p>
Compact design: Tighter padding for more efficient space usage
</p>
</div>
<h2 className="mt-8 text-2xl font-bold">Dynamic Breadcrumbs</h2>
<div className="text-muted-foreground space-y-2 text-sm">
<p>
Automatic pluralization: "Business" becomes "Businesses" on list
pages
</p>
<p> Smart capitalization: Route segments are properly capitalized</p>
<p> Context awareness: Shows resource names instead of UUIDs</p>
<p>
Clean presentation: Edit pages show the resource name, not "Edit"
</p>
</div>
<div className="mt-4 space-y-2">
<p className="text-sm font-medium">Try these example routes:</p>
<div className="flex flex-wrap gap-2">
<Link href="/dashboard/businesses">
<Button variant="outline" size="sm">
Businesses List
</Button>
</Link>
<Link href="/dashboard/clients">
<Button variant="outline" size="sm">
Clients List
</Button>
</Link>
<Link href="/dashboard/invoices">
<Button variant="outline" size="sm">
Invoices List
</Button>
</Link>
</div>
</div>
</div>
</div>
);
}