import { ColumnConfigDropdown, type ColumnConfigEntry } from "@/components/table"; import { Button } from "@/components/ui/button"; import { Command, CommandItem, CommandList } from "@/components/ui/command"; import { DateTimePickerWithRange } from "@/components/ui/datePickerWithRange"; import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { getErrorMessage, useRecalculateLogCostsMutation } from "@/lib/store"; import type { LogFilters as LogFiltersType } from "@/lib/types/logs"; import { getRangeForPeriod, TIME_PERIODS } from "@/lib/utils/timeRange"; import { Calculator, MoreVertical, Radio, RefreshCw, Search } from "lucide-react"; import { useCallback, useEffect, useRef, useState } from "react"; import { toast } from "sonner"; interface LogsHeaderViewProps { filters: LogFiltersType; onFiltersChange: (filters: LogFiltersType) => void; fetchLogs: () => Promise; fetchStats: () => Promise; fetchHistogram: () => Promise; loading?: boolean; polling: boolean; onPollToggle: (enabled: boolean) => void; period: string; onPeriodChange: (period: string, from: Date, to: Date) => void; /** Column config for the ColumnConfigDropdown */ columnEntries: ColumnConfigEntry[]; columnLabels: Record; onToggleColumnVisibility: (id: string) => void; onResetColumns: () => void; } export function LogsHeaderView({ filters, onFiltersChange, fetchLogs, fetchStats, fetchHistogram, loading = false, polling, onPollToggle, period, onPeriodChange, columnEntries, columnLabels, onToggleColumnVisibility, onResetColumns, }: LogsHeaderViewProps) { const [openMoreActionsPopover, setOpenMoreActionsPopover] = useState(false); const [localSearch, setLocalSearch] = useState(filters.content_search || ""); const searchTimeoutRef = useRef(undefined); const filtersRef = useRef(filters); const [recalculateCosts] = useRecalculateLogCostsMutation(); const [startTime, setStartTime] = useState(filters.start_time ? new Date(filters.start_time) : undefined); const [endTime, setEndTime] = useState(filters.end_time ? new Date(filters.end_time) : undefined); useEffect(() => { setStartTime(filters.start_time ? new Date(filters.start_time) : undefined); setEndTime(filters.end_time ? new Date(filters.end_time) : undefined); }, [filters.start_time, filters.end_time]); useEffect(() => { filtersRef.current = filters; }, [filters]); useEffect(() => { setLocalSearch(filters.content_search || ""); }, [filters.content_search]); useEffect(() => { return () => { if (searchTimeoutRef.current) clearTimeout(searchTimeoutRef.current); }; }, []); const handleRecalculateCosts = useCallback(async () => { try { const response = await recalculateCosts({ filters }).unwrap(); await fetchLogs(); await fetchStats(); setOpenMoreActionsPopover(false); toast.success(`Recalculated costs for ${response.updated} logs`, { description: `${response.updated} logs updated, ${response.skipped} logs skipped, ${response.remaining} logs remaining`, duration: 5000, }); } catch (err) { toast.error(getErrorMessage(err)); } }, [filters, recalculateCosts, fetchLogs, fetchStats]); const handleSearchChange = useCallback( (value: string) => { setLocalSearch(value); if (searchTimeoutRef.current) clearTimeout(searchTimeoutRef.current); searchTimeoutRef.current = setTimeout(() => { onFiltersChange({ ...filtersRef.current, content_search: value }); }, 500); }, [onFiltersChange], ); return (
handleSearchChange(e.target.value)} />
{ setStartTime(p.from); setEndTime(p.to); onFiltersChange({ ...filters, start_time: p.from?.toISOString(), end_time: p.to?.toISOString(), }); }} preDefinedPeriods={TIME_PERIODS} onPredefinedPeriodChange={(periodValue) => { if (!periodValue) return; const { from, to } = getRangeForPeriod(periodValue); setStartTime(from); setEndTime(to); // Relative period: store it in URL and update timestamps via parent onPeriodChange(periodValue, from, to); }} />
Recalculate costs For all logs that don't have a cost
); }