diff --git a/.env b/.env index e56d4f2..223c636 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -VITE_BACKEND_BASE_URL="http://localhost:9004/api" -VITE_APP_PORT="5175" \ No newline at end of file +VITE_BACKEND_BASE_URL="http://localhost:9001/api/v1" +VITE_APP_PORT="5173" \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 279d8af..ededdb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,12 +8,15 @@ "name": "dashboard-kyouid", "version": "0.0.0", "dependencies": { + "@reduxjs/toolkit": "^2.1.0", + "@tanstack/react-table": "^8.11.7", "axios": "^1.6.7", "framer-motion": "^11.0.3", "moment": "^2.30.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^5.0.1", + "react-redux": "^9.1.0", "react-responsive": "^9.0.2", "react-router-dom": "^6.21.3" }, @@ -662,6 +665,29 @@ "node": ">=14" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.1.0.tgz", + "integrity": "sha512-nfJ/b4ZhzUevQ1ZPKjlDL6CMYxO4o7ZL7OSsvSOxzT/EN11LsBDgTqP7aedHtBrFSVoK7oTP1SbMWUwGb30NLg==", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.0.1" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.14.2", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.14.2.tgz", @@ -1049,6 +1075,37 @@ "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", "dev": true }, + "node_modules/@tanstack/react-table": { + "version": "8.11.7", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.11.7.tgz", + "integrity": "sha512-ZbzfMkLjxUTzNPBXJYH38pv2VpC9WUA+Qe5USSHEBz0dysDTv4z/ARI3csOed/5gmlmrPzVUN3UXGuUMbod3Jg==", + "dependencies": { + "@tanstack/table-core": "8.11.7" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.11.7", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.11.7.tgz", + "integrity": "sha512-N3ksnkbPbsF3PjubuZCB/etTqvctpXWRHIXTmYfJFnhynQKjeZu8BCuHvdlLPpumKbA+bjY4Ay9AELYLOXPWBg==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -1059,13 +1116,13 @@ "version": "15.7.11", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", - "dev": true + "devOptional": true }, "node_modules/@types/react": { "version": "18.2.48", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz", "integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==", - "dev": true, + "devOptional": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1085,7 +1142,12 @@ "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "dev": true + "devOptional": true + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", @@ -1627,7 +1689,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "devOptional": true }, "node_modules/debug": { "version": "4.3.4", @@ -2582,6 +2644,15 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.3.tgz", + "integrity": "sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -3814,6 +3885,32 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-redux": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.0.tgz", + "integrity": "sha512-6qoDzIO+gbrza8h3hjMA9aq4nwVFCKFtY2iLxCtVT38Swyy2C/dJCGBXHeHLtx6qlg/8qzc2MrhOeduf5K32wQ==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "react-native": ">=0.69", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-responsive": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-9.0.2.tgz", @@ -3882,6 +3979,19 @@ "node": ">=8.10.0" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", @@ -3919,6 +4029,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/reselect": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz", + "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==" + }, "node_modules/resolve": { "version": "2.0.0-next.5", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", @@ -4686,6 +4801,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 3a3061d..132b788 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,15 @@ "preview": "vite preview" }, "dependencies": { + "@reduxjs/toolkit": "^2.1.0", + "@tanstack/react-table": "^8.11.7", "axios": "^1.6.7", "framer-motion": "^11.0.3", "moment": "^2.30.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^5.0.1", + "react-redux": "^9.1.0", "react-responsive": "^9.0.2", "react-router-dom": "^6.21.3" }, diff --git a/src/App.jsx b/src/App.jsx index 827467e..d33d880 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,33 +1,35 @@ -import { Route, Routes } from 'react-router-dom'; -import RootLayout from './layouts/RootLayout'; -import Users from './pages/Users'; -import Dashboard from './pages/Dashboard'; -import Products from './pages/Products'; -import Discount from './pages/Discount'; -import Order from './pages/Order'; -import Profile from './pages/Profile'; -import Settings from './pages/Settings'; -import AddProducts from './pages/AddProducts'; -import Health from './pages/health'; +import { Route, Routes } from "react-router-dom"; +import RootLayout from "./layouts/RootLayout"; +import Users from "./pages/Users"; +import Dashboard from "./pages/Dashboard"; +import Products from "./pages/Products"; +import Discount from "./pages/Discount"; +import Order from "./pages/Order"; +import Profile from "./pages/Profile"; +import Settings from "./pages/Settings"; +import AddProducts from "./pages/AddProducts"; +import Health from "./pages/health"; +import EditProducts from "./pages/EditProducts"; const App = () => { - return ( - <> - - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - - ); + return ( + <> + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + ); }; export default App; diff --git a/src/components/Box.jsx b/src/components/Box.jsx index ec5fb8c..018ae01 100644 --- a/src/components/Box.jsx +++ b/src/components/Box.jsx @@ -1,13 +1,15 @@ -import React from 'react'; +import React from "react"; const Box = ({ children, className }) => { - return ( - <> -
- {children} -
- - ); + return ( + <> +
+ {children} +
+ + ); }; export default Box; diff --git a/src/components/Button.jsx b/src/components/Button.jsx new file mode 100644 index 0000000..e6860a4 --- /dev/null +++ b/src/components/Button.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +const Button = ({ children, className, onClick }) => { + return ( + + ); +}; + +export default Button; diff --git a/src/components/Input.jsx b/src/components/Input.jsx new file mode 100644 index 0000000..9c497c1 --- /dev/null +++ b/src/components/Input.jsx @@ -0,0 +1,36 @@ +import React from 'react'; + +const Input = ({ + label, + type, + placeholder, + onChange, + name, + cols, + rows, + className = 'w-1/2 mt-1 mb-3 p-2 font-medium bg-slate-100 border shadow border-zinc-200 focus:outline-none rounded-md', +}) => { + return ( +
+ + {type === 'textarea' ? ( + + ) : ( + + )} +
+ ); +}; + +export default Input; diff --git a/src/layouts/RootLayout.jsx b/src/layouts/RootLayout.jsx index 8d9d141..f1f6ab8 100644 --- a/src/layouts/RootLayout.jsx +++ b/src/layouts/RootLayout.jsx @@ -5,7 +5,7 @@ function RootLayout({ children }) { return (
-
+
{children}
diff --git a/src/layouts/sidebar/index.jsx b/src/layouts/sidebar/index.jsx index 1f2f6d3..170f8b9 100644 --- a/src/layouts/sidebar/index.jsx +++ b/src/layouts/sidebar/index.jsx @@ -114,8 +114,7 @@ const Sidebar = () => { initial={{ x: isTabletMid ? -250 : 0 }} animate={open ? 'open' : 'closed'} className=" bg-white text-gray shadow-xl z-[999] max-w-[16rem] w-[16rem] - overflow-hidden md:relative fixed - h-screen " + overflow-hidden md:relative fixed h-screen " >
{ - return ( - - Add Products - - ); + const baseUrl = import.meta.env.VITE_BACKEND_BASE_URL; + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [status, setStatus] = useState(""); + const [price, setPrice] = useState(""); + const [stock, setStock] = useState(""); + const [minimumCredits, setMinimumCredits] = useState(""); + const [category, setCategory] = useState(""); + const [series, setSeries] = useState(""); + const [character, setCharacter] = useState(""); + const [manufacture, setManufacture] = useState(""); + const [images, setImages] = useState([]); + const [thumbnail, setThumbnail] = useState([]); + const thumbnailRef = useRef(null); + + const categories = [ + { value: "Nendoroid" }, + { value: "Scaled Figure" }, + { value: "Prize Figure" }, + { value: "Pop Up Parade" }, + { value: "Model Kit" }, + { value: "Figma" }, + { value: "Merchandise" }, + { value: "Plush" }, + { value: "Light Novel" }, + { value: "Manga" }, + { value: "" }, + ]; + // const handleImageChange = (e, index) => { + // const newImages = [...images]; + // newImages[index - 1] = e.target.files[0]; + // setImages(newImages); + // }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + try { + // Check if at least one image is selected + if (images.length === 0) { + alert("Please select at least one image"); + return; + } + + const formData = new FormData(); + + // Add product information to formData + formData.append("title", title); + formData.append("description", description); + formData.append("status", status); + formData.append("price", price); + formData.append("stock", stock); + formData.append("minimumCredits", minimumCredits); + formData.append("category", category); + formData.append("character", character); + formData.append("series", series); + formData.append("manufacture", manufacture); + + // Add thumbnail to formData + if (thumbnail) { + formData.append("thumbnail", thumbnail); + } + + // Add images to formData + for (let i = 0; i < images.length; i++) { + formData.append("image", images[i]); + } + + // Send product information and images in a single request + const createProduct = await axios.post(`${baseUrl}/products`, formData); + + console.log("Product created successfully:", createProduct.data); + alert("Product and images are successfully uploaded!"); + resetForm(); + } catch (error) { + console.error("Error creating product:", error); + } + }; + + const resetForm = () => { + setTitle(""); + setDescription(""); + setStatus(""); + setPrice(""); + setStock(""); + setMinimumCredits(""); + setCategory(""); + setCharacter(""); + setSeries(""); + setManufacture(""); + setThumbnail([]); + setImages([]); + thumbnailRef.current.value = ""; + }; + + return ( + <> + +
+
+ {/* information Product */} +
+

Informasi Produk

+
+ + setTitle(e.target.value)} + className="w-full px-3 py-2 placeholder-gray-400 border rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 shadow-sm" + /> +
+ +
+ + +
+
+ + setStock(e.target.value)} + className="w-32 px-3 py-2 placeholder-gray-400 border rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 shadow-sm" + /> +
+
+ + +
+ +
+ +