style: change list brand to dropdown menu format

This commit is contained in:
Mahen 2026-03-31 21:22:32 +07:00
parent 1593fd75bd
commit 277a237654
4 changed files with 36 additions and 363 deletions

View File

@ -1,22 +1,18 @@
"use client";
import { cn } from "@/lib/utils";
import { useBrandFilter } from "@/src/hooks/useBrandFilter";
import { Button } from "../ui/button";
import { ChevronRight } from "lucide-react";
import { motion } from "framer-motion";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../ui/select";
export function BrandFilter() {
const {
isLoading,
totalCount,
selectedBrand,
visibleBrands,
isExpanded,
validBrands,
setIsExpanded,
handleSelect,
} = useBrandFilter();
const { isLoading, totalCount, selectedBrand, validBrands, handleSelect } =
useBrandFilter();
if (isLoading) {
return (
@ -31,72 +27,35 @@ export function BrandFilter() {
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.2, ease: "circOut" }}
className="flex items-center justify-center"
>
<div className="flex flex-wrap gap-2">
<button
onClick={() => handleSelect(null)}
className={cn(
"rounded-lg border px-4 py-2 text-sm font-medium transition-all cursor-pointer",
selectedBrand === null
? "border-primary bg-primary text-primary-foreground"
: "border-border bg-card text-muted-foreground hover:border-primary/50 hover:text-foreground",
)}
<Select
value={selectedBrand ?? "__all__"}
onValueChange={(val) => handleSelect(val === "__all__" ? null : val)}
>
<SelectTrigger className="w-48">
<SelectValue placeholder="Pilih Brand" />
</SelectTrigger>
<SelectContent
className="bg-card border-border shadow-lg"
position="popper"
>
Semua ({totalCount.toLocaleString()})
</button>
<div className="flex flex-wrap gap-2">
{visibleBrands.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 cursor-pointer",
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>
);
})}
{!isExpanded && validBrands.length > 3 && (
<button
onClick={() => setIsExpanded(true)}
className={cn(
"rounded-lg pl-2 text-sm font-medium transition-all cursor-pointer",
"flex-1 sm:flex-none animate-in fade-in slide-in-from-left-2",
)}
<SelectItem
value="__all__"
className="cursor-pointer hover:bg-primary hover:text-card focus:bg-primary focus:text-card"
>
Semua ({totalCount.toLocaleString()})
</SelectItem>
{validBrands.map((brand) => (
<SelectItem
key={brand.name}
value={brand.name}
className="cursor-pointer hover:bg-primary hover:text-card focus:bg-primary focus:text-card"
>
<div className="flex items-center justify-center gap-1 hover:text-primary">
{validBrands.length - 3}
<span>Lainnya</span>
<ChevronRight className="w-4 h-4" />
</div>
</button>
)}
{isExpanded && (
<Button
type="button"
variant="ghost"
onClick={() => {
setIsExpanded(false);
}}
className="text-sentiment-negative hover:text-sentiment-negative hover:bg-sentiment-negative-light shrink-0"
>
</Button>
)}
</div>
</div>
{brand.name} ({brand.count.toLocaleString()})
</SelectItem>
))}
</SelectContent>
</Select>
</motion.div>
);
}

View File

