Project files

This commit is contained in:
2023-11-09 18:47:11 +01:00
parent 695abe054b
commit c415135aae
8554 changed files with 858111 additions and 0 deletions

38
reactFrontend/src/App.css Normal file
View File

@@ -0,0 +1,38 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

54
reactFrontend/src/App.js Normal file
View File

@@ -0,0 +1,54 @@
import React from 'react';
import './App.css';
import { BrowserRouter, Routes, Route } from "react-router-dom";
import {Box, Stack} from '@mui/material';
import Navbar from "./components/Navbar";
import Sidebar from "./components/Sidebar";
//import Feed from "./components/Feed";
import PurchaseDetails from "./components/PurchaseDetails.component";
import ArticleDetails from "./components/ArticleDetails.component";
import ArticleOverview from "./components/ArticleOverview.component";
import PurchaseOverview from "./components/PurchaseOverview.component";
import AnalysesOverview from "./components/AnalysesOverview.component";
//import MyRoutes from './MyRoutes';
const App = () => {
return (
<Box>
{/* Set up the browser router */}
<BrowserRouter>
{/* Render the navbar */}
<Navbar />
{/* Set up a stack layout with a sidebar and content */}
<Stack direction="row" spacing={2} justifyContent="space-between">
{/* Render the sidebar */}
<Sidebar />
{/* Set up the routes */}
<Routes>
{/* Set up the route for the purchase overview */}
<Route path="/" element={<PurchaseOverview />} exact/>
{/* Set up the route for the purchase details */}
<Route path="/PurchaseDetails" element={<PurchaseDetails />} />
{/* Set up the route for the article details */}
<Route path="/ArticleDetails" element={<ArticleDetails />} />
{/* Set up the route for the article overview */}
<Route path="/Articles" element={<ArticleOverview />} />
{/* Set up the route for the analyses overview */}
<Route path="/Analyses" element={<AnalysesOverview />} />
</Routes>
</Stack>
</BrowserRouter>
</Box>
);
}
export default App;

View File

@@ -0,0 +1,8 @@
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();
});

View File

@@ -0,0 +1,23 @@
import React from 'react'
import { BrowserRouter, Routes, Route } from "react-router-dom";
import PurchaseDetails from "./components/PurchaseDetails.component";
import ArticleDetails from "./components/ArticleDetails.component";
import ArticleOverview from "./components/ArticleOverview.component";
import Feed from "./components/Feed";
const MyRoutes = (props) => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Feed />} exact/>
<Route path="/PurchaseDetails" element={<PurchaseDetails />} />
<Route path="/ArticleDetails" element={<ArticleDetails />} />
<Route path="/Articles" element={<ArticleOverview />} />
</Routes>
</BrowserRouter>
)
}
export default MyRoutes

View File

@@ -0,0 +1,221 @@
import * as React from 'react';
import {useState, useEffect} from 'react'
import dayjs from 'dayjs';
//import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Container from '@mui/material/Container';
import Grid from '@mui/material/Grid';
import { Button, Paper, styled, } from '@mui/material'
import Tabs from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import PropTypes from 'prop-types';
import Box from '@mui/material/Box';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import Plot from 'react-plotly.js';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
const baseURL = "http://localhost:8000/api"
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`vertical-tabpanel-${index}`}
aria-labelledby={`vertical-tab-${index}`}
{...other}
>
{value === index && (
<Box sx={{ p: 3 }}>
<Typography>{children}</Typography>
</Box>
)}
</div>
);
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.number.isRequired,
value: PropTypes.number.isRequired,
};
function a11yProps(index) {
return {
id: `vertical-tab-${index}`,
'aria-controls': `vertical-tabpanel-${index}`,
};
}
const AnalysesOverview = (props) => {
const navigate = useNavigate();
const [tableData, setTableData] = useState([{article_id: -1, count: 0}]);
const [fromDate, setFromDate] = useState(dayjs('2023-01-01'));
const [toDate, setToDate] = useState(dayjs('2023-09-01'));
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
useEffect(()=>{
},[])
const handlePlotButton = async () => {
const data = {fromDate: fromDate.format("YYYY-MM-DD"), toDate: toDate.format("YYYY-MM-DD")};
console.log(data);
const receivedData = await axios.post(baseURL+"/analyses/countArticles", {
data: data,
})
setTableData(receivedData.data)
}
const handleBrandPlotButton = async () => {
const data = {fromDate: fromDate.format("YYYY-MM-DD"), toDate: toDate.format("YYYY-MM-DD")};
console.log(data);
const receivedData = await axios.post(baseURL+"/analyses/countBrands", {
data: data,
})
setTableData(receivedData.data)
}
return (
<div style={{ height: 800, width: '100%' }}>
<Typography variant="h2" gutterBottom>
Übersicht über alle Analysen
</Typography>
<Box
sx={{ flexGrow: 1, bgcolor: 'background.paper', display: 'flex', height: 500 }} >
<Tabs
orientation="vertical"
variant="scrollable"
value={value}
onChange={handleChange}
aria-label="Vertical tabs example"
sx={{ borderRight: 1, borderColor: 'divider' }}
>
<Tab label="Barplot: Häufigkeit Artikel" {...a11yProps(0)} />
<Tab label="Barplot: Häufigkeit Marke" {...a11yProps(1)} />
<Tab label="Item Three" {...a11yProps(2)} />
<Tab label="Item Four" {...a11yProps(3)} />
<Tab label="Item Five" {...a11yProps(4)} />
<Tab label="Item Six" {...a11yProps(5)} />
<Tab label="Item Seven" {...a11yProps(6)} />
</Tabs>
<TabPanel value={value} index={0}>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
label="Start Datum"
format="DD.MM.YYYY"
value={dayjs(fromDate)}
onChange={val => {setFromDate(val)}}
/>
<DatePicker
label="End Datum"
format="DD.MM.YYYY"
value={dayjs(toDate)}
onChange={val => {setToDate(val)}}
/>
<Button variant="contained"
onClick={handlePlotButton}
>
Plotten
</Button>
</LocalizationProvider>
<Plot
data={[
{
type: 'bar',
y: Array.from(Array(tableData.length).keys()),
x: tableData.map( (val) => (val.count) ),
orientation: 'h'
},
]}
layout={ {height: 3500, title: 'Häufigkeit Artikel',
yaxis: {
tickvals: Array.from(Array(tableData.length).keys()),
ticktext: tableData.map( (val) => (val.article_name) ),
automargin: true,
}}
}
/>
</TabPanel>
<TabPanel value={value} index={1}>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
label="Start Datum"
format="DD.MM.YYYY"
value={dayjs(fromDate)}
onChange={val => {setFromDate(val)}}
/>
<DatePicker
label="End Datum"
format="DD.MM.YYYY"
value={dayjs(toDate)}
onChange={val => {setToDate(val)}}
/>
<Button variant="contained"
onClick={handleBrandPlotButton}
>
Plotten
</Button>
</LocalizationProvider>
<Plot
data={[
{
type: 'bar',
y: Array.from(Array(tableData.length).keys()),
x: tableData.map( (val) => (val.count) ),
orientation: 'h'
},
]}
layout={ {height: 3500, title: 'Häufigkeit Marken',
yaxis: {
tickvals: Array.from(Array(tableData.length).keys()),
ticktext: tableData.map( (val) => (val.brand_name) ),
automargin: true,
}}
}
/>
</TabPanel>
<TabPanel value={value} index={2}>
Item Three
</TabPanel>
<TabPanel value={value} index={3}>
Item Four
</TabPanel>
<TabPanel value={value} index={4}>
Item Five
</TabPanel>
<TabPanel value={value} index={5}>
Item Six
</TabPanel>
<TabPanel value={value} index={6}>
Item Seven
</TabPanel>
</Box>
</div>
);
}
export default AnalysesOverview

View File

@@ -0,0 +1,535 @@
import {useState, useEffect} from 'react'
import React from 'react'
import axios from 'axios';
import { useLocation } from 'react-router-dom';
import { Grid, Typography, TextField, Select, MenuItem, Stack} from '@mui/material'
import { Button, Paper, styled, } from '@mui/material'
import Box from '@mui/material/Box';
import { grey } from '@mui/material/colors';
import Snackbar from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
//import ReceipeCanvas from "./Canvas.component"
import CategoryTree from "./CategoryTree.component"
import ListFieldApi from "./ListFieldApi.component"
import PackagingDataGrid2 from "./PackagingDataGrid2.component"
import NutriDataGrid from "./NutriDataGrid.component"
import PictureCard from "./pictureCard.component"
import IngredienceDataGrid from "./IngredienceDataGrid.component"
const baseURL = "http://localhost:8000/api"
const Item = styled(Paper)(({ theme }) => ({
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
...theme.typography.body2,
padding: theme.spacing(1),
textAlign: 'center',
color: theme.palette.text.secondary,
}));
//article is pushed from PurchaseDetail.component
const ArticleDetails = (props) => {
const location = useLocation();
var earlyExit = false;
if (location.state != null){
console.log(location)
console.log(location.state.id)
console.log(location.state.article_id)
}
else {
console.log("No data to show");
console.log(location)
location.state = {'article_id': {'name': '','EAN':'', 'brand':''}};
console.log(location)
earlyExit = true;
}
const units = ['kg','g','l','ml','St.','kJ','kcal','µg'];
const grades = ['','A','B','C','D','E'];
const novas = ['','1','2','3','4'];
const [article, setArticle] = useState(location.state.article_id);
const [name, setName] = useState(article.name);
const [EAN, setEAN] = useState(article.EAN);
const [offlink, setOfflink] = useState(article.offlink);
const [quantityPerPackage, setQuantityPerPackage] = useState(article.quantityPerPackage);
const [unit, setUnit] = useState(article.unit);
//const [nutritionGrade, setNutritionGrade] = useState(article.nutritionGrade);
//const [novaGroup, setNovaGroup] = useState(article.novaGroup);
//const [ecoGrade, setEcoGrade] = useState(article.ecoGrade);
const [snackbar, setSnackbar] = useState(null);
const EANvalid = () => {
if (EAN === null)
return false;
else {
if (EAN.length === 13 || EAN.length === 8) {
return true;
} else {
return false;
}
}
}
const handleSearchButton = async () => {
try {
const ans = await axios.get(baseURL+"/offsearch_get_by_ean/"+EAN+"/"+article.pk, { });
//setArticle(ans.data.article);
const newArticle = ans.data;
setArticle(newArticle);
//console.log('New Article:')
//console.log(newArticle)
location.state.article_id = newArticle;
setSnackbar({ children: 'Daten erfolgreich übernommen', severity: 'success' });
}
catch(error){
setSnackbar({ children: error.message, severity: 'error' });
}
};
const handelArticleChange = (value) => {
setArticle(value);
}
const handleArticleChangeAPI = async (event, field, value) => {
try {
if (field === 'nutritionGrade' || field === 'novaGroup' || field === 'ecoGrade'){
console.log(value)
const newArticle = { ...article, [field]: value};
const sendArticle = await axios.put(baseURL+"/article/"+article.pk, {
article: newArticle
});
setArticle(newArticle);
}
else {
const newArticle = { ...article, [field]: value};
const sendArticle = await axios.put(baseURL+"/article/"+article.pk, {
article: newArticle
});
setArticle(newArticle);
}
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
}
catch(error){
setSnackbar({ children: error.message, severity: 'error' });
setName(eval('article.'+field));
}
}
const handleCloseSnackbar = () => setSnackbar(null);
if (earlyExit){
return(<div> </div>);
}
return (
<Grid container spacing={1}>
<Grid item xs={12}>
<Typography variant="h3" component="div" gutterBottom>
{article.brand === null ? '': article.brand.name} {" "+ article.name}
</Typography>
</Grid>
<Grid item xs={12}>
<TextField
fullWidth
margin="dense"
id="nameField"
//defaultValue={name != null ? name : ""}
value={name}
onChange={(e) => setName(e.target.value)}
error={(article.name.length === 0)}
label="Name"
type="text"
variant="standard"
onBlur={(e) => handleArticleChangeAPI(e,'name', e.target.value)}
/>
</Grid>
<Grid item xs={12}>
<TextField
//autoFocus
fullWidth
margin="dense"
id="EAN"
value={EAN != null ? EAN : "" }
onChange={(e) => setEAN(e.target.value)}
error={!EANvalid()}
label="EAN/EAN-13"
type="number"
variant="standard"
onBlur={(e) => handleArticleChangeAPI(e,'EAN', e.target.value)}
/>
</Grid>
<Grid item xs={12}>
<TextField
//autoFocus
fullWidth
margin="dense"
id="offlink"
value={offlink != null ? offlink : ""}
onChange={(e) => setOfflink(e.target.value)}
label="OpenFoodFact Link"
type="text"
variant="standard"
onBlur={(e) => handleArticleChangeAPI(e,'offlink', e.target.value)}
/>
</Grid>
<Grid item xs={4}>
<Button variant="contained"
onClick={handleSearchButton}
disabled={!EANvalid()}
>
EAN bei OpenFoodFact suchen
</Button>
</Grid>
{/*-----------------------------------------------------------------------------------*/}
{/*-----------------------------------------------------------------------------------*/}
<Grid item xs={12}>
<Typography variant="h4" component="div" gutterBottom textAlign="center">
Produkteigenschaften
</Typography>
</Grid>
<Grid item xs={4}>
<PictureCard
type="Artikelbild"
article={article}
pictureType="frontImage"
onChange={handelArticleChange}
>
</PictureCard>
</Grid>
<Grid item xs={8}>
<Grid container spacing={1}>
<Grid item xs={12}>
<ListFieldApi
apiURL="/article/brands/"
titleName="Marke"
addName="Marke"
multiple={false}
value={article.brand}
onChange={handelArticleChange}
article={article}
fieldName={'brand'}
/>
</Grid>
<Grid item xs={10}>
<TextField
//autoFocus
fullWidth
margin="dense"
id="quantityPerPackage"
value={quantityPerPackage != null ? quantityPerPackage : ""}
onChange={(e) => setQuantityPerPackage(e.target.value)}
label="Mengenangabe"
type="number"
variant="standard"
onBlur={(e) => handleArticleChangeAPI(e,'quantityPerPackage',e.target.value)}
/>
</Grid>
<Grid item xs={2}>
<Select
fullWidth
id="unit"
value={unit}
label="Einheit"
onChange={(e) => {setUnit(e.target.value);handleArticleChangeAPI(e,'unit',e.target.value)}}
>
{units.map((unit) => (
<MenuItem
key={unit}
value={unit}
//style={getStyles(name, personName, theme)}
>
{unit}
</MenuItem>
))}
</Select>
</Grid>
<Grid item xs={12}>
<Typography variant="subtitle1" component="div" gutterBottom>
Verpackung
</Typography>
</Grid>
<Grid item xs={12}>
<PackagingDataGrid2
articleId={article.pk}
/>
</Grid>
<Grid item xs={12}>
<ListFieldApi
apiURL="/article/labels/"
titleName="Kennzeichnungen"
addName="Label"
multiple={true}
value={article.labels}
onChange={handelArticleChange}
article={article}
fieldName={'labels'}
/>
</Grid>
<Grid item xs={12}>
<Typography variant="subtitle1" component="div" gutterBottom>
Kategorie
</Typography>
</Grid>
<Grid item xs={12}>
<Box sx={{ borderRadius: '10px', p: 2, border: '1px solid', borderColor: grey[300] }}>
<CategoryTree
value={article.category}
onChange={handelArticleChange}
article={article}
/>
</Box>
</Grid>
<Grid item xs={12}>
<Typography variant="subtitle1" component="div" gutterBottom>
Scors
</Typography>
</Grid>
<Grid item xs={12}>
<Stack direction="row" spacing={2}>
<Item>
<Select
fullWidth
labelId="nutriSelect"
id="nutri"
value={article.nutritionGrade === null ? '' : article.nutritionGrade}
label="Nutri-Score"
onChange={(e) => {const value = e.target.value;
//setNutritionGrade(typeof value === 'string' ? value.split(',') : value);
handleArticleChangeAPI(e,'nutritionGrade', value)
}
}
>
{grades.map((grade) => (
<MenuItem
key={grade}
value={grade}
//style={getStyles(name, personName, theme)}
>
{grade}
</MenuItem>
))}
</Select>
<img width={'100%'} src={"http://127.0.0.1:8080/Nutri-score-"+article.nutritionGrade+".svg"} alt="NutriScore"/>
</Item>
<Item>
<Select
fullWidth
labelId="nutriSelect"
id="nova"
value={article.novaGroup === null ? '' : article.novaGroup}
label="NovaGroup"
onChange={(e) => {const value = e.target.value;
//setNovaGroup(typeof value === 'string' ? value.split(',') : value);
handleArticleChangeAPI(e,'novaGroup', value)
}
}
>
{novas.map((grade) => (
<MenuItem
key={grade}
value={grade}
//style={getStyles(name, personName, theme)}
>
{grade}
</MenuItem>
))}
</Select>
<img width={'100%'} src={"http://127.0.0.1:8080/nova-group-"+article.novaGroup+".svg"} alt="NovaGroup"/>
</Item>
<Item>
<Select
fullWidth
labelId="nutriSelect"
id="eco"
value={article.ecoGrade === null ? '' : article.ecoGrade}
label="EcoGrade"
onChange={(e) => {const value = e.target.value;
//setEcoGrade(typeof value === 'string' ? value.split(',') : value);
handleArticleChangeAPI(e,'ecoGrade', value)
}
}
>
{grades.map((grade) => (
<MenuItem
key={grade}
value={grade}
//style={getStyles(name, personName, theme)}
>
{grade}
</MenuItem>
))}
</Select>
<img width={'100%'} src={"http://127.0.0.1:8080/Eco-score-"+article.ecoGrade+".svg"} alt="EcoGrade"/>
</Item>
</Stack>
</Grid>
</Grid>
</Grid>
{/*-----------------------------------------------------------------------------------*/}
{/*-----------------------------------------------------------------------------------*/}
<Grid item xs={12}>
<Typography variant="h4" component="div" gutterBottom textAlign="center">
Inhaltsstoffe
</Typography>
</Grid>
<Grid item xs={4}>
<PictureCard
type="Bild der Inhaltsstoffe"
article={article}
pictureType="ingredientsImage"
onChange={handelArticleChange}
>
</PictureCard>
</Grid>
<Grid item xs={8}>
<Grid item xs={12}>
{/*
<ListFieldApi
apiURL="/article/ingrediences"
titleName="Inhaltsstoffe"
addName="Zutat"
multiple={true}
value={article.ingredients}
onChange={handelArticleChange}
article={article}
fieldName={'ingredients'}
/>
*/}
<IngredienceDataGrid
articleId={article.pk}
/>
</Grid>
<Grid item xs={12}>
<ListFieldApi
apiURL="/article/allergenes/"
titleName="Allergene"
addName="Zutat"
multiple={true}
value={article.allergenes}
onChange={handelArticleChange}
article={article}
fieldName={'allergenes'}
/>
</Grid>
<Grid item xs={12}>
<ListFieldApi
apiURL="/article/traces/"
titleName="Spuren"
addName="Zutat"
multiple={true}
value={article.traces}
onChange={handelArticleChange}
article={article}
fieldName={'traces'}
/>
</Grid>
<Grid item xs={12}>
<ListFieldApi
apiURL="/article/embs/"
titleName="EMB Nummern"
addName="Nummer"
multiple={true}
value={article.embs}
onChange={handelArticleChange}
article={article}
fieldName={'embs'}
/>
</Grid>
<Grid item xs={12}>
<ListFieldApi
apiURL="/article/manufacturingPlaces/"
titleName="Ort der Herstellung"
addName="Ort"
multiple={true}
value={article.manufacturingPlaces}
onChange={handelArticleChange}
article={article}
fieldName={'manufacturingPlaces'}
/>
</Grid>
<Grid item xs={12}>
<ListFieldApi
apiURL="/article/originIngredients/"
titleName="Herkunft der Inhaltsstoffe"
addName="Ort"
multiple={true}
value={article.originIngredients}
onChange={handelArticleChange}
article={article}
fieldName={'originIngredients'}
/>
</Grid>
</Grid>
{/*-----------------------------------------------------------------------------------*/}
{/*-----------------------------------------------------------------------------------*/}
<Grid item xs={12}>
<Typography variant="h4" component="div" gutterBottom textAlign="center">
Nährwertangaben
</Typography>
</Grid>
<Grid item xs={4}>
<PictureCard
type="Bild der Nährwerte"
article={article}
pictureType="nutritionImage"
onChange={handelArticleChange}
>
</PictureCard>
</Grid>
<Grid item xs={8}>
<NutriDataGrid
article={article}
/>
</Grid>
<div>
{!!snackbar && (
<Snackbar
open
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
onClose={handleCloseSnackbar}
autoHideDuration={6000}
>
<Alert {...snackbar} onClose={handleCloseSnackbar} />
</Snackbar>
)}
</div>
</Grid>
)
}
export default ArticleDetails

