Skip to content

Commit

Permalink
feat: product page
Browse files Browse the repository at this point in the history
  • Loading branch information
Kiranism committed Sep 24, 2024
1 parent 21d4df2 commit 8acc01b
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 34 deletions.
30 changes: 17 additions & 13 deletions app/dashboard/product/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import ProductTable from '@/components/tables/product-tables';
import { buttonVariants } from '@/components/ui/button';
import { Heading } from '@/components/ui/heading';
import { Separator } from '@/components/ui/separator';
import { Employee, users } from '@/constants/data';
import { Employee, Product, users } from '@/constants/data';
import { searchParamsCache } from '@/lib/searchparams';
import { cn } from '@/lib/utils';
import { Plus } from 'lucide-react';
Expand All @@ -13,19 +13,24 @@ import type { SearchParams } from 'nuqs/server';

const breadcrumbItems = [
{ title: 'Dashboard', link: '/dashboard' },
{ title: 'Product', link: '/dashboard/product' }
{ title: 'Products', link: '/dashboard/product' }
];

type pageProps = {
searchParams: SearchParams;
};

async function fetchData(page: number, pageLimit: number, search: string) {
async function fetchProductData(
page: number,
pageLimit: number,
search: string
) {
const offset = (page - 1) * pageLimit; // Calculate the offset
const searchQuery = search ? `&search=${search}` : ''; // If there's a search query
const res = await fetch(
`https://api.slingacademy.com/v1/sample-data/users?offset=${
page - 1
}&limit=${pageLimit}` + (search ? `&search=${search}` : '')
`https://api.slingacademy.com/v1/sample-data/products?offset=${offset}&limit=${pageLimit}${searchQuery}`
);

return res.json();
}

Expand All @@ -39,19 +44,18 @@ export default async function page({ searchParams }: pageProps) {

console.log('filters', page, pageLimit, search);

const data = await fetchData(page, pageLimit, search);
const totalUsers = data.total_users; //1000
const pageCount = Math.ceil(totalUsers / pageLimit);
const employee: Employee[] = data.users;
const data = await fetchProductData(page, pageLimit, search);
const totalProducts = data.total_products;
const products: Product[] = data.products;

return (
<PageContainer>
<div className="space-y-4">
<Breadcrumbs items={breadcrumbItems} />
<div className="flex items-start justify-between">
<Heading
title={`Users (${totalUsers})`}
description="Manage users (Client side table functionalities.)"
title={`Products (${totalProducts})`}
description="Manage products (Server side table functionalities.)"
/>
<Link
href={'/'}
Expand All @@ -61,7 +65,7 @@ export default async function page({ searchParams }: pageProps) {
</Link>
</div>
<Separator />
<ProductTable data={employee} totalData={totalUsers} />
<ProductTable data={products} totalData={totalProducts} />
</div>
</PageContainer>
);
Expand Down
57 changes: 57 additions & 0 deletions components/tables/product-tables/cell-action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use client';
import { AlertModal } from '@/components/modal/alert-modal';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu';
import { Product } from '@/constants/data';
import { Edit, MoreHorizontal, Trash } from 'lucide-react';
import { useRouter } from 'next/navigation';
import { useState } from 'react';

interface CellActionProps {
data: Product;
}

export const CellAction: React.FC<CellActionProps> = ({ data }) => {
const [loading, setLoading] = useState(false);
const [open, setOpen] = useState(false);
const router = useRouter();

const onConfirm = async () => {};

return (
<>
<AlertModal
isOpen={open}
onClose={() => setOpen(false)}
onConfirm={onConfirm}
loading={loading}
/>
<DropdownMenu modal={false}>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span>
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>

<DropdownMenuItem
onClick={() => router.push(`/dashboard/user/${data.id}`)}
>
<Edit className="mr-2 h-4 w-4" /> Update
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setOpen(true)}>
<Trash className="mr-2 h-4 w-4" /> Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</>
);
};
45 changes: 45 additions & 0 deletions components/tables/product-tables/columns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use client';
import { Product } from '@/constants/data';
import { ColumnDef } from '@tanstack/react-table';
import Image from 'next/image';
import { CellAction } from './cell-action';

export const columns: ColumnDef<Product>[] = [
{
accessorKey: 'photo_url',
header: 'IMAGE',
cell: ({ row }) => {
return (
<div className="relative aspect-square">
<Image
src={row.getValue('photo_url')}
alt={row.getValue('name')}
fill
className="rounded-lg"
/>
</div>
);
}
},
{
accessorKey: 'name',
header: 'NAME'
},
{
accessorKey: 'category',
header: 'CATEGORY'
},
{
accessorKey: 'price',
header: 'PRICE'
},
{
accessorKey: 'description',
header: 'DESCRIPTION'
},

{
id: 'actions',
cell: ({ row }) => <CellAction data={row.original} />
}
];
8 changes: 4 additions & 4 deletions components/tables/product-tables/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { useCallback, useMemo } from 'react';

import { Button } from '@/components/ui/button';
import { DataTableSearch } from '@/components/ui/table/data-table-search';
import { Employee } from '@/constants/data';
import { columns } from '../employee-tables/columns';
import { Product } from '@/constants/data';
import { columns } from './columns';

const statusOptions = [
{ value: 'active', label: 'Active' },
Expand All @@ -26,7 +26,7 @@ export default function ProductTable({
data,
totalData
}: {
data: Employee[];
data: Product[];
totalData: number;
}) {
const [searchQuery, setSearchQuery] = useQueryState(
Expand Down Expand Up @@ -55,7 +55,7 @@ export default function ProductTable({
return (
<div className="space-y-4 ">
<div className="flex flex-wrap items-center gap-4">
<DataTableSearch searchKey="name" />
<DataTableSearch searchKey="Products" />
<FilterBox filterKey="status" title="Status" options={statusOptions} />
<FilterBox filterKey="role" title="Role" options={roleOptions} />
{isAnyFilterActive && (
Expand Down
9 changes: 8 additions & 1 deletion components/ui/table/data-table-search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,18 @@ export function DataTableSearch({ searchKey }: DataTableSearchProps) {
.withDefault('')
);

const [, setPage] = useQueryState('page', searchParams.page.withDefault(1));

const handleSearch = (value: string) => {
setSearchQuery(value || null);
setPage(1); // Reset page to 1 when search changes
};

return (
<Input
placeholder={`Search ${searchKey}...`}
value={searchQuery ?? ''}
onChange={(e) => setSearchQuery(e.target.value || null)}
onChange={(e) => handleSearch(e.target.value)}
className={cn('w-full md:max-w-sm', isLoading && 'animate-pulse')}
/>
);
Expand Down
32 changes: 23 additions & 9 deletions components/ui/table/data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ export function DataTable<TData, TValue>({
pageSize: pageSize
};

const pageCount = Math.ceil(totalItems / pageSize);

const handlePaginationChange = (
updaterOrValue:
| PaginationState
Expand All @@ -76,7 +78,7 @@ export function DataTable<TData, TValue>({
const table = useReactTable({
data,
columns,
pageCount: Math.ceil(totalItems / paginationState.pageSize),
pageCount: pageCount,
state: {
pagination: paginationState
},
Expand Down Expand Up @@ -142,13 +144,19 @@ export function DataTable<TData, TValue>({
<div className="flex flex-col items-center justify-end gap-2 space-x-2 py-4 sm:flex-row">
<div className="flex w-full items-center justify-between">
<div className="flex-1 text-sm text-muted-foreground">
Showing {paginationState.pageIndex * paginationState.pageSize + 1}{' '}
to{' '}
{Math.min(
(paginationState.pageIndex + 1) * paginationState.pageSize,
totalItems
)}{' '}
of {totalItems} entries
{totalItems > 0 ? (
<>
Showing{' '}
{paginationState.pageIndex * paginationState.pageSize + 1} to{' '}
{Math.min(
(paginationState.pageIndex + 1) * paginationState.pageSize,
totalItems
)}{' '}
of {totalItems} entries
</>
) : (
'No entries found'
)}
</div>
<div className="flex flex-col items-center gap-4 sm:flex-row sm:gap-6 lg:gap-8">
<div className="flex items-center space-x-2">
Expand Down Expand Up @@ -177,7 +185,13 @@ export function DataTable<TData, TValue>({
</div>
<div className="flex w-full items-center justify-between gap-2 sm:justify-end">
<div className="flex w-[150px] items-center justify-center text-sm font-medium">
Page {paginationState.pageIndex + 1} of {table.getPageCount()}
{totalItems > 0 ? (
<>
Page {paginationState.pageIndex + 1} of {table.getPageCount()}
</>
) : (
'No pages'
)}
</div>
<div className="flex items-center space-x-2">
<Button
Expand Down
11 changes: 11 additions & 0 deletions constants/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,17 @@ export type Employee = {
profile_picture?: string | null; // Profile picture can be a string (URL) or null (if no picture)
};

export type Product = {
photo_url: string;
name: string;
description: string;
created_at: string;
price: number;
id: number;
category: string;
updated_at: string;
};

export const navItems: NavItem[] = [
{
title: 'Dashboard',
Expand Down
2 changes: 1 addition & 1 deletion next.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
domains: ['utfs.io']
domains: ['utfs.io', 'api.slingacademy.com']
}
};

Expand Down
Loading

0 comments on commit 8acc01b

Please sign in to comment.