feat(quotes): filter list to the logged-in rep's own quotes

This commit is contained in:
Dave 2026-08-31 23:26:42 +02:00
parent defa481661
commit 8faa4437b6

View file

@ -32,6 +32,8 @@ import {
import { toast } from "sonner";
import { cn } from "@/lib/utils";
import getPocketBase from "@/lib/pocketbase";
import { useAuth } from "@/components/auth-provider";
import { isRepUser } from "@/lib/rep-visibility";
import {
getQuoteItems,
fetchQuoteItemCounts,
@ -140,6 +142,7 @@ const AGED_STATUSES: ReadonlySet<string> = new Set(["new", "claimed"]);
export default function QuotesPage() {
const router = useRouter();
const { user } = useAuth();
const [quotes, setQuotes] = useState<Quote[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
@ -439,6 +442,15 @@ export default function QuotesPage() {
return map;
}, [users]);
// Rep-scoped UI filter — same rationale as the Orders page (see
// src/lib/rep-visibility.ts). Quotes only carry rep via the
// customer relation (no denormalized text field like orders.rep).
const visibleQuotes = useMemo(() => {
const userType = (user as { user_type?: string[] } | null)?.user_type;
if (!user || !isRepUser(userType)) return quotes;
return quotes.filter((quote) => quote.expand?.customer?.rep === user.id);
}, [quotes, user]);
/**
* Toggle a row's expanded state. The first time a row opens we kick
* off a `getQuoteItems(id)` fetch; subsequent toggles use the cache.
@ -867,7 +879,7 @@ export default function QuotesPage() {
},
},
]}
data={quotes}
data={visibleQuotes}
loading={loading}
error={error}
// Placeholder lists every surface the pushdown actually hits
@ -898,7 +910,7 @@ export default function QuotesPage() {
"text-xs whitespace-nowrap",
loading
? "text-muted-foreground/60"
: quotes.length === 0
: visibleQuotes.length === 0
? "text-warn font-medium"
: "text-muted-foreground",
)}
@ -906,11 +918,11 @@ export default function QuotesPage() {
>
{loading ? (
<>Searching…</>
) : quotes.length === 0 ? (
) : visibleQuotes.length === 0 ? (
<>0 matches for &ldquo;{searchInput.trim()}&rdquo;</>
) : (
<>
{quotes.length} match{quotes.length === 1 ? "" : "es"} for{" "}
{visibleQuotes.length} match{visibleQuotes.length === 1 ? "" : "es"} for{" "}
<span className="font-medium text-foreground">
&ldquo;{searchInput.trim()}&rdquo;
</span>