From d86bbeec05ebeb459a129fa1049089ea76548bb8 Mon Sep 17 00:00:00 2001 From: Taqib Date: Fri, 14 Feb 2025 10:20:29 +0100 Subject: [PATCH 1/5] feat: revamped dashboard --- .../(dashboard)/dashboard/analytics/page.tsx | 218 ++++++------- .../(dashboard)/dashboard/clients/page.tsx | 35 +-- .../(dashboard)/dashboard/invoices/page.tsx | 65 ++-- src/app/(dashboard)/dashboard/layout.tsx | 8 +- src/app/(dashboard)/dashboard/page.tsx | 95 +++--- .../(dashboard)/dashboard/reports/page.tsx | 43 +-- .../(dashboard)/dashboard/revenue/page.tsx | 207 ++++++------- .../dashboard/settings/general/page.tsx | 289 +++++++++--------- .../dashboard/settings/profile/page.tsx | 273 ++++++++--------- .../dashboard/dynamic-breadcrumbs.tsx | 45 +++ src/components/dashboard/header.tsx | 18 ++ src/components/dashboard/sidebar.tsx | 12 +- 12 files changed, 591 insertions(+), 717 deletions(-) create mode 100644 src/components/dashboard/dynamic-breadcrumbs.tsx create mode 100644 src/components/dashboard/header.tsx diff --git a/src/app/(dashboard)/dashboard/analytics/page.tsx b/src/app/(dashboard)/dashboard/analytics/page.tsx index 6582371..e9ecc09 100644 --- a/src/app/(dashboard)/dashboard/analytics/page.tsx +++ b/src/app/(dashboard)/dashboard/analytics/page.tsx @@ -1,140 +1,110 @@ -"use client" +"use client"; -import { useState } from "react" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" -import { DateRangePicker } from "@/components/date-range-picker" -import { RevenueChart } from "@/components/charts/revenue-chart" -import { InvoiceStatusChart } from "@/components/charts/invoice-status-chart" -import { TopProjectsChart } from "@/components/charts/top-projects-chart" -import { MonthlyComparisonChart } from "@/components/charts/monthly-comparison-chart" -import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, -} from "@/components/ui/breadcrumb" -import { SidebarInset, SidebarTrigger } from "@/components/ui/sidebar" -import { Separator } from "@/components/ui/separator" +import { InvoiceStatusChart } from "@/components/charts/invoice-status-chart"; +import { MonthlyComparisonChart } from "@/components/charts/monthly-comparison-chart"; +import { RevenueChart } from "@/components/charts/revenue-chart"; +import { TopProjectsChart } from "@/components/charts/top-projects-chart"; +import { DateRangePicker } from "@/components/date-range-picker"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { useState } from "react"; export default function Analytics() { - const [dateRange, setDateRange] = useState({ from: new Date(2023, 0, 1), to: new Date() }) + const [dateRange, setDateRange] = useState({ from: new Date(2023, 0, 1), to: new Date() }); return ( - -
-
- - - - - - Dashboard - - - - Analytics - - - -
-
-
-
-

Analytics

- -
-
- - - Total Revenue - - -
€45,231.89
-

+20.1% from last month

-
-
- - - Invoices Sent - - -
+2,350
-

+180.1% from last month

-
-
- - - Paid Invoices - - -
+12,234
-

+19% from last month

-
-
- - - Outstanding Balance - - -
€6,354.12
-

-4% from last month

-
-
-
- - - Overview - Invoices - Projects - - -
- - - Revenue Overview - - - - - - - - Invoice Status - - - - - -
-
- +
+
+

Analytics

+ +
+
+ + + Total Revenue + + +
€45,231.89
+

+20.1% from last month

+
+
+ + + Invoices Sent + + +
+2,350
+

+180.1% from last month

+
+
+ + + Paid Invoices + + +
+12,234
+

+19% from last month

+
+
+ + + Outstanding Balance + + +
€6,354.12
+

-4% from last month

