first commit

This commit is contained in:
2022-12-13 07:42:44 +08:00
commit 7ef2644e20
46 changed files with 7810 additions and 0 deletions

6
web/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
/node_modules
/dist
/.parcel-cache
/public/ais.js
/build

6
web/Caddyfile Normal file
View File

@@ -0,0 +1,6 @@
:8001 {
route {
reverse_proxy /api/* 127.0.0.1:8888
reverse_proxy * 127.0.0.1:1234
}
}

5979
web/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

24
web/package.json Normal file
View File

@@ -0,0 +1,24 @@
{
"name": "web",
"version": "1.0.0",
"description": "",
"scripts": {
"build": "esbuild src/index.jsx --bundle --minify --outfile=public/ais.js --loader:.webp=dataurl --analyze",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@mui/icons-material": "^5.10.16",
"@mui/material": "^5.10.17",
"esbuild": "^0.16.4",
"parcel": "^2.8.1",
"process": "^0.11.10",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.4.5"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

13
web/public/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<title>Title</title>
</head>
<body>
<div id="root">
<h1 style="text-align: center;">Loading Sam's AIS...</h1>
</div>
<script type="module" src="/ais.js"></script>
</body>
</html>

54
web/src/common.js Normal file
View File

@@ -0,0 +1,54 @@
const API_ENDPOINT = "/api";
const get = async (url) => {
const resp = await fetch(`${API_ENDPOINT}${url}`);
const json = await resp.json();
return json;
};
const post = async (url, data) => {
return await _post(url, data, "POST");
};
const del = async (url, data) => {
return await _post(url, data, "DELETE");
};
const put = async (url, data) => {
return await _post(url, data, "PUT");
};
const _post = async (url, data, method) => {
const resp = await fetch(`${API_ENDPOINT}${url}`, {
method,
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
const json = await resp.json();
if (json.errors) {
alert(json.errors);
throw json.errors;
}
return json;
};
const fixZero = (n) => {
if (n < 10) return `0${n}`;
return n;
};
const time = (s) => {
const t = new Date(s);
const year = t.getFullYear();
const month = fixZero(t.getMonth())
const day = fixZero(t.getDay());
const hours = fixZero(t.getHours());
const minutes = fixZero(t.getMinutes());
const second = fixZero(t.getSeconds());
return `${year}-${month}-${day} ${hours}:${minutes}:${second}`;
};
export { get, post, del, put, time };

View File

@@ -0,0 +1,38 @@
import {
TableContainer,
Paper,
Table,
TableHead,
TableRow,
TableCell,
TableBody,
} from "@mui/material";
import React from "react";
const AutoTable = ({ data }) => {
const columns = Object.keys(data[0] || []);
return (
<TableContainer component={Paper} sx={{ m: 1 }}>
<Table>
<TableHead>
<TableRow>
{columns.map((col) => (
<TableCell key={col}>{col}</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{data.map((row) => (
<TableRow key={row.id}>
{columns.map((col) => (
<TableCell key={col}>{row[col]}</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
};
export default AutoTable;

View File

@@ -0,0 +1,22 @@
import React from "react";
import {
Card,
CardActionArea,
CardContent,
CardMedia,
Typography,
} from "@mui/material";
const ImageCard = ({ title, description, image, onClick }) => (
<Card sx={{ m: 1 }}>
<CardActionArea onClick={onClick}>
<CardMedia component="img" height="239" image={image} alt="Card Image" />
<CardContent>
<Typography variant="h5">{title}</Typography>
<Typography>{description}</Typography>
</CardContent>
</CardActionArea>
</Card>
);
export default ImageCard;

View File

@@ -0,0 +1,36 @@
import { Button } from "@mui/material";
import React from "react";
import { useNavigate } from "react-router-dom";
import userContext from "../context/userContext";
import { get } from "../common";
const LoginButton = () => {
const { user, setUser } = React.useContext(userContext);
const navigator = useNavigate();
const handleClick = () => {
if (user.role === 1) {
navigator("/customer");
} else if (user.role === 2) {
navigator("/supplier");
} else {
navigator('/login');
}
};
React.useEffect(() => {
get("/login")
.then((resp) => {
setUser(resp);
})
.catch((e) => console.log(e));
}, []);
return (
<Button variant="contained" color="secondary" onClick={handleClick}>
{user.username || "Login"}
</Button>
);
};
export default LoginButton;

View File

@@ -0,0 +1,31 @@
import React from "react";
import { Card, CardActionArea, CardContent, Typography } from "@mui/material";
import { useNavigate } from "react-router-dom";
import LogoutIcon from "@mui/icons-material/Logout";
import { post } from "../common";
import userContext from "../context/userContext";
const LogoutCard = () => {
const nevigator = useNavigate();
const { setUser } = React.useContext(userContext);
const handelLogout = async () => {
await post("/logout", {});
setUser({});
nevigator("/login");
};
return (
<Card sx={{ m: 1 }}>
<CardActionArea onClick={handelLogout}>
<LogoutIcon />
<CardContent>
<Typography variant="h5">Logout</Typography>
<Typography>Clear session</Typography>
</CardContent>
</CardActionArea>
</Card>
);
};
export default LogoutCard;

View File

@@ -0,0 +1,23 @@
import React from "react";
import { InputLabel, FormControl, Select, MenuItem } from "@mui/material";
const SelectRole = ({ inputRole, setInputRole }) => {
return (
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">Role</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={inputRole}
label="Role"
onChange={(event) => setInputRole(event.target.value)}
>
<MenuItem value={1}>Customer</MenuItem>
<MenuItem value={2}>Supplier</MenuItem>
<MenuItem value={3}>Admin</MenuItem>
</Select>
</FormControl>
);
};
export default SelectRole;

View File

@@ -0,0 +1,3 @@
import {createContext} from 'react';
const userContext = createContext({});
export default userContext;

12
web/src/index.html Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<title>Title</title>
</head>
<body>
<div id="root">
</div>
<script type="module" src="./index.jsx"></script>
</body>
</html>

44
web/src/index.jsx Normal file
View File

@@ -0,0 +1,44 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { createHashRouter as Router, RouterProvider } from "react-router-dom";
import LoginPage from "./pages/LoginPage";
import HomePage from "./pages/HomePage";
import RegisterPage from "./pages/RegisterPage";
import CustomerPage from './pages/CustomerPage';
import CustomerMarketPage from './pages/CustomerMarketPage';
import userContext from "./context/userContext";
import CustomerMarketDeatilPage from "./pages/CustomerMarketDetailPage";
import CustomerPurchasedPage from "./pages/CustomerPurchasedPage";
import SupplierPage from './pages/SupplierPage';
import SupplierGoods from './pages/SupplierGoods';
import SupplierAddGoods from './pages/SupplierAddGoods';
const router = Router([
{ path: "/", element: <HomePage /> },
{ path: "/ping", element: <div>pong</div> },
{ path: "/login", element: <LoginPage /> },
{ path: "/register", element: <RegisterPage /> },
{ path: "/customer", element: <CustomerPage /> },
{ path: "/customer/market", element: <CustomerMarketPage /> },
{ path: "/customer/market/:marketid", element: <CustomerMarketDeatilPage /> },
{ path: "/customer/purchased", element: <CustomerPurchasedPage /> },
{ path: "/supplier", element: <SupplierPage /> },
{ path: "/supplier/goods", element: <SupplierGoods /> },
{ path: "/supplier/goods/new", element: <SupplierAddGoods /> },
]);
const App = () => {
const [user, setUser] = React.useState({});
return (
<userContext.Provider value={{ user, setUser }}>
<RouterProvider router={router}></RouterProvider>
</userContext.Provider>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@@ -0,0 +1,35 @@
import { AppBar, Box, CssBaseline, Toolbar, Typography } from "@mui/material";
import React from "react";
import { Link } from "react-router-dom";
import LoginButton from "../components/LoginButton";
const BasicLayout = ({ children }) => {
return (
<>
<CssBaseline />
<header>
<AppBar position="relative">
<Toolbar>
<Typography variant="h6" sx={{ flexGrow: 1 }}>
<Link style={{ textDecoration: "none", color: "unset" }} to="/">
<img
style={{
height: "1.39rem",
}}
src="data:image/svg+xml;charset=utf-8,%3Csvg width='214' height='37' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M196.157.298l16.449 16.624c.75.758.75 1.987.001 2.745l-16.445 16.63-2.84-2.87a1.987 1.987 0 010-2.79l12.204-12.341-12.1-12.229a2.142 2.142 0 010-3.007l2.731-2.762zm-5.293 2.75l-2.719-2.75-16.362 16.546a2.075 2.075 0 000 2.911l16.367 16.542 2.716-2.747c.83-.84.83-2.2 0-3.038L178.781 18.3l12.084-12.22a2.161 2.161 0 00-.001-3.033zm-30.713 17.22c0 3.678-2.362 6.457-5.807 6.457-3.407 0-5.769-2.78-5.769-6.38 0-3.64 2.362-6.496 5.769-6.496 3.445 0 5.807 2.778 5.807 6.418zm-11.653 5.791c1.317 2.857 3.833 4.344 6.814 4.344 4.994 0 8.981-3.874 8.981-10.097 0-6.262-3.987-10.136-8.981-10.136-2.981 0-5.497 1.487-6.814 4.344V2.656l-3.871.783V30.09h3.871v-4.03zm-32.861-2.387c0 4.736 2.207 6.849 6.195 6.692.348 0 .735-.039 1.084-.117v-3.835h-1.007c-1.393 0-2.4-.861-2.4-2.74V2.656l-3.872.783v20.233zM69.951 4.847h4.18l-2.438 7.71H68.48l1.471-7.71zM51.521 30.05h3.87V19.523c0-3.209 1.975-5.713 4.801-5.713 2.594 0 3.872 1.878 3.872 4.343v11.898h3.87V18.193c0-4.97-2.98-8.062-7.045-8.062-2.594 0-4.762 1.135-6.388 3.718-1.161-2.349-3.29-3.718-5.884-3.718-2.478 0-4.607 1.37-5.768 3.835v-3.483h-3.872V30.05h3.872V19.523c0-3.209 1.974-5.713 4.8-5.713 2.594 0 3.871 1.878 3.871 4.343v11.898zM0 28.29l1.742-3.562c1.278.94 3.562 1.996 5.304 1.996 2.168 0 3.02-1.056 3.02-2.192.038-1.33-1.046-1.839-3.33-2.582-3.832-1.174-6-2.544-6-6.145 0-3.287 2.477-5.635 6.968-5.635 2.323 0 4.452.743 6.156 1.957l-1.743 3.248c-1.006-.626-2.71-1.566-4.607-1.566-1.664 0-2.67.666-2.67 1.957 0 1.057.735 1.761 3.174 2.505 4.142 1.252 6.194 2.818 6.194 6.105 0 3.718-2.981 5.988-7.124 5.988-2.826 0-5.535-.94-7.084-2.074zm25.628-14.44c-3.445 0-5.807 2.778-5.807 6.457 0 3.64 2.362 6.418 5.807 6.418 3.407 0 5.769-2.857 5.769-6.496 0-3.601-2.362-6.38-5.769-6.38zm5.844 12.21c-1.316 2.857-3.832 4.344-6.813 4.344-4.994 0-8.982-3.875-8.982-10.136 0-6.223 3.988-10.098 8.982-10.098 2.98 0 5.497 1.488 6.813 4.345v-4.031h3.872v19.568h-3.872V26.06zm43.01-1.332L72.74 28.29c1.549 1.135 4.259 2.074 7.085 2.074 4.142 0 7.123-2.27 7.123-5.988 0-3.287-2.052-4.853-6.194-6.105-2.439-.744-3.175-1.448-3.175-2.505 0-1.291 1.007-1.957 2.672-1.957 1.897 0 3.6.94 4.607 1.566l1.742-3.248a10.59 10.59 0 00-6.156-1.957c-4.49 0-6.968 2.348-6.968 5.635 0 3.6 2.168 4.97 6 6.145 2.284.743 3.368 1.252 3.33 2.582 0 1.136-.852 2.192-3.02 2.192-1.742 0-4.026-1.056-5.304-1.996zm21.991-4.46c0-6.223 4.259-10.098 10.182-10.098 2.787 0 5.265.9 7.162 2.818l-2.362 2.935c-1.471-1.487-3.213-2.074-4.762-2.074-3.561 0-6.078 2.152-6.078 6.38 0 4.226 2.517 6.456 6.078 6.456 1.549 0 3.291-.626 4.801-2.191l2.361 2.896c-1.897 1.917-4.336 2.974-7.2 2.974-5.885 0-10.182-3.874-10.182-10.097zm28.077 1.721V10.483h3.871v11.82c0 2.582 1.626 4.343 4.336 4.343 2.71 0 4.336-1.76 4.336-4.344V10.483h3.871V21.99c0 5.362-3.291 8.375-8.207 8.375-4.917 0-8.207-3.013-8.207-8.375z' fill='%23fff'/%3E%3C/svg%3E"
/>
</Link>
</Typography>
<LoginButton />
</Toolbar>
</AppBar>
</header>
<main>
<Box sx={{ m: 3 }}>{children}</Box>
</main>
<footer></footer>
</>
);
};
export default BasicLayout;

View File

@@ -0,0 +1,13 @@
import React from "react";
import BasicLayout from "./BasicLayout";
import { Paper, Container } from "@mui/material";
const SmallPaperLayout = ({ children }) => (
<BasicLayout>
<Container maxWidth="sm">
<Paper sx={{ p: 3}}>{children}</Paper>
</Container>
</BasicLayout>
);
export default SmallPaperLayout;

View File

@@ -0,0 +1,56 @@
import { Button, Typography } from "@mui/material";
import React from "react";
import BasicLayout from "../layouts/BasicLayout";
import userContext from "../context/userContext";
import { useParams } from "react-router-dom";
import AutoTable from "../components/AutoTable";
import { get, post } from "../common";
const CustomerMarketDeatilPage = () => {
const { user } = React.useContext(userContext);
const [data, setData] = React.useState([]);
const params = useParams();
const buyFunc = async (goodsid) => {
await post(`/customer/market/${params.marketid}/${goodsid}`, {});
refresh();
};
const refresh = () => {
get(`/customer/market/${params.marketid}`).then((resp) => {
let goods = resp.goods.map((g) => {
delete g.supplier_id;
delete g.market_id;
g.action = (
<Button
variant="contained"
color="success"
onClick={() => {
buyFunc(g.id);
}}
>
Buy!
</Button>
);
return g;
});
setData(goods);
});
};
React.useEffect(() => {
refresh();
}, []);
return (
<BasicLayout>
<Typography variant="h4">
Hi {user.username}, you are in market {params.marketid}
</Typography>
<hr />
<AutoTable data={data} />
</BasicLayout>
);
};
export default CustomerMarketDeatilPage;

View File

@@ -0,0 +1,51 @@
import { TextField, Typography, Box, Button } from "@mui/material";
import React from "react";
import BasicLayout from "../layouts/BasicLayout";
import AutoTable from "../components/AutoTable";
import { Link } from "react-router-dom";
import { get } from "../common";
const CustomerMarketPage = () => {
const [inputPoint, setInputPoint] = React.useState("");
const [data, setData] = React.useState([]);
React.useEffect(() => {
get("/customer/market").then((resp) => {
const d = resp.markets.map((m) => {
m.action = <Link to={`${m.id}`}>Enter</Link>;
return m;
});
setData(d);
});
}, []);
const handleSearch = () => {
get(`/customer/market/distance?point=${inputPoint}`).then((resp) => {
const d = resp.markets.map((m) => {
m.action = <Link to={`${m.id}`}>Enter</Link>;
return m;
});
setData(d);
});
};
return (
<BasicLayout>
<Typography variant="h4">Find your neaset market</Typography>
<hr />
<Box style={{ display: "flex" }}>
<TextField
value={inputPoint}
onChange={(event) => setInputPoint(event.target.value)}
label="Location"
/>
<Button variant="contained" color="secondary" onClick={handleSearch}>
Search
</Button>
</Box>
<AutoTable data={data} />
</BasicLayout>
);
};
export default CustomerMarketPage;

View File

@@ -0,0 +1,40 @@
import React from "react";
import { Link } from "react-router-dom";
import userContext from "../context/userContext";
import BasicLayout from "../layouts/BasicLayout";
import ImageCard from "../components/ImageCard";
import LogoutCard from "../components/LogoutCard";
// images
import locationImage from "../../public/images/location.webp";
import purchasedImage from "../../public/images/purchased.webp";
import { Box, Typography } from "@mui/material";
const CustomerPage = () => {
const { user } = React.useContext(userContext);
return (
<BasicLayout>
<Typography variant="h4">Hello, customer {user.username}</Typography>
<hr />
<Box style={{ display: "flex", flexWrap: "wrap" }}>
<Link to="/customer/market">
<ImageCard
title="Nearby Market"
description="Find your neaest market"
image={locationImage}
/>
</Link>
<Link to="/customer/purchased">
<ImageCard
title="Purchased"
description="See what you have purchased"
image={purchasedImage}
/>
</Link>
<LogoutCard />
</Box>
</BasicLayout>
);
};
export default CustomerPage;

View File

@@ -0,0 +1,75 @@
import { Button, Typography } from "@mui/material";
import React from "react";
import BasicLayout from "../layouts/BasicLayout";
import userContext from "../context/userContext";
import AutoTable from "../components/AutoTable";
import { get, del, time } from "../common";
const CustomerPurchasedPage = () => {
const { user } = React.useContext(userContext);
const [data, setData] = React.useState([]);
const handleDelete = (id) => {
del(`/customer/purchase/${id}`).then(() => {
refresh();
});
};
const refresh = () => {
get(`/customer/purchase`).then((resp) => {
// flatten
const result = resp.map((r) => {
const { id, quantity, purchased_time } = r;
const pt = time(purchased_time);
const { name, price } = r.goods;
const deleteButton = (
<Button
variant="contained"
color="error"
onClick={() => {
handleDelete(id);
}}
>
Delete
</Button>
);
return {
id,
quantity,
pt,
name,
price,
action: deleteButton,
};
});
setData(result);
});
};
React.useEffect(() => {
refresh();
}, []);
return (
<BasicLayout>
<Typography variant="h4">
Hi {user.username}, here is what you have purchased
</Typography>
<Button
variant="contained"
color="success"
onClick={() => {
get(`/customer/purchase/report`).then((data) => {
alert(`$The sum of all your perchase is ${data.sum}`);
});
}}
>
Summary Report
</Button>
<hr />
<AutoTable data={data} />
</BasicLayout>
);
};
export default CustomerPurchasedPage;

View File

@@ -0,0 +1,57 @@
import { Box, Typography } from "@mui/material";
import React from "react";
import BasicLayout from "../layouts/BasicLayout";
import ImageCard from "../components/ImageCard";
import userContext from "../context/userContext";
// images
import CustomerImage from "../../public/images/customer.webp";
import SupplierImage from "../../public/images/supplier.webp";
import { useNavigate } from "react-router-dom";
const HomePage = () => {
const { user } = React.useContext(userContext);
const navigator = useNavigate();
return (
<BasicLayout>
<Box sx={{ m: 3 }}>
<Typography variant="h4">{"Welcome to sam's club"}</Typography>
<Typography>Select a role to continue</Typography>
<hr />
<Box
style={{
display: "flex",
flexWrap: "wrap",
}}
>
<ImageCard
title="Customer module"
description="Find a neaest market and purchase goods"
image={CustomerImage}
onClick={() => {
if (user.role !== 1) {
alert("You are not allow to use this module");
} else {
navigator("/customer");
}
}}
/>
<ImageCard
title="Supplier"
description="Manage the supply chain system and goods."
image={SupplierImage}
onClick={() => {
if (user.role !== 2) {
alert("You are not allow to use this module");
} else {
navigator("/supplier");
}
}}
/>
</Box>
</Box>
</BasicLayout>
);
};
export default HomePage;

View File

@@ -0,0 +1,80 @@
import { Button, Stack, TextField, Typography } from "@mui/material";
import React from "react";
import { Link, useNavigate } from "react-router-dom";
import SmallPaperLayout from "../layouts/SmallPaperLayout";
import SelectRole from "../components/SelectRole";
import userContext from "../context/userContext";
import { post } from "../common";
const LoginPage = () => {
const { setUser } = React.useContext(userContext);
const [inputUsername, setInputUsername] = React.useState("");
const [inputPassword, setInputPassword] = React.useState("");
const [inputRole, setInputRole] = React.useState(0);
const validateInput = () => {
if (!inputUsername) {
alert("Username can't be empty");
return;
}
if (inputUsername.length >= 30) {
alert("Username too long");
return;
}
if (!inputPassword) {
alert("Password can't be empty");
return;
}
if (!inputRole) {
alert("Role can't be empty");
return;
}
// do login
loginFunc();
};
const navigator = useNavigate();
const loginFunc = async () => {
const url = '/login';
const resp = await post(url, {
username: inputUsername,
password: inputPassword,
});
setUser(resp)
const nextURL = inputRole === 1 ? "/customer" : "/supplier";
navigator(nextURL);
};
return (
<SmallPaperLayout>
<Typography variant="h4" sx={{ pb: 2 }}>
Login to Sam AIS
</Typography>
<Stack spacing={3}>
<TextField
label="User name"
value={inputUsername}
onChange={(event) => setInputUsername(event.target.value)}
/>
<TextField
label="Password"
value={inputPassword}
onChange={(event) => setInputPassword(event.target.value)}
type="password"
/>
<SelectRole inputRole={inputRole} setInputRole={setInputRole} />
<Button variant="contained" color="success" onClick={validateInput}>
Login
</Button>
<Typography>
Click <Link to="/register">here</Link> to register a new account
</Typography>
</Stack>
</SmallPaperLayout>
);
};
export default LoginPage;

View File

@@ -0,0 +1,16 @@
import React from "react";
import SmallPaperLayout from '../layouts/SmallPaperLayout';
import userContext from "../context/userContext";
import { Box, Typography } from "@mui/material";
const ProfilePages = () => {
const { user, setUser } = React.useContext(userContext);
return (
<SmallPaperLayout>
<Typography variant="h3">Hello, {user.username}</Typography>
<hr />
</SmallPaperLayout>
);
};
export default ProfilePages;

View File

@@ -0,0 +1,122 @@
import {
Button,
Checkbox,
FormControlLabel,
FormGroup,
Stack,
TextField,
Typography,
} from "@mui/material";
import React from "react";
import { useNavigate, Link } from "react-router-dom";
import SmallPaperLayout from "../layouts/SmallPaperLayout";
import SelectRole from "../components/SelectRole";
import { post } from "../common";
const RegisterPage = () => {
const [inputUsername, setInputUsername] = React.useState("");
const [inputPassword, setInputPassword] = React.useState("");
const [repeatInputPassword, setRepeatInputPassword] = React.useState("");
const [inputRole, setInputRole] = React.useState(0);
const validateInput = () => {
if (!inputUsername) {
alert("Username can't be empty");
return;
}
if (inputUsername.length >= 30) {
alert("Username too long");
return;
}
if (!inputPassword) {
alert("Password can't be empty");
return;
}
if (inputPassword !== repeatInputPassword) {
alert("Password not matched!");
return;
}
if (!inputRole) {
alert("Role can't be empty");
return;
}
// do register
registerFunc();
};
const navigator = useNavigate();
const registerFunc = () => {
const url = "/register";
post(url, {
username: inputUsername,
password: inputPassword,
role: inputRole,
}).then(() => {
alert("Successfully registered, please go to login page");
navigator("/login");
});
};
const validatePassword = (p) => {
if (p.length === 0) {
return "";
}
if (p.length < 8) {
return "password should be at least 8 characters";
}
return "";
};
return (
<SmallPaperLayout>
<Typography variant="h4" sx={{ pb: 2 }}>
Register
</Typography>
<Stack spacing={3}>
<TextField
label="User name"
value={inputUsername}
onChange={(event) => setInputUsername(event.target.value)}
error={inputUsername.length >= 30}
helperText={inputUsername.length >= 30 && "User name too long !"}
/>
<TextField
label="Password"
value={inputPassword}
onChange={(event) => setInputPassword(event.target.value)}
type="password"
error={validatePassword(inputPassword)}
helperText={validatePassword(inputPassword)}
/>
<TextField
label="Repeat password"
value={repeatInputPassword}
onChange={(event) => setRepeatInputPassword(event.target.value)}
type="password"
error={inputPassword !== repeatInputPassword}
helperText={
inputPassword !== repeatInputPassword && "Password not matched"
}
/>
<SelectRole inputRole={inputRole} setInputRole={setInputRole} />
<FormGroup>
<FormControlLabel
control={<Checkbox />}
label={
<Typography>
I agree the{" "}
<Link to="/eula">end-user license agreement (EULA)</Link>
</Typography>
}
/>
</FormGroup>
<Button variant="contained" color="warning" onClick={validateInput}>
Register
</Button>
</Stack>
</SmallPaperLayout>
);
};
export default RegisterPage;

View File

@@ -0,0 +1,67 @@
import { Button, Stack, TextField, Typography } from "@mui/material";
import React from "react";
import SmallPaperLayout from "../layouts/SmallPaperLayout";
import { post } from "../common";
const SupplierAddGoods = () => {
const [inputName, setInputName] = React.useState("");
const [inputDescription, setInputDescription] = React.useState("");
const [inputMarketId, setInputMarketId] = React.useState(0);
const [inputQuantity, setInputQuantity] = React.useState(0);
const [inputPrice, setInputPrice] = React.useState(0);
return (
<SmallPaperLayout>
<Typography variant="h4">Add New Goods</Typography>
<hr />
<Stack spacing={2}>
<TextField
value={inputName}
onChange={(event) => setInputName(event.target.value)}
label="Product name"
/>
<TextField
value={inputDescription}
onChange={(event) => setInputDescription(event.target.value)}
label="Product description"
/>
<TextField
value={inputMarketId}
onChange={(event) => setInputMarketId(event.target.value)}
label="Market ID"
type="number"
/>
<TextField
value={inputQuantity}
onChange={(event) => setInputQuantity(event.target.value)}
label="Product quantity"
type="number"
/>
<TextField
value={inputPrice}
onChange={(event) => setInputPrice(event.target.value)}
label="Product price"
type="number"
/>
<Button
variant="contained"
color="warning"
onClick={() => {
post(`/supplier/goods`, {
name: inputName,
description: inputDescription,
market_id: parseInt(inputMarketId),
quantity: parseInt(inputQuantity),
price: inputPrice,
}).then(() => {
alert("Successfully add new goods");
});
}}
>
Add
</Button>
</Stack>
</SmallPaperLayout>
);
};
export default SupplierAddGoods;

View File

@@ -0,0 +1,40 @@
import { Button, Typography } from "@mui/material";
import React from "react";
import BasicLayout from "../layouts/BasicLayout";
import { get } from "../common";
import AutoTable from "../components/AutoTable";
import { Link } from "react-router-dom";
const SupplierGoods = () => {
const [data, setData] = React.useState([]);
const refresh = () => {
get("/supplier/goods").then((resp) => {
// delete unused data
setData(
resp.map((r) => {
delete r.supplier_id;
return r;
})
);
});
};
React.useEffect(() => {
refresh();
}, []);
return (
<BasicLayout>
<Typography variant="h4">Goods management</Typography>
<Typography>The list of your goods</Typography>
<Link to="/supplier/goods/new">
<Button variant="contained" color="info">
New Goods
</Button>
</Link>
<hr />
<AutoTable data={data} />
</BasicLayout>
);
};
export default SupplierGoods;

View File

@@ -0,0 +1,36 @@
import { Box, Typography } from "@mui/material";
import React from "react";
import ImageCard from "../components/ImageCard";
import BasicLayout from "../layouts/BasicLayout";
import supplierGoodsImage from "../../public/images/supplierGoods.webp";
import meetingImage from "../../public/images/meeting.webp";
import userContext from "../context/userContext";
import { Link } from "react-router-dom";
const SupplierPage = () => {
const { user } = React.useContext(userContext);
return (
<BasicLayout>
<Typography variant="h4">Welcome, supplier {user.username}</Typography>
<hr />
<Box style={{ display: "flex", flexWrap: "wrap" }}>
<Link to="/supplier/goods">
<ImageCard
title="Goods Management"
description="Create, View, Update, and Delete your goods"
image={supplierGoodsImage}
/>
</Link>
<Link to="/supplier/report">
<ImageCard
title="Sales Report"
description="Generate your fanaical report"
image={meetingImage}
/>
</Link>
</Box>
</BasicLayout>
);
};
export default SupplierPage;