View File

@@ -0,0 +1,65 @@
import * as React from 'react';
import {useState, useEffect} from 'react'
//import Button from '@mui/material/Button';
import Typography from '@mui/material/Typography';
import Container from '@mui/material/Container';
import Grid from '@mui/material/Grid';
//import Box from '@mui/material/Box';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
import ArticleCard from "./articleCard.component";
const baseURL = "http://localhost:8000/api"
const ArticelOverview = (props) => {
const navigate = useNavigate();
const [tableData, setTableData] = useState([]);
useEffect(()=>{
axios.get(baseURL+"/articles/")
.then((res) => {
const data = res.data;
console.log(data)
setTableData(data);
})
},[])
return (
<div style={{ height: 800, width: '100%' }}>
<Typography variant="h2" gutterBottom>
Übersicht über alle Artikel
</Typography>
<Container maxWidth="xl">
<Grid
container
direction="row"
justify="space-evenly"
alignItems="stretch"
spacing={2}
>
{
tableData.map(data => (
<Grid item
key={`GridItem-${data.pk}`} xs={2}
>
<ArticleCard
article={data}
navigate={navigate}
/>
</Grid>
))
}
</Grid>
</Container>
</div>
);
}
export default ArticelOverview

View File

@@ -0,0 +1,193 @@
import React from 'react'
import { useRef, useEffect, useState } from 'react'
const ReceipeCanvas = props => {
const canvasRef = useRef(null)
const contextRef = useRef(null);
const { image, myRef, setNewRect, ...rest } = props
const [isDrawing, setIsDrawing] = useState(false);
//const [data, setData] = useState(drawData)
const canvasOffSetX = useRef(null);
const canvasOffSetY = useRef(null);
const startX = useRef(null);
const startY = useRef(null);
const rectWidth = useRef(null);
const rectHeight = useRef(null);
const imgWidth = useRef(null);
const imgHeight = useRef(null);
var ratio;
const draw = (drawData) => {
console.log('draw')
//contextRef.current.beginPath();
contextRef.current.strokeStyle="red";
//contextRef.current.rect(drawData.location_x*ratio,drawData.location_y*ratio,drawData.location_w*ratio,drawData.location_h*ratio);
//contextRef.current.stroke();
const hRatio = contextRef.current.canvas.width / image.width;
//Clears the entire canvas
contextRef.current.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
//Redraw the background image
contextRef.current.drawImage(image, 0,0,
image.width,
image.height,
0,0,
image.width*hRatio,
image.height*hRatio);
contextRef.current.strokeRect(drawData.location_x*hRatio,drawData.location_y*hRatio,drawData.location_w*hRatio,drawData.location_h*hRatio);
}
const drawImage = async (image) => {
console.log('Draw image fkt')
/*
var img = new Image();
img.onload = function(){
imgWidth.current = img.naturalWidth
imgHeight.current = img.naturalHeight;
//console.log("width,height:", img.width, img.height);
//console.log("offsetW,offsetH:", img.offsetWidth, img.offsetHeight);
}
img.src = image.src;
*/
await image.decode()
contextRef.current.clearRect(0, 0, contextRef.current.canvas.width, contextRef.current.canvas.height);
ratio = contextRef.current.canvas.width / image.width;
contextRef.current.canvas.height = image.height*ratio;
contextRef.current.drawImage(image, 0,0,
image.width,
image.height,
0,0,
image.width*ratio,
image.height*ratio);
console.log(image.width, ratio, contextRef.current.canvas.height)
}
myRef.current.draw = draw
myRef.current.drawImage = drawImage
useEffect(() => {
const canvas = canvasRef.current
const context = canvas.getContext('2d')
context.canvas.width = 300
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
ratio = context.canvas.width / image.width ;
context.canvas.height = image.height*ratio;
context.drawImage(image, 0,0,
image.width,
image.height,
0,0,
image.width*ratio,
image.height*ratio);
//draw(context,hRatio);
contextRef.current = context;
console.log('useeffect',image)
},[image])
const startDrawingRectangle = ({nativeEvent}) => {
nativeEvent.preventDefault();
nativeEvent.stopPropagation();
const canvasOffSet = canvasRef.current.getBoundingClientRect();
canvasOffSetX.current = canvasOffSet.left;
canvasOffSetY.current = canvasOffSet.top;
startX.current = nativeEvent.clientX - canvasOffSetX.current;
startY.current = nativeEvent.clientY - canvasOffSetY.current;
console.log('#nativ',nativeEvent.clientX,nativeEvent.clientY)
setIsDrawing(true);
console.log("Start drawing")
};
const drawRectangle = ({nativeEvent}) => {
if (!isDrawing) {
return;
}
nativeEvent.preventDefault();
nativeEvent.stopPropagation();
const newMouseX = nativeEvent.clientX - canvasOffSetX.current;
const newMouseY = nativeEvent.clientY - canvasOffSetY.current;
rectWidth.current = newMouseX - startX.current;
rectHeight.current = newMouseY - startY.current;
contextRef.current.strokeStyle="red";
//Clears the entire canvas
contextRef.current.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
//Redraw the background image
const hRatio = contextRef.current.canvas.width / image.width;
contextRef.current.drawImage(image, 0,0,
image.width,
image.height,
0,0,
image.width*hRatio,
image.height*hRatio);
contextRef.current.strokeRect(startX.current, startY.current, rectWidth.current, rectHeight.current);
};
const stopDrawingRectangle = ({nativeEvent}) => {
nativeEvent.preventDefault();
nativeEvent.stopPropagation();
const hRatio = contextRef.current.canvas.width / image.width;
console.log({location_x: startX.current/hRatio, location_y: startY.current/hRatio, location_w: rectWidth.current/hRatio,location_h: rectHeight.current/hRatio})
if (startX.current/hRatio < 0){
startX.current = 0;
}
if ((rectWidth.current < 0) && (rectHeight.current > 0)) {
setNewRect({location_x: (startX.current+rectWidth.current)/hRatio, location_y: startY.current/hRatio, location_w: -rectWidth.current/hRatio,location_h: rectHeight.current/hRatio});
}
else if ((rectWidth.current > 0) && (rectHeight.current < 0)) {
setNewRect({location_x: startX.current/hRatio, location_y: (startY.current+rectHeight.current)/hRatio, location_w: rectWidth.current/hRatio,location_h: -rectHeight.current/hRatio});
}
else if ((rectHeight.current < 0) && (rectWidth.current < 0)) {
console.log('<<')
setNewRect({location_x: (startX.current+rectWidth.current)/hRatio, location_y: (startY.current+rectHeight.current)/hRatio, location_w: -rectWidth.current/hRatio,location_h: -rectHeight.current/hRatio});
}
else {
setNewRect({location_x: startX.current/hRatio, location_y: startY.current/hRatio, location_w: rectWidth.current/hRatio,location_h: rectHeight.current/hRatio});
}
setIsDrawing(false);
};
return <canvas
ref={canvasRef}
onMouseDown={startDrawingRectangle}
onMouseMove={drawRectangle}
onMouseUp={stopDrawingRectangle}
//onMouseLeave={stopDrawingRectangle}
{...rest}>
</canvas>
}
ReceipeCanvas.defaultProps = {
myRef: {current: {}}
}
export default ReceipeCanvas

View File

@@ -0,0 +1,94 @@
import {useState, useEffect} from 'react'
import React from 'react'
import axios from 'axios';
import {Snackbar, Alert} from '@mui/material';
import TreeView from '@mui/lab/TreeView';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import TreeItem from '@mui/lab/TreeItem';
const baseURL = "http://localhost:8000/api"
const apiURL = "/article/categories/"
const CategoryTree = (props) => {
const { value, article, onChange } = props;
const [categories, setCategories] = useState({id: 'root', data: {id: -1, name: ''}});
const [snackbar, setSnackbar] = useState(null);
const renderTree = (nodes) => (
<TreeItem key={nodes.id} nodeId={nodes.id.toString()} label={nodes.data.name}>
{Array.isArray(nodes.children)
? nodes.children.map((node) => renderTree(node))
: null}
</TreeItem>
);
const handleCloseSnackbar = () => setSnackbar(null);
const handleNodeSelect = async (e, nodeid) => {
try {
//var completeCategory = {};
const completeCategory = await axios.get(baseURL+"/article/categories/"+nodeid);
//console.log(completeCategory.data);
const newArticle = { ...article, 'category': completeCategory.data};
const sendArticle = await axios.put(baseURL+"/article/"+article.pk, {
article: newArticle
});
//console.log(newArticle)
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
onChange(newArticle);
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
}
};
useEffect(()=>{
axios.get(baseURL+apiURL)
.then((res) => {
const data = res.data;
setCategories(data);
//console.log(data);
})
},[])
return (
<div style={{ height: 350, width: '100%' }}>
<TreeView
//aria-label="rich object"
defaultExpanded={value === null ? [] : value.ancestorIds.map(x => String(x)) }
defaultExpandIcon={<ChevronRightIcon />}
defaultCollapseIcon={<ExpandMoreIcon />}
defaultSelected={value === null ? [''] : value.id.toString()}
onNodeSelect={handleNodeSelect}
sx={{ height: 350, flexGrow: 1, overflowY: 'auto' }}
>
{renderTree(categories)}
</TreeView>
{!!snackbar && (
<Snackbar
open
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
onClose={handleCloseSnackbar}
autoHideDuration={6000}
>
<Alert {...snackbar} onClose={handleCloseSnackbar} />
</Snackbar>
)}
</div>
);
}
export default CategoryTree

