feat: add filter review by brand filter button
This commit is contained in:
parent
0fe22e8283
commit
644e299e14
|
|
@ -1,14 +1,11 @@
|
|||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { BrandFilterProps } from "@/src/types";
|
||||
import { useBrandFilter } from "@/src/hooks/useBrandFilter";
|
||||
|
||||
export function BrandFilter({
|
||||
selectedBrand,
|
||||
onSelect,
|
||||
}: Omit<BrandFilterProps, "brands">) {
|
||||
const { brands, isLoading, totalCount } = useBrandFilter();
|
||||
export function BrandFilter() {
|
||||
const { brands, isLoading, totalCount, selectedBrand, handleSelect } =
|
||||
useBrandFilter();
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
|
|
@ -21,7 +18,7 @@ export function BrandFilter({
|
|||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
onClick={() => onSelect(null)}
|
||||
onClick={() => handleSelect(null)}
|
||||
className={cn(
|
||||
"rounded-lg border px-4 py-2 text-sm font-medium transition-all",
|
||||
selectedBrand === null
|
||||
|
|
@ -32,20 +29,25 @@ export function BrandFilter({
|
|||
Semua ({totalCount.toLocaleString()})
|
||||
</button>
|
||||
|
||||
{brands.map((brand) => (
|
||||
<button
|
||||
key={brand.name}
|
||||
onClick={() => onSelect(brand.name)}
|
||||
className={cn(
|
||||
"rounded-lg border px-4 py-2 text-sm font-medium transition-all",
|
||||
selectedBrand === brand.name
|
||||
? "border-primary bg-primary text-primary-foreground"
|
||||
: "border-border bg-card text-muted-foreground hover:border-primary/50 hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{brand.name} ({brand.count.toLocaleString()})
|
||||
</button>
|
||||
))}
|
||||
{brands.map((brand) => {
|
||||
const isActive =
|
||||
selectedBrand?.toLowerCase() === brand.name.toLowerCase();
|
||||
|
||||
return (
|
||||
<button
|
||||
key={brand.name}
|
||||
onClick={() => handleSelect(brand.name)}
|
||||
className={cn(
|
||||
"rounded-lg border px-4 py-2 text-sm font-medium transition-all",
|
||||
isActive
|
||||
? "border-primary bg-primary text-primary-foreground"
|
||||
: "border-border bg-card text-muted-foreground hover:border-primary/50 hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{brand.name} ({brand.count.toLocaleString()})
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,7 @@
|
|||
import { Header } from "./Header";
|
||||
import { Frown, Meh, MessageSquareText, Smile, TrendingUp } from "lucide-react";
|
||||
import { StatCard } from "./StatCard";
|
||||
import {
|
||||
brandData,
|
||||
sentimentDistribution,
|
||||
trendData,
|
||||
} from "@/src/app/dashboard/lib/data";
|
||||
import { sentimentDistribution, trendData } from "@/src/app/dashboard/lib/data";
|
||||
import { ModelInfoSkeleton } from "../skeletons/ModelInfoSkeleton";
|
||||
import { ModelInfo } from "./ModelInfo";
|
||||
import { BrandFilter } from "./BrandFilter";
|
||||
|
|
@ -24,10 +20,8 @@ export default function DashboardClient() {
|
|||
positiveCount,
|
||||
negativeCount,
|
||||
neutralCount,
|
||||
selectedBrand,
|
||||
loading,
|
||||
modelData,
|
||||
setSelectedBrand,
|
||||
percentage,
|
||||
} = useDashboards();
|
||||
|
||||
|
|
@ -142,10 +136,7 @@ export default function DashboardClient() {
|
|||
Hasil klasifikasi sentimen ulasan produk laptop
|
||||
</p>
|
||||
</div>
|
||||
<BrandFilter
|
||||
selectedBrand={selectedBrand}
|
||||
onSelect={setSelectedBrand}
|
||||
/>
|
||||
<BrandFilter />
|
||||
</div>
|
||||
<ReviewTable />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -26,9 +26,15 @@ import {
|
|||
PaginationNext,
|
||||
PaginationPrevious,
|
||||
} from "../ui/pagination";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
|
||||
export function ReviewTable() {
|
||||
const { currentData, isLoading, pagination } = useReviewTable(10);
|
||||
const searchParams = useSearchParams();
|
||||
const selectedBrand = searchParams.get("brand");
|
||||
const { currentData, isLoading, pagination } = useReviewTable(
|
||||
10,
|
||||
selectedBrand,
|
||||
);
|
||||
const { currentPage, totalPages, nextPage, prevPage, goToPage } = pagination;
|
||||
|
||||
if (isLoading) {
|
||||
|
|
@ -41,7 +47,7 @@ export function ReviewTable() {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border bg-card shadow-sm">
|
||||
<div className="rounded-xl border bg-card">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="hover:bg-transparent">
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { getTotalBrandAnalysis } from "../app/dashboard/lib/actions";
|
||||
import { useSelectSearch } from "./useSelectSearch";
|
||||
|
||||
export const useBrandFilter = () => {
|
||||
const [brands, setBrands] = useState<{ name: string; count: number }[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const { selectedBrand, handleSelect } = useSelectSearch();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchBrands = async () => {
|
||||
|
|
@ -24,5 +26,5 @@ export const useBrandFilter = () => {
|
|||
|
||||
const totalCount = brands.reduce((sum, b) => sum + (b?.count || 0), 0);
|
||||
|
||||
return { brands, isLoading, totalCount };
|
||||
return { brands, isLoading, totalCount, selectedBrand, handleSelect };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import { useEffect, useState, useMemo } from "react";
|
||||
import { ApiResponse, ReviewItem } from "../types";
|
||||
|
||||
export const useReviewTable = (itemsPerPage: number = 10) => {
|
||||
export const useReviewTable = (
|
||||
itemsPerPage: number = 10,
|
||||
selectedBrand: string | null = null,
|
||||
) => {
|
||||
const [data, setData] = useState<ReviewItem[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
|
@ -27,34 +30,43 @@ export const useReviewTable = (itemsPerPage: number = 10) => {
|
|||
getReviewData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
}, [selectedBrand]);
|
||||
|
||||
const { currentData, totalPages } = useMemo(() => {
|
||||
const total = Math.ceil(data.length / itemsPerPage);
|
||||
const start = (currentPage - 1) * itemsPerPage;
|
||||
const filteredData = selectedBrand
|
||||
? data.filter(
|
||||
(review) =>
|
||||
review.product?.brand?.toLowerCase() ===
|
||||
selectedBrand.toLowerCase(),
|
||||
)
|
||||
: data;
|
||||
|
||||
const total = Math.ceil(filteredData.length / itemsPerPage) || 1;
|
||||
|
||||
const safePage = currentPage > total ? total : currentPage;
|
||||
|
||||
const start = (safePage - 1) * itemsPerPage;
|
||||
const end = start + itemsPerPage;
|
||||
const slicedData = data.slice(start, end);
|
||||
|
||||
return {
|
||||
currentData: slicedData,
|
||||
currentData: filteredData.slice(start, end),
|
||||
totalPages: total,
|
||||
};
|
||||
}, [data, currentPage, itemsPerPage]);
|
||||
}, [data, currentPage, itemsPerPage, selectedBrand]);
|
||||
|
||||
const nextPage = () => {
|
||||
if (currentPage < totalPages) setCurrentPage((prev) => prev + 1);
|
||||
};
|
||||
|
||||
const prevPage = () => {
|
||||
if (currentPage > 1) setCurrentPage((prev) => prev - 1);
|
||||
};
|
||||
|
||||
const goToPage = (pageNumber: number) => {
|
||||
if (pageNumber >= 1 && pageNumber <= totalPages) {
|
||||
setCurrentPage(pageNumber);
|
||||
}
|
||||
if (pageNumber >= 1 && pageNumber <= totalPages) setCurrentPage(pageNumber);
|
||||
};
|
||||
|
||||
return {
|
||||
data,
|
||||
currentData,
|
||||
isLoading,
|
||||
pagination: {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
|
||||
export const useSelectSearch = () => {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const selectedBrand = searchParams.get("brand");
|
||||
|
||||
const handleSelect = (brandName: string | null) => {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
|
||||
if (brandName) {
|
||||
params.set("brand", brandName);
|
||||
} else {
|
||||
params.delete("brand");
|
||||
}
|
||||
|
||||
router.push(`${pathname}?${params.toString()}`, { scroll: false });
|
||||
};
|
||||
|
||||
return { selectedBrand, handleSelect };
|
||||
};
|
||||
Loading…
Reference in New Issue