From 644e299e145485b3fd3110f754dbdec282776ec4 Mon Sep 17 00:00:00 2001
From: Mahen
Date: Mon, 16 Feb 2026 08:59:51 +0700
Subject: [PATCH] feat: add filter review by brand filter button
---
src/components/dashboards/BrandFilter.tsx | 44 ++++++++++---------
src/components/dashboards/DashboardClient.tsx | 13 +-----
src/components/dashboards/ReviewTable.tsx | 10 ++++-
src/hooks/useBrandFilter.ts | 4 +-
src/hooks/useReviewTable.ts | 36 ++++++++++-----
src/hooks/useSelectSearch.ts | 23 ++++++++++
6 files changed, 83 insertions(+), 47 deletions(-)
create mode 100644 src/hooks/useSelectSearch.ts
diff --git a/src/components/dashboards/BrandFilter.tsx b/src/components/dashboards/BrandFilter.tsx
index a6df500..bff385c 100644
--- a/src/components/dashboards/BrandFilter.tsx
+++ b/src/components/dashboards/BrandFilter.tsx
@@ -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) {
- 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 (
- {brands.map((brand) => (
-
- ))}
+ {brands.map((brand) => {
+ const isActive =
+ selectedBrand?.toLowerCase() === brand.name.toLowerCase();
+
+ return (
+
+ );
+ })}
);
}
diff --git a/src/components/dashboards/DashboardClient.tsx b/src/components/dashboards/DashboardClient.tsx
index b249b71..9c65611 100644
--- a/src/components/dashboards/DashboardClient.tsx
+++ b/src/components/dashboards/DashboardClient.tsx
@@ -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
-
+
diff --git a/src/components/dashboards/ReviewTable.tsx b/src/components/dashboards/ReviewTable.tsx
index b07ad63..25b5a8f 100644
--- a/src/components/dashboards/ReviewTable.tsx
+++ b/src/components/dashboards/ReviewTable.tsx
@@ -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 (
-
+
diff --git a/src/hooks/useBrandFilter.ts b/src/hooks/useBrandFilter.ts
index 143a9b5..625fb6b 100644
--- a/src/hooks/useBrandFilter.ts
+++ b/src/hooks/useBrandFilter.ts
@@ -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 };
};
diff --git a/src/hooks/useReviewTable.ts b/src/hooks/useReviewTable.ts
index 9101558..c21305e 100644
--- a/src/hooks/useReviewTable.ts
+++ b/src/hooks/useReviewTable.ts
@@ -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([]);
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: {
diff --git a/src/hooks/useSelectSearch.ts b/src/hooks/useSelectSearch.ts
new file mode 100644
index 0000000..74a938a
--- /dev/null
+++ b/src/hooks/useSelectSearch.ts
@@ -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 };
+};