View File

@@ -0,0 +1,17 @@
import React, { Component } from "react";
export default class Home extends Component {
render() {
return (
<div className="Home">
<div className="lander">
<h1>Home page</h1>
<p>A simple app showing react button click navigation</p>
<form>
</form>
</div>
</div>
);
}
}

View File

@@ -0,0 +1,362 @@
import {useState, useEffect} from 'react'
import React from 'react'
import axios from 'axios';
import PropTypes from 'prop-types';
//import { useNavigate, useLocation } from 'react-router-dom';
import { Button } from '@mui/material'
import AddIcon from '@mui/icons-material/Add';
import DeleteIcon from '@mui/icons-material/DeleteOutlined';
import { DataGrid, GridToolbarContainer, GridActionsCellItem } from '@mui/x-data-grid';
import Snackbar from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
import Grid from '@mui/material/Grid';
import Dialog from '@mui/material/Dialog';
import TextField from '@mui/material/TextField';
import FormControlLabel from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContentText from '@mui/material/DialogContentText';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import ListFieldApiwoArticle from "./ListFieldApiwoArticle.component"
const baseURL = "http://localhost:8000/api"
const apiURL = "/article/ingrediences/"
function EditToolbar(props) {
const { setTableData, articleId, setOpenAddDialog } = props;
const handleNewEntryClick = async () => {
const newValue = { ingredient: {id: -1, name: 'Bitte auswählen'}, percent: null, isNew: true, article: articleId };
const receivedData = await axios.post(baseURL+"/article/articleIngrediences/"+articleId, {
newValue,
})
//console.log(receivedData)
const newRow = { id: receivedData.data.id, ingredient: {id: -1, name: 'Bitte auswählen'}, percent: receivedData.data.percent, isNew: true, article: receivedData.data.article, position: receivedData.data.position };
setTableData((oldRows) => [...oldRows, newRow]);
};
const handleNewIngredientClick = () => {
setOpenAddDialog(true);
/*
const newValue = { packaging_id: {id: -1, name: 'Bitte auswählen'}, weight: 0, isNew: true, article_id: articleId };
const receivedData = await axios.post(baseURL+"/article/articleIngrediences/"+articleId, {
newValue,
})
//console.log(receivedData)
const newRow = { id: receivedData.data.id, ingredient: {id: -1, name: 'Bitte auswählen'}, percent: receivedData.data.percent, isNew: true, article: receivedData.data.article, position: receivedData.data.positio };
setTableData((oldRows) => [...oldRows, newRow]);
*/
};
return (
<GridToolbarContainer>
<Button color="primary" startIcon={<AddIcon />} onClick={handleNewEntryClick}>
Neuer Eintrag
</Button>
<Button color="primary" startIcon={<AddIcon />} onClick={handleNewIngredientClick}>
Neue Zutat
</Button>
</GridToolbarContainer>
);
}
EditToolbar.propTypes = {
setTableData: PropTypes.func.isRequired,
setOpenAddDialog: PropTypes.func.isRequired,
}
const IngredienceDataGrid = (props) => {
const { articleId } = props;
//list contains all ingrediences
const [list, setList] = useState([{id: -1, name: '', subIngredients: []}]);
const [tableData, setTableData] = useState([{id: -1, ingredient: {id: -1, name: ''}, percent: null, article: -1, position: -1}]);
const [snackbar, setSnackbar] = useState(null);
const [openAddDialog, setOpenAddDialog] = useState(false);
const [newIngredient, setNewIngredient] = useState({name: '', allergenes: [], vegan: false, additive: false, vegetarian: false, palmoilfree: true, subIngredients: [], eNumber: ''});
const columnsIngrediences = [
{
field: 'position',
headerName: 'Pos.',
width: 50,
editable: true,
},
{
field: 'ingredient',
headerName: 'Name',
flex: 1,
type: 'singleSelect',
valueOptions: list,
getOptionValue: (value) => value.id,
getOptionLabel: (value) => {if (value.subIngredients.length === 0) {
return value.name
} else {
return value.name + ' ('+value.subIngredients.map(x => x.name).join(', ')+')'
}
},
editable: true,
valueGetter: (params) => {try {return params.row.ingredient.id;} catch {return '';} },
},
{
field: 'percent',
headerName: 'Anteil an Gesamtmasse',
type: 'number',
width:170,
editable: true,
valueFormatter: (params) => {
if (params.value == null) {
return '';
}
return `${params.value} %`;
},
},
{
field: 'actions',
type: 'actions',
headerName: 'Aktionen',
width: 100,
cellClassName: 'actions',
getActions: ({ id }) => {
return [
<GridActionsCellItem
icon={<DeleteIcon />}
label="Delete"
onClick={(e) => handleDeleteClick(id,e)}
color="inherit"
/>,
];
},
}
]
const findId = (idToLookFor) => {
for (var i = 0; i < list.length; i++) {
if (list[i].name === idToLookFor) {
return(list[i].id);
}
}
}
const handleCloseSnackbar = () => setSnackbar(null);
//Anscheinend ist es notwendig das event e zu übergeben, ansonsten passieren komische Dinge (kein async, multiple trigger beim rendern etc)
const handleDeleteClick = async (id,e) => {
const newData = tableData.filter((row) => row.id !== id);
//console.log('Delete clicked')
//console.log(id)
//console.log(e)
try {
const receivedData = await axios.delete(baseURL+"/article/articleIngrediences/"+articleId, {
data: id,
})
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
setTableData(newData);
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
}
};
const processRowUpdate = React.useCallback( async (newRow, oldRow) => {
console.log(newRow)
const ingredient = list.find(x => x.id === newRow.ingredient);
const updatedRow = {'id': newRow.id, 'ingredient': ingredient, 'position': newRow.position, 'isNew': false, 'article': articleId, 'percent': newRow.percent};
const newData = tableData.map((row) => (row.id === newRow.id ? updatedRow : row));
try {
const response = await axios.put(baseURL+"/article/articleIngrediences/"+articleId, {
updatedRow
});
//console.log(response);
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
setTableData(newData);
//onChange(newData);
return updatedRow;
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
return oldRow;
}
}
);
//const handleProcessRowUpdateError = React.useCallback((error) => {
// setSnackbar({ children: error.message, severity: 'error' });
//}, []);
const handleClose = () => {
}
const handleAdd = async() => {
console.log(newIngredient)
try {
const ans = await axios.post(baseURL+"/article/ingrediences/", {
newIngredient,
})
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
setOpenAddDialog(false);
setNewIngredient({name: '', allergenes: [], vegan: false, additive: false, vegetarian: false, palmoilfree: true, subIngredients: [], eNumber: ''});
setList(list.concat(ans.data));
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
}
}
useEffect(()=>{
axios.get(baseURL+apiURL)
.then((res) => {
const data = res.data;
console.log(data);
setList(data);
})
axios.get(baseURL+"/article/articleIngrediences/"+articleId)
.then((res) => {
const data = res.data;
//Add id for each element for displaying in Data Grid
//data.forEach((item, i) => {
// item.id = i + 1;
//});
setTableData(data);
console.log("Fetching ingrediences data")
console.log(data)
})
},[])
return (
<div style={{ height: 350, width: '100%' }}>
<DataGrid
rows={tableData}
columns={columnsIngrediences}
pageSize={10}
editMode="row"
//getRowId={(row) => row.internalId}
processRowUpdate={processRowUpdate}
//onProcessRowUpdateError={handleProcessRowUpdateError}
experimentalFeatures={{ newEditingApi: true }}
components={{
Toolbar: EditToolbar,
}}
componentsProps={{
toolbar: { setTableData, articleId, setOpenAddDialog },
}}
/>
<Dialog open={openAddDialog} onClose={handleClose}>
<DialogTitle>Zutat hinzufügen</DialogTitle>
<DialogContent>
<DialogContentText>
Füge eine neue Zutat hinzu:
</DialogContentText>
<Grid container spacing={1}>
<Grid item xs={12}>
<TextField
fullWidth
margin="dense"
id="nameField"
//defaultValue={name != null ? name : ""}
value={newIngredient.name}
onChange={(e) => setNewIngredient({ ...newIngredient, name: e.target.value})}
//error={(article.name.length === 0)}
label="Name"
type="text"
variant="standard"
//onBlur={(e) => handleArticleChangeAPI(e,'name', e.target.value)}
/>
</Grid>
<Grid item xs={12}>
<ListFieldApiwoArticle
apiURL="/article/allergenes/"
titleName="Allergene"
addName="Zutat"
multiple={true}
value={newIngredient.allergenes}
onChange={(e) => setNewIngredient({ ...newIngredient, allergenes: e})}
addNew={false}
/>
</Grid>
<Grid item xs={12}>
<FormControlLabel control={<Checkbox checked={newIngredient.vegan} onChange={(e) => setNewIngredient({ ...newIngredient, vegan: e.target.checked})} />} label="vegan?" />
<FormControlLabel control={<Checkbox checked={newIngredient.vegetarian} onChange={(e) => setNewIngredient({ ...newIngredient, vegetarian: e.target.checked})} />} label="vegetarisch?" />
<FormControlLabel control={<Checkbox checked={newIngredient.palmoilfree} onChange={(e) => setNewIngredient({ ...newIngredient, palmoilfree: e.target.checked})} />} label="Palmölfrei?" />
</Grid>
<Grid item xs={12}>
<FormControlLabel control={<Checkbox checked={newIngredient.additive} onChange={(e) => setNewIngredient({ ...newIngredient, additive: e.target.checked})} />} label="Zusatzstoff?" />
</Grid>
<Grid item xs={12}>
<TextField
fullWidth
margin="dense"
id="EnumberField"
disabled={!newIngredient.additive}
value={newIngredient.eNumber}
onChange={(e) => setNewIngredient({ ...newIngredient, eNumber: e.target.value})}
error={(newIngredient.eNumber.length === 0 && newIngredient.additive)}
label="E-Nummer"
type="text"
variant="standard"
/>
</Grid>
<Grid item xs={12}>
<ListFieldApiwoArticle
apiURL="/article/ingrediences/"
titleName="Inhaltsstoffe"
addName="Zutat"
multiple={true}
value={newIngredient.subIngredients}
onChange={(e) => setNewIngredient({ ...newIngredient, subIngredients: e})}
/>
</Grid>
</Grid>
</DialogContent>
<DialogActions>
<Button onClick={() => setOpenAddDialog(false)}>Abbrechen</Button>
<Button onClick={handleAdd}>Hinzufügen</Button>
</DialogActions>
</Dialog>
{!!snackbar && (
<Snackbar
open
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
onClose={handleCloseSnackbar}
autoHideDuration={6000}
>
<Alert {...snackbar} onClose={handleCloseSnackbar} />
</Snackbar>
)}
</div>
);
}
export default IngredienceDataGrid

View File

@@ -0,0 +1,152 @@
import React from 'react'
import axios from 'axios';
import {useState, useEffect} from 'react'
import {TextField, Autocomplete, createFilterOptions} from '@mui/material'
import Snackbar from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
const filter = createFilterOptions();
const baseURL = "http://localhost:8000/api"
const ListFieldApi = (props) => {
const { apiURL, titleName, addName, multiple, value, onChange, sx, article, fieldName } = props
const [list, setList] = useState([""]);
const [inputValue, setInputValue] = React.useState('')
const [snackbar, setSnackbar] = useState(null);
const handleAdd = async(newValue) => {
const send = await axios.post(baseURL+apiURL+'/', {
name: newValue,
})
const rec = await axios.get(baseURL+apiURL)
setList(rec.data);
};
const handleCloseSnackbar = () => setSnackbar(null);
const handleOnChange = async (event, newValue) => {
//Neuer Wert ist letztes Array element
//console.log(event)
//console.log(newValue)
if (newValue === null){
}
// Fall das wir mehr als einen Eintrag haben
else if (Array.isArray(newValue)) {
if (!(newValue.length === 0)){
// Wenn letztes array element nicht in der Liste der möglichen, dann haben wir ein neues Element was wir hinzufügen
if (!list.some(item => item.name === newValue[newValue.length - 1].name)) {
handleAdd(newValue[newValue.length - 1].inputValue);
newValue[newValue.length - 1].name = newValue[newValue.length - 1].inputValue;
delete newValue[newValue.length - 1]["inputValue"];
}
}
}
// Hinzufügen des ersten Elements
else{
if (!list.some(item => item.name === newValue.name)) {
handleAdd(newValue.inputValue);
newValue.name = newValue.inputValue;
delete newValue["inputValue"];
}
}
try {
const newArticle = { ...article, [fieldName]: newValue};
const sendArticle = await axios.put(baseURL+"/article/"+article.pk, {
article: newArticle
});
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
onChange(newArticle);
}
catch(error){
setSnackbar({ children: error.message, severity: 'error' });
if (Array.isArray(newValue)) {
newValue = newValue.slice(0, -1);
}
else {
newValue = null;
}
}
}
useEffect(()=>{
axios.get(baseURL+apiURL)
.then((res) => {
const data = res.data;
setList(data);
//console.log(data)
})
},[])
return (
<div>
<Autocomplete
multiple={multiple}
id="tags-standard"
options={list}
sx={sx}
getOptionLabel={(option) => {
if (option.name === undefined){
return ""
}
else{
return option.name
}
}}
isOptionEqualToValue={(option, value) => option.name === value.name}
inputValue={inputValue}
onInputChange={(_, newInputValue) => {
setInputValue(newInputValue)
}}
//defaultValue={[]}
value={value}
renderInput={(params) => (
<TextField
{...params}
variant="standard"
label={titleName}
placeholder={addName}
/>
)}
onChange={handleOnChange}
filterOptions={(options, params) => {
const filtered = filter(options, params);
if (params.inputValue !== '') {
filtered.push({
inputValue: params.inputValue,
name: `Add "${params.inputValue}"`,
});
}
return filtered;
}}
renderOption={(props, option) => <li {...props}>{option.name}</li>}
/>
{!!snackbar && (
<Snackbar
open
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
onClose={handleCloseSnackbar}
autoHideDuration={6000}
>
<Alert {...snackbar} onClose={handleCloseSnackbar} />
</Snackbar>
)}
</div>
)
}
export default ListFieldApi

