From 289ae236f56c11c6e37fb56eb4bdb3408103861d Mon Sep 17 00:00:00 2001 From: Eric Kibuchi Date: Tue, 23 Jul 2024 16:14:41 +0300 Subject: [PATCH 01/13] feat: inventory page implemented --- package.json | 6 +- .../shop/inventory/InventoryCardComponent.jsx | 29 ++ .../shop/inventory/InventoryPreviewColumn.jsx | 33 +++ .../shop/inventory/InventoryPreviewTable.jsx | 82 ++++++ src/components/admin/shop/inventory/Modal.jsx | 249 ++++++++++++++++++ src/index.js | 2 + .../admin/shop/inventory/InventoryReport.jsx | 72 +++++ src/pages/admin/shop/inventory/data.js | 58 ++++ src/router/index.jsx | 9 + 9 files changed, 538 insertions(+), 2 deletions(-) create mode 100644 src/components/admin/shop/inventory/InventoryCardComponent.jsx create mode 100644 src/components/admin/shop/inventory/InventoryPreviewColumn.jsx create mode 100644 src/components/admin/shop/inventory/InventoryPreviewTable.jsx create mode 100644 src/components/admin/shop/inventory/Modal.jsx create mode 100644 src/pages/admin/shop/inventory/InventoryReport.jsx create mode 100644 src/pages/admin/shop/inventory/data.js diff --git a/package.json b/package.json index 23a03116..2727808e 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dependencies": { "@emailjs/browser": "^3.11.0", "@headlessui/react": "^2.1.2", + "@hookform/resolvers": "^3.9.0", "@hookstate/core": "^4.0.1", "@tanstack/react-query": "^5.22.2", "@tanstack/react-query-devtools": "^5.24.0", @@ -36,7 +37,7 @@ "react-dom": "^18.2.0", "react-fast-marquee": "^1.6.2", "react-helmet-async": "^2.0.5", - "react-hook-form": "^7.48.2", + "react-hook-form": "^7.52.1", "react-hot-toast": "^2.4.1", "react-icons": "^5.2.1", "react-lazy-load-image-component": "^1.6.0", @@ -48,7 +49,8 @@ "tailwind-merge": "^2.3.0", "tailwind-scrollbar-hide": "^1.1.7", "tailwindcss-animate": "^1.0.7", - "yet-another-react-lightbox": "^3.15.6" + "yet-another-react-lightbox": "^3.15.6", + "yup": "^1.4.0" }, "devDependencies": { "@playwright/test": "^1.44.1", diff --git a/src/components/admin/shop/inventory/InventoryCardComponent.jsx b/src/components/admin/shop/inventory/InventoryCardComponent.jsx new file mode 100644 index 00000000..339ec658 --- /dev/null +++ b/src/components/admin/shop/inventory/InventoryCardComponent.jsx @@ -0,0 +1,29 @@ +function InventoryCardComponent({ + title = "All Orders", + number = 500, + categories + }) { + + return ( +
+
+
+
+ {title} + {number} + {categories && ( +
+

{categories} Categories

+
+ )} + + +
+ +
+
+
+ ); + } + +export default InventoryCardComponent; \ No newline at end of file diff --git a/src/components/admin/shop/inventory/InventoryPreviewColumn.jsx b/src/components/admin/shop/inventory/InventoryPreviewColumn.jsx new file mode 100644 index 00000000..7f148bb0 --- /dev/null +++ b/src/components/admin/shop/inventory/InventoryPreviewColumn.jsx @@ -0,0 +1,33 @@ +const columns = [ + { + accessorKey: "id", + header: "Item ID", + }, + { + accessorKey: "name", + header: "Name", + }, + { + accessorKey: "items-sold", + header: "Items Sold", + }, + { + accessorKey: "items-remaining", + header: "Items Remaining", + }, + + { + id: "actions", + enableHiding: false, + cell: () => ( +
+ +
+ ), + }, + ]; + + export default columns; + \ No newline at end of file diff --git a/src/components/admin/shop/inventory/InventoryPreviewTable.jsx b/src/components/admin/shop/inventory/InventoryPreviewTable.jsx new file mode 100644 index 00000000..55971060 --- /dev/null +++ b/src/components/admin/shop/inventory/InventoryPreviewTable.jsx @@ -0,0 +1,82 @@ +import { + flexRender, + getCoreRowModel, + useReactTable, + } from "@tanstack/react-table"; + + import PropTypes from "prop-types"; + import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, + } from "../../../ui/table"; + + function InventoryPreviewTable({ columns, data }) { + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + return ( + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ))} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+ ); + } + + export default InventoryPreviewTable; + + InventoryPreviewTable.propTypes = { + columns: PropTypes.arrayOf( + PropTypes.shape({ + accessorKey: PropTypes.string, + cell: PropTypes.func, + header: PropTypes.func, + id: PropTypes.string, + }) + ).isRequired, + // eslint-disable-next-line react/forbid-prop-types + data: PropTypes.arrayOf(PropTypes.object).isRequired, + }; + \ No newline at end of file diff --git a/src/components/admin/shop/inventory/Modal.jsx b/src/components/admin/shop/inventory/Modal.jsx new file mode 100644 index 00000000..d0ee4ac2 --- /dev/null +++ b/src/components/admin/shop/inventory/Modal.jsx @@ -0,0 +1,249 @@ +import { yupResolver } from "@hookform/resolvers/yup"; +import { useForm } from "react-hook-form"; +import { IoMdClose } from "react-icons/io"; +import * as yup from "yup"; + +const validFileExtensions = { + image: ["jpg", "gif", "png", "jpeg", "svg", "webp"], +}; + +const MAX_FILE_SIZE = 10485760; + +// get allowed file extensions +function getAllowedExt(type) { + return validFileExtensions[type].map((e) => `.${e}`).toString(); +} + +// validate file type and extension +function isValidFileType(fileName, fileType) { + if (!fileName) return false; + const extension = fileName.split(".").pop().toLowerCase(); + return validFileExtensions[fileType].includes(extension); +} + +// form validation schema in yup +const schema = yup.object().shape({ + price: yup.number().required("Price is required"), + size: yup.string().required("Size is required"), + category: yup.string().required("Category is required"), + color: yup.string().required("Color is required"), + description: yup.string().required("Description is required"), + image: yup + .mixed() + .required("Image is required") + .test("is-valid-type", "Not a valid image type", (value) => + isValidFileType(value && value[0]?.name, "image") + ) + .test("is-valid-size", "Max allowed size is 10MB", (value) => + value && value[0]?.size <= MAX_FILE_SIZE + ), +}); + +export default function Modal({ showModal, onClose }) { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + const onSubmit = (data) => console.log(data); + + const allowedExts = getAllowedExt("image"); + + return ( + <> +
+ {showModal ? ( + <> +
+
+ {/* content */} +
+
+
+ {/* header */} +
+

+ Add Item +

+ +
+ {/* body */} +
+
+
+ + + {errors.price && ( +

price is required

+ )} +
+
+ + + {errors.size && ( +

{errors.size.message}

+ )} +
+
+ + + {errors.category && ( +

{errors.category.message}

+ )} +
+
+ + + {errors.color && ( +

{errors.color.message}

+ )} +
+
+ +