frontend/components/filter-sidebar.tsx

163 lines
5.6 KiB
TypeScript

"use client"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Separator } from "@/components/ui/separator"
import { Star, X } from "lucide-react"
import {useGetGenres} from "@/api/mangamochi";
interface FilterSidebarProps {
selectedGenres: number[]
selectedStatus: string[]
minRating: number
onGenresChange: (genres: number[]) => void
onStatusChange: (status: string[]) => void
onRatingChange: (rating: number) => void
}
const STATUSES = ["Ongoing", "Completed", "Hiatus"]
const RATINGS = [
{ label: "4.5+ Stars", value: 4.5 },
{ label: "4.0+ Stars", value: 4.0 },
{ label: "3.5+ Stars", value: 3.5 },
{ label: "All Ratings", value: 0 },
]
export function FilterSidebar({
selectedGenres,
selectedStatus,
minRating,
onGenresChange,
onStatusChange,
onRatingChange,
}: FilterSidebarProps) {
const { data: genresData } = useGetGenres();
const toggleGenre = (genre: number) => {
if (selectedGenres.includes(genre)) {
onGenresChange(selectedGenres.filter((g) => g !== genre))
} else {
onGenresChange([...selectedGenres, genre])
}
}
const toggleStatus = (status: string) => {
if (selectedStatus.includes(status)) {
onStatusChange(selectedStatus.filter((s) => s !== status))
} else {
onStatusChange([...selectedStatus, status])
}
}
const clearAllFilters = () => {
onGenresChange([])
onStatusChange([])
onRatingChange(0)
}
const hasActiveFilters = selectedGenres.length > 0 || selectedStatus.length > 0 || minRating > 0
return (
<aside className="w-64 border-r border-border bg-sidebar">
<div className="sticky top-0 flex h-screen flex-col">
{/* Header */}
<div className="flex items-center justify-between border-b border-sidebar-border px-6 py-6">
<h2 className="text-lg font-semibold text-sidebar-foreground">Filters</h2>
{hasActiveFilters && (
<Button variant="ghost" size="sm" onClick={clearAllFilters} className="h-8 px-2 text-xs">
Clear
</Button>
)}
</div>
{/* Filters */}
<div className="flex-1 overflow-y-auto px-6 py-6">
<div className="space-y-8">
{/* Genres */}
<div>
<h3 className="mb-3 text-sm font-medium text-sidebar-foreground">Genres</h3>
<div className="flex flex-wrap gap-2">
{genresData?.map((genre) => {
const isSelected = selectedGenres.includes(genre.id)
return (
<Badge
key={genre.id}
variant={isSelected ? "default" : "outline"}
className={`cursor-pointer transition-colors ${
isSelected
? "bg-sidebar-primary text-sidebar-primary-foreground hover:bg-sidebar-primary/90"
: "border-sidebar-border text-sidebar-foreground hover:bg-sidebar-accent"
}`}
onClick={() => toggleGenre(genre.id)}
>
{genre.name}
{isSelected && <X className="ml-1 h-3 w-3" />}
</Badge>
)
})}
</div>
</div>
<Separator className="bg-sidebar-border" />
{/* Status */}
<div>
<h3 className="mb-3 text-sm font-medium text-sidebar-foreground">Status</h3>
<div className="flex flex-wrap gap-2">
{STATUSES.map((status) => {
const isSelected = selectedStatus.includes(status)
return (
<Badge
key={status}
variant={isSelected ? "default" : "outline"}
className={`cursor-pointer transition-colors ${
isSelected
? "bg-sidebar-primary text-sidebar-primary-foreground hover:bg-sidebar-primary/90"
: "border-sidebar-border text-sidebar-foreground hover:bg-sidebar-accent"
}`}
onClick={() => toggleStatus(status)}
>
{status}
{isSelected && <X className="ml-1 h-3 w-3" />}
</Badge>
)
})}
</div>
</div>
<Separator className="bg-sidebar-border" />
{/* Rating */}
<div>
<h3 className="mb-3 text-sm font-medium text-sidebar-foreground">Minimum Rating</h3>
<div className="space-y-2">
{RATINGS.map((rating) => {
const isSelected = minRating === rating.value
return (
<button
key={rating.value}
onClick={() => onRatingChange(rating.value)}
className={`flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm transition-colors ${
isSelected
? "bg-sidebar-accent text-sidebar-accent-foreground"
: "text-sidebar-foreground hover:bg-sidebar-accent/50"
}`}
>
<Star
className={`h-4 w-4 ${isSelected ? "fill-sidebar-primary text-sidebar-primary" : "text-muted-foreground"}`}
/>
<span>{rating.label}</span>
</button>
)
})}
</div>
</div>
</div>
</div>
</div>
</aside>
)
}