225 lines
7.7 KiB
TypeScript
225 lines
7.7 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";
|
|
import { useAuth } from "@/contexts/auth-context";
|
|
import { Switch } from "@/components/ui/switch";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
|
|
interface FilterSidebarProps {
|
|
selectedGenres: number[];
|
|
selectedStatus: string[];
|
|
minRating: number;
|
|
userFavorites: boolean;
|
|
showAdultContent: boolean;
|
|
onGenresChange: (genres: number[]) => void;
|
|
onStatusChange: (status: string[]) => void;
|
|
onRatingChange: (rating: number) => void;
|
|
onUserFavoritesChange: (favorites: boolean) => void;
|
|
onShowAdultContentChange: (showAdult: boolean) => void;
|
|
}
|
|
|
|
const STATUSES = ["Ongoing", "Completed", "Hiatus"];
|
|
|
|
const RATINGS = [
|
|
{ label: "8.5+ Stars", value: 8.5 },
|
|
{ label: "7.0+ Stars", value: 7.0 },
|
|
{ label: "5.0+ Stars", value: 5.0 },
|
|
{ label: "All Ratings", value: 0 },
|
|
];
|
|
|
|
export function FilterSidebar({
|
|
selectedGenres,
|
|
selectedStatus,
|
|
minRating,
|
|
userFavorites,
|
|
showAdultContent,
|
|
onGenresChange,
|
|
onStatusChange,
|
|
onRatingChange,
|
|
onUserFavoritesChange,
|
|
onShowAdultContentChange,
|
|
}: FilterSidebarProps) {
|
|
const { data: genresData, isPending: isPendingGenres } = useGetGenres();
|
|
const { isAuthenticated } = useAuth();
|
|
|
|
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);
|
|
onUserFavoritesChange(false);
|
|
onShowAdultContentChange(false);
|
|
};
|
|
|
|
const hasActiveFilters =
|
|
selectedGenres.length > 0 ||
|
|
selectedStatus.length > 0 ||
|
|
minRating > 0 ||
|
|
userFavorites;
|
|
|
|
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">
|
|
{isAuthenticated && (
|
|
<>
|
|
<div>
|
|
<h3 className="mb-3 text-sm font-medium text-sidebar-foreground">
|
|
Content
|
|
</h3>
|
|
<div className="flex items-center justify-between rounded-md px-3 py-2">
|
|
<label className="text-sm text-sidebar-foreground">
|
|
Show Only Favorites
|
|
</label>
|
|
<Switch
|
|
checked={userFavorites}
|
|
onCheckedChange={onUserFavoritesChange}
|
|
/>
|
|
</div>
|
|
<div className="flex items-center justify-between rounded-md px-3 py-2">
|
|
<label className="text-sm text-sidebar-foreground">
|
|
Show Adult Content
|
|
</label>
|
|
<Switch
|
|
checked={showAdultContent}
|
|
onCheckedChange={onShowAdultContentChange}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<Separator className="bg-sidebar-border" />
|
|
</>
|
|
)}
|
|
|
|
{/* Genres */}
|
|
<div>
|
|
<h3 className="mb-3 text-sm font-medium text-sidebar-foreground">
|
|
Genres
|
|
</h3>
|
|
<div className="flex flex-wrap gap-2">
|
|
{isPendingGenres && <Skeleton className="h-[240px] w-full" />}
|
|
{!isPendingGenres &&
|
|
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>
|
|
);
|
|
}
|