Product detail modal
This commit is contained in:
8
frontend/public/manifest.json
Normal file
8
frontend/public/manifest.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"short_name": "React App",
|
||||||
|
"name": "Create React App Sample",
|
||||||
|
"start_url": "./index.html",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { API_URL } from "../const";
|
|
||||||
|
|
||||||
export class ApiBase {}
|
export class ApiBase {}
|
||||||
|
|
||||||
@ -12,7 +11,6 @@ const commonHeaders = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const axiosUnauthorizedInstance = axios.create({
|
export const axiosUnauthorizedInstance = axios.create({
|
||||||
baseURL: API_URL,
|
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
headers: commonHeaders,
|
headers: commonHeaders,
|
||||||
});
|
});
|
||||||
@ -25,7 +23,6 @@ axiosUnauthorizedInstance.interceptors.response.use(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const axiosInstance = axios.create({
|
export const axiosInstance = axios.create({
|
||||||
baseURL: API_URL,
|
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
headers: {
|
headers: {
|
||||||
...commonHeaders,
|
...commonHeaders,
|
||||||
|
|||||||
@ -1 +1,2 @@
|
|||||||
export { machinesApi } from "./machines";
|
export { machinesApi } from "./machines";
|
||||||
|
export { productsApi } from "./products";
|
||||||
|
|||||||
@ -1,14 +1,17 @@
|
|||||||
import { axiosInstance, ApiBase } from "./common";
|
import { axiosInstance, ApiBase } from "./common";
|
||||||
|
import { MACHINES_API_URL } from "../const";
|
||||||
|
|
||||||
|
const baseUrl = `${MACHINES_API_URL}/machines`;
|
||||||
|
|
||||||
class MachinesApi extends ApiBase {
|
class MachinesApi extends ApiBase {
|
||||||
list = async () => {
|
list = async () => {
|
||||||
return axiosInstance.get(`/machines`, {});
|
return axiosInstance.get(`${baseUrl}`, {});
|
||||||
};
|
};
|
||||||
get = async (machineId) => {
|
get = async (machineId) => {
|
||||||
return axiosInstance.get(`/machines/${machineId}`, {});
|
return axiosInstance.get(`${baseUrl}/${machineId}`, {});
|
||||||
};
|
};
|
||||||
listProducts = async (machineId) => {
|
listProducts = async (machineId) => {
|
||||||
return axiosInstance.get(`/machines/${machineId}/products`, {});
|
return axiosInstance.get(`${baseUrl}/${machineId}/products`, {});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
12
frontend/src/api/products.js
Normal file
12
frontend/src/api/products.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { axiosInstance, ApiBase } from "./common";
|
||||||
|
import { PRODUCTS_API_URL } from "../const";
|
||||||
|
|
||||||
|
const baseUrl = `${PRODUCTS_API_URL}/products`;
|
||||||
|
|
||||||
|
class ProductsApi extends ApiBase {
|
||||||
|
get = async (productId) => {
|
||||||
|
return axiosInstance.get(`${baseUrl}/${productId}`, {});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const productsApi = new ProductsApi();
|
||||||
@ -6,20 +6,21 @@ import Typography from "@mui/material/Typography";
|
|||||||
import { CardActionArea } from "@mui/material";
|
import { CardActionArea } from "@mui/material";
|
||||||
import { PRODUCT_IMAGE_DIR } from "../const";
|
import { PRODUCT_IMAGE_DIR } from "../const";
|
||||||
|
|
||||||
function ProductCard({ product }) {
|
function ProductCard({ product, onClick }) {
|
||||||
const productImg = `${PRODUCT_IMAGE_DIR}${product.image}`;
|
const productImg = `${PRODUCT_IMAGE_DIR}/${product.image}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card sx={{ width: "100%" }}>
|
||||||
<CardActionArea>
|
<CardActionArea
|
||||||
<CardMedia component="img" height="140" image={productImg} alt={product.name} />
|
onClick={() => {
|
||||||
|
onClick(product.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardMedia component="img" height="200" image={productImg} alt={product.name} />
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography gutterBottom variant="h5" component="div">
|
<Typography gutterBottom variant="h5" component="div" sx={{ marginBottom: 0 }}>
|
||||||
{product.name}
|
{product.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
|
||||||
{product.description}
|
|
||||||
</Typography>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</CardActionArea>
|
</CardActionArea>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
36
frontend/src/components/ProductModal.js
Normal file
36
frontend/src/components/ProductModal.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import Button from "@mui/material/Button";
|
||||||
|
import Dialog from "@mui/material/Dialog";
|
||||||
|
import DialogActions from "@mui/material/DialogActions";
|
||||||
|
import DialogContent from "@mui/material/DialogContent";
|
||||||
|
import DialogContentText from "@mui/material/DialogContentText";
|
||||||
|
import DialogTitle from "@mui/material/DialogTitle";
|
||||||
|
import CardMedia from "@mui/material/CardMedia";
|
||||||
|
import { PRODUCT_IMAGE_DIR } from "../const";
|
||||||
|
|
||||||
|
function ProductModal({ product, onClose }) {
|
||||||
|
const productImg = `${PRODUCT_IMAGE_DIR}/${product.image}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={true} onClose={onClose}>
|
||||||
|
<DialogTitle>{product.name}</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<CardMedia
|
||||||
|
component="img"
|
||||||
|
height="300"
|
||||||
|
image={productImg}
|
||||||
|
alt={product.name}
|
||||||
|
sx={{ marginBottom: "1rem" }}
|
||||||
|
/>
|
||||||
|
<DialogContentText>{product.description}</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={onClose} variant="contained" autoFocus>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ProductModal };
|
||||||
@ -2,12 +2,32 @@ import * as React from "react";
|
|||||||
import Grid from "@mui/material/Unstable_Grid2";
|
import Grid from "@mui/material/Unstable_Grid2";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import { ProductCard } from "./ProductCard";
|
import { ProductCard } from "./ProductCard";
|
||||||
|
import { ProductModal } from "./ProductModal";
|
||||||
|
import { productsApi } from "../api";
|
||||||
|
|
||||||
function Products({ machineName, products, onSelect }) {
|
function Products({ machineName, products, onSelect }) {
|
||||||
|
const [productModal, setProductModal] = React.useState({ isOpen: false, product: null });
|
||||||
|
|
||||||
|
const onProductSelect = (productId) => {
|
||||||
|
productsApi.get(productId).then((response) => {
|
||||||
|
setProductModal({
|
||||||
|
isOpen: true,
|
||||||
|
product: response.data,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onProductModalClose = () => {
|
||||||
|
setProductModal({
|
||||||
|
isOpen: false,
|
||||||
|
productId: null,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const productItems = products.map((product) => {
|
const productItems = products.map((product) => {
|
||||||
return (
|
return (
|
||||||
<Grid md={6} key={product.id} sx={{ display: "flex" }}>
|
<Grid md={6} key={product.id} sx={{ display: "flex" }}>
|
||||||
<ProductCard product={product} />
|
<ProductCard product={product} onClick={onProductSelect} />
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -21,6 +41,8 @@ function Products({ machineName, products, onSelect }) {
|
|||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
{productItems}
|
{productItems}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
{productModal.isOpen && <ProductModal product={productModal.product} onClose={onProductModalClose} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export const API_URL = process.env.REACT_APP_BACKEND_API_URL || "http://localhost:10000";
|
export const MACHINES_API_URL = process.env.REACT_APP_MACHINES_API_URL || "http://localhost:4000";
|
||||||
|
export const PRODUCTS_API_URL = process.env.REACT_APP_PRODUCTS_API_URL || "http://localhost:4001";
|
||||||
export const PRODUCT_IMAGE_DIR = "/static/products/";
|
export const PRODUCT_IMAGE_DIR = "/static/products/";
|
||||||
|
|||||||
@ -18,9 +18,7 @@ function Home() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onMachineSelect = (machineName, machineId) => {
|
const onMachineSelect = (machineName, machineId) => {
|
||||||
console.log("selected:", machineName);
|
|
||||||
machinesApi.listProducts(machineId).then((response) => {
|
machinesApi.listProducts(machineId).then((response) => {
|
||||||
console.log(response.data.products);
|
|
||||||
setProductsData({
|
setProductsData({
|
||||||
machineName,
|
machineName,
|
||||||
products: response.data.products,
|
products: response.data.products,
|
||||||
|
|||||||
Reference in New Issue
Block a user