import { productApi } from "@/api/product-api.service"; import { ImprovedToggleFilter } from "@/components/improved-toggle-filter"; import Loader from "@/components/loader"; import ProductModal from "@/components/product-modal"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { removeFalsyValues } from "@/features/app"; import { mapToIPost, type IPost } from "@/lib/utils"; import { useMutation, useQuery } from "@tanstack/react-query"; import { ChevronLeft, ChevronRight, DoorOpenIcon, Edit, Eye, EyeOff, MoreHorizontal, RefreshCcwIcon, Search, } from "lucide-react"; import { useMemo, useState } from "react"; import { useDebounce } from "use-debounce"; export default function Popup() { const [searchTerm, setSearchTerm] = useState(""); const [filter, setFilter] = useState>({}); const [currentPage, setCurrentPage] = useState(1); const [search] = useDebounce(searchTerm, 400); const queryKey = useMemo( () => ["products", { currentPage, search, filter }], [currentPage, search, filter] ); // --- React Query fetch --- const { data: rawProducts, isFetching, ...dataQuery } = useQuery({ queryKey, queryFn: async () => { const data = await productApi.apiRequest( "index", removeFalsyValues({ skip: (currentPage - 1) * productApi.item_per_page, where: { productModelCode: searchTerm, status_listing: filter?.statusFilter === "all" ? undefined : filter.statusFilter, }, }) ); return data; }, }); const { data: publistedProducts, ...publistQuery } = useQuery({ queryKey: ["publised-products"], queryFn: async () => { const data = await productApi.apiRequest("getPublistedProducts", {}); return data ?? []; }, staleTime: 0, // luôn coi là stale -> gọi lại API mỗi lần mount refetchOnMount: "always", }); const actionMutation = useMutation({ mutationKey: ["action-mutaions"], mutationFn: async (data: IPost) => { if (data.status) { return productApi.apiRequest("unlist", data); } const res = await productApi.apiRequest("get", data); if (!res || !(res as any)?.data) return; return productApi.apiRequest("publist", { ...data, images: mapToIPost({ ...(res as any)?.data }).images, }); }, }); const data: IPost[] = useMemo(() => { if (!rawProducts || !(rawProducts as any)?.data) return []; return (rawProducts as any)?.data.map((item: any) => mapToIPost(item)); }, [rawProducts]); const formatPrice = (price: number) => new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }).format(price); const clearFilters = () => { setSearchTerm(""); setFilter({ statusFilter: "" }); setCurrentPage(1); }; const activeFiltersCount = [filter.statusFilter, searchTerm !== ""].filter( Boolean ).length; const totalPages = useMemo(() => { if (!(rawProducts as any)?.total) return 0; return Math.ceil((rawProducts as any).total / productApi.item_per_page); // eslint-disable-next-line react-hooks/exhaustive-deps }, [(rawProducts as any)?.total, productApi.item_per_page]); // --- reset page when filter changes --- // eslint-disable-next-line react-hooks/exhaustive-deps const from = useMemo(() => { return (currentPage - 1) * productApi.item_per_page + 1; }, [currentPage]); const to = useMemo(() => { return Math.min( currentPage * productApi.item_per_page, (rawProducts as any)?.total ?? 0 ); }, [currentPage, rawProducts]); const handleActionListing = async (data: IPost) => { console.log({ post: data }); actionMutation.mutate(data); }; return (
setSearchTerm(e.target.value)} className="pl-10" />
Showing {from}-{to} of {(rawProducts as any)?.total ?? 0} products {totalPages > 1 && (
{totalPages <= 7 ? ( // Show all pages if 7 or fewer Array.from({ length: totalPages }, (_, i) => ( )) ) : ( // Show pages with ellipsis for more than 7 pages <> {/* First page */} {/* Left ellipsis */} {currentPage > 4 && ( ... )} {/* Middle pages */} {Array.from({ length: 3 }, (_, i) => { let pageNum; if (currentPage <= 4) { pageNum = i + 2; } else if (currentPage >= totalPages - 3) { pageNum = totalPages - 4 + i; } else { pageNum = currentPage - 1 + i; } if (pageNum > 1 && pageNum < totalPages) { return ( ); } return null; }).filter(Boolean)} {/* Right ellipsis */} {currentPage < totalPages - 3 && ( ... )} {/* Last page */} )}
)}
Name Price Status Actions {isFetching && (
)} {data.length === 0 && !isFetching ? (
No products found
) : ( data.map((post) => { const status = (publistedProducts as any)?.some( (item: any) => item.title.includes(post.sku) ); post.status = status; return (
{post.title}
{post.description}
{formatPrice(post.price)} {post?.status ? "Listed" : "Unlisted"}
{ e.preventDefault(); // Ngăn dropdown đóng lại e.stopPropagation(); }} > Edit handleActionListing(post)} > {post.status ? ( ) : ( )} {post.status ? "Unlist" : "List"} {/* {actionMutation.isPending && } */} {/* handleUnListing(post)}> unList handleRePublist(post)}> re publist */}
); }) )}
); }