✨ Register: working order sum logic
💄 Add more icons and load them as react components
This commit is contained in:
1 parent
d86afd9392
commit
d5bff90ee3
15 files changed
+1446
-46
No files matched your search
Generated
+1165
-1
File diff suppressed because it is too large.
Load diff
@@ -16,6 +16,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.4.16",
|
"@apollo/client": "^3.4.16",
|
||||||
|
"bootstrap": "^5.1.3",
|
||||||
|
"bootstrap-icons": "^1.6.1",
|
||||||
|
"clsx": "^1.1.1",
|
||||||
"date-fns": "^2.25.0",
|
"date-fns": "^2.25.0",
|
||||||
"graphql": "^15.7.2",
|
"graphql": "^15.7.2",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
@@ -48,6 +51,7 @@
|
|||||||
"stylelint-order": "^5.0.0",
|
"stylelint-order": "^5.0.0",
|
||||||
"stylelint-prettier": "^1.2.0",
|
"stylelint-prettier": "^1.2.0",
|
||||||
"typescript": "^4.4.4",
|
"typescript": "^4.4.4",
|
||||||
"vite": "^2.6.13"
|
"vite": "^2.6.13",
|
||||||
|
"vite-plugin-react-svg": "^0.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
.logo {
|
.app {
|
||||||
width: 100px;
|
margin: 16px;
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { ApolloProvider } from "@apollo/client";
|
import { ApolloProvider } from "@apollo/client";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { BrowserRouter as Router } from "react-router-dom";
|
import { BrowserRouter as Router } from "react-router-dom";
|
||||||
|
import "bootstrap/dist/css/bootstrap.min.css";
|
||||||
|
|
||||||
import styles from "./App.module.css";
|
import styles from "./App.module.css";
|
||||||
import { Routes } from "./Routes";
|
import { Routes } from "./Routes";
|
||||||
import logo from "./assets/brick.svg";
|
|
||||||
import client from "./client";
|
import client from "./client";
|
||||||
import { Header } from "./components/Header";
|
import { Header } from "./components/Header";
|
||||||
|
|
||||||
@@ -13,7 +13,6 @@ const App: React.FC = () => (
|
|||||||
<Router>
|
<Router>
|
||||||
<Header />
|
<Header />
|
||||||
<div className={styles.app}>
|
<div className={styles.app}>
|
||||||
<img src={logo} className={styles.logo} alt="logo" />
|
|
||||||
<Routes />
|
<Routes />
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ import { Register } from "./features/register/Register";
|
|||||||
export const Routes: React.FC = () => (
|
export const Routes: React.FC = () => (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/deposit" component={Deposit} />
|
<Route path="/deposit" component={Deposit} />
|
||||||
<Route path="/" component={Register} />
|
<Route path="/" exact component={Register} />
|
||||||
</Switch>
|
</Switch>
|
||||||
);
|
);
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
.logo {
|
||||||
|
display: inline-block;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin: 0 8px;
|
||||||
|
position: relative;
|
||||||
|
top: -1px
|
||||||
|
}
|
||||||
|
|
||||||
|
.navIcon {
|
||||||
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
}
|
||||||
@@ -1,13 +1,66 @@
|
|||||||
|
import BarChart from "bootstrap-icons/icons/bar-chart.svg";
|
||||||
|
import CardList from "bootstrap-icons/icons/card-list.svg";
|
||||||
|
import House from "bootstrap-icons/icons/house.svg";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
|
|
||||||
|
import Logo from "../assets/brick.svg";
|
||||||
|
import styles from "./Header.module.css";
|
||||||
|
|
||||||
export const Header: React.FC = () => {
|
export const Header: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<header>
|
<header>
|
||||||
<div>Biceps Bar System - BBS</div>
|
<nav className="navbar navbar-dark bg-dark navbar-expand-md">
|
||||||
<nav>
|
<NavLink to="/" className="navbar-brand">
|
||||||
<Link to="/">Kjøp drikke</Link>
|
<span className={styles.logo}>
|
||||||
<Link to="/deposit">Sett inn penger</Link>
|
<Logo />
|
||||||
|
</span>
|
||||||
|
Biceps Bar System - BBS
|
||||||
|
</NavLink>
|
||||||
|
<button
|
||||||
|
className="navbar-toggler"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#navbarNav"
|
||||||
|
aria-controls="navbarNav"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-label="Toggle navigation"
|
||||||
|
>
|
||||||
|
<span className="navbar-toggler-icon" />
|
||||||
|
</button>
|
||||||
|
<div className="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul className="navbar-nav">
|
||||||
|
<li className="nav-item">
|
||||||
|
<NavLink to="/" className="nav-link" activeClassName="active" exact>
|
||||||
|
<span className={styles.navIcon}>
|
||||||
|
<House />
|
||||||
|
</span>{" "}
|
||||||
|
Kjøp drikke
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item">
|
||||||
|
<NavLink to="/deposit" className="nav-link" activeClassName="active">
|
||||||
|
Sett inn penger
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item">
|
||||||
|
<NavLink to="/log" className="nav-link" activeClassName="active">
|
||||||
|
<span className={styles.navIcon}>
|
||||||
|
<CardList />
|
||||||
|
</span>{" "}
|
||||||
|
Logg
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
<li className="nav-item">
|
||||||
|
<NavLink to="/stats" className="nav-link" activeClassName="active">
|
||||||
|
<span className={styles.navIcon}>
|
||||||
|
<BarChart />
|
||||||
|
</span>{" "}
|
||||||
|
Statistikk
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
.active {
|
||||||
|
background: green;
|
||||||
|
}
|
||||||
|
.product {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.productText {
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
.productImage {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import clsx from "clsx";
|
||||||
|
import React, { MouseEventHandler } from "react";
|
||||||
|
|
||||||
|
import { Product } from "../types";
|
||||||
|
import styles from "./ProductCard.module.css";
|
||||||
|
|
||||||
|
type ProductCardProps = {
|
||||||
|
product: Product;
|
||||||
|
onProduct: MouseEventHandler;
|
||||||
|
active: boolean;
|
||||||
|
amount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ProductCard: React.FC<ProductCardProps> = ({ product, onProduct, amount, active = false }) => {
|
||||||
|
const classes = clsx(styles.product, { [styles.active]: active });
|
||||||
|
return (
|
||||||
|
<button className={classes} type="button" onClick={onProduct}>
|
||||||
|
<span>{amount}</span>
|
||||||
|
{product.imageUrl && <img src={product.imageUrl} className={styles.productImage} alt={product.name} />}
|
||||||
|
<span className={styles.productText}>{product.name}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
.card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.active {
|
||||||
|
background: green;
|
||||||
|
}
|
||||||
|
.avatar {
|
||||||
|
border-radius: 50px;
|
||||||
|
height: 50px;
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
.avatar svg {
|
||||||
|
color: #ccc;
|
||||||
|
height: 50px;
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import PersonFill from "bootstrap-icons/icons/person-fill.svg";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import React, { MouseEventHandler } from "react";
|
||||||
|
|
||||||
|
import { User } from "../types";
|
||||||
|
import styles from "./UserCard.module.css";
|
||||||
|
|
||||||
|
type UserCardProps = {
|
||||||
|
user: User | null;
|
||||||
|
onUser: MouseEventHandler;
|
||||||
|
active: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** If user is null, then it's the special external user */
|
||||||
|
export const UserCard: React.FC<UserCardProps> = ({ user, onUser, active = false }) => {
|
||||||
|
const classes = clsx(styles.card, { [styles.active]: active });
|
||||||
|
return (
|
||||||
|
<button className={classes} onClick={onUser} type="button">
|
||||||
|
{user?.profile?.image && (
|
||||||
|
<img src={user.profile?.image} alt={`Profile for ${user.firstName}`} className={styles.avatar} />
|
||||||
|
)}
|
||||||
|
{!user?.profile?.image && (
|
||||||
|
<span className={styles.avatar}>
|
||||||
|
<PersonFill />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span className={styles.name}>{user?.firstName || "External"}</span>
|
||||||
|
<span className={styles.balance}>{user?.profile?.balance || ""}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,19 +1,23 @@
|
|||||||
|
.userList {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(12, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
}
|
||||||
.productList {
|
.productList {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(6, 1fr);
|
grid-template-columns: repeat(12, 1fr);
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
.product {
|
.checkout {
|
||||||
display: flex;
|
margin-top: 32px;
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
.productText {
|
.total {
|
||||||
padding-top: 8px;
|
display: inline-block;
|
||||||
}
|
margin-right: 8px;
|
||||||
.productImage {
|
padding: 4px 8px;
|
||||||
width: 100px;
|
font-size: 1.8rem;
|
||||||
height: 100px;
|
color: white;
|
||||||
object-fit: contain;
|
background: black;
|
||||||
|
min-width: 200px;
|
||||||
}
|
}
|
||||||
@@ -1,45 +1,105 @@
|
|||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
|
import { ProductCard } from "../../components/ProductCard";
|
||||||
|
import { UserCard } from "../../components/UserCard";
|
||||||
import { Product, User } from "../../types";
|
import { Product, User } from "../../types";
|
||||||
import styles from "./Register.module.css";
|
import styles from "./Register.module.css";
|
||||||
import { useRegister } from "./useRegister";
|
import { useRegister } from "./useRegister";
|
||||||
|
|
||||||
|
const USER_EXTERNAL = "external";
|
||||||
|
|
||||||
|
type OrderLine = {
|
||||||
|
id: string;
|
||||||
|
amount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sumTotal = (products: Product[], order: OrderLine[], isExternal: boolean) => {
|
||||||
|
const prices = order.map((orderLine: OrderLine) => {
|
||||||
|
const product = products.find((product: Product) => product.id === orderLine.id);
|
||||||
|
if (!product) return 0;
|
||||||
|
return orderLine.amount * (isExternal ? product.salePriceExt : product.salePriceInt);
|
||||||
|
});
|
||||||
|
return prices.reduce((partialSum, a) => partialSum + a, 0);
|
||||||
|
};
|
||||||
|
|
||||||
export const Register: React.FC = () => {
|
export const Register: React.FC = () => {
|
||||||
const { users, products, loading, showAll, setShowAll } = useRegister();
|
const { users, products, loading, showAll, setShowAll } = useRegister();
|
||||||
|
const [selectedUser, setSelectedUser] = useState("");
|
||||||
|
const [order, setOrder] = useState<OrderLine[]>([]);
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
setOrder([]);
|
||||||
|
setSelectedUser("");
|
||||||
|
};
|
||||||
|
const addToOrder = (id: string) => {
|
||||||
|
let added = false;
|
||||||
|
const newOrder = order.map((orderLine: OrderLine) => {
|
||||||
|
if (orderLine.id === id) {
|
||||||
|
added = true;
|
||||||
|
return { ...orderLine, amount: orderLine.amount + 1 };
|
||||||
|
}
|
||||||
|
return orderLine;
|
||||||
|
});
|
||||||
|
if (!added) {
|
||||||
|
newOrder.push({ id, amount: 1 });
|
||||||
|
}
|
||||||
|
setOrder(newOrder);
|
||||||
|
};
|
||||||
|
|
||||||
|
const total = sumTotal(products, order, selectedUser === USER_EXTERNAL);
|
||||||
|
|
||||||
|
console.log(selectedUser, order);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<section>
|
<section>
|
||||||
<h2>Hvem kjøper?</h2>
|
<h2>Hvem kjøper?</h2>
|
||||||
<div key="external">Ekstern</div>
|
<div className={styles.userList}>
|
||||||
{users.map((user: User) => {
|
<UserCard
|
||||||
return <div key={user.id}>{user.firstName}</div>;
|
key={USER_EXTERNAL}
|
||||||
})}
|
user={null}
|
||||||
<button type="button" onClick={() => setShowAll(!showAll)}>
|
onUser={() => setSelectedUser(USER_EXTERNAL)}
|
||||||
|
active={selectedUser == USER_EXTERNAL}
|
||||||
|
/>
|
||||||
|
{users.map((user: User) => (
|
||||||
|
<UserCard
|
||||||
|
key={user.id}
|
||||||
|
user={user}
|
||||||
|
onUser={() => setSelectedUser(user.id)}
|
||||||
|
active={selectedUser == user.id}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<button type="button" className="btn btn-link" onClick={() => setShowAll(!showAll)}>
|
||||||
{showAll ? "Skjul gamliser" : "Vis alle"}
|
{showAll ? "Skjul gamliser" : "Vis alle"}
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>Kjøper hva?</h2>
|
<h2>Kjøper hva?</h2>
|
||||||
<div className={styles.productList}>
|
<div className={styles.productList}>
|
||||||
{products.map((product: Product) => {
|
{products.map((product: Product) => (
|
||||||
return (
|
<ProductCard
|
||||||
<div key={product.id} className={styles.product}>
|
key={product.id}
|
||||||
{product.imageUrl && <img src={product.imageUrl} className={styles.productImage} alt={product.name} />}
|
product={product}
|
||||||
<span className={styles.productText}>{product.name}</span>
|
onProduct={() => addToOrder(product.id)}
|
||||||
</div>
|
amount={order.find((orderLine: OrderLine) => orderLine.id === product.id)?.amount || 0}
|
||||||
);
|
active={order.some((orderLine: OrderLine) => orderLine.id === product.id)}
|
||||||
})}
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section className={styles.checkout}>
|
||||||
Totalt: 0
|
<span className={styles.total}>
|
||||||
<button type="button" onClick={() => console.log("kjøp")}>
|
Totalt: <span>{total}</span>
|
||||||
|
</span>
|
||||||
|
<button type="button" className="btn btn-primary btn-lg" onClick={() => console.log("kjøp", order)}>
|
||||||
Kjøp
|
Kjøp
|
||||||
</button>
|
</button>{" "}
|
||||||
<button type="button" onClick={() => console.log("reset")}>
|
<button type="button" className="btn btn-outline-secondary" onClick={reset}>
|
||||||
Start på nytt
|
Start på nytt
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Vendored
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
declare module "*.svg" {
|
declare module "*.svg" {
|
||||||
const content: unknown;
|
import { ReactElement, SVGProps } from "react";
|
||||||
|
const content: (props: SVGProps<SVGElement>) => ReactElement;
|
||||||
export default content;
|
export default content;
|
||||||
}
|
}
|
||||||
declare module "*.module.css";
|
declare module "*.module.css";
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
/* eslint-disable import/no-extraneous-dependencies */
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import reactSvgPlugin from "vite-plugin-react-svg";
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [reactSvgPlugin({ defaultExport: "component" })],
|
||||||
|
define: {
|
||||||
|
__DEV__: process.env.NODE_ENV !== "prodution",
|
||||||
|
},
|
||||||
|
envDir: "..",
|
||||||
|
});
|
||||||
Reference in new issue
Block a user