View File

@@ -0,0 +1,154 @@
import React from 'react'
import axios from 'axios';
import {useState, useEffect} from 'react'
import {TextField, Autocomplete, createFilterOptions} from '@mui/material'
import Snackbar from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
const filter = createFilterOptions();
const baseURL = "http://localhost:8000/api"
const ListFieldApiwoArticle = (props) => {
const { apiURL, titleName, addName, multiple, value, onChange, sx, addNew} = props
const [list, setList] = useState([""]);
const [inputValue, setInputValue] = React.useState('')
const [snackbar, setSnackbar] = useState(null);
const handleAdd = async(newValue) => {
const send = await axios.post(baseURL+apiURL, {
name: newValue,
})
const rec = await axios.get(baseURL+apiURL)
setList(rec.data);
};
const handleCloseSnackbar = () => setSnackbar(null);
const handleOnChange = async (event, newValue) => {
//Neuer Wert ist letztes Array element
//console.log(event)
//console.log(newValue)
if (newValue === null){
}
// Fall das wir mehr als einen Eintrag haben
else if (Array.isArray(newValue)) {
if (!(newValue.length === 0)){
// Wenn letztes array element nicht in der Liste der möglichen, dann haben wir ein neues Element was wir hinzufügen
if (!list.some(item => item.name === newValue[newValue.length - 1].name)) {
handleAdd(newValue[newValue.length - 1].inputValue);
newValue[newValue.length - 1].name = newValue[newValue.length - 1].inputValue;
delete newValue[newValue.length - 1]["inputValue"];
}
}
}
// Hinzufügen des ersten Elements
else{
if (!list.some(item => item.name === newValue.name)) {
handleAdd(newValue.inputValue);
newValue.name = newValue.inputValue;
delete newValue["inputValue"];
}
}
/*
try {
const newArticle = { ...article, [fieldName]: newValue};
const sendArticle = await axios.put(baseURL+"/article/"+article.pk, {
article: newArticle
});
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
*/
onChange(newValue);
/*
}
catch(error){
setSnackbar({ children: error.message, severity: 'error' });
if (Array.isArray(newValue)) {
newValue = newValue.slice(0, -1);
}
else {
newValue = null;
}
}
*/
}
useEffect(()=>{
axios.get(baseURL+apiURL)
.then((res) => {
const data = res.data;
setList(data);
console.log(data)
})
},[])
return (
<div>
<Autocomplete
multiple={multiple}
id="tags-standard"
options={list}
sx={sx}
getOptionLabel={(option) => {
if (option.name === undefined){
return ""
}
else{
return option.name
}
}}
isOptionEqualToValue={(option, value) => option.name === value.name}
inputValue={inputValue}
onInputChange={(_, newInputValue) => {
setInputValue(newInputValue)
}}
//defaultValue={[]}
value={value}
renderInput={(params) => (
<TextField
{...params}
variant="standard"
label={titleName}
placeholder={addName}
/>
)}
onChange={handleOnChange}
filterOptions={(options, params) => {
const filtered = filter(options, params);
if (params.inputValue !== '' && addNew) {
filtered.push({
inputValue: params.inputValue,
name: `Add "${params.inputValue}"`,
});
}
return filtered;
}}
renderOption={(props, option) => <li {...props}>{option.name}</li>}
/>
{!!snackbar && (
<Snackbar
open
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
onClose={handleCloseSnackbar}
autoHideDuration={6000}
>
<Alert {...snackbar} onClose={handleCloseSnackbar} />
</Snackbar>
)}
</div>
)
}
export default ListFieldApiwoArticle

View File

@@ -0,0 +1,271 @@
import {useState, useEffect} from 'react'
import React from 'react'
import { Grid, TextField } from '@mui/material'
import Button from '@mui/material/Button'
import Snackbar from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
import Checkbox from '@mui/material/Checkbox';
import FormControlLabel from '@mui/material/FormControlLabel';
import axios from 'axios';
const baseURL = "http://localhost:8000/api"
const MarketDetail = (props) => {
const [openExchangeDialog, setOpenExchangeDialog] = useState(false);
const [exchangeMarket, setExchangeMarket] = useState({oldMarket: {}, newMarket: {name: '', street: '', street_number: 0, zip_code: '', city: '', phone:''}, deleteOld: false});
const [markets, setMarkets] = useState([]);
const [snackbar, setSnackbar] = useState(null);
const [market, setMarket] = useState({
name: '',
street: '',
street_number: 0,
zip_code: '',
city: '',
phone: ''
});
useEffect(()=>{
axios.get(baseURL+"/market/"+props.marketId)
.then((res) => {
const data = res.data;
//console.log(data)
setMarket(data);
})
axios.get(baseURL+"/markets/")
.then((res) => {
console.log(res.data)
setMarkets(res.data);
})
},[])
const handleExchange = () => {
setExchangeMarket({ ...exchangeMarket, oldMarket: market });
setOpenExchangeDialog(true);
}
const handleExchangeClose = () => {
setOpenExchangeDialog(false);
}
const handleMarketExchange = async () => {
console.log(exchangeMarket)
try {
const receivedData = await axios.post(baseURL+"/exchangePurchaseMarket/"+props.purchaseId, {
data: exchangeMarket,
})
//const newData = tableData.map((row) => (row.id === exchangeArticle.oldRow.id ? receivedData.data : row));
setMarket(exchangeMarket.newMarket);
//const receivedData = null
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
setOpenExchangeDialog(false);
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
}
}
const handleCloseSnackbar = () => setSnackbar(null);
const renderExchangeDialog = () => {
return (
<Dialog open={openExchangeDialog} onClose={handleExchangeClose}>
<DialogTitle>Austausch eines Marketes gegen einen vorhandenen Markt</DialogTitle>
<DialogContent>
<Grid container spacing={1}>
<Grid item xs={5} alignItems="stretch" style={{ display: "flex" }}>
Markt austauschen gegen:
</Grid>
<Grid item xs={7} alignItems="stretch" style={{ display: "flex" }}>
<Select
fullWidth
id="exchangeArticle"
value={exchangeMarket.newMarket.name}
label="Market"
onChange={(e,t) => {setExchangeMarket({...exchangeMarket, newMarket: markets.filter((element) => element.id === Number(t.key.replace('.$','')))[0] });}}
>
{markets.map((market) => (
<MenuItem
key={market.id}
value={market.name}
//style={getStyles(name, personName, theme)}
>
{market.name+', '+market.street+' '+market.street_number+', '+market.city}
</MenuItem>
))}
</Select>
</Grid>
<FormControlLabel control={
<Checkbox
label='delete?'
checked={exchangeMarket.deleteOld}
onChange={(e) => {setExchangeMarket({ ...exchangeMarket, deleteOld: !exchangeMarket.deleteOld });}}
inputProps={{ 'aria-label': 'controlled' }}
/>
} label="Ursprünglichen Artikel löschen?"
/>
</Grid>
</DialogContent>
<DialogActions>
<Button onClick={handleExchangeClose}>Abbrechen</Button>
<Button onClick={handleMarketExchange}>Änderung speichern</Button>
</DialogActions>
</Dialog>
)
}
return(
<Grid container spacing={1}>
{renderExchangeDialog()}
<Grid item xs={12}>
<TextField
autoFocus
fullWidth
margin="dense"
id="name"
value={market.name}
/*
onChange={(event) =>
setMarket({
...market,
name: event.target.value,
})
}
*/
label="Name"
type="text"
variant="standard"
/>
</Grid>
<Grid item xs={9}>
<TextField
margin="dense"
fullWidth
id="street"
value={market.street}
/*
onChange={(event) =>
setMarket({
...market,
street: event.target.value,
})
}
*/
label="Straße"
type="text"
variant="standard"
/>
</Grid>
<Grid item xs={2}>
<TextField
margin="dense"
id="streetnum"
fullWidth
value={market.street_number}
/*
onChange={(event) =>
setMarket({
...market,
streetNum: event.target.value,
})
}
*/
label="Nr."
type="number"
variant="standard"
/>
</Grid>
<Grid item xs={4}>
<TextField
margin="dense"
id="zip"
fullWidth
value={market.zip_code}
/*
onChange={(event) =>
setMarket({
...market,
zip: event.target.value,
})
}
*/
label="PLZ"
type="number"
variant="standard"
/>
</Grid>
<Grid item xs={8}>
<TextField
margin="dense"
id="city"
fullWidth
value={market.city}
/*
onChange={(event) =>
setMarket({
...market,
city: event.target.value,
})
}
*/
label="Stadt"
type="text"
variant="standard"
/>
</Grid>
<Grid item xs={12}>
<TextField
margin="dense"
id="phone"
fullWidth
value={market.phone}
/*
onChange={(event) =>
setMarket({
...market,
tel: event.target.value,
})
}
*/
label="Telefon"
type="text"
variant="standard"
/>
</Grid>
<Grid item xs={3}>
<Button onClick={handleExchange}>Markt austauschen</Button>
</Grid>
{!!snackbar && (
<Snackbar
open
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
onClose={handleCloseSnackbar}
autoHideDuration={6000}
>
<Alert {...snackbar} onClose={handleCloseSnackbar} />
</Snackbar>
)}
</Grid>
)
}
export default MarketDetail

View File

@@ -0,0 +1,90 @@
import { AppBar, Toolbar, styled, Typography, IconButton, alpha, InputBase } from '@mui/material'
import MenuIcon from '@mui/icons-material/Menu';
import SearchIcon from '@mui/icons-material/Search';
import React from 'react'
const StyledToolbar = styled(Toolbar)({
display:"flex",
justifyContent:"space-between",
})
const Search = styled('div')(({ theme }) => ({
position: 'relative',
borderRadius: theme.shape.borderRadius,
backgroundColor: alpha(theme.palette.common.white, 0.15),
'&:hover': {
backgroundColor: alpha(theme.palette.common.white, 0.25),
},
marginLeft: 0,
width: '100%',
[theme.breakpoints.up('sm')]: {
marginLeft: theme.spacing(1),
width: 'auto',
},
}));
const SearchIconWrapper = styled('div')(({ theme }) => ({
padding: theme.spacing(0, 2),
height: '100%',
position: 'absolute',
pointerEvents: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}));
const StyledInputBase = styled(InputBase)(({ theme }) => ({
color: 'inherit',
'& .MuiInputBase-input': {
padding: theme.spacing(1, 1, 1, 0),
// vertical padding + font size from searchIcon
paddingLeft: `calc(1em + ${theme.spacing(4)})`,
transition: theme.transitions.create('width'),
width: '100%',
[theme.breakpoints.up('sm')]: {
width: '12ch',
'&:focus': {
width: '20ch',
},
},
},
}));
const Navbar = () => {
return (
<AppBar
position="sticky"
>
<StyledToolbar>
<IconButton
size="large"
edge="start"
color="inherit"
aria-label="open drawer"
sx={{ mr: 2 }}
>
<MenuIcon />
</IconButton>
<Typography
variant="h6"
sx={{ display: {xs: "none", sm: "block"} }}
>
Kassenzettel
</Typography>
<Search>
<SearchIconWrapper>
<SearchIcon />
</SearchIconWrapper>
<StyledInputBase
placeholder="Search…"
inputProps={{ 'aria-label': 'search' }}
/>
</Search>
</StyledToolbar>
</AppBar>
)
}
export default Navbar

View File

@@ -0,0 +1,231 @@
import {useState, useEffect} from 'react'
import React from 'react'
import axios from 'axios';
import PropTypes from 'prop-types';
//import { useNavigate, useLocation } from 'react-router-dom';
import { Button } from '@mui/material'
import AddIcon from '@mui/icons-material/Add';
import DeleteIcon from '@mui/icons-material/DeleteOutlined';
import { DataGrid, GridToolbarContainer, GridActionsCellItem } from '@mui/x-data-grid';
import Snackbar from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
const baseURL = "http://localhost:8000/api"
//const apiURL = "/article/packaging"
/*
- Crashes if deleting an entry
*/
function EditToolbar(props) {
const { setTableData, article } = props;
const handleClick = async () => {
const newValue = {id: Math.floor(Math.random() * 1000)+10, value: 0, isEstimated: false, unit: 'g'}
setTableData((oldRows) => [...oldRows, newValue]);
};
return (
<GridToolbarContainer>
<Button color="primary" startIcon={<AddIcon />} onClick={handleClick}>
Neuer Eintrag
</Button>
</GridToolbarContainer>
);
}
EditToolbar.propTypes = {
setTableData: PropTypes.func.isRequired,
}
const NutriDataGrid = (props) => {
const { article } = props;
const [tableData, setTableData] = useState([{id: -1, type: '', serving: 0, unit: 'g'}]);
const [options, setOptions] = useState([{id: -1, name: ''}]);
const [snackbar, setSnackbar] = useState(null);
const units = ['kg','g','mg','µg','l','ml','St','kJ','kcal'];
const columns = [
{
field: 'isEstimated',
headerName: 'Schätzung?',
width: 100,
editable: true,
type: 'boolean',
},
{
field: 'nutrient',
headerName: 'Nährwert',
width:150,
type: 'singleSelect',
//valueOptions: () => {return options.map(x => x.name)},
valueOptions: options,
getOptionValue: (value) => value.id,
getOptionLabel: (value) => value.name,
editable: true,
flex: 1,
valueGetter: (params) => {try {return params.row.nutrient.id;} catch {return '';} },
},
{
field: 'value',
headerName: '100g/100ml',
editable: true,
type: 'number',
//valueGetter: (params) => { return params.row.data.serving },
},
{
field: 'unit',
headerName: 'Einheit',
type: 'singleSelect',
valueOptions: units,
editable: true,
//valueGetter: (params) => { return params.row.data.unit },
},
/*
{field: 'portion', headerName: 'Portion'},
*/
{
field: 'actions',
type: 'actions',
headerName: 'Aktionen',
width: 100,
cellClassName: 'actions',
getActions: ({ id }) => {
return [
<GridActionsCellItem
icon={<DeleteIcon />}
label="Delete"
onClick={(e) => handleDeleteClick(id,e)}
color="inherit"
/>,
];
},
}
]
const handleCloseSnackbar = () => setSnackbar(null);
//Anscheinend ist es notwendig das event e zu übergeben, ansonsten passieren komische Dinge (kein async, multiple trigger beim rendern etc)
const handleDeleteClick = async (id,e) => {
const newData = tableData.filter((row) => row.id !== id);
try {
const receivedData = await axios.delete(baseURL+"/article/nutritionalValues/"+article.pk, {
data: id,
})
setSnackbar({ children: 'Nährwert erfolgreich gelöscht', severity: 'success' });
setTableData(newData);
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
}
};
const processRowUpdate = React.useCallback( async (newRow, oldRow) => {
console.log(newRow)
var nutrient;
if (newRow.nutrient === undefined) {
return newRow;
}
else if (typeof newRow.nutrient === 'number') {
nutrient = options.find(x => x.id === newRow.nutrient);
}
else {
nutrient = newRow.nutrient;
}
const updatedRow = {id: newRow.id, article_id: article.pk, nutrient: nutrient, value: newRow.value, unit: newRow.unit, isEstimated: newRow.isEstimated, isNew: false};
const newData = tableData.map((row) => (row.id === newRow.id ? updatedRow : row));
//console.log(newData)
try {
//const newCompleteData = {article: article.pk, data: newData, id: id};
//console.log(newCompleteData)
const response = await axios.put(baseURL+"/article/nutritionalValues/"+article.pk, {
nutriData: updatedRow
});
//console.log(response);
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
setTableData(newData);
//onChange(newData);
return updatedRow;
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
return oldRow;
}
}
);
//const handleProcessRowUpdateError = React.useCallback((error) => {
// setSnackbar({ children: error.message, severity: 'error' });
//}, []);
useEffect(() =>{
async function fetchOptions() {
const ans = await axios.get(baseURL+"/article/nutrients/");
setOptions(ans.data);
}
async function fetchData() {
const values = await axios.get(baseURL+"/article/nutritionalValues/"+article.pk);
setTableData(values.data);
}
fetchOptions();
fetchData();
},[article])
return (
<div style={{ height: 570, width: '100%' }}>
<DataGrid
rows={tableData}
columns={columns}
pageSize={10}
editMode="cell"
//getRowId={(row) => row.internalId}
processRowUpdate={processRowUpdate}
//onProcessRowUpdateError={handleProcessRowUpdateError}
//isCellEditable={(params) => {console.log(params);return !permanentOptions.includes(params.field) }}
experimentalFeatures={{ newEditingApi: true }}
components={{
Toolbar: EditToolbar,
}}
componentsProps={{
toolbar: { setTableData, article },
}}
initialState={{
sorting: {
sortModel: [{ field: 'id', sort: 'desc' }],
},
}}
/>
{!!snackbar && (
<Snackbar
open
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
onClose={handleCloseSnackbar}
autoHideDuration={6000}
>
<Alert {...snackbar} onClose={handleCloseSnackbar} />
</Snackbar>
)}
</div>
);
}
export default NutriDataGrid

