✨ Register: working order sum logic

💄 Add more icons and load them as react components
This commit is contained in:
nikolark committed 2021-11-01 20:12:30 +01:00
1 parent d86afd9392
commit d5bff90ee3
15 files changed
+1447 -47

No files matched your search

+1165 -1
View File
File diff suppressed because it is too large. Load diff
+5 -1
View File
@@ -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"
} }
} }
+3 -4
View File
@@ -1,4 +1,3 @@
.logo { .app {
width: 100px; margin: 16px;
pointer-events: none; }
}
+1 -2
View File
@@ -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>
+1 -1
View File
@@ -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>
); );
+13
View File
@@ -0,0 +1,13 @@
.logo {
display: inline-block;
width: 24px;
height: 24px;
margin: 0 8px;
position: relative;
top: -1px
}
.navIcon {
position: relative;
top: -2px;
}
+58 -5
View File
@@ -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;
}
+23
View File
@@ -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;
}
+31
View File
@@ -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;
} }
+79 -19
View File
@@ -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>
+2 -1
View File
@@ -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";
+12
View File
@@ -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: "..",
});