+
+
+
+ + + Overview + Invoices + Projects + + +
- Monthly Comparison - Compare invoice totals month by month + Revenue Overview - - + + - - - + - Top Projects - Highest revenue generating projects + Invoice Status - + - - -
- - ) +
+
+ + + + Monthly Comparison + Compare invoice totals month by month + + + + + + + + + + Top Projects + Highest revenue generating projects + + + + + + +
+
+ ); } - diff --git a/src/app/(dashboard)/dashboard/clients/page.tsx b/src/app/(dashboard)/dashboard/clients/page.tsx index ffbaeb4..bcbc24c 100644 --- a/src/app/(dashboard)/dashboard/clients/page.tsx +++ b/src/app/(dashboard)/dashboard/clients/page.tsx @@ -1,24 +1,15 @@ "use client"; -import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; +import { ClientModal } from "@/components/client-modal"; +import { ClientsTable } from "@/components/dashboard/clients-table"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; import { Pagination, PaginationContent, PaginationItem, PaginationLink, } from "@/components/ui/pagination"; -import { SidebarInset, SidebarTrigger } from "@/components/ui/sidebar"; -import { ClientsTable } from "@/components/dashboard/clients-table"; -import { ClientModal } from "@/components/client-modal"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; import { Plus } from "lucide-react"; import { useState } from "react"; @@ -87,21 +78,7 @@ export default function Clients() { const totalPages = Math.ceil(filteredClients.length / itemsPerPage); return ( - -
- - - - - Dashboard - - - - Clients - - - -
+ <>

Clients

@@ -141,6 +118,6 @@ export default function Clients() { onClose={() => setIsModalOpen(false)} onAddClient={addClient} /> - + ); } diff --git a/src/app/(dashboard)/dashboard/invoices/page.tsx b/src/app/(dashboard)/dashboard/invoices/page.tsx index bb67124..5c46239 100644 --- a/src/app/(dashboard)/dashboard/invoices/page.tsx +++ b/src/app/(dashboard)/dashboard/invoices/page.tsx @@ -1,22 +1,19 @@ -"use client" +"use client"; -import { useState } from "react" -import { Plus } from "lucide-react" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { InvoiceModal } from "@/components/dashboard/invoice-modal" -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { InvoiceModal } from "@/components/dashboard/invoice-modal"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, -} from "@/components/ui/breadcrumb" -import { SidebarInset, SidebarTrigger } from "@/components/ui/sidebar" -import { Separator } from "@/components/ui/separator" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Plus } from "lucide-react"; +import { useState } from "react"; // Mock data const invoices = [ @@ -62,37 +59,20 @@ const invoices = [ totalAmount: "€300.00", paymentMethod: "Credit Card", }, -] +]; export default function InvoicesPage() { - const [isModalOpen, setIsModalOpen] = useState(false) - const [searchQuery, setSearchQuery] = useState("") + const [isModalOpen, setIsModalOpen] = useState(false); + const [searchQuery, setSearchQuery] = useState(""); const filteredInvoices = invoices.filter((invoice) => Object.values(invoice).some((value) => value.toLowerCase().includes(searchQuery.toLowerCase())), - ) + ); return ( - -
-
- - - - - - Dashboard - - - - Invoices - - - -
-
+ <>
-
+

Invoices

