init
This commit is contained in:
23
src/features/overview/components/area-graph-skeleton.tsx
Normal file
23
src/features/overview/components/area-graph-skeleton.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
|
||||
export function AreaGraphSkeleton() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Skeleton className='h-6 w-[140px]' />
|
||||
<Skeleton className='h-5 w-[60px] rounded-full' />
|
||||
</div>
|
||||
<Skeleton className='h-4 w-[250px]' />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className='relative aspect-auto h-[280px] w-full'>
|
||||
<div className='from-primary/5 to-primary/20 absolute inset-0 rounded-lg bg-linear-to-t' />
|
||||
<Skeleton className='absolute right-0 bottom-0 left-0 h-[1px]' />
|
||||
<Skeleton className='absolute top-0 bottom-0 left-0 w-[1px]' />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
116
src/features/overview/components/area-graph.tsx
Normal file
116
src/features/overview/components/area-graph.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
'use client';
|
||||
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } 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 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 }
|
||||
];
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
color: 'var(--chart-1)'
|
||||
},
|
||||
mobile: {
|
||||
label: 'Mobile',
|
||||
color: 'var(--chart-2)'
|
||||
}
|
||||
} satisfies 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>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<AreaChart accessibilityLayer data={chartData}>
|
||||
<CartesianGrid vertical={false} strokeDasharray='3 3' />
|
||||
<XAxis
|
||||
dataKey='month'
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickMargin={8}
|
||||
tickFormatter={(value) => value.slice(0, 3)}
|
||||
/>
|
||||
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
|
||||
<defs>
|
||||
<DottedBackgroundPattern config={chartConfig} />
|
||||
</defs>
|
||||
<Area
|
||||
dataKey='mobile'
|
||||
type='natural'
|
||||
fill='url(#dotted-background-pattern-mobile)'
|
||||
fillOpacity={0.4}
|
||||
stroke='var(--color-mobile)'
|
||||
stackId='a'
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
<Area
|
||||
dataKey='desktop'
|
||||
type='natural'
|
||||
fill='url(#dotted-background-pattern-desktop)'
|
||||
fillOpacity={0.4}
|
||||
stroke='var(--color-desktop)'
|
||||
stackId='a'
|
||||
strokeWidth={0.8}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</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>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
29
src/features/overview/components/bar-graph-skeleton.tsx
Normal file
29
src/features/overview/components/bar-graph-skeleton.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
|
||||
export function BarGraphSkeleton() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Skeleton className='h-6 w-[160px]' />
|
||||
<Skeleton className='h-5 w-[60px] rounded-full' />
|
||||
</div>
|
||||
<Skeleton className='h-4 w-[150px]' />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className='flex aspect-auto h-[280px] w-full items-end justify-around gap-2 pt-8'>
|
||||
{Array.from({ length: 12 }).map((_, i) => (
|
||||
<Skeleton
|
||||
key={i}
|
||||
className='w-full rounded-t-sm'
|
||||
style={{
|
||||
height: `${Math.max(20, Math.random() * 100)}%`
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
144
src/features/overview/components/bar-graph.tsx
Normal file
144
src/features/overview/components/bar-graph.tsx
Normal file
@@ -0,0 +1,144 @@
|
||||
'use client';
|
||||
|
||||
import { Bar, BarChart, XAxis } 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';
|
||||
|
||||
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 }
|
||||
];
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: 'Desktop',
|
||||
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>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ChartContainer config={chartConfig}>
|
||||
<BarChart accessibilityLayer data={chartData}>
|
||||
<rect
|
||||
x='0'
|
||||
y='0'
|
||||
width='100%'
|
||||
height='85%'
|
||||
fill='url(#default-multiple-pattern-dots)'
|
||||
/>
|
||||
<defs>
|
||||
<DottedBackgroundPattern />
|
||||
</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}
|
||||
/>
|
||||
<Bar
|
||||
dataKey='mobile'
|
||||
fill='var(--color-mobile)'
|
||||
shape={<CustomHatchedBar />}
|
||||
radius={4}
|
||||
/>
|
||||
</BarChart>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
138
src/features/overview/components/overview.tsx
Normal file
138
src/features/overview/components/overview.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import PageContainer from '@/components/layout/page-container';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Card,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardAction
|
||||
} from '@/components/ui/card';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { AreaGraph } from './area-graph';
|
||||
import { BarGraph } from './bar-graph';
|
||||
import { PieGraph } from './pie-graph';
|
||||
import { RecentSales } from './recent-sales';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
|
||||
export default function OverViewPage() {
|
||||
return (
|
||||
<PageContainer>
|
||||
<div className='flex flex-1 flex-col space-y-2'>
|
||||
<div className='flex items-center justify-between space-y-2'>
|
||||
<h2 className='text-2xl font-bold tracking-tight'>Hi, Welcome back 👋</h2>
|
||||
<div className='hidden items-center space-x-2 md:flex'>
|
||||
<Button>Download</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Tabs defaultValue='overview' className='space-y-4'>
|
||||
<TabsList>
|
||||
<TabsTrigger value='overview'>Overview</TabsTrigger>
|
||||
<TabsTrigger value='analytics' disabled>
|
||||
Analytics
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value='overview' className='space-y-4'>
|
||||
<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 px-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:shadow-xs lg:px-6 @xl/main:grid-cols-2 @5xl/main: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>
|
||||
</div>
|
||||
<div className='grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-7'>
|
||||
<div className='col-span-4'>
|
||||
<BarGraph />
|
||||
</div>
|
||||
<Card className='col-span-4 md:col-span-3'>
|
||||
<RecentSales />
|
||||
</Card>
|
||||
<div className='col-span-4'>
|
||||
<AreaGraph />
|
||||
</div>
|
||||
<div className='col-span-4 md:col-span-3'>
|
||||
<PieGraph />
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
19
src/features/overview/components/pie-graph-skeleton.tsx
Normal file
19
src/features/overview/components/pie-graph-skeleton.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||
|
||||
export function PieGraphSkeleton() {
|
||||
return (
|
||||
<Card className='flex h-full flex-col'>
|
||||
<CardHeader className='items-center pb-0'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Skeleton className='h-6 w-[100px]' />
|
||||
<Skeleton className='h-5 w-[60px] rounded-full' />
|
||||
</div>
|
||||
<Skeleton className='h-4 w-[150px]' />
|
||||
</CardHeader>
|
||||
<CardContent className='flex flex-1 items-center justify-center pb-0'>
|
||||
<Skeleton className='h-[250px] w-[250px] rounded-full' />
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
91
src/features/overview/components/pie-graph.tsx
Normal file
91
src/features/overview/components/pie-graph.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
'use client';
|
||||
|
||||
import { LabelList, 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';
|
||||
|
||||
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 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;
|
||||
|
||||
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>
|
||||
<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>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
27
src/features/overview/components/recent-sales-skeleton.tsx
Normal file
27
src/features/overview/components/recent-sales-skeleton.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||
|
||||
export function RecentSalesSkeleton() {
|
||||
return (
|
||||
<Card className='h-full'>
|
||||
<CardHeader>
|
||||
<Skeleton className='h-6 w-[140px]' /> {/* CardTitle */}
|
||||
<Skeleton className='h-4 w-[180px]' /> {/* CardDescription */}
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className='space-y-8'>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<div key={i} className='flex items-center'>
|
||||
<Skeleton className='h-9 w-9 rounded-full' /> {/* Avatar */}
|
||||
<div className='ml-4 space-y-1'>
|
||||
<Skeleton className='h-4 w-[120px]' /> {/* Name */}
|
||||
<Skeleton className='h-4 w-[160px]' /> {/* Email */}
|
||||
</div>
|
||||
<Skeleton className='ml-auto h-4 w-[80px]' /> {/* Amount */}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
68
src/features/overview/components/recent-sales.tsx
Normal file
68
src/features/overview/components/recent-sales.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { Card, CardHeader, CardContent, CardTitle, CardDescription } from '@/components/ui/card';
|
||||
|
||||
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'
|
||||
},
|
||||
{
|
||||
name: 'Sofia Davis',
|
||||
email: 'sofia.davis@email.com',
|
||||
avatar: 'https://api.slingacademy.com/public/sample-users/5.png',
|
||||
fallback: 'SD',
|
||||
amount: '+$39.00'
|
||||
}
|
||||
];
|
||||
|
||||
export function RecentSales() {
|
||||
return (
|
||||
<Card className='h-full'>
|
||||
<CardHeader>
|
||||
<CardTitle>Recent Sales</CardTitle>
|
||||
<CardDescription>You made 265 sales this month.</CardDescription>
|
||||
</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>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user