View File

@@ -0,0 +1,240 @@
import {useState, useEffect} from 'react'
import React from 'react'
import axios from 'axios';
import PropTypes from 'prop-types';
//import { useNavigate, useLocation } from 'react-router-dom';
import { Button, Autocomplete, TextField } from '@mui/material'
import AddIcon from '@mui/icons-material/Add';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/DeleteOutlined';
import SaveIcon from '@mui/icons-material/Save';
import CancelIcon from '@mui/icons-material/Close';
import { DataGrid, GridRowModes, GridToolbarContainer, GridActionsCellItem } from '@mui/x-data-grid';
import ListFieldApi from "./ListFieldApi.component"
const baseURL = "http://localhost:8000/api"
const apiURL = "/article/packaging"
const makeId = () => {
let ID = "";
let characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
for ( var i = 0; i < 12; i++ ) {
ID += characters.charAt(Math.floor(Math.random() * 36));
}
return ID;
}
const EditToolbar = (props) => {
const { onChange, setRowModesModel, setList } = props;
const [inputValue, setInputValue] = React.useState('')
const handleClick = () => {
const id = makeId();
onChange([{ id, packaging: '', weight: '', isNew: true }]);
setRowModesModel((oldModel) => ({
...oldModel,
[id]: { mode: GridRowModes.Edit, fieldToFocus: 'packaging' },
}));
};
const handelMaterialChange = (event) => {
if (event == null){
setInputValue('')
}
else{
setInputValue(event.name);
}
//axios.get(baseURL+apiURL)
// .then((res) => {
// const data = res.data;
// setList(data);
// //console.log(data)
// })
}
return (
<GridToolbarContainer>
<Button color="primary" startIcon={<AddIcon />} onClick={handleClick}>
Eintrag hinzufügen
</Button>
<ListFieldApi
apiURL={apiURL}
titleName="Material hinzufügen"
addName="Material"
multiple={false}
value={inputValue}
onChange={handelMaterialChange}
sx={{ width: 300 }}
/>
</GridToolbarContainer>
);
}
EditToolbar.propTypes = {
setRowModesModel: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
};
const initialRows = [
{
id: makeId(),
packaging: 'Pappe',
weight: 0,
},
]
const PackagingDataGrid = (props) => {
const { value, onChange, setArticle,...rest } = props;
const [rows, setRows] = React.useState([]);
const [rowModesModel, setRowModesModel] = React.useState({});
const [list, setList] = useState([]);
const [inputValue, setInputValue] = React.useState('')
const columnsPackaging = [
{
field: 'packaging',
headerName: 'Material',
width: 350,
type: 'singleSelect',
valueOptions: () => {return list.map(x => x.name)},
editable: true,
},
{
field: 'weight',
headerName: 'Gewicht in g',
type: 'number',
width:100,
editable: true,
},
{
field: 'actions',
type: 'actions',
headerName: 'Actions',
width: 150,
cellClassName: 'actions',
getActions: ({ id }) => {
const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit;
if (isInEditMode) {
return [
<GridActionsCellItem
icon={<SaveIcon />}
label="Save"
onClick={handleSaveClick(id)}
/>,
<GridActionsCellItem
icon={<CancelIcon />}
label="Cancel"
className="textPrimary"
onClick={handleCancelClick(id)}
color="inherit"
/>,
];
}
return [
<GridActionsCellItem
icon={<EditIcon />}
label="Edit"
className="textPrimary"
onClick={handleEditClick(id,rows)}
color="inherit"
/>,
<GridActionsCellItem
icon={<DeleteIcon />}
label="Delete"
onClick={handleDeleteClick(id)}
color="inherit"
/>,
];
},
},
]
//const handleRowEditStart = (params, event) => {
// event.defaultMuiPrevented = true;
//};
const handleRowEditStop = (params, event) => {
event.defaultMuiPrevented = true;
};
const handleEditClick = (id, rows) => () => {
console.log(rowModesModel)
setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } });
};
const handleSaveClick = (id) => () => {
setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } });
};
const handleDeleteClick = (id) => () => {
setRows(rows.filter((row) => row.id !== id));
};
const handleCancelClick = (id) => () => {
setRowModesModel({
...rowModesModel,
[id]: { mode: GridRowModes.View, ignoreModifications: true },
});
const editedRow = rows.find((row) => row.id === id);
if (editedRow.isNew) {
setRows(rows.filter((row) => row.id !== id));
}
};
const processRowUpdate = (newRow) => {
const updatedRow = { ...newRow, isNew: false };
console.log(newRow)
setRows(rows.map((row) => (row.id === newRow.id ? updatedRow : row)));
return updatedRow;
};
useEffect(()=>{
axios.get(baseURL+apiURL)
.then((res) => {
const data = res.data;
setList(data);
//console.log(data)
})
},[])
return (
<DataGrid
rows={value}
columns={columnsPackaging}
pageSize={10}
editMode="row"
rowModesModel={rowModesModel}
//onRowEditStart={handleRowEditStart}
onRowEditStop={handleRowEditStop}
processRowUpdate={processRowUpdate}
components={{
Toolbar: EditToolbar,
}}
componentsProps={{
toolbar: { onChange, setRowModesModel, setList },
}}
experimentalFeatures={{ newEditingApi: true }}
//onRowEditStop={handleRowEditStop}
//checkboxSelection
//disableSelectionOnClick
//onCellClick={handleDataGridClick}
//onCellDoubleClick={handleDataGridDoubleClick}
/>
);
}
export default PackagingDataGrid

View File

@@ -0,0 +1,207 @@
import {useState, useEffect} from 'react'
import React from 'react'
import axios from 'axios';
import PropTypes from 'prop-types';
//import { useNavigate, useLocation } from 'react-router-dom';
import { Button } from '@mui/material'
import AddIcon from '@mui/icons-material/Add';
import DeleteIcon from '@mui/icons-material/DeleteOutlined';
import { DataGrid, GridToolbarContainer, GridActionsCellItem } from '@mui/x-data-grid';
import Snackbar from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
const baseURL = "http://localhost:8000/api"
const apiURL = "/article/packaging/"
function EditToolbar(props) {
const { setTableData, articleId } = props;
const handleClick = async () => {
const newValue = { packaging_id: {id: -1, name: 'Bitte auswählen'}, weight: 0, isNew: true, article_id: articleId };
const receivedData = await axios.post(baseURL+"/article/articlePackagings/"+articleId, {
newValue,
})
//console.log(receivedData)
const newRow = { id: receivedData.data.id, packaging_id: {id: -1, name: 'Bitte auswählen'}, weight: receivedData.data.weight, isNew: true, article_id: receivedData.data.article_id };
setTableData((oldRows) => [...oldRows, newRow]);
};
return (
<GridToolbarContainer>
<Button color="primary" startIcon={<AddIcon />} onClick={handleClick}>
Neuer Eintrag
</Button>
</GridToolbarContainer>
);
}
EditToolbar.propTypes = {
setTableData: PropTypes.func.isRequired,
}
const PackagingDataGrid2 = (props) => {
const { articleId } = props;
const [list, setList] = useState([]);
const [tableData, setTableData] = useState([{id: -1, packaging_id: {id: -1, name: ''}, weight: -1, article_id: -1}]);
const [snackbar, setSnackbar] = useState(null);
const columnsPackaging = [
{
field: 'packaging_id',
headerName: 'Material',
flex: 1,
type: 'singleSelect',
valueOptions: () => {return list.map(x => x.name).sort()},
editable: true,
valueGetter: (params) => {try {return params.row.packaging_id.name;} catch {return '';} },
},
{
field: 'weight',
headerName: 'Gewicht in g',
type: 'number',
width:100,
editable: true,
},
{
field: 'actions',
type: 'actions',
headerName: 'Aktionen',
width: 100,
cellClassName: 'actions',
getActions: ({ id }) => {
return [
<GridActionsCellItem
icon={<DeleteIcon />}
label="Delete"
onClick={(e) => handleDeleteClick(id,e)}
color="inherit"
/>,
];
},
}
]
const findId = (idToLookFor) => {
for (var i = 0; i < list.length; i++) {
if (list[i].name === idToLookFor) {
return(list[i].id);
}
}
}
const handleCloseSnackbar = () => setSnackbar(null);
//Anscheinend ist es notwendig das event e zu übergeben, ansonsten passieren komische Dinge (kein async, multiple trigger beim rendern etc)
const handleDeleteClick = async (id,e) => {
const newData = tableData.filter((row) => row.id !== id);
//console.log('Delete clicked')
//console.log(id)
//console.log(e)
try {
const receivedData = await axios.delete(baseURL+"/article/articlePackagings/"+articleId, {
data: id,
})
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
setTableData(newData);
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
}
};
const processRowUpdate = React.useCallback( async (newRow, oldRow) => {
const id = findId(newRow.packaging_id);
const updatedRow = {'id': newRow.id, 'packaging_id':{'id': id, 'name': newRow.packaging_id}, 'weight': newRow.weight, 'isNew': false, 'article_id': articleId};
const newData = tableData.map((row) => (row.id === newRow.id ? updatedRow : row));
try {
const response = await axios.put(baseURL+"/article/articlePackagings/"+articleId, {
updatedRow
});
//console.log(response);
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
setTableData(newData);
//onChange(newData);
return updatedRow;
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
return oldRow;
}
}
);
//const handleProcessRowUpdateError = React.useCallback((error) => {
// setSnackbar({ children: error.message, severity: 'error' });
//}, []);
useEffect(()=>{
axios.get(baseURL+apiURL)
.then((res) => {
const data = res.data;
setList(data);
})
axios.get(baseURL+"/article/articlePackagings/"+articleId)
.then((res) => {
const data = res.data;
//Add id for each element for displaying in Data Grid
//data.forEach((item, i) => {
// item.id = i + 1;
//});
setTableData(data);
//console.log("Fetching packaging data")
//console.log(data)
})
},[])
return (
<div style={{ height: 350, width: '100%' }}>
<DataGrid
rows={tableData}
columns={columnsPackaging}
pageSize={10}
editMode="row"
//getRowId={(row) => row.internalId}
processRowUpdate={processRowUpdate}
//onProcessRowUpdateError={handleProcessRowUpdateError}
experimentalFeatures={{ newEditingApi: true }}
components={{
Toolbar: EditToolbar,
}}
componentsProps={{
toolbar: { setTableData, articleId },
}}
/>
{!!snackbar && (
<Snackbar
open
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
onClose={handleCloseSnackbar}
autoHideDuration={6000}
>
<Alert {...snackbar} onClose={handleCloseSnackbar} />
</Snackbar>
)}
</div>
);
}
export default PackagingDataGrid2

View File

