[WIP] added files from fossil to git, added multilanguage

This commit is contained in:
Sina Blattmann 2023-02-23 08:19:42 +01:00
parent 3aaf0d7cb0
commit 5cceb33c50
18 changed files with 317 additions and 88 deletions

23
typescript/Frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -0,0 +1,50 @@
{
"alarms": {
"defaultMessage": "Alarms"
},
"allInstallations": {
"defaultMessage": "All installations"
},
"applyChanges": {
"defaultMessage": "Apply changes"
},
"country": {
"defaultMessage": "Country"
},
"customerName": {
"defaultMessage": "Customer name"
},
"english": {
"defaultMessage": "English"
},
"german": {
"defaultMessage": "German"
},
"installation": {
"defaultMessage": "Installation"
},
"location": {
"defaultMessage": "Location"
},
"log": {
"defaultMessage": "Log"
},
"logout": {
"defaultMessage": "Logout"
},
"orderNumbers": {
"defaultMessage": "Order number"
},
"region": {
"defaultMessage": "Region"
},
"search": {
"defaultMessage": "Search"
},
"updatedSuccessfully": {
"defaultMessage": "Updated successfully"
},
"users": {
"defaultMessage": "Users"
}
}

View File

@ -32,6 +32,9 @@
"reactflow": "^11.5.6", "reactflow": "^11.5.6",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
},
"devDependencies": {
"@formatjs/cli": "^6.0.3"
} }
}, },
"node_modules/@adobe/css-tools": { "node_modules/@adobe/css-tools": {
@ -2306,6 +2309,26 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/@formatjs/cli": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@formatjs/cli/-/cli-6.0.3.tgz",
"integrity": "sha512-YPhDUw5zQwufAZRUlk054Z/J9jeZWH4LEbAVnQFrfJ8NNgOziDqn9SSTEfRB4W0w8JBGgte4x1SwwJjH/HGoRA==",
"dev": true,
"bin": {
"formatjs": "bin/formatjs"
},
"engines": {
"node": ">= 16"
},
"peerDependencies": {
"@vue/compiler-sfc": "^3.2.34"
},
"peerDependenciesMeta": {
"@vue/compiler-sfc": {
"optional": true
}
}
},
"node_modules/@formatjs/ecma402-abstract": { "node_modules/@formatjs/ecma402-abstract": {
"version": "1.14.3", "version": "1.14.3",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz",
@ -19710,6 +19733,13 @@
} }
} }
}, },
"@formatjs/cli": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@formatjs/cli/-/cli-6.0.3.tgz",
"integrity": "sha512-YPhDUw5zQwufAZRUlk054Z/J9jeZWH4LEbAVnQFrfJ8NNgOziDqn9SSTEfRB4W0w8JBGgte4x1SwwJjH/HGoRA==",
"dev": true,
"requires": {}
},
"@formatjs/ecma402-abstract": { "@formatjs/ecma402-abstract": {
"version": "1.14.3", "version": "1.14.3",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz",

View File

@ -32,7 +32,8 @@
"start": "react-scripts start", "start": "react-scripts start",
"build": "react-scripts build", "build": "react-scripts build",
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject" "eject": "react-scripts eject",
"extract": "formatjs extract"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [
@ -51,5 +52,8 @@
"last 1 firefox version", "last 1 firefox version",
"last 1 safari version" "last 1 safari version"
] ]
},
"devDependencies": {
"@formatjs/cli": "^6.0.3"
} }
} }

View File

