task-b complate

This commit is contained in:
phaichayon
2026-06-11 16:55:45 +07:00
parent 7a390cf0df
commit 67545d9295
135 changed files with 3295 additions and 6385 deletions

View File

@@ -0,0 +1,3 @@
export default function Default() {
return null;
}

View File

@@ -0,0 +1,14 @@
'use client';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Icons } from '@/components/icons';
export default function AreaStatsError({ error }: { error: Error }) {
return (
<Alert variant='destructive'>
<Icons.alertCircle className='h-4 w-4' />
<AlertTitle>Error</AlertTitle>
<AlertDescription>Failed to load area statistics: {error.message}</AlertDescription>
</Alert>
);
}

View File

@@ -0,0 +1,5 @@
import { AreaGraphSkeleton } from '@/features/overview/components/area-graph-skeleton';
export default function Loading() {
return <AreaGraphSkeleton />;
}

View File

@@ -0,0 +1,7 @@
import { delay } from '@/constants/mock-api';
import { AreaGraph } from '@/features/overview/components/area-graph';
export default async function AreaStats() {
await delay(2000);
return <AreaGraph />;
}

View File

@@ -0,0 +1,3 @@
export default function Default() {
return null;
}

View File

@@ -0,0 +1,60 @@
'use client';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader } from '@/components/ui/card';
import { Icons } from '@/components/icons';
import { useRouter } from 'next/navigation';
import { useEffect, useTransition } from 'react';
import * as Sentry from '@sentry/nextjs';
interface StatsErrorProps {
error: Error;
reset: () => void; // Add reset function from error boundary
}
export default function StatsError({ error, reset }: StatsErrorProps) {
const router = useRouter();
const [isPending, startTransition] = useTransition();
useEffect(() => {
Sentry.captureException(error);
}, [error]);
// the reload fn ensures the refresh is deffered until the next render phase allowing react to handle any pending states before processing
const reload = () => {
startTransition(() => {
router.refresh();
reset();
});
};
return (
<Card className='border-red-500'>
<CardHeader className='flex flex-col items-stretch space-y-0 border-b p-0 sm:flex-row'>
<div className='flex flex-1 flex-col justify-center gap-1 px-6 py-5 sm:py-6'>
<Alert variant='destructive' className='border-none'>
<Icons.alertCircle className='h-4 w-4' />
<AlertTitle>Error</AlertTitle>
<AlertDescription className='mt-2'>
Failed to load statistics: {error.message}
</AlertDescription>
</Alert>
</div>
</CardHeader>
<CardContent className='flex h-[316px] items-center justify-center p-6'>
<div className='text-center'>
<p className='text-muted-foreground mb-4 text-sm'>
Unable to display statistics at this time
</p>
<Button
onClick={() => reload()}
variant='outline'
className='min-w-[120px]'
disabled={isPending}
>
Try again
</Button>
</div>
</CardContent>
</Card>
);
}

View File

@@ -0,0 +1,5 @@
import { BarGraphSkeleton } from '@/features/overview/components/bar-graph-skeleton';
export default function Loading() {
return <BarGraphSkeleton />;
}

View File

@@ -0,0 +1,8 @@
import { delay } from '@/constants/mock-api';
import { BarGraph } from '@/features/overview/components/bar-graph';
export default async function BarStats() {
await delay(1000);
return <BarGraph />;
}

View File

@@ -0,0 +1,3 @@
export default function Default() {
return null;
}

View File

@@ -0,0 +1,14 @@
'use client';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Icons } from '@/components/icons';
export default function PieStatsError({ error }: { error: Error }) {
return (
<Alert variant='destructive'>
<Icons.alertCircle className='h-4 w-4' />
<AlertTitle>Error</AlertTitle>
<AlertDescription>Failed to load pie statistics: {error.message}</AlertDescription>
</Alert>
);
}

View File

@@ -0,0 +1,5 @@
import { PieGraphSkeleton } from '@/features/overview/components/pie-graph-skeleton';
export default function Loading() {
return <PieGraphSkeleton />;
}

View File

@@ -0,0 +1,7 @@
import { delay } from '@/constants/mock-api';
import { PieGraph } from '@/features/overview/components/pie-graph';
export default async function Stats() {
await delay(1000);
return <PieGraph />;
}

View File

@@ -0,0 +1,3 @@
export default function Default() {
return null;
}

View File

@@ -0,0 +1,14 @@
'use client';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Icons } from '@/components/icons';
export default function SalesError({ error }: { error: Error }) {
return (
<Alert variant='destructive'>
<Icons.alertCircle className='h-4 w-4' />
<AlertTitle>Error</AlertTitle>
<AlertDescription>Failed to load sales data: {error.message}</AlertDescription>
</Alert>
);
}

View File

@@ -0,0 +1,6 @@
import { RecentSalesSkeleton } from '@/features/overview/components/recent-sales-skeleton';
import React from 'react';
export default function Loading() {
return <RecentSalesSkeleton />;
}

View File

@@ -0,0 +1,7 @@
import { delay } from '@/constants/mock-api';
import { RecentSales } from '@/features/overview/components/recent-sales';
export default async function Sales() {
await delay(3000);
return <RecentSales />;
}

View File

@@ -0,0 +1,14 @@
'use client';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Icons } from '@/components/icons';
export default function OverviewError({ error }: { error: Error }) {
return (
<Alert variant='destructive'>
<Icons.alertCircle className='h-4 w-4' />
<AlertTitle>Error</AlertTitle>
<AlertDescription>Failed to load statistics: {error.message}</AlertDescription>
</Alert>
);
}

View File

@@ -0,0 +1,126 @@
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 { Icons } from '@/components/icons';
import React from 'react';
export default function OverViewLayout({
sales,
pie_stats,
bar_stats,
area_stats
}: {
sales: React.ReactNode;
pie_stats: React.ReactNode;
bar_stats: React.ReactNode;
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>
</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>
</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>
</div>
</div>
</PageContainer>
);
}