style: change list brand to dropdown menu format
This commit is contained in:
parent
1593fd75bd
commit
277a237654
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ export default function DashboardClient() {
|
|||
title="Total Ulasan"
|
||||
value={totalReviews}
|
||||
icon={MessageSquareText}
|
||||
trend={{ value: 12.5, isPositive: true }}
|
||||
delay={0}
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue