migrate: replace Supabase REST with Drizzle/pg in 28 more files (wave 5 final)
- admin components: TaxDashboard, OrderTableBody, TaxQuarterlySummary, StopProductAssignment, StopTableClient, ProductAssignmentForm, StopMessagingForm - api routes: wholesale/checkout, wholesale/price-sheet, api/supabase (DELETED), api/reports/export, route-trace/* (stubbed - feature retired) - lib: src/lib/supabase.ts (mock-only shim, no @supabase imports), src/lib/supabase/server.ts (DELETED) - actions: wholesale-auth (stubbed - awaiting Auth.js migration), shipping/settings, water-log/*, tax (added getTaxSummaryAction/getTaxableOrdersAction), time-tracking/* (stubbed) - new actions: stops/manage-stop-products (assign/unassign), stops/get-stop-customers (pending pickup list), orders.toggleOrderPickupComplete (legacy column) All Supabase REST calls replaced with Drizzle ORM, raw pg.Pool queries, or existing server actions. Typecheck: clean. Tests: 22/22 pass. Build: OK.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { formatDate } from "@/lib/format-date";
|
||||
import { toggleOrderPickupComplete } from "@/actions/orders";
|
||||
|
||||
type Order = {
|
||||
id: string;
|
||||
@@ -18,21 +19,18 @@ export default function OrderTableBody({ orders }: { orders: Order[] }) {
|
||||
const [pickupToggles, setPickupToggles] = useState<Record<string, boolean>>(
|
||||
() => Object.fromEntries(orders.map((o) => [o.id, o.pickup_complete]))
|
||||
);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
async function togglePickup(orderId: string, current: boolean) {
|
||||
setPickupToggles((prev) => ({ ...prev, [orderId]: !current }));
|
||||
await fetch(
|
||||
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest/v1/orders?id=eq.${orderId}`,
|
||||
{
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
"Content-Type": "application/json",
|
||||
Prefer: "return=representation",
|
||||
},
|
||||
body: JSON.stringify({ pickup_complete: !current }),
|
||||
}
|
||||
);
|
||||
const next = !current;
|
||||
setPickupToggles((prev) => ({ ...prev, [orderId]: next }));
|
||||
setError(null);
|
||||
const result = await toggleOrderPickupComplete({ orderId, pickupComplete: next });
|
||||
if (!result.success) {
|
||||
// Revert optimistic update on failure
|
||||
setPickupToggles((prev) => ({ ...prev, [orderId]: current }));
|
||||
setError(result.error);
|
||||
}
|
||||
}
|
||||
|
||||
const statusColors: Record<string, string> = {
|
||||
@@ -45,6 +43,13 @@ export default function OrderTableBody({ orders }: { orders: Order[] }) {
|
||||
|
||||
return (
|
||||
<tbody className="divide-y divide-slate-200">
|
||||
{error && (
|
||||
<tr>
|
||||
<td colSpan={6} className="px-3 py-2 text-sm text-red-400">
|
||||
{error}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{orders.map((order) => (
|
||||
<tr key={order.id} className="hover:bg-zinc-800">
|
||||
<td className="px-3 py-2">
|
||||
@@ -96,4 +101,4 @@ export default function OrderTableBody({ orders }: { orders: Order[] }) {
|
||||
))}
|
||||
</tbody>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { assignProductToStop, unassignProductFromStop } from "@/actions/stops/manage-stop-products";
|
||||
|
||||
type Product = {
|
||||
id: string;
|
||||
@@ -34,25 +35,10 @@ export default function ProductAssignmentForm({
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const res = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest/v1/rpc/assign_product_to_stop`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
p_stop_id: stopId,
|
||||
p_product_id: selected,
|
||||
}),
|
||||
}
|
||||
);
|
||||
const result = await assignProductToStop({ stopId, productId: selected });
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok || data.success === false) {
|
||||
setError(data.error ?? "Failed to assign product");
|
||||
if (!result.success) {
|
||||
setError(result.error);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
@@ -63,25 +49,10 @@ export default function ProductAssignmentForm({
|
||||
}
|
||||
|
||||
async function handleRemove(productId: string) {
|
||||
const res = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest/v1/rpc/unassign_product_from_stop`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
p_stop_id: stopId,
|
||||
p_product_id: productId,
|
||||
}),
|
||||
}
|
||||
);
|
||||
const result = await unassignProductFromStop({ stopId, productId });
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok || data.success === false) {
|
||||
setError(data.error ?? "Failed to remove");
|
||||
if (!result.success) {
|
||||
setError(result.error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -168,4 +139,4 @@ export default function ProductAssignmentForm({
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
|
||||
type Customer = {
|
||||
id: string;
|
||||
customer_name: string;
|
||||
customer_email: string | null;
|
||||
customer_phone: string | null;
|
||||
pickup_complete: boolean;
|
||||
};
|
||||
|
||||
type StopMessagingFormProps = {
|
||||
stopId: string;
|
||||
};
|
||||
import { getStopPendingCustomers, type StopCustomer } from "@/actions/stops/get-stop-customers";
|
||||
import { sendStopBlast } from "@/actions/communications/stop-blast";
|
||||
|
||||
const quickMessages = [
|
||||
{ label: "Truck running late", value: "Heads up — the truck is running about 15 minutes behind schedule. Thanks for your patience!" },
|
||||
@@ -23,8 +13,14 @@ const quickMessages = [
|
||||
{ label: "Pickup reminder", value: "Reminder — you have an order ready for pickup today. See you soon!" },
|
||||
];
|
||||
|
||||
export default function StopMessagingForm({ stopId }: StopMessagingFormProps) {
|
||||
const [customers, setCustomers] = useState<Customer[]>([]);
|
||||
export default function StopMessagingForm({
|
||||
stopId,
|
||||
brandId,
|
||||
}: {
|
||||
stopId: string;
|
||||
brandId: string;
|
||||
}) {
|
||||
const [customers, setCustomers] = useState<StopCustomer[]>([]);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [channel, setChannel] = useState<"sms" | "email" | "both">("sms");
|
||||
@@ -38,22 +34,16 @@ export default function StopMessagingForm({ stopId }: StopMessagingFormProps) {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const res = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest/v1/orders?stop_id=eq.${stopId}&pickup_complete=eq.false&pickup_complete=eq.false&select=customer_name,customer_email,customer_phone,pickup_complete`,
|
||||
{
|
||||
headers: {
|
||||
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
},
|
||||
}
|
||||
);
|
||||
const result = await getStopPendingCustomers(stopId);
|
||||
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
setError(data.message);
|
||||
} else {
|
||||
setCustomers(data);
|
||||
setLoaded(true);
|
||||
if (!result.success) {
|
||||
setError(result.error);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setCustomers(result.customers);
|
||||
setLoaded(true);
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -67,41 +57,21 @@ export default function StopMessagingForm({ stopId }: StopMessagingFormProps) {
|
||||
setSending(true);
|
||||
setError(null);
|
||||
|
||||
const recipients = customers.filter((c) => {
|
||||
if (channel === "sms") return c.customer_phone;
|
||||
if (channel === "email") return c.customer_email;
|
||||
return c.customer_phone || c.customer_email;
|
||||
const blast = await sendStopBlast({
|
||||
stopId,
|
||||
brandId,
|
||||
channel,
|
||||
body: message,
|
||||
audience: "pending",
|
||||
});
|
||||
|
||||
// Call Supabase Edge Function to send messages
|
||||
const res = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/functions/v1/send-messages`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
channel,
|
||||
message,
|
||||
recipients: recipients.map((r) => ({
|
||||
phone: r.customer_phone,
|
||||
email: r.customer_email,
|
||||
name: r.customer_name,
|
||||
})),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
const err = await res.json();
|
||||
setError(err.message ?? "Failed to send messages");
|
||||
if (!blast.success) {
|
||||
setError(blast.error);
|
||||
setSending(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setSent(recipients.length);
|
||||
setSent(blast.messages_logged);
|
||||
setSending(false);
|
||||
}
|
||||
|
||||
@@ -253,4 +223,4 @@ export default function StopMessagingForm({ stopId }: StopMessagingFormProps) {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useState, useMemo, useEffect } from "react";
|
||||
import Image from "next/image";
|
||||
import { logAuditEvent } from "@/actions/audit";
|
||||
import { assignProductToStop, unassignProductFromStop } from "@/actions/stops/manage-stop-products";
|
||||
|
||||
type Product = {
|
||||
id: string;
|
||||
@@ -89,65 +90,43 @@ export default function StopProductAssignment({
|
||||
setPendingId(productId);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest/v1/rpc/assign_product_to_stop`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
p_stop_id: stopId,
|
||||
p_product_id: productId,
|
||||
p_caller_uid: callerUid,
|
||||
}),
|
||||
}
|
||||
);
|
||||
const data = await res.json();
|
||||
const result = await assignProductToStop({ stopId, productId });
|
||||
|
||||
if (!res.ok || data.success === false) {
|
||||
const errMsg = data?.error ?? data?.message ?? `HTTP ${res.status}`;
|
||||
const errDetail = data?.details ?? data?.hint ?? data?.code ?? "";
|
||||
setError(`Failed to assign: ${errMsg}${errDetail ? " — " + errDetail : ""}`);
|
||||
setPendingId(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimistic insert: build the entry from the product we already have
|
||||
// locally, keyed by the row id returned from the RPC. Use functional
|
||||
// setState so concurrent calls compose correctly.
|
||||
setProducts((prev) => {
|
||||
if (prev.some((p) => p.product_id === productId)) return prev;
|
||||
return [
|
||||
...prev,
|
||||
{
|
||||
id: data.id,
|
||||
product_id: productId,
|
||||
products: {
|
||||
name: product.name,
|
||||
type: product.type,
|
||||
price: product.price,
|
||||
image_url: product.image_url ?? null,
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
setPendingId(null);
|
||||
|
||||
logAuditEvent({
|
||||
table_name: "product_stops",
|
||||
record_id: data.id,
|
||||
action: "INSERT",
|
||||
old_data: null,
|
||||
new_data: { stop_id: stopId, product_id: productId },
|
||||
brand_id: null,
|
||||
});
|
||||
} catch {
|
||||
setError("Network error while assigning product.");
|
||||
if (!result.success) {
|
||||
setError(`Failed to assign: ${result.error}`);
|
||||
setPendingId(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimistic insert: build the entry from the product we already have
|
||||
// locally, keyed by the row id returned from the RPC. Use functional
|
||||
// setState so concurrent calls compose correctly.
|
||||
setProducts((prev) => {
|
||||
if (prev.some((p) => p.product_id === productId)) return prev;
|
||||
return [
|
||||
...prev,
|
||||
{
|
||||
id: result.id,
|
||||
product_id: productId,
|
||||
products: {
|
||||
name: product.name,
|
||||
type: product.type,
|
||||
price: product.price,
|
||||
image_url: product.image_url ?? null,
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
setPendingId(null);
|
||||
|
||||
logAuditEvent({
|
||||
table_name: "product_stops",
|
||||
record_id: result.id,
|
||||
action: "INSERT",
|
||||
old_data: null,
|
||||
new_data: { stop_id: stopId, product_id: productId },
|
||||
brand_id: null,
|
||||
});
|
||||
}
|
||||
|
||||
async function remove(productId: string) {
|
||||
@@ -158,47 +137,25 @@ export default function StopProductAssignment({
|
||||
setRemovingId(productId);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest/v1/rpc/unassign_product_from_stop`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
p_stop_id: stopId,
|
||||
p_product_id: productId,
|
||||
p_caller_uid: callerUid,
|
||||
}),
|
||||
}
|
||||
);
|
||||
const data = await res.json();
|
||||
const result = await unassignProductFromStop({ stopId, productId });
|
||||
|
||||
if (!res.ok || data.success === false) {
|
||||
const errMsg = data?.error ?? data?.message ?? `HTTP ${res.status}`;
|
||||
const errDetail = data?.details ?? data?.hint ?? data?.code ?? "";
|
||||
setError(`Failed to remove: ${errMsg}${errDetail ? " — " + errDetail : ""}`);
|
||||
setRemovingId(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setProducts((prev) => prev.filter((p) => p.product_id !== productId));
|
||||
setRemovingId(null);
|
||||
|
||||
logAuditEvent({
|
||||
table_name: "product_stops",
|
||||
record_id: entry.id,
|
||||
action: "DELETE",
|
||||
old_data: { stop_id: stopId, product_id: productId },
|
||||
new_data: null,
|
||||
brand_id: null,
|
||||
});
|
||||
} catch {
|
||||
setError("Network error while removing product.");
|
||||
if (!result.success) {
|
||||
setError(`Failed to remove: ${result.error}`);
|
||||
setRemovingId(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setProducts((prev) => prev.filter((p) => p.product_id !== productId));
|
||||
setRemovingId(null);
|
||||
|
||||
logAuditEvent({
|
||||
table_name: "product_stops",
|
||||
record_id: entry.id,
|
||||
action: "DELETE",
|
||||
old_data: { stop_id: stopId, product_id: productId },
|
||||
new_data: null,
|
||||
brand_id: null,
|
||||
});
|
||||
}
|
||||
|
||||
function toggle(productId: string) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import React, { useState, useTransition, useEffect, useMemo } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { publishStop } from "@/actions/stops";
|
||||
import { publishStop, deleteStop } from "@/actions/stops";
|
||||
import {
|
||||
AdminSearchInput,
|
||||
AdminFilterTabs,
|
||||
@@ -449,26 +449,15 @@ function StopRowBase({
|
||||
|
||||
async function handleDelete() {
|
||||
setDeletingId(stop.id);
|
||||
const res = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest/v1/rpc/delete_stop`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ p_stop_id: stop.id, p_brand_id: stop.brand_id }),
|
||||
}
|
||||
);
|
||||
const data = await res.json();
|
||||
const result = await deleteStop(stop.id, stop.brand_id);
|
||||
setDeletingId(null);
|
||||
setConfirmDelete(null);
|
||||
setOpenMenu(null);
|
||||
if (data.success) {
|
||||
if (result.success) {
|
||||
showSuccess("Stop deleted", "The stop has been removed");
|
||||
onDeleted();
|
||||
} else {
|
||||
onDeleteError(data.error ?? "Delete failed");
|
||||
onDeleteError(result.error ?? "Delete failed");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,11 @@ import {
|
||||
type TaxOrderRow,
|
||||
type TaxByStateRow,
|
||||
} from "@/lib/reports-export";
|
||||
import {
|
||||
getTaxSummaryAction,
|
||||
getTaxableOrdersAction,
|
||||
type TaxSummaryData,
|
||||
} from "@/actions/tax";
|
||||
import { AdminButton, AdminFilterTabs } from "@/components/admin/design-system";
|
||||
|
||||
type DatePreset = "month" | "quarter" | "this_year" | "custom";
|
||||
@@ -44,17 +49,7 @@ function quarterLabel(start: string, end: string): string {
|
||||
return `Q${q} ${s.getFullYear()}`;
|
||||
}
|
||||
|
||||
type TaxSummaryData = {
|
||||
total_tax_collected: number;
|
||||
total_gross_sales: number;
|
||||
order_count: number;
|
||||
tax_by_state: Array<{
|
||||
state: string;
|
||||
total_tax: number;
|
||||
gross_sales: number;
|
||||
order_count: number;
|
||||
}>;
|
||||
};
|
||||
type TaxSummaryDataLocal = TaxSummaryData;
|
||||
|
||||
function SummaryCard({
|
||||
label,
|
||||
@@ -143,7 +138,7 @@ export default function TaxDashboard({
|
||||
const [customEnd, setCustomEnd] = useState("");
|
||||
const [selectedBrandId, setSelectedBrandId] = useState<string>(initialBrandId ?? "");
|
||||
const [activeTab, setActiveTab] = useState("summary");
|
||||
const [summary, setSummary] = useState<TaxSummaryData | null>(null);
|
||||
const [summary, setSummary] = useState<TaxSummaryDataLocal | null>(null);
|
||||
const [orders, setOrders] = useState<TaxOrderRow[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -156,29 +151,13 @@ export default function TaxDashboard({
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest/v1/rpc/get_tax_summary`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
p_brand_id: selectedBrandId,
|
||||
p_start_date: range.start,
|
||||
p_end_date: range.end,
|
||||
}),
|
||||
}
|
||||
);
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
const data = await res.json();
|
||||
setSummary({
|
||||
total_tax_collected: Number(data?.total_tax_collected ?? 0),
|
||||
total_gross_sales: Number(data?.total_gross_sales ?? 0),
|
||||
order_count: Number(data?.order_count ?? 0),
|
||||
tax_by_state: data?.tax_by_state ?? [],
|
||||
const result = await getTaxSummaryAction({
|
||||
brandId: selectedBrandId,
|
||||
startDate: range.start,
|
||||
endDate: range.end,
|
||||
});
|
||||
if (!result.success) throw new Error(result.error);
|
||||
setSummary(result.data);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to load tax summary");
|
||||
} finally {
|
||||
@@ -192,36 +171,13 @@ export default function TaxDashboard({
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest/v1/rpc/get_taxable_orders`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
p_brand_id: selectedBrandId,
|
||||
p_start_date: range.start,
|
||||
p_end_date: range.end,
|
||||
}),
|
||||
}
|
||||
);
|
||||
if (!res.ok) throw new Error(await res.text());
|
||||
const data = await res.json();
|
||||
setOrders(
|
||||
(data ?? []).map((row: Record<string, unknown>) => ({
|
||||
order_id: String(row.order_id ?? ""),
|
||||
date: String(row.date ?? ""),
|
||||
customer_name: String(row.customer_name ?? ""),
|
||||
city: String(row.city ?? ""),
|
||||
state: String(row.state ?? ""),
|
||||
taxable_amount: Number(row.taxable_amount ?? 0),
|
||||
tax_amount: Number(row.tax_amount ?? 0),
|
||||
tax_rate: Number(row.tax_rate ?? 0),
|
||||
tax_location: String(row.tax_location ?? ""),
|
||||
}))
|
||||
);
|
||||
const result = await getTaxableOrdersAction({
|
||||
brandId: selectedBrandId,
|
||||
startDate: range.start,
|
||||
endDate: range.end,
|
||||
});
|
||||
if (!result.success) throw new Error(result.error);
|
||||
setOrders(result.data);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to load orders");
|
||||
} finally {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import Link from "next/link";
|
||||
import { getTaxSummaryAction } from "@/actions/tax";
|
||||
|
||||
type TaxSummaryData = {
|
||||
total_tax_collected: number;
|
||||
@@ -29,31 +30,26 @@ export default function TaxQuarterlySummary({ brandId }: { brandId: string }) {
|
||||
|
||||
useEffect(() => {
|
||||
const range = quarterDateRange();
|
||||
fetch(
|
||||
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest/v1/rpc/get_tax_summary`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
p_brand_id: brandId,
|
||||
p_start_date: range.start,
|
||||
p_end_date: range.end,
|
||||
}),
|
||||
}
|
||||
)
|
||||
.then((r) => r.json())
|
||||
.then((d) => {
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
const result = await getTaxSummaryAction({
|
||||
brandId,
|
||||
startDate: range.start,
|
||||
endDate: range.end,
|
||||
});
|
||||
if (cancelled) return;
|
||||
if (result.success) {
|
||||
setData({
|
||||
total_tax_collected: Number(d?.total_tax_collected ?? 0),
|
||||
total_gross_sales: Number(d?.total_gross_sales ?? 0),
|
||||
order_count: Number(d?.order_count ?? 0),
|
||||
total_tax_collected: result.data.total_tax_collected,
|
||||
total_gross_sales: result.data.total_gross_sales,
|
||||
order_count: result.data.order_count,
|
||||
});
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => setLoading(false));
|
||||
}
|
||||
setLoading(false);
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [brandId]);
|
||||
|
||||
if (loading) {
|
||||
|
||||
Reference in New Issue
Block a user