From 85143520617aee2bdb231ad5db929d954d85826e Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Tue, 30 Nov 2021 16:21:45 +0200 Subject: [PATCH 01/26] Ignore .tomcat folders --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7f0b1dd..16fb120 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,6 @@ dist/ log/ # Skip VisualStudio Code folders -.vscode/ \ No newline at end of file +.vscode/ + +.tomcat/ \ No newline at end of file From a6fe4ac6e4f301370aed41cd78ece18608479b7b Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Tue, 30 Nov 2021 16:23:01 +0200 Subject: [PATCH 02/26] Join Success and Error message columns in table with Upload results together --- cm-frontend/src/pages/UploadPage.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cm-frontend/src/pages/UploadPage.js b/cm-frontend/src/pages/UploadPage.js index 27277c6..21653ec 100644 --- a/cm-frontend/src/pages/UploadPage.js +++ b/cm-frontend/src/pages/UploadPage.js @@ -93,7 +93,7 @@ export default function Upload() { setLoading(false); } ) - }; + } function handleUpload() { if (loading) { @@ -180,8 +180,7 @@ export default function Upload() { Version Action ID - Success - Error message + Result Line count Add Update @@ -195,8 +194,7 @@ export default function Upload() { {u.productCatalogUpdate?.documentVersion} {u.productCatalogUpdate?.document?.actionCode} {u.productCatalogUpdate?.document?.id} - {u.success} - {u.errorMessage} + {u.success ? 'OK' : ('ERROR: ' + u.errorMessage)} {u.lineCount} {u.lineActionStat.ADD} {u.lineActionStat.UPDATE} From 9c0b8deb69922a9bec7e66fc580668963ea06a05 Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Tue, 30 Nov 2021 16:59:20 +0200 Subject: [PATCH 03/26] Clean pom - global java version and project encoding --- pom.xml | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index 7e38376..b841899 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,11 @@ 4.12 1.22 1.2.14 + + 1.8 + ${java.version} + ${java.version} + UTF-8 @@ -68,23 +73,11 @@ 3 - - - org.apache.maven.wagon - wagon-http - 3.0.0 - - org.apache.maven.plugins maven-compiler-plugin 2.3.2 - - 1.8 - 1.8 - UTF-8 - org.apache.maven.plugins From 77769f57911c26a7aac49ae6de584a200bd8acaa Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Tue, 30 Nov 2021 16:59:54 +0200 Subject: [PATCH 04/26] Fix Idea warnings --- cm-frontend/src/components/ProductDetail.js | 41 +++++++++++---------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/cm-frontend/src/components/ProductDetail.js b/cm-frontend/src/components/ProductDetail.js index 0ff9dee..bfec7ef 100644 --- a/cm-frontend/src/components/ProductDetail.js +++ b/cm-frontend/src/components/ProductDetail.js @@ -1,3 +1,5 @@ +// noinspection JSUnresolvedVariable + import { makeStyles } from "@material-ui/core"; import { Fragment } from "react"; import ItemDetailsService from '../services/ItemDetailsService'; @@ -26,6 +28,7 @@ const useStyles = makeStyles(theme => ({ function DataView(props) { const _isValueDefined = (value) => value ? true : false; + // noinspection JSUnusedLocalSymbols const _renderValue = (v, i) => { return v }; const { name, value, isValueDefined = _isValueDefined, renderValue = _renderValue } = props; @@ -104,29 +107,29 @@ export default function ProductView(props) { <> { showTech && ( <> - - - - - - - - + + + + + + + + )} - - - - {return e.id}}> - {return e?.partyName?.name}}> - - - - + + + + {return e.id}}/> + {return e?.partyName?.name}}/> + + + + - - + + ) From d2a131fdced38246079ac3fb6e94c664370bc858 Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Tue, 30 Nov 2021 18:23:03 +0200 Subject: [PATCH 05/26] chore: typo, warnings --- cm-frontend/src/components/ProductListContainer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cm-frontend/src/components/ProductListContainer.js b/cm-frontend/src/components/ProductListContainer.js index b773174..741aa6e 100644 --- a/cm-frontend/src/components/ProductListContainer.js +++ b/cm-frontend/src/components/ProductListContainer.js @@ -22,7 +22,7 @@ const currentPosition = (list, id) => { for (var i = 0; i < list.length; i++) if (list[i].id === id) { list._cachedPos[id] = (i + 1); return i; - }; + } return 0; }; @@ -35,7 +35,7 @@ const currentPosition = (list, id) => { } } -export function ProductListContainer(props) { +export function ProductListContainer() { const [showBanner, setShowBanner] = useStickyState(true, 'dcm-banner'); const [productList, setProductList] = React.useState([]); @@ -74,7 +74,7 @@ export function ProductListContainer(props) { setProductListLoading(false); } ).catch(error => { - console.log('Error occured: ' + error.message); + console.log('Error occurred: ' + error.message); setProductListLoading(false); }); } From d0d1d4b43d5bc6d5c3a73c6a6de67c8a842e2ec2 Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Tue, 30 Nov 2021 18:24:49 +0200 Subject: [PATCH 06/26] start to add button --- cm-frontend/src/components/AddToBasket.js | 26 ++++++++++++++++++ cm-frontend/src/components/ProductDetail.js | 29 +++++++++++++++++---- 2 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 cm-frontend/src/components/AddToBasket.js diff --git a/cm-frontend/src/components/AddToBasket.js b/cm-frontend/src/components/AddToBasket.js new file mode 100644 index 0000000..b6f36c0 --- /dev/null +++ b/cm-frontend/src/components/AddToBasket.js @@ -0,0 +1,26 @@ +import {Button} from "@material-ui/core"; +import React, {useEffect, useRef} from "react"; + +export default function AddToBasket() { + + const [state, setState] = React.useState('empty'); + + const timerRef = useRef(null); + + function handleAdd() { + setState('adding'); + timerRef.current = setTimeout(() => { + setState('added') + }, 500); + } + + useEffect(() => { + return () => clearTimeout(timerRef.current) + }, []); + + return ( + <> + + + ) +} \ No newline at end of file diff --git a/cm-frontend/src/components/ProductDetail.js b/cm-frontend/src/components/ProductDetail.js index bfec7ef..699e4c6 100644 --- a/cm-frontend/src/components/ProductDetail.js +++ b/cm-frontend/src/components/ProductDetail.js @@ -5,6 +5,7 @@ import { Fragment } from "react"; import ItemDetailsService from '../services/ItemDetailsService'; import CatalogBadge from "./CatalogBadge"; import ProductPictureList from './ProductPictureList'; +import AddToBasket from "./AddToBasket"; const useStyles = makeStyles(theme => ({ row: { @@ -25,6 +26,19 @@ const useStyles = makeStyles(theme => ({ }, })); +function DataRow(props) { + const { name, children } = props; + + const classes = useStyles(); + + return ( +
+
{name}
+
{children}
+
+ ) +} + function DataView(props) { const _isValueDefined = (value) => value ? true : false; @@ -32,14 +46,10 @@ function DataView(props) { const _renderValue = (v, i) => { return v }; const { name, value, isValueDefined = _isValueDefined, renderValue = _renderValue } = props; - const classes = useStyles(); return ( <> {isValueDefined(value) ? ( -
-
{name}
-
{renderValue(value)}
-
+ {renderValue(value)} ) : (<>)} ) @@ -101,10 +111,19 @@ export default function ProductView(props) { const showTech = false; + const showOrdering = true; + const { product } = props; return ( <> + { showOrdering && ( + <> + + + + + )} { showTech && ( <> From c8093cb27ebdbba7583fb0316dfe35a830766988 Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 11:58:09 +0200 Subject: [PATCH 07/26] AddToBasket - use enums with state, change button name on state change, show progress on adding --- cm-frontend/src/components/AddToBasket.js | 48 +++++++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/cm-frontend/src/components/AddToBasket.js b/cm-frontend/src/components/AddToBasket.js index b6f36c0..9b3f657 100644 --- a/cm-frontend/src/components/AddToBasket.js +++ b/cm-frontend/src/components/AddToBasket.js @@ -1,17 +1,44 @@ import {Button} from "@material-ui/core"; import React, {useEffect, useRef} from "react"; +import CircularProgress from "@material-ui/core/CircularProgress"; + + +const ProductBasketStatus = { + Empty: 'empty', + Adding: 'adding', + Added: 'added', +} export default function AddToBasket() { - const [state, setState] = React.useState('empty'); + const [state, setState] = React.useState(ProductBasketStatus.Empty); - const timerRef = useRef(null); + const getButtonTitle = () => { + switch (state) { + case ProductBasketStatus.Adding: + return 'Adding to basket'; + case ProductBasketStatus.Added: + return 'Remove from basket'; + default: + return 'Add to basket'; + } + } - function handleAdd() { - setState('adding'); - timerRef.current = setTimeout(() => { - setState('added') - }, 500); + const isProgress = () => { + return state === ProductBasketStatus.Adding; + } + + // TODO: Remove - temporary code to imitate slow adding + const timerRef = useRef(null); + const handleClick = () => { + if (state === ProductBasketStatus.Empty) { + setState(ProductBasketStatus.Adding); + timerRef.current = setTimeout(() => { + setState(ProductBasketStatus.Added) + }, 800); + } else if (state === ProductBasketStatus.Added) { + setState(ProductBasketStatus.Empty); + } } useEffect(() => { @@ -20,7 +47,12 @@ export default function AddToBasket() { return ( <> - + ) } \ No newline at end of file From 0e128ec0135f8f2c6659f20a5098aae2f968b2a1 Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 11:59:03 +0200 Subject: [PATCH 08/26] Add dummy BasketBar to Top navigation --- cm-frontend/src/components/BasketBar.js | 26 +++++++++++++++++++++++++ cm-frontend/src/components/TopNav.js | 9 +++++++++ 2 files changed, 35 insertions(+) create mode 100644 cm-frontend/src/components/BasketBar.js diff --git a/cm-frontend/src/components/BasketBar.js b/cm-frontend/src/components/BasketBar.js new file mode 100644 index 0000000..28c8394 --- /dev/null +++ b/cm-frontend/src/components/BasketBar.js @@ -0,0 +1,26 @@ +import React from 'react'; +import {makeStyles} from '@material-ui/core/styles'; +import ShoppingBasketIcon from '@material-ui/icons/ShoppingBasket'; + +const useStyles = makeStyles((theme) => ({ + basket: { + padding: theme.spacing(0, 2), + }, + basketIcon: { + color: theme.palette.common.white + }, +})); + +export default function BasketBar() { + const classes = useStyles(); + + return ( + +
+
+ +
+
+ + ) +} \ No newline at end of file diff --git a/cm-frontend/src/components/TopNav.js b/cm-frontend/src/components/TopNav.js index 26a8fa5..9aaaf4f 100644 --- a/cm-frontend/src/components/TopNav.js +++ b/cm-frontend/src/components/TopNav.js @@ -7,6 +7,7 @@ import Button from '@material-ui/core/Button'; import { Link } from 'react-router-dom'; import SearchBar from './SearchBar'; import './TopNav.css'; +import BasketBar from "./BasketBar"; const useStyles = makeStyles((theme) => ({ root: { @@ -28,6 +29,11 @@ link: { color: "#d3d3d3" } }, + basketBar: { + [theme.breakpoints.down('xs')]: { + margin: theme.spacing(2), + } + }, searchBar: { [theme.breakpoints.down('xs')]: { margin: theme.spacing(2), @@ -60,6 +66,9 @@ export default function TopNav(props) {
+
+ +
From 064306b0503e5c4ccff7f96fe4d0884940983ab2 Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 12:56:11 +0200 Subject: [PATCH 09/26] Change visibility of BasketBar depending on number of items in BasketData, add/remove/change quantity per product. Tried to use a data class for this - but had issues with React.useState on object class... --- cm-frontend/src/components/AddToBasket.js | 8 ++++-- cm-frontend/src/components/BasketData.js | 15 +++++++++++ cm-frontend/src/components/ProductDetail.js | 4 +-- .../src/components/ProductListContainer.js | 25 ++++++++++++++++--- cm-frontend/src/components/TopNav.js | 4 ++- cm-frontend/src/pages/ProductDetailPage.js | 4 +-- 6 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 cm-frontend/src/components/BasketData.js diff --git a/cm-frontend/src/components/AddToBasket.js b/cm-frontend/src/components/AddToBasket.js index 9b3f657..273c800 100644 --- a/cm-frontend/src/components/AddToBasket.js +++ b/cm-frontend/src/components/AddToBasket.js @@ -9,7 +9,9 @@ const ProductBasketStatus = { Added: 'added', } -export default function AddToBasket() { +export default function AddToBasket(props) { + + const {changeBasket, product} = props; const [state, setState] = React.useState(ProductBasketStatus.Empty); @@ -34,9 +36,11 @@ export default function AddToBasket() { if (state === ProductBasketStatus.Empty) { setState(ProductBasketStatus.Adding); timerRef.current = setTimeout(() => { + changeBasket(product.id, 1); setState(ProductBasketStatus.Added) - }, 800); + }, 400); } else if (state === ProductBasketStatus.Added) { + changeBasket(product.id, 0); setState(ProductBasketStatus.Empty); } } diff --git a/cm-frontend/src/components/BasketData.js b/cm-frontend/src/components/BasketData.js new file mode 100644 index 0000000..3ce7aba --- /dev/null +++ b/cm-frontend/src/components/BasketData.js @@ -0,0 +1,15 @@ +export function createBasketData() { + + class BasketData { + constructor() { + this.orderLines = {}; + this.orderLinesCount = 0; + } + + isEmpty() { + return this.orderLines.length === 0; + } + } + + return new BasketData(); +} \ No newline at end of file diff --git a/cm-frontend/src/components/ProductDetail.js b/cm-frontend/src/components/ProductDetail.js index 699e4c6..6a09134 100644 --- a/cm-frontend/src/components/ProductDetail.js +++ b/cm-frontend/src/components/ProductDetail.js @@ -113,14 +113,14 @@ export default function ProductView(props) { const showOrdering = true; - const { product } = props; + const { product, changeBasket } = props; return ( <> { showOrdering && ( <> - + )} diff --git a/cm-frontend/src/components/ProductListContainer.js b/cm-frontend/src/components/ProductListContainer.js index 741aa6e..6b12280 100644 --- a/cm-frontend/src/components/ProductListContainer.js +++ b/cm-frontend/src/components/ProductListContainer.js @@ -19,7 +19,7 @@ const currentPosition = (list, id) => { if (!list) { return 0; } - for (var i = 0; i < list.length; i++) if (list[i].id === id) { + for (let i = 0; i < list.length; i++) if (list[i].id === id) { list._cachedPos[id] = (i + 1); return i; } @@ -44,6 +44,25 @@ export function ProductListContainer() { const [productListTotal, setProductListTotal] = React.useState(0); const [productListLoading, setProductListLoading] = React.useState(false); + const [basketData, setBasketData] = React.useState({orderLines: {}, orderLinesCount: 0}); + const changeBasket = (productId, quantity) => { + let newOrderLines = {...basketData.orderLines}; + let newOrderLinesCount = basketData.orderLinesCount; + if (productId in newOrderLines) { + if (quantity > 0) { + newOrderLines[productId] += quantity; + } else { + delete newOrderLines[productId] + newOrderLinesCount--; + } + } else { + newOrderLines[productId] = quantity; + newOrderLinesCount++; + } + console.log(newOrderLines); + setBasketData({orderLines: newOrderLines, orderLinesCount: newOrderLinesCount}); + } + const setBannerClosed = () => { setShowBanner(false); }; @@ -81,14 +100,14 @@ export function ProductListContainer() { return ( <> - + - + ); diff --git a/cm-frontend/src/components/TopNav.js b/cm-frontend/src/components/TopNav.js index 9aaaf4f..21c999c 100644 --- a/cm-frontend/src/components/TopNav.js +++ b/cm-frontend/src/components/TopNav.js @@ -52,7 +52,7 @@ link: { export default function TopNav(props) { const classes = useStyles(); - const { aboutAction, searchAction } = props; + const { aboutAction, searchAction, showBasketBar } = props; return (
@@ -66,9 +66,11 @@ export default function TopNav(props) {
+ { showBasketBar && (
+ )}
diff --git a/cm-frontend/src/pages/ProductDetailPage.js b/cm-frontend/src/pages/ProductDetailPage.js index 8e22953..69f49a3 100644 --- a/cm-frontend/src/pages/ProductDetailPage.js +++ b/cm-frontend/src/pages/ProductDetailPage.js @@ -35,7 +35,7 @@ function ViewToggle(props) { export default function ProductDetailPage(props) { - const { navigator } = props; + const { navigator, changeBasket } = props; let { id } = useParams(); @@ -78,7 +78,7 @@ export default function ProductDetailPage(props) { {viewMode === "json" ? (
{JSON.stringify(data, null, 2)}
) : ( - + ) } From fa8c8732f89f6a7c49076cbfd1666418fa0e2268 Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 13:11:18 +0200 Subject: [PATCH 10/26] Use external class BasketData to manage basket state and reference it in React.useState --- cm-frontend/src/components/BasketData.js | 25 ++- .../src/components/ProductListContainer.js | 161 +++++++++--------- 2 files changed, 99 insertions(+), 87 deletions(-) diff --git a/cm-frontend/src/components/BasketData.js b/cm-frontend/src/components/BasketData.js index 3ce7aba..5a2e78a 100644 --- a/cm-frontend/src/components/BasketData.js +++ b/cm-frontend/src/components/BasketData.js @@ -1,13 +1,30 @@ export function createBasketData() { class BasketData { - constructor() { - this.orderLines = {}; - this.orderLinesCount = 0; + constructor(orderLines = {}, orderLinesCount = 0) { + this.orderLines = orderLines; + this.orderLinesCount = orderLinesCount; } isEmpty() { - return this.orderLines.length === 0; + return this.orderLinesCount === 0; + } + + changeBasket(productId, quantity) { + let newOrderLines = {...this.orderLines}; + let newOrderLinesCount = this.orderLinesCount; + if (productId in newOrderLines) { + if (quantity > 0) { + newOrderLines[productId] += quantity; + } else { + delete newOrderLines[productId] + newOrderLinesCount--; + } + } else { + newOrderLines[productId] = quantity; + newOrderLinesCount++; + } + return new BasketData(newOrderLines, newOrderLinesCount); } } diff --git a/cm-frontend/src/components/ProductListContainer.js b/cm-frontend/src/components/ProductListContainer.js index 6b12280..ba0361d 100644 --- a/cm-frontend/src/components/ProductListContainer.js +++ b/cm-frontend/src/components/ProductListContainer.js @@ -1,5 +1,5 @@ import React from "react"; -import { Route, useHistory } from "react-router-dom"; +import {Route, useHistory} from "react-router-dom"; import ProductListPage from "../pages/ProductListPage"; import Banner from "./Banner"; import UploadPage from "../pages/UploadPage"; @@ -7,108 +7,103 @@ import ProductDetailPage from "../pages/ProductDetailPage"; import TopNav from "./TopNav"; import DataService from "../services/DataService"; import useStickyState from '../utils/useStickyState'; +import {createBasketData} from "./BasketData"; const currentPosition = (list, id) => { if (list._cachedPos) { - if (list._cachedPos[id]) { - return list._cachedPos[id] - 1; // Cache position with + 1 - so 0 is not considered as absent - } + if (list._cachedPos[id]) { + return list._cachedPos[id] - 1; // Cache position with + 1 - so 0 is not considered as absent + } } else { - list._cachedPos = {}; + list._cachedPos = {}; } if (!list) { - return 0; + return 0; } for (let i = 0; i < list.length; i++) if (list[i].id === id) { - list._cachedPos[id] = (i + 1); - return i; + list._cachedPos[id] = (i + 1); + return i; } return 0; - }; - - export const listNavigator = (list) => { +}; + +export const listNavigator = (list) => { return { - hasNext: (id) => { return currentPosition(list, id) < list.length - 1}, - hasPrevious: (id) => { return currentPosition(list, id) > 0}, - getNext: (id) => { return '/product/view/'+ list[currentPosition(list, id)+1].id}, - getPrevious: (id) => { return '/product/view/'+ list[currentPosition(list, id)-1].id}, + hasNext: (id) => { + return currentPosition(list, id) < list.length - 1 + }, + hasPrevious: (id) => { + return currentPosition(list, id) > 0 + }, + getNext: (id) => { + return '/product/view/' + list[currentPosition(list, id) + 1].id + }, + getPrevious: (id) => { + return '/product/view/' + list[currentPosition(list, id) - 1].id + }, } - } +} export function ProductListContainer() { - const [showBanner, setShowBanner] = useStickyState(true, 'dcm-banner'); - const [productList, setProductList] = React.useState([]); - const [productListPage, setProductListPage] = React.useState(0); - const [productListPageSize, setProductListPageSize] = React.useState(20); - const [productListTotal, setProductListTotal] = React.useState(0); - const [productListLoading, setProductListLoading] = React.useState(false); + const [showBanner, setShowBanner] = useStickyState(true, 'dcm-banner'); + const [productList, setProductList] = React.useState([]); + const [productListPage, setProductListPage] = React.useState(0); + const [productListPageSize, setProductListPageSize] = React.useState(20); + const [productListTotal, setProductListTotal] = React.useState(0); + const [productListLoading, setProductListLoading] = React.useState(false); - const [basketData, setBasketData] = React.useState({orderLines: {}, orderLinesCount: 0}); - const changeBasket = (productId, quantity) => { - let newOrderLines = {...basketData.orderLines}; - let newOrderLinesCount = basketData.orderLinesCount; - if (productId in newOrderLines) { - if (quantity > 0) { - newOrderLines[productId] += quantity; - } else { - delete newOrderLines[productId] - newOrderLinesCount--; - } - } else { - newOrderLines[productId] = quantity; - newOrderLinesCount++; - } - console.log(newOrderLines); - setBasketData({orderLines: newOrderLines, orderLinesCount: newOrderLinesCount}); - } + const [basketData, setBasketData] = React.useState(createBasketData()); + const changeBasket = (productId, quantity) => { + setBasketData(basketData.changeBasket(productId, quantity)); + } - const setBannerClosed = () => { - setShowBanner(false); - }; - const setBannerOpened = () => { - setShowBanner(true); - }; + const setBannerClosed = () => { + setShowBanner(false); + }; + const setBannerOpened = () => { + setShowBanner(true); + }; - const history = useHistory(); - const searchAction = (...params) => { - history.push('/'); - loadProducts(...params); - }; + const history = useHistory(); + const searchAction = (...params) => { + history.push('/'); + loadProducts(...params); + }; - React.useEffect(() => { - loadProducts(); - }, []); + React.useEffect(() => { + loadProducts(); + }, []); - async function loadProducts(search = null, page = 0, size = 20) { - console.log("Load products: "+search+" "+page+" "+size); - setProductListLoading(true); - await DataService.fetchProducts(search, page, size).then(response => { - let responseData = response.data; - console.log(responseData); - setProductList(responseData.content); - setProductListPage(responseData.number); - setProductListPageSize(responseData.size); - setProductListTotal(responseData.totalElements); - setProductListLoading(false); + async function loadProducts(search = null, page = 0, size = 20) { + console.log("Load products: " + search + " " + page + " " + size); + setProductListLoading(true); + await DataService.fetchProducts(search, page, size).then(response => { + let responseData = response.data; + console.log(responseData); + setProductList(responseData.content); + setProductListPage(responseData.number); + setProductListPageSize(responseData.size); + setProductListTotal(responseData.totalElements); + setProductListLoading(false); + } + ).catch(error => { + console.log('Error occurred: ' + error.message); + setProductListLoading(false); + }); } - ).catch(error => { - console.log('Error occurred: ' + error.message); - setProductListLoading(false); - }); - } - return ( - <> - - - - - - - - - - - ); + return ( + <> + + + + + + + + + + + ); } From f38ed4b38333abbdc906c6e9fd671e5362d05c3d Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 13:28:14 +0200 Subject: [PATCH 11/26] One loading product, show correct basket status; move ProductBasketStatus to BasketData; pass changeBasket and basketData through intermediate components to AddToBasket via ...props without explicitly naming parameters. --- cm-frontend/src/components/AddToBasket.js | 11 +++-------- cm-frontend/src/components/BasketData.js | 10 ++++++++++ cm-frontend/src/components/ProductDetail.js | 4 ++-- cm-frontend/src/components/ProductListContainer.js | 2 +- cm-frontend/src/pages/ProductDetailPage.js | 4 ++-- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/cm-frontend/src/components/AddToBasket.js b/cm-frontend/src/components/AddToBasket.js index 273c800..fbfba0f 100644 --- a/cm-frontend/src/components/AddToBasket.js +++ b/cm-frontend/src/components/AddToBasket.js @@ -1,19 +1,14 @@ import {Button} from "@material-ui/core"; import React, {useEffect, useRef} from "react"; import CircularProgress from "@material-ui/core/CircularProgress"; +import {ProductBasketStatus} from "./BasketData"; -const ProductBasketStatus = { - Empty: 'empty', - Adding: 'adding', - Added: 'added', -} - export default function AddToBasket(props) { - const {changeBasket, product} = props; + const {changeBasket, basketData, product} = props; - const [state, setState] = React.useState(ProductBasketStatus.Empty); + const [state, setState] = React.useState(basketData.getProductBasketStatus(product.id)); const getButtonTitle = () => { switch (state) { diff --git a/cm-frontend/src/components/BasketData.js b/cm-frontend/src/components/BasketData.js index 5a2e78a..a78fa8d 100644 --- a/cm-frontend/src/components/BasketData.js +++ b/cm-frontend/src/components/BasketData.js @@ -1,3 +1,9 @@ +export const ProductBasketStatus = { + Empty: 'empty', + Adding: 'adding', + Added: 'added', +} + export function createBasketData() { class BasketData { @@ -26,6 +32,10 @@ export function createBasketData() { } return new BasketData(newOrderLines, newOrderLinesCount); } + + getProductBasketStatus(productId) { + return productId in this.orderLines ? ProductBasketStatus.Added : ProductBasketStatus.Empty; + } } return new BasketData(); diff --git a/cm-frontend/src/components/ProductDetail.js b/cm-frontend/src/components/ProductDetail.js index 6a09134..180ec75 100644 --- a/cm-frontend/src/components/ProductDetail.js +++ b/cm-frontend/src/components/ProductDetail.js @@ -113,14 +113,14 @@ export default function ProductView(props) { const showOrdering = true; - const { product, changeBasket } = props; + const { product } = props; return ( <> { showOrdering && ( <> - + )} diff --git a/cm-frontend/src/components/ProductListContainer.js b/cm-frontend/src/components/ProductListContainer.js index ba0361d..490c487 100644 --- a/cm-frontend/src/components/ProductListContainer.js +++ b/cm-frontend/src/components/ProductListContainer.js @@ -102,7 +102,7 @@ export function ProductListContainer() { - + ); diff --git a/cm-frontend/src/pages/ProductDetailPage.js b/cm-frontend/src/pages/ProductDetailPage.js index 69f49a3..2138073 100644 --- a/cm-frontend/src/pages/ProductDetailPage.js +++ b/cm-frontend/src/pages/ProductDetailPage.js @@ -35,7 +35,7 @@ function ViewToggle(props) { export default function ProductDetailPage(props) { - const { navigator, changeBasket } = props; + const { navigator } = props; let { id } = useParams(); @@ -78,7 +78,7 @@ export default function ProductDetailPage(props) { {viewMode === "json" ? (
{JSON.stringify(data, null, 2)}
) : ( - + ) } From b67255ced31c37baf5717af149754355a1577f15 Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 13:53:46 +0200 Subject: [PATCH 12/26] Add basket page with product ids and quantities --- .../src/components/ProductListContainer.js | 4 + cm-frontend/src/components/TopNav.js | 4 +- cm-frontend/src/pages/BasketPage.js | 101 ++++++++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 cm-frontend/src/pages/BasketPage.js diff --git a/cm-frontend/src/components/ProductListContainer.js b/cm-frontend/src/components/ProductListContainer.js index 490c487..690e5e7 100644 --- a/cm-frontend/src/components/ProductListContainer.js +++ b/cm-frontend/src/components/ProductListContainer.js @@ -8,6 +8,7 @@ import TopNav from "./TopNav"; import DataService from "../services/DataService"; import useStickyState from '../utils/useStickyState'; import {createBasketData} from "./BasketData"; +import BasketPage from "../pages/BasketPage"; const currentPosition = (list, id) => { if (list._cachedPos) { @@ -100,6 +101,9 @@ export function ProductListContainer() { + + + diff --git a/cm-frontend/src/components/TopNav.js b/cm-frontend/src/components/TopNav.js index 21c999c..7969fbf 100644 --- a/cm-frontend/src/components/TopNav.js +++ b/cm-frontend/src/components/TopNav.js @@ -67,9 +67,9 @@ export default function TopNav(props) {
{ showBasketBar && ( -
+ -
+ )}
diff --git a/cm-frontend/src/pages/BasketPage.js b/cm-frontend/src/pages/BasketPage.js new file mode 100644 index 0000000..3808f3b --- /dev/null +++ b/cm-frontend/src/pages/BasketPage.js @@ -0,0 +1,101 @@ +import React from "react"; +import {makeStyles, withStyles} from "@material-ui/core/styles"; +import Table from "@material-ui/core/Table"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableContainer from "@material-ui/core/TableContainer"; +import TableHead from "@material-ui/core/TableHead"; +import TableRow from "@material-ui/core/TableRow"; +import Paper from "@material-ui/core/Paper"; +import CircularProgress from "@material-ui/core/CircularProgress"; +import {useHistory} from "react-router"; +import ProductListHeader from '../components/ProductListHeader'; + +const useStyles = makeStyles(theme => ({ + table: { + minWidth: 600, + }, + header: { + marginBottom: '1em' + }, + paper: { + padding: theme.spacing(2), + paddingBottom: theme.spacing(5), + marginBottom: theme.spacing(3), + } +})); + +const StyledTableCell = withStyles(() => ({ + head: { + fontWeight: 'bold', + }, +}))(TableCell); + +const StyledTableRow = withStyles((theme) => ({ + root: { + '&:nth-of-type(odd)': { + backgroundColor: theme.palette.action.hover, + }, + '&:hover': { + backgroundColor: 'rgba(0,0,0,.08)', + }, + cursor: 'pointer' + }, +}))(TableRow); + +export default function BasketPage(props) { + + const {basketData, changeBasket} = props; + + const [isLoading, setLoading] = React.useState(false); + + const classes = useStyles(); + + const {push} = useHistory(); + + const refreshAction = () => { + + } + + const showRowDetails = (productId) => { + push('/product/view/' + productId); + } + + return ( + <> + + + {(isLoading || false) ? ( + + ) : ( + <> + + + + + # + Product id + Quantity + + + + {(!basketData.isEmpty()) ? Object.keys(basketData.orderLines).map((productId, index) => ( + showRowDetails(productId)}> + {(index + 1)} + {productId} + {basketData.orderLines[productId]} + + )) : ( + + Basket is empty + + )} + +
+
+ + )} +
+ + ); +} From 467664b285490d285c703c9ba92559868c69d355 Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 14:13:35 +0200 Subject: [PATCH 13/26] Rename ProductListHeader to PageHeader, add support for nested action buttons, add mouse-over titles to buttons --- .../{ProductListHeader.js => PageHeader.js} | 101 +++++++++--------- .../src/components/ProductDetailHeader.js | 41 ++++--- cm-frontend/src/pages/BasketPage.js | 17 +-- cm-frontend/src/pages/ProductListPage.js | 5 +- 4 files changed, 91 insertions(+), 73 deletions(-) rename cm-frontend/src/components/{ProductListHeader.js => PageHeader.js} (84%) diff --git a/cm-frontend/src/components/ProductListHeader.js b/cm-frontend/src/components/PageHeader.js similarity index 84% rename from cm-frontend/src/components/ProductListHeader.js rename to cm-frontend/src/components/PageHeader.js index 6e965d1..2621d44 100644 --- a/cm-frontend/src/components/ProductListHeader.js +++ b/cm-frontend/src/components/PageHeader.js @@ -1,50 +1,53 @@ -import { Card, CardContent, Fab, makeStyles, Typography } from "@material-ui/core"; -import RefreshIcon from '@material-ui/icons/Refresh'; - -const useStyles = makeStyles(theme => ({ - cardContent: { - '&:last-child': { - paddingBottom: theme.spacing(2), - }, - }, - row: { - display: "flex", - }, - header: { - flex: '1', - }, - buttons: { - flex: '1', - display: "flex", - placeContent: 'stretch flex-end', - alignItems: 'stretch', - - '& button': { - marginLeft: theme.spacing(4), - } - } -})); - -export default function ProductListHeader(prop) { - const { name, refreshAction } = prop; - - const classes = useStyles(); - - return ( - - -
-
- {name} -
- -
- - refreshAction()}/> - -
-
-
-
- ) +import { Card, CardContent, Fab, makeStyles, Typography } from "@material-ui/core"; +import RefreshIcon from '@material-ui/icons/Refresh'; + +const useStyles = makeStyles(theme => ({ + cardContent: { + '&:last-child': { + paddingBottom: theme.spacing(2), + }, + }, + row: { + display: "flex", + }, + header: { + flex: '1', + }, + buttons: { + flex: '1', + display: "flex", + placeContent: 'stretch flex-end', + alignItems: 'stretch', + + '& button': { + marginLeft: theme.spacing(4), + } + } +})); + +export default function PageHeader(prop) { + const { name, refreshAction, children } = prop; + + const classes = useStyles(); + + return ( + + +
+
+ {name} +
+ +
+ {refreshAction && ( + + refreshAction()}/> + + )} + {children} +
+
+
+
+ ) } \ No newline at end of file diff --git a/cm-frontend/src/components/ProductDetailHeader.js b/cm-frontend/src/components/ProductDetailHeader.js index 085484a..2f8d823 100644 --- a/cm-frontend/src/components/ProductDetailHeader.js +++ b/cm-frontend/src/components/ProductDetailHeader.js @@ -1,7 +1,7 @@ -import { Card, CardContent, Fab, makeStyles, Typography } from "@material-ui/core"; +import {Card, CardContent, Fab, makeStyles, Typography} from "@material-ui/core"; import ArrowIcon from '@material-ui/icons/KeyboardBackspaceOutlined'; import RefreshIcon from '@material-ui/icons/Refresh'; -import { useHistory } from "react-router"; +import {useHistory} from "react-router"; const useStyles = makeStyles(theme => ({ @@ -31,16 +31,25 @@ const useStyles = makeStyles(theme => ({ } })); +// noinspection JSUnusedLocalSymbols const _emptyNavigator = { - hasNext: (id) => {return false}, - hasPrevious: (id) => {return false}, - getNext: (id) => { return null}, - getPrevious: (id) => { return null}, + hasNext: (id) => { + return false + }, + hasPrevious: (id) => { + return false + }, + getNext: (id) => { + return null + }, + getPrevious: (id) => { + return null + }, } export default function ProductDetailHeader(prop) { - const { name, navigator = _emptyNavigator, id, refreshAction } = prop; + const {name, navigator = _emptyNavigator, id, refreshAction} = prop; const classes = useStyles(); @@ -55,24 +64,24 @@ export default function ProductDetailHeader(prop) { } return ( - + -
+
{name}
- navigateTo(navigator.getPrevious(id)) } > - + navigateTo(navigator.getPrevious(id))}> + - navigateTo(navigator.getNext(id)) } > - + navigateTo(navigator.getNext(id))}> + - refreshAction(id)}> + refreshAction(id)}> - - + +
diff --git a/cm-frontend/src/pages/BasketPage.js b/cm-frontend/src/pages/BasketPage.js index 3808f3b..067f668 100644 --- a/cm-frontend/src/pages/BasketPage.js +++ b/cm-frontend/src/pages/BasketPage.js @@ -9,7 +9,9 @@ import TableRow from "@material-ui/core/TableRow"; import Paper from "@material-ui/core/Paper"; import CircularProgress from "@material-ui/core/CircularProgress"; import {useHistory} from "react-router"; -import ProductListHeader from '../components/ProductListHeader'; +import PageHeader from '../components/PageHeader'; +import {Fab} from "@material-ui/core"; +import SendIcon from "@material-ui/icons/Send"; const useStyles = makeStyles(theme => ({ table: { @@ -53,9 +55,8 @@ export default function BasketPage(props) { const {push} = useHistory(); - const refreshAction = () => { - - } + const refreshAction = () => {} + const sendAction = () => {} const showRowDetails = (productId) => { push('/product/view/' + productId); @@ -63,7 +64,11 @@ export default function BasketPage(props) { return ( <> - + + + sendAction()}/> + + {(isLoading || false) ? ( @@ -87,7 +92,7 @@ export default function BasketPage(props) { )) : ( - Basket is empty + Basket is empty )} diff --git a/cm-frontend/src/pages/ProductListPage.js b/cm-frontend/src/pages/ProductListPage.js index 53f1ee4..8795b44 100644 --- a/cm-frontend/src/pages/ProductListPage.js +++ b/cm-frontend/src/pages/ProductListPage.js @@ -11,7 +11,7 @@ import CircularProgress from "@material-ui/core/CircularProgress"; import TablePagination from "@material-ui/core/TablePagination"; import { useHistory } from "react-router"; import ItemDetailsService from "../services/ItemDetailsService"; -import ProductListHeader from '../components/ProductListHeader'; +import PageHeader from '../components/PageHeader'; const useStyles = makeStyles(theme => ({ table: { @@ -27,6 +27,7 @@ const useStyles = makeStyles(theme => ({ } })); +// noinspection JSUnusedLocalSymbols const StyledTableCell = withStyles((theme) => ({ head: { fontWeight: 'bold', @@ -66,7 +67,7 @@ export default function ProductListPage(props) { return ( <> - + {(isLoading || false) ? ( From d8bf5533e29973fae310b8bb2df6931f5c4c1592 Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 14:14:10 +0200 Subject: [PATCH 14/26] Fix small screen basket icon position --- cm-frontend/src/components/TopNav.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cm-frontend/src/components/TopNav.js b/cm-frontend/src/components/TopNav.js index 7969fbf..d11e57f 100644 --- a/cm-frontend/src/components/TopNav.js +++ b/cm-frontend/src/components/TopNav.js @@ -30,9 +30,9 @@ link: { } }, basketBar: { - [theme.breakpoints.down('xs')]: { + [theme.breakpoints.up('xs')]: { margin: theme.spacing(2), - } + }, }, searchBar: { [theme.breakpoints.down('xs')]: { @@ -65,12 +65,12 @@ export default function TopNav(props) { -
{ showBasketBar && ( )} +
From 8aec03101bf7195a0ed101b0dfc9883701fc8537 Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 15:03:44 +0200 Subject: [PATCH 15/26] Hide internal implementation of BasketData - add getOrderLineList() method --- cm-frontend/src/components/BasketData.js | 6 ++++++ cm-frontend/src/pages/BasketPage.js | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cm-frontend/src/components/BasketData.js b/cm-frontend/src/components/BasketData.js index a78fa8d..06b9fbd 100644 --- a/cm-frontend/src/components/BasketData.js +++ b/cm-frontend/src/components/BasketData.js @@ -16,6 +16,12 @@ export function createBasketData() { return this.orderLinesCount === 0; } + getOrderLineList() { + return Object.keys(this.orderLines).map((productId) => { + return {productId: productId, quantity: this.orderLines[productId]} + }); + } + changeBasket(productId, quantity) { let newOrderLines = {...this.orderLines}; let newOrderLinesCount = this.orderLinesCount; diff --git a/cm-frontend/src/pages/BasketPage.js b/cm-frontend/src/pages/BasketPage.js index 067f668..d809a1a 100644 --- a/cm-frontend/src/pages/BasketPage.js +++ b/cm-frontend/src/pages/BasketPage.js @@ -84,11 +84,11 @@ export default function BasketPage(props) { - {(!basketData.isEmpty()) ? Object.keys(basketData.orderLines).map((productId, index) => ( - showRowDetails(productId)}> + {!basketData.isEmpty() ? basketData.getOrderLineList().map((orderLine, index) => ( + showRowDetails(orderLine.productId)}> {(index + 1)} - {productId} - {basketData.orderLines[productId]} + {orderLine.productId} + {orderLine.quantity} )) : ( From 47dedac6ecc1ad025ab42875fd12ebe933aea0cc Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 15:36:23 +0200 Subject: [PATCH 16/26] Add plus/minus controls on basket page per product --- cm-frontend/src/components/BasketData.js | 2 +- cm-frontend/src/pages/BasketPage.js | 40 +++++++++++++++++++++--- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/cm-frontend/src/components/BasketData.js b/cm-frontend/src/components/BasketData.js index 06b9fbd..63f20d8 100644 --- a/cm-frontend/src/components/BasketData.js +++ b/cm-frontend/src/components/BasketData.js @@ -26,7 +26,7 @@ export function createBasketData() { let newOrderLines = {...this.orderLines}; let newOrderLinesCount = this.orderLinesCount; if (productId in newOrderLines) { - if (quantity > 0) { + if (quantity !== 0) { newOrderLines[productId] += quantity; } else { delete newOrderLines[productId] diff --git a/cm-frontend/src/pages/BasketPage.js b/cm-frontend/src/pages/BasketPage.js index d809a1a..06a29cc 100644 --- a/cm-frontend/src/pages/BasketPage.js +++ b/cm-frontend/src/pages/BasketPage.js @@ -12,6 +12,8 @@ import {useHistory} from "react-router"; import PageHeader from '../components/PageHeader'; import {Fab} from "@material-ui/core"; import SendIcon from "@material-ui/icons/Send"; +import RemoveIcon from "@material-ui/icons/Remove"; +import AddIcon from "@material-ui/icons/Add"; const useStyles = makeStyles(theme => ({ table: { @@ -45,6 +47,32 @@ const StyledTableRow = withStyles((theme) => ({ }, }))(TableRow); +function QuantityControl(props) { + const {productId, quantity, changeBasket} = props; + + const changeQuantity = (e, quantityChange) => { + e.stopPropagation(); + if (quantity + quantityChange === 0) { + changeBasket(productId, 0); + } else { + changeBasket(productId, quantityChange); + } + return false; + } + + return ( +
{e.stopPropagation(); return false;}}> + + changeQuantity(e, -1)}/> + + {quantity} + + changeQuantity(e, 1)}/> + +
+ ) +} + export default function BasketPage(props) { const {basketData, changeBasket} = props; @@ -55,8 +83,10 @@ export default function BasketPage(props) { const {push} = useHistory(); - const refreshAction = () => {} - const sendAction = () => {} + const refreshAction = () => { + } + const sendAction = () => { + } const showRowDetails = (productId) => { push('/product/view/' + productId); @@ -66,7 +96,7 @@ export default function BasketPage(props) { <> - sendAction()}/> + sendAction()}/> @@ -80,7 +110,7 @@ export default function BasketPage(props) { # Product id - Quantity + Quantity @@ -88,7 +118,7 @@ export default function BasketPage(props) { showRowDetails(orderLine.productId)}> {(index + 1)} {orderLine.productId} - {orderLine.quantity} + )) : ( From dca57a4f15331cec9d7e07a8c88d5e99848c1dfb Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 16:34:57 +0200 Subject: [PATCH 17/26] Load products details on basket page --- .../dk/erst/cm/api/item/ProductService.java | 172 +++++++++--------- cm-frontend/src/pages/BasketPage.js | 52 +++++- cm-frontend/src/services/DataService.js | 71 ++++---- .../src/services/ItemDetailsService.js | 23 ++- .../dk/erst/cm/webapi/ProductController.java | 26 ++- 5 files changed, 211 insertions(+), 133 deletions(-) diff --git a/cm-api/src/main/java/dk/erst/cm/api/item/ProductService.java b/cm-api/src/main/java/dk/erst/cm/api/item/ProductService.java index 2563011..1262e33 100644 --- a/cm-api/src/main/java/dk/erst/cm/api/item/ProductService.java +++ b/cm-api/src/main/java/dk/erst/cm/api/item/ProductService.java @@ -1,9 +1,10 @@ package dk.erst.cm.api.item; -import java.time.Instant; -import java.util.List; -import java.util.Optional; - +import dk.erst.cm.api.dao.mongo.ProductRepository; +import dk.erst.cm.api.data.Product; +import dk.erst.cm.api.data.ProductCatalogUpdate; +import dk.erst.cm.xml.ubl21.model.CatalogueLine; +import dk.erst.cm.xml.ubl21.model.NestedSchemeID; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -11,91 +12,88 @@ import org.springframework.data.mongodb.core.query.TextCriteria; import org.springframework.stereotype.Service; -import dk.erst.cm.api.dao.mongo.ProductRepository; -import dk.erst.cm.api.data.Product; -import dk.erst.cm.api.data.ProductCatalogUpdate; -import dk.erst.cm.xml.ubl21.model.CatalogueLine; -import dk.erst.cm.xml.ubl21.model.NestedSchemeID; +import java.time.Instant; +import java.util.List; +import java.util.Optional; @Service public class ProductService { - private ProductRepository productRepository; - - @Autowired - public ProductService(ProductRepository itemRepository) { - this.productRepository = itemRepository; - } - - public Product saveCatalogUpdateItem(ProductCatalogUpdate catalog, CatalogueLine line) { - String lineLogicalId = line.getLogicalId(); - String productCatalogId = catalog.getProductCatalogId(); - - String itemLogicalId = productCatalogId + "_" + lineLogicalId; - - boolean deleteAction = line.getActionCode() != null && "Delete".equals(line.getActionCode().getId()); - - Product product; - Optional optional = productRepository.findById(itemLogicalId); - if (optional.isPresent()) { - product = optional.get(); - product.setUpdateTime(Instant.now()); - product.setVersion(product.getVersion() + 1); - } else { - product = new Product(); - product.setId(itemLogicalId); - product.setCreateTime(Instant.now()); - product.setUpdateTime(null); - product.setVersion(1); - } - product.setDocumentVersion(ProductDocumentVersion.PEPPOL_CATALOGUE_3_1); - product.setProductCatalogId(productCatalogId); - product.setStandardNumber(getLineStandardNumber(line)); - product.setDocument(line); - - if (deleteAction) { - if (optional.isPresent()) { - productRepository.delete(product); - } - return null; - } - productRepository.save(product); - - return product; - } - - private String getLineStandardNumber(CatalogueLine line) { - if (line != null && line.getItem() != null) { - if (line.getItem().getStandardItemIdentification() != null) { - NestedSchemeID sn = line.getItem().getStandardItemIdentification(); - if (sn.getId() != null && sn.getId().getId() != null) { - return sn.getId().getId().toUpperCase(); - } - } - } - return null; - } - - public long countItems() { - return productRepository.count(); - } - - public Page findAll(String searchParam, Pageable pageable) { - Page productList; - if (!StringUtils.isEmpty(searchParam)) { - TextCriteria textCriteria = TextCriteria.forDefaultLanguage().matching(searchParam); - productList = productRepository.findAllBy(textCriteria, pageable); - } else { - productList = productRepository.findAll(pageable); - } - return productList; - } - - public Optional findById(String id) { - return productRepository.findById(id); - } - - public List findByStandardNumber(String standardNumber) { - return productRepository.findByStandardNumber(standardNumber); - } + private final ProductRepository productRepository; + + @Autowired + public ProductService(ProductRepository itemRepository) { + this.productRepository = itemRepository; + } + + public Product saveCatalogUpdateItem(ProductCatalogUpdate catalog, CatalogueLine line) { + String lineLogicalId = line.getLogicalId(); + String productCatalogId = catalog.getProductCatalogId(); + String itemLogicalId = productCatalogId + "_" + lineLogicalId; + boolean deleteAction = line.getActionCode() != null && "Delete".equals(line.getActionCode().getId()); + Product product; + Optional optional = productRepository.findById(itemLogicalId); + if (optional.isPresent()) { + product = optional.get(); + product.setUpdateTime(Instant.now()); + product.setVersion(product.getVersion() + 1); + } else { + product = new Product(); + product.setId(itemLogicalId); + product.setCreateTime(Instant.now()); + product.setUpdateTime(null); + product.setVersion(1); + } + product.setDocumentVersion(ProductDocumentVersion.PEPPOL_CATALOGUE_3_1); + product.setProductCatalogId(productCatalogId); + product.setStandardNumber(getLineStandardNumber(line)); + product.setDocument(line); + if (deleteAction) { + if (optional.isPresent()) { + productRepository.delete(product); + } + return null; + } + productRepository.save(product); + return product; + } + + private String getLineStandardNumber(CatalogueLine line) { + if (line != null && line.getItem() != null) { + if (line.getItem().getStandardItemIdentification() != null) { + NestedSchemeID sn = line.getItem().getStandardItemIdentification(); + if (sn.getId() != null && sn.getId().getId() != null) { + return sn.getId().getId().toUpperCase(); + } + } + } + return null; + } + + public long countItems() { + return productRepository.count(); + } + + public Page findAll(String searchParam, Pageable pageable) { + Page productList; + if (!StringUtils.isEmpty(searchParam)) { + TextCriteria textCriteria = TextCriteria.forDefaultLanguage().matching(searchParam); + productList = productRepository.findAllBy(textCriteria, pageable); + } else { + productList = productRepository.findAll(pageable); + } + return productList; + } + + public Optional findById(String id) { + return productRepository.findById(id); + } + + public Iterable findAllByIds(Iterable ids) { + return productRepository.findAllById(ids); + } + + public List findByStandardNumber(String standardNumber) { + return productRepository.findByStandardNumber(standardNumber); + } } diff --git a/cm-frontend/src/pages/BasketPage.js b/cm-frontend/src/pages/BasketPage.js index 06a29cc..5849d79 100644 --- a/cm-frontend/src/pages/BasketPage.js +++ b/cm-frontend/src/pages/BasketPage.js @@ -14,6 +14,8 @@ import {Fab} from "@material-ui/core"; import SendIcon from "@material-ui/icons/Send"; import RemoveIcon from "@material-ui/icons/Remove"; import AddIcon from "@material-ui/icons/Add"; +import DataService from "../services/DataService"; +import ItemDetailsService from "../services/ItemDetailsService"; const useStyles = makeStyles(theme => ({ table: { @@ -61,7 +63,10 @@ function QuantityControl(props) { } return ( -
{e.stopPropagation(); return false;}}> +
{ + e.stopPropagation(); + return false; + }}> changeQuantity(e, -1)}/> @@ -78,16 +83,51 @@ export default function BasketPage(props) { const {basketData, changeBasket} = props; const [isLoading, setLoading] = React.useState(false); + const [productList, setProductList] = React.useState({}); const classes = useStyles(); const {push} = useHistory(); const refreshAction = () => { + loadProducts().then(() => setLoading(false)); } const sendAction = () => { } + const productItem = (productId) => { + if (productId in productList) { + return productList[productId].document.item; + } + return null; + } + + async function loadProducts() { + if (!basketData.isEmpty()) { + setLoading(true); + const productIdList = basketData.getOrderLineList().map((orderLine) => orderLine.productId); + + await DataService.fetchProductsByIds(productIdList).then(response => { + let responseData = response.data; + console.log(responseData); + const productMapById = {} + for (const index in responseData) { + const p = responseData[index]; + productMapById[p.id] = p; + } + console.log(productMapById); + setProductList(productMapById); + } + ).catch(error => { + console.log('Error occurred: ' + error.message); + }); + } + } + + React.useEffect(() => { + loadProducts().then(() => setLoading(false)); + }, []); + const showRowDetails = (productId) => { push('/product/view/' + productId); } @@ -109,20 +149,24 @@ export default function BasketPage(props) { # - Product id Quantity + Name + Standard number + Seller number {!basketData.isEmpty() ? basketData.getOrderLineList().map((orderLine, index) => ( showRowDetails(orderLine.productId)}> {(index + 1)} - {orderLine.productId} + {ItemDetailsService.itemName(productItem(orderLine.productId))} + {ItemDetailsService.itemStandardNumber(productItem(orderLine.productId))} + {ItemDetailsService.itemSellerNumber(productItem(orderLine.productId))} )) : ( - Basket is empty + Basket is empty )} diff --git a/cm-frontend/src/services/DataService.js b/cm-frontend/src/services/DataService.js index f5f7df4..5fd8df0 100644 --- a/cm-frontend/src/services/DataService.js +++ b/cm-frontend/src/services/DataService.js @@ -1,33 +1,40 @@ -import Axios from "axios"; - -//const apiUrl = "http://localhost:8080/api"; -const apiUrl = "/dcm/api"; - -const fetchProductDetails = (productId) => { - return fetch(apiUrl + "/products/" + productId); -} - -const fetchProducts = (search, page = 0, size = 20) => { - const params = { - page: page, - size: size, - } - if (search) { - params.search = search; - } - return Axios.get(apiUrl + "/products", { - params: params - }); -} - -const uploadFiles = (formData) => { - return Axios.post(apiUrl + "/upload", formData); -} - -const DataService = { - fetchProductDetails, - fetchProducts, - uploadFiles, -} - +import Axios from "axios"; + +//const apiUrl = "http://localhost:8080/api"; +const apiUrl = "/dcm/api"; + +const fetchProductDetails = (productId) => { + return fetch(apiUrl + "/products/" + productId); +} + +const fetchProducts = (search, page = 0, size = 20) => { + const params = { + page: page, + size: size, + } + if (search) { + params.search = search; + } + return Axios.get(apiUrl + "/products", { + params: params + }); +} + +const fetchProductsByIds = (productIdList = []) => { + return Axios.post(apiUrl + "/products_by_ids", { + ids: productIdList + }); +} + +const uploadFiles = (formData) => { + return Axios.post(apiUrl + "/upload", formData); +} + +const DataService = { + fetchProductDetails, + fetchProducts, + fetchProductsByIds, + uploadFiles, +} + export default DataService; \ No newline at end of file diff --git a/cm-frontend/src/services/ItemDetailsService.js b/cm-frontend/src/services/ItemDetailsService.js index c855354..4a544b7 100644 --- a/cm-frontend/src/services/ItemDetailsService.js +++ b/cm-frontend/src/services/ItemDetailsService.js @@ -1,4 +1,4 @@ -import { Box } from "@material-ui/core"; +import {Box} from "@material-ui/core"; const itemOriginCountry = (item) => { if (item && item.originCountry) { @@ -18,6 +18,12 @@ const itemUNSPSC = (item) => { } return null; } +const itemName = (item) => { + if (item) { + return item.name; + } + return null; +} const itemSellerNumber = (item) => { if (item) { if (item.sellersItemIdentification) { @@ -96,7 +102,7 @@ const renderItemCertificate = (cert) => { const renderItemSpecification = (s) => { return (
- {s.documentTypeCode && ({s.documentTypeCode})} + {s.documentTypeCode && ({s.documentTypeCode})} {renderUrl(s.attachment.externalReference.uri)}
) @@ -104,8 +110,8 @@ const renderItemSpecification = (s) => { const renderItemAdditionalProperty = (s) => { return (
- {s.name && ({s.name})} - {s.nameCode && ({' '}{s.nameCode.id})} + {s.name && ({s.name})} + {s.nameCode && ({' '}{s.nameCode.id})} {(s.name || s.nameCode) && (":")} {s.value && ({' '}{s.value})} {s.valueQuantity && ({' '}{s.valueQuantity.quantity}{' '} {s.valueQuantity.unitCode})} @@ -113,11 +119,16 @@ const renderItemAdditionalProperty = (s) => { ) } -const renderUrlListValue = (v) => { return (renderUrl(v.attachment.externalReference.uri)) }; +const renderUrlListValue = (v) => { + return (renderUrl(v.attachment.externalReference.uri)) +}; -const renderUrl = (v) => { return ({v}) }; +const renderUrl = (v) => { + return ({v}) +}; const ItemDetailsService = { + itemName, itemOriginCountry, itemUNSPSC, itemSellerNumber, diff --git a/cm-webapi/src/main/java/dk/erst/cm/webapi/ProductController.java b/cm-webapi/src/main/java/dk/erst/cm/webapi/ProductController.java index 29a7f01..998114b 100644 --- a/cm-webapi/src/main/java/dk/erst/cm/webapi/ProductController.java +++ b/cm-webapi/src/main/java/dk/erst/cm/webapi/ProductController.java @@ -1,9 +1,11 @@ package dk.erst.cm.webapi; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; +import lombok.Data; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -12,6 +14,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -25,8 +28,12 @@ @Slf4j public class ProductController { + private final ProductService productService; + @Autowired - private ProductService productService; + public ProductController(ProductService productService) { + this.productService = productService; + } @RequestMapping(value = "/api/products") public Page getProducts(@RequestParam(required = false) String search, Pageable pageable) { @@ -34,11 +41,22 @@ public Page getProducts(@RequestParam(required = false) String search, return productService.findAll(search, pageable); } + @Data + public static class IdList { + private String[] ids; + } + + @RequestMapping(value = "/api/products_by_ids") + public Iterable getProductsByIds(@RequestBody IdList query) { + log.info("Search products by ids " + query); + return productService.findAllByIds(Arrays.asList(query.ids)); + } + @RequestMapping(value = "/api/product/{id}") public ResponseEntity getProductById(@PathVariable("id") String id) { Optional findById = productService.findById(id); if (findById.isPresent()) { - return new ResponseEntity(findById.get(), HttpStatus.OK); + return new ResponseEntity<>(findById.get(), HttpStatus.OK); } return ResponseEntity.notFound().build(); } @@ -52,9 +70,9 @@ public ResponseEntity> getProductsById(@PathVariable("id") String if (!StringUtils.isEmpty(product.getStandardNumber())) { list = productService.findByStandardNumber(product.getStandardNumber()); } else { - list = Arrays.asList(product); + list = Collections.singletonList(product); } - return new ResponseEntity>(list, HttpStatus.OK); + return new ResponseEntity<>(list, HttpStatus.OK); } return ResponseEntity.notFound().build(); } From a4b3a1a7070d8e82d43ea5c1434f1730c66256c4 Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 16:44:34 +0200 Subject: [PATCH 18/26] Funny automatic extraction of component OrderLineList by IntelliJ Idea... --- cm-frontend/src/pages/BasketPage.js | 66 +++++++++++++++++------------ 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/cm-frontend/src/pages/BasketPage.js b/cm-frontend/src/pages/BasketPage.js index 5849d79..a57b053 100644 --- a/cm-frontend/src/pages/BasketPage.js +++ b/cm-frontend/src/pages/BasketPage.js @@ -16,6 +16,7 @@ import RemoveIcon from "@material-ui/icons/Remove"; import AddIcon from "@material-ui/icons/Add"; import DataService from "../services/DataService"; import ItemDetailsService from "../services/ItemDetailsService"; +import * as PropTypes from "prop-types"; const useStyles = makeStyles(theme => ({ table: { @@ -78,6 +79,34 @@ function QuantityControl(props) { ) } +function OrderLineList(props) { + return + + + + # + Quantity + Name + Standard number + Seller number + + + + {!props.basketData.isEmpty() ? props.basketData.getOrderLineList().map(props.callbackFn) : ( + + Basket is empty + + )} + +
+
; +} + +OrderLineList.propTypes = { + classes: PropTypes.string, + basketData: PropTypes.any, + callbackFn: PropTypes.func +}; export default function BasketPage(props) { const {basketData, changeBasket} = props; @@ -144,34 +173,15 @@ export default function BasketPage(props) { ) : ( <> - - - - - # - Quantity - Name - Standard number - Seller number - - - - {!basketData.isEmpty() ? basketData.getOrderLineList().map((orderLine, index) => ( - showRowDetails(orderLine.productId)}> - {(index + 1)} - - {ItemDetailsService.itemName(productItem(orderLine.productId))} - {ItemDetailsService.itemStandardNumber(productItem(orderLine.productId))} - {ItemDetailsService.itemSellerNumber(productItem(orderLine.productId))} - - )) : ( - - Basket is empty - - )} - -
-
+ ( + showRowDetails(orderLine.productId)}> + {(index + 1)} + + {ItemDetailsService.itemName(productItem(orderLine.productId))} + {ItemDetailsService.itemStandardNumber(productItem(orderLine.productId))} + {ItemDetailsService.itemSellerNumber(productItem(orderLine.productId))} + + )}/> )} From 33254ec99f8b214220a02c0a457bec5337488afd Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 17:07:44 +0200 Subject: [PATCH 19/26] fix: Warning: Failed prop type: Invalid prop `cellHeight` supplied to `ForwardRef(GridList)` - change from string "360" to {360} --- cm-frontend/src/components/ProductPictureList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cm-frontend/src/components/ProductPictureList.js b/cm-frontend/src/components/ProductPictureList.js index f283944..a600f33 100644 --- a/cm-frontend/src/components/ProductPictureList.js +++ b/cm-frontend/src/components/ProductPictureList.js @@ -23,7 +23,7 @@ export default function RenderPictureList(props) { const classes = useStyles(); return ( - + {props.specList.map((spec) => ( Product From ec37147f07257eb8e9e16bc4fdeaf78eedffa12d Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 17:13:56 +0200 Subject: [PATCH 20/26] fix: js error "Warning: Each child in a list should have a unique "key" prop." Add several keys to render objects --- cm-frontend/src/components/ProductDetail.js | 4 ++-- cm-frontend/src/services/ItemDetailsService.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cm-frontend/src/components/ProductDetail.js b/cm-frontend/src/components/ProductDetail.js index 180ec75..66edf9f 100644 --- a/cm-frontend/src/components/ProductDetail.js +++ b/cm-frontend/src/components/ProductDetail.js @@ -92,7 +92,7 @@ const renderCatalogs = (source) => { {source.length}{source.length > 1 ? ' catalogs: ':' catalog'} {source.map((s,i) => { return ( - + ) })} @@ -101,7 +101,7 @@ const renderCatalogs = (source) => { const renderSourcedValue = (v, extractValue = (e)=> {return e.value}) => { return ( -
+
{v._source ? (<>{' '}{extractValue(v)}) : <>{v}}
) diff --git a/cm-frontend/src/services/ItemDetailsService.js b/cm-frontend/src/services/ItemDetailsService.js index 4a544b7..3e6f98b 100644 --- a/cm-frontend/src/services/ItemDetailsService.js +++ b/cm-frontend/src/services/ItemDetailsService.js @@ -101,7 +101,7 @@ const renderItemCertificate = (cert) => { } const renderItemSpecification = (s) => { return ( -
+
{s.documentTypeCode && ({s.documentTypeCode})} {renderUrl(s.attachment.externalReference.uri)}
@@ -109,7 +109,7 @@ const renderItemSpecification = (s) => { } const renderItemAdditionalProperty = (s) => { return ( -
+
{s.name && ({s.name})} {s.nameCode && ({' '}{s.nameCode.id})} {(s.name || s.nameCode) && (":")} From bbcc4763d9ddeec14f0b56702e9c66eb675d1645 Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 17:35:57 +0200 Subject: [PATCH 21/26] fix: tried to fix "Line 159:39: React Hook React.useEffect has a missing dependency: 'loadProducts'. Either include it or remove the dependency array react-hooks/exhaustive-deps" - but give up. --- cm-frontend/src/pages/BasketPage.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/cm-frontend/src/pages/BasketPage.js b/cm-frontend/src/pages/BasketPage.js index a57b053..f5b5430 100644 --- a/cm-frontend/src/pages/BasketPage.js +++ b/cm-frontend/src/pages/BasketPage.js @@ -103,7 +103,7 @@ function OrderLineList(props) { } OrderLineList.propTypes = { - classes: PropTypes.string, + classes: PropTypes.any, basketData: PropTypes.any, callbackFn: PropTypes.func }; @@ -113,13 +113,14 @@ export default function BasketPage(props) { const [isLoading, setLoading] = React.useState(false); const [productList, setProductList] = React.useState({}); + const [reloadCount, setReloadCount] = React.useState(0); const classes = useStyles(); const {push} = useHistory(); const refreshAction = () => { - loadProducts().then(() => setLoading(false)); + setReloadCount(reloadCount + 1); } const sendAction = () => { } @@ -131,6 +132,8 @@ export default function BasketPage(props) { return null; } + const callLoadProducts = () => { loadProducts().finally(() => setLoading(false))}; + async function loadProducts() { if (!basketData.isEmpty()) { setLoading(true); @@ -153,9 +156,7 @@ export default function BasketPage(props) { } } - React.useEffect(() => { - loadProducts().then(() => setLoading(false)); - }, []); + React.useEffect(callLoadProducts, [reloadCount]); const showRowDetails = (productId) => { push('/product/view/' + productId); From ef8747f44eb955bc9945ea23bb319c7bf5bc3315 Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 17:54:05 +0200 Subject: [PATCH 22/26] chore: move OrderLineList to a separate file from BasketPage before adding OrderHeader component to BasketPage --- cm-frontend/src/components/OrderLineList.js | 108 +++++++++++++++++++ cm-frontend/src/pages/BasketPage.js | 111 ++------------------ 2 files changed, 115 insertions(+), 104 deletions(-) create mode 100644 cm-frontend/src/components/OrderLineList.js diff --git a/cm-frontend/src/components/OrderLineList.js b/cm-frontend/src/components/OrderLineList.js new file mode 100644 index 0000000..2e77eff --- /dev/null +++ b/cm-frontend/src/components/OrderLineList.js @@ -0,0 +1,108 @@ +import {makeStyles, withStyles} from "@material-ui/core/styles"; +import TableContainer from "@material-ui/core/TableContainer"; +import Table from "@material-ui/core/Table"; +import TableHead from "@material-ui/core/TableHead"; +import TableRow from "@material-ui/core/TableRow"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import ItemDetailsService from "../services/ItemDetailsService"; +import React from "react"; +import {Fab} from "@material-ui/core"; +import RemoveIcon from "@material-ui/icons/Remove"; +import AddIcon from "@material-ui/icons/Add"; + +const StyledTableCell = withStyles(() => ({ + head: { + fontWeight: 'bold', + }, +}))(TableCell); + +const StyledTableRow = withStyles((theme) => ({ + root: { + '&:nth-of-type(odd)': { + backgroundColor: theme.palette.action.hover, + }, + '&:hover': { + backgroundColor: 'rgba(0,0,0,.08)', + }, + cursor: 'pointer' + }, +}))(TableRow); + +function QuantityControl(props) { + const {productId, quantity, changeBasket} = props; + + const changeQuantity = (e, quantityChange) => { + e.stopPropagation(); + if (quantity + quantityChange === 0) { + changeBasket(productId, 0); + } else { + changeBasket(productId, quantityChange); + } + return false; + } + + return ( +
{ + e.stopPropagation(); + return false; + }}> + + changeQuantity(e, -1)}/> + + {quantity} + + changeQuantity(e, 1)}/> + +
+ ) +} + +export default function OrderLineList(props) { + + const {basketData, showRowDetails, changeBasket, productList} = props; + + const useStyles = makeStyles(() => ({ + table: { + minWidth: 600, + }, + })); + + const classes = useStyles(); + + const productItem = (productId) => { + if (productId in productList) { + return productList[productId].document.item; + } + return null; + } + + return + + + + # + Quantity + Name + Standard number + Seller number + + + + {!basketData.isEmpty() ? basketData.getOrderLineList().map((orderLine, index) => ( + showRowDetails(orderLine.productId)}> + {(index + 1)} + + {ItemDetailsService.itemName(productItem(orderLine.productId))} + {ItemDetailsService.itemStandardNumber(productItem(orderLine.productId))} + {ItemDetailsService.itemSellerNumber(productItem(orderLine.productId))} + + )) : ( + + Basket is empty + + )} + +
+
; +} diff --git a/cm-frontend/src/pages/BasketPage.js b/cm-frontend/src/pages/BasketPage.js index f5b5430..3ea5f73 100644 --- a/cm-frontend/src/pages/BasketPage.js +++ b/cm-frontend/src/pages/BasketPage.js @@ -1,22 +1,13 @@ import React from "react"; -import {makeStyles, withStyles} from "@material-ui/core/styles"; -import Table from "@material-ui/core/Table"; -import TableBody from "@material-ui/core/TableBody"; -import TableCell from "@material-ui/core/TableCell"; -import TableContainer from "@material-ui/core/TableContainer"; -import TableHead from "@material-ui/core/TableHead"; -import TableRow from "@material-ui/core/TableRow"; +import {makeStyles} from "@material-ui/core/styles"; import Paper from "@material-ui/core/Paper"; import CircularProgress from "@material-ui/core/CircularProgress"; import {useHistory} from "react-router"; import PageHeader from '../components/PageHeader'; import {Fab} from "@material-ui/core"; import SendIcon from "@material-ui/icons/Send"; -import RemoveIcon from "@material-ui/icons/Remove"; -import AddIcon from "@material-ui/icons/Add"; import DataService from "../services/DataService"; -import ItemDetailsService from "../services/ItemDetailsService"; -import * as PropTypes from "prop-types"; +import OrderLineList from "../components/OrderLineList"; const useStyles = makeStyles(theme => ({ table: { @@ -32,81 +23,7 @@ const useStyles = makeStyles(theme => ({ } })); -const StyledTableCell = withStyles(() => ({ - head: { - fontWeight: 'bold', - }, -}))(TableCell); - -const StyledTableRow = withStyles((theme) => ({ - root: { - '&:nth-of-type(odd)': { - backgroundColor: theme.palette.action.hover, - }, - '&:hover': { - backgroundColor: 'rgba(0,0,0,.08)', - }, - cursor: 'pointer' - }, -}))(TableRow); -function QuantityControl(props) { - const {productId, quantity, changeBasket} = props; - - const changeQuantity = (e, quantityChange) => { - e.stopPropagation(); - if (quantity + quantityChange === 0) { - changeBasket(productId, 0); - } else { - changeBasket(productId, quantityChange); - } - return false; - } - - return ( -
{ - e.stopPropagation(); - return false; - }}> - - changeQuantity(e, -1)}/> - - {quantity} - - changeQuantity(e, 1)}/> - -
- ) -} - -function OrderLineList(props) { - return - - - - # - Quantity - Name - Standard number - Seller number - - - - {!props.basketData.isEmpty() ? props.basketData.getOrderLineList().map(props.callbackFn) : ( - - Basket is empty - - )} - -
-
; -} - -OrderLineList.propTypes = { - classes: PropTypes.any, - basketData: PropTypes.any, - callbackFn: PropTypes.func -}; export default function BasketPage(props) { const {basketData, changeBasket} = props; @@ -125,14 +42,9 @@ export default function BasketPage(props) { const sendAction = () => { } - const productItem = (productId) => { - if (productId in productList) { - return productList[productId].document.item; - } - return null; - } - - const callLoadProducts = () => { loadProducts().finally(() => setLoading(false))}; + const callLoadProducts = () => { + loadProducts().finally(() => setLoading(false)) + }; async function loadProducts() { if (!basketData.isEmpty()) { @@ -169,21 +81,12 @@ export default function BasketPage(props) { sendAction()}/> + {(isLoading || false) ? ( ) : ( - <> - ( - showRowDetails(orderLine.productId)}> - {(index + 1)} - - {ItemDetailsService.itemName(productItem(orderLine.productId))} - {ItemDetailsService.itemStandardNumber(productItem(orderLine.productId))} - {ItemDetailsService.itemSellerNumber(productItem(orderLine.productId))} - - )}/> - + )} From 36b528d9fba8cf107901755be9c5442ea18bf267 Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 17:54:29 +0200 Subject: [PATCH 23/26] chore: 400ms imtation looks too slow now, reduce to 300ms :) --- cm-frontend/src/components/AddToBasket.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cm-frontend/src/components/AddToBasket.js b/cm-frontend/src/components/AddToBasket.js index fbfba0f..29ce636 100644 --- a/cm-frontend/src/components/AddToBasket.js +++ b/cm-frontend/src/components/AddToBasket.js @@ -33,7 +33,7 @@ export default function AddToBasket(props) { timerRef.current = setTimeout(() => { changeBasket(product.id, 1); setState(ProductBasketStatus.Added) - }, 400); + }, 300); } else if (state === ProductBasketStatus.Added) { changeBasket(product.id, 0); setState(ProductBasketStatus.Empty); From 134d9e145763561a163140bdfcddcde1c843833a Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 17:54:54 +0200 Subject: [PATCH 24/26] chore: start to add order header --- cm-frontend/src/components/OrderHeader.js | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 cm-frontend/src/components/OrderHeader.js diff --git a/cm-frontend/src/components/OrderHeader.js b/cm-frontend/src/components/OrderHeader.js new file mode 100644 index 0000000..b817d42 --- /dev/null +++ b/cm-frontend/src/components/OrderHeader.js @@ -0,0 +1,7 @@ +import {Paper} from "@material-ui/core"; + +export default function OrderHeader() { + return + Test + +} \ No newline at end of file From 2d9ff73059657bdb94a63f490637ce8f96ef16af Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 19:12:42 +0200 Subject: [PATCH 25/26] chore: utility function to test loading layout - delay --- cm-frontend/src/utils/delay.js | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 cm-frontend/src/utils/delay.js diff --git a/cm-frontend/src/utils/delay.js b/cm-frontend/src/utils/delay.js new file mode 100644 index 0000000..782424e --- /dev/null +++ b/cm-frontend/src/utils/delay.js @@ -0,0 +1,4 @@ +// Usage example: +// import delay from "../utils/delay" +// await delay(10000); +export default function delay( ms ) { return new Promise(res => setTimeout(res, ms)); } From e829f11ac0ebbc311a5289890663f68760917309 Mon Sep 17 00:00:00 2001 From: Dmytro Lapko Date: Wed, 1 Dec 2021 19:14:11 +0200 Subject: [PATCH 26/26] feature: add dummy OrderHeader with buyer company info and contact info --- cm-frontend/src/components/OrderHeader.js | 60 +++++++++++++++++- cm-frontend/src/components/OrderLineList.js | 68 ++++++++++++--------- cm-frontend/src/pages/BasketPage.js | 17 +++--- 3 files changed, 104 insertions(+), 41 deletions(-) diff --git a/cm-frontend/src/components/OrderHeader.js b/cm-frontend/src/components/OrderHeader.js index b817d42..2d93935 100644 --- a/cm-frontend/src/components/OrderHeader.js +++ b/cm-frontend/src/components/OrderHeader.js @@ -1,7 +1,61 @@ -import {Paper} from "@material-ui/core"; +import {Grid, Paper, TextField, Typography} from "@material-ui/core"; +import {makeStyles} from "@material-ui/core/styles"; export default function OrderHeader() { - return - Test + + const useStyles = makeStyles((theme) => ({ + paper: { + padding: theme.spacing(2), + marginBottom: theme.spacing(2), + }, + formHeader: { + paddingTop: theme.spacing(1), + paddingLeft: theme.spacing(2), + textAlign: "left", + fontSize: '1em', + }, + form: { + padding: theme.spacing(2), + display: "flex", + flex: "1", + flexDirection: "row", + justifyContent: "space-between", + }, + input: { + paddingInline: theme.spacing(0.5), + } + })); + + const classes = useStyles(); + + function DataInput(props) { + return + } + + function DataBlock(props) { + return + +
{props.name}
+
+ {props.children} +
+
+
+ + } + + return + + + + + + + + + + + + } \ No newline at end of file diff --git a/cm-frontend/src/components/OrderLineList.js b/cm-frontend/src/components/OrderLineList.js index 2e77eff..7ff1bc9 100644 --- a/cm-frontend/src/components/OrderLineList.js +++ b/cm-frontend/src/components/OrderLineList.js @@ -7,7 +7,7 @@ import TableBody from "@material-ui/core/TableBody"; import TableCell from "@material-ui/core/TableCell"; import ItemDetailsService from "../services/ItemDetailsService"; import React from "react"; -import {Fab} from "@material-ui/core"; +import {Fab, Paper} from "@material-ui/core"; import RemoveIcon from "@material-ui/icons/Remove"; import AddIcon from "@material-ui/icons/Add"; @@ -62,7 +62,12 @@ export default function OrderLineList(props) { const {basketData, showRowDetails, changeBasket, productList} = props; - const useStyles = makeStyles(() => ({ + const useStyles = makeStyles((theme) => ({ + paper: { + padding: theme.spacing(2), + paddingBottom: theme.spacing(5), + marginBottom: theme.spacing(3), + }, table: { minWidth: 600, }, @@ -77,32 +82,35 @@ export default function OrderLineList(props) { return null; } - return - - - - # - Quantity - Name - Standard number - Seller number - - - - {!basketData.isEmpty() ? basketData.getOrderLineList().map((orderLine, index) => ( - showRowDetails(orderLine.productId)}> - {(index + 1)} - - {ItemDetailsService.itemName(productItem(orderLine.productId))} - {ItemDetailsService.itemStandardNumber(productItem(orderLine.productId))} - {ItemDetailsService.itemSellerNumber(productItem(orderLine.productId))} - - )) : ( - - Basket is empty - - )} - -
-
; + return + + + + + # + Quantity + Name + Standard number + Seller number + + + + {!basketData.isEmpty() ? basketData.getOrderLineList().map((orderLine, index) => ( + showRowDetails(orderLine.productId)}> + {(index + 1)} + + {ItemDetailsService.itemName(productItem(orderLine.productId))} + {ItemDetailsService.itemStandardNumber(productItem(orderLine.productId))} + {ItemDetailsService.itemSellerNumber(productItem(orderLine.productId))} + + )) : ( + + Basket is empty + + )} + +
+
+
+ ; } diff --git a/cm-frontend/src/pages/BasketPage.js b/cm-frontend/src/pages/BasketPage.js index 3ea5f73..12856ec 100644 --- a/cm-frontend/src/pages/BasketPage.js +++ b/cm-frontend/src/pages/BasketPage.js @@ -8,6 +8,7 @@ import {Fab} from "@material-ui/core"; import SendIcon from "@material-ui/icons/Send"; import DataService from "../services/DataService"; import OrderLineList from "../components/OrderLineList"; +import OrderHeader from "../components/OrderHeader"; const useStyles = makeStyles(theme => ({ table: { @@ -18,8 +19,6 @@ const useStyles = makeStyles(theme => ({ }, paper: { padding: theme.spacing(2), - paddingBottom: theme.spacing(5), - marginBottom: theme.spacing(3), } })); @@ -50,7 +49,6 @@ export default function BasketPage(props) { if (!basketData.isEmpty()) { setLoading(true); const productIdList = basketData.getOrderLineList().map((orderLine) => orderLine.productId); - await DataService.fetchProductsByIds(productIdList).then(response => { let responseData = response.data; console.log(responseData); @@ -82,13 +80,16 @@ export default function BasketPage(props) { - - {(isLoading || false) ? ( + {(isLoading || false) ? ( + - ) : ( + + ) : ( + <> + - )} - + + )} ); }