setIsModalOpen(false)} /> - - ) + + ); } - diff --git a/src/app/(dashboard)/dashboard/layout.tsx b/src/app/(dashboard)/dashboard/layout.tsx index e12d71c..bba8efe 100644 --- a/src/app/(dashboard)/dashboard/layout.tsx +++ b/src/app/(dashboard)/dashboard/layout.tsx @@ -1,5 +1,6 @@ -import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; +import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; import { DashboardSidebar } from "@/components/dashboard/sidebar"; +import Header from "@/components/dashboard/header"; import { ThemeProvider } from "next-themes"; export default function DashboardLayout({ children }: { children: React.ReactNode }) { @@ -7,7 +8,10 @@ export default function DashboardLayout({ children }: { children: React.ReactNod - {children} + +
+
{children}
+ ); diff --git a/src/app/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx index 10342fe..3a92e2f 100644 --- a/src/app/(dashboard)/dashboard/page.tsx +++ b/src/app/(dashboard)/dashboard/page.tsx @@ -1,14 +1,6 @@ -import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, -} from "@/components/ui/breadcrumb"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { RecentInvoices } from "@/components/dashboard/recent-invoices"; -import { SidebarInset, SidebarTrigger } from "@/components/ui/sidebar"; import { Overview } from "@/components/dashboard/overview"; -import { Separator } from "@/components/ui/separator"; +import { RecentInvoices } from "@/components/dashboard/recent-invoices"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; // Helper function to determine the color class based on the percentage function getPercentageColorClass(percentage: number): string { @@ -31,59 +23,44 @@ export default function Dashboard() { ]; return ( - -
-
- - - - - - Dashboard - - - -
-
-
- {/* Updated main content container */} -
- {cardData.map((card, index) => ( - - - {card.title} - - -
{card.value}
-

- - {formatPercentage(card.percentage)} - - {" from last month"} -

-
-
- ))} -
-
- - - Overview - - - - - - - - Recent Invoices +
+ {/* Updated main content container */} +
+ {cardData.map((card, index) => ( + + + {card.title} - +
{card.value}
+

+ + {formatPercentage(card.percentage)} + + {" from last month"} +

-
+ ))} +
+
+ + + Overview + + + + + + + + Recent Invoices + + + + +
- +
); } diff --git a/src/app/(dashboard)/dashboard/reports/page.tsx b/src/app/(dashboard)/dashboard/reports/page.tsx index 8f47f9d..6972e84 100644 --- a/src/app/(dashboard)/dashboard/reports/page.tsx +++ b/src/app/(dashboard)/dashboard/reports/page.tsx @@ -1,42 +1,13 @@ -import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; -import { SidebarInset, SidebarTrigger } from "@/components/ui/sidebar"; -import { Separator } from "@/components/ui/separator"; export default function Reports() { return ( - -
-
- - - - - - Dashboard - - - - Reports - - - -
-
-
-
-
-
-
-
-
+
+
+
+
+
- +
+
); } diff --git a/src/app/(dashboard)/dashboard/revenue/page.tsx b/src/app/(dashboard)/dashboard/revenue/page.tsx index 168fe0a..fd4fae5 100644 --- a/src/app/(dashboard)/dashboard/revenue/page.tsx +++ b/src/app/(dashboard)/dashboard/revenue/page.tsx @@ -1,129 +1,108 @@ -"use client" +"use client"; -import { useState } from "react" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { DateRangePicker } from "@/components/date-range-picker" -import { RevenueChart } from "@/components/charts/revenue-chart" -import { RevenueBySourceChart } from "@/components/charts/revenue-by-source-chart" -import { TopProductsChart } from "@/components/charts/top-products-chart" +import { RevenueBySourceChart } from "@/components/charts/revenue-by-source-chart"; +import { RevenueChart } from "@/components/charts/revenue-chart"; +import { TopProductsChart } from "@/components/charts/top-products-chart"; +import { DateRangePicker } from "@/components/date-range-picker"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, -} from "@/components/ui/breadcrumb" -import { SidebarInset, SidebarTrigger } from "@/components/ui/sidebar" -import { Separator } from "@/components/ui/separator" + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { useState } from "react"; export default function RevenuePage() { - const [dateRange, setDateRange] = useState({ from: new Date(2023, 0, 1), to: new Date() }) + const [dateRange, setDateRange] = useState({ from: new Date(2023, 0, 1), to: new Date() }); return ( - -
-
- - - - - - Dashboard - - - - Revenue - - - -
-
-
-
-

Revenue Overview

-
- - -
-
-
- - - Total Revenue - - -
€45,231.89
-

+20.1% from last month

-
-
- - - Average Order Value - - -
€189.34
-

+4.3% from last month

-
-
- - - Orders - - -
239
-

+15.2% from last month

-
-
- - - Conversion Rate - - -
3.24%
-

+0.8% from last month

-
-
-
-
- - - Revenue Over Time - - - - - - - - Revenue by Source - - - - - +
+
+

Revenue Overview

+
+ +
+
+
+ + + Total Revenue + + +
€45,231.89
+

+20.1% from last month

+
+
+ + + Average Order Value + + +
€189.34
+

+4.3% from last month

+
+
+ + Orders + + +
239
+

+15.2% from last month

+
+
+ + + Conversion Rate + + +
3.24%
+

+0.8% from last month

+
+
+
+
+ + + Revenue Over Time + + + + + + - Top Products by Revenue - A breakdown of the top-selling products and their revenue contribution. + Revenue by Source - +
- - ) + + + Top Products by Revenue + + A breakdown of the top-selling products and their revenue contribution. + + + + + + +
+ ); } diff --git a/src/app/(dashboard)/dashboard/settings/general/page.tsx b/src/app/(dashboard)/dashboard/settings/general/page.tsx index 50bd1c6..9b25303 100644 --- a/src/app/(dashboard)/dashboard/settings/general/page.tsx +++ b/src/app/(dashboard)/dashboard/settings/general/page.tsx @@ -1,28 +1,31 @@ -"use client" +"use client"; -import { useState } from "react" -import { zodResolver } from "@hookform/resolvers/zod" -import { useForm } from "react-hook-form" -import { z } from "zod" -import { Save } from "lucide-react" -import { ThemeToggle } from "@/components/theme-toggle" -import { Button } from "@/components/ui/button" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { Switch } from "@/components/ui/switch" +import { ThemeToggle } from "@/components/theme-toggle"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, -} from "@/components/ui/breadcrumb" -import { SidebarInset, SidebarTrigger } from "@/components/ui/sidebar" -import { Separator } from "@/components/ui/separator" + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Save } from "lucide-react"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; const formSchema = z.object({ companyName: z.string().min(2, { @@ -32,11 +35,11 @@ const formSchema = z.object({ message: "Please enter a valid email address.", }), currency: z.string(), -}) +}); export default function Settings() { - const [emailNotifications, setEmailNotifications] = useState(true) - const [weeklyReports, setWeeklyReports] = useState(false) + const [emailNotifications, setEmailNotifications] = useState(true); + const [weeklyReports, setWeeklyReports] = useState(false); const form = useForm>({ resolver: zodResolver(formSchema), @@ -45,144 +48,124 @@ export default function Settings() { email: "admin@acme.com", currency: "EUR", }, - }) + }); function onSubmit(values: z.infer) { - console.log(values) + console.log(values); } return ( - -
-
- - - - - - Dashboard - - - - Settings - - - - General - - - -
-
-
-
- - - General Settings - Manage your company information and preferences. - - -
- - ( - - Company Name +
+
+ + + General Settings + Manage your company information and preferences. + + + + + ( + + Company Name + + + + + + )} + /> + ( + + Email Address + + + + + + )} + /> + ( + + Default Currency + + + + - - - )} - /> - ( - - Email Address - - - - - - )} - /> - ( - - Default Currency - - - - )} - /> - - - + + USD ($) + EUR (€) + GBP (£) + + + + + )} + /> + + + + + +
+ + + Appearance + Customize how the dashboard looks. + + +
+ + +
-
- - - Appearance - Customize how the dashboard looks. - - -
- - -
-
-
- - - Notifications - Configure your notification preferences. - - -
-
- -

