diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 4dcb439..7fb53a1 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -16,5 +16,6 @@ module.exports = {
'warn',
{ allowConstantExport: true },
],
+ 'react/prop-types': 'off',
},
-}
+};
diff --git a/index.html b/index.html
index 0c589ec..5fbf71e 100644
--- a/index.html
+++ b/index.html
@@ -1,10 +1,10 @@
-
+
- Vite + React
+ BDC Shoe Dashboard
diff --git a/package-lock.json b/package-lock.json
index b23a001..bb17f03 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,8 @@
"@mantine/core": "^6.0.18",
"@mantine/form": "^6.0.18",
"@mantine/hooks": "^6.0.18",
+ "@mantine/modals": "^6.0.20",
+ "@tabler/icons-react": "^2.30.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.14.2"
@@ -1048,19 +1050,19 @@
}
},
"node_modules/@mantine/core": {
- "version": "6.0.18",
- "resolved": "https://registry.npmjs.org/@mantine/core/-/core-6.0.18.tgz",
- "integrity": "sha512-i2Powv0sQYdW8vjJIC7xeo6XJDfGRpavCcQ8JpNVsCoC+db5RToEIsQc3jpgYBPY1/iWeUJuKPMMkQA5V+xMMA==",
+ "version": "6.0.20",
+ "resolved": "https://registry.npmjs.org/@mantine/core/-/core-6.0.20.tgz",
+ "integrity": "sha512-v/51klCP3nRZIIv0tl8zvOHpksbEK6+AXr0PcQzLnV0KY9xEbV6mh6mEM/w6ygigXRMTQ3oSK85fX1JYg9xC6Q==",
"dependencies": {
"@floating-ui/react": "^0.19.1",
- "@mantine/styles": "6.0.18",
- "@mantine/utils": "6.0.18",
+ "@mantine/styles": "6.0.20",
+ "@mantine/utils": "6.0.20",
"@radix-ui/react-scroll-area": "1.0.2",
"react-remove-scroll": "^2.5.5",
"react-textarea-autosize": "8.3.4"
},
"peerDependencies": {
- "@mantine/hooks": "6.0.18",
+ "@mantine/hooks": "6.0.20",
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
@@ -1078,17 +1080,31 @@
}
},
"node_modules/@mantine/hooks": {
- "version": "6.0.18",
- "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-6.0.18.tgz",
- "integrity": "sha512-i9RxEtC+7t5qG5Gn5SqTHkHxH9MSsGi9mQebt/GitX9+d9BVBwYLqutlDlHvb8bvDteuYphX5VWLSnJXT4Pivw==",
+ "version": "6.0.20",
+ "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-6.0.20.tgz",
+ "integrity": "sha512-Sys2qr6KwyNjAWgzm94F9rGG94l709G69pO3iofQXwzKX/fZushk1NMIt5g9De9F+qXm+67wa0DpA0QWLNLxlg==",
"peerDependencies": {
"react": ">=16.8.0"
}
},
+ "node_modules/@mantine/modals": {
+ "version": "6.0.20",
+ "resolved": "https://registry.npmjs.org/@mantine/modals/-/modals-6.0.20.tgz",
+ "integrity": "sha512-KcAauiVPuXzqph19f3S5+y35QIW8T2zgGjZed7PV6Fc2kFlp6w96ofzU+I2XrOjNhCULb5wneZ5gfObf4hi2VQ==",
+ "dependencies": {
+ "@mantine/utils": "6.0.20"
+ },
+ "peerDependencies": {
+ "@mantine/core": "6.0.20",
+ "@mantine/hooks": "6.0.20",
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
"node_modules/@mantine/styles": {
- "version": "6.0.18",
- "resolved": "https://registry.npmjs.org/@mantine/styles/-/styles-6.0.18.tgz",
- "integrity": "sha512-SNKcHkkmqLWUtE9lPucqOGNopvP4VsUtVRVtIUldRwvpzqoeuX1NJH6BwB8GPfVU0Wck/VeEhvGs+XzBe2xTRA==",
+ "version": "6.0.20",
+ "resolved": "https://registry.npmjs.org/@mantine/styles/-/styles-6.0.20.tgz",
+ "integrity": "sha512-Ft0kVwr299Cots5/z3EFXsFpZBAGqC1AlvYQ4XvDRSfHH12QfFAl3zKz1UCFSfk25TAd9cgEm5PFEo/DRxbN/w==",
"dependencies": {
"clsx": "1.1.1",
"csstype": "3.0.9"
@@ -1105,9 +1121,9 @@
"integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw=="
},
"node_modules/@mantine/utils": {
- "version": "6.0.18",
- "resolved": "https://registry.npmjs.org/@mantine/utils/-/utils-6.0.18.tgz",
- "integrity": "sha512-xvTnAUUHsdpsBm7OrcBueGEPdBwDm7wzUBsHweqSZMjT/HQOf4w4iirefNrFhMD2wNVetNL42kEngEBe6t63/w==",
+ "version": "6.0.20",
+ "resolved": "https://registry.npmjs.org/@mantine/utils/-/utils-6.0.20.tgz",
+ "integrity": "sha512-9vsSskPtFyfPW2DOad6brLl9x4MnZtlyhszyoZOyMgXf/vdQbThKhL6PpqUbmlalwQpnCObbkEwVydugeh2dyQ==",
"peerDependencies": {
"react": ">=16.8.0"
}
@@ -1286,6 +1302,31 @@
"node": ">=14"
}
},
+ "node_modules/@tabler/icons": {
+ "version": "2.30.0",
+ "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.30.0.tgz",
+ "integrity": "sha512-tvtmkI4ALjKThVVORh++sB9JnkFY7eGInKxNy+Df7WVQiF7T85tlvGADzlgX4Ic+CK5MIUzZ0jhOlQ/RRlgXpg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/codecalm"
+ }
+ },
+ "node_modules/@tabler/icons-react": {
+ "version": "2.30.0",
+ "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-2.30.0.tgz",
+ "integrity": "sha512-aYggXusHW133L4KujJkVf4GIIrjg7tIRHgNf/n37mnoHqMjwNP+PjmVdrBM1Z8Ywx9PKFRlrwM0eUMDcG+I4HA==",
+ "dependencies": {
+ "@tabler/icons": "2.30.0",
+ "prop-types": "^15.7.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/codecalm"
+ },
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
@@ -3093,7 +3134,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3366,7 +3406,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
diff --git a/package.json b/package.json
index 78ed3ed..60f8ead 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,8 @@
"@mantine/core": "^6.0.18",
"@mantine/form": "^6.0.18",
"@mantine/hooks": "^6.0.18",
+ "@mantine/modals": "^6.0.20",
+ "@tabler/icons-react": "^2.30.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.14.2"
diff --git a/src/app.jsx b/src/app.jsx
index a34d2b4..3b7bea2 100644
--- a/src/app.jsx
+++ b/src/app.jsx
@@ -1,12 +1,15 @@
-import { RouterProvider } from "react-router-dom";
-import { MantineProvider } from "@mantine/core";
+import { RouterProvider } from 'react-router-dom';
+import { MantineProvider } from '@mantine/core';
+import { ModalsProvider } from '@mantine/modals';
-import router from "./route";
+import router from './route';
export default function App() {
return (
-
+
+
+
);
}
diff --git a/src/assets/images/shoe-example.jpg b/src/assets/images/shoe-example.jpg
new file mode 100644
index 0000000..679b12b
Binary files /dev/null and b/src/assets/images/shoe-example.jpg differ
diff --git a/src/components/header.jsx b/src/components/header.jsx
new file mode 100644
index 0000000..3dc6407
--- /dev/null
+++ b/src/components/header.jsx
@@ -0,0 +1,15 @@
+import { Header, Title, MediaQuery, Flex } from '@mantine/core';
+import { IconMenu2 } from '@tabler/icons-react';
+
+export function HeaderMain({ onToggle }) {
+ return (
+
+
+
+ onToggle()} />
+
+ Dasboard
+
+
+ );
+}
diff --git a/src/components/navbar/index.jsx b/src/components/navbar/index.jsx
new file mode 100644
index 0000000..784a62e
--- /dev/null
+++ b/src/components/navbar/index.jsx
@@ -0,0 +1,77 @@
+import { NavLink } from 'react-router-dom';
+import { Navbar, Group, Title, Flex, MediaQuery } from '@mantine/core';
+import {
+ IconDashboard,
+ IconShoe,
+ IconCategory,
+ IconLogout,
+ IconX,
+} from '@tabler/icons-react';
+
+import { useStyles } from './style';
+
+const links = [
+ { link: '/', label: 'Dashboard', icon: IconDashboard },
+ { link: '/shoe', label: 'Shoes', icon: IconShoe },
+ { link: '/category', label: 'Category', icon: IconCategory },
+];
+
+export default function NavbarMain({ status, onToggle }) {
+ const { classes, cx } = useStyles();
+
+ return (
+
+
+
+
+
+
+ BDC Shoe
+
+
+
+ onToggle()} />
+
+
+
+
+ {links.map((item) => (
+
+ cx(classes.link, {
+ [classes.linkActive]: isActive,
+ })
+ }
+ to={item.link}
+ key={item.label}
+ >
+
+ {item.label}
+
+ ))}
+
+
+
+ event.preventDefault()}
+ >
+
+ Logout
+
+
+
+ );
+}
diff --git a/src/components/navbar/style.js b/src/components/navbar/style.js
new file mode 100644
index 0000000..3b33481
--- /dev/null
+++ b/src/components/navbar/style.js
@@ -0,0 +1,57 @@
+import { createStyles, getStylesRef, rem } from '@mantine/core';
+
+export const useStyles = createStyles((theme) => ({
+ header: {
+ paddingBottom: theme.spacing.md,
+ marginBottom: `calc(${theme.spacing.md} * 1.5)`,
+ borderBottom: `${rem(1)} solid ${theme.colors.gray[2]}`,
+ },
+
+ footer: {
+ paddingTop: theme.spacing.md,
+ marginTop: theme.spacing.md,
+ borderTop: `${rem(1)} solid ${theme.colors.gray[2]}`,
+ },
+
+ link: {
+ ...theme.fn.focusStyles(),
+ display: 'flex',
+ alignItems: 'center',
+ textDecoration: 'none',
+ fontSize: theme.fontSizes.sm,
+ color: theme.colors.gray[7],
+ padding: `${theme.spacing.xs} ${theme.spacing.sm}`,
+ borderRadius: theme.radius.sm,
+ fontWeight: 500,
+
+ '&:hover': {
+ backgroundColor: theme.colors.gray[0],
+ color: theme.black,
+
+ [`& .${getStylesRef('icon')}`]: {
+ color: theme.black,
+ },
+ },
+ },
+
+ linkIcon: {
+ ref: getStylesRef('icon'),
+ color: theme.colors.gray[6],
+ marginRight: theme.spacing.sm,
+ },
+
+ linkActive: {
+ '&, &:hover': {
+ backgroundColor: theme.fn.variant({
+ variant: 'light',
+ color: theme.primaryColor,
+ }).background,
+ color: theme.fn.variant({ variant: 'light', color: theme.primaryColor })
+ .color,
+ [`& .${getStylesRef('icon')}`]: {
+ color: theme.fn.variant({ variant: 'light', color: theme.primaryColor })
+ .color,
+ },
+ },
+ },
+}));
diff --git a/src/layouts/main.jsx b/src/layouts/main.jsx
index 8459bd3..e621710 100644
--- a/src/layouts/main.jsx
+++ b/src/layouts/main.jsx
@@ -1,5 +1,26 @@
-import { Outlet } from "react-router-dom";
+import { useState } from 'react';
+import { Outlet } from 'react-router-dom';
+import { AppShell, Container } from '@mantine/core';
+
+import NavbarMain from '../components/navbar';
+import { HeaderMain } from '../components/header';
export default function LayoutMain() {
- return ;
+ const [opened, setOpened] = useState(false);
+
+ return (
+ setOpened(!opened)} />
+ }
+ header={ setOpened(!opened)} />}
+ >
+
+
+
+
+ );
}
diff --git a/src/pages/category/create.jsx b/src/pages/category/create.jsx
new file mode 100644
index 0000000..034d171
--- /dev/null
+++ b/src/pages/category/create.jsx
@@ -0,0 +1,57 @@
+import { Form, Link, redirect } from 'react-router-dom';
+import { Button, Flex, Group, TextInput, Title } from '@mantine/core';
+import { IconArrowBack } from '@tabler/icons-react';
+
+export async function action({ request }) {
+ const formData = await request.formData();
+ const payload = Object.fromEntries(formData);
+
+ await fetch('http://localhost:3000/category', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(payload),
+ });
+
+ return redirect('/category');
+}
+
+export default function PageCategoryCreate() {
+ return (
+ <>
+
+
+ Add Category
+
+
+ }
+ >
+ Back
+
+
+
+
+ >
+ );
+}
diff --git a/src/pages/category/edit.jsx b/src/pages/category/edit.jsx
new file mode 100644
index 0000000..11a1e48
--- /dev/null
+++ b/src/pages/category/edit.jsx
@@ -0,0 +1,69 @@
+import { Link, useLoaderData, redirect, Form } from 'react-router-dom';
+import { Button, Flex, Group, TextInput, Title } from '@mantine/core';
+import { IconArrowBack } from '@tabler/icons-react';
+
+export async function loader({ params }) {
+ const response = await fetch(`http://localhost:3000/category/${params.id}`);
+ const category = await response.json();
+
+ return {
+ category,
+ };
+}
+
+export async function action({ request, params }) {
+ const formData = await request.formData();
+ const payload = Object.fromEntries(formData);
+
+ await fetch(`http://localhost:3000/category/${params.id}`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(payload),
+ });
+
+ return redirect('/category');
+}
+
+export default function PageCategoryEdit() {
+ const data = useLoaderData();
+
+ return (
+ <>
+
+
+ Edit Category
+
+
+ }
+ >
+ Back
+
+
+
+
+ >
+ );
+}
diff --git a/src/pages/category/list.jsx b/src/pages/category/list.jsx
new file mode 100644
index 0000000..b133a83
--- /dev/null
+++ b/src/pages/category/list.jsx
@@ -0,0 +1,76 @@
+import { Form, Link, useLoaderData, redirect } from 'react-router-dom';
+import { ActionIcon, Button, Flex, Table, Title } from '@mantine/core';
+import { IconPencil, IconPlus, IconTrash } from '@tabler/icons-react';
+
+export async function loader() {
+ const response = await fetch('http://localhost:3000/category');
+ const categories = await response.json();
+
+ return {
+ categories,
+ };
+}
+
+export async function action({ request }) {
+ const formData = await request.formData();
+ const id = formData.get('id');
+ await fetch(`http://localhost:3000/category/${id}`, {
+ method: 'DELETE',
+ });
+
+ return redirect('/category');
+}
+
+export default function PageCategoryList() {
+ const data = useLoaderData();
+
+ return (
+ <>
+
+
+ Category List
+
+
+ }>
+ Add
+
+
+
+
+
+
+ | Name |
+ Action |
+
+
+
+
+ {data.categories.map((category) => (
+
+ | {category.name} |
+
+
+
+
+
+
+
+
+ |
+
+ ))}
+
+
+ >
+ );
+}
diff --git a/src/pages/home.jsx b/src/pages/home.jsx
index f98b49e..b1b0202 100644
--- a/src/pages/home.jsx
+++ b/src/pages/home.jsx
@@ -1,3 +1,3 @@
-export default function Home() {
+export default function PageHome() {
return Home Page
;
}
diff --git a/src/pages/shoe/create.jsx b/src/pages/shoe/create.jsx
index 2b8c162..4974c88 100644
--- a/src/pages/shoe/create.jsx
+++ b/src/pages/shoe/create.jsx
@@ -1,7 +1,169 @@
-export default function ShoeCreate() {
+import {
+ Link,
+ redirect,
+ Form,
+ useLoaderData,
+ useNavigation,
+ useActionData,
+} from 'react-router-dom';
+import {
+ Button,
+ Flex,
+ Group,
+ NumberInput,
+ Radio,
+ Select,
+ TextInput,
+ Textarea,
+ Title,
+} from '@mantine/core';
+import { IconArrowBack } from '@tabler/icons-react';
+
+export async function loader() {
+ const response = await fetch('http://localhost:3000/category');
+ const payload = await response.json();
+
+ const shoes = payload.map((category) => ({
+ value: category.id,
+ label: category.name,
+ }));
+
+ return { shoes };
+}
+
+export async function action({ request }) {
+ const formData = await request.formData();
+ const payload = Object.fromEntries(formData);
+
+ const errors = {};
+
+ if (Number(formData.get('qty')) < 1) {
+ errors.qty = 'Qty should be more than zero';
+ }
+
+ if (Number(formData.get('price')) < 0) {
+ errors.price = 'Price should be start from zero';
+ }
+
+ if (Object.keys(errors).length > 0) {
+ return errors;
+ }
+
+ await fetch('http://localhost:3000/shoe', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(payload),
+ });
+
+ return redirect('/shoe');
+}
+
+export default function PageShoeCreate() {
+ const navigation = useNavigation();
+ const isSubmitting = navigation.state === 'submitting';
+
+ const data = useLoaderData();
+ const errors = useActionData();
+
return (
-
+ <>
+
+
+ Add Shoe
+
+
+ }
+ >
+ Back
+
+
+
+
+ >
);
}
diff --git a/src/pages/shoe/detail.jsx b/src/pages/shoe/detail.jsx
index bb1a68e..e74dc49 100644
--- a/src/pages/shoe/detail.jsx
+++ b/src/pages/shoe/detail.jsx
@@ -1,7 +1,78 @@
-export default function ShoeDetail() {
+import { Link, useLoaderData } from 'react-router-dom';
+import { Button, Flex, Title, Image, Text, Badge, Group } from '@mantine/core';
+import { IconArrowBack } from '@tabler/icons-react';
+
+import imgShoe from '../../assets/images/shoe-example.jpg';
+
+export async function loader({ params }) {
+ const response = await fetch(`http://localhost:3000/shoe/${params.id}`);
+ const json = await response.json();
+
+ return {
+ shoe: json,
+ };
+}
+
+export default function PageShoeDetail() {
+ const data = useLoaderData();
+
return (
-
+ <>
+
+
+ Detail Shoe
+
+
+ }
+ >
+ Back
+
+
+
+
+
+
+
+
+
+ {data.shoe.category.name}
+
+ {data.shoe.available ? (
+ Available
+ ) : (
+ Unavailable
+ )}
+
+
+ {data.shoe.name}
+
+
+ Quantity: {data.shoe.qty}
+
+
+
+ Rp {data.shoe.price}
+
+
+ {data.shoe.desc}
+
+
+
+
+
+
+
+ >
);
}
diff --git a/src/pages/shoe/edit.jsx b/src/pages/shoe/edit.jsx
index b2f78d5..dbe91a2 100644
--- a/src/pages/shoe/edit.jsx
+++ b/src/pages/shoe/edit.jsx
@@ -1,7 +1,184 @@
-export default function ShoeEdit() {
+import {
+ Link,
+ useLoaderData,
+ redirect,
+ useActionData,
+ Form,
+ useNavigation,
+} from 'react-router-dom';
+import {
+ Button,
+ Flex,
+ Group,
+ NumberInput,
+ Radio,
+ Select,
+ TextInput,
+ Textarea,
+ Title,
+} from '@mantine/core';
+import { IconArrowBack } from '@tabler/icons-react';
+
+export async function loader({ params }) {
+ const [shoeResponse, categoryResponse] = await Promise.all([
+ fetch(`http://localhost:3000/shoe/${params.id}`),
+ fetch('http://localhost:3000/category'),
+ ]);
+
+ const shoe = await shoeResponse.json();
+ const categories = await categoryResponse.json();
+
+ const categoryOptions = categories.map((category) => ({
+ value: category.id,
+ label: category.name,
+ }));
+
+ return {
+ shoe,
+ categoryOptions,
+ };
+}
+
+export async function action({ request, params }) {
+ const formData = await request.formData();
+ const payload = Object.fromEntries(formData);
+
+ const errors = {};
+
+ if (Number(formData.get('qty')) < 1) {
+ errors.qty = 'Qty should be more than zero';
+ }
+
+ if (Number(formData.get('price')) < 0) {
+ errors.price = 'Price should be start from zero';
+ }
+
+ if (Object.keys(errors).length > 0) {
+ return errors;
+ }
+
+ await fetch(`http://localhost:3000/shoe/${params.id}`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(payload),
+ });
+
+ return redirect('/shoe');
+}
+
+export default function PageShoeEdit() {
+ const data = useLoaderData();
+ const errors = useActionData();
+
+ const navigation = useNavigation();
+ const isSubmitting = navigation.state === 'submitting';
+
return (
-
+ <>
+
+
+ Edit Shoe
+
+
+ }
+ >
+ Back
+
+
+
+
+ >
);
}
diff --git a/src/pages/shoe/list.jsx b/src/pages/shoe/list.jsx
index 327ce59..325214a 100644
--- a/src/pages/shoe/list.jsx
+++ b/src/pages/shoe/list.jsx
@@ -1,10 +1,160 @@
-import { Button } from "@mantine/core";
+import { useEffect } from 'react';
+import {
+ Link,
+ useLoaderData,
+ redirect,
+ useSubmit,
+ useNavigation,
+} from 'react-router-dom';
+import {
+ ActionIcon,
+ Badge,
+ Button,
+ Flex,
+ Table,
+ Title,
+ Text,
+ Loader,
+} from '@mantine/core';
+import { modals } from '@mantine/modals';
+import { IconEye, IconPencil, IconPlus, IconTrash } from '@tabler/icons-react';
+
+export async function loader() {
+ const response = await fetch('http://localhost:3000/shoe');
+ const shoes = await response.json();
+
+ return {
+ shoes,
+ };
+}
+
+export async function action({ request }) {
+ const formData = await request.formData();
+ const id = formData.get('id');
+
+ await fetch(`http://localhost:3000/shoe/${id}`, {
+ method: 'DELETE',
+ });
+
+ return redirect('/shoe');
+}
+
+export default function PageShoeList() {
+ const data = useLoaderData();
+ const submit = useSubmit();
+ const navigation = useNavigation();
+
+ function handleDelete(id) {
+ modals.openConfirmModal({
+ title: 'Delete Shoe',
+ centered: true,
+ children: (
+
+ Are you sure you want to delete this shoe? This action is destructive
+ and data will be lost.
+
+ ),
+ labels: { confirm: 'Delete', cancel: 'Cancel' },
+ confirmProps: { color: 'red' },
+ onConfirm: () => {
+ const formData = new FormData();
+ formData.append('id', id);
+ submit(formData, { method: 'post' });
+ },
+ });
+ }
+
+ useEffect(() => {
+ if (navigation.state === 'submitting') {
+ modals.open({
+ title: 'Loading...',
+ closeOnClickOutside: false,
+ closeOnEscape: false,
+ withCloseButton: false,
+ children: (
+ <>
+
+
+
+ >
+ ),
+ });
+ } else {
+ modals.closeAll();
+ }
+ }, [navigation.state]);
-export default function ShoeList() {
return (
-
-
List Page
-
-
+ <>
+
+
+ Shoe List
+
+
+ }>
+ Add
+
+
+
+
+
+
+ | Name |
+ Brand |
+ Quantity |
+ Availability |
+ Action |
+
+
+
+
+ {data.shoes.map((shoe) => (
+
+ | {shoe.name} |
+ {shoe.merk} |
+
+ {shoe.qty}
+ |
+
+ {shoe.available ? (
+ Yes
+ ) : (
+ No
+ )}
+ |
+
+
+
+
+
+
+
+
+
+
+ handleDelete(shoe.id)}
+ >
+
+
+
+ |
+
+ ))}
+
+
+ >
);
}
diff --git a/src/route.jsx b/src/route.jsx
index 004459c..861a850 100644
--- a/src/route.jsx
+++ b/src/route.jsx
@@ -1,22 +1,102 @@
-import { createBrowserRouter } from "react-router-dom";
+import { Outlet, createBrowserRouter } from 'react-router-dom';
-import LayoutMain from "./layouts/main";
-import Home from "./pages/home";
-import ShoeList from "./pages/shoe/list";
-import ShoeDetail from "./pages/shoe/detail";
-import ShoeCreate from "./pages/shoe/create";
-import ShoeEdit from "./pages/shoe/edit";
+import LayoutMain from './layouts/main';
+import PageHome from './pages/home';
+
+import PageShoeList, {
+ loader as loaderShoeList,
+ action as actionShoeList,
+} from './pages/shoe/list';
+
+import PageShoeDetail, {
+ loader as loaderShoeDetail,
+} from './pages/shoe/detail';
+
+import PageShoeCreate, {
+ loader as loaderShoeCreate,
+ action as actionShoeCreate,
+} from './pages/shoe/create';
+
+import PageShoeEdit, {
+ loader as loaderShoeEdit,
+ action as actionShoeEdit,
+} from './pages/shoe/edit';
+
+import PageCategoryList, {
+ action as actionCategoryList,
+ loader as loaderCategoryList,
+} from './pages/category/list';
+
+import PageCategoryCreate, {
+ action as actionCategoryCreate,
+} from './pages/category/create';
+
+import PageCategoryEdit, {
+ loader as loaderCategoryEdit,
+ action as actionCategoryEdit,
+} from './pages/category/edit';
const router = createBrowserRouter([
{
- path: "/",
+ path: '/',
element: ,
children: [
- { index: true, element: },
- { path: "/shoe", element: },
- { path: "/shoe/:id/detail", element: },
- { path: "/shoe/create", element: },
- { path: "/shoe/:id/edit", element: },
+ {
+ index: true,
+ element: ,
+ },
+ {
+ path: '/shoe',
+ element: ,
+ children: [
+ {
+ index: true,
+ element: ,
+ loader: loaderShoeList,
+ action: actionShoeList,
+ },
+ {
+ path: ':id/detail',
+ element: ,
+ loader: loaderShoeDetail,
+ },
+ {
+ path: 'create',
+ element: ,
+ action: actionShoeCreate,
+ loader: loaderShoeCreate,
+ },
+ {
+ path: ':id/edit',
+ element: ,
+ loader: loaderShoeEdit,
+ action: actionShoeEdit,
+ },
+ ],
+ },
+ {
+ path: '/category',
+ element: ,
+ children: [
+ {
+ index: true,
+ element: ,
+ action: actionCategoryList,
+ loader: loaderCategoryList,
+ },
+ {
+ path: 'create',
+ element: ,
+ action: actionCategoryCreate,
+ },
+ {
+ path: ':id/edit',
+ element: ,
+ loader: loaderCategoryEdit,
+ action: actionCategoryEdit,
+ },
+ ],
+ },
],
},
]);