update to git code before switching from fossil

This commit is contained in:
Kim 2023-02-22 14:43:12 +01:00
parent 9f1e1e4730
commit dff51b0d53
13 changed files with 594 additions and 148 deletions

View File

@ -1,18 +1,114 @@
import Home from "./routes/Home";
import useToken from "./hooks/useToken";
import Login from "./Login";
import { BrowserRouter } from "react-router-dom";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import {
Box,
Grid,
ButtonGroup,
Button,
Divider,
Select,
MenuItem,
} from "@mui/material";
import NestedList from "./components/NestedList";
import BasicTable from "./components/Table";
import BasicTabs from "./components/Tabs";
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 { useState } from "react";
const App = () => {
const { token, setToken } = 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;
}
return en;
};
return (
<BrowserRouter>
<Home />
<IntlProvider
messages={getTranslations()}
locale={language}
defaultLocale="en"
>
<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>
<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>
</Grid>
<Grid
item
xs={1}
container
direction="row"
justifyContent="center"
alignItems="center"
>
<Divider orientation="vertical" variant="middle" />
</Grid>
<Grid item xs={8}>
<BasicTabs />
<Routes>
<Route
path={routes.installationWithId}
element={<InstallationDetail />}
/>
<Route path={routes.alarmsWithId} element={<Alarms />} />
<Route path={routes.usersWithId} element={<BasicTable />} />
<Route path={routes.logWithId} element={<Log />} />
</Routes>
</Grid>
</Grid>
</Box>
</IntlProvider>
</BrowserRouter>
);
};

View File

@ -1,15 +1,14 @@
import React, { useState } from "react";
import axios from "axios";
import { Button, CircularProgress, TextField } from "@mui/material";
import { Button, CircularProgress } from "@mui/material";
import Container from "@mui/material/Container";
import InnovenergyTextfield from "./components/InnovenergyTextfield";
import { axiosConfigWithoutToken } from "./config/axiosConfig";
const loginUser = async (username: string, password: string) => {
return axios
.post("https://localhost:7087/api/Login", {
username,
password,
})
.then((data) => data);
return axiosConfigWithoutToken.post("/Login", {
username,
password,
});
};
const Login = ({ setToken }: { setToken: any }) => {
@ -26,24 +25,27 @@ const Login = ({ setToken }: { setToken: any }) => {
};
return (
<Container>
<TextField
id="outlined-basic"
<Container maxWidth="xs" sx={{ p: 2, alignContent: "center" }}>
<InnovenergyTextfield
id="username-textfield"
label="Username"
variant="outlined"
name="email"
sx={{ my: 0.5 }}
onChange={(e) => setUsername(e.target.value)}
value={username}
handleChange={(e) => setUsername(e.target.value)}
/>
<TextField
id="outlined-password-input"
<InnovenergyTextfield
id="password-textfield"
label="Password"
name="password"
type="password"
autoComplete="current-password"
sx={{ my: 0.5 }}
onChange={(e) => setPassword(e.target.value)}
value={password}
handleChange={(e) => setPassword(e.target.value)}
/>
<Button onClick={handleSubmit}>Login</Button>
<div>
<Button variant="outlined" onClick={handleSubmit} sx={{ my: 1 }}>
Login
</Button>
</div>
{loading && <CircularProgress />}
</Container>
);

View File

@ -0,0 +1,95 @@
import { Button } from "@mui/material";
import { useFormik } from "formik";
import { FormattedMessage, useIntl } from "react-intl";
import axiosConfig from "../config/axiosConfig";
import { I_Installation } from "../util/installation.util";
import InnovenergyTextfield from "./InnovenergyTextfield";
interface I_CustomerFormProps {
values: I_Installation;
id: string | undefined;
}
const CustomerForm = (props: I_CustomerFormProps) => {
const { values, id } = props;
const intl = useIntl();
const formik = useFormik({
initialValues: {
name: values.name,
region: values.region,
location: values.location,
country: values.country,
orderNumber: values.orderNumber,
},
onSubmit: (formikValues) => {
axiosConfig
.put("https://localhost:7087/api/UpdateInstallation", {
...formikValues,
id,
})
.then((res) => {
console.log(res);
});
},
});
return (
<form onSubmit={formik.handleSubmit}>
<InnovenergyTextfield
id="name-textfield"
label={intl.formatMessage({
id: "customerName",
defaultMessage: "Customer name",
})}
name="name"
value={formik.values.name}
handleChange={formik.handleChange}
/>
<InnovenergyTextfield
id="region-textfield"
label={intl.formatMessage({
id: "region",
defaultMessage: "Region",
})}
name="region"
value={formik.values.region}
handleChange={formik.handleChange}
/>
<InnovenergyTextfield
id="location-textfield"
label={intl.formatMessage({
id: "location",
defaultMessage: "Location",
})}
name="location"
value={formik.values.location}
handleChange={formik.handleChange}
/>
<InnovenergyTextfield
id="country-textfield"
label={intl.formatMessage({
id: "country",
defaultMessage: "Country",
})}
name="country"
value={formik.values.country}
handleChange={formik.handleChange}
/>
<InnovenergyTextfield
id="orderNumber-textfield"
label={intl.formatMessage({
id: "orderNumber",
defaultMessage: "Order number",
})}
name="orderNumber"
value={formik.values.orderNumber}
handleChange={formik.handleChange}
/>
<Button variant="outlined" type="submit">
<FormattedMessage id="applyChanges" defaultMessage="Apply changes" />
</Button>
</form>
);
};
export default CustomerForm;

View File

@ -0,0 +1,57 @@
import { useState, useCallback } from "react";
import ReactFlow, {
addEdge,
FitViewOptions,
applyNodeChanges,
applyEdgeChanges,
Node,
Edge,
NodeChange,
EdgeChange,
Connection,
} from "reactflow";
const initialNodes: Node[] = [
{ id: "1", data: { label: "Node 1" }, position: { x: 5, y: 5 } },
{ id: "2", data: { label: "Node 2" }, position: { x: 5, y: 100 } },
];
const initialEdges: Edge[] = [{ id: "e1-2", source: "1", target: "2" }];
const fitViewOptions: FitViewOptions = {
padding: 0.2,
};
function Flow() {
const [nodes, setNodes] = useState<Node[]>(initialNodes);
const [edges, setEdges] = useState<Edge[]>(initialEdges);
const onNodesChange = useCallback(
(changes: NodeChange[]) =>
setNodes((nds) => applyNodeChanges(changes, nds)),
[setNodes]
);
const onEdgesChange = useCallback(
(changes: EdgeChange[]) =>
setEdges((eds) => applyEdgeChanges(changes, eds)),
[setEdges]
);
const onConnect = useCallback(
(connection: Connection) => setEdges((eds) => addEdge(connection, eds)),
[setEdges]
);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
fitViewOptions={fitViewOptions}
/>
);
}
export default Flow;

View File

@ -0,0 +1,28 @@
import { TextField } from "@mui/material";
interface I_InnovenergyTextfieldProps {
id: string;
label: string;
value: string;
name: string;
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
type?: string;
}
const InnovenergyTextfield = (props: I_InnovenergyTextfieldProps) => {
return (
<TextField
id={props.id}
label={props.label}
variant="outlined"
name={props.name}
type={props.type}
fullWidth
sx={{ my: 0.5 }}
value={props.value || ""}
onChange={props.handleChange}
/>
);
};
export default InnovenergyTextfield;

View File

@ -1,14 +1,14 @@
import List from "@mui/material/List";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemText from "@mui/material/ListItemText";
import { Divider } from "@mui/material";
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 axios from "axios";
import { Installation } from "../util/installation.util";
import { I_Installation } from "../util/installation.util";
import axiosConfig from "../config/axiosConfig";
import { useIntl } from "react-intl";
const getPathWithoutId = (path?: string) => {
if (path) {
@ -18,8 +18,26 @@ const getPathWithoutId = (path?: string) => {
return routes.installation;
};
const filterData = (
searchQuery: string,
data: I_Installation[] | undefined
) => {
if (data) {
return data.filter(
(installation) =>
installation.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
installation.location.toLowerCase().includes(searchQuery.toLowerCase())
);
}
return data;
};
const NestedList = () => {
const [data, setData] = useState<Installation[]>();
const [data, setData] = useState<I_Installation[]>();
const [searchQuery, setSearchQuery] = useState("");
const intl = useIntl();
const filteredData = filterData(searchQuery, data);
const routeMatch = useRouteMatch([
routes.installationWithId,
routes.alarmsWithId,
@ -28,41 +46,51 @@ const NestedList = () => {
]);
useEffect(() => {
const tokenString = sessionStorage.getItem("token");
const token = tokenString !== null ? JSON.parse(tokenString) : "";
axios
.get("https://localhost:7087/api/GetAllInstallations", {
headers: {
auth: token,
},
})
axiosConfig
.get("https://localhost:7087/api/GetAllInstallations", {})
.then((res) => {
setData(res.data);
});
}, []);
return (
<List
sx={{ width: "100%", bgcolor: "background.paper" }}
component="nav"
aria-labelledby="nested-list-subheader"
>
{data?.map((installation) => (
<>
<Link
to={getPathWithoutId(routeMatch?.pattern?.path) + installation.id}
style={{ textDecoration: "none" }}
>
<ListItemButton>
<ListItemText
primary={installation.location + " | " + installation.name}
/>
</ListItemButton>
</Link>
<Divider />
</>
))}
</List>
<>
<TextField
id="outlined-search"
label={intl.formatMessage({
id: "search",
defaultMessage: "Search",
})}
type="search"
fullWidth
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
<List
sx={{ width: "100%", bgcolor: "background.paper" }}
component="nav"
aria-labelledby="nested-list-subheader"
>
{filteredData?.map((installation) => (
<>
<Link
key={installation.id}
to={getPathWithoutId(routeMatch?.pattern?.path) + installation.id}
style={{ textDecoration: "none", color: "black" }}
>
<ListItemButton
selected={installation.id === Number(routeMatch?.params.id)}
>
<ListItemText
primary={installation.location + " | " + installation.name}
/>
</ListItemButton>
</Link>
<Divider />
</>
))}
</List>
</>
);
};

View File

@ -5,6 +5,7 @@ import Box from "@mui/material/Box";
import { Link } from "react-router-dom";
import routes from "../routes.json";
import useRouteMatch from "../hooks/useRouteMatch";
import { useIntl } from "react-intl";
const BasicTabs = () => {
const routeMatch = useRouteMatch([
@ -15,31 +16,44 @@ const BasicTabs = () => {
]);
const id = routeMatch?.params?.id;
const intl = useIntl();
return (
<Box sx={{ width: "100%" }}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs value={routeMatch?.pattern?.path} aria-label="basic tabs example">
<Tab
label="Installation"
label={intl.formatMessage({
id: "installation",
defaultMessage: "Installation",
})}
value={routes.installationWithId}
component={Link}
to={routes.installation + id}
/>
<Tab
label="Alarms"
label={intl.formatMessage({
id: "alarms",
defaultMessage: "Alarms",
})}
value={routes.alarmsWithId}
component={Link}
to={routes.alarms + id}
/>
<Tab
label="Users"
label={intl.formatMessage({
id: "users",
defaultMessage: "Users",
})}
value={routes.usersWithId}
component={Link}
to={routes.users + id}
/>
<Tab
label="Log"
label={intl.formatMessage({
id: "log",
defaultMessage: "Log",
})}
value={routes.logWithId}
component={Link}
to={routes.log + id}

View File

@ -0,0 +1,25 @@
import axios from "axios";
export const axiosConfigWithoutToken = axios.create({
baseURL: "https://localhost:7087/api",
});
const axiosConfig = axios.create({
baseURL: "https://localhost:7087/api",
});
axiosConfig.interceptors.request.use(
(config) => {
const tokenString = sessionStorage.getItem("token");
const token = tokenString !== null ? JSON.parse(tokenString) : "";
if (token) {
config.headers.auth = token;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
export default axiosConfig;

View File

@ -1,22 +1,22 @@
import { useState } from "react";
const getToken = (): string => {
const tokenString = sessionStorage.getItem("token");
return tokenString !== null ? JSON.parse(tokenString).token : "";
};
import axiosConfig from "../config/axiosConfig";
const useToken = () => {
const [token, setToken] = useState(getToken());
const getToken = () => {
const tokenString = sessionStorage.getItem("token");
return tokenString !== null ? JSON.parse(tokenString) : "";
};
const [token, setToken] = useState(getToken());
const saveToken = (userToken: any) => {
setToken(userToken);
sessionStorage.setItem("token", JSON.stringify(userToken));
setToken(userToken);
axiosConfig.defaults.headers.common["auth"] = userToken;
};
return {
setToken: saveToken,
token,
getToken: getToken,
};
};

View File

@ -0,0 +1,5 @@
const Alarms = () => {
return <div>alarms</div>;
};
export default Alarms;

View File

@ -1,93 +1,31 @@
import { Box, TextField } from "@mui/material";
import axios from "axios";
import { Box, CircularProgress } from "@mui/material";
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { Installation } from "../util/installation.util";
import CustomerForm from "../components/CustomerForm";
import axiosConfig from "../config/axiosConfig";
import { I_Installation } from "../util/installation.util";
const InstallationDetail = () => {
const { id } = useParams();
const [values, setValues] = useState<Installation>();
const [values, setValues] = useState<I_Installation>();
const [loading, setLoading] = useState(false);
useEffect(() => {
const tokenString = sessionStorage.getItem("token");
const token = tokenString !== null ? JSON.parse(tokenString) : "";
axios
.get("https://localhost:7087/api/GetInstallationById?id=" + id, {
headers: {
auth: token,
},
})
.then((res) => {
setValues(res.data);
console.log(res.data);
});
setLoading(true);
axiosConfig.get("/GetInstallationById?id=" + id).then((res) => {
setValues(res.data);
setLoading(false);
});
}, [id]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setValues({
...values,
[name]: value,
} as Installation);
};
if (values) {
if (values && values.id.toString() === id) {
return (
<Box sx={{ py: 3, width: 1 / 2 }}>
<TextField
id="outlined-basic"
label="Customer name"
variant="outlined"
name="name"
type="text"
fullWidth
sx={{ my: 0.5 }}
value={values.name}
onChange={handleChange}
/>
<TextField
id="outlined-basic"
label="Region"
variant="outlined"
name="region"
fullWidth
sx={{ my: 0.5 }}
value={values.region}
onChange={handleChange}
/>
<TextField
id="outlined-basic"
label="Location"
variant="outlined"
name="location"
fullWidth
sx={{ my: 0.5 }}
value={values.location}
onChange={handleChange}
/>
<TextField
id="outlined-basic"
label="Country"
variant="outlined"
name="country"
fullWidth
sx={{ my: 0.5 }}
value={values.country}
onChange={handleChange}
/>
<TextField
id="outlined-basic"
label="Order number"
variant="outlined"
name="orderNumber"
fullWidth
sx={{ my: 0.5 }}
value={values.orderNumbers}
onChange={handleChange}
/>
<CustomerForm values={values} id={id} />
</Box>
);
} else if (loading) {
return <CircularProgress />;
}
return null;
};

View File

@ -0,0 +1,156 @@
const Log = () => {
const foo = {
TimeStamp: "1676643900",
Devices: [
{
"TruConvertAc 205330741": {
Ac: {
L1: {
Current: 2.49,
Voltage: 239.4,
Phi: 0.200334842323119592691046359,
},
L2: {
Current: 2.65,
Voltage: 239.6,
Phi: 0.200334842323119592691046359,
},
L3: {
Current: 2.63,
Voltage: 239.8,
Phi: 0,
},
Frequency: 49.98,
},
Dc: {
Current: 2.249388753056234718826405868,
Voltage: 818,
Power: 1840.0,
},
Alarms: [],
},
},
{
"TruConvertDc 3214": {
Dc: {
Current: -2.1173594132029339853300733496,
Voltage: 818,
Power: -1732.0,
},
Dc48: {
Current: -30,
Voltage: 56.0,
Power: -1680.0,
},
Warnings: [],
Alarms: [],
},
},
{
"EmuMeter 123": {
Ac: {
L1: {
Current: 3.212,
Voltage: 239.4,
Phi: 1.1483422646081408626645746948,
},
L2: {
Current: -2.462,
Voltage: 238.9,
Phi: 1.8441893582623698418074097834,
},
L3: {
Current: 2.995,
Voltage: 238.8,
Phi: 0.1415394733244272187457893568,
},
Frequency: 49.9,
},
},
},
{
"EmuMeter 123": {
Ac: {
L1: {
Current: 3.212,
Voltage: 239.4,
Phi: 1.1483422646081408626645746948,
},
L2: {
Current: -2.462,
Voltage: 238.9,
Phi: 1.8441893582623698418074097834,
},
L3: {
Current: 2.995,
Voltage: 238.8,
Phi: 0.1415394733244272187457893568,
},
Frequency: 49.9,
},
},
},
{
Name: "AMPT",
Type: "PvOnDc",
"Current 1": 2.098,
"Current 2": 2.575,
"Voltage 1": 822.989,
"Voltage 2": 823.169,
"Power 1": 1726.630922,
"Power 2": 2119.660175,
},
{
Name: "48TL Battery",
Type: "Battery",
Dc48: {
Current: 14.17,
Voltage: 53.41,
Power: 756.8197,
},
Alarms: [],
Warnings: [],
Soc: 77.4,
HeaterOn: true,
EocReached: false,
BatteryCold: false,
Temperature: 265.4,
},
{
Name: "48TL Battery",
Type: "Battery",
Dc48: {
Current: 11.3,
Voltage: 53.4,
Power: 603.42,
},
Alarms: [],
Warnings: ["bit44:"],
Soc: 77.6,
HeaterOn: true,
EocReached: false,
BatteryCold: false,
Temperature: 264.9,
},
],
};
const flattenObject = (obj: any, prefix = "") =>
Object.keys(obj).reduce((previous: any, current: any) => {
const pre = prefix.length ? prefix + "/" : "";
if (Array.isArray(obj) || typeof obj === "string") {
return previous;
}
if (typeof obj[current] === "object") {
Object.assign(previous, flattenObject(obj[current], pre + current));
} else {
previous[pre + current] = obj[current];
}
return previous;
}, {});
console.log(flattenObject(foo.Devices[0]));
return <div>log</div>;
};
export default Log;

View File

@ -1,5 +1,5 @@
// TODO add if required or not
export interface Installation {
export interface I_Installation {
type: string;
title: string;
status: number;
@ -8,7 +8,7 @@ export interface Installation {
location: string;
region: string;
country: string;
orderNumbers: string;
orderNumber: string;
lat: number;
long: number;
s3Bucket: string;
@ -17,3 +17,5 @@ export interface Installation {
information: string;
parentId: number;
}