Compare commits
2 commits
master
...
heiclone-m
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ac69b72c5 | |||
| d287175249 |
1
data/productGroups.json
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
[{"name":"Maitotuotteet","subgroups":["Lehmänmaito","Vuohenmaito","Cheddar","Mozzarella"]},{"name":"Kukat","subgroups":["Leikkokukat","Rairuohot","Vehnä"]},{"name":"Lihatuotteet","subgroups":["Kana","Lammas","Possu","Kebabeläin"]}]
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
"webpack-dev-server": "^3.10.3"
|
"webpack-dev-server": "^3.10.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"antd": "^4.1.3",
|
"antd": "^4.10.1",
|
||||||
"mobx": "^5.15.4",
|
"mobx": "^5.15.4",
|
||||||
"mobx-react": "^6.2.2",
|
"mobx-react": "^6.2.2",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
|
|
|
||||||
164
src/index.tsx
|
|
@ -1,38 +1,160 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as ReactDOM from 'react-dom';
|
import * as ReactDOM from 'react-dom';
|
||||||
import {observable} from 'mobx';
|
import { observable, action, configure } from 'mobx';
|
||||||
import {observer} from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
|
import "antd/es/layout/style";
|
||||||
|
import Layout from "antd/es/layout";
|
||||||
|
|
||||||
|
import "antd/es/menu/style";
|
||||||
|
import Menu from "antd/es/menu";
|
||||||
|
|
||||||
|
import "antd/es/card/style";
|
||||||
|
import Card from "antd/es/card";
|
||||||
|
|
||||||
|
import "antd/es/grid/style";
|
||||||
|
import { Row, Col } from "antd/es/grid";
|
||||||
|
|
||||||
|
import "antd/es/input/style";
|
||||||
|
import Input from "antd/es/input";
|
||||||
|
|
||||||
|
import { ShoppingCartOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
import * as productGroups from "../data/productGroups.json";
|
||||||
|
|
||||||
|
|
||||||
|
configure({ enforceActions: 'always' });
|
||||||
|
|
||||||
|
|
||||||
|
interface ProductGroup {
|
||||||
|
name: String,
|
||||||
|
subgroups: String[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const PRODUCTGROUPS: ProductGroup[] = productGroups; // from GET productgroups
|
||||||
|
|
||||||
class AppState {
|
class AppState {
|
||||||
@observable timer = 0;
|
@observable productCardsOnDisplay: Array<ProductCard>;
|
||||||
|
|
||||||
constructor() {
|
@action
|
||||||
setInterval(() => {
|
setProductCardsOnDisplay(cards: Array<ProductCard>) {
|
||||||
this.timer += 1;
|
this.productCardsOnDisplay = cards;
|
||||||
}, 1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resetTimer() {
|
async fetchProductCards() {
|
||||||
this.timer = 0;
|
try {
|
||||||
|
const res = await fetch("http://localhost:8000/productCards");
|
||||||
|
const cards = await res.json();
|
||||||
|
this.setProductCardsOnDisplay(cards)
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.setProductCardsOnDisplay([]);
|
||||||
|
this.fetchProductCards();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ProductCard {
|
||||||
|
name: string,
|
||||||
|
thumbnail: string,
|
||||||
|
display_price: string
|
||||||
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class TimerView extends React.Component<{appState: AppState}, {}> {
|
class MainView extends React.Component<{ appState: AppState }, {}> {
|
||||||
|
productRows(products: ProductCard[]) {
|
||||||
|
const cardWidth = 180;
|
||||||
|
let ret: Array<JSX.Element> = [];
|
||||||
|
|
||||||
|
// TODO: there's probably thousand better ways than the initial copy paste rinse and repeat model below to form some rows .. PRs are welcomed
|
||||||
|
|
||||||
|
for (let index = 0; index < products.length; index += 4) {
|
||||||
|
let cols: Array<JSX.Element> = [
|
||||||
|
<Col offset={1}>
|
||||||
|
<Card hoverable style={{ width: cardWidth }} cover={<img alt="example" src={products[index].thumbnail} />}>
|
||||||
|
<Card.Meta title={products[index].name} description={products[index].display_price} />
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
];
|
||||||
|
if (index + 1 < products.length) {
|
||||||
|
cols.push(
|
||||||
|
<Col >
|
||||||
|
<Card hoverable style={{ width: cardWidth }} cover={<img alt="example" src={products[index + 1].thumbnail} />}>
|
||||||
|
<Card.Meta title={products[index + 1].name} description={products[index + 1].display_price} />
|
||||||
|
</Card>
|
||||||
|
</Col>);
|
||||||
|
}
|
||||||
|
if (index + 2 < products.length) {
|
||||||
|
cols.push(
|
||||||
|
<Col>
|
||||||
|
<Card hoverable style={{ width: cardWidth }} cover={<img alt="example" src={products[index + 2].thumbnail} />}>
|
||||||
|
<Card.Meta title={products[index + 2].name} description={products[index + 2].display_price} />
|
||||||
|
</Card>
|
||||||
|
</Col>);
|
||||||
|
}
|
||||||
|
if (index + 3 < products.length) {
|
||||||
|
cols.push(
|
||||||
|
<Col>
|
||||||
|
<Card hoverable style={{ width: cardWidth }} cover={<img alt="example" src={products[index + 3].thumbnail} />}>
|
||||||
|
<Card.Meta title={products[index + 3].name} description={products[index + 3].display_price} />
|
||||||
|
</Card>
|
||||||
|
</Col>);
|
||||||
|
}
|
||||||
|
ret.push(
|
||||||
|
<Row justify="start" gutter={[{ xs: 8, sm: 16, md: 24, lg: 32 }, { xs: 8, sm: 16, md: 24, lg: 32 }]}>
|
||||||
|
{cols}
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<Layout>
|
||||||
<button onClick={this.onReset}>
|
<Layout.Header style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||||
Seconds passed: {this.props.appState.timer}
|
|
||||||
</button>
|
<img src="/static/logo.png" alt="logo" style={{ height: "inherit" }}></img>
|
||||||
</div>
|
|
||||||
);
|
<Menu mode="horizontal" theme="dark">
|
||||||
}
|
{PRODUCTGROUPS.map((group, groupindex) =>
|
||||||
|
<Menu.SubMenu key={groupindex} title={group.name}>
|
||||||
|
{group.subgroups.map((subgroup, subgroupindex) =>
|
||||||
|
<Menu.Item key={subgroupindex}>{subgroup}</Menu.Item>
|
||||||
|
)}
|
||||||
|
</Menu.SubMenu>
|
||||||
|
)}
|
||||||
|
<Menu.SubMenu title="Muuta tietoa">
|
||||||
|
<Menu.Item>Yhteystiedot</Menu.Item>
|
||||||
|
<Menu.Item>Aukioloajat</Menu.Item>
|
||||||
|
</Menu.SubMenu>
|
||||||
|
</Menu>
|
||||||
|
<Input.Search style={{ maxWidth: "20rem" }} />
|
||||||
|
</Layout.Header>
|
||||||
|
<Layout>
|
||||||
|
<Layout.Content>
|
||||||
|
{this.productRows(this.props.appState.productCardsOnDisplay)}
|
||||||
|
</Layout.Content>
|
||||||
|
|
||||||
|
<Layout.Sider theme="light">
|
||||||
|
<ShoppingCartOutlined style={{ fontSize: "6rem", width: "100%" }} />
|
||||||
|
</Layout.Sider>
|
||||||
|
</Layout>
|
||||||
|
<Layout.Footer>
|
||||||
|
<h4>Foobar</h4>
|
||||||
|
</Layout.Footer>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
onReset = () => {
|
|
||||||
this.props.appState.resetTimer();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const appState = new AppState();
|
const appState = new AppState();
|
||||||
ReactDOM.render(<TimerView appState={appState} />, document.getElementById('root'));
|
|
||||||
|
ReactDOM.render(<MainView appState={appState} />, document.getElementById('root'));
|
||||||
|
|
|
||||||
BIN
static/logo.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
static/prod/karhunkieli.jpg
Normal file
|
After Width: | Height: | Size: 6.6 MiB |
BIN
static/prod/mokka.jpg
Normal file
|
After Width: | Height: | Size: 6.6 MiB |
BIN
static/prod/oatly.jpg
Normal file
|
After Width: | Height: | Size: 6.5 MiB |
BIN
static/prod/reko.jpg
Normal file
|
After Width: | Height: | Size: 7.4 MiB |
BIN
static/prod/suopa.jpg
Normal file
|
After Width: | Height: | Size: 6.6 MiB |
BIN
static/prod/t_karhunkieli.jpg
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
static/prod/t_mokka.jpg
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
static/prod/t_oatly.jpg
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
static/prod/t_reko.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
static/prod/t_suopa.jpg
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
static/prod/turkey-180-240.jpg
Normal file
|
After Width: | Height: | Size: 14 KiB |