feat(quotes): filter list to the logged-in rep's own quotes
This commit is contained in:
parent
defa481661
commit
8faa4437b6
1 changed files with 16 additions and 4 deletions
|
|
@ -32,6 +32,8 @@ import {
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import getPocketBase from "@/lib/pocketbase";
|
import getPocketBase from "@/lib/pocketbase";
|
||||||
|
import { useAuth } from "@/components/auth-provider";
|
||||||
|
import { isRepUser } from "@/lib/rep-visibility";
|
||||||
import {
|
import {
|
||||||
getQuoteItems,
|
getQuoteItems,
|
||||||
fetchQuoteItemCounts,
|
fetchQuoteItemCounts,
|
||||||
|
|
@ -140,6 +142,7 @@ const AGED_STATUSES: ReadonlySet<string> = new Set(["new", "claimed"]);
|
||||||
|
|
||||||
export default function QuotesPage() {
|
export default function QuotesPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { user } = useAuth();
|
||||||
const [quotes, setQuotes] = useState<Quote[]>([]);
|
const [quotes, setQuotes] = useState<Quote[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
|
@ -439,6 +442,15 @@ export default function QuotesPage() {
|
||||||
return map;
|
return map;
|
||||||
}, [users]);
|
}, [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
|
* Toggle a row's expanded state. The first time a row opens we kick
|
||||||
* off a `getQuoteItems(id)` fetch; subsequent toggles use the cache.
|
* off a `getQuoteItems(id)` fetch; subsequent toggles use the cache.
|
||||||
|
|
@ -867,7 +879,7 @@ export default function QuotesPage() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
data={quotes}
|
data={visibleQuotes}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
error={error}
|
error={error}
|
||||||
// Placeholder lists every surface the pushdown actually hits
|
// Placeholder lists every surface the pushdown actually hits
|
||||||
|
|
@ -898,7 +910,7 @@ export default function QuotesPage() {
|
||||||
"text-xs whitespace-nowrap",
|
"text-xs whitespace-nowrap",
|
||||||
loading
|
loading
|
||||||
? "text-muted-foreground/60"
|
? "text-muted-foreground/60"
|
||||||
: quotes.length === 0
|
: visibleQuotes.length === 0
|
||||||
? "text-warn font-medium"
|
? "text-warn font-medium"
|
||||||
: "text-muted-foreground",
|
: "text-muted-foreground",
|
||||||
)}
|
)}
|
||||||
|
|
@ -906,11 +918,11 @@ export default function QuotesPage() {
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<>Searching…</>
|
<>Searching…</>
|
||||||
) : quotes.length === 0 ? (
|
) : visibleQuotes.length === 0 ? (
|
||||||
<>0 matches for “{searchInput.trim()}”</>
|
<>0 matches for “{searchInput.trim()}”</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{quotes.length} match{quotes.length === 1 ? "" : "es"} for{" "}
|
{visibleQuotes.length} match{visibleQuotes.length === 1 ? "" : "es"} for{" "}
|
||||||
<span className="font-medium text-foreground">
|
<span className="font-medium text-foreground">
|
||||||
“{searchInput.trim()}”
|
“{searchInput.trim()}”
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue