From 8f007ad51daf9cd4b57cbae93c7d4b369237a597 Mon Sep 17 00:00:00 2001 From: Abhinav Pandey Date: Wed, 1 Jul 2026 21:31:19 +0530 Subject: [PATCH 1/3] Added loader for sales --- app/blocks/sales-log/sales-log-skeleton.tsx | 35 ++++++++++++++++++ app/routes/sales-log.tsx | 39 ++++++++++++++------- 2 files changed, 62 insertions(+), 12 deletions(-) create mode 100644 app/blocks/sales-log/sales-log-skeleton.tsx diff --git a/app/blocks/sales-log/sales-log-skeleton.tsx b/app/blocks/sales-log/sales-log-skeleton.tsx new file mode 100644 index 0000000..18ac19a --- /dev/null +++ b/app/blocks/sales-log/sales-log-skeleton.tsx @@ -0,0 +1,35 @@ +import { Skeleton } from "~/blocks/__global/skeleton"; +import styles from "./sales-table.module.css"; + +export function SalesTableSkeleton() { + return ( +
+
+ ß + + + + + + + + + + + + {Array.from({ length: 6 }).map((_, i) => ( + + + + + + + + + ))} + +
ItemMarketplaceSale PriceDateMarginProfit
+
+
+ ); +} \ No newline at end of file diff --git a/app/routes/sales-log.tsx b/app/routes/sales-log.tsx index 4c3fdb6..c7a6374 100644 --- a/app/routes/sales-log.tsx +++ b/app/routes/sales-log.tsx @@ -1,5 +1,5 @@ -import { useState, useEffect } from "react"; -import { useLoaderData, useActionData } from "react-router"; +import { Suspense, useState, useEffect } from "react"; +import { useLoaderData, useActionData, Await } from "react-router"; import type { Route } from "./+types/sales-log"; import { toast } from "sonner"; import { getSupabaseServerClient } from "~/utils/supabase.server"; @@ -8,7 +8,9 @@ import styles from "./sales-log.module.css"; import { SalesHeader } from "~/blocks/sales-log/sales-header"; import { SalesSummaryCards } from "~/blocks/sales-log/sales-summary-cards"; import { SalesTable } from "~/blocks/sales-log/sales-table"; +import { SalesTableSkeleton } from "~/blocks/sales-log/sales-log-skeleton"; import { LogSaleModal } from "~/blocks/sales-log/log-sale-modal"; +import { Skeleton } from "~/blocks/__global/skeleton"; const prisma = new PrismaClient(); @@ -16,9 +18,9 @@ export async function loader({ request }: Route.LoaderArgs) { const { supabase } = getSupabaseServerClient(request); const { data: { user } } = await supabase.auth.getUser(); - if (!user) return { sales: [], inventory: [] }; + if (!user) return { salesData: Promise.resolve({ sales: [], inventory: [] }) }; - const [sales, inventory] = await Promise.all([ + const salesData = Promise.all([ prisma.sale.findMany({ where: { userId: user.id }, include: { inventoryItem: true }, @@ -28,9 +30,9 @@ export async function loader({ request }: Route.LoaderArgs) { where: { userId: user.id, status: "IN_STOCK" }, orderBy: { createdAt: "desc" }, }), - ]); + ]).then(([sales, inventory]) => ({ sales, inventory })); - return { sales, inventory }; + return { salesData }; } export async function action({ request }: Route.ActionArgs) { @@ -70,7 +72,7 @@ export async function action({ request }: Route.ActionArgs) { } export default function SalesLogPage() { - const { sales, inventory } = useLoaderData(); + const { salesData } = useLoaderData(); const actionData = useActionData(); const [showLogSale, setShowLogSale] = useState(false); @@ -82,13 +84,26 @@ export default function SalesLogPage() { } } }, [actionData]); - + return (
setShowLogSale(true)} /> - - - {showLogSale && setShowLogSale(false)} />} + + + +
+ }> + + {({ sales, inventory }) => ( + <> + + + {showLogSale && setShowLogSale(false)} />} + + )} + + ); -} +} \ No newline at end of file From fca4fb85c52a8de62db479abca803510e8126582 Mon Sep 17 00:00:00 2001 From: Abhinav Pandey Date: Wed, 1 Jul 2026 21:38:42 +0530 Subject: [PATCH 2/3] Added Inventory loader --- .../inventory-table-skeleton.tsx | 41 ++++++++ app/routes/inventory-management.tsx | 99 ++++++++++--------- 2 files changed, 95 insertions(+), 45 deletions(-) create mode 100644 app/blocks/inventory-management/inventory-table-skeleton.tsx diff --git a/app/blocks/inventory-management/inventory-table-skeleton.tsx b/app/blocks/inventory-management/inventory-table-skeleton.tsx new file mode 100644 index 0000000..28a58f5 --- /dev/null +++ b/app/blocks/inventory-management/inventory-table-skeleton.tsx @@ -0,0 +1,41 @@ +import { Skeleton } from "~/blocks/__global/skeleton"; +import styles from "./inventory-table.module.css"; + +export function InventoryTableSkeleton() { + return ( +
+
+ + + + + + + + + + + + + + + + {Array.from({ length: 8 }).map((_, i) => ( + + + + + + + + + + + + ))} + +
ItemSKUSizeBuy PriceMarket ValueP/LStatusActions
+
+
+ ); +} \ No newline at end of file diff --git a/app/routes/inventory-management.tsx b/app/routes/inventory-management.tsx index 7abbdf4..470bc71 100644 --- a/app/routes/inventory-management.tsx +++ b/app/routes/inventory-management.tsx @@ -1,5 +1,5 @@ -import { useState, useEffect } from "react"; -import { useLoaderData, useActionData } from "react-router"; +import { Suspense, useState, useEffect } from "react"; +import { useLoaderData, useActionData, Await } from "react-router"; import type { Route } from "./+types/inventory-management"; import { toast } from "sonner"; import { getSupabaseServerClient } from "~/utils/supabase.server"; @@ -7,6 +7,7 @@ import { PrismaClient } from "@prisma/client"; import styles from "./inventory-management.module.css"; import { InventoryHeader } from "~/blocks/inventory-management/inventory-header"; import { InventoryTable } from "~/blocks/inventory-management/inventory-table"; +import { InventoryTableSkeleton } from "~/blocks/inventory-management/inventory-table-skeleton"; import { BulkActionsBar } from "~/blocks/inventory-management/bulk-actions-bar"; import { AddItemModal } from "~/blocks/inventory-management/add-item-modal"; import { ImportExcelModal } from "~/blocks/inventory-management/import-excel-modal"; @@ -17,9 +18,9 @@ export async function loader({ request }: Route.LoaderArgs) { const { supabase } = getSupabaseServerClient(request); const { data: { user } } = await supabase.auth.getUser(); - if (!user) return { items: [] }; + if (!user) return { inventoryData: Promise.resolve({ items: [] }) }; - const items = await prisma.inventoryItem.findMany({ + const inventoryData = prisma.inventoryItem.findMany({ where: { userId: user.id }, orderBy: { createdAt: "desc" }, include: { @@ -28,14 +29,16 @@ export async function loader({ request }: Route.LoaderArgs) { take: 1 } } - }); - - const formattedItems = items.map(item => ({ - ...item, - marketValue: item.priceHistory[0]?.askPrice ? Number(item.priceHistory[0].askPrice) : null, + }).then(items => ({ + items: items.map(item => ({ + ...item, + marketValue: item.priceHistory[0]?.askPrice + ? Number(item.priceHistory[0].askPrice) + : null, + })) })); - return { items: formattedItems }; + return { inventoryData }; } export async function action({ request }: Route.ActionArgs) { @@ -52,15 +55,10 @@ export async function action({ request }: Route.ActionArgs) { const brand = formData.get("brand") as string; const size = formData.get("size") as string; const purchasePrice = Number(formData.get("purchasePrice")); - await prisma.inventoryItem.create({ data: { userId: user.id, - sku, - name, - brand, - size, - purchasePrice, + sku, name, brand, size, purchasePrice, purchaseDate: new Date(), condition: "DEADSTOCK", status: "IN_STOCK", @@ -73,22 +71,14 @@ export async function action({ request }: Route.ActionArgs) { const brand = formData.get("brand") as string; const size = formData.get("size") as string; const purchasePrice = Number(formData.get("purchasePrice")); - await prisma.inventoryItem.update({ where: { id: itemId, userId: user.id }, - data: { - sku, - name, - brand, - size, - purchasePrice, - // other fields could be updated here - } + data: { sku, name, brand, size, purchasePrice } }); } else if (intent === "delete") { const itemId = formData.get("itemId") as string; await prisma.inventoryItem.delete({ - where: { id: itemId, userId: user.id } // Ensures the user owns the item + where: { id: itemId, userId: user.id } }); } else if (intent === "bulk-delete") { const ids = formData.getAll("ids") as string[]; @@ -107,7 +97,7 @@ export async function action({ request }: Route.ActionArgs) { } export default function InventoryManagementPage() { - const { items } = useLoaderData(); + const { inventoryData } = useLoaderData(); const actionData = useActionData(); const [showAddItem, setShowAddItem] = useState(false); const [showImport, setShowImport] = useState(false); @@ -135,16 +125,6 @@ export default function InventoryManagementPage() { } }, [actionData]); - const filteredItems = items.filter((item) => { - if (!searchQuery) return true; - const lowerQuery = searchQuery.toLowerCase(); - return ( - (item.name?.toLowerCase() || "").includes(lowerQuery) || - (item.sku?.toLowerCase() || "").includes(lowerQuery) || - (item.brand?.toLowerCase() || "").includes(lowerQuery) - ); - }); - return (
- {selected.length > 0 && ( - setSelected([])} selectedIds={selected} items={items} /> - )} - - {showAddItem && setShowAddItem(false)} />} - {editingItem && setEditingItem(null)} />} - {showImport && setShowImport(false)} />} + }> + + {({ items }) => { + const filteredItems = items.filter((item) => { + if (!searchQuery) return true; + const lowerQuery = searchQuery.toLowerCase(); + return ( + (item.name?.toLowerCase() || "").includes(lowerQuery) || + (item.sku?.toLowerCase() || "").includes(lowerQuery) || + (item.brand?.toLowerCase() || "").includes(lowerQuery) + ); + }); + return ( + <> + {selected.length > 0 && ( + setSelected([])} + selectedIds={selected} + items={items} + /> + )} + + {showAddItem && setShowAddItem(false)} />} + {editingItem && setEditingItem(null)} />} + {showImport && setShowImport(false)} />} + + ); + }} + +
); -} +} \ No newline at end of file From cc2433eb4c4d15a524aa35d8ab9e0c1b5266c2b3 Mon Sep 17 00:00:00 2001 From: Abhinav Pandey Date: Wed, 1 Jul 2026 21:41:02 +0530 Subject: [PATCH 3/3] Created Dashboard loader --- app/blocks/dashboard/dashboard-skeleton.tsx | 34 +++++++++ app/routes/dashboard.tsx | 78 ++++++++++++--------- 2 files changed, 77 insertions(+), 35 deletions(-) create mode 100644 app/blocks/dashboard/dashboard-skeleton.tsx diff --git a/app/blocks/dashboard/dashboard-skeleton.tsx b/app/blocks/dashboard/dashboard-skeleton.tsx new file mode 100644 index 0000000..6d8661f --- /dev/null +++ b/app/blocks/dashboard/dashboard-skeleton.tsx @@ -0,0 +1,34 @@ +import { Skeleton } from "~/blocks/__global/skeleton"; + +export function DashboardSkeleton() { + return ( +
+ + {/* Stats cards row — 4 cards */} +
+ {Array.from({ length: 4 }).map((_, i) => ( +
+ +
+ ))} +
+ + {/* Cash flow chart */} + + + {/* 3 charts row */} +
+ + + +
+ + {/* Top selling items table */} + + + {/* Recent sales */} + + +
+ ); +} \ No newline at end of file diff --git a/app/routes/dashboard.tsx b/app/routes/dashboard.tsx index c2661b6..e83d258 100644 --- a/app/routes/dashboard.tsx +++ b/app/routes/dashboard.tsx @@ -1,4 +1,5 @@ -import { useLoaderData } from "react-router"; +import { Suspense } from "react"; +import { useLoaderData, Await } from "react-router"; import type { Route } from "./+types/dashboard"; import { getSupabaseServerClient } from "~/utils/supabase.server"; import { PrismaClient } from "@prisma/client"; @@ -11,8 +12,8 @@ import { SalesByMarketplacePie } from "~/blocks/dashboard/sales-by-marketplace-p import { TopSellingItemsTable } from "~/blocks/dashboard/top-selling-items-table"; import { RecentSales } from "~/blocks/dashboard/recent-sales"; import { ExpenseCategoriesChart } from "~/blocks/dashboard/expense-categories-chart"; - import { AIInsightsPanel } from "~/blocks/dashboard/ai-insights-panel"; +import { DashboardSkeleton } from "~/blocks/dashboard/dashboard-skeleton"; const prisma = new PrismaClient(); @@ -21,10 +22,10 @@ export async function loader({ request }: Route.LoaderArgs) { const { data: { user } } = await supabase.auth.getUser(); if (!user) { - return { inventoryStats: null, salesData: [], expensesData: [] }; + return { dashboardData: Promise.resolve({ inventoryStats: null, salesData: [], expensesData: [] }) }; } - const [inventoryStats, salesData, expensesData] = await Promise.all([ + const dashboardData = Promise.all([ prisma.inventoryItem.aggregate({ where: { userId: user.id, status: 'IN_STOCK' }, _sum: { purchasePrice: true }, @@ -38,45 +39,52 @@ export async function loader({ request }: Route.LoaderArgs) { prisma.expense.findMany({ where: { userId: user.id }, }), - ]); - - const serializedStats = { - _count: inventoryStats?._count || 0, - _sum: { purchasePrice: Number(inventoryStats?._sum?.purchasePrice || 0) } - }; - - const serializedSales = salesData.map(s => ({ - ...s, - salePrice: Number(s.salePrice), - inventoryItem: { - ...s.inventoryItem, - purchasePrice: Number(s.inventoryItem.purchasePrice), - } + ]).then(([inventoryStats, salesData, expensesData]) => ({ + inventoryStats: { + _count: inventoryStats?._count || 0, + _sum: { purchasePrice: Number(inventoryStats?._sum?.purchasePrice || 0) } + }, + salesData: salesData.map(s => ({ + ...s, + salePrice: Number(s.salePrice), + inventoryItem: { + ...s.inventoryItem, + purchasePrice: Number(s.inventoryItem.purchasePrice), + } + })), + expensesData: expensesData.map(e => ({ + ...e, + amount: Number(e.amount) + })) })); - const serializedExpenses = expensesData.map(e => ({ - ...e, - amount: Number(e.amount) - })); - - return { inventoryStats: serializedStats, salesData: serializedSales, expensesData: serializedExpenses }; + return { dashboardData }; } export default function DashboardPage() { - const { inventoryStats, salesData, expensesData } = useLoaderData(); + const { dashboardData } = useLoaderData(); + return (
- - -
- - - -
- - + }> + + {({ inventoryStats, salesData, expensesData }) => ( + <> + + +
+ + + +
+ + + + )} +
+
); -} +} \ No newline at end of file