Merge pull request #221 from juliamuiruri4/main

added total shopping cost & quantity functionality
This commit is contained in:
Lee Stott 2023-07-25 10:14:23 +01:00 коммит произвёл GitHub
Родитель a0f6da86fb d929ef9687
Коммит cbd422e605
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 3655 добавлений и 13764 удалений

17014
full/smart-shopping-planner-app/Solution/package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -13,9 +13,11 @@
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"graphql": "^16.6.0",
"nth-check": "^2.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"react-loading-icons": "^1.1.0",
"react-scripts": "^5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 256 KiB

После

Ширина:  |  Высота:  |  Размер: 305 KiB

Просмотреть файл

@ -1,21 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
@ -24,12 +22,13 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
<title>Smart Shopping</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
@ -39,5 +38,6 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
</body>
</html>

Просмотреть файл

@ -2,32 +2,45 @@ import './App.css';
import CreateItem from './components/CreateItem';
import ItemList from './components/ItemList';
import PriceTag from './components/PriceTag';
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
import { Container, Grid, Box } from '@mui/material';
import Typography from '@mui/joy/Typography';
import React, { useState } from 'react';
import { totalItems, totalCost } from './utils/Utils'
import Loading from './components/Loading';
// Apollo Client setup for GraphQL API calls to the backend server
const client = new ApolloClient({
uri: '/data-api/graphql',
cache: new InMemoryCache({
addTypename: false
})
});
function App() {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const fetchData = async () => {
setLoading(true)
try {
const response = await fetch('/data-api/api/Item');
if (!response.ok) {
throw new Error(response.statusText);
}
const data = await response.json();
setItems(data.value);
setLoading(false);
} catch (error) {
console.log(error);
setLoading(false);
}
};
return (
<ApolloProvider client={client}>
<Container maxWidth="lg" sx={{ padding:5 }}>
<Container maxWidth="lg" sx={{ padding: 5 }}>
{loading && <Loading />}
<Typography level="h1" color="info" variant='outlined'>Smart Shopping Planner</Typography>
<Grid item xs={12} md={6} sx={{ display: 'grid', gridTemplateColumns: '1fr 2fr 1fr', gap: '20px', paddingTop: '20px' }}>
{ <CreateItem />}
{ <ItemList />}
<Box>
{ <PriceTag />}
</Box>
</Grid>
<Grid item xs={12} md={6} sx={{ display: 'grid', gridTemplateColumns: '1fr 2fr 1fr', gap: '20px', paddingTop: '20px' }}>
{<CreateItem fetchData={fetchData} />}
{<ItemList fetchData={fetchData} items={items} />}
<Box>
{<PriceTag itemsNo={totalItems(items)} totalCost={totalCost(items)} />}
</Box>
</Grid>
</Container>
</ApolloProvider>
);
}

Просмотреть файл

@ -1,8 +0,0 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

Просмотреть файл

