diff --git a/src/app/(dashboard)/dashboard/account/page.tsx b/src/app/(dashboard)/dashboard/account/page.tsx index 0c4209c8..27df1322 100644 --- a/src/app/(dashboard)/dashboard/account/page.tsx +++ b/src/app/(dashboard)/dashboard/account/page.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next" import { env } from "@/env.mjs" + import { UserProfile } from "@/components/auth/user-profile" import { PageHeader, @@ -7,7 +8,6 @@ import { PageHeaderHeading, } from "@/components/page-header" import { Shell } from "@/components/shells/shell" -import {dark} from "@clerk/themes"; export const metadata: Metadata = { metadataBase: new URL(env.NEXT_PUBLIC_APP_URL), @@ -33,9 +33,7 @@ export default function AccountPage() { aria-labelledby="user-account-info-heading" className="w-full overflow-hidden" > - + ) diff --git a/src/app/(dashboard)/dashboard/stores/[storeId]/analytics/_components/overview-card.tsx b/src/app/(dashboard)/dashboard/stores/[storeId]/analytics/_components/overview-card.tsx new file mode 100644 index 00000000..0a27aca8 --- /dev/null +++ b/src/app/(dashboard)/dashboard/stores/[storeId]/analytics/_components/overview-card.tsx @@ -0,0 +1,49 @@ +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Skeleton } from "@/components/ui/skeleton" +import { Icons } from "@/components/icons" + +interface OverviewCardProps { + title: string + value: string + description: string + icon: keyof typeof Icons +} + +export function OverviewCard({ + title, + value, + description, + icon, +}: OverviewCardProps) { + const Icon = Icons[icon] + + return ( + + + {title} + + +
{value}
+ {description && ( +

{description}

+ )} +
+
+ ) +} + +export function OverviewCardSkeleton() { + return ( + + + + + + + + + + + ) +} diff --git a/src/app/(dashboard)/dashboard/stores/[storeId]/analytics/_components/sales-chart.tsx b/src/app/(dashboard)/dashboard/stores/[storeId]/analytics/_components/sales-chart.tsx new file mode 100644 index 00000000..c001d20a --- /dev/null +++ b/src/app/(dashboard)/dashboard/stores/[storeId]/analytics/_components/sales-chart.tsx @@ -0,0 +1,28 @@ +"use client" + +import { LineChart, type LineChartProps } from "@tremor/react" + +import { cn, formatPrice } from "@/lib/utils" + +interface SalesChartProps + extends Omit { + data: { + name: string + Total: number + }[] +} + +export function SalesChart({ data, className, ...props }: SalesChartProps) { + return ( + formatPrice(value)} + yAxisWidth={48} + {...props} + /> + ) +} diff --git a/src/app/(dashboard)/dashboard/stores/[storeId]/analytics/page.tsx b/src/app/(dashboard)/dashboard/stores/[storeId]/analytics/page.tsx index 0e1eb33b..5cd87fcb 100644 --- a/src/app/(dashboard)/dashboard/stores/[storeId]/analytics/page.tsx +++ b/src/app/(dashboard)/dashboard/stores/[storeId]/analytics/page.tsx @@ -4,21 +4,38 @@ import { db } from "@/db" import { stores } from "@/db/schema" import { env } from "@/env.mjs" import type { SearchParams } from "@/types" +import { format } from "date-fns" import { eq } from "drizzle-orm" +import { z } from "zod" -import { getCustomers, getSalesCount } from "@/lib/fetchers/order" -import { formatNumber, formatPrice } from "@/lib/utils" +import { + getCustomers, + getOrderCount, + getSaleCount, + getSales, +} from "@/lib/fetchers/order" +import { cn, formatNumber, formatPrice } from "@/lib/utils" import { searchParamsSchema } from "@/lib/validations/params" -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { Avatar, AvatarFallback } from "@/components/ui/avatar" import { Card, CardContent, CardDescription, + CardFooter, CardHeader, CardTitle, } from "@/components/ui/card" +import { + Pagination, + PaginationContent, + PaginationItem, + PaginationNext, + PaginationPrevious, +} from "@/components/ui/pagination" import { DateRangePicker } from "@/components/date-range-picker" -import { Icons } from "@/components/icons" + +import { OverviewCard } from "./_components/overview-card" +import { SalesChart } from "./_components/sales-chart" export const metadata: Metadata = { metadataBase: new URL(env.NEXT_PUBLIC_APP_URL), @@ -39,8 +56,11 @@ export default async function AnalyticsPage({ }: AnalyticsPageProps) { const storeId = Number(params.storeId) - const { from, to } = searchParamsSchema - .pick({ from: true, to: true }) + const { page, per_page, from, to } = searchParamsSchema + .omit({ per_page: true, sort: true }) + .extend({ + per_page: z.coerce.number().default(5), + }) .parse(searchParams) const fromDay = from ? new Date(from) : undefined @@ -65,7 +85,19 @@ export default async function AnalyticsPage({ notFound() } - const salesCountPromise = getSalesCount({ + const orderCountPromise = getOrderCount({ + storeId, + fromDay: fromDay, + toDay: toDay, + }) + + const saleCountPromise = getSaleCount({ + storeId, + fromDay: fromDay, + toDay: toDay, + }) + + const salesPromise = getSales({ storeId, fromDay: fromDay, toDay: toDay, @@ -73,14 +105,19 @@ export default async function AnalyticsPage({ const customersPromise = getCustomers({ storeId, + limit: per_page ?? 5, + offset: (page - 1) * per_page, fromDay: fromDay, toDay: toDay, }) - const [sales, customers] = await Promise.all([ - salesCountPromise, - customersPromise, - ]) + const [saleCount, orderCount, sales, { customers, customerCount }] = + await Promise.all([ + saleCountPromise, + orderCountPromise, + salesPromise, + customersPromise, + ]) return (
@@ -88,84 +125,114 @@ export default async function AnalyticsPage({

Analytics

-
- - - Total Revenue -
) } diff --git a/src/app/(lobby)/products/page.tsx b/src/app/(lobby)/products/page.tsx index 4422d38b..0ff58c94 100644 --- a/src/app/(lobby)/products/page.tsx +++ b/src/app/(lobby)/products/page.tsx @@ -40,12 +40,9 @@ export default async function ProductsPage({ } = productsSearchParamsSchema.parse(searchParams) // Products transaction - const pageAsNumber = Number(page) - const fallbackPage = - isNaN(pageAsNumber) || pageAsNumber < 1 ? 1 : pageAsNumber - const perPageAsNumber = Number(per_page) + const fallbackPage = isNaN(page) || page < 1 ? 1 : page // Number of items per page - const limit = isNaN(perPageAsNumber) ? 10 : perPageAsNumber + const limit = isNaN(per_page) ? 10 : per_page // Number of items to skip const offset = fallbackPage > 0 ? (fallbackPage - 1) * limit : 0 diff --git a/src/components/ui/pagination.tsx b/src/components/ui/pagination.tsx new file mode 100644 index 00000000..507b35ac --- /dev/null +++ b/src/components/ui/pagination.tsx @@ -0,0 +1,126 @@ +import * as React from "react" +import Link from "next/link" +import { + ChevronLeftIcon, + ChevronRightIcon, + DotsHorizontalIcon, +} from "@radix-ui/react-icons" + +import { cn } from "@/lib/utils" +import { buttonVariants, type ButtonProps } from "@/components/ui/button" + +const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => ( +