Data Tables
Install Dependencies
You will needto Add the <Table />
component to your project:
npx shadcn-ui@latest add table
and Also Add tanstack/react-table dependency:
npm install @tanstack/react-table
Folder Structure For the Route
Lets take an example i have this route called dashboard/inventory/categories
- columns.tsx
- page.tsx
So go ahead and Create the route and add those files
Folder Structure For the Components
So go ahead and Create these Components
- columns.tsx
(client component)
will contain our column definitions. - data-table.tsx
(client component)
will contain our<DataTable />
component. - page.tsx (server component) is where we'll fetch data and render our table.
Create the Page.tsx in the categories
in the Page we fetch the Data and also we need to send the Coumns and the Data to the DataTable
import React from "react";
import { columns } from "./columns";
import { Category } from "@prisma/client";
import DataTable from "@/components/DataTableComponents/DataTable";
import TableHeader from "../../../../components/dashboard/Tables/TableHeader";
import { getAllCategories } from "@/actions/categories";
export default async function page() {
const categories: Category[] = (await getAllCategories()) || [];
return (
<div className="p-8">
<TableHeader
title="Categories"
linkTitle="Add Category"
href="/dashboard/categories/new"
data={categories}
model="category"
/>
<div className="py-8">
<DataTable data={categories} columns={columns} />
</div>
</div>
);
}
Create the Columns.tsx
Make sure to Install all the missing shad components
"use client";
import Image from "next/image";
import { ArrowUpDown, MoreHorizontal } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import DateColumn from "@/components/DataTableColumns/DateColumn";
import ImageColumn from "@/components/DataTableColumns/ImageColumn";
import SortableColumn from "@/components/DataTableColumns/SortableColumn";
import { ColumnDef } from "@tanstack/react-table";
import ActionColumn from "@/components/DataTableColumns/ActionColumn";
import { Category } from "@prisma/client";
export const columns: ColumnDef<Category>[] = [
{
id: "select",
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "title",
header: ({ column }) => <SortableColumn column={column} title="Title" />,
},
{
accessorKey: "imageUrl",
header: "Category Image",
cell: ({ row }) => <ImageColumn row={row} accessorKey="imageUrl" />,
},
{
accessorKey: "createdAt",
header: "Date Created",
cell: ({ row }) => <DateColumn row={row} accessorKey="createdAt" />,
},
{
id: "actions",
cell: ({ row }) => {
const category = row.original;
return (
<ActionColumn
row={row}
model="category"
editEndpoint={`categories/update/${category.id}`}
id={category.id}
/>
);
},
},
];
Create the Components in the DataTableComponents Folder
ActionColumn.tsx
"use client";
import React from "react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog";
import { MoreHorizontal, Pencil, Trash } from "lucide-react";
import { deleteCategory } from "@/actions/categories";
import toast from "react-hot-toast";
import Link from "next/link";
type ActionColumnProps = {
row: any;
model: any;
editEndpoint: string;
id: string | undefined;
// revPath: string;
};
export default function ActionColumn({
row,
model,
editEndpoint,
id = "",
}: ActionColumnProps) {
const isActive = row.isActive;
async function handleDelete() {
try {
if (model === "category") {
const res = await deleteCategory(id);
if (res?.ok) {
window.location.reload();
}
toast.success(`${model} Deleted Successfully`);
}
} catch (error) {
console.log(error);
toast.error("Category Couldn't be deleted");
}
}
return (
<DropdownMenu>
<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>
<DropdownMenuSeparator />
<AlertDialog>
<AlertDialogTrigger asChild>
{/* <DropdownMenuItem className="text-red-600 hover:text-red-700 transition-all duration-500 cursor-pointer">
</DropdownMenuItem> */}
<Button
variant={"ghost"}
size={"sm"}
className="text-red-600 hover:text-red-700 transition-all duration-500 cursor-pointer "
>
<Trash className="w-4 h-4 mr-2 flex-shrink-0" />
<span>Delete</span>
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete this{" "}
{model}.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button variant={"destructive"} onClick={() => handleDelete()}>
Permanently Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* <DropdownMenuItem
className="text-red-600 hover:text-red-700 transition-all duration-500 cursor-pointer"
onClick={() => handleDelete()}
>
<Trash className="w-4 h-4 mr-2 flex-shrink-0" />
<span>Delete</span>
</DropdownMenuItem> */}
<DropdownMenuItem>
<Link href={editEndpoint} className="flex item gap-2">
<Pencil className="w-4 h-4 " />
<span>Edit</span>
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
DateColumn.tsx
import { getNormalDate } from "@/lib/getNormalDate";
import React from "react";
export default function DateColumn({
row,
accessorKey,
}: {
row: any;
accessorKey: any;
}) {
const createdAt = row.getValue(`${accessorKey}`);
const date = getNormalDate(createdAt);
const originalDate = new Date(createdAt);
const day = originalDate.getDate();
const month = originalDate.toLocaleString("default", { month: "short" });
const year = originalDate.getFullYear();
const formatted = `${day}th ${month} ${year}`;
// console.log(imageUrl);
return <div className="">{date}</div>;
}
ImageColumn.tsx
import Image from "next/image";
import React from "react";
export default function ImageColumn({
row,
accessorKey,
}: {
row: any;
accessorKey: any;
}) {
const imageUrl = row.getValue(`${accessorKey}`);
// const thum = row.getValue(`${accessorKey}`);
// console.log(imageUrl);
return (
<div className="shrink-0">
<Image
alt={`${accessorKey}`}
className="aspect-square rounded-md object-cover"
height="50"
src={imageUrl ?? ""}
width="50"
/>
</div>
);
}
SortableColumn.tsx
import React from "react";
import { Button } from "@/components/ui/button";
import { ArrowUpDown } from "lucide-react";
export default function SortableColumn({
column,
title,
}: {
column: any;
title: string;
}) {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
{title}
<ArrowUpDown className="ml-2 h-4 w-4" />
</Button>
);
}
Create the DataTable components
DataTable.tsx
"use client";
import * as React from "react";
import {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useState } from "react";
import SearchBar from "./SearchBar";
import { DataTableViewOptions } from "./DataTableViewOptions";
import { Button } from "../ui/button";
import { ListFilter } from "lucide-react";
import DateFilters from "./DateFilters";
import DateRangeFilter from "./DateRangeFilter";
import { DataTablePagination } from "./DataTablePagination";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
}
export default function DataTable<TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) {
const [rowSelection, setRowSelection] = useState({});
const [columnVisibility, setColumnVisibility] = useState({});
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[]
);
const [searchResults, setSearchResults] = useState(data);
const [filteredData, setFilteredData] = useState(data);
const [sorting, setSorting] = React.useState<SortingState>([]);
const [isSearch, setIsSearch] = useState(true);
// console.log(isSearch);
const table = useReactTable({
data: isSearch ? searchResults : filteredData,
columns,
state: {
sorting,
columnVisibility,
rowSelection,
columnFilters,
},
enableRowSelection: true,
onRowSelectionChange: setRowSelection,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
});
// console.log(searchResults);
return (
<div className="space-y-4">
<div className="flex justify-between items-center gap-8">
<div className="flex-1 w-full">
<SearchBar
data={data}
onSearch={setSearchResults}
setIsSearch={setIsSearch}
/>
</div>
<div className="flex items-center gap-2 ">
<DateRangeFilter
data={data}
onFilter={setFilteredData}
setIsSearch={setIsSearch}
/>
<DateFilters
data={data}
onFilter={setFilteredData}
setIsSearch={setIsSearch}
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="h-8 gap-1">
<ListFilter className="h-3.5 w-3.5" />
<span className="sr-only sm:not-sr-only sm:whitespace-nowrap">
Filter
</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Filter by</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuCheckboxItem checked>
Active
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem>Draft</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem>Archived</DropdownMenuCheckboxItem>
</DropdownMenuContent>
</DropdownMenu>
<DataTableViewOptions table={table} />
</div>
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<DataTablePagination table={table} />
</div>
);
}
DataTablePagination.tsx
"use client";
import {
ChevronLeftIcon,
ChevronRightIcon,
DoubleArrowLeftIcon,
DoubleArrowRightIcon,
} from "@radix-ui/react-icons";
import { Table } from "@tanstack/react-table";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
interface DataTablePaginationProps<TData> {
table: Table<TData>;
}
import { Button } from "@/components/ui/button";
export function DataTablePagination<TData>({
table,
}: DataTablePaginationProps<TData>) {
return (
<div className="flex items-center justify-between px-2">
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{" "}
{table.getFilteredRowModel().rows.length} row(s) selected.
</div>
<div className="flex items-center space-x-6 lg:space-x-8">
<div className="flex items-center space-x-2">
<p className="text-sm font-medium">Rows per page</p>
<Select
value={`${table.getState().pagination.pageSize}`}
onValueChange={(value) => {
table.setPageSize(Number(value));
}}
>
<SelectTrigger className="h-8 w-[70px]">
<SelectValue placeholder={table.getState().pagination.pageSize} />
</SelectTrigger>
<SelectContent side="top">
{[10, 20, 30, 40, 50].map((pageSize) => (
<SelectItem key={pageSize} value={`${pageSize}`}>
{pageSize}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
Page {table.getState().pagination.pageIndex + 1} of{" "}
{table.getPageCount()}
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to first page</span>
<DoubleArrowLeftIcon className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="h-8 w-8 p-0"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to previous page</span>
<ChevronLeftIcon className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="h-8 w-8 p-0"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to next page</span>
<ChevronRightIcon className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to last page</span>
<DoubleArrowRightIcon className="h-4 w-4" />
</Button>
</div>
</div>
</div>
);
}
DataTableToolbar.tsx
"use client";
import { Cross2Icon } from "@radix-ui/react-icons";
import { Table } from "@tanstack/react-table";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
export function DataTableToolbar({ table }: { table: any }) {
return <div className="flex items-center justify-between"></div>;
}
DataTableViewOptions.tsx
"use client";
import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu";
import { MixerHorizontalIcon } from "@radix-ui/react-icons";
import { Table } from "@tanstack/react-table";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
interface DataTablePaginationProps<TData> {
table: Table<TData>;
}
export function DataTableViewOptions<TData>({
table,
}: DataTablePaginationProps<TData>) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="sm"
className="ml-auto hidden h-8 lg:flex"
>
<MixerHorizontalIcon className="mr-2 h-4 w-4" />
View
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-[150px]">
<DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
<DropdownMenuSeparator />
{table
.getAllColumns()
.filter(
(column) =>
typeof column.accessorFn !== "undefined" && column.getCanHide()
)
.map((column) => {
return (
<DropdownMenuCheckboxItem
key={column.id}
className="capitalize"
checked={column.getIsVisible()}
onCheckedChange={(value) => column.toggleVisibility(!!value)}
>
{column.id}
</DropdownMenuCheckboxItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
);
}
DateFilters.tsx
"use client";
import {
filterByLast7Days,
filterByThisMonth,
filterByThisYear,
filterByToday,
filterByYesterday,
} from "@/lib/dateFilters";
import React, { useState } from "react";
import Select from "react-tailwindcss-select";
import { SelectValue } from "react-tailwindcss-select/dist/components/type";
export default function DateFilters({
data,
onFilter,
setIsSearch,
}: {
data: any[];
onFilter: any;
setIsSearch: any;
}) {
const options = [
{ value: "life", label: "Life time" },
{ value: "today", label: "Today" },
{ value: "last-7-days", label: "Last 7 days" },
{ value: "month", label: "This Month" },
{ value: "year", label: "This year" },
];
const [selectedFilter, setSelectedFilter] = useState<SelectValue>(options[0]);
const handleChange = (item: any) => {
const valueString = item!.value;
setSelectedFilter(item);
setIsSearch(false);
if (valueString === "life") {
onFilter(data);
} else if (valueString === "today") {
const filteredData = filterByToday(data);
onFilter(filteredData);
} else if (valueString === "yesterday") {
const filteredData = filterByYesterday(data);
onFilter(filteredData);
} else if (valueString === "last-7-days") {
const filteredData = filterByLast7Days(data);
onFilter(filteredData);
} else if (valueString === "month") {
const filteredData = filterByThisMonth(data);
onFilter(filteredData);
} else if (valueString === "year") {
const filteredData = filterByThisYear(data);
onFilter(filteredData);
}
console.log("value:", valueString);
// setAnimal(value);
// onFilter(filteredData);
};
return (
<div>
<Select
value={selectedFilter}
onChange={handleChange}
options={options}
primaryColor={"indigo"}
/>
</div>
);
}
DateRangeFilter.tsx
"use client";
import {
filterByDateRange,
filterByLast7Days,
filterByThisMonth,
filterByThisYear,
filterByToday,
filterByYesterday,
} from "@/lib/dateFilters";
import React, { useState } from "react";
import Select from "react-tailwindcss-select";
import { SelectValue } from "react-tailwindcss-select/dist/components/type";
import { addDays, format } from "date-fns";
import { Calendar as CalendarIcon } from "lucide-react";
import { DateRange } from "react-day-picker";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
export default function DateRangeFilter({
data,
onFilter,
setIsSearch,
className,
}: {
data: any[];
onFilter: any;
setIsSearch: any;
className?: string;
}) {
const [date, setDate] = React.useState<DateRange | undefined>({
from: new Date(2024, 0, 20),
to: addDays(new Date(2024, 0, 20), 20),
});
// console.log(date);
const handleChange = (selectedDate: any) => {
console.log(selectedDate);
setDate(selectedDate);
setIsSearch(false);
const startDate = selectedDate.from;
const endDate = selectedDate.to;
const filteredData = filterByDateRange(data, startDate, endDate);
onFilter(filteredData);
};
return (
<div className={cn("grid gap-2", className)}>
<Popover>
<PopoverTrigger asChild>
<Button
id="date"
variant={"outline"}
className={cn(
"w-[300px] justify-start text-left font-normal",
!date && "text-muted-foreground"
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date?.from ? (
date.to ? (
<>
{format(date.from, "LLL dd, y")} -{" "}
{format(date.to, "LLL dd, y")}
</>
) : (
format(date.from, "LLL dd, y")
)
) : (
<span>Pick a date</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
initialFocus
mode="range"
defaultMonth={date?.from}
selected={date}
onSelect={(value) => handleChange(value)}
numberOfMonths={2}
/>
</PopoverContent>
</Popover>
</div>
);
}
SearchBar.tsx
import { Search } from "lucide-react";
import React, { useState } from "react";
export default function SearchBar({
data,
onSearch,
setIsSearch,
}: {
data: any[];
onSearch: any;
setIsSearch: any;
}) {
const [searchTerm, setSearchTerm] = useState("");
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(e.target.value);
const filteredData = data.filter((item: any) =>
Object.values(item).some(
(value: any) =>
value &&
value.toString().toLowerCase().includes(e.target.value.toLowerCase())
)
);
setIsSearch(true);
onSearch(filteredData);
};
return (
<div className="flex justify-between items-center gap-8 w-full">
<div className="mt-2 relativeclea">
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<Search className="text-slate-300 w-4 h-4" />
</div>
<input
id="search"
name="search"
type="text"
autoComplete="search"
value={searchTerm}
onChange={handleSearch}
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 pl-8"
/>
</div>
</div>
);
}
TableHeader.tsx
"use client";
import { Button } from "@/components/ui/button";
import * as XLSX from "xlsx";
import { ScrollArea } from "@/components/ui/scroll-area";
import { ExcelCategoryProps, ProductProps, SelectOption } from "@/types/types";
import {
Check,
CloudUpload,
Delete,
File,
ListFilter,
Loader2,
PlusCircle,
Search,
X,
} from "lucide-react";
import Link from "next/link";
import React, { useState } from "react";
import { RiFileExcel2Line } from "react-icons/ri";
import { SiMicrosoftexcel } from "react-icons/si";
import Select from "react-tailwindcss-select";
import {
Options,
SelectValue,
} from "react-tailwindcss-select/dist/components/type";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { DialogDemo } from "./Button";
import { formatBytes } from "@/lib/formatBytes";
import { generateSlug } from "@/lib/generateSlug";
import { createBulkCategories } from "@/actions/category";
import toast from "react-hot-toast";
import exportDataToExcel from "@/lib/exportDataToExcel";
import { createBulkBrands } from "@/actions/brand";
import { createBulkWarehouses } from "@/actions/warehouse";
import { createBulkSuppliers } from "@/actions/supplier";
import { createBulkUnits } from "@/actions/unit";
import { createBulkProducts } from "@/actions/products";
type TableHeaderProps = {
title: string;
href: string;
linkTitle: string;
data: any;
model: string;
showImport?: boolean;
};
export default function TableHeader({
title,
href,
linkTitle,
data,
model,
showImport = true,
}: TableHeaderProps) {
const [status, setStatus] = useState<SelectValue>(null);
const [date, setDate] = useState<SelectValue>(null);
const [excelFile, setExcelFile] = useState<File | null>(null);
const [jsonData, setJsonData] = useState("");
const [preview, setPreview] = useState(false);
const [loading, setLoading] = useState(false);
const [uploadSuccess, setUploadSuccess] = useState(false);
let excelDownload = "#";
if (model === "category") {
excelDownload = "/Categories.xlsx";
} else if (model === "brand") {
excelDownload = "/Brands.xlsx";
} else if (model === "warehouse") {
excelDownload = "/Warehouses.xlsx";
} else if (model === "supplier") {
excelDownload = "/Suppliers.xlsx";
} else if (model === "unit") {
excelDownload = "/Units.xlsx";
} else if (model === "product") {
excelDownload = "/Products.xlsx";
}
console.log(excelFile);
const options: Options = [
{ value: "true", label: "Active" },
{ value: "false", label: "Disabled" },
];
const dateOptions: Options = [
{ value: "lastMonth", label: "Last Month" },
{ value: "thisMonth", label: "This Month" },
];
const handleStatusChange = (item: SelectValue) => {
console.log("value:", item);
setStatus(item);
};
const handleDateChange = (item: SelectValue) => {
console.log("value:", item);
setDate(item);
};
function previewData() {
setPreview(true);
if (excelFile) {
const reader = new FileReader();
reader.onload = (e) => {
const data = e.target?.result;
if (data) {
const workbook = XLSX.read(data, { type: "binary" });
// SheetName
const sheetName = workbook.SheetNames[0];
// Worksheet
const workSheet = workbook.Sheets[sheetName];
// Json
const json = XLSX.utils.sheet_to_json(workSheet);
setJsonData(JSON.stringify(json, null, 2));
}
};
reader.readAsBinaryString(excelFile);
}
}
function saveData() {
setPreview(false);
if (excelFile) {
const reader = new FileReader();
reader.onload = async (e) => {
const data = e.target?.result;
if (data) {
const workbook = XLSX.read(data, { type: "binary" });
// SheetName
const sheetName = workbook.SheetNames[0];
// Worksheet
const workSheet = workbook.Sheets[sheetName];
// Json
const json = XLSX.utils.sheet_to_json(workSheet);
setJsonData(JSON.stringify(json, null, 2));
try {
setLoading(true);
if (model === "category") {
const categories = json.map((item: any) => {
return {
title: item.Title,
slug: generateSlug(item.Title),
description: item.Description,
imageUrl: item.Image,
mainCategoryId: item.mainCategoryId,
status: true,
};
});
await createBulkCategories(categories);
} else if (model === "brand") {
const brands = json.map((item: any) => {
return {
title: item.Title,
slug: generateSlug(item.Title),
logo: item.Logo,
status: true,
};
});
await createBulkBrands(brands);
// console.log(brands);
} else if (model === "warehouse") {
const warehouses = json.map((item: any) => {
return {
name: item.name,
slug: generateSlug(item.name),
logo: item.logo,
country: item.country,
city: item.city,
phone: item.phone,
email: item.email,
zipCode: item.zipCode,
contactPerson: item.contactPerson,
status: true,
};
});
await createBulkWarehouses(warehouses);
// console.log(warehouses);
} else if (model === "supplier") {
const suppliers = json.map((item: any) => {
return {
name: item.name,
imageUrl: item.imageUrl,
country: item.country,
city: item.city,
phone: String(item.phone),
email: item.email,
state: item.state,
companyName: item.companyName,
vatNumber: String(item.vatNumber),
address: item.address,
postalCode: String(item.postalCode),
status: true,
};
});
await createBulkSuppliers(suppliers);
// console.log(suppliers);
} else if (model === "unit") {
const units = json.map((item: any) => {
return {
title: item.title,
abbreviation: item.abbreviation,
};
});
await createBulkUnits(units);
// console.log(brands);
} else if (model === "product") {
const products = json.map((item: any) => {
return {
name: item.name,
slug: generateSlug(item.name),
productCode: item.productCode,
stockQty: item.stockQty,
warehouseId: item.warehouseId,
brandId: item.brandId,
supplierId: item.supplierId,
categoryId: item.categoryId,
unitId: item.unitId,
productCost: item.productCost,
productPrice: item.productPrice,
alertQty: item.alertQty,
productTax: item.productTax,
productThumbnail: item.productThumbnail,
taxMethod: item.taxMethod,
status: true,
productImages: [...item.productThumbnail],
productDetails: item.productDetails,
};
});
await createBulkProducts(products as any);
// console.log(brands);
}
setLoading(false);
setUploadSuccess(true);
// window.location.reload();
// toast.success("All Data Synced Successfully with No errors 👍");
} catch (error) {
setUploadSuccess(false);
setLoading(false);
toast.error("Something went wrong, Please Try again 😢");
console.log(error);
}
}
};
reader.readAsBinaryString(excelFile);
}
}
function handleExportData() {
console.log("data exported");
const today = new Date();
const filename = `Exported ${title} ${today.toDateString()}`;
// console.log(filename);
exportDataToExcel(data, filename);
}
return (
<div className=" mb-3">
<div className="flex justify-between items-center border-b border-gray-200 dark:border-gray-600 py-3">
<h2 className="scroll-m-20 text-2xl font-semibold tracking-tight first:mt-0">
{title}
</h2>
<div className="ml-auto flex items-center gap-2">
<Button
onClick={handleExportData}
size="sm"
variant="outline"
className="h-8 gap-1"
>
<SiMicrosoftexcel className="h-3.5 w-3.5" />
<span className="sr-only sm:not-sr-only sm:whitespace-nowrap">
Export
</span>
</Button>
{showImport && (
<Dialog>
<DialogTrigger asChild>
<Button
onClick={() => setUploadSuccess(false)}
size="sm"
variant="outline"
className="h-8 gap-1"
>
<RiFileExcel2Line className="h-3.5 w-3.5" />
<span className="sr-only sm:not-sr-only sm:whitespace-nowrap">
Import
</span>
</Button>
</DialogTrigger>
{loading ? (
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Excel Upload</DialogTitle>
<DialogDescription className="text-xs">
You can Bring all your Data from excel, Please Download
the Sample file First to Make Sure you have Data Columns
Named Correctly
</DialogDescription>
</DialogHeader>
<div className="h-60 w-full rounded-md border flex items-center justify-center">
<Button disabled className="items-center">
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Syncing Data Please wait ...
</Button>
</div>
{!loading && (
<DialogFooter className="justify-between ">
{preview ? (
<Button
onClick={() => setPreview(false)}
variant={"outline"}
type="button"
>
Stop Preview
</Button>
) : (
<Button
onClick={previewData}
variant={"outline"}
type="button"
>
Preview
</Button>
)}
<Button onClick={saveData} type="button">
Save Data
</Button>
</DialogFooter>
)}
</DialogContent>
) : (
<>
{uploadSuccess ? (
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Excel Upload</DialogTitle>
<DialogDescription className="text-xs">
You can Bring all your Data from excel, Please
Download the Sample file First to Make Sure you have
Data Columns Required
</DialogDescription>
</DialogHeader>
<div className="h-72 w-full rounded-md border flex items-center justify-center flex-col">
<div className="flex items-center justify-center w-24 h-24 bg-green-100 rounded-full">
<Check />
</div>
<h2 className="text-xs pt-2 px-8 text-center">
Data Synced Successfully. You can close the Window
</h2>
</div>
<DialogFooter className="justify-between ">
<DialogClose asChild>
<Button
onClick={() => window.location.reload()}
type="button"
variant="secondary"
>
Close
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
) : (
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>Excel Upload</DialogTitle>
<DialogDescription className="text-xs">
You can Bring all your Data from excel, Please
Download the Sample file First to Make Sure you have
Data Columns Required
</DialogDescription>
</DialogHeader>
{preview && jsonData ? (
<ScrollArea className="h-72 w-full rounded-md border">
<div className="p-4">
<pre>{jsonData}</pre>
</div>
</ScrollArea>
) : (
<div className="grid gap-4 py-4">
<Button asChild variant="outline">
<Link href={excelDownload} download>
Download {model} Sample Data
</Link>
</Button>
<div className="flex items-center justify-center w-full">
<label
htmlFor="dropzone-file"
className="flex lg:flex-col flex-row items-center justify-center w-full h-16 lg:h-36 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-bray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600"
>
<div className="flex flex-row lg:flex-col items-center justify-center pt-5 pb-6 gap-4 lg:gap-0">
<CloudUpload className="w-8 h-8 mb-4 text-gray-500 dark:text-gray-400" />
<p className="lg:mb-2 text-sm text-gray-500 dark:text-gray-400">
<span className="font-semibold">
Click to upload
</span>{" "}
<span className="hidden lg:inline">
{" "}
or drag and drop
</span>
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
Only Excel Files (.xlsx)
</p>
</div>
<input
id="dropzone-file"
accept=".xls,.xlsx"
type="file"
className="hidden"
onChange={(e) =>
setExcelFile(
e.target.files ? e.target.files[0] : null
)
}
/>
</label>
</div>
{excelFile && (
<div className="flex items-center shadow-lg rounded-md lg:py-3 py-2 px-6 bg-slate-100 dark:bg-slate-800 justify-between">
<div className="flex items-center gap-3">
<div className="w-8 h-8 lg:w-14 lg:h-14 p-2 lg:p-4 bg-slate-300 dark:bg-slate-500 rounded flex items-center justify-center flex-shrink-0">
<RiFileExcel2Line className="h-4 w-4" />
</div>
<div className="">
<p className="text-sm font-semibold">
{excelFile.name}
</p>
<span className="text-xs">
{formatBytes(excelFile.size)}
</span>
</div>
</div>
<button onClick={() => setExcelFile(null)}>
<X className="text-slate-600 w-5 h-5" />
</button>
</div>
)}
</div>
)}
<DialogFooter className="justify-between ">
{preview ? (
<Button
onClick={() => setPreview(false)}
variant={"outline"}
type="button"
>
Stop Preview
</Button>
) : (
<Button
onClick={previewData}
variant={"outline"}
type="button"
>
Preview
</Button>
)}
<Button onClick={saveData} type="button">
Save Data
</Button>
</DialogFooter>
</DialogContent>
)}
</>
)}
</Dialog>
)}
<Button size="sm" asChild className="h-8 gap-1">
<Link href={href}>
<PlusCircle className="h-3.5 w-3.5" />
<span className="sr-only sm:not-sr-only sm:whitespace-nowrap">
{linkTitle}
</span>
</Link>
</Button>
</div>
</div>
</div>
);
}