@ -7,159 +7,98 @@ import TextField from '@mui/material/TextField';
import CardContent from '@mui/material/CardContent';
import Box from '@mui/material/Box';
import { MenuItem, Button } from '@mui/material';
import { categories } from '../utils/Utils'
import Loading from './Loading';
const categories = [
{
value: 'Utensils',
label: 'Utensils',
},
{
value: 'Furniture',
label: 'Furniture',
},
{
value: 'Kitchen Wear',
label: 'Kitchen Wear',
},
{
value: 'Bathroom Wear',
label: 'Bathroom Wear',
},
{
value: 'Food Items',
label: 'Food Items',
},
{
value: 'Office Wear',
label: 'Office Wear',
},
];
function CreateItem({ refetch }) {
function CreateItem({ fetchData }) {
const [category, setCategory] = useState('');
const [name, setName] = useState('');
const [quantity, setQuantity] = useState('');
const [unitPrice, setUnitPrice] = useState('');
const [description, setDescription] = useState('');
const [loading, setLoading] = useState(false);
const createItemRequest = async () => {
try {
const response = await fetch('/data-api/rest/Item', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-MS-API-ROLE' : 'admin',
},
body: JSON.stringify({
category,
name,
quantity,
description
})
});
const data = await response.json();
if (response.ok){
} else {
throw new Error(data.error.message);
if (category === '' || name === '' || quantity === '' || unitPrice === '' || description === '') { // check if all fields are filled
alert('Please fill in all fields');
return;
} else {
setLoading(true) // start the loading
try {
const response = await fetch('/data-api/api/Item/', { // fetch data from the API
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-MS-API-ROLE': 'admin' },
body: JSON.stringify({ category, name, quantity, description, unitPrice }) // send the data to the API
});
const data = await response.json();
if (response.ok) {
fetchData(); // refresh the list
} else {
throw new Error(data.error.message);
}
setLoading(false) // stop the loading
} catch (error) {
console.error(error);
setLoading(false)
}
} catch (error) {
console.error(error);
}
}
const handleSubmit = async (event) => {
event.preventDefault();
await createItemRequest();
refetch();
event.preventDefault(); // prevent the default behavior of the form
await createItemRequest(); // call the createItemRequest function
};
return (
<Card>
<CardHeader
sx={{
'& .MuiTextField-root': { m: 1 },
}}
action={
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
}
{loading && <Loading />}
<CardHeader sx={{ '& .MuiTextField-root': { m: 1 }, }}
action={<IconButton aria-label="settings"><MoreVertIcon /></IconButton>}
title="Add Item"
/>
<CardContent>
<Box
component="form"
sx={{
'& .MuiTextField-root': { m: 1 },
}}
noValidate
autoComplete="on"
>
<div>
<TextField
id="category"
select
label="Select"
defaultValue="EUR"
value={category}
onChange={(e) => setCategory(e.target.value)}
<Box component="form" sx={{ '& .MuiTextField-root': { m: 1 }, }} noValidate autoComplete="on">
<TextField id="category" select label="Select" value={category} onChange={(e) => setCategory(e.target.value)}
helperText="Please select your category"
>
required >
{categories.map((option) => (
<MenuItem key={option.value} value={option.value}>
{option.label}
{option.label}
</MenuItem>
))}
</TextField>
</div>
<div>
<TextField
id="name"
label="Item Name"
placeholder="New Item Name"
multiline
helperText="Please type in a new item name"
</TextField>
<TextField id="name" label="Item name" placeholder="New Item Name" multiline helperText="Please type in a new item name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
<div>
<TextField
id="description"
label="Item description"
placeholder="Item Description"
multiline
required
/>
<TextField id="description" label="Item description" placeholder="Item Description" multiline
helperText="Please type in a a short description"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</div>
<div>
required
/>
<TextField
id="quantity"
label="Quantity"
type='number'
placeholder="New Quantity"
multiline
id="unitPrice" label="Unit price in $" type='number' placeholder="Unit Price" multiline
helperText="Please add unit price"
value={unitPrice}
onChange={(e) => setUnitPrice(e.target.value)}
required
/>
<TextField id="quantity" label="Quantity" type='number' placeholder="New Quantity" multiline
helperText="Please add quantity"
value={quantity}
onChange={(e) => setQuantity(e.target.value)}
/>
</div>
required
/>
</Box>
<Button
type="submit"
onClick={handleSubmit}
size="small"
variant="contained"
color="primary"
sx={{ p: 2 }}
>
Add
</Button>
</CardContent>
</Card>
<Button type="submit" onClick={handleSubmit} size="small" variant="contained" color="primary" sx={{ p: 2 }}>Add</Button>
</CardContent>
</Card >
);
}

Просмотреть файл

@ -8,58 +8,51 @@ import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Card from '@mui/material/Card';
import { Button } from '@mui/material';
import Loading from './Loading';
const ItemList = () => {
const [items, setItems] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const fetchData = async (disableLoadState) => {
if(!disableLoadState) setIsLoading(true);
try {
const response = await fetch('/data-api/rest/Item');
if (!response.ok) {
throw new Error(response.statusText);
}
const data = await response.json();
setItems(data.value);
} catch (error) {
setError(error);
}
setIsLoading(false);
};
const ItemList = ({ fetchData, items }) => {
const [loading, setLoading] = useState(false);
const deleteItem = async (id) => {
setLoading(true) // start the loading
try {
const response = await fetch(`/data-api/rest/Item/id/${id}`, {
const response = await fetch('/data-api/api/Item/id/${id}', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-MS-API-ROLE' : 'admin',
'X-MS-API-ROLE': 'admin',
}
});
if (!response.ok) {
throw new Error(response.statusText);
}
await fetchData(true);
} catch (error) { }
await fetchData(true); // refresh the list
setLoading(false) // stop the loading
} catch (error) {
console.error(error);
setLoading(false) // stop the loading
}
}
useEffect(() => {
fetchData();
setLoading(true) // start the loading
fetchData(); // fetch the data once the page loads[componentDidMount]
setLoading(false)
}, []);
return (
<div className='item-list'>
{loading && <Loading />}
<Card>
<TableContainer component={Paper}>
<Table aria-label="simple table">
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Category</TableCell>
<TableCell>Name</TableCell>
<TableCell>Quantity</TableCell>
<TableCell>Description</TableCell>
<TableCell>Unit Price</TableCell>
</TableRow>
</TableHead>
<TableBody>
@ -74,6 +67,7 @@ const ItemList = () => {
<TableCell>{item.name}</TableCell>
<TableCell>{item.quantity}</TableCell>
<TableCell>{item.description}</TableCell>
<TableCell>${item.unitPrice}</TableCell>
<TableCell>
<Button variant="contained" color="error" onClick={() => deleteItem(item.id)}>Delete</Button>
</TableCell>
@ -83,7 +77,7 @@ const ItemList = () => {
</Table>
</TableContainer>
</Card>
</div>
</div>
);
};

Просмотреть файл

@ -0,0 +1,17 @@
.Loading-container {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: #000000;
opacity: 0.5;
}
.loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 50px;
}

Просмотреть файл

@ -0,0 +1,9 @@
import { ThreeDots } from 'react-loading-icons'
import './Loading.css'
export default function Loading() {
return (
<div className='Loading-container'>
<ThreeDots className='loading' stroke="#fff" fill="#fff" />
</div>
)
}

Просмотреть файл

@ -9,11 +9,11 @@ import BookmarkAdd from '@mui/icons-material/BookmarkAddOutlined';
var today = new Date();
var dd = String(today.getDate()).padStart(2, '0');
var mm = String(today.getMonth() +1).padStart(2, '0');
var mm = String(today.getMonth() + 1).padStart(2, '0');
var yyyy = today.getFullYear();
today = mm + '/' + dd + '/' + yyyy;
export default function PriceTag() {
export default function PriceTag({ itemsNo, totalCost }) {
return (
<Card variant="outlined" sx={{ width: 320 }}>
<div>
@ -42,13 +42,13 @@ export default function PriceTag() {
<div>
<Typography level="body3">Total price:</Typography>
<Typography fontSize="lg" fontWeight="lg">
$--
${totalCost}
</Typography>
</div>
<div>
<Typography level="body3">Total items:</Typography>
<Typography fontSize="lg" fontWeight="lg">
--
{itemsNo}
</Typography>
</div>
<Button

Просмотреть файл

@ -6,9 +6,9 @@ import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
// <React.StrictMode>
<App />
);
// If you want to start measuring performance in your app, pass a function

Просмотреть файл

@ -0,0 +1,43 @@
// total number of items
export const totalItems = (items) => {
return items.reduce((total, item) => {
return total + item.quantity;
}, 0);
};
// total cost of all items
export const totalCost = (items) => {
return items.reduce((total, item) => {
return total + (item.quantity * item.unitPrice);
}, 0);
};
//all categories to be used in the select input in the UI
export const categories = [
{
value: 'Utensils',
label: 'Utensils',
},
{
value: 'Furniture',
label: 'Furniture',
},
{
value: 'Kitchen Wear',
label: 'Kitchen Wear',
},
{
value: 'Bathroom Wear',
label: 'Bathroom Wear',
},
{
value: 'Food Items',
label: 'Food Items',
},
{
value: 'Office Wear',
label: 'Office Wear',
},
];