-
Notifications
You must be signed in to change notification settings - Fork 264
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #846 from elitenoire/feat/product-grid-view-user-d…
…ashboard (feat:user-dashboard) add products grid view for User Dashboard
- Loading branch information
Showing
11 changed files
with
572 additions
and
65 deletions.
There are no files selected for viewing
161 changes: 161 additions & 0 deletions
161
src/app/dashboard/(user-dashboard)/products/_components/product-content-view.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
102 changes: 102 additions & 0 deletions
102
src/app/dashboard/(user-dashboard)/products/_components/product-grid-card.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
Oops, something went wrong.