fix(halachot): ספירת-תור אמיתית + עדכון-חי בתגי-הכרעה (#6/#7/#8) #273

Merged
chaim merged 1 commits from worktree-halacha-real-counts into main 2026-06-16 08:48:49 +00:00
3 changed files with 23 additions and 16 deletions

View File

@@ -6719,6 +6719,12 @@ async def precedent_library_stats() -> dict:
halachot_approved = await conn.fetchval( halachot_approved = await conn.fetchval(
"SELECT COUNT(*) FROM halachot WHERE review_status IN ('approved', 'published')" "SELECT COUNT(*) FROM halachot WHERE review_status IN ('approved', 'published')"
) )
halachot_rejected = await conn.fetchval(
"SELECT COUNT(*) FROM halachot WHERE review_status = 'rejected'"
)
halachot_deferred = await conn.fetchval(
"SELECT COUNT(*) FROM halachot WHERE review_status = 'deferred'"
)
return { return {
"precedents_total": int(total or 0), "precedents_total": int(total or 0),
"by_practice_area": [ "by_practice_area": [
@@ -6732,6 +6738,8 @@ async def precedent_library_stats() -> dict:
"halachot_total": int(halachot_total or 0), "halachot_total": int(halachot_total or 0),
"halachot_pending": int(halachot_pending or 0), "halachot_pending": int(halachot_pending or 0),
"halachot_approved": int(halachot_approved or 0), "halachot_approved": int(halachot_approved or 0),
"halachot_rejected": int(halachot_rejected or 0),
"halachot_deferred": int(halachot_deferred or 0),
} }

View File

@@ -11,7 +11,7 @@ import { CorroborationBadge } from "./corroboration-badge";
import { practiceAreaLabel } from "./practice-area"; import { practiceAreaLabel } from "./practice-area";
import { import {
useHalachotPending, useHalachotByStatus, useUpdateHalacha, useBatchReviewHalachot, useHalachotPending, useHalachotByStatus, useUpdateHalacha, useBatchReviewHalachot,
isExtractionFixItem, type Halacha, useLibraryStats, isExtractionFixItem, type Halacha,
} from "@/lib/api/precedent-library"; } from "@/lib/api/precedent-library";
import { AuthorityBadge, ruleTypeLabel } from "./halacha-meta"; import { AuthorityBadge, ruleTypeLabel } from "./halacha-meta";
@@ -979,13 +979,6 @@ function PendingPanel() {
); );
} }
// ─── Count badge for tabs ─────────────────────────────────────────────────────
function useHalachaCount(status: string) {
const { data } = useHalachotByStatus(status, 1000);
return data?.count ?? data?.items.length ?? null;
}
// ─── Main export ────────────────────────────────────────────────────────────── // ─── Main export ──────────────────────────────────────────────────────────────
type Tab = "pending" | "rejected" | "approved"; type Tab = "pending" | "rejected" | "approved";
@@ -998,16 +991,16 @@ const TAB_LABELS: Record<Tab, string> = {
export function HalachaReviewPanel() { export function HalachaReviewPanel() {
const [tab, setTab] = useState<Tab>("pending"); const [tab, setTab] = useState<Tab>("pending");
const { data: pendingData } = useHalachotPending({ limit: 500 });
const rejectedCount = useHalachaCount("rejected");
const approvedCount = useHalachaCount("approved");
const pendingCount = pendingData?.items.length ?? null; // Real COUNT(*) totals from the stats endpoint — not the limit-capped
// /api/halachot len(rows), which pinned these chips at 500/1000/1000 and only
// refreshed on navigation. The stats query polls, so the chips track live (#6/#7/#8).
const { data: stats } = useLibraryStats();
const counts: Record<Tab, number | null> = { const counts: Record<Tab, number | null> = {
pending: pendingCount, pending: stats?.halachot_pending ?? null,
rejected: rejectedCount, rejected: stats?.halachot_rejected ?? null,
approved: approvedCount, approved: stats?.halachot_approved ?? null,
}; };
return ( return (

View File

@@ -188,6 +188,8 @@ export type LibraryStats = {
halachot_total: number; halachot_total: number;
halachot_pending: number; halachot_pending: number;
halachot_approved: number; halachot_approved: number;
halachot_rejected: number;
halachot_deferred: number;
}; };
export type ListFilters = { export type ListFilters = {
@@ -301,7 +303,11 @@ export function useLibraryStats() {
queryKey: libraryKeys.stats(), queryKey: libraryKeys.stats(),
queryFn: ({ signal }) => queryFn: ({ signal }) =>
apiRequest<LibraryStats>("/api/precedent-library/stats", { signal }), apiRequest<LibraryStats>("/api/precedent-library/stats", { signal }),
staleTime: 60_000, staleTime: 30_000,
// The halacha-review tab counts ride on this (real COUNT(*) — not the
// limit-capped /api/halachot len), so poll so the chips track approvals
// live instead of only refreshing on navigation (#6/#7/#8).
refetchInterval: 30_000,
}); });
} }