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..9540ca3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
"@mantine/core": "^6.0.18",
"@mantine/form": "^6.0.18",
"@mantine/hooks": "^6.0.18",
+ "@tabler/icons-react": "^2.30.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.14.2"
@@ -1286,6 +1287,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 +3119,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 +3391,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..05316bc 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"@mantine/core": "^6.0.18",
"@mantine/form": "^6.0.18",
"@mantine/hooks": "^6.0.18",
+ "@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/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..cb9c0fe 100644
--- a/src/layouts/main.jsx
+++ b/src/layouts/main.jsx
@@ -1,5 +1,35 @@
-import { Outlet } from "react-router-dom";
+import { useState } from 'react';
+import { Outlet, useNavigation } from 'react-router-dom';
+import { AppShell, Container, LoadingOverlay } from '@mantine/core';
+
+import NavbarMain from '../components/navbar';
+import { HeaderMain } from '../components/header';
export default function LayoutMain() {
- return ;
+ const navigation = useNavigation();
+
+ 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..8221c11
--- /dev/null
+++ b/src/pages/category/create.jsx
@@ -0,0 +1,37 @@
+import { Link } from 'react-router-dom';
+import { Button, Flex, Group, TextInput, Title } from '@mantine/core';
+import { IconArrowBack } from '@tabler/icons-react';
+
+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..e546165
--- /dev/null
+++ b/src/pages/category/edit.jsx
@@ -0,0 +1,37 @@
+import { Link } from 'react-router-dom';
+import { Button, Flex, Group, TextInput, Title } from '@mantine/core';
+import { IconArrowBack } from '@tabler/icons-react';
+
+export default function PageCategoryEdit() {
+ return (
+ <>
+
+
+ Edit Category
+
+
+ }
+ >
+ Back
+
+
+
+
+ >
+ );
+}
diff --git a/src/pages/category/list.jsx b/src/pages/category/list.jsx
new file mode 100644
index 0000000..e9463db
--- /dev/null
+++ b/src/pages/category/list.jsx
@@ -0,0 +1,59 @@
+import { Link } from 'react-router-dom';
+import { ActionIcon, Button, Flex, Table, Title } from '@mantine/core';
+import { IconPencil, IconPlus, IconTrash } from '@tabler/icons-react';
+
+const elements = [
+ { name: 'Sport' },
+ { name: 'Casual' },
+ { name: 'School' },
+ { name: 'Adventure' },
+];
+
+export default function PageCategoryList() {
+ return (
+ <>
+
+
+ Category List
+
+
+ }>
+ Add
+
+
+
+
+
+
+ Name |
+ Action |
+
+
+
+
+ {elements.map((element) => (
+
+ {element.name} |
+
+
+
+
+
+
+
+
+
+
+ |
+
+ ))}
+
+
+ >
+ );
+}
diff --git a/src/pages/home.jsx b/src/pages/home.jsx
index f98b49e..61c4461 100644
--- a/src/pages/home.jsx
+++ b/src/pages/home.jsx
@@ -1,3 +1,5 @@
-export default function Home() {
- return Home Page
;
+import { Title } from '@mantine/core';
+
+export default function PageHome() {
+ return Welcome to BDC Shoe 👋;
}
diff --git a/src/pages/shoe/create.jsx b/src/pages/shoe/create.jsx
index 2b8c162..35a02db 100644
--- a/src/pages/shoe/create.jsx
+++ b/src/pages/shoe/create.jsx
@@ -1,7 +1,109 @@
-export default function ShoeCreate() {
+import { Link, Form, redirect } from 'react-router-dom';
+import {
+ Button,
+ Flex,
+ Group,
+ NumberInput,
+ Radio,
+ TextInput,
+ Textarea,
+ 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/shoe', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(payload),
+ });
+
+ return redirect('/shoe');
+}
+
+export default function PageShoeCreate() {
return (
-
+ <>
+
+
+ Add Shoe
+
+
+ }
+ >
+ Back
+
+
+
+
+ >
);
}
diff --git a/src/pages/shoe/detail.jsx b/src/pages/shoe/detail.jsx
index bb1a68e..986bd13 100644
--- a/src/pages/shoe/detail.jsx
+++ b/src/pages/shoe/detail.jsx
@@ -1,7 +1,76 @@
-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.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..e169942 100644
--- a/src/pages/shoe/edit.jsx
+++ b/src/pages/shoe/edit.jsx
@@ -1,7 +1,127 @@
-export default function ShoeEdit() {
+import { Link, Form, redirect, useLoaderData } from 'react-router-dom';
+import {
+ Button,
+ Flex,
+ Group,
+ NumberInput,
+ Radio,
+ TextInput,
+ Textarea,
+ Title,
+} from '@mantine/core';
+import { IconArrowBack } from '@tabler/icons-react';
+
+export async function loader({ params }) {
+ const response = await fetch(`http://localhost:3000/shoe/${params.id}`);
+ const json = await response.json();
+
+ return {
+ shoe: json,
+ };
+}
+
+export async function action({ request, params }) {
+ const formData = await request.formData();
+ const payload = Object.fromEntries(formData);
+ console.log(payload);
+ 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();
+
return (
-
+ <>
+
+
+ Edit Shoe
+
+
+ }
+ >
+ Back
+
+
+
+
+ >
);
}
diff --git a/src/pages/shoe/list.jsx b/src/pages/shoe/list.jsx
index 327ce59..497718c 100644
--- a/src/pages/shoe/list.jsx
+++ b/src/pages/shoe/list.jsx
@@ -1,10 +1,95 @@
-import { Button } from "@mantine/core";
+import { Link, useLoaderData, redirect, Form } from 'react-router-dom';
+import { ActionIcon, Badge, Button, Flex, Table, Title } from '@mantine/core';
+import { IconEye, IconPencil, IconPlus, IconTrash } from '@tabler/icons-react';
+
+export async function loader() {
+ const response = await fetch('http://localhost:3000/shoe');
+ const json = await response.json();
+
+ return {
+ shoes: json,
+ };
+}
+
+export async function action({ params }) {
+ await fetch(`http://localhost:3000/shoe/${params.id}`, {
+ method: 'DELETE',
+ });
+ return redirect('/shoe');
+}
+
+export default function PageShoeList() {
+ const data = useLoaderData();
-export default function ShoeList() {
return (
-
-
List Page
-
-
+ <>
+
+
+ Shoe List
+
+
+ }>
+ Add
+
+
+
+
+
+
+ Name |
+ Brand |
+ Quantity |
+ Availability |
+ Action |
+
+
+
+
+ {data.shoes.map((item) => (
+
+ {item.name} |
+ {item.merk} |
+
+ {item.qty}
+ |
+
+ {item.available ? (
+ Yes
+ ) : (
+ No
+ )}
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+ ))}
+
+
+ >
);
}
diff --git a/src/route.jsx b/src/route.jsx
index 004459c..b3cb65d 100644
--- a/src/route.jsx
+++ b/src/route.jsx
@@ -1,22 +1,89 @@
-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 shoesLoader,
+ action as shoeDeleteAction,
+} from './pages/shoe/list';
+
+import PageShoeDetail, {
+ loader as shoeDetailLoader,
+} from './pages/shoe/detail';
+
+import PageShoeCreate, {
+ action as shoeCreateAction,
+} from './pages/shoe/create';
+
+import PageShoeEdit, {
+ loader as shoeEditLoader,
+ action as shoeEditAction,
+} from './pages/shoe/edit';
+
+import PageCategoryList from './pages/category/list';
+import PageCategoryCreate from './pages/category/create';
+import PageCategoryEdit 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: shoesLoader,
+ },
+ {
+ path: '/shoe/:id/detail',
+ element: ,
+ loader: shoeDetailLoader,
+ },
+
+ {
+ path: '/shoe/create',
+ element: ,
+ action: shoeCreateAction,
+ },
+ {
+ path: '/shoe/:id/edit',
+ element: ,
+ loader: shoeEditLoader,
+ action: shoeEditAction,
+ },
+ {
+ path: '/shoe/:id/delete',
+ action: shoeDeleteAction,
+ },
+ ],
+ },
+ {
+ path: '/category',
+ element: ,
+ children: [
+ {
+ index: true,
+ element: ,
+ },
+ {
+ path: '/category/create',
+ element: ,
+ },
+ {
+ path: '/category/:id/edit',
+ element: ,
+ },
+ ],
+ },
],
},
]);