style: change list brand to dropdown menu format
This commit is contained in:
parent
1593fd75bd
commit
277a237654
|
|
@ -1,22 +1,18 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { useBrandFilter } from "@/src/hooks/useBrandFilter";
|
import { useBrandFilter } from "@/src/hooks/useBrandFilter";
|
||||||
import { Button } from "../ui/button";
|
|
||||||
import { ChevronRight } from "lucide-react";
|
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "../ui/select";
|
||||||
|
|
||||||
export function BrandFilter() {
|
export function BrandFilter() {
|
||||||
const {
|
const { isLoading, totalCount, selectedBrand, validBrands, handleSelect } =
|
||||||
isLoading,
|
useBrandFilter();
|
||||||
totalCount,
|
|
||||||
selectedBrand,
|
|
||||||
visibleBrands,
|
|
||||||
isExpanded,
|
|
||||||
validBrands,
|
|
||||||
setIsExpanded,
|
|
||||||
handleSelect,
|
|
||||||
} = useBrandFilter();
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -31,72 +27,35 @@ export function BrandFilter() {
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.2, ease: "circOut" }}
|
transition={{ duration: 0.2, ease: "circOut" }}
|
||||||
className="flex items-center justify-center"
|
|
||||||
>
|
>
|
||||||
<div className="flex flex-wrap gap-2">
|
<Select
|
||||||
<button
|
value={selectedBrand ?? "__all__"}
|
||||||
onClick={() => handleSelect(null)}
|
onValueChange={(val) => handleSelect(val === "__all__" ? null : val)}
|
||||||
className={cn(
|
>
|
||||||
"rounded-lg border px-4 py-2 text-sm font-medium transition-all cursor-pointer",
|
<SelectTrigger className="w-48">
|
||||||
selectedBrand === null
|
<SelectValue placeholder="Pilih Brand" />
|
||||||
? "border-primary bg-primary text-primary-foreground"
|
</SelectTrigger>
|
||||||
: "border-border bg-card text-muted-foreground hover:border-primary/50 hover:text-foreground",
|
<SelectContent
|
||||||
)}
|
className="bg-card border-border shadow-lg"
|
||||||
|
position="popper"
|
||||||
|
>
|
||||||
|
<SelectItem
|
||||||
|
value="__all__"
|
||||||
|
className="cursor-pointer hover:bg-primary hover:text-card focus:bg-primary focus:text-card"
|
||||||
>
|
>
|
||||||
Semua ({totalCount.toLocaleString()})
|
Semua ({totalCount.toLocaleString()})
|
||||||
</button>
|
</SelectItem>
|
||||||
|
{validBrands.map((brand) => (
|
||||||
<div className="flex flex-wrap gap-2">
|
<SelectItem
|
||||||
{visibleBrands.map((brand) => {
|
|
||||||
const isActive =
|
|
||||||
selectedBrand?.toLowerCase() === brand.name.toLowerCase();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={brand.name}
|
key={brand.name}
|
||||||
onClick={() => handleSelect(brand.name)}
|
value={brand.name}
|
||||||
className={cn(
|
className="cursor-pointer hover:bg-primary hover:text-card focus:bg-primary focus:text-card"
|
||||||
"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()})
|
{brand.name} ({brand.count.toLocaleString()})
|
||||||
</button>
|
</SelectItem>
|
||||||
);
|
))}
|
||||||
})}
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
{!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",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,6 @@ export default function DashboardClient() {
|
||||||
title="Total Ulasan"
|
title="Total Ulasan"
|
||||||
value={totalReviews}
|
value={totalReviews}
|
||||||
icon={MessageSquareText}
|
icon={MessageSquareText}
|
||||||
trend={{ value: 12.5, isPositive: true }}
|
|
||||||
delay={0}
|
delay={0}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export default function ExportExcel() {
|
||||||
<Button
|
<Button
|
||||||
onClick={() => downloadAllData(data)}
|
onClick={() => downloadAllData(data)}
|
||||||
variant="outline"
|
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}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
<Download className="h-4 w-4" />
|
<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";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue