• Statistics filter panel component with collapsible sections for filtering data.

    Parameters

    Returns ReactElement

    export function StatisticsFilterPanel({
    filters,
    onFiltersChange,
    availableGenres,
    availableFormats,
    availableStatuses,
    availableTags,
    matchCount,
    }: Readonly<StatisticsFilterPanelProps>): React.ReactElement {
    const [isOpen, setIsOpen] = useState(false);

    // Calculate active filter count
    const activeFilterCount = useMemo(() => {
    let count = 0;
    if (filters.genres.length > 0) count++;
    if (filters.formats.length > 0) count++;
    if (filters.tags.length > 0) count++;
    if (filters.statuses.length > 0) count++;
    if (filters.dateRange.start || filters.dateRange.end) count++;
    if (filters.confidenceRange.min > 0 || filters.confidenceRange.max < 100)
    count++;
    return count;
    }, [filters]);

    const handleConfidenceChange = (value: { min: number; max: number }) => {
    onFiltersChange({ ...filters, confidenceRange: value });
    };

    const handleFormatToggle = (format: string) => {
    const newFormats = filters.formats.includes(format)
    ? filters.formats.filter((f) => f !== format)
    : [...filters.formats, format];
    onFiltersChange({ ...filters, formats: newFormats });
    };

    const handleStatusToggle = (status: MatchStatus) => {
    const newStatuses = filters.statuses.includes(status)
    ? filters.statuses.filter((s) => s !== status)
    : [...filters.statuses, status];
    onFiltersChange({ ...filters, statuses: newStatuses });
    };

    const handleDateRangeChange = (
    type: "start" | "end",
    value: string | null,
    ) => {
    const newDateRange = { ...filters.dateRange };
    if (value) {
    newDateRange[type] = parseDateInputValue(value);
    } else {
    newDateRange[type] = null;
    }
    onFiltersChange({ ...filters, dateRange: newDateRange });
    };

    const handleClearAllFilters = () => {
    onFiltersChange(defaultStatisticsFilters);
    };

    const handlePresetClick = (preset: FilterPreset) => {
    onFiltersChange({ ...filters, ...preset.filters });
    };

    return (
    <Card className="bg-linear-to-br from-slate-50 to-slate-100/50 backdrop-blur-sm dark:from-slate-800/50 dark:to-slate-900/30">
    <Collapsible open={isOpen} onOpenChange={setIsOpen}>
    <CardHeader className="pb-4">
    <div className="flex items-start justify-between gap-4">
    <div className="flex items-center gap-3">
    <SlidersHorizontal className="h-5 w-5 text-slate-600 dark:text-slate-400" />
    <div>
    <CardTitle className="text-lg">Advanced Filters</CardTitle>
    <CardDescription>Fine-tune your statistics</CardDescription>
    </div>
    </div>

    <div className="flex items-center gap-2">
    {/* Active filter count badge */}
    {activeFilterCount > 0 && (
    <Badge
    variant="secondary"
    className="rounded-full bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300"
    >
    {activeFilterCount} active
    </Badge>
    )}

    {/* Collapse toggle */}
    <CollapsibleTrigger asChild>
    <Button variant="ghost" size="sm" className="h-8 w-8 p-0">
    <CollapsibleChevron isExpanded={isOpen} />
    <span className="sr-only">Toggle advanced filters</span>
    </Button>
    </CollapsibleTrigger>
    </div>
    </div>

    {/* Filter presets */}
    <div className="space-y-3 pt-3">
    <div className="flex flex-wrap gap-2">
    {FILTER_PRESETS.map((preset) => {
    const Icon = preset.icon;
    return (
    <Button
    key={preset.id}
    variant="outline"
    size="sm"
    onClick={() => handlePresetClick(preset)}
    className="h-7 gap-1.5 text-xs"
    title={preset.description}
    >
    <Icon className="h-3 w-3" />
    {preset.name}
    </Button>
    );
    })}
    </div>
    </div>
    </CardHeader>

    <CollapsibleContent>
    <CardContent className="space-y-6 pt-0">
    {/* Confidence Range */}
    <div className="space-y-3">
    <div>
    <div className="text-sm font-medium text-slate-700 dark:text-slate-300">
    Confidence Score
    </div>
    <p className="text-xs text-slate-500 dark:text-slate-400">
    Filter by match confidence percentage
    </p>
    </div>
    <RangeSlider
    min={0}
    max={100}
    step={5}
    value={filters.confidenceRange}
    onChange={handleConfidenceChange}
    />
    </div>

    {/* Date Range */}
    <div className="space-y-3">
    <div>
    <div className="text-sm font-medium text-slate-700 dark:text-slate-300">
    Date Range
    </div>
    <p className="text-xs text-slate-500 dark:text-slate-400">
    Filter by match date
    </p>
    </div>
    <div className="grid grid-cols-2 gap-3">
    <div>
    <label
    htmlFor="start-date"
    className="mb-1 block text-xs text-slate-600 dark:text-slate-400"
    >
    Start Date
    </label>
    <input
    id="start-date"
    type="date"
    value={
    filters.dateRange.start
    ? toDateInputValue(filters.dateRange.start)
    : ""
    }
    onChange={(event) =>
    handleDateRangeChange("start", event.target.value || null)
    }
    className="w-full rounded-md border border-slate-200 bg-white px-3 py-1.5 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-slate-700 dark:bg-slate-800"
    />
    </div>
    <div>
    <label
    htmlFor="end-date"
    className="mb-1 block text-xs text-slate-600 dark:text-slate-400"
    >
    End Date
    </label>
    <input
    id="end-date"
    type="date"
    value={
    filters.dateRange.end
    ? toDateInputValue(filters.dateRange.end)
    : ""
    }
    onChange={(event) =>
    handleDateRangeChange("end", event.target.value || null)
    }
    className="w-full rounded-md border border-slate-200 bg-white px-3 py-1.5 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:border-slate-700 dark:bg-slate-800"
    />
    </div>
    </div>
    </div>

    {/* Format Filter */}
    {availableFormats.length > 0 && (
    <div className="space-y-3">
    <div className="text-sm font-medium text-slate-700 dark:text-slate-300">
    Format
    </div>
    <Input
    type="text"
    placeholder="Search formats..."
    value=""
    onChange={() => {}}
    aria-label="Search formats"
    />
    <div className="space-y-2">
    {availableFormats.map((format) => (
    <div key={format} className="flex items-center gap-2">
    <input
    type="checkbox"
    id={`format-${format}`}
    checked={filters.formats.includes(format)}
    onChange={() => handleFormatToggle(format)}
    className="h-4 w-4 rounded border-slate-300 text-blue-600"
    />
    <label
    htmlFor={`format-${format}`}
    className="cursor-pointer text-sm text-slate-700 dark:text-slate-300"
    >
    {formatLabel(format)}
    </label>
    </div>
    ))}
    </div>
    </div>
    )}

    {/* Genre Filter */}
    {availableGenres.length > 0 && (
    <SearchableFilterList
    items={availableGenres}
    selectedItems={filters.genres}
    onToggle={(genre) => {
    const newGenres = filters.genres.includes(genre)
    ? filters.genres.filter((g) => g !== genre)
    : [...filters.genres, genre];
    onFiltersChange({ ...filters, genres: newGenres });
    }}
    label={(genre) => genre}
    shouldShowSelectClear
    onSelectAll={() =>
    onFiltersChange({ ...filters, genres: [...availableGenres] })
    }
    onClearAll={() => onFiltersChange({ ...filters, genres: [] })}
    searchPlaceholder="Search genres..."
    />
    )}

    {/* Tags Filter */}
    {availableTags.length > 0 && (
    <SearchableFilterList
    items={availableTags}
    selectedItems={filters.tags}
    onToggle={(tag) => {
    const newTags = filters.tags.includes(tag)
    ? filters.tags.filter((t) => t !== tag)
    : [...filters.tags, tag];
    onFiltersChange({ ...filters, tags: newTags });
    }}
    label={(tag) => tag}
    shouldShowSelectClear
    onSelectAll={() =>
    onFiltersChange({ ...filters, tags: [...availableTags] })
    }
    onClearAll={() => onFiltersChange({ ...filters, tags: [] })}
    searchPlaceholder="Search tags..."
    />
    )}

    {/* Status Filter */}
    {availableStatuses.length > 0 && (
    <div className="space-y-3">
    <div className="text-sm font-medium text-slate-700 dark:text-slate-300">
    Match Status
    </div>
    <div className="space-y-2">
    {availableStatuses.map((status) => (
    <div key={status} className="flex items-center gap-2">
    <input
    type="checkbox"
    id={`status-${status}`}
    checked={filters.statuses.includes(status)}
    onChange={() => handleStatusToggle(status)}
    className="h-4 w-4 rounded border-slate-300 text-blue-600"
    />
    <label
    htmlFor={`status-${status}`}
    className="cursor-pointer text-sm text-slate-700 dark:text-slate-300"
    >
    {formatPublicationStatusLabel(status)}
    </label>
    </div>
    ))}
    </div>
    </div>
    )}

    {/* Footer */}
    <div className="flex items-center justify-between border-t border-slate-200 pt-4 dark:border-slate-700">
    <span className="text-sm text-slate-600 dark:text-slate-400">
    Showing {matchCount} {matchCount === 1 ? "match" : "matches"}
    </span>
    <Button
    variant="outline"
    size="sm"
    onClick={handleClearAllFilters}
    disabled={activeFilterCount === 0}
    >
    Clear All
    </Button>
    </div>
    </CardContent>
    </CollapsibleContent>
    </Collapsible>
    </Card>
    );
    }