@@ -0,0 +1,999 @@
import {useState, useEffect} from 'react'
import React from 'react'
import axios from 'axios';
import dayjs from 'dayjs';
import { useNavigate, useLocation } from 'react-router-dom';
import { DataGrid, GridToolbarContainer, GridActionsCellItem, GridRowModes } from '@mui/x-data-grid';
import PropTypes from 'prop-types';
import Grid from '@mui/material/Grid'
import Button from '@mui/material/Button'
import TextField from '@mui/material/TextField';
import Snackbar from '@mui/material/Snackbar';
import Alert from '@mui/material/Alert';
import Box from '@mui/material/Box';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import Select from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
import Checkbox from '@mui/material/Checkbox';
import InputAdornment from '@mui/material/InputAdornment';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormControl from '@mui/material/FormControl';
import InputLabel from '@mui/material/InputLabel';
import {Autocomplete, createFilterOptions} from '@mui/material'
import { grey } from '@mui/material/colors';
import AddIcon from '@mui/icons-material/Add';
import DeleteIcon from '@mui/icons-material/DeleteOutlined';
import EditIcon from '@mui/icons-material/Edit';
import SaveIcon from '@mui/icons-material/Save';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import MarketDetail from "./MarketDetail.component";
import ReceipeCanvas from "./Canvas.component"
import ListFieldApiwoArticle from "./ListFieldApiwoArticle.component"
const baseURL = "http://localhost:8000/api"
const filter = createFilterOptions();
function EditToolbar(props) {
const { setArticles, setOpenNewEntryDialog } = props;
const handleClick = async () => {
console.log("Neuer Eintrag")
setOpenNewEntryDialog(true);
const retArticles = await axios.get(baseURL+"/articles/", {
});
// Here we just add a "Neuer Artikel" object to the list of all available articles. If this is selected later on, we ask the backend for a real new object.
const answerWithNewArticelOption = retArticles.data.concat({pk:-1, name: 'Neuer Artikel', brand: null});
setArticles(answerWithNewArticelOption);
};
return (
<GridToolbarContainer>
<Button color="primary" startIcon={<AddIcon />} onClick={handleClick}>
Neuer Eintrag
</Button>
</GridToolbarContainer>
);
}
EditToolbar.propTypes = {
setArticles: PropTypes.func.isRequired,
}
const PurchaseDetails = (props) => {
const location = useLocation();
//location.state != null ? console.log(location.state.id) : console.log("No data to show");
const navigate = useNavigate();
const noButtonRef = React.useRef(null);
const drawRef = React.useRef({});
const [img, setImg] = useState(new Image());
const [tableData, setTableData] = useState([{id: 1, quantity: 0, article_id: {name: '', brand: {name: ''}}, price: 0, MwSt: 7, inSale: false, map: '', purchase_id: -1}]);
const [drawData, setDrawData] = useState([]);
const [snackbar, setSnackbar] = useState(null);
const [rowModesModel, setRowModesModel] = useState({});
const [purchaseArticleRowId, setPurchaseArticleRowId] = useState(null);
const [doubleClickAllowed, setDoubleClickAllowed] = useState(true);
const [promiseArguments, setPromiseArguments] = React.useState(null);
const [openMapDialog, setOpenMapDialog] = useState(false);
const [openExchangeDialog, setOpenExchangeDialog] = useState(false);
const [openNewEntryDialog, setOpenNewEntryDialog] = useState(false);
const [mapString, setMapString] = useState({receipeString: {id: -1, name: ""}, location_x: 0, location_y: 0, location_h: 0, location_w: 0, pk: -1, rowId: -1, purchase_id: -1, receipeString: ""});
const [rowMode, setRowMod] = useState('view');
const [stringData, setStringData] = useState([]);
const [paymentType, setPaymentType] = useState(location.state.payment_type);
const [totalPrice, setTotalPrice] = useState(location.state.total_price);
const [purchaseDate, setpurchaseDate] = useState(location.state.purchase_date);
const [newArticle,setNewArticle] = useState({id: 1, quantity: 0, article_id: {name: '', brand: {name: ''}}, price: 0, MwSt: 7, inSale: false, map: '', purchase_id: -1});
const [articles, setArticles] = useState([]);
const [exchangeArticle, setExchangeArticle] = useState({rowData: {}, newArticle: {}, deleteOld: false});
//const [purchaseArticleRowId, setPurchaseArticleRowId, clearPurchaseArticleRowId] = useStickyState()
const paymentTypes = ['','EC','Bar','Kreditkarte']
const columns = [
{
field: 'quantity',
headerName: '#',
width: 60,
type: 'number',
editable: true,
},
{
field: 'name',
headerName: 'Artikel',
valueGetter: (params) => { const brandName = params.row.article_id.brand === null ? '' : params.row.article_id.brand.name; const articleName = params.row.article_id === null ? '' : params.row.article_id.name;
return brandName + ' ' + articleName },
width: 190,
type: 'string',
},
{
field: 'price',
headerName: 'Preis',
width: 80,
type: 'number',
editable: true,
valueFormatter: (params) => {
if (params.value == null) {
return '';
}
//const valueFormatted = Number(params.value * 100).toLocaleString();
return `${params.value}`;
},
},
{
field: 'MwSt',
headerName: 'MwSt',
valueGetter: (params) => { return params.row.article_id.MwSt },
valueFormatter: (params) => {
if (params.value == null) {
return '';
}
//const valueFormatted = Number(params.value * 100).toLocaleString();
return `${params.value} %`;
},
width: 55,
type: 'number',
editable: true,
},
{
field: 'inSale',
headerName: 'Sale',
type: 'boolean',
width: 55,
editable: true,
},
{
field: 'sum_price',
headerName: '∑ Preise',
width: 80,
type: 'number',
valueFormatter: (params) => {
if (params.value == null) {
return '';
}
//const valueFormatted = Number(params.value * 100).toLocaleString();
return `${params.value.toFixed(2)}`;
},
},
{
field: 'actions',
type: 'actions',
headerName: 'Aktionen',
width: 100,
cellClassName: 'actions',
getActions: ({ id }) => {
return [
<GridActionsCellItem
icon={(rowMode === 'edit' && purchaseArticleRowId === id) ? <SaveIcon /> : <EditIcon />}
label="Edit"
onClick={(e) => handleEditSaveClick(e,id)}
color="inherit"
/>,
<GridActionsCellItem
icon={<DeleteIcon />}
label="Delete"
onClick={(e) => handleDeleteClick(id,e)}
color="inherit"
/>,
<GridActionsCellItem
icon={<EditIcon />}
label="Kassenzettel-String bearbeiten"
onClick={(e) => handleEditMapClick(id,e)}
showInMenu
/>,
<GridActionsCellItem
icon={<EditIcon />}
label="Artikel austauschen"
onClick={(e) => handleExchangeClick(id,e)}
showInMenu
/>,
<GridActionsCellItem
icon={<EditIcon />}
label="Bereich übernehmen"
onClick={(e) => handleSaveLocation(id,e)}
showInMenu
/>,
];
},
}
]
const handleCloseSnackbar = () => setSnackbar(null);
const handleDataGridClick = (e) => {
if (purchaseArticleRowId !== e.row.id) {
setRowModesModel({ ...rowModesModel, [purchaseArticleRowId]: { mode: GridRowModes.View } });
setDoubleClickAllowed(true);
setRowMod('view')
}
setPurchaseArticleRowId( e.row.id );
//setDrawData(e.row.map)
drawRef.current.draw(e.row.map)
}
const handleDataGridDoubleClick = (e) => {
console.log("PurchaseDetail->DoubleClick")
console.log(e.row)
if (doubleClickAllowed) {
navigate('/ArticleDetails', {state:e.row});
}
}
const handleEditSaveClick = (e, id) => {
if (!purchaseArticleRowId) {
return;
}
if (rowMode === 'edit') {
setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.View } });
setDoubleClickAllowed(true);
setRowMod('view')
} else {
setRowModesModel({ ...rowModesModel, [id]: { mode: GridRowModes.Edit } });
setDoubleClickAllowed(false);
setRowMod('edit')
}
//console.log("Edit")
};
const processRowUpdate = React.useCallback( async (newRow, oldRow) => {
const newArticle = { ...newRow.article_id, MwSt: newRow.MwSt };
const updatedRow = {id: newRow.id, MSt: newRow.MwSt, article_id: newArticle, inSale: newRow.inSale,
map: newRow.map, net_weight: newRow.net_weight, price: newRow.price,
quantity: newRow.quantity, sum_price: newRow.quantity*newRow.price
};
const newData = tableData.map((row) => (row.id === updatedRow.id ? updatedRow : row));
try {
const response = await axios.put(baseURL+"/purchase/article/"+newRow.id, {
newRow: updatedRow
});
//console.log(response);
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
setTableData(newData);
updateTotalPrice(newData);
//onChange(newData);
return updatedRow;
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
return oldRow;
}
});
const handleDeleteClick = async (id,e) => {
console.log("Delete: "+id)
/*
try {
const receivedData = await axios.delete(baseURL+"/purchase/article/"+id, {
data: id,
})
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
setTableData(newData);
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
}*/
new Promise((resolve, reject) => {
setPromiseArguments({ resolve, reject, id });
resolve(); // Nothing was changed
})
};
const handleEditMapClick = (id, e) => {
const currentRow = tableData.filter((row) => row.id === id)[0];
setMapString({...currentRow.map,rowId: id});
setOpenMapDialog(true);
}
const handleSaveLocation = async (id, e) => {
var currentRow = tableData.filter((row) => row.id === id)[0];
const locationData = {pk: currentRow.map.pk,
receipeString: currentRow.map.receipeString,
location_x: drawData.location_x,
location_y: drawData.location_y,
location_h: drawData.location_h,
location_w: drawData.location_w
};
try {
const receivedData = await axios.put(baseURL+"/article/maps/"+locationData.pk, {
data: locationData,
})
currentRow.map = locationData;
const newData = tableData.map((row) => (row.id === currentRow.id ? currentRow : row));
setTableData(newData);
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
}
}
const handleMapClose = () => {
setOpenMapDialog(false);
}
const handleExchangeClick = (id, e) => {
const currentRow = tableData.filter((row) => row.id === id)[0];
console.log(currentRow);
setExchangeArticle({ ...exchangeArticle, oldRow: currentRow });
setOpenExchangeDialog(true);
}
const handleExchangeClose = () => {
setOpenExchangeDialog(false);
}
const handleNewEntryClose = async () => {
setOpenNewEntryDialog(false);
}
const handleMapEdit = async () => {
try {
//const newMap = { ...mapString, receipeString: mapString };
console.log(mapString)
const receivedData = await axios.put(baseURL+"/article/maps/"+mapString.pk, {
data: mapString,
})
//const receivedData = null
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
var newRow = tableData.filter((row) => row.id === mapString.rowId)[0];
newRow.map = mapString;
const newData = tableData.map((row) => (row.id === mapString.rowId ? newRow : row));
console.log(newData)
setTableData(newData);
setOpenMapDialog(false);
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
}
}
const handleArticleExchange = async () => {
console.log(exchangeArticle)
try {
//const newMap = { ...mapString, receipeString: mapString };
console.log(mapString)
const receivedData = await axios.post(baseURL+"/exchangePurchaseArticles/", {
data: exchangeArticle,
})
const newData = tableData.map((row) => (row.id === exchangeArticle.oldRow.id ? receivedData.data : row));
setTableData(newData);
//const receivedData = null
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
setOpenExchangeDialog(false);
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
}
}
const handleNewEntryAdd = async () => {
console.log(newArticle);
if (newArticle.article_id.name === "Neuer Artikel") {
try {
const receivedData = await axios.post(baseURL+"/newPurchaseArticles/"+location.state.id+"/", {
data: newArticle,
})
console.log("Received new article:")
console.log(receivedData.data)
const newTableData = tableData.concat(receivedData.data);
console.log(newTableData)
setTableData(newTableData);
updateTotalPrice(newTableData);
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
setOpenNewEntryDialog(false);
setNewArticle({id: 1, quantity: 0, article_id: {name: '', brand: {name: ''}}, price: 0, MwSt: 7, inSale: false, map: '', purchase_id: -1})
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
}
} else {
try {
const sendData = {...newArticle, article_id: newArticle.article_id}
const receivedData = await axios.post(baseURL+"/purchaseArticles/"+location.state.id+"/", {
data: sendData,
})
//console.log(receivedData.data)
const newTableData = tableData.concat(receivedData.data);
console.log(receivedData.data)
setTableData(newTableData);
updateTotalPrice(newTableData);
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
setOpenNewEntryDialog(false);
setNewArticle({id: 1, quantity: 0, article_id: {name: '', brand: {name: ''}}, price: 0, MwSt: 7, inSale: false, map: '', purchase_id: -1})
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
}
}
}
const handleNewArticleChange = (value) => {
const selectedArticle = articles.filter((article) => article.pk === value.id )[0]
setNewArticle({ ...newArticle, article_id: selectedArticle })
}
const handleSavePrice = async () => {
console.log(location.state)
const newPurchaseData = { ...location.state, total_price: totalPrice, payment_type: paymentType, purchase_date: purchaseDate, edit_finished: true, receipeImage: location.state.receipeImage.pk};
try {
const receivedData = await axios.put(baseURL+"/purchase/"+newPurchaseData.id, {
data: newPurchaseData,
})
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
setPromiseArguments(null);
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
setPromiseArguments(null);
}
};
const handleSaveDate = async (val) => {
console.log(val.format("YYYY-MM-DD"))
const newPurchaseData = { ...location.state, purchase_date: val.format("YYYY-MM-DD"), receipeImage: location.state.receipeImage.pk};
try {
const receivedData = await axios.put(baseURL+"/purchase/"+newPurchaseData.id, {
data: newPurchaseData,
})
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
setPromiseArguments(null);
setpurchaseDate(val.format("YYYY-MM-DD"));
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
setPromiseArguments(null);
}
};
const handleNo = () => {
const { resolve } = promiseArguments;
resolve(); // Resolve with the old row to not update the internal state
setPromiseArguments(null);
};
// If in delete dialog box yes is choosen, we will delete the article in the backend
const handleYes = async () => {
const { reject, resolve, id } = promiseArguments;
try {
const receivedData = await axios.delete(baseURL+"/purchase/article/"+id, {
data: id,
})
const newData = tableData.filter((row) => row.id !== id);
setTableData(newData);
updateTotalPrice(newData);
setSnackbar({ children: 'Änderungen erfolgreich gespeichert', severity: 'success' });
resolve(receivedData);
setPromiseArguments(null);
} catch (error) {
setSnackbar({ children: error.message, severity: 'error' });
reject();
setPromiseArguments(null);
}
};
const handleEntered = () => {
// The `autoFocus` is not used because, if used, the same Enter that saves
// the cell triggers "No". Instead, we manually focus the "No" button once
// the dialog is fully open.
// noButtonRef.current?.focus();
};
const renderExchangeDialog = () => {
return (
<Dialog open={openExchangeDialog} onClose={handleExchangeClose}>
<DialogTitle>Austausch eines Artikels gegen einen vorhandenen Artikel</DialogTitle>
<DialogContent>
<Grid container spacing={1}>
<Grid item xs={5} alignItems="stretch" style={{ display: "flex" }}>
Artikel austauschen gegen:
</Grid>
<Grid item xs={7} alignItems="stretch" style={{ display: "flex" }}>
{/*
<Select
fullWidth
id="exchangeArticle"
value={exchangeArticle.newArticle.name}
label="Artikel"
onChange={(e,t) => {setExchangeArticle({ ...exchangeArticle, newArticle: articles.filter((element) => element.pk === Number(t.key.replace('.$','')))[0] });}}
>
{articles.map((article) => (
<MenuItem
key={article.pk}
value={article.name}
//style={getStyles(name, personName, theme)}
>
{(article.brand !== null ? article.brand.name : '')+' '+article.name}
</MenuItem>
))}
</Select> */}
<Autocomplete
multiple={false}
id="exchangeArticle"
fullWidth
options={articles.map((article) => (
{name: article.name, brand: article.brand !== null ? article.brand.name : '', id: article.pk}
)).sort((a, b) => {
const nameA = a.brand + ' ' +a.name.toUpperCase(); // ignore upper and lowercase
const nameB = b.brand + ' ' +b.name.toUpperCase(); // ignore upper and lowercase
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
// names must be equal
return 0;
})
}
groupBy={(option) => option.brand}
renderOption={(props, option) => (
<Box component="li" {...props} key={option.id}>
{option.brand + ' ' +option.name}
</Box>
)}
getOptionLabel={(option) => {
if (option.name === undefined){
return ""
}
else{
return option.brand + ' ' +option.name
}
}}
renderInput={(params) => (
<TextField
{...params}
variant="standard"
label='Marke + Artikel'
/>
)}
//onChange={(event, newValue) => {handleNewArticleChange(newValue)}}
onChange={(e,t) => {setExchangeArticle({ ...exchangeArticle, newArticle: articles.filter((element) => element.pk === t.id)[0] });}}
/>
</Grid>
<FormControlLabel control={
<Checkbox
label='Sale?'
checked={exchangeArticle.deleteOld}
onChange={(e) => {setExchangeArticle({ ...exchangeArticle, deleteOld: !exchangeArticle.deleteOld });}}
inputProps={{ 'aria-label': 'controlled' }}
/>
} label="Ursprünglichen Artikel löschen?"
/>
</Grid>
</DialogContent>
<DialogActions>
<Button onClick={handleExchangeClose}>Abbrechen</Button>
<Button onClick={handleArticleExchange}>Änderung speichern</Button>
</DialogActions>
</Dialog>
)
}
const renderConfirmDialog = () => {
if (!promiseArguments) {
return null;
}
//const { newRow, oldRow } = promiseArguments;
return (
<Dialog
maxWidth="xs"
TransitionProps={{ onEntered: handleEntered }}
open={!!promiseArguments}
>
<DialogTitle>Sind sie sicher?</DialogTitle>
<DialogContent dividers>
{`Ein klick auf 'Ja' wir die Daten löschen.`}
</DialogContent>
<DialogActions>
<Button ref={noButtonRef} onClick={handleNo}>
Nein
</Button>
<Button onClick={handleYes}>Ja</Button>
</DialogActions>
</Dialog>
);
};
const updateTotalPrice = (val) => {
const sumWithInitial = val.reduce(
(accumulator, currentValue) => accumulator + currentValue.sum_price,
0
);
setTotalPrice(sumWithInitial.toFixed(2));
};
useEffect(()=>{
var myImage = new Image();
myImage.src = baseURL+"/receipeImage/"+location.state.receipeImage.pk;
setImg(myImage)
drawRef.current.drawImage(myImage);
axios.get(baseURL+"/purchaseArticles/"+location.state.id)
.then((res) => {
const data = res.data;
//console.log(tableData)
console.log(data)
setTableData(data);
updateTotalPrice(data);
})
axios.get(baseURL+"/articles/")
.then((res) => {
setArticles(res.data);
})
axios.get(baseURL+"/article/mapStrings/")
.then((res) => {
setStringData(res.data);
})
},[])
return(
<div style={{ height: 800, width: '100%' }}>
<h1> Details zum Einkauf vom {purchaseDate} </h1>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
value={dayjs(purchaseDate)}
onChange={handleSaveDate}
/>
</LocalizationProvider>
<Box sx={{ borderRadius: '4px', p: 2, border: '1px solid', borderColor: grey[300] }}>
<MarketDetail
marketId={location.state.market.id}
purchaseId={location.state.id}
/>
</Box>
<Grid container spacing={1}>
<Grid item xs={8}>
<div style={{ display: 'flex', height: '1200px' }}>
<div style={{ flexGrow: 1 }}>
{renderConfirmDialog()}
{renderExchangeDialog()}
{/*Map String Dialog*/}
<Dialog open={openMapDialog} onClose={handleMapClose}>
<DialogTitle>Bearbeiten des OCR-Artikel Strings</DialogTitle>
<DialogContent>
<Grid container spacing={1}>
{/*
<TextField
fullWidth
margin="dense"
id="mapStringField"
//defaultValue={name != null ? name : ""}
value={mapString.receipeString.receipeString}
onChange={(e) => setMapString({ ...mapString, receipeString: {...mapString.receipeString, receipeString: e.target.value} })}
error={(mapString.length === 0)}
label="OCR Mapping String"
type="text"
variant="standard"
/>
*/}
<ListFieldApiwoArticle
apiURL="/article/mapStrings/"
titleName="String"
addName="String hinzufügen"
multiple={false}
value={mapString.receipeString}
onChange={(e) => {setMapString({ ...mapString, receipeString: e })}}
sx={{width:'400px'}}
addNew={true}
/>
</Grid>
</DialogContent>
<DialogActions>
<Button onClick={handleMapClose}>Abbrechen</Button>
<Button onClick={handleMapEdit}>Änderung speichern</Button>
</DialogActions>
</Dialog>
<Dialog open={openNewEntryDialog} onClose={handleNewEntryClose}>
<DialogTitle>Neuer Eintrag hinzufügen</DialogTitle>
<DialogContent>
<Grid container spacing={1}>
<Grid item xs={1}>
<TextField
fullWidth
margin="dense"
id="newQuantityField"
//defaultValue={name != null ? name : ""}
value={newArticle.quantity}
onChange={(e) => setNewArticle({ ...newArticle, quantity: e.target.value })}
error={(newArticle.quantity === 0)}
label="#"
type="number"
variant="standard"
/>
</Grid>
<Grid item xs={5}>
<Autocomplete
multiple={false}
id="tags-standard"
fullWidth
options={articles.map((article) => (
{name: article.name, brand: article.brand !== null ? article.brand.name : '', id: article.pk}
)).sort((a, b) => {
const nameA = a.brand + ' ' +a.name.toUpperCase(); // ignore upper and lowercase
const nameB = b.brand + ' ' +b.name.toUpperCase(); // ignore upper and lowercase
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
// names must be equal
return 0;
})
}
groupBy={(option) => option.brand}
renderOption={(props, option) => (
<Box component="li" {...props} key={option.id}>
{option.brand + ' ' +option.name}
</Box>
)}
getOptionLabel={(option) => {
if (option.name === undefined){
return ""
}
else{
return option.brand + ' ' +option.name
}
}}
renderInput={(params) => (
<TextField
{...params}
variant="standard"
label='Marke + Artikel'
/>
)}
onChange={(event, newValue) => {handleNewArticleChange(newValue)}}
/>
</Grid>
<Grid item xs={2}>
<TextField
fullWidth
margin="dense"
id="newPriceField"
//defaultValue={name != null ? name : ""}
value={newArticle.price}
onChange={(e) => setNewArticle({ ...newArticle, price: e.target.value })}
error={(newArticle.price === 0)}
label="Preis"
type="number"
variant="standard"
InputProps={{
endAdornment: <InputAdornment position="end"></InputAdornment>
}}
/>
</Grid>
<Grid item xs={2}>
<TextField
width='10px'
margin="dense"
id="newMwSTField"
//defaultValue={name != null ? name : ""}
value={newArticle.MwSt}
onChange={(e) => setNewArticle({ ...newArticle, MwSt: e.target.value })}
label="MwSt"
type="number"
variant="standard"
InputProps={{
endAdornment: <InputAdornment position="end">%</InputAdornment>
}}
/>
</Grid>
<Grid item xs={2}>
<FormControlLabel control={
<Checkbox
label='Sale?'
checked={newArticle.inSale}
onChange={(e) => setNewArticle({ ...newArticle, inSale: !newArticle.inSale })}
inputProps={{ 'aria-label': 'controlled' }}
/>
} label="Sale?" />
</Grid>
</Grid>
</DialogContent>
<DialogActions>
<Button onClick={handleNewEntryClose}>Abbrechen</Button>
<Button onClick={handleNewEntryAdd}>Hinzufügen</Button>
</DialogActions>
</Dialog>
<DataGrid
rows={tableData}
columns={columns}
editMode="row"
//pageSize={15}
onCellClick={handleDataGridClick}
onCellDoubleClick={handleDataGridDoubleClick}
//onCellKeyDown={handleCellKeyDown}
rowModesModel={rowModesModel}
onRowModesModelChange={(model) => setRowModesModel(model)}
processRowUpdate={processRowUpdate}
experimentalFeatures={{ newEditingApi: true }}
components={{
Toolbar: EditToolbar,
}}
componentsProps={{
toolbar: {
setArticles,
setOpenNewEntryDialog
},
}}
/>
<Grid container spacing={1}>
<Grid item xs={4}>
<FormControl sx={{ m: 1, minWidth: 120 }}>
<InputLabel>Zahlungsart</InputLabel>
<Select
id="payment_type"
value={paymentType}
label="Zahlungsart"
onChange={(e) => {setPaymentType(e.target.value)}}
>
{paymentTypes.map((unit) => (
<MenuItem
key={unit}
value={unit}
//style={getStyles(name, personName, theme)}
>
{unit}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
<Grid item xs={6}>
<TextField
disabled
fullWidth
margin="dense"
id="Gesamtpreis"
value={totalPrice}
onChange={(e) => setTotalPrice(e.target.value)}
label="Gesamtpreis"
type="number"
variant="standard"
/*
inputProps: object
Attributes applied to the input element.
InputProps: object
Props applied to the Input element. It will be a FilledInput, OutlinedInput or Input component depending on the variant prop value.
*/
InputProps={{ endAdornment: <InputAdornment position="end"></InputAdornment> }}
inputProps={{ style: { textAlign: 'right' } }}
//onBlur={(e) => handleArticleChangeAPI(e,'EAN', e.target.value)}
/>
</Grid>
<Grid item xs={2} alignItems="stretch" style={{ display: "flex" }}>
<Button onClick={handleSavePrice}>
Preis übernehmen
</Button>
</Grid>
</Grid>
{!!snackbar && (
<Snackbar
open
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
onClose={handleCloseSnackbar}
autoHideDuration={6000}
>
<Alert {...snackbar} onClose={handleCloseSnackbar} />
</Snackbar>
)}
</div>
</div>
</Grid>
<Grid item xs={4}>
<ReceipeCanvas
setNewRect={setDrawData}
image={img}
myRef={drawRef}
/>
</Grid>
</Grid>
</div>
)
}
export default PurchaseDetails

View File

@@ -0,0 +1,162 @@
import { Button } from '@mui/material'
import {Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField } from '@mui/material'
//import {Autocomplete, createFilterOptions } from '@mui/material';
import { DataGrid } from '@mui/x-data-grid';
import { Grid } from '@mui/material'
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
import { MobileDatePicker } from '@mui/x-date-pickers/MobileDatePicker';
import Typography from '@mui/material/Typography';
import {useState, useEffect} from 'react'
import React from 'react'
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
import SelectMarketField from "./selectMarket";
import UploadFiles from "./upload-files.component";
//const baseURL = "https://jsonplaceholder.typicode.com/comments";
const baseURL = "http://localhost:8000/api"
const PurchaseOverview = (props) => {
const columns = [
{
field: 'edit_finished',
headerName: 'Fertig?',
type: 'boolean',
width: 100,
editable: true,
},
{
field: 'purchase_date',
headerName: 'Datum'
},
{
field: 'market',
headerName: 'Markt',
valueGetter: (params) => { return params.row.market.name + ', ' + params.row.market.street + ' ' + params.row.market.street_number + ', ' + params.row.market.city},
width: 400,
},
{
field: 'total_price',
headerName: 'Gesamtpreis',
flex: 1,
valueFormatter: (params) => {
if (params.value == null) {
return '';
}
//const valueFormatted = Number(params.value * 100).toLocaleString();
return `${params.value}`;
},
},
//{field: 'body', headerName: 'Body'}
]
const navigate = useNavigate();
const [tableData, setTableData] = useState([]);
const [openAddDialog, setOpenAddDialog] = useState(false);
const [selectedDate, setSelectedDate] = React.useState(new Date());
const handleDataGridClick = (e) => {
//console.log(e.row)
navigate('/PurchaseDetails', {state:e.row});
}
const handleClickAddDialogOpen = () => {
setOpenAddDialog(true);
};
const handleClose = () => {
setOpenAddDialog(false);
};
useEffect(()=>{
axios.get(baseURL+"/purchase/")
.then((res) => {
const data = res.data;
console.log(data)
setTableData(data);
})
},[])
return (
<div style={{ height: 800, width: '100%' }}>
<Typography variant="h2" gutterBottom>
Übersicht über alle Einkäufe
</Typography>
<Button variant="contained"
onClick={handleClickAddDialogOpen}
>
Einkauf hinzufügen
</Button>
<Dialog open={openAddDialog} onClose={handleClose}>
<DialogTitle>Einkauf hinzufügen</DialogTitle>
<DialogContent>
<DialogContentText>
Füge einen neuen Einkauf hinzu. Entweder per Datum und Markt oder per Upload eines Kassenzettels.
</DialogContentText>
<Grid container spacing={1}>
<Grid item xs={5}>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<MobileDatePicker
label="Datum des Einkaufs"
value={selectedDate}
onChange={(newValue) => {
setSelectedDate(newValue);
}}
renderInput={(params) => <TextField {...params} />}
/>
</LocalizationProvider>
</Grid>
<Grid item xs={6}>
<SelectMarketField/>
</Grid>
<Grid item xs={12}>
<UploadFiles
apiURL='/purchase/post_receipe/'
fileName='receipe'
buttonText='Upload'
applyBinarize={false}
debug={true}
/>
</Grid>
</Grid>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Abbrechen</Button>
<Button onClick={handleClose}>Hinzufügen</Button>
</DialogActions>
</Dialog>
<DataGrid
rows={tableData}
columns={columns}
pageSize={10}
//checkboxSelection
//disableSelectionOnClick
onCellClick={handleDataGridClick}
/>
</div>
)
}
export default PurchaseOverview

View File

@@ -0,0 +1,53 @@
import { Box} from '@mui/material'
import React from 'react'
import { useNavigate } from 'react-router-dom';
import Button from '@mui/material/Button';
import Stack from '@mui/material/Stack';
const Sidebar = () => {
const navigate = useNavigate();
const showArticles = (e) => {
console.log('Show articles');
navigate('/Articles', {});
};
const showPurchases = (e) => {
navigate('/', {});
};
const showAnalyses = (e) => {
console.log('Show analyses');
navigate('/Analyses', {});
};
return (
<Box bgcolor="skyblue"
flex={1}
p={2}
sx={{ display: {xs: "none", sm: "block"} }}
>
<Stack spacing={2}>
<Button variant="text"
onClick={showPurchases}
>
Einkäufe
</Button>
<Button variant="text"
onClick={showArticles}
>
Artikel
</Button>
<Button variant="text"
onClick={showAnalyses}
>
Analysen
</Button>
</Stack>
</Box>
)
}
export default Sidebar

View File

@@ -0,0 +1,58 @@
import * as React from 'react';
import {useState} from 'react'
import axios from 'axios';
import Button from '@mui/material/Button';
//import Typography from '@mui/material/Typography';
import {Card, CardActions, CardContent, CardMedia } from '@mui/material'
//import {Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from '@mui/material'
//import FormGroup from '@mui/material/FormGroup';
//import FormControlLabel from '@mui/material/FormControlLabel';
const baseURL = "http://localhost:8000/api"
const ArticleCard = (props) => {
const { article, navigate, onChange, filterBrand } = props;
const [openAddDialog, setOpenAddDialog] = useState(false);
const [error, setError] = useState(false);
const handleClick = () => {
navigate('/ArticleDetails', {state:{id: -1, article_id: article}});
}
return (
<Card sx={{ height: 350, maxWidth: 345, position: "relative"}}>
<CardMedia
component="img"
height="200"
image={article.frontImage}
title="Bild"
sx={{ padding: "1em 1em 0 1em", objectFit: "contain" }}
/>
<CardContent>
<Button variant="text"
sx={{textTransform: "none"}}
onClick={filterBrand}
>
{article.brand === null ? "Unbekannte Marke:": article.brand.name+":"}
</Button>
{article.name}
</CardContent>
<CardActions sx={{position: "absolute", bottom: 0,}}>
<Button size="small" onClick={handleClick}>
Öffnen
</Button>
</CardActions>
</Card>
);
}
export default ArticleCard

View File

@@ -0,0 +1,110 @@
import * as React from 'react';
import {useState} from 'react'
import axios from 'axios';
import {Button, Stack, Input } from '@mui/material';
import Typography from '@mui/material/Typography';
import {Card, CardActions, CardContent, CardMedia } from '@mui/material'
import {Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from '@mui/material'
const baseURL = "http://localhost:8000/api"
const PictureCard = (props) => {
const { type, article, pictureType, onChange } = props;
const [openAddDialog, setOpenAddDialog] = useState(false);
const [uploadFile, setUploadFile] = useState(null);
const [error, setError] = useState(false);
//const [image, setImage] = useState(eval("article."+pictureType));
const handleClick = () => {
setOpenAddDialog(true);
}
const handleClose = () => {
setOpenAddDialog(false);
};
const handleUploadSelection = (e) => {
//console.log(e.target.files[0])
setUploadFile(e.target.files[0]);
};
const handleUpload = (event) => {
event.preventDefault()
const formData = new FormData();
formData.append("fileTitle", pictureType);
formData.append("uploadedFile", uploadFile);
axios.post(baseURL+'/article/post_articleImage/'+article.pk, formData, {
headers: {
'content-type': 'multipart/form-data'
}
})
.then(function (response) {
setOpenAddDialog(false);
//console.log(response);
//setImage(eval("response.data."+pictureType))
onChange(response.data)
})
.catch(function (error) {
//console.log(error);
setError(true);
});
};
return (
<Card sx={{ maxWidth: 345 }}>
<CardContent>
<Typography gutterBottom variant="h5" component="div">
{type}
</Typography>
</CardContent>
<CardMedia
component="img"
height="450"
image={eval("article."+pictureType)}
title="Bild"
sx={{ padding: "1em 1em 0 1em", objectFit: "contain" }}
/>
<CardActions>
<Button size="small" onClick={handleClick}>
{eval("article."+pictureType) === null ? "Bild hinzufügen" : "Neues Bild"}
</Button>
<Dialog open={openAddDialog} onClose={handleClose}>
<DialogTitle>Bild hinzufügen</DialogTitle>
<DialogContent>
<DialogContentText>
Bild auswählen und uploaden
</DialogContentText>
<Stack container spacing={1}>
<Input
accept="image/*"
id="contained-button-file"
multiple type="file"
onChange={handleUploadSelection}
//error={error.toString()}
/>
<Typography gutterBottom variant="subtitle1" component="div" color="red">
{error ? "Fehler beim uploaden des Files!" : ""}
</Typography>
</Stack>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>
Abbrechen
</Button>
<Button onClick={handleUpload}>
Hinzufügen
</Button>
</DialogActions>
</Dialog>
</CardActions>
</Card>
);
}
export default PictureCard

View File

@@ -0,0 +1,56 @@
/**
* These hooks re-implement the now removed useBlocker and usePrompt hooks in 'react-router-dom'.
* Thanks for the idea @piecyk https://github.com/remix-run/react-router/issues/8139#issuecomment-953816315
* Source: https://github.com/remix-run/react-router/commit/256cad70d3fd4500b1abcfea66f3ee622fb90874#diff-b60f1a2d4276b2a605c05e19816634111de2e8a4186fe9dd7de8e344b65ed4d3L344-L381
*/
import { useContext, useEffect, useCallback } from 'react';
import { UNSAFE_NavigationContext as NavigationContext } from 'react-router-dom';
/**
* Blocks all navigation attempts. This is useful for preventing the page from
* changing until some condition is met, like saving form data.
*
* @param blocker
* @param when
* @see https://reactrouter.com/api/useBlocker
*/
export function useBlocker( blocker, when = true ) {
const { navigator } = useContext( NavigationContext );
useEffect( () => {
if ( ! when ) return;
const unblock = navigator.block( ( tx ) => {
const autoUnblockingTx = {
...tx,
retry() {
// Automatically unblock the transition so it can play all the way
// through before retrying it. TODO: Figure out how to re-enable
// this block if the transition is cancelled for some reason.
unblock();
tx.retry();
},
};
blocker( autoUnblockingTx );
} );
return unblock;
}, [ navigator, blocker, when ] );
}
/**
* Prompts the user with an Alert before they leave the current screen.
*
* @param message
* @param when
*/
export default function usePrompt( message, when = true ) {
const blocker = useCallback(
( tx ) => {
// eslint-disable-next-line no-alert
if ( window.confirm( message ) ) tx.retry();
},
[ message ]
);
useBlocker( blocker, when );
}

View File

@@ -0,0 +1,292 @@
import {Autocomplete, createFilterOptions } from '@mui/material';
import { Button, Grid } from '@mui/material'
import {Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField } from '@mui/material'
import {React, useState, useEffect, Fragment} from 'react'
import axios from 'axios';
const baseURL = "http://localhost:8000/api"
const filter = createFilterOptions();
const SelectMarketField = () => {
const [value, setValue] = useState(null);
const [open, toggleOpen] = useState(false);
const [optionData, setOptionData] = useState([]);
const handleClose = () => {
//setDialogValue({
// name: '',
// street: '',
//});
console.log(dialogValue)
axios.post(baseURL+'/markets/', {
name: dialogValue.name,
street: dialogValue.street,
street_number: dialogValue.streetNum,
zip_code: dialogValue.zip,
city: dialogValue.city,
phone: dialogValue.phone
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
axios.get(baseURL+"/market")
.then((res) => {
const data = res.data;
setOptionData(data);
})
.then(function (res) {
console.log(res);
})
.catch(function (error) {
console.log(error);
});
toggleOpen(false);
};
const [dialogValue, setDialogValue] = useState({
name: '',
street: '',
streetNum: 0,
zip: '',
city: '',
phone: ''
});
const handleSubmit = (event) => {
event.preventDefault();
setValue({
name: dialogValue.name,
street: dialogValue.street,
streetNum: parseInt(dialogValue.streetNum,10),
zip: parseInt(dialogValue.zip,10),
city: dialogValue.city,
phone: dialogValue.phone
});
handleClose();
};
useEffect(()=>{
axios.get(baseURL+"/markets")
.then((res) => {
const data = res.data;
//console.log(data)
setOptionData(data);
})
},[])
return (
<Fragment>
<Autocomplete
value={value}
onChange={(event, newValue) => {
if (typeof newValue === 'string') {
// timeout to avoid instant validation of the dialog's form.
setTimeout(() => {
toggleOpen(true);
console.log('this')
setDialogValue({
name: newValue,
street: '',
streetNum: 0,
zip: '',
city: '',
phone: ''
});
});
}
else if (newValue && newValue.inputValue) {
toggleOpen(true);
setDialogValue({
name: newValue.inputValue,
street: '',
streetNum: 0,
zip: '',
city: '',
phone: ''
});
}
else {
setValue(newValue);
}
}}
filterOptions={(options, params) => {
const filtered = filter(options, params);
if (params.inputValue !== '') {
filtered.push({
inputValue: params.inputValue,
name: `Add "${params.inputValue}"`,
});
}
return filtered;
}}
id="free-solo-dialog"
options={optionData}
getOptionLabel={(option) => {
// e.g value selected with enter, right from the input
if (typeof option === 'string') {
return option;
}
if (option.inputValue) {
return option.inputValue;
}
return option.name;
}}
selectOnFocus
clearOnBlur
handleHomeEndKeys
renderOption={(props, option) => <li {...props}>{option.name}</li>}
sx={{ width: 300 }}
freeSolo
renderInput={(params) => <TextField {...params} label="Markt auswählen" />}
/>
<Dialog open={open} onClose={handleClose}>
<form onSubmit={handleSubmit}>
<DialogTitle>Neuen Markt hinzufügen</DialogTitle>
<DialogContent>
<DialogContentText>
Ist der Markt noch nicht in der Liste? Dann füge ihn hinzu.
</DialogContentText>
<Grid container spacing={1}>
<Grid item xs={12}>
<TextField
autoFocus
fullWidth
margin="dense"
id="name"
error={dialogValue.name === ''}
value={dialogValue.name}
onChange={(event) =>
setDialogValue({
...dialogValue,
name: event.target.value,
})
}
label="Name"
type="text"
variant="standard"
/>
</Grid>
<Grid item xs={9}>
<TextField
margin="dense"
fullWidth
id="street"
error={dialogValue.street === ''}
//value={dialogValue.street}
onChange={(event) =>
setDialogValue({
...dialogValue,
street: event.target.value,
})
}
label="Straße"
type="text"
variant="standard"
/>
</Grid>
<Grid item xs={2}>
<TextField
margin="dense"
id="streetnum"
fullWidth
//value={dialogValue.streetNum}
error={dialogValue.streetNum === 0}
onChange={(event) =>
setDialogValue({
...dialogValue,
streetNum: event.target.value,
})
}
label="Nr."
type="number"
variant="standard"
/>
</Grid>
<Grid item xs={4}>
<TextField
margin="dense"
id="zip"
fullWidth
error={dialogValue.zip === ''}
//helperText="Incorrect entry."
//value={dialogValue.zip}
onChange={(event) =>
setDialogValue({
...dialogValue,
zip: event.target.value,
})
}
label="PLZ"
type="number"
variant="standard"
/>
</Grid>
<Grid item xs={8}>
<TextField
margin="dense"
id="city"
fullWidth
error={dialogValue.city === ''}
//value={dialogValue.city}
onChange={(event) =>
setDialogValue({
...dialogValue,
city: event.target.value,
})
}
label="Stadt"
type="text"
variant="standard"
/>
</Grid>
<Grid item xs={12}>
<TextField
margin="dense"
id="phone"
fullWidth
//value={dialogValue.tel}
onChange={(event) =>
setDialogValue({
...dialogValue,
tel: event.target.value,
})
}
label="Telefon"
type="text"
variant="standard"
/>
</Grid>
</Grid>
</DialogContent>
<DialogActions>
<Button
onClick={handleClose}
>
Abbrechen
</Button>
<Button
type="submit"
disabled={dialogValue.name === "" ||
dialogValue.street === "" ||
dialogValue.streetNum === 0 ||
dialogValue.zip === "" ||
dialogValue.city === ""
}
>
Hinzufügen</Button>
</DialogActions>
</form>
</Dialog>
</Fragment>
);
}
export default SelectMarketField

View File

@@ -0,0 +1,102 @@
import React from "react";
import {useState, useEffect} from 'react'
import PropTypes from 'prop-types';
import { styled } from '@mui/material/styles';
import LinearProgress from '@mui/material/LinearProgress';
import { Box, Typography, Button } from '@mui/material';
import axios from 'axios';
const baseURL = "http://localhost:8000/api"
const Input = styled('input')({
display: 'none',
});
function LinearProgressWithLabel(props) {
return (
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ width: '100%', mr: 1 }}>
<LinearProgress variant="determinate" {...props} />
</Box>
<Box sx={{ minWidth: 35 }}>
<Typography variant="body2" color="text.secondary">{`${Math.round(
props.value,
)}%`}</Typography>
</Box>
</Box>
);
}
LinearProgressWithLabel.propTypes = {
/**
* The value of the progress indicator for the determinate and buffer variants.
* Value between 0 and 100.
*/
value: PropTypes.number.isRequired,
};
const UploadFiles = (props) => {
const { apiURL, fileName, buttonText, applyBinarize, debug } = props;
const [progress, setProgress] = useState(0);
const [uploadFile, setUploadFile] = useState(0);
const handleUploadSelection = (e) => {
console.log(e.target.files[0])
setUploadFile(e.target.files[0]);
};
const handleUpload = (event) => {
event.preventDefault()
const formData = new FormData();
console.log(uploadFile)
formData.append("fileTitle", fileName);
formData.append("applyBinarize", applyBinarize);
formData.append("debug", debug);
formData.append("uploadedFile", uploadFile);
axios.post(baseURL+apiURL, formData, {
headers: {
'content-type': 'multipart/form-data'
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
};
return(
<div style={{ height: 100, width: '100%' }}>
<Box sx={{ width: '100%' }}>
<LinearProgressWithLabel value={progress} />
</Box>
<label htmlFor="contained-button-file">
<Input
accept="image/*"
id="contained-button-file"
multiple type="file"
onChange={handleUploadSelection}
/>
<Button variant="contained" component="span">
Bild auswählen
</Button>
<div className="file-name">
{uploadFile && uploadFile.length > 0 ? uploadFile[0].name : null}
</div>
<Button
variant="contained"
component="span"
onClick={handleUpload}
>
{buttonText}
</Button>
</label>
</div>
)
}
export default UploadFiles

View File

@@ -0,0 +1,50 @@
/**
* Prompts a user when they exit the page
*/
import { useCallback, useContext, useEffect } from 'react';
import { UNSAFE_NavigationContext as NavigationContext } from 'react-router-dom';
function useConfirmExit(confirmExit, when = true) {
const navigator = useContext(NavigationContext);
useEffect(() => {
if (!when) {
return;
}
const push = navigator.push;
navigator.push = (...args) => {
const result = confirmExit();
if (result !== false) {
push(...args);
}
};
return () => {
navigator.push = push;
};
}, [navigator, confirmExit, when]);
}
export default function usePrompt(message, when = true) {
useEffect(() => {
if (when) {
window.onbeforeunload = function () {
return message;
};
}
return () => {
window.onbeforeunload = null;
};
}, [message, when]);
const confirmExit = useCallback(() => {
const confirm = window.confirm(message);
return confirm;
}, [message]);
useConfirmExit(confirmExit, when);
}

View File

@@ -0,0 +1,8 @@
import axios from "axios";
export default axios.create({
baseURL: "http://localhost:8000/api",
headers: {
"Content-type": "application/json"
}
});

View File

@@ -0,0 +1,19 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

View File

@@ -0,0 +1,17 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;

View File

@@ -0,0 +1,15 @@
import http from "../http-common";
class UploadFilesService {
upload(file, onUploadProgress) {
let formData = new FormData();
formData.append("file", file);
return http.post("/purchase/post_receipe/", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
onUploadProgress,
});
}
}
export default new UploadFilesService();

View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';