frontend: date range picker on Incidents and Archive; fix Archive paging
Incidents and Archive get a from/to date range (native date inputs, local-day bounds). Incidents filters in the Firestore query; Archive passes date_from/date_to to GET /calls/search, which applies them as a started_at range — both ride the existing org_id/started_at index. Also fixes /calls/search and /calls/eval-queue paging: the cursor went to Firestore as a raw ISO string against a timestamp field, which compares by type rather than time, so "Load more" re-read the first page. Cursor and range bounds are now parsed to datetimes (400 on garbage). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c043298902
commit
6479174022
@@ -24,6 +24,7 @@ import { Button } from "@/components/ui/Button";
|
||||
import { EmptyState, ErrorBanner } from "@/components/ui/EmptyState";
|
||||
import { SkeletonCard } from "@/components/ui/Skeleton";
|
||||
import { MachineOutputNotice } from "@/components/ui/MachineOutputNotice";
|
||||
import { DateRange, dayStart, dayEnd } from "@/components/ui/DateRange";
|
||||
|
||||
type LinkFilter = "any" | "orphan" | "linked";
|
||||
type TranscriptFilter = "any" | "yes" | "no";
|
||||
@@ -239,6 +240,8 @@ export default function ArchivePage() {
|
||||
const [systemId, setSystemId] = useState("");
|
||||
const [q, setQ] = useState("");
|
||||
const [submittedQ, setSubmittedQ] = useState("");
|
||||
const [dateFrom, setDateFrom] = useState("");
|
||||
const [dateTo, setDateTo] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (!authLoading && !canView) router.replace("/");
|
||||
@@ -256,6 +259,8 @@ export default function ArchivePage() {
|
||||
transcript,
|
||||
system_id: systemId || undefined,
|
||||
q: submittedQ || undefined,
|
||||
date_from: dayStart(dateFrom)?.toISOString(),
|
||||
date_to: dayEnd(dateTo)?.toISOString(),
|
||||
});
|
||||
setCalls((prev) => (append ? [...prev, ...res.calls] : res.calls));
|
||||
setCursor(res.next_cursor);
|
||||
@@ -266,7 +271,7 @@ export default function ArchivePage() {
|
||||
setLoading(false);
|
||||
}
|
||||
},
|
||||
[link, transcript, systemId, submittedQ],
|
||||
[link, transcript, systemId, submittedQ, dateFrom, dateTo],
|
||||
);
|
||||
|
||||
// Reload from the top whenever a filter changes.
|
||||
@@ -335,6 +340,8 @@ export default function ArchivePage() {
|
||||
))}
|
||||
</select>
|
||||
|
||||
<DateRange from={dateFrom} to={dateTo} onChange={(f, t) => { setDateFrom(f); setDateTo(t); }} />
|
||||
|
||||
<form
|
||||
onSubmit={(e) => { e.preventDefault(); setSubmittedQ(q.trim()); }}
|
||||
className="flex items-center gap-2 ml-auto"
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Badge } from "@/components/ui/Badge";
|
||||
import { EmptyState, ErrorBanner } from "@/components/ui/EmptyState";
|
||||
import { SkeletonCard } from "@/components/ui/Skeleton";
|
||||
import { MachineOutputNotice } from "@/components/ui/MachineOutputNotice";
|
||||
import { DateRange, dayStart, dayEnd } from "@/components/ui/DateRange";
|
||||
import { isKnownSeverity, severityRank } from "@/lib/severity";
|
||||
import { SeverityMark, SeveritySpine } from "@/components/marks/SeverityMark";
|
||||
import { TypeGlyph } from "@/components/marks/TypeGlyph";
|
||||
@@ -196,7 +197,11 @@ function CreateModal({ onClose, onCreate }: { onClose: () => void; onCreate: (bo
|
||||
export default function IncidentsPage() {
|
||||
const { isAdmin } = useAuth();
|
||||
const [pageLimit, setPageLimit] = useState(PAGE_SIZE);
|
||||
const { incidents, loading, error, hasMore } = useIncidents(pageLimit);
|
||||
const [dateFrom, setDateFrom] = useState("");
|
||||
const [dateTo, setDateTo] = useState("");
|
||||
const rangeFrom = useMemo(() => dayStart(dateFrom), [dateFrom]);
|
||||
const rangeTo = useMemo(() => dayEnd(dateTo), [dateTo]);
|
||||
const { incidents, loading, error, hasMore } = useIncidents(pageLimit, rangeFrom, rangeTo);
|
||||
const activeCalls = useActiveCalls();
|
||||
const [showCreate, setShowCreate] = useState(false);
|
||||
const [severityFilter, setSeverityFilter] = useState<SeverityFilter>("all");
|
||||
@@ -228,9 +233,10 @@ export default function IncidentsPage() {
|
||||
return list; // useIncidents() already orders by started_at desc
|
||||
}, [incidents, severityFilter, sortMode, statusFilter, typeFilter, search]);
|
||||
|
||||
const filtersActive = severityFilter !== "all" || statusFilter !== "any" || typeFilter !== "" || search.trim() !== "";
|
||||
const filtersActive = severityFilter !== "all" || statusFilter !== "any" || typeFilter !== "" || search.trim() !== "" || dateFrom !== "" || dateTo !== "";
|
||||
function clearFilters() {
|
||||
setSeverityFilter("all"); setStatusFilter("any"); setTypeFilter(""); setSearch("");
|
||||
setDateFrom(""); setDateTo(""); setPageLimit(PAGE_SIZE);
|
||||
}
|
||||
|
||||
const hiddenCount = incidents.length - filtered.length;
|
||||
@@ -308,6 +314,11 @@ export default function IncidentsPage() {
|
||||
<option value="">All types</option>
|
||||
{INCIDENT_TYPES.map((t) => <option key={t} value={t}>{t}</option>)}
|
||||
</select>
|
||||
<DateRange
|
||||
from={dateFrom}
|
||||
to={dateTo}
|
||||
onChange={(f, t) => { setDateFrom(f); setDateTo(t); setPageLimit(PAGE_SIZE); }}
|
||||
/>
|
||||
<label className="flex items-center gap-2 text-xs text-ink-muted ml-auto">
|
||||
Sort
|
||||
<select
|
||||
@@ -362,14 +373,14 @@ export default function IncidentsPage() {
|
||||
|
||||
{filtered.length === 0 && !error && (
|
||||
<EmptyState
|
||||
title={incidents.length === 0 ? "No incidents recorded yet" : "No incidents match this filter"}
|
||||
title={incidents.length === 0 && !filtersActive ? "No incidents recorded yet" : "No incidents match these filters"}
|
||||
description={
|
||||
incidents.length === 0
|
||||
incidents.length === 0 && !filtersActive
|
||||
? "Incidents appear automatically once calls start correlating."
|
||||
: "Try clearing a filter, or load older incidents."
|
||||
}
|
||||
action={
|
||||
incidents.length > 0 && filtersActive ? (
|
||||
filtersActive ? (
|
||||
<Button variant="secondary" size="sm" onClick={clearFilters}>Clear filters</Button>
|
||||
) : undefined
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user