From 7db93e9e8ae042dfb1bd232bea4eb06d2584e5af Mon Sep 17 00:00:00 2001 From: Thastertyn Date: Tue, 22 Apr 2025 10:09:40 +0200 Subject: [PATCH] Dashboard fix --- .../src/api/mock/data/create-purchase-data.ts | 28 +++-- .../components/layout/data/sidebar-data.ts | 12 +- .../pages/dashboard/components/overview.tsx | 78 ++++-------- .../dashboard/components/recent-sales.tsx | 111 ++++++------------ frontend/src/pages/dashboard/index.tsx | 98 +++++++++++----- .../products/components/product-list.tsx | 3 +- frontend/src/routes/(auth)/sign-in.tsx | 2 +- frontend/src/utils/format-currency.ts | 6 + 8 files changed, 165 insertions(+), 173 deletions(-) create mode 100644 frontend/src/utils/format-currency.ts diff --git a/frontend/src/api/mock/data/create-purchase-data.ts b/frontend/src/api/mock/data/create-purchase-data.ts index b7fbc52..dd2da83 100644 --- a/frontend/src/api/mock/data/create-purchase-data.ts +++ b/frontend/src/api/mock/data/create-purchase-data.ts @@ -13,7 +13,6 @@ export async function createSamplePurchase() { for (let i = 0; i < Math.floor(Math.random() * 3) + 1; i++) { const product = products[Math.floor(Math.random() * products.length)]; - const quantity = Math.floor(Math.random() * 4) + 1; entries.push({ @@ -26,13 +25,15 @@ export async function createSamplePurchase() { if (entries.length === 0) throw new Error("No suitable product variants found"); - const total = await entries.reduce(async (accP, entry) => { - const acc = await accP; - const product = await mockDB.products.get(entry.product_id); - return acc + ((product?.price ?? 0) * entry.quantity); - }, Promise.resolve(0)); + let total = 0; + + for (const entry of entries) { + const product = await mockDB.products.get(entry.product_id); + if (product) { + total += product.price * entry.quantity; + } + } - // Randomly apply a valid coupon const coupons = await mockDB.coupons.toArray(); const validCoupons = coupons.filter( (c) => new Date(c.valid_due) > new Date() @@ -46,18 +47,27 @@ export async function createSamplePurchase() { ? Math.max(0, total - chosenCoupon.discount_amount) : total; + // ✅ Generate a random date within the past year + const now = new Date(); + const monthsAgo = Math.floor(Math.random() * 12); // 0–11 months ago + const date = new Date(now.getFullYear(), now.getMonth() - monthsAgo, 1); + + const randomDay = Math.floor(Math.random() * 28) + 1; + date.setDate(randomDay); + const isoDate = date.toISOString(); + const purchase = await MockPurchaseAPI.createPurchase( { user_id: user.id, used_coupon_id: chosenCoupon?.id ?? null, - date_purchased: new Date().toISOString(), + date_purchased: isoDate, total: discountedTotal, }, entries, ); console.log( - `Created mock purchase${chosenCoupon ? " with coupon" : ""}:`, + `Created mock purchase${chosenCoupon ? " with coupon" : ""} on ${date.toDateString()}:`, purchase, ); } diff --git a/frontend/src/components/layout/data/sidebar-data.ts b/frontend/src/components/layout/data/sidebar-data.ts index 04e37ff..d2afa4f 100644 --- a/frontend/src/components/layout/data/sidebar-data.ts +++ b/frontend/src/components/layout/data/sidebar-data.ts @@ -2,7 +2,7 @@ import { IconBuildingStore, IconClipboardCheckFilled, - IconCoin, IconPackage, + IconCoin, IconLayoutDashboard, IconPackage, IconPalette, IconSettings, IconTag, @@ -16,11 +16,11 @@ export const sidebarData: SidebarData = { { title: "Dashboard", items: [ - // { - // title: "Dashboard", - // url: "/dashboard", - // icon: IconLayoutDashboard, - // }, + { + title: "Dashboard", + url: "/dashboard", + icon: IconLayoutDashboard, + }, { title: "Shop", url: "/dashboard/shop", diff --git a/frontend/src/pages/dashboard/components/overview.tsx b/frontend/src/pages/dashboard/components/overview.tsx index 7269fb4..b5f6286 100644 --- a/frontend/src/pages/dashboard/components/overview.tsx +++ b/frontend/src/pages/dashboard/components/overview.tsx @@ -1,60 +1,32 @@ -import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from "recharts"; +import { + Bar, + BarChart, + ResponsiveContainer, + XAxis, + YAxis, +} from "recharts"; -const data = [ - { - name: "Jan", - total: Math.floor(Math.random() * 5000) + 1000, - }, - { - name: "Feb", - total: Math.floor(Math.random() * 5000) + 1000, - }, - { - name: "Mar", - total: Math.floor(Math.random() * 5000) + 1000, - }, - { - name: "Apr", - total: Math.floor(Math.random() * 5000) + 1000, - }, - { - name: "May", - total: Math.floor(Math.random() * 5000) + 1000, - }, - { - name: "Jun", - total: Math.floor(Math.random() * 5000) + 1000, - }, - { - name: "Jul", - total: Math.floor(Math.random() * 5000) + 1000, - }, - { - name: "Aug", - total: Math.floor(Math.random() * 5000) + 1000, - }, - { - name: "Sep", - total: Math.floor(Math.random() * 5000) + 1000, - }, - { - name: "Oct", - total: Math.floor(Math.random() * 5000) + 1000, - }, - { - name: "Nov", - total: Math.floor(Math.random() * 5000) + 1000, - }, - { - name: "Dec", - total: Math.floor(Math.random() * 5000) + 1000, - }, -]; +interface OverviewProps { + data: { name: string; total: number }[]; +} + +export function Overview({ data }: OverviewProps) { + // Create labels for the past 12 months ending with current + const monthFormatter = new Intl.DateTimeFormat("en-US", { month: "short" }); + const now = new Date(); + const past12Months = Array.from({ length: 12 }).map((_, i) => { + const d = new Date(now.getFullYear(), now.getMonth() - (11 - i), 1); + return monthFormatter.format(d); + }); + + const chartData = past12Months.map((month) => ({ + name: month, + total: data.find((d) => d.name === month)?.total ?? 0, + })); -export function Overview() { return ( - + new Date(b.date_purchased).getTime() - new Date(a.date_purchased).getTime()) + .slice(0, 5) // take 5 most recent + return (
-
- - - OM - -
-
-

Olivia Martin

-

- olivia.martin@email.com -

-
-
+$1,999.00
-
-
-
- - - JL - -
-
-

Jackson Lee

-

- jackson.lee@email.com -

-
-
+$39.00
-
-
-
- - - IN - -
-
-

Isabella Nguyen

-

- isabella.nguyen@email.com -

-
-
+$299.00
-
-
+ {recent?.map((purchase) => { + const user = users?.find((u) => u.id === purchase.user_id); + const initials = user + ? `${user.first_name?.[0] ?? "U"}${user.last_name?.[0] ?? ""}` + : "??"; + const name = user + ? `${user.first_name ?? "Unknown"} ${user.last_name ?? ""}` + : "Unknown User"; + const email = user?.email ?? "unknown@email.com"; -
- - - WK - -
-
-

William Kim

-

will@email.com

+ return ( +
+ + {initials} + +
+
+

{name}

+

{email}

+
+
+{formatCurrency(purchase.total, shop?.currency ?? "USD")}
+
-
+$99.00
-
-
- -
- - - SD - -
-
-

Sofia Davis

-

- sofia.davis@email.com -

-
-
+$39.00
-
-
+ ); + })}
); } diff --git a/frontend/src/pages/dashboard/index.tsx b/frontend/src/pages/dashboard/index.tsx index fb58360..4ced709 100644 --- a/frontend/src/pages/dashboard/index.tsx +++ b/frontend/src/pages/dashboard/index.tsx @@ -3,7 +3,7 @@ import { CardContent, CardDescription, CardHeader, - CardTitle, + CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent } from "@/components/ui/tabs"; import { Header } from "@/components/layout/header"; @@ -13,11 +13,43 @@ import { Search } from "@/components/search"; import { ThemeSwitch } from "@/components/theme-switch"; import { Overview } from "./components/overview"; import { RecentSales } from "./components/recent-sales"; +import { useUsers } from "@/hooks/useUsers"; +import { usePurchases } from "@/hooks/usePurchases"; +import { Purchase } from "@/api/mock/models"; +import { formatCurrency } from "@/utils/format-currency"; +import { useShop } from "@/hooks/useShop"; export default function Dashboard() { + const { data: users } = useUsers(); + const { shop } = useShop(); + const { data: purchases } = usePurchases(); + + const totalRevenue = purchases?.reduce((sum, p) => sum + p.total, 0) ?? 0; + + const totalCustomers = + users?.filter((u) => u.user_role === "customer").length ?? 0; + + const totalSales = purchases?.length ?? 0; + + const averageOrderValue = totalSales ? totalRevenue / totalSales : 0; + + function getMonthlyRevenue(purchases: Purchase[]) { + + const result: Record = {}; + purchases.forEach((p) => { + const date = new Date(p.date_purchased); + const month = date.toLocaleString("en-US", { month: "short" }); + result[month] = (result[month] || 0) + p.total; + }); + return result; + } + + const revenueFormatted = formatCurrency(totalRevenue, shop?.currency ?? "USD"); + + const avgFormatted = formatCurrency(averageOrderValue, shop?.currency ?? "USD"); + return ( <> - {/* ===== Top Heading ===== */}
@@ -26,7 +58,6 @@ export default function Dashboard() {
- {/* ===== Main ===== */}

Dashboard

@@ -34,10 +65,10 @@ export default function Dashboard() { + className="space-y-4">
+ {/* Total Revenue */} @@ -51,22 +82,23 @@ export default function Dashboard() { strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" - className="h-4 w-4 text-muted-foreground" - > + className="h-4 w-4 text-muted-foreground"> -
$45,231.89
+
{revenueFormatted}

- +20.1% from last month + Based on all time data

+ + {/* Total Customers */} - Subscriptions + Total Customers - - - + className="h-4 w-4 text-muted-foreground"> + + + -
+2350
+
{totalCustomers}

- +180.1% from last month + All-time registered customers

+ + {/* Sales */} Sales @@ -101,23 +134,24 @@ export default function Dashboard() { strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" - className="h-4 w-4 text-muted-foreground" - > + className="h-4 w-4 text-muted-foreground"> -
+12,234
+
+{totalSales}

- +19% from last month + Total purchases made

+ + {/* Average Order Value */} - Active Now + Average Order Value - + className="h-4 w-4 text-muted-foreground"> + + -
+573
+
{avgFormatted}

- +201 since last hour + Across all sales

+ + {/* Overview + Recent Sales */}
Overview - + ({ name, total }))} + /> Recent Sales - You made 265 sales this month. + You made {totalSales} sales overall. diff --git a/frontend/src/pages/products/components/product-list.tsx b/frontend/src/pages/products/components/product-list.tsx index a489db5..127c036 100644 --- a/frontend/src/pages/products/components/product-list.tsx +++ b/frontend/src/pages/products/components/product-list.tsx @@ -6,6 +6,7 @@ import { CardHeader, CardTitle, } from "@/components/ui/card"; +import { formatCurrency } from "@/utils/format-currency"; export function ProductList({ currency, @@ -47,7 +48,7 @@ export function ProductList({ {product.stock_quantity} in stock - {product.price.toFixed(2)} {currency} + {formatCurrency(product.price, currency)}
diff --git a/frontend/src/routes/(auth)/sign-in.tsx b/frontend/src/routes/(auth)/sign-in.tsx index 3d4712a..5734c1e 100644 --- a/frontend/src/routes/(auth)/sign-in.tsx +++ b/frontend/src/routes/(auth)/sign-in.tsx @@ -7,7 +7,7 @@ export const Route = createFileRoute("/(auth)/sign-in")({ beforeLoad: async () => { if (isLoggedIn()) { throw redirect({ - to: "/dashboard/shop", + to: "/dashboard", }); } }, diff --git a/frontend/src/utils/format-currency.ts b/frontend/src/utils/format-currency.ts new file mode 100644 index 0000000..6f25baf --- /dev/null +++ b/frontend/src/utils/format-currency.ts @@ -0,0 +1,6 @@ +export function formatCurrency(amount: number, currency: string) { + return new Intl.NumberFormat("en-US", { + style: "currency", + currency, + }).format(amount); +} \ No newline at end of file