"use client"; import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { apiFetch, ApiError } from "@/lib/api"; import { AuthUser, Branch } from "@/lib/types"; import { formatSize } from "@/lib/units"; import { useToast } from "@/components/ui/Toast"; import { SkeletonList } from "@/components/ui/Skeleton"; import EmptyState from "@/components/ui/EmptyState"; interface PriceRow { productId: string; label: string; unit: string; sizeLiters: number; isActive: boolean; defaultPrice: number; branchPrice: number | null; updatedAt: string | null; } interface PricesResponse { branchId: string; branchName: string; allowed: boolean; prices: PriceRow[]; } /** * Branch prices: what each branch charges for each product. Owners edit; * branch managers see their own branch's prices. */ export default function BranchPricesPage() { const router = useRouter(); const { show } = useToast(); const [me, setMe] = useState(null); const [branches, setBranches] = useState([]); const [branchId, setBranchId] = useState(""); const [data, setData] = useState(null); const [drafts, setDrafts] = useState>({}); const [savingId, setSavingId] = useState(null); const [loading, setLoading] = useState(true); const isOwner = me?.role === "SUPER_ADMIN"; useEffect(() => { (async () => { try { const result = await apiFetch<{ user: AuthUser }>("/api/auth/me"); if (result.user.role === "DELIVERY") { router.replace("/terminal"); return; } setMe(result.user); const br = await apiFetch<{ branches: Branch[] }>("/api/branches"); setBranches(br.branches); const first = result.user.role === "SUPER_ADMIN" ? br.branches[0]?._id : result.user.branchId; if (first) setBranchId(first); } catch { router.replace("/login"); return; } setLoading(false); })(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { if (!branchId) return; loadPrices(branchId); // eslint-disable-next-line react-hooks/exhaustive-deps }, [branchId]); async function loadPrices(id: string) { try { const res = await apiFetch("/api/branch-prices?branchId=" + id); setData(res); setDrafts(Object.fromEntries(res.prices.map((p) => [p.productId, String(p.branchPrice ?? p.defaultPrice)]))); } catch (err) { show(err instanceof ApiError ? err.message : "Could not load prices", "error"); } } async function save(row: PriceRow) { const price = parseFloat(drafts[row.productId]); if (!Number.isFinite(price) || price <= 0) { show("Enter a price above zero", "error"); return; } setSavingId(row.productId); try { await apiFetch("/api/branch-prices", { method: "PUT", body: JSON.stringify({ branchId, productId: row.productId, unitPrice: price }), }); show( price === row.defaultPrice ? `${row.label} back to the default price` : `${row.label} now KES ${price.toLocaleString()} at ${data?.branchName}`, "success" ); await loadPrices(branchId); } catch (err) { show(err instanceof ApiError ? err.message : "Could not save the price", "error"); } setSavingId(null); } async function reset(row: PriceRow) { setSavingId(row.productId); try { await apiFetch(`/api/branch-prices?branchId=${branchId}&productId=${row.productId}`, { method: "DELETE" }); show(`${row.label} back to the default price`, "success"); await loadPrices(branchId); } catch (err) { show(err instanceof ApiError ? err.message : "Could not reset the price", "error"); } setSavingId(null); } if (loading || !me) { return (
); } const canEdit = isOwner && !!data?.allowed; const overrides = data?.prices.filter((p) => p.branchPrice !== null).length ?? 0; return (

{me.businessName ?? "Vessel"}

Branch prices

← Products
{branches.length === 0 ? (
) : (
{isOwner ? (
) : (

{data?.branchName ?? "Your branch"}

)}

Each product sells at its default price unless this branch has its own price. Cashiers at the branch see the new price the next time their till loads products. {!isOwner && " Only the business owner can change prices."}

{isOwner && data && !data.allowed && (

Branch prices are part of the Growth plan. Existing branch prices still apply and can be reset to the default.

)} {data &&

{overrides} product(s) priced differently here

}
{!data ? ( ) : data.prices.length === 0 ? ( ) : (
    {data.prices.map((row) => { const custom = row.branchPrice !== null; const draft = drafts[row.productId] ?? ""; const changed = parseFloat(draft) !== (row.branchPrice ?? row.defaultPrice); return (
  • {row.label}{" "} · {formatSize(row.sizeLiters, row.unit)} {!row.isActive && (deactivated)}

    Default KES {row.defaultPrice.toLocaleString()} {custom && ( This branch: KES {row.branchPrice!.toLocaleString()} )}

    {canEdit ? (
    KES setDrafts((d) => ({ ...d, [row.productId]: e.target.value }))} aria-label={`Price of ${row.label} at this branch`} className="w-24 rounded-lg border border-depth-200 px-2 py-1.5 text-sm" /> {custom && ( )}
    ) : (
    KES {(row.branchPrice ?? row.defaultPrice).toLocaleString()} {isOwner && custom && ( )}
    )}
  • ); })}
)}
)}
); }