Skip to content

Commit

Permalink
Merge pull request #846 from elitenoire/feat/product-grid-view-user-d…
Browse files Browse the repository at this point in the history
…ashboard

(feat:user-dashboard) add products grid view for User Dashboard
  • Loading branch information
chumex412 authored Aug 1, 2024
2 parents 520077d + cf30506 commit 39f9f27
Show file tree
Hide file tree
Showing 11 changed files with 572 additions and 65 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { AnimatePresence } from "framer-motion";
import { useRouter } from "next-nprogress-bar";

import {
Table,
TableBody,
TableHead,
TableHeader,
TableRow,
} from "~/components/ui/table";
import { useProductModal } from "~/hooks/admin-product/use-product.modal";
import { useProducts } from "~/hooks/admin-product/use-products.persistence";
import { cn } from "~/lib/utils";
import { ProductTableProperties } from "~/types/admin-product.types";
import { ProductGridCard } from "./product-grid-card";
import { ProductListRow } from "./product-list-row";
import { ProductNotFound } from "./product-not-found";

type Properties = {
subset: ProductTableProperties[];
filteredProducts: ProductTableProperties[];
searchTerm: string;
view: "grid" | "list";
};

export const ProductContentView = ({
subset,
filteredProducts,
searchTerm,
view = "grid",
}: Properties) => {
const { products } = useProducts();
const {
updateOpen,
updateProductId,
product_id,
isActionModal,
setIsActionModal,
setIsDelete,
} = useProductModal();
const router = useRouter();

if (!products) return;

const handleOpenActionModal = (product_id: string) => {
updateProductId(product_id);
setIsActionModal(!isActionModal);
};

const handleOpenDetail = (product_id: string) => {
setIsActionModal(false);
updateProductId(product_id);
updateOpen(true);
};
const handleDeleteAction = (id: string) => {
setIsActionModal(false);
updateProductId(id);
setIsDelete(true);
};
const handleEditAction = (id: string) => {
setIsActionModal(false);
router.push(`/dashboard/products/${id}`);
};

return (
<AnimatePresence>
{view === "list" && (
<div className="show_scrollbar rounded-xl border border-gray-300 bg-[#F1F5F9] pt-1 sm:pt-4">
<Table
divClassName={cn(
"relative h-full min-h-[400px] xl:min-h-[600px] bg-white",
)}
>
<TableHeader>
<TableRow className="bg-[#F1F5F9]">
<TableHead className="w-[100px]x overflow-x-auto text-center text-xs min-[380px]:text-sm md:w-[200px] md:text-base lg:w-[200px]">
Product Name
</TableHead>
<TableHead className="whitespace-nowrap text-xs min-[380px]:text-sm md:text-base">
Product ID
</TableHead>
<TableHead className="whitespace-nowrap text-xs min-[380px]:text-sm md:text-base">
Category
</TableHead>
<TableHead className="whitespace-nowrap text-xs min-[380px]:text-sm md:text-base">
Price
</TableHead>
<TableHead className="whitespace-nowrap text-xs min-[380px]:text-sm md:text-base">
Status
</TableHead>
<TableHead className="whitespace-nowrap text-xs min-[380px]:text-sm md:text-base">
Actions
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredProducts.length > 0 &&
subset.length > 0 &&
subset.map((product, index) => (
<ProductListRow
key={product.product_id}
id={product.product_id}
selectedId={product_id}
searchTerm={searchTerm}
price={product.price}
description={product.description}
title={product.name}
category={product.category}
imgSrc={product.image}
status={product.status}
isBottomRow={
index === subset.length - 1 || index === subset.length - 2
}
isActionModal={isActionModal}
onOpenDetails={() => handleOpenDetail(product.product_id)}
onEdit={() => handleEditAction(product.product_id)}
onDelete={() => handleDeleteAction(product.product_id)}
onOpenActionModal={() =>
handleOpenActionModal(product.product_id)
}
onCloseActionModal={() => setIsActionModal(false)}
/>
))}
</TableBody>
{filteredProducts.length === 0 && searchTerm.length > 1 && (
<ProductNotFound term={searchTerm} />
)}
</Table>
</div>
)}
{view === "grid" && (
<div className="flex h-full flex-col justify-center">
<div className="grid w-full grid-cols-[repeat(auto-fit,minmax(var(--min),1fr))] gap-x-[8.91px] gap-y-[26px] [--min:185px] sm:gap-y-8 sm:[--min:240px] md:gap-x-[36px]">
{filteredProducts.length > 0 &&
subset.length > 0 &&
subset.map((product) => (
<ProductGridCard
key={product.product_id}
id={product.product_id}
// selectedId={product_id}
searchTerm={searchTerm}
price={product.price}
description={product.description}
title={product.name}
category={product.category}
imgSrc={product.image}
status={product.status}
onSelect={() => ({})}
onEdit={() => handleEditAction(product.product_id)}
onDelete={() => handleDeleteAction(product.product_id)}
/>
))}
</div>
{filteredProducts.length === 0 && searchTerm.length > 1 && (
<ProductNotFound term={searchTerm} />
)}
</div>
)}
</AnimatePresence>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,12 @@ import { useEffect, useMemo, useState } from "react";

import LoadingSpinner from "~/components/miscellaneous/loading-spinner";
import ProductCardSkeleton from "~/components/skeleton/product.skeleton";

import "~/components/ui/table";

import {
Table,
TableBody,
TableHead,
TableHeader,
TableRow,
} from "~/components/ui/table";
import { useProductModal } from "~/hooks/admin-product/use-product.modal";
import { useProductsFilters } from "~/hooks/admin-product/use-products.-filters.persistence";
import { useProducts } from "~/hooks/admin-product/use-products.persistence";
import { cn } from "~/lib/utils";
import { ProductTableProperties } from "~/types/admin-product.types";
import ProductBodyShadcn from "./product-body-shadcn";
import { ProductContentView } from "./product-content-view";

const Pagination = dynamic(() => import("react-paginate"), {
ssr: false,
Expand Down Expand Up @@ -108,60 +98,17 @@ const ProductContent = ({
<div className="relative flex w-full flex-col overflow-hidden pb-10">
<div
className={cn(
"show_scrollbar rounded-xl border border-gray-300 bg-[#F1F5F9] pt-1 sm:pt-4",
isOpen
? "max-w-full lg:max-w-[600px] min-[1090px]:max-w-[650px] min-[1150px]:max-w-[750px] min-[1200px]:max-w-[800px] xl:max-w-[820px] min-[1300px]:max-w-full"
: "max- w-full",
)}
>
<AnimatePresence>
{view === "list" && (
<Table
divClassName={cn(
"relative h-full min-h-[400px] xl:min-h-[600px] bg-white",
)}
>
<TableHeader>
<TableRow className="bg-[#F1F5F9]">
<TableHead className="w-[100px]x overflow-x-auto text-center text-xs min-[380px]:text-sm md:w-[200px] md:text-base lg:w-[200px]">
Product Name
</TableHead>
<TableHead className="whitespace-nowrap text-xs min-[380px]:text-sm md:text-base">
Product ID
</TableHead>
<TableHead className="whitespace-nowrap text-xs min-[380px]:text-sm md:text-base">
Category
</TableHead>
<TableHead className="whitespace-nowrap text-xs min-[380px]:text-sm md:text-base">
Price
</TableHead>
<TableHead className="whitespace-nowrap text-xs min-[380px]:text-sm md:text-base">
Status
</TableHead>
<TableHead className="whitespace-nowrap text-xs min-[380px]:text-sm md:text-base">
Actions
</TableHead>
</TableRow>
</TableHeader>
<TableBody className="w-full">
<ProductBodyShadcn
subset={subset}
filteredProducts={filteredProducts}
searchTerm={searchTerm}
/>
</TableBody>
{filteredProducts.length === 0 && searchTerm.length > 1 && (
<div className="absolute top-1/2 flex w-full items-center justify-center">
<p className="w-full text-center">
No product found for &quot;
<span className="font-bold">{searchTerm}</span>
&quot;
</p>
</div>
)}
</Table>
)}
</AnimatePresence>
<ProductContentView
view={view}
searchTerm={searchTerm}
filteredProducts={filteredProducts}
subset={subset}
/>
{!products && <ProductCardSkeleton count={9} />}
</div>
<AnimatePresence>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const ProductFilter = ({
<div className="flex items-center gap-x-2">
<Button
onClick={() => setView("grid")}
disabled
disabled={view === "grid"}
variant="outline"
size="icon"
className={cn(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import CustomButton from "~/components/common/common-button/common-button";
import BlurImage from "~/components/miscellaneous/blur-image";
import { Badge, type BadgeProperties } from "~/components/ui/badge";
import { Input } from "~/components/ui/input";
import { formatPrice } from "~/lib/utils";
import { ProductHighlightTerm } from "./product-highlight-term";

const stockStatus = {
in_stock: {
variant: "success-dot",
label: "In Stock",
},
out_of_stock: {
variant: "error-dot",
label: "Out of Stock",
},
low_on_stock: {
variant: "warning-dot",
label: "Low on Stock",
},
};

type ProductGridCardProperties = {
id?: string;
title: string;
description?: string;
status: "in_stock" | "out_of_stock" | "low_on_stock";
price: number;
imgSrc: string;
searchTerm?: string;
category: string;
onEdit: () => void; // (id: string) => void;
onDelete: () => void; // (id: string) => void;
onSelect: () => void; // (id: string) => void;
};

export function ProductGridCard({
// id,
title = "Product 1",
// description,
imgSrc,
price = 15,
status = "in_stock",
searchTerm = "",
category,
onEdit,
onDelete,
onSelect,
}: ProductGridCardProperties) {
const stock = stockStatus[status];
return (
<div className="flex max-w-[240px] flex-col gap-[17.305px] rounded-[6px] border-[0.705px] bg-white px-4 py-4">
<div>
<div className="relative aspect-[21/11] w-full overflow-hidden rounded-[7.049px]">
<Input
type="checkbox"
className="z-1 absolute left-2 top-2 size-4"
onClick={onSelect}
/>
<BlurImage src={imgSrc} alt={title} fill className="object-cover" />
</div>
</div>
<div className="flex flex-1 flex-col justify-between gap-2">
<div className="space-y-2">
<div className="flex flex-wrap justify-between gap-2 text-[16.917px] font-bold -tracking-[0.338px]">
<h2>
<ProductHighlightTerm searchTerm={searchTerm} title={title} />
</h2>
<p>{formatPrice(price)}</p>
</div>
<div>
<p className="text-[11.278px] text-neutral-dark-1">{category}</p>
</div>
<div>
<Badge
variant={stock.variant as BadgeProperties["variant"]}
className="h-5"
>
{stock.label}
</Badge>
</div>
</div>
<div className="flex flex-wrap justify-between gap-1.5">
<CustomButton
variant="outline"
className="h-8 flex-1 border-[0.705px] text-xs"
onClick={onEdit}
>
Edit
</CustomButton>
<CustomButton
variant="outline"
className="h-8 flex-1 border-[0.705px] text-xs text-error"
onClick={onDelete}
>
Delete
</CustomButton>
</div>
</div>
</div>
);
}
Loading

0 comments on commit 39f9f27

Please sign in to comment.