@@ -15,33 +27,64 @@ export function SalesTable({ className, sales = [] }: Props) {
Date |
Margin |
Profit |
+ Actions |
- {sales.map(s => {
- const salePrice = Number(s.salePrice);
- const cost = Number(s.inventoryItem.purchasePrice);
- const platformFee = Number(s.platformFee);
- const shippingCost = Number(s.shippingCost);
- const profit = salePrice - cost - platformFee - shippingCost;
- const margin = salePrice > 0 ? ((profit / salePrice) * 100).toFixed(1) : 0;
- const dateObj = new Date(s.saleDate);
-
- return (
-
- | {s.inventoryItem.name} |
- {s.marketplace} |
- ${salePrice.toFixed(2)} |
- {dateObj.toLocaleDateString()} |
- {margin}% |
-
- = 0 ? styles.positive : styles.negative].join(" ")}>
- {profit >= 0 ? "+" : ""}${profit.toFixed(2)}
-
- |
-
- );
- })}
+ {sales.map((s) => {
+ const salePrice = Number(s.salePrice);
+ const cost = Number(s.inventoryItem.purchasePrice);
+ const platformFee = Number(s.platformFee ?? 0);
+ const shippingCost = Number(s.shippingCost ?? 0);
+
+ const profit = salePrice - cost - platformFee - shippingCost;
+ const margin =
+ salePrice > 0 ? ((profit / salePrice) * 100).toFixed(1) : "0.0";
+
+ const dateObj = new Date(s.saleDate);
+
+ return (
+
+ | {s.inventoryItem.name} |
+ {s.marketplace} |
+ ${salePrice.toFixed(2)} |
+ {dateObj.toLocaleDateString()} |
+ {margin}% |
+
+
+ = 0 ? styles.positive : styles.negative,
+ ].join(" ")}
+ >
+ {profit >= 0 ? "+" : ""}
+ ${profit.toFixed(2)}
+
+ |
+
+
+
+
+
+ |
+
+ );
+})}
diff --git a/app/routes/sales-log.tsx b/app/routes/sales-log.tsx
index af1c77a..b67a800 100644
--- a/app/routes/sales-log.tsx
+++ b/app/routes/sales-log.tsx
@@ -9,6 +9,7 @@ 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 { LogSaleModal } from "~/blocks/sales-log/log-sale-modal";
+import { DeleteSaleModal } from "~/blocks/sales-log/delete-sale-modal";
import { Pagination } from "~/blocks/__global/pagination";
import { CACHE_PRIVATE_NO_STORE } from "~/utils/cache-headers";
@@ -37,7 +38,7 @@ export async function loader({ request }: Route.LoaderArgs) {
const page = Math.max(1, Number(url.searchParams.get("page")) || 1);
const pageSize = Number(url.searchParams.get("pageSize")) || 10;
- const [totalSales, sales, metricsResult] = await Promise.all([
+ const [totalSales, sales, inventory, metricsResult] = await Promise.all([
prisma.sale.count({ where: { userId: user.id } }),
prisma.sale.findMany({
where: { userId: user.id },
@@ -46,7 +47,16 @@ export async function loader({ request }: Route.LoaderArgs) {
skip: (page - 1) * pageSize,
take: pageSize,
}),
-
+ prisma.inventoryItem.findMany({
+ where: {
+ userId: user.id,
+ status: "IN_STOCK",
+ },
+ orderBy: {
+ createdAt: "desc",
+ },
+}),
+
prisma.$queryRaw<{ totalRevenue: number; totalProfit: number }[]>`
SELECT
COALESCE(SUM(s."salePrice"), 0) AS "totalRevenue",
@@ -66,25 +76,32 @@ export async function loader({ request }: Route.LoaderArgs) {
`
]);
- const totalRevenue = Number(metricsResult[0]?.totalRevenue || 0);
- const totalProfit = Number(metricsResult[0]?.totalProfit || 0);
+const totalRevenue = Number(metricsResult[0]?.totalRevenue || 0);
+const totalProfit = Number(metricsResult[0]?.totalProfit || 0);
- return {
- sales: sales.map(s => ({
- ...s,
- salePrice: Number(s.salePrice),
- inventoryItem: {
- ...s.inventoryItem,
- purchasePrice: Number(s.inventoryItem.purchasePrice),
- }
- })),
- totalPages: Math.ceil(totalSales / pageSize),
- summary: {
- totalSalesCount: totalSales,
- totalRevenue,
- totalProfit,
+
+ return {
+ sales: sales.map((sale) => ({
+ ...sale,
+ salePrice: Number(sale.salePrice),
+ platformFee: Number(sale.platformFee),
+ shippingCost: Number(sale.shippingCost),
+
+ inventoryItem: {
+ ...sale.inventoryItem,
+ purchasePrice: Number(sale.inventoryItem.purchasePrice),
},
- };
+ })),
+
+ inventory,
+ totalPages: Math.ceil(totalSales / pageSize),
+
+ summary: {
+ totalSalesCount: totalSales,
+ totalRevenue,
+ totalProfit,
+ },
+};
}
export async function action({ request }: Route.ActionArgs) {
@@ -101,8 +118,8 @@ export async function action({ request }: Route.ActionArgs) {
const inventoryItemId = formData.get("inventoryItemId") as string;
const salePrice = Number(formData.get("salePrice"));
- const platformFee = Number(formData.get("platformFee") || 0);
- const shippingCost = Number(formData.get("shippingCost") || 0);
+ const platformFee = Number(formData.get("platformFee") || 0);
+ const shippingCost = Number(formData.get("shippingCost") || 0);
const saleDate = new Date(formData.get("saleDate") as string);
const marketplace = formData.get("marketplace") as any;
@@ -144,31 +161,144 @@ export async function action({ request }: Route.ActionArgs) {
}),
]);
}
+ if (intent === "edit") {
+ const saleId = formData.get("saleId") as string;
+ const salePrice = Number(formData.get("salePrice"));
+ const saleDate = new Date(formData.get("saleDate") as string);
+ const marketplace = formData.get("marketplace") as any;
+ const trackingNumber = formData.get("trackingNumber") as string;
+ const platformFee = Number(formData.get("platformFee") || 0);
+const shippingCost = Number(formData.get("shippingCost") || 0);
+
+ const sale = await prisma.sale.findFirst({
+ where: {
+ id: saleId,
+ userId: user.id,
+ },
+ });
+
+ if (!sale) {
+ return { ok: false };
+ }
+
+ await prisma.sale.update({
+ where: {
+ id: saleId,
+ },
+ data: {
+ platformFee,
+ shippingCost,
+ salePrice,
+ saleDate,
+ marketplace,
+ trackingNumber,
+ },
+ });
+
+ return { ok: true, intent: "edit" };
+}
+
+ if (intent === "delete") {
+ const saleId = formData.get("saleId") as string;
+
+ const sale = await prisma.sale.findFirst({
+ where: { id: saleId,
+ userId: user.id,
+ },
+ });
+
+ if (!sale) {
+ return { ok: false };
+ }
+
+ await prisma.$transaction([
+ prisma.sale.delete({
+ where: { id: saleId },
+ }),
+ prisma.inventoryItem.update({
+ where: { id: sale.inventoryItemId },
+ data: {
+ status: "IN_STOCK",
+ },
+ }),
+ ]);
+ return { ok: true, intent: "delete" };
+ }
return { ok: true, intent };
}
export default function SalesLogPage() {
- const { sales, totalPages, summary } = useLoaderData
();
+ const { sales,inventory, totalPages, summary } = useLoaderData();
const actionData = useActionData();
const [showLogSale, setShowLogSale] = useState(false);
+ const [selectedSale, setSelectedSale] = useState(null);
+ const [showDelete, setShowDelete] = useState(false);
+ const [showEdit, setShowEdit] = useState(false);
- useEffect(() => {
- if (actionData?.ok) {
- if (actionData.intent === "create") {
- toast.success("Sale logged successfully");
- setShowLogSale(false);
- }
- }
- }, [actionData]);
-
- return (
-
-
setShowLogSale(true)} />
-
-
-
- {showLogSale && setShowLogSale(false)} />}
-
- );
+useEffect(() => {
+ if (!actionData?.ok) return;
+
+ if (actionData.intent === "create") {
+ toast.success("Sale logged successfully");
+ setShowLogSale(false);
+ }
+
+ if (actionData.intent === "edit") {
+ toast.success("Sale updated successfully");
+ setShowEdit(false);
+ }
+
+ if (actionData.intent === "delete") {
+ toast.success("Sale deleted successfully");
+ setShowDelete(false);
+ }
+}, [actionData]);
+
+return (
+
+
setShowLogSale(true)} />
+
+
+
+ {
+ setSelectedSale(sale);
+ setShowEdit(true);
+ }}
+ onDelete={(sale) => {
+ setSelectedSale(sale);
+ setShowDelete(true);
+ }}
+ />
+
+
+
+ {showLogSale && (
+ setShowLogSale(false)}
+ />
+ )}
+
+ {showEdit && (
+ setShowEdit(false)}
+ />
+ )}
+
+ {showDelete && selectedSale && (
+ setShowDelete(false)}
+ />
+ )}
+
+);
}