Receive notifications via email.

-
- + + + Notifications + Configure your notification preferences. + + +
+
+ +

Receive notifications via email.

-
-
- -

Receive weekly summary reports.

-
- + +
+
+
+ +

Receive weekly summary reports.

- - -
+ +
+
+
- - ) +
+ ); } - diff --git a/src/app/(dashboard)/dashboard/settings/profile/page.tsx b/src/app/(dashboard)/dashboard/settings/profile/page.tsx index 4a8246a..c32de61 100644 --- a/src/app/(dashboard)/dashboard/settings/profile/page.tsx +++ b/src/app/(dashboard)/dashboard/settings/profile/page.tsx @@ -1,11 +1,8 @@ -"use client" +"use client"; -import { useState } from "react" -import { zodResolver } from "@hookform/resolvers/zod" -import { useForm } from "react-hook-form" -import { z } from "zod" -import { Save } from 'lucide-react' -import { Button } from "@/components/ui/button" +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Form, FormControl, @@ -14,23 +11,15 @@ import { FormItem, FormLabel, FormMessage, -} from "@/components/ui/form" -import { Input } from "@/components/ui/input" -import { Textarea } from "@/components/ui/textarea" -import { useToast } from "@/hooks/use-toast" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" -import { Switch } from "@/components/ui/switch" -import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, -} from "@/components/ui/breadcrumb" -import { SidebarInset, SidebarTrigger } from "@/components/ui/sidebar" -import { Separator } from "@/components/ui/separator" +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Switch } from "@/components/ui/switch"; +import { Textarea } from "@/components/ui/textarea"; +import { useToast } from "@/hooks/use-toast"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; const profileFormSchema = z.object({ username: z @@ -49,32 +38,29 @@ const profileFormSchema = z.object({ .array( z.object({ value: z.string().url({ message: "Please enter a valid URL." }), - }) + }), ) .optional(), -}) +}); -type ProfileFormValues = z.infer +type ProfileFormValues = z.infer; // This can come from your database or API. const defaultValues: Partial = { bio: "I'm a software developer based in New York City. I love building things for the web.", - urls: [ - { value: "https://example.com" }, - { value: "https://twitter.com/johndoe" }, - ], -} + urls: [{ value: "https://example.com" }, { value: "https://twitter.com/johndoe" }], +}; export default function ProfileSettingsPage() { - const { toast } = useToast() - const [avatar, setAvatar] = useState("/placeholder.svg") - const [emailNotifications, setEmailNotifications] = useState(true) + const { toast } = useToast(); + const [avatar, setAvatar] = useState("/placeholder.svg"); + const [emailNotifications, setEmailNotifications] = useState(true); const form = useForm({ resolver: zodResolver(profileFormSchema), defaultValues, mode: "onChange", - }) + }); function onSubmit(data: ProfileFormValues) { toast({ @@ -84,124 +70,109 @@ export default function ProfileSettingsPage() { {JSON.stringify(data, null, 2)} ), - }) + }); } return ( - -
-
- - - - - - Dashboard - - - - Settings - - - - Profile - - - -
-
-
-

Profile Settings

-
- - - Profile Information - Update your profile information and manage your account settings. - - -
- - - JD - - -
-
- - ( - - Username - - - - - This is your public display name. It can be your real name or a pseudonym. - - - - )} - /> - ( - - Email - - - - - You can manage verified email addresses in your email settings. - - - - )} - /> - ( - - Bio - -