-
-
Save shricodev/ec49249394524a94139a5af16250f2df to your computer and use it in GitHub Desktop.
Admin Dashboard - Opus 4.6 (First)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| From 9856811178f74c8bcec2132f0014b6e67f20da52 Mon Sep 17 00:00:00 2001 | |
| From: Shrijal Acharya <shrijal.acharya@gmail.com> | |
| Date: Sat, 28 Mar 2026 18:46:10 +0545 | |
| Subject: [PATCH 1/2] opus 1` | |
| --- | |
| src/app/dashboard/overview/layout.tsx | 211 +++++++++--------- | |
| .../overview/components/area-graph.tsx | 130 +++++------ | |
| .../overview/components/bar-graph.tsx | 150 ++++--------- | |
| .../overview/components/pie-graph.tsx | 146 ++++++------ | |
| .../overview/components/recent-sales.tsx | 114 +++++----- | |
| .../overview/components/review-cards.tsx | 91 ++++++++ | |
| 6 files changed, 420 insertions(+), 422 deletions(-) | |
| create mode 100644 src/features/overview/components/review-cards.tsx | |
| diff --git a/src/app/dashboard/overview/layout.tsx b/src/app/dashboard/overview/layout.tsx | |
| index 0d6393d..91fab5b 100644 | |
| --- a/src/app/dashboard/overview/layout.tsx | |
| +++ b/src/app/dashboard/overview/layout.tsx | |
| @@ -1,15 +1,53 @@ | |
| import PageContainer from '@/components/layout/page-container'; | |
| -import { Badge } from '@/components/ui/badge'; | |
| -import { | |
| - Card, | |
| - CardHeader, | |
| - CardTitle, | |
| - CardDescription, | |
| - CardAction, | |
| - CardFooter | |
| -} from '@/components/ui/card'; | |
| +import { Button } from '@/components/ui/button'; | |
| +import { Card, CardContent } from '@/components/ui/card'; | |
| import { Icons } from '@/components/icons'; | |
| +import { ReviewCards } from '@/features/overview/components/review-cards'; | |
| import React from 'react'; | |
| +import { cn } from '@/lib/utils'; | |
| + | |
| +const statCards = [ | |
| + { | |
| + label: 'Total Orders', | |
| + value: '1,840', | |
| + trend: '+4%', | |
| + trendUp: true, | |
| + period: '30 days', | |
| + icon: 'forms' as const, | |
| + iconBg: 'bg-blue-100 dark:bg-blue-900/30', | |
| + iconColor: 'text-blue-600 dark:text-blue-400' | |
| + }, | |
| + { | |
| + label: 'Total Delivered', | |
| + value: '1,622', | |
| + trend: '+6%', | |
| + trendUp: true, | |
| + period: '30 days', | |
| + icon: 'circleCheck' as const, | |
| + iconBg: 'bg-green-100 dark:bg-green-900/30', | |
| + iconColor: 'text-green-600 dark:text-green-400' | |
| + }, | |
| + { | |
| + label: 'Total Canceled', | |
| + value: '218', | |
| + trend: '+25%', | |
| + trendUp: false, | |
| + period: '30 days', | |
| + icon: 'circleX' as const, | |
| + iconBg: 'bg-red-100 dark:bg-red-900/30', | |
| + iconColor: 'text-red-600 dark:text-red-400' | |
| + }, | |
| + { | |
| + label: 'Total Revenue', | |
| + value: '$84,250', | |
| + trend: '+12%', | |
| + trendUp: true, | |
| + period: '30 days', | |
| + icon: 'creditCard' as const, | |
| + iconBg: 'bg-purple-100 dark:bg-purple-900/30', | |
| + iconColor: 'text-purple-600 dark:text-purple-400' | |
| + } | |
| +]; | |
| export default function OverViewLayout({ | |
| sales, | |
| @@ -23,103 +61,72 @@ export default function OverViewLayout({ | |
| area_stats: React.ReactNode; | |
| }) { | |
| return ( | |
| - <PageContainer> | |
| - <div className='flex flex-1 flex-col space-y-2'> | |
| - <div className='flex items-center justify-between'> | |
| - <h2 className='text-2xl font-bold tracking-tight'>Hi, Welcome back 👋</h2> | |
| + <PageContainer | |
| + pageTitle='Dashboard' | |
| + pageDescription='Hi, Welcome back to Admin!' | |
| + pageHeaderAction={ | |
| + <Button variant='outline' className='gap-2'> | |
| + <Icons.calendar className='h-4 w-4' /> | |
| + <span className='hidden sm:inline'>Filter Period</span> | |
| + <Icons.chevronDown className='h-4 w-4' /> | |
| + </Button> | |
| + } | |
| + > | |
| + <div className='flex flex-1 flex-col gap-4'> | |
| + {/* Stat Cards */} | |
| + <div className='grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4'> | |
| + {statCards.map((stat) => { | |
| + const Icon = Icons[stat.icon]; | |
| + return ( | |
| + <Card key={stat.label} className='shadow-xs'> | |
| + <CardContent className='flex items-center gap-4 p-6'> | |
| + <div | |
| + className={cn( | |
| + 'flex h-16 w-16 shrink-0 items-center justify-center rounded-full', | |
| + stat.iconBg | |
| + )} | |
| + > | |
| + <Icon className={cn('h-7 w-7', stat.iconColor)} /> | |
| + </div> | |
| + <div> | |
| + <div className='text-3xl font-bold tabular-nums'>{stat.value}</div> | |
| + <div className='text-muted-foreground text-sm'>{stat.label}</div> | |
| + <div | |
| + className={cn( | |
| + 'mt-1 flex items-center gap-1 text-xs font-medium', | |
| + stat.trendUp | |
| + ? 'text-green-600 dark:text-green-400' | |
| + : 'text-red-600 dark:text-red-400' | |
| + )} | |
| + > | |
| + {stat.trendUp ? ( | |
| + <Icons.trendingUp className='h-3 w-3' /> | |
| + ) : ( | |
| + <Icons.trendingDown className='h-3 w-3' /> | |
| + )} | |
| + {stat.trend} ({stat.period}) | |
| + </div> | |
| + </div> | |
| + </CardContent> | |
| + </Card> | |
| + ); | |
| + })} | |
| </div> | |
| - <div className='*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card grid grid-cols-1 gap-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:shadow-xs md:grid-cols-2 lg:grid-cols-4'> | |
| - <Card className='@container/card'> | |
| - <CardHeader> | |
| - <CardDescription>Total Revenue</CardDescription> | |
| - <CardTitle className='text-2xl font-semibold tabular-nums @[250px]/card:text-3xl'> | |
| - $1,250.00 | |
| - </CardTitle> | |
| - <CardAction> | |
| - <Badge variant='outline'> | |
| - <Icons.trendingUp /> | |
| - +12.5% | |
| - </Badge> | |
| - </CardAction> | |
| - </CardHeader> | |
| - <CardFooter className='flex-col items-start gap-1.5 text-sm'> | |
| - <div className='line-clamp-1 flex gap-2 font-medium'> | |
| - Trending up this month <Icons.trendingUp className='size-4' /> | |
| - </div> | |
| - <div className='text-muted-foreground'>Visitors for the last 6 months</div> | |
| - </CardFooter> | |
| - </Card> | |
| - <Card className='@container/card'> | |
| - <CardHeader> | |
| - <CardDescription>New Customers</CardDescription> | |
| - <CardTitle className='text-2xl font-semibold tabular-nums @[250px]/card:text-3xl'> | |
| - 1,234 | |
| - </CardTitle> | |
| - <CardAction> | |
| - <Badge variant='outline'> | |
| - <Icons.trendingDown /> | |
| - -20% | |
| - </Badge> | |
| - </CardAction> | |
| - </CardHeader> | |
| - <CardFooter className='flex-col items-start gap-1.5 text-sm'> | |
| - <div className='line-clamp-1 flex gap-2 font-medium'> | |
| - Down 20% this period <Icons.trendingDown className='size-4' /> | |
| - </div> | |
| - <div className='text-muted-foreground'>Acquisition needs attention</div> | |
| - </CardFooter> | |
| - </Card> | |
| - <Card className='@container/card'> | |
| - <CardHeader> | |
| - <CardDescription>Active Accounts</CardDescription> | |
| - <CardTitle className='text-2xl font-semibold tabular-nums @[250px]/card:text-3xl'> | |
| - 45,678 | |
| - </CardTitle> | |
| - <CardAction> | |
| - <Badge variant='outline'> | |
| - <Icons.trendingUp /> | |
| - +12.5% | |
| - </Badge> | |
| - </CardAction> | |
| - </CardHeader> | |
| - <CardFooter className='flex-col items-start gap-1.5 text-sm'> | |
| - <div className='line-clamp-1 flex gap-2 font-medium'> | |
| - Strong user retention <Icons.trendingUp className='size-4' /> | |
| - </div> | |
| - <div className='text-muted-foreground'>Engagement exceed targets</div> | |
| - </CardFooter> | |
| - </Card> | |
| - <Card className='@container/card'> | |
| - <CardHeader> | |
| - <CardDescription>Growth Rate</CardDescription> | |
| - <CardTitle className='text-2xl font-semibold tabular-nums @[250px]/card:text-3xl'> | |
| - 4.5% | |
| - </CardTitle> | |
| - <CardAction> | |
| - <Badge variant='outline'> | |
| - <Icons.trendingUp /> | |
| - +4.5% | |
| - </Badge> | |
| - </CardAction> | |
| - </CardHeader> | |
| - <CardFooter className='flex-col items-start gap-1.5 text-sm'> | |
| - <div className='line-clamp-1 flex gap-2 font-medium'> | |
| - Steady performance increase <Icons.trendingUp className='size-4' /> | |
| - </div> | |
| - <div className='text-muted-foreground'>Meets growth projections</div> | |
| - </CardFooter> | |
| - </Card> | |
| + {/* Row 2: Metrics gauges (left) + Chart Order (right) */} | |
| + <div className='grid grid-cols-1 gap-4 lg:grid-cols-7'> | |
| + <div className='lg:col-span-4'>{pie_stats}</div> | |
| + <div className='lg:col-span-3'>{bar_stats}</div> | |
| </div> | |
| - <div className='grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-7'> | |
| - <div className='col-span-4'>{bar_stats}</div> | |
| - <div className='col-span-4 md:col-span-3'> | |
| - {/* sales arallel routes */} | |
| - {sales} | |
| - </div> | |
| - <div className='col-span-4'>{area_stats}</div> | |
| - <div className='col-span-4 min-h-0 md:col-span-3'>{pie_stats}</div> | |
| + | |
| + {/* Row 3: Total Revenue chart (left) + Customer Map (right) */} | |
| + <div className='grid grid-cols-1 gap-4 lg:grid-cols-7'> | |
| + <div className='lg:col-span-4'>{area_stats}</div> | |
| + <div className='lg:col-span-3'>{sales}</div> | |
| </div> | |
| + | |
| + {/* Row 4: Recent Reviews */} | |
| + <ReviewCards /> | |
| </div> | |
| </PageContainer> | |
| ); | |
| diff --git a/src/features/overview/components/area-graph.tsx b/src/features/overview/components/area-graph.tsx | |
| index ea75fd8..81ae8d6 100644 | |
| --- a/src/features/overview/components/area-graph.tsx | |
| +++ b/src/features/overview/components/area-graph.tsx | |
| @@ -1,40 +1,37 @@ | |
| 'use client'; | |
| -import { Area, AreaChart, CartesianGrid, XAxis } from 'recharts'; | |
| +import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts'; | |
| -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; | |
| +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; | |
| import { | |
| ChartConfig, | |
| ChartContainer, | |
| ChartTooltip, | |
| ChartTooltipContent | |
| } from '@/components/ui/chart'; | |
| -import { Badge } from '@/components/ui/badge'; | |
| -import { Icons } from '@/components/icons'; | |
| -import React from 'react'; | |
| const chartData = [ | |
| - { month: 'January', desktop: 342, mobile: 245 }, | |
| - { month: 'February', desktop: 876, mobile: 654 }, | |
| - { month: 'March', desktop: 512, mobile: 387 }, | |
| - { month: 'April', desktop: 629, mobile: 521 }, | |
| - { month: 'May', desktop: 458, mobile: 412 }, | |
| - { month: 'June', desktop: 781, mobile: 598 }, | |
| - { month: 'July', desktop: 394, mobile: 312 }, | |
| - { month: 'August', desktop: 925, mobile: 743 }, | |
| - { month: 'September', desktop: 647, mobile: 489 }, | |
| - { month: 'October', desktop: 532, mobile: 476 }, | |
| - { month: 'November', desktop: 803, mobile: 687 }, | |
| - { month: 'December', desktop: 271, mobile: 198 } | |
| + { month: 'Jan', current: 15000, previous: 10000 }, | |
| + { month: 'Feb', current: 22000, previous: 18000 }, | |
| + { month: 'Mar', current: 18000, previous: 20000 }, | |
| + { month: 'Apr', current: 25000, previous: 15000 }, | |
| + { month: 'May', current: 38753, previous: 22000 }, | |
| + { month: 'Jun', current: 30000, previous: 28000 }, | |
| + { month: 'Jul', current: 28000, previous: 25000 }, | |
| + { month: 'Aug', current: 35000, previous: 20000 }, | |
| + { month: 'Sep', current: 32000, previous: 18000 }, | |
| + { month: 'Oct', current: 12657, previous: 30000 }, | |
| + { month: 'Nov', current: 27000, previous: 35000 }, | |
| + { month: 'Dec', current: 22000, previous: 32000 } | |
| ]; | |
| const chartConfig = { | |
| - desktop: { | |
| - label: 'Desktop', | |
| + current: { | |
| + label: '2025', | |
| color: 'var(--chart-1)' | |
| }, | |
| - mobile: { | |
| - label: 'Mobile', | |
| + previous: { | |
| + label: '2024', | |
| color: 'var(--chart-2)' | |
| } | |
| } satisfies ChartConfig; | |
| @@ -42,48 +39,52 @@ const chartConfig = { | |
| export function AreaGraph() { | |
| return ( | |
| <Card> | |
| - <CardHeader> | |
| - <CardTitle> | |
| - Dotted Area Chart | |
| - <Badge variant='outline'> | |
| - <Icons.trendingUp /> | |
| - -5.2% | |
| - </Badge> | |
| - </CardTitle> | |
| - <CardDescription>Showing total visitors for the last 6 months</CardDescription> | |
| + <CardHeader className='flex-row items-center justify-between pb-4'> | |
| + <CardTitle>Total Revenue</CardTitle> | |
| + <div className='flex items-center gap-4 text-sm'> | |
| + <div className='flex items-center gap-1.5'> | |
| + <span | |
| + className='inline-block h-3 w-3 rounded-full' | |
| + style={{ background: 'var(--chart-1)' }} | |
| + /> | |
| + <span className='text-muted-foreground'>2025</span> | |
| + </div> | |
| + <div className='flex items-center gap-1.5'> | |
| + <span | |
| + className='inline-block h-3 w-3 rounded-full' | |
| + style={{ background: 'var(--chart-2)' }} | |
| + /> | |
| + <span className='text-muted-foreground'>2024</span> | |
| + </div> | |
| + </div> | |
| </CardHeader> | |
| <CardContent> | |
| - <ChartContainer config={chartConfig}> | |
| - <AreaChart accessibilityLayer data={chartData}> | |
| - <CartesianGrid vertical={false} strokeDasharray='3 3' /> | |
| - <XAxis | |
| - dataKey='month' | |
| + <ChartContainer config={chartConfig} className='h-[240px] w-full'> | |
| + <AreaChart data={chartData} margin={{ top: 10, right: 10, left: 0, bottom: 0 }}> | |
| + <CartesianGrid vertical={false} strokeDasharray='3 3' opacity={0.3} /> | |
| + <XAxis dataKey='month' tickLine={false} axisLine={false} tickMargin={8} /> | |
| + <YAxis | |
| tickLine={false} | |
| axisLine={false} | |
| - tickMargin={8} | |
| - tickFormatter={(value) => value.slice(0, 3)} | |
| + tickFormatter={(v) => `$${(v / 1000).toFixed(0)}k`} | |
| + width={44} | |
| /> | |
| - <ChartTooltip cursor={false} content={<ChartTooltipContent />} /> | |
| - <defs> | |
| - <DottedBackgroundPattern config={chartConfig} /> | |
| - </defs> | |
| + <ChartTooltip content={<ChartTooltipContent />} /> | |
| <Area | |
| - dataKey='mobile' | |
| + dataKey='current' | |
| type='natural' | |
| - fill='url(#dotted-background-pattern-mobile)' | |
| - fillOpacity={0.4} | |
| - stroke='var(--color-mobile)' | |
| - stackId='a' | |
| - strokeWidth={0.8} | |
| + fill='var(--color-current)' | |
| + fillOpacity={0.15} | |
| + stroke='var(--color-current)' | |
| + strokeWidth={2} | |
| /> | |
| <Area | |
| - dataKey='desktop' | |
| + dataKey='previous' | |
| type='natural' | |
| - fill='url(#dotted-background-pattern-desktop)' | |
| - fillOpacity={0.4} | |
| - stroke='var(--color-desktop)' | |
| - stackId='a' | |
| - strokeWidth={0.8} | |
| + fill='var(--color-previous)' | |
| + fillOpacity={0.15} | |
| + stroke='var(--color-previous)' | |
| + strokeWidth={2} | |
| /> | |
| </AreaChart> | |
| </ChartContainer> | |
| @@ -91,26 +92,3 @@ export function AreaGraph() { | |
| </Card> | |
| ); | |
| } | |
| - | |
| -const DottedBackgroundPattern = ({ config }: { config: ChartConfig }) => { | |
| - const items = Object.fromEntries( | |
| - Object.entries(config).map(([key, value]) => [key, value.color]) | |
| - ); | |
| - return ( | |
| - <> | |
| - {Object.entries(items).map(([key, value]) => ( | |
| - <pattern | |
| - key={key} | |
| - id={`dotted-background-pattern-${key}`} | |
| - x='0' | |
| - y='0' | |
| - width='7' | |
| - height='7' | |
| - patternUnits='userSpaceOnUse' | |
| - > | |
| - <circle cx='5' cy='5' r='1.5' fill={value} opacity={0.5}></circle> | |
| - </pattern> | |
| - ))} | |
| - </> | |
| - ); | |
| -}; | |
| diff --git a/src/features/overview/components/bar-graph.tsx b/src/features/overview/components/bar-graph.tsx | |
| index b961c73..c3c2be6 100644 | |
| --- a/src/features/overview/components/bar-graph.tsx | |
| +++ b/src/features/overview/components/bar-graph.tsx | |
| @@ -1,7 +1,8 @@ | |
| 'use client'; | |
| -import { Bar, BarChart, XAxis } from 'recharts'; | |
| +import { Area, AreaChart, XAxis } from 'recharts'; | |
| +import { Button } from '@/components/ui/button'; | |
| import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; | |
| import { | |
| ChartConfig, | |
| @@ -9,136 +10,61 @@ import { | |
| ChartTooltip, | |
| ChartTooltipContent | |
| } from '@/components/ui/chart'; | |
| -import { Badge } from '@/components/ui/badge'; | |
| import { Icons } from '@/components/icons'; | |
| const chartData = [ | |
| - { month: 'January', desktop: 186, mobile: 80 }, | |
| - { month: 'February', desktop: 305, mobile: 200 }, | |
| - { month: 'March', desktop: 237, mobile: 120 }, | |
| - { month: 'April', desktop: 73, mobile: 190 }, | |
| - { month: 'May', desktop: 209, mobile: 130 }, | |
| - { month: 'June', desktop: 214, mobile: 140 } | |
| + { day: 'Sun', orders: 280 }, | |
| + { day: 'Mon', orders: 350 }, | |
| + { day: 'Tue', orders: 420 }, | |
| + { day: 'Wed', orders: 380 }, | |
| + { day: 'Thu', orders: 456 }, | |
| + { day: 'Fri', orders: 390 }, | |
| + { day: 'Sat', orders: 320 } | |
| ]; | |
| const chartConfig = { | |
| - desktop: { | |
| - label: 'Desktop', | |
| + orders: { | |
| + label: 'Orders', | |
| color: 'var(--chart-1)' | |
| - }, | |
| - mobile: { | |
| - label: 'Mobile', | |
| - color: 'var(--chart-2)' | |
| } | |
| } satisfies ChartConfig; | |
| export function BarGraph() { | |
| return ( | |
| - <Card> | |
| - <CardHeader> | |
| - <CardTitle> | |
| - Bar Chart - Multiple | |
| - <Badge variant='outline'> | |
| - <Icons.trendingDown /> | |
| - -5.2% | |
| - </Badge> | |
| - </CardTitle> | |
| - <CardDescription>January - June 2025</CardDescription> | |
| + <Card className='h-full'> | |
| + <CardHeader className='flex-row items-start justify-between pb-2'> | |
| + <div> | |
| + <CardTitle>Chart Order</CardTitle> | |
| + <CardDescription>Weekly order activity</CardDescription> | |
| + </div> | |
| + <Button variant='outline' size='sm' className='gap-1.5 shrink-0'> | |
| + <Icons.upload className='h-4 w-4' /> | |
| + Save Report | |
| + </Button> | |
| </CardHeader> | |
| <CardContent> | |
| - <ChartContainer config={chartConfig}> | |
| - <BarChart accessibilityLayer data={chartData}> | |
| - <rect | |
| - x='0' | |
| - y='0' | |
| - width='100%' | |
| - height='85%' | |
| - fill='url(#default-multiple-pattern-dots)' | |
| - /> | |
| + <ChartContainer config={chartConfig} className='h-[220px] w-full'> | |
| + <AreaChart data={chartData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}> | |
| <defs> | |
| - <DottedBackgroundPattern /> | |
| + <linearGradient id='orderGradient' x1='0' y1='0' x2='0' y2='1'> | |
| + <stop offset='5%' stopColor='var(--chart-1)' stopOpacity={0.3} /> | |
| + <stop offset='95%' stopColor='var(--chart-1)' stopOpacity={0} /> | |
| + </linearGradient> | |
| </defs> | |
| - <XAxis | |
| - dataKey='month' | |
| - tickLine={false} | |
| - tickMargin={10} | |
| - axisLine={false} | |
| - tickFormatter={(value) => value.slice(0, 3)} | |
| - /> | |
| - <ChartTooltip | |
| - cursor={false} | |
| - content={<ChartTooltipContent indicator='dashed' hideLabel />} | |
| - /> | |
| - <Bar | |
| - dataKey='desktop' | |
| - color='var(--chart-1)' | |
| - fill='var(--color-desktop)' | |
| - shape={<CustomHatchedBar isHatched={false} />} | |
| - radius={4} | |
| + <XAxis dataKey='day' tickLine={false} axisLine={false} tickMargin={8} /> | |
| + <ChartTooltip cursor={false} content={<ChartTooltipContent />} /> | |
| + <Area | |
| + dataKey='orders' | |
| + type='natural' | |
| + fill='url(#orderGradient)' | |
| + stroke='var(--color-orders)' | |
| + strokeWidth={2} | |
| + dot={{ r: 4, fill: 'var(--color-orders)', strokeWidth: 0 }} | |
| + activeDot={{ r: 6 }} | |
| /> | |
| - <Bar | |
| - dataKey='mobile' | |
| - fill='var(--color-mobile)' | |
| - shape={<CustomHatchedBar />} | |
| - radius={4} | |
| - /> | |
| - </BarChart> | |
| + </AreaChart> | |
| </ChartContainer> | |
| </CardContent> | |
| </Card> | |
| ); | |
| } | |
| - | |
| -const CustomHatchedBar = ( | |
| - props: React.SVGProps<SVGRectElement> & { | |
| - dataKey?: string; | |
| - isHatched?: boolean; | |
| - } | |
| -) => { | |
| - const { fill, x, y, width, height, dataKey } = props; | |
| - | |
| - const isHatched = props.isHatched ?? true; | |
| - | |
| - return ( | |
| - <> | |
| - <rect | |
| - rx={4} | |
| - x={x} | |
| - y={y} | |
| - width={width} | |
| - height={height} | |
| - stroke='none' | |
| - fill={isHatched ? `url(#hatched-bar-pattern-${dataKey})` : fill} | |
| - /> | |
| - <defs> | |
| - <pattern | |
| - key={dataKey} | |
| - id={`hatched-bar-pattern-${dataKey}`} | |
| - x='0' | |
| - y='0' | |
| - width='5' | |
| - height='5' | |
| - patternUnits='userSpaceOnUse' | |
| - patternTransform='rotate(-45)' | |
| - > | |
| - <rect width='10' height='10' opacity={0.5} fill={fill}></rect> | |
| - <rect width='1' height='10' fill={fill}></rect> | |
| - </pattern> | |
| - </defs> | |
| - </> | |
| - ); | |
| -}; | |
| -const DottedBackgroundPattern = () => { | |
| - return ( | |
| - <pattern | |
| - id='default-multiple-pattern-dots' | |
| - x='0' | |
| - y='0' | |
| - width='10' | |
| - height='10' | |
| - patternUnits='userSpaceOnUse' | |
| - > | |
| - <circle className='dark:text-muted/40 text-muted' cx='2' cy='2' r='1' fill='currentColor' /> | |
| - </pattern> | |
| - ); | |
| -}; | |
| diff --git a/src/features/overview/components/pie-graph.tsx b/src/features/overview/components/pie-graph.tsx | |
| index b28e5ea..41a4b1f 100644 | |
| --- a/src/features/overview/components/pie-graph.tsx | |
| +++ b/src/features/overview/components/pie-graph.tsx | |
| @@ -1,90 +1,82 @@ | |
| 'use client'; | |
| -import { LabelList, Pie, PieChart } from 'recharts'; | |
| +import { Label, Pie, PieChart } from 'recharts'; | |
| -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; | |
| -import { | |
| - ChartConfig, | |
| - ChartContainer, | |
| - ChartTooltip, | |
| - ChartTooltipContent | |
| -} from '@/components/ui/chart'; | |
| -import { Badge } from '@/components/ui/badge'; | |
| -import { Icons } from '@/components/icons'; | |
| +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; | |
| +import { ChartConfig, ChartContainer } from '@/components/ui/chart'; | |
| -const chartData = [ | |
| - { browser: 'chrome', visitors: 275, fill: 'var(--color-chrome)' }, | |
| - { browser: 'safari', visitors: 200, fill: 'var(--color-safari)' }, | |
| - { browser: 'firefox', visitors: 187, fill: 'var(--color-firefox)' }, | |
| - { browser: 'edge', visitors: 173, fill: 'var(--color-edge)' }, | |
| - { browser: 'other', visitors: 90, fill: 'var(--color-other)' } | |
| -]; | |
| +const emptyConfig = {} satisfies ChartConfig; | |
| -const chartConfig = { | |
| - visitors: { | |
| - label: 'Visitors' | |
| - }, | |
| - chrome: { | |
| - label: 'Chrome', | |
| - color: 'var(--chart-1)' | |
| - }, | |
| - safari: { | |
| - label: 'Safari', | |
| - color: 'var(--chart-2)' | |
| - }, | |
| - firefox: { | |
| - label: 'Firefox', | |
| - color: 'var(--chart-3)' | |
| - }, | |
| - edge: { | |
| - label: 'Edge', | |
| - color: 'var(--chart-4)' | |
| - }, | |
| - other: { | |
| - label: 'Other', | |
| - color: 'var(--chart-5)' | |
| - } | |
| -} satisfies ChartConfig; | |
| +interface DonutGaugeProps { | |
| + value: number; | |
| + label: string; | |
| + color: string; | |
| +} | |
| + | |
| +function DonutGauge({ value, label, color }: DonutGaugeProps) { | |
| + const data = [ | |
| + { name: 'filled', value, fill: color }, | |
| + { name: 'empty', value: 100 - value, fill: 'var(--muted)' } | |
| + ]; | |
| + | |
| + return ( | |
| + <div className='flex flex-col items-center gap-2'> | |
| + <ChartContainer config={emptyConfig} className='h-[148px] w-[148px]'> | |
| + <PieChart> | |
| + <Pie | |
| + data={data} | |
| + cx='50%' | |
| + cy='50%' | |
| + innerRadius={48} | |
| + outerRadius={66} | |
| + startAngle={90} | |
| + endAngle={-270} | |
| + dataKey='value' | |
| + strokeWidth={0} | |
| + cornerRadius={4} | |
| + > | |
| + <Label | |
| + content={({ viewBox }) => { | |
| + if (viewBox && 'cx' in viewBox && 'cy' in viewBox) { | |
| + return ( | |
| + <text | |
| + x={viewBox.cx} | |
| + y={viewBox.cy} | |
| + textAnchor='middle' | |
| + dominantBaseline='middle' | |
| + > | |
| + <tspan | |
| + x={viewBox.cx} | |
| + y={viewBox.cy} | |
| + className='fill-foreground' | |
| + fontSize={20} | |
| + fontWeight={700} | |
| + > | |
| + {value}% | |
| + </tspan> | |
| + </text> | |
| + ); | |
| + } | |
| + }} | |
| + /> | |
| + </Pie> | |
| + </PieChart> | |
| + </ChartContainer> | |
| + <span className='text-muted-foreground text-sm font-medium'>{label}</span> | |
| + </div> | |
| + ); | |
| +} | |
| export function PieGraph() { | |
| return ( | |
| <Card className='flex h-full flex-col'> | |
| - <CardHeader className='items-center pb-0'> | |
| - <CardTitle> | |
| - Pie Chart | |
| - <Badge variant='outline'> | |
| - <Icons.trendingUp /> | |
| - +5.2% | |
| - </Badge> | |
| - </CardTitle> | |
| - <CardDescription>January - June 2024</CardDescription> | |
| + <CardHeader className='pb-2'> | |
| + <CardTitle>Metrics Overview</CardTitle> | |
| </CardHeader> | |
| - <CardContent className='flex flex-1 items-center justify-center pb-0'> | |
| - <ChartContainer | |
| - config={chartConfig} | |
| - className='[&_.recharts-text]:fill-background mx-auto aspect-square max-h-[300px] min-h-[250px]' | |
| - > | |
| - <PieChart> | |
| - <ChartTooltip content={<ChartTooltipContent nameKey='visitors' hideLabel />} /> | |
| - <Pie | |
| - data={chartData} | |
| - innerRadius={30} | |
| - dataKey='visitors' | |
| - radius={10} | |
| - cornerRadius={8} | |
| - paddingAngle={4} | |
| - > | |
| - <LabelList | |
| - dataKey='visitors' | |
| - stroke='none' | |
| - fontSize={12} | |
| - fontWeight={500} | |
| - fill='currentColor' | |
| - formatter={(value: number) => value.toString()} | |
| - /> | |
| - </Pie> | |
| - </PieChart> | |
| - </ChartContainer> | |
| + <CardContent className='flex flex-1 items-center justify-around gap-4 py-4'> | |
| + <DonutGauge value={81} label='Total Orders' color='var(--chart-1)' /> | |
| + <DonutGauge value={22} label='Customer Growth' color='var(--chart-3)' /> | |
| + <DonutGauge value={62} label='Total Revenue' color='var(--chart-2)' /> | |
| </CardContent> | |
| </Card> | |
| ); | |
| diff --git a/src/features/overview/components/recent-sales.tsx b/src/features/overview/components/recent-sales.tsx | |
| index 7ceff1f..45f5e9e 100644 | |
| --- a/src/features/overview/components/recent-sales.tsx | |
| +++ b/src/features/overview/components/recent-sales.tsx | |
| @@ -1,67 +1,71 @@ | |
| -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; | |
| -import { Card, CardHeader, CardContent, CardTitle, CardDescription } from '@/components/ui/card'; | |
| +'use client'; | |
| -const salesData = [ | |
| - { | |
| - name: 'Olivia Martin', | |
| - email: 'olivia.martin@email.com', | |
| - avatar: 'https://api.slingacademy.com/public/sample-users/1.png', | |
| - fallback: 'OM', | |
| - amount: '+$1,999.00' | |
| - }, | |
| - { | |
| - name: 'Jackson Lee', | |
| - email: 'jackson.lee@email.com', | |
| - avatar: 'https://api.slingacademy.com/public/sample-users/2.png', | |
| - fallback: 'JL', | |
| - amount: '+$39.00' | |
| - }, | |
| - { | |
| - name: 'Isabella Nguyen', | |
| - email: 'isabella.nguyen@email.com', | |
| - avatar: 'https://api.slingacademy.com/public/sample-users/3.png', | |
| - fallback: 'IN', | |
| - amount: '+$299.00' | |
| - }, | |
| - { | |
| - name: 'William Kim', | |
| - email: 'will@email.com', | |
| - avatar: 'https://api.slingacademy.com/public/sample-users/4.png', | |
| - fallback: 'WK', | |
| - amount: '+$99.00' | |
| +import { Bar, BarChart, XAxis, YAxis } from 'recharts'; | |
| + | |
| +import { Button } from '@/components/ui/button'; | |
| +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; | |
| +import { | |
| + ChartConfig, | |
| + ChartContainer, | |
| + ChartTooltip, | |
| + ChartTooltipContent | |
| +} from '@/components/ui/chart'; | |
| + | |
| +const chartData = [ | |
| + { day: 'Sun', returning: 45, newVisitors: 30 }, | |
| + { day: 'Mon', returning: 80, newVisitors: 60 }, | |
| + { day: 'Tue', returning: 60, newVisitors: 50 }, | |
| + { day: 'Wed', returning: 75, newVisitors: 65 }, | |
| + { day: 'Thu', returning: 55, newVisitors: 40 }, | |
| + { day: 'Fri', returning: 30, newVisitors: 20 }, | |
| + { day: 'Sat', returning: 50, newVisitors: 45 } | |
| +]; | |
| + | |
| +const chartConfig = { | |
| + returning: { | |
| + label: 'Returning', | |
| + color: 'var(--chart-3)' | |
| }, | |
| - { | |
| - name: 'Sofia Davis', | |
| - email: 'sofia.davis@email.com', | |
| - avatar: 'https://api.slingacademy.com/public/sample-users/5.png', | |
| - fallback: 'SD', | |
| - amount: '+$39.00' | |
| + newVisitors: { | |
| + label: 'New', | |
| + color: 'var(--chart-5)' | |
| } | |
| -]; | |
| +} satisfies ChartConfig; | |
| export function RecentSales() { | |
| return ( | |
| <Card className='h-full'> | |
| - <CardHeader> | |
| - <CardTitle>Recent Sales</CardTitle> | |
| - <CardDescription>You made 265 sales this month.</CardDescription> | |
| + <CardHeader className='flex-row items-center justify-between pb-2'> | |
| + <CardTitle>Customer Map</CardTitle> | |
| + <Button variant='outline' size='sm' className='gap-1'> | |
| + Weekly | |
| + </Button> | |
| </CardHeader> | |
| <CardContent> | |
| - <div className='space-y-8'> | |
| - {salesData.map((sale, index) => ( | |
| - <div key={index} className='flex items-center'> | |
| - <Avatar className='h-9 w-9'> | |
| - <AvatarImage src={sale.avatar} alt='Avatar' /> | |
| - <AvatarFallback>{sale.fallback}</AvatarFallback> | |
| - </Avatar> | |
| - <div className='ml-4 space-y-1'> | |
| - <p className='text-sm leading-none font-medium'>{sale.name}</p> | |
| - <p className='text-muted-foreground text-sm'>{sale.email}</p> | |
| - </div> | |
| - <div className='ml-auto font-medium'>{sale.amount}</div> | |
| - </div> | |
| - ))} | |
| - </div> | |
| + <ChartContainer config={chartConfig} className='h-[220px] w-full'> | |
| + <BarChart | |
| + data={chartData} | |
| + barGap={2} | |
| + barCategoryGap='30%' | |
| + margin={{ top: 10, right: 10, left: -20, bottom: 0 }} | |
| + > | |
| + <XAxis dataKey='day' tickLine={false} axisLine={false} tickMargin={8} /> | |
| + <YAxis tickLine={false} axisLine={false} width={35} /> | |
| + <ChartTooltip content={<ChartTooltipContent />} /> | |
| + <Bar | |
| + dataKey='returning' | |
| + fill='var(--color-returning)' | |
| + radius={[4, 4, 0, 0]} | |
| + maxBarSize={14} | |
| + /> | |
| + <Bar | |
| + dataKey='newVisitors' | |
| + fill='var(--color-newVisitors)' | |
| + radius={[4, 4, 0, 0]} | |
| + maxBarSize={14} | |
| + /> | |
| + </BarChart> | |
| + </ChartContainer> | |
| </CardContent> | |
| </Card> | |
| ); | |
| diff --git a/src/features/overview/components/review-cards.tsx b/src/features/overview/components/review-cards.tsx | |
| new file mode 100644 | |
| index 0000000..246c475 | |
| --- /dev/null | |
| +++ b/src/features/overview/components/review-cards.tsx | |
| @@ -0,0 +1,91 @@ | |
| +import { Avatar, AvatarFallback } from '@/components/ui/avatar'; | |
| +import { Button } from '@/components/ui/button'; | |
| +import { Card, CardContent } from '@/components/ui/card'; | |
| +import { Icons } from '@/components/icons'; | |
| + | |
| +const reviews = [ | |
| + { | |
| + name: 'Alex Johnson', | |
| + initials: 'AJ', | |
| + timeAgo: '2 days ago', | |
| + rating: 4.5, | |
| + text: 'The platform has significantly improved our workflow. Easy to navigate and the reporting tools are excellent for tracking performance metrics across all departments.' | |
| + }, | |
| + { | |
| + name: 'Maria Rodriguez', | |
| + initials: 'MR', | |
| + timeAgo: '3 days ago', | |
| + rating: 4.0, | |
| + text: 'Great experience overall. The dashboard gives real-time insights that help us make better data-driven decisions. The UI is clean and intuitive to use.' | |
| + }, | |
| + { | |
| + name: 'Sam Patel', | |
| + initials: 'SP', | |
| + timeAgo: '4 days ago', | |
| + rating: 4.5, | |
| + text: 'Reliable, intuitive, and packed with features. The analytics section is particularly impressive and saves our team hours every week without any setup overhead.' | |
| + } | |
| +]; | |
| + | |
| +function StarRating({ rating }: { rating: number }) { | |
| + return ( | |
| + <div className='flex items-center gap-1'> | |
| + {[1, 2, 3, 4, 5].map((star) => ( | |
| + <Icons.exclusive | |
| + key={star} | |
| + className={`h-4 w-4 ${ | |
| + star <= Math.floor(rating) | |
| + ? 'text-yellow-400' | |
| + : star - 0.5 <= rating | |
| + ? 'text-yellow-300' | |
| + : 'text-muted-foreground/30' | |
| + }`} | |
| + /> | |
| + ))} | |
| + <span className='text-muted-foreground ml-1 text-sm font-medium'>{rating}</span> | |
| + </div> | |
| + ); | |
| +} | |
| + | |
| +export function ReviewCards() { | |
| + return ( | |
| + <div className='space-y-4'> | |
| + <div className='flex items-start justify-between'> | |
| + <div> | |
| + <h3 className='text-xl font-semibold'>Recent Reviews</h3> | |
| + <p className='text-muted-foreground text-sm'>What users are saying about the platform</p> | |
| + </div> | |
| + <div className='flex gap-2'> | |
| + <Button variant='outline' size='icon' aria-label='Previous reviews'> | |
| + <Icons.chevronLeft className='h-4 w-4' /> | |
| + </Button> | |
| + <Button variant='outline' size='icon' aria-label='Next reviews'> | |
| + <Icons.chevronRight className='h-4 w-4' /> | |
| + </Button> | |
| + </div> | |
| + </div> | |
| + | |
| + <div className='grid grid-cols-1 gap-4 md:grid-cols-3'> | |
| + {reviews.map((review) => ( | |
| + <Card key={review.name} className='overflow-hidden'> | |
| + <CardContent className='p-6'> | |
| + <div className='mb-4 flex items-center gap-3'> | |
| + <Avatar className='h-12 w-12'> | |
| + <AvatarFallback className='text-sm font-semibold'> | |
| + {review.initials} | |
| + </AvatarFallback> | |
| + </Avatar> | |
| + <div> | |
| + <p className='font-semibold leading-tight'>{review.name}</p> | |
| + <p className='text-muted-foreground text-xs'>{review.timeAgo}</p> | |
| + </div> | |
| + </div> | |
| + <p className='text-muted-foreground mb-4 text-sm leading-relaxed'>{review.text}</p> | |
| + <StarRating rating={review.rating} /> | |
| + </CardContent> | |
| + </Card> | |
| + ))} | |
| + </div> | |
| + </div> | |
| + ); | |
| +} | |
| -- | |
| 2.53.0 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment