[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",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
},
"devDependencies": {
"@formatjs/cli": "^6.0.3"
}
},
"node_modules/@adobe/css-tools": {
@ -2306,6 +2309,26 @@
"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": {
"version": "1.14.3",
"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": {
"version": "1.14.3",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz",

View File

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

View File

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

View File

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

View File

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

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

View File

@ -21,7 +21,10 @@ const BasicTabs = () => {
return (
<Box sx={{ width: "100%" }}>
<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
label={intl.formatMessage({
id: "installation",

View File

@ -1,5 +1,4 @@
import { useState } from "react";
import axiosConfig from "../config/axiosConfig";
const useToken = () => {
const getToken = () => {
@ -11,12 +10,17 @@ const useToken = () => {
const saveToken = (userToken: any) => {
sessionStorage.setItem("token", JSON.stringify(userToken));
setToken(userToken);
axiosConfig.defaults.headers.common["auth"] = userToken;
};
const removeToken = () => {
sessionStorage.removeItem("token");
setToken(null);
};
return {
setToken: saveToken,
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";
const Home = () => {
return (
<Box sx={{ padding: 2, bgcolor: "orange.50" }}>
<Grid container spacing={2}>
@ -27,7 +26,7 @@ const Home = () => {
type="search"
fullWidth
/>
<NestedList />
<NestedList />
</Grid>
<Grid
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 { useParams } from "react-router-dom";
import CustomerForm from "../components/CustomerForm";
@ -9,23 +10,36 @@ const InstallationDetail = () => {
const { id } = useParams();
const [values, setValues] = useState<I_Installation>();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<AxiosError>();
useEffect(() => {
setLoading(true);
axiosConfig.get("/GetInstallationById?id=" + id).then((res) => {
setValues(res.data);
setLoading(false);
});
axiosConfig
.get("/GetInstallationById?id=" + id)
.then((res) => {
setValues(res.data);
setLoading(false);
})
.catch((err: AxiosError) => {
setError(err);
setLoading(false);
});
}, [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 (
<Box sx={{ py: 3, width: 1 / 2 }}>
<CustomerForm values={values} id={id} />
</Box>
);
} else if (loading) {
return <CircularProgress />;
return <CircularProgress sx={{ m: 2 }} />;
}
return null;
};

View File

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