@ -66,7 +66,6 @@ export default function DashboardClient() {
title="Total Ulasan"
value={totalReviews}
icon={MessageSquareText}
trend={{ value: 12.5, isPositive: true }}
delay={0}
/>

View File

@ -13,7 +13,7 @@ export default function ExportExcel() {
<Button
onClick={() => downloadAllData(data)}
variant="outline"
className="flex items-center gap-2 border-primary/20 text-primary hover:bg-primary/5"
className="flex items-center gap-2 border-primary/20 text-primary hover:bg-primary hover:text-card"
disabled={isLoading}
>
<Download className="h-4 w-4" />

View File

@ -1,288 +1,3 @@
// "use client";
// import {
// Table,
// TableBody,
// TableCell,
// TableHead,
// TableHeader,
// TableRow,
// } from "../../components/ui/table";
// import { Badge } from "../../components/ui/badge";
// import { Inbox, Loader2 } from "lucide-react";
// import getSentimentBadge from "./SentimentBadge";
// import { useReviewTable } from "@/src/hooks/useReviewTable";
// import {
// Pagination,
// PaginationContent,
// PaginationEllipsis,
// PaginationItem,
// PaginationLink,
// PaginationNext,
// PaginationPrevious,
// } from "../ui/pagination";
// import { useSearchParams } from "next/navigation";
// import { getVisiblePages } from "@/src/utils/datas";
// import { exportToExcel } from "@/src/services/report.service";
// export function ReviewTable() {
// const searchParams = useSearchParams();
// const selectedBrand = searchParams.get("brand");
// const { currentData, isLoading, pagination } = useReviewTable(
// 10,
// selectedBrand,
// );
// const { currentPage, totalPages, nextPage, prevPage, goToPage } = pagination;
// const visiblePage = getVisiblePages({ totalPages, currentPage });
// if (isLoading) {
// return (
// <div className="flex h-75 w-full flex-col items-center justify-center gap-2 rounded-xl border bg-card text-muted-foreground">
// <Loader2 className="h-8 w-8 animate-spin text-primary" />
// <p className="text-sm">Memuat data ulasan...</p>
// </div>
// );
// }
// return (
// <div className="rounded-xl border bg-card">
// <Table>
// <TableHeader>
// <TableRow className="hover:bg-transparent">
// <TableHead className="w-62.5">Produk</TableHead>
// <TableHead className="w-auto min-w-75">
// Ulasan & Kata Kunci
// </TableHead>
// <TableHead className="w-30 whitespace-nowrap">Tanggal</TableHead>
// <TableHead className="w-30">Sentimen</TableHead>
// <TableHead className="w-50">Confidence Score</TableHead>
// </TableRow>
// </TableHeader>
// <TableBody>
// {currentData.length === 0 ? (
// <TableRow>
// <TableCell colSpan={5} className="h-75 text-center">
// <div className="flex flex-col items-center justify-center gap-2 text-muted-foreground">
// <div className="rounded-full bg-muted">
// <Inbox className="h-8 w-8" />
// </div>
// <p className="text-lg font-medium text-foreground">
// Belum ada data
// </p>
// <p className="text-sm">
// Belum ada ulasan yang dianalisis oleh sistem.
// </p>
// </div>
// </TableCell>
// </TableRow>
// ) : (
// currentData.map((review, index) => (
// <TableRow
// key={review.id || index}
// className="group animate-in fade-in transition-colors hover:bg-muted/40"
// style={{
// animationDelay: `${index * 50}ms`,
// animationFillMode: "backwards",
// }}
// >
// <TableCell className="align-top">
// <div className="flex flex-col gap-1.5">
// <div className="flex items-center gap-2">
// <span className="inline-flex items-center rounded-md bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary ring-1 ring-inset ring-primary/20">
// {/* Tambahkan .name di sini */}
// {review.product?.brand?.name || "Generic"}
// </span>
// </div>
// <span className="text-sm font-medium leading-tight text-foreground line-clamp-2">
// {review.product?.name || "Unknown Product"}
// </span>
// </div>
// </TableCell>
// <TableCell className="align-top">
// <div className="flex flex-col gap-3">
// <p className="text-sm leading-relaxed text-muted-foreground line-clamp-3 group-hover:text-foreground transition-colors">
// {review.content}
// </p>
// {review.keywords && review.keywords.length > 0 && (
// <div className="flex flex-wrap gap-1.5">
// {review.keywords.slice(0, 5).map((k, i) => (
// <Badge
// key={i}
// variant="secondary"
// className="h-5 px-1.5 text-[10px] font-normal text-muted-foreground border-border bg-muted group-hover:bg-background transition-all"
// >
// {k}
// </Badge>
// ))}
// </div>
// )}
// </div>
// </TableCell>
// <TableCell className="align-top whitespace-nowrap">
// <span className="text-xs text-muted-foreground font-medium">
// {review.createdAt
// ? new Date(review.createdAt).toLocaleDateString("id-ID", {
// day: "numeric",
// month: "short",
// year: "numeric",
// })
// : "-"}
// </span>
// </TableCell>
// <TableCell className="align-top">
// {getSentimentBadge(review.sentiment ?? null)}
// </TableCell>
// <TableCell className="align-top">
// <span className="font-mono text-sm font-semibold text-foreground">
// {review.confidenceScore
// ? `${(review.confidenceScore * 100).toFixed(1)}%`
// : "-"}
// </span>
// </TableCell>
// {/* <TableCell className="align-top ">
// <DropdownMenu>
// <DropdownMenuTrigger asChild className="cursor-pointer">
// <EllipsisVertical className="w-4 h-4" />
// </DropdownMenuTrigger>
// <DropdownMenuContent
// align="center"
// className={`w-max bg-card border-border shadow-md `}
// >
// <DropdownMenuItem className="cursor-pointer gap-2 focus:bg-sentiment-neutral-light focus:text-sentiment-neutral transition-colors hover:text-primary">
// <Pencil />
// <span>Edit</span>
// </DropdownMenuItem>
// <DropdownMenuItem className="flex cursor-pointer gap-2 text-destructive focus:bg-red-500 focus:text-white transition-colors">
// <Trash />
// <span>Delete</span>
// </DropdownMenuItem>
// </DropdownMenuContent>
// </DropdownMenu>
// </TableCell> */}
// </TableRow>
// ))
// )}
// </TableBody>
// </Table>
// {/* {totalPages > 1 && (
// <div className="border-t bg-muted/20 px-6 py-4">
// <Pagination className="justify-center sm:justify-end">
// <PaginationContent>
// <PaginationItem>
// <PaginationPrevious
// href="#"
// onClick={(e) => {
// e.preventDefault();
// prevPage();
// }}
// className={
// currentPage === 1
// ? "pointer-events-none opacity-50"
// : "cursor-pointer"
// }
// />
// </PaginationItem>
// {[...Array(totalPages)].map((_, i) => (
// <PaginationItem key={i + 1}>
// <PaginationLink
// href="#"
// onClick={(e) => {
// e.preventDefault();
// goToPage(i + 1);
// }}
// isActive={currentPage === i + 1}
// >
// {i + 1}
// </PaginationLink>
// </PaginationItem>
// ))}
// <PaginationItem>
// <PaginationNext
// href="#"
// onClick={(e) => {
// e.preventDefault();
// nextPage();
// }}
// className={
// currentPage === totalPages
// ? "pointer-events-none opacity-50"
// : "cursor-pointer"
// }
// />
// </PaginationItem>
// </PaginationContent>
// </Pagination>
// </div>
// )} */}
// {totalPages > 1 && (
// <div className="border-t bg-muted/20 px-6 py-4">
// <Pagination className="justify-center sm:justify-end">
// <PaginationContent>
// <PaginationItem>
// <PaginationPrevious
// href="#"
// onClick={(e) => {
// e.preventDefault();
// prevPage();
// }}
// className={
// currentPage === 1
// ? "pointer-events-none opacity-50"
// : "cursor-pointer hover:bg-[#F8FBFF] hover:text-primary"
// }
// />
// </PaginationItem>
// {visiblePage.map((page, index) => (
// <PaginationItem key={index}>
// {page === "..." ? (
// <PaginationEllipsis className="hover:cursor-not-allowed" />
// ) : (
// <PaginationLink
// href="#"
// onClick={(e) => {
// e.preventDefault();
// goToPage(page as number);
// }}
// isActive={currentPage === page}
// >
// {page}
// </PaginationLink>
// )}
// </PaginationItem>
// ))}
// <PaginationItem>
// <PaginationNext
// href="#"
// onClick={(e) => {
// e.preventDefault();
// nextPage();
// }}
// className={
// currentPage === totalPages
// ? "pointer-events-none opacity-50"
// : "cursor-pointer hover:bg-primary hover:text-card"
// }
// />
// </PaginationItem>
// </PaginationContent>
// </Pagination>
// </div>
// )}
// </div>
// );
// }
"use client";
import {