@ -1,15 +1,7 @@
import useToken from "./hooks/useToken"; import useToken from "./hooks/useToken";
import Login from "./Login"; import Login from "./Login";
import { BrowserRouter, Route, Routes } from "react-router-dom"; import { BrowserRouter, Route, Routes } from "react-router-dom";
import { import { Box, Grid, Divider } from "@mui/material";
Box,
Grid,
ButtonGroup,
Button,
Divider,
Select,
MenuItem,
} from "@mui/material";
import NestedList from "./components/NestedList"; import NestedList from "./components/NestedList";
import BasicTable from "./components/Table"; import BasicTable from "./components/Table";
import BasicTabs from "./components/Tabs"; import BasicTabs from "./components/Tabs";
@ -17,25 +9,18 @@ import Alarms from "./routes/Alarms";
import InstallationDetail from "./routes/Installation"; import InstallationDetail from "./routes/Installation";
import Log from "./routes/Log"; import Log from "./routes/Log";
import routes from "./routes.json"; import routes from "./routes.json";
import { FormattedMessage, IntlProvider } from "react-intl"; import { IntlProvider } from "react-intl";
import { useState } from "react"; import { useState } from "react";
import en from "./lang/en.json";
import de from "./lang/de.json";
import NavigationButtons from "./components/NavigationButtons";
import LanguageSelect from "./components/LanguageSelect";
import LogoutButton from "./components/LogoutButton";
const App = () => { const App = () => {
const { token, setToken } = useToken(); const { token, setToken, removeToken } = useToken();
const [language, setLanguage] = useState("en"); const [language, setLanguage] = useState("en");
if (!token) {
return <Login setToken={setToken} />;
}
const de = {
allInstallations: "Alle Installationen",
};
const en = {
allInstallations: "All installations",
};
const getTranslations = () => { const getTranslations = () => {
if (language === "de") { if (language === "de") {
return de; return de;
@ -43,6 +28,12 @@ const App = () => {
return en; return en;
}; };
if (!token) {
return <Login setToken={setToken} />;
}
console.log("app");
return ( return (
<BrowserRouter> <BrowserRouter>
<IntlProvider <IntlProvider
@ -53,36 +44,10 @@ const App = () => {
<Box sx={{ padding: 2 }}> <Box sx={{ padding: 2 }}>
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={3}> <Grid item xs={3}>
<ButtonGroup <NavigationButtons />
variant="outlined"
aria-label="outlined primary button group"
sx={{ paddingBottom: 3 }}
>
<Button>
<FormattedMessage
id="allInstallations"
defaultMessage="All installations"
/>
</Button>
<Button>
<FormattedMessage id="users" defaultMessage="Users" />
</Button>
</ButtonGroup>
<NestedList /> <NestedList />
<Select <LanguageSelect language={language} setLanguage={setLanguage} />
labelId="demo-simple-select-label" <LogoutButton removeToken={removeToken} />
id="demo-simple-select"
value={language}
label="Age"
onChange={(e) => setLanguage(e.target.value)}
>
<MenuItem value="en">
<FormattedMessage id="english" defaultMessage="English" />
</MenuItem>
<MenuItem value="de">
<FormattedMessage id="german" defaultMessage="German" />
</MenuItem>
</Select>
</Grid> </Grid>
<Grid <Grid
item item

View File

@ -1,5 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { Button, CircularProgress } from "@mui/material"; import { Alert, Button, CircularProgress } from "@mui/material";
import Container from "@mui/material/Container"; import Container from "@mui/material/Container";
import InnovenergyTextfield from "./components/InnovenergyTextfield"; import InnovenergyTextfield from "./components/InnovenergyTextfield";
import { axiosConfigWithoutToken } from "./config/axiosConfig"; import { axiosConfigWithoutToken } from "./config/axiosConfig";
@ -15,11 +15,16 @@ const Login = ({ setToken }: { setToken: any }) => {
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState();
const handleSubmit = async (e: any) => { const handleSubmit = (e: any) => {
setLoading(true); setLoading(true);
loginUser(username, password).then((res) => { loginUser(username, password).then(({ data }) => {
setToken(res.data); if (typeof data === "string") {
setToken(data);
setLoading(false);
}
setError(data);
setLoading(false); setLoading(false);
}); });
}; };
@ -41,6 +46,7 @@ const Login = ({ setToken }: { setToken: any }) => {
value={password} value={password}
handleChange={(e) => setPassword(e.target.value)} handleChange={(e) => setPassword(e.target.value)}
/> />
{error && <Alert severity="error">Incorrect username or password</Alert>}
<div> <div>
<Button variant="outlined" onClick={handleSubmit} sx={{ my: 1 }}> <Button variant="outlined" onClick={handleSubmit} sx={{ my: 1 }}>
Login Login

View File

@ -1,5 +1,6 @@
import { Button } from "@mui/material"; import { Alert, Button, Snackbar } from "@mui/material";
import { useFormik } from "formik"; import { useFormik } from "formik";
import { useState } from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import axiosConfig from "../config/axiosConfig"; import axiosConfig from "../config/axiosConfig";
import { I_Installation } from "../util/installation.util"; import { I_Installation } from "../util/installation.util";
@ -13,13 +14,15 @@ const CustomerForm = (props: I_CustomerFormProps) => {
const { values, id } = props; const { values, id } = props;
const intl = useIntl(); const intl = useIntl();
const [open, setOpen] = useState(false);
const formik = useFormik({ const formik = useFormik({
initialValues: { initialValues: {
name: values.name, name: values.name,
region: values.region, region: values.region,
location: values.location, location: values.location,
country: values.country, country: values.country,
orderNumber: values.orderNumber, orderNumbers: values.orderNumbers,
}, },
onSubmit: (formikValues) => { onSubmit: (formikValues) => {
axiosConfig axiosConfig
@ -28,11 +31,15 @@ const CustomerForm = (props: I_CustomerFormProps) => {
id, id,
}) })
.then((res) => { .then((res) => {
console.log(res); setOpen(true);
}); });
}, },
}); });
const handleClose = () => {
setOpen(false);
};
return ( return (
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
<InnovenergyTextfield <InnovenergyTextfield
@ -76,18 +83,31 @@ const CustomerForm = (props: I_CustomerFormProps) => {
handleChange={formik.handleChange} handleChange={formik.handleChange}
/> />
<InnovenergyTextfield <InnovenergyTextfield
id="orderNumber-textfield" id="orderNumbers-textfield"
label={intl.formatMessage({ label={intl.formatMessage({
id: "orderNumber", id: "orderNumbers",
defaultMessage: "Order number", defaultMessage: "Order number",
})} })}
name="orderNumber" name="orderNumbers"
value={formik.values.orderNumber} value={formik.values.orderNumbers}
handleChange={formik.handleChange} handleChange={formik.handleChange}
/> />
<Button variant="outlined" type="submit"> <Button variant="outlined" type="submit">
<FormattedMessage id="applyChanges" defaultMessage="Apply changes" /> <FormattedMessage id="applyChanges" defaultMessage="Apply changes" />
</Button> </Button>
<Snackbar
open={open}
anchorOrigin={{
vertical: "bottom",
horizontal: "right",
}}
autoHideDuration={6000}
onClose={handleClose}
>
<Alert onClose={handleClose} severity="success" sx={{ width: "100%" }}>
<FormattedMessage id="updatedSuccessfully" defaultMessage="Updated successfully" />
</Alert>
</Snackbar>
</form> </form>
); );
}; };

View File

@ -0,0 +1,27 @@
import { MenuItem, Select } from "@mui/material";
import { FormattedMessage } from "react-intl";
interface LanguageSelectProps {
language: string;
setLanguage: (language: string) => void;
}
const LanguageSelect = (props: LanguageSelectProps) => {
return (
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={props.language}
label="Age"
onChange={(e) => props.setLanguage(e.target.value)}
>
<MenuItem value="en">
<FormattedMessage id="english" defaultMessage="English" />
</MenuItem>
<MenuItem value="de">
<FormattedMessage id="german" defaultMessage="German" />
</MenuItem>
</Select>
);
};
export default LanguageSelect;

View File

@ -0,0 +1,28 @@
import { Button } from "@mui/material";
import { FormattedMessage } from "react-intl";
import axiosConfig from "../config/axiosConfig";
import { useNavigate } from "react-router-dom";
interface LogoutButtonProps {
removeToken: () => void;
}
const LogoutButton = (props: LogoutButtonProps) => {
const navigate = useNavigate();
return (
<Button
variant="outlined"
onClick={() => {
axiosConfig.post("/Logout").then(() => {
navigate("/");
props.removeToken();
});
}}
sx={{ mx: 1 }}
>
<FormattedMessage id="logout" defaultMessage="Logout" />
</Button>
);
};
export default LogoutButton;

View File

@ -0,0 +1,24 @@
import { Button, ButtonGroup } from "@mui/material";
import { FormattedMessage } from "react-intl";
const NavigationButtons = () => {
return (
<ButtonGroup
variant="outlined"
aria-label="outlined primary button group"
sx={{ paddingBottom: 3 }}
>
<Button>
<FormattedMessage
id="allInstallations"
defaultMessage="All installations"
/>
</Button>
<Button>
<FormattedMessage id="users" defaultMessage="Users" />
</Button>
</ButtonGroup>
);
};
export default NavigationButtons;

View File

@ -5,7 +5,7 @@ import { Divider, TextField } from "@mui/material";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import useRouteMatch from "../hooks/useRouteMatch"; import useRouteMatch from "../hooks/useRouteMatch";
import routes from "../routes.json"; import routes from "../routes.json";
import { useEffect, useState } from "react"; import { Fragment, useEffect, useState } from "react";
import { I_Installation } from "../util/installation.util"; import { I_Installation } from "../util/installation.util";
import axiosConfig from "../config/axiosConfig"; import axiosConfig from "../config/axiosConfig";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
@ -46,11 +46,10 @@ const NestedList = () => {
]); ]);
useEffect(() => { useEffect(() => {
axiosConfig console.log("useeffect");
.get("https://localhost:7087/api/GetAllInstallations", {}) axiosConfig.get("/GetAllInstallations", {}).then((res) => {
.then((res) => { setData(res.data);
setData(res.data); });
});
}, []); }, []);
return ( return (
@ -72,9 +71,8 @@ const NestedList = () => {
aria-labelledby="nested-list-subheader" aria-labelledby="nested-list-subheader"
> >
{filteredData?.map((installation) => ( {filteredData?.map((installation) => (
<> <Fragment key={installation.id}>
<Link <Link
key={installation.id}
to={getPathWithoutId(routeMatch?.pattern?.path) + installation.id} to={getPathWithoutId(routeMatch?.pattern?.path) + installation.id}
style={{ textDecoration: "none", color: "black" }} style={{ textDecoration: "none", color: "black" }}
> >
@ -87,7 +85,7 @@ const NestedList = () => {
</ListItemButton> </ListItemButton>
</Link> </Link>
<Divider /> <Divider />
</> </Fragment>
))} ))}
</List> </List>
</> </>

View File

@ -21,7 +21,10 @@ const BasicTabs = () => {
return ( return (
<Box sx={{ width: "100%" }}> <Box sx={{ width: "100%" }}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}> <Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs value={routeMatch?.pattern?.path} aria-label="basic tabs example"> <Tabs
value={routeMatch?.pattern?.path ?? routes.installationWithId}
aria-label="basic tabs example"
>
<Tab <Tab
label={intl.formatMessage({ label={intl.formatMessage({
id: "installation", id: "installation",

View File

@ -1,5 +1,4 @@
import { useState } from "react"; import { useState } from "react";
import axiosConfig from "../config/axiosConfig";
const useToken = () => { const useToken = () => {
const getToken = () => { const getToken = () => {
@ -11,12 +10,17 @@ const useToken = () => {
const saveToken = (userToken: any) => { const saveToken = (userToken: any) => {
sessionStorage.setItem("token", JSON.stringify(userToken)); sessionStorage.setItem("token", JSON.stringify(userToken));
setToken(userToken); setToken(userToken);
axiosConfig.defaults.headers.common["auth"] = userToken; };
const removeToken = () => {
sessionStorage.removeItem("token");
setToken(null);
}; };
return { return {
setToken: saveToken, setToken: saveToken,
token, token,
removeToken,
}; };
}; };

View File

@ -0,0 +1,18 @@
{
"alarms": "Alarme",
"allInstallations": "Alle Installationen",
"applyChanges": "Änderungen übernehmen",
"country": "Land",
"customerName": "Kundenname",
"english": "Englisch",
"german": "Deutsch",
"installation": "Installation",
"location": "Ort",
"log": "Log",
"orderNumbers": "Bestellnummern",
"region": "Region",
"search": "Suche",
"users": "Benutzer",
"logout": "Logout",
"updatedSuccessfully": "Erfolgreich aktualisiert"
}

View File

@ -0,0 +1,18 @@
{
"alarms": "Alarms",
"allInstallations": "All installations",
"applyChanges": "Apply changes",
"country": "country",
"customerName": "Customer name",
"english": "English",
"german": "German",
"installation": "Installation",
"location": "Location",
"log": "Log",
"orderNumbers": "Order numbers",
"region": "Region",
"search": "Search",
"users": "Users",
"logout": "Logout",
"updatedSuccessfully": "Updated successfully"
}

View File

@ -8,7 +8,6 @@ import routes from "../routes.json";
import InstallationDetail from "./Installation"; import InstallationDetail from "./Installation";
const Home = () => { const Home = () => {
return ( return (
<Box sx={{ padding: 2, bgcolor: "orange.50" }}> <Box sx={{ padding: 2, bgcolor: "orange.50" }}>
<Grid container spacing={2}> <Grid container spacing={2}>
@ -27,7 +26,7 @@ const Home = () => {
type="search" type="search"
fullWidth fullWidth
/> />
<NestedList /> <NestedList />
</Grid> </Grid>
<Grid <Grid
item item

View File

@ -1,4 +1,5 @@
import { Box, CircularProgress } from "@mui/material"; import { Alert, Box, CircularProgress } from "@mui/material";
import { AxiosError } from "axios";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import CustomerForm from "../components/CustomerForm"; import CustomerForm from "../components/CustomerForm";
@ -9,23 +10,36 @@ const InstallationDetail = () => {
const { id } = useParams(); const { id } = useParams();
const [values, setValues] = useState<I_Installation>(); const [values, setValues] = useState<I_Installation>();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState<AxiosError>();
useEffect(() => { useEffect(() => {
setLoading(true); setLoading(true);
axiosConfig.get("/GetInstallationById?id=" + id).then((res) => { axiosConfig
setValues(res.data); .get("/GetInstallationById?id=" + id)
setLoading(false); .then((res) => {
}); setValues(res.data);
setLoading(false);
})
.catch((err: AxiosError) => {
setError(err);
setLoading(false);
});
}, [id]); }, [id]);
if (values && values.id.toString() === id) { if (error) {
return (
<Alert severity="error" sx={{ mt: 1 }}>
{error.message}
</Alert>
);
} else if (values && values.id.toString() === id) {
return ( return (
<Box sx={{ py: 3, width: 1 / 2 }}> <Box sx={{ py: 3, width: 1 / 2 }}>
<CustomerForm values={values} id={id} /> <CustomerForm values={values} id={id} />
</Box> </Box>
); );
} else if (loading) { } else if (loading) {
return <CircularProgress />; return <CircularProgress sx={{ m: 2 }} />;
} }
return null; return null;
}; };

View File

@ -8,7 +8,7 @@ export interface I_Installation {
location: string; location: string;
region: string; region: string;
country: string; country: string;
orderNumber: string; orderNumbers: string;
lat: number; lat: number;
long: number; long: number;
s3Bucket: string; s3Bucket: string;
@ -17,5 +17,3 @@ export interface I_Installation {
information: string; information: string;
parentId: number; parentId: number;
} }