feat: enhance dashboard layout and UI components for improved responsiveness

- Introduce new 'xs' screen size in Tailwind configuration for better mobile support
- Update dashboard layout to use a cosmic gradient background for a modern look
- Refactor Quick Actions component for improved styling and layout consistency
- Add Current Open Invoice Card for quick access to ongoing invoices
- Standardize button sizes across various components for a cohesive user experience
- Implement responsive design adjustments in invoice forms and data tables

This update enhances the overall user experience by improving responsiveness and visual appeal across the dashboard and related components.
This commit is contained in:
2025-07-15 03:04:10 -04:00
parent c9a664869c
commit c0279c4095
16 changed files with 432 additions and 140 deletions
+11 -8
View File
@@ -722,9 +722,10 @@ function InvoiceEditor({ invoiceId }: { invoiceId: string }) {
variant="outline"
disabled={isLoading}
className="border-border/40 hover:bg-accent/50"
size="sm"
>
<ArrowLeft className="mr-2 h-4 w-4" />
Cancel
<ArrowLeft className="h-4 w-4 sm:mr-2" />
<span className="hidden sm:inline">Cancel</span>
</Button>
</Link>
<Button
@@ -732,25 +733,27 @@ function InvoiceEditor({ invoiceId }: { invoiceId: string }) {
disabled={isLoading || !isFormValid()}
variant="outline"
className="border-border/40 hover:bg-accent/50"
size="sm"
>
{isLoading ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="h-4 w-4 animate-spin sm:mr-2" />
) : (
<Save className="mr-2 h-4 w-4" />
<Save className="h-4 w-4 sm:mr-2" />
)}
Save Draft
<span className="hidden sm:inline">Save Draft</span>
</Button>
<Button
onClick={handleUpdateInvoice}
disabled={isLoading || !isFormValid()}
className="bg-gradient-to-r from-emerald-600 to-teal-600 shadow-md transition-all duration-200 hover:from-emerald-700 hover:to-teal-700 hover:shadow-lg"
size="sm"
>
{isLoading ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="h-4 w-4 animate-spin sm:mr-2" />
) : (
<Send className="mr-2 h-4 w-4" />
<Send className="h-4 w-4 sm:mr-2" />
)}
Update Invoice
<span className="hidden sm:inline">Update Invoice</span>
</Button>
</FloatingActionBar>
</div>
@@ -79,28 +79,6 @@ const formatCurrency = (amount: number) => {
};
const columns: ColumnDef<Invoice>[] = [
{
accessorKey: "invoiceNumber",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Invoice" />
),
cell: ({ row }) => {
const invoice = row.original;
return (
<div className="flex items-center gap-3">
<div className="bg-status-success-muted hidden rounded-lg p-2 sm:flex">
<FileText className="text-status-success h-4 w-4" />
</div>
<div className="min-w-0">
<p className="truncate font-medium">{invoice.invoiceNumber}</p>
<p className="text-muted-foreground truncate text-sm">
{invoice.items?.length || 0} items
</p>
</div>
</div>
);
},
},
{
accessorKey: "client.name",
header: ({ column }) => (
@@ -109,10 +87,10 @@ const columns: ColumnDef<Invoice>[] = [
cell: ({ row }) => {
const invoice = row.original;
return (
<div className="min-w-0">
<p className="truncate font-medium">{invoice.client?.name || "—"}</p>
<p className="text-muted-foreground truncate text-sm">
{invoice.client?.email || "—"}
<div className="min-w-0 max-w-[80px] sm:max-w-[200px] lg:max-w-[300px]">
<p className="truncate font-medium">{invoice.client?.name ?? "—"}</p>
<p className="text-muted-foreground truncate text-xs sm:text-sm">
{invoice.invoiceNumber}
</p>
</div>
);
@@ -121,33 +99,18 @@ const columns: ColumnDef<Invoice>[] = [
{
accessorKey: "issueDate",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Issue Date" />
),
cell: ({ row }) => formatDate(row.getValue("issueDate")),
meta: {
headerClassName: "hidden md:table-cell",
cellClassName: "hidden md:table-cell",
},
},
{
accessorKey: "dueDate",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Due Date" />
),
cell: ({ row }) => formatDate(row.getValue("dueDate")),
meta: {
headerClassName: "hidden lg:table-cell",
cellClassName: "hidden lg:table-cell",
},
},
{
accessorKey: "totalAmount",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Amount" />
<DataTableColumnHeader column={column} title="Date" />
),
cell: ({ row }) => {
const amount = row.getValue("totalAmount") as number;
return <p className="font-semibold">{formatCurrency(amount)}</p>;
const date = row.getValue("issueDate") as Date;
return (
<div className="min-w-0">
<p className="truncate text-sm">{formatDate(date)}</p>
<p className="text-muted-foreground truncate text-xs">
Due {formatDate(new Date(row.original.dueDate))}
</p>
</div>
);
},
},
{
@@ -164,6 +127,31 @@ const columns: ColumnDef<Invoice>[] = [
const status = getStatusType(invoice);
return value.includes(status);
},
meta: {
headerClassName: "hidden xs:table-cell",
cellClassName: "hidden xs:table-cell",
},
},
{
accessorKey: "totalAmount",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Amount" />
),
cell: ({ row }) => {
const amount = row.getValue("totalAmount") as number;
return (
<div className="text-right">
<p className="font-semibold text-sm">{formatCurrency(amount)}</p>
<p className="text-muted-foreground text-xs">
{row.original.items?.length ?? 0} items
</p>
</div>
);
},
meta: {
headerClassName: "hidden xs:table-cell",
cellClassName: "hidden xs:table-cell",
},
},
{
id: "actions",
+11 -8
View File
@@ -704,9 +704,10 @@ export default function NewInvoicePage() {
variant="outline"
disabled={isLoading}
className="border-border/40 hover:bg-accent/50"
size="sm"
>
<ArrowLeft className="mr-2 h-4 w-4" />
Cancel
<ArrowLeft className="h-4 w-4 sm:mr-2" />
<span className="hidden sm:inline">Cancel</span>
</Button>
</Link>
<Button
@@ -714,25 +715,27 @@ export default function NewInvoicePage() {
disabled={isLoading || !isFormValid()}
variant="outline"
className="border-border/40 hover:bg-accent/50"
size="sm"
>
{isLoading ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="h-4 w-4 animate-spin sm:mr-2" />
) : (
<Save className="mr-2 h-4 w-4" />
<Save className="h-4 w-4 sm:mr-2" />
)}
Save Draft
<span className="hidden sm:inline">Save Draft</span>
</Button>
<Button
onClick={handleCreateInvoice}
disabled={isLoading || !isFormValid()}
className="bg-gradient-to-r from-emerald-600 to-teal-600 shadow-md transition-all duration-200 hover:from-emerald-700 hover:to-teal-700 hover:shadow-lg"
size="sm"
>
{isLoading ? (
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
<Loader2 className="h-4 w-4 animate-spin sm:mr-2" />
) : (
<Send className="mr-2 h-4 w-4" />
<Send className="h-4 w-4 sm:mr-2" />
)}
Create Invoice
<span className="hidden sm:inline">Create Invoice</span>
</Button>
</FloatingActionBar>
</div>
+4 -4
View File
@@ -8,23 +8,23 @@ export default function DashboardLayout({
children: React.ReactNode;
}) {
return (
<>
<div className="bg-cosmic-gradient bg-nebula-overlay relative min-h-screen">
<Navbar />
<Sidebar />
{/* Mobile layout - no left margin */}
<main className="min-h-screen pt-20 md:hidden">
<main className="relative z-10 min-h-screen pt-20 md:hidden">
<div className="px-4 pt-4 pb-6 sm:px-6">
<DashboardBreadcrumbs />
{children}
</div>
</main>
{/* Desktop layout - with sidebar margin */}
<main className="hidden min-h-screen pt-20 md:ml-[276px] md:block">
<main className="relative z-10 hidden min-h-screen pt-20 md:ml-[276px] md:block">
<div className="px-6 pt-6 pb-6">
<DashboardBreadcrumbs />
{children}
</div>
</main>
</>
</div>
);
}
+43 -35
View File
@@ -6,6 +6,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import { StatusBadge, type StatusType } from "~/components/data/status-badge";
import { DataTableSkeleton } from "~/components/data/data-table";
import { CurrentOpenInvoiceCard } from "~/components/data/current-open-invoice-card";
import { auth } from "~/server/auth";
import Link from "next/link";
import {
@@ -98,39 +99,43 @@ async function DashboardStats() {
// Quick Actions Component
function QuickActions() {
return (
<Card className="mb-6 border-0 shadow-sm">
<CardContent className="p-4 py-0">
<div className="flex flex-col gap-3 sm:flex-row sm:gap-4">
<Button
asChild
className="flex-1 bg-gradient-to-r from-emerald-600 to-teal-600 text-white shadow-sm hover:from-emerald-700 hover:to-teal-700"
>
<Link href="/dashboard/invoices/new">
<FileText className="mr-2 h-4 w-4" />
Create Invoice
</Link>
</Button>
<Button
asChild
variant="outline"
className="flex-1 border-0 shadow-sm"
>
<Link href="/dashboard/clients/new">
<Users className="mr-2 h-4 w-4" />
Add Client
</Link>
</Button>
<Button
asChild
variant="outline"
className="flex-1 border-0 shadow-sm"
>
<Link href="/dashboard/businesses/new">
<TrendingUp className="mr-2 h-4 w-4" />
Add Business
</Link>
</Button>
</div>
<Card className="border-0 shadow-sm">
<CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2 text-lg">
<Plus className="h-5 w-5 text-emerald-600" />
Quick Actions
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<Button
asChild
className="w-full bg-gradient-to-r from-emerald-600 to-teal-600 text-white shadow-sm hover:from-emerald-700 hover:to-teal-700"
>
<Link href="/dashboard/invoices/new">
<FileText className="mr-2 h-4 w-4" />
Create Invoice
</Link>
</Button>
<Button
asChild
variant="outline"
className="w-full border-0 shadow-sm"
>
<Link href="/dashboard/clients/new">
<Users className="mr-2 h-4 w-4" />
Add Client
</Link>
</Button>
<Button
asChild
variant="outline"
className="w-full border-0 shadow-sm"
>
<Link href="/dashboard/businesses/new">
<TrendingUp className="mr-2 h-4 w-4" />
Add Business
</Link>
</Button>
</CardContent>
</Card>
);
@@ -245,10 +250,13 @@ export default async function DashboardPage() {
</Suspense>
</HydrateClient>
<QuickActions />
<div className="grid gap-6 md:grid-cols-2">
<CurrentOpenInvoiceCard />
<QuickActions />
</div>
<HydrateClient>
<Suspense fallback={<DataTableSkeleton columns={1} rows={3} />}>
<Suspense fallback={<DataTableSkeleton columns={1} rows={5} />}>
<RecentActivity />
</Suspense>
</HydrateClient>