163 lines
5.6 KiB
TypeScript
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>
|
|
)
|
|
}
|