Merged PR 15: Added charts support

Added charts support
master
Tudor Stanciu 2020-06-05 17:02:55 +00:00
commit 45e3b3ae83
20 changed files with 2227 additions and 1121 deletions

3
.gitignore vendored
View File

@ -34,6 +34,3 @@ build
# Mac files
.DS_Store
# json-server db
db.json

2974
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -28,6 +28,7 @@
"react-i18next": "^11.4.0",
"react-redux": "6.0.1",
"react-router-dom": "5.0.0",
"recharts": "^1.8.5",
"redux": "4.0.1",
"redux-thunk": "2.3.0",
"reselect": "4.0.0"
@ -57,9 +58,9 @@
"rimraf": "2.6.3",
"style-loader": "0.23.1",
"webpack": "^4.43.0",
"webpack-bundle-analyzer": "3.1.0",
"webpack-cli": "3.3.0",
"webpack-dev-server": "3.2.1"
"webpack-bundle-analyzer": "^3.8.0",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.11.0"
},
"engines": {
"node": ">=8"

View File

@ -66,6 +66,17 @@
},
"MessageForAuthor": "Message for author"
},
"Charts": {
"Server": {
"Sessions": {
"RunningTime": {
"Title": "Sessions running time",
"Session": "Session",
"X": "Running time"
}
}
}
},
"Notifications": {
"MessageSaved": "Message saved"
}

View File

@ -57,6 +57,17 @@
},
"MessageForAuthor": "Mesaj pentru autor"
},
"Charts": {
"Server": {
"Sessions": {
"RunningTime": {
"Title": "Timp de rulare sesiuni",
"Session": "Sesiunea",
"X": "Timp de rulare"
}
}
}
},
"Notifications": {
"MessageSaved": "Mesaj salvat"
}

View File

@ -61,6 +61,15 @@ const Header = () => {
setAnchorEl(null);
};
const getFlagsPath = () => {
const basePath = "public/flags";
if (process.env.PUBLIC_URL) {
return `${process.env.PUBLIC_URL}/${basePath}`;
} else {
return basePath;
}
};
return (
<div className={classes.root}>
<AppBar position="static">
@ -94,7 +103,7 @@ const Header = () => {
format="png"
pngSize={32}
shiny={true}
basePath={`${process.env.PUBLIC_URL}/public/flags`}
basePath={getFlagsPath()}
alt={flag.alt}
/>
)}

View File

@ -0,0 +1,8 @@
import { combineReducers } from "redux";
import serverChartsReducer from "./server/reducer";
const chartsReducer = combineReducers({
server: serverChartsReducer
});
export default chartsReducer;

View File

@ -0,0 +1,20 @@
import * as types from "./actionTypes";
import api from "./api";
import { sendHttpRequest } from "../../../redux/actions/httpActions";
export function loadSessionsRunningTime() {
return async function (dispatch) {
try {
dispatch({ type: types.LOAD_SERVER_CHART_SESSIONS_RUNNING_TIME_STARTED });
const data = await dispatch(
sendHttpRequest(api.getSessionsRunningTime())
);
dispatch({
type: types.LOAD_SERVER_CHART_SESSIONS_RUNNING_TIME_SUCCESS,
payload: data
});
} catch (error) {
throw error;
}
};
}

View File

@ -0,0 +1,4 @@
export const LOAD_SERVER_CHART_SESSIONS_RUNNING_TIME_STARTED =
"LOAD_SERVER_CHART_SESSIONS_RUNNING_TIME_STARTED";
export const LOAD_SERVER_CHART_SESSIONS_RUNNING_TIME_SUCCESS =
"LOAD_SERVER_CHART_SESSIONS_RUNNING_TIME_SUCCESS";

View File

@ -0,0 +1,9 @@
import { get } from "../../../api/axiosApi";
const baseUrl = `${process.env.REVERSE_PROXY_API_URL}/charts`;
const getSessionsRunningTime = () =>
get(`${baseUrl}/server/sessions-running-time`);
export default {
getSessionsRunningTime
};

View File

@ -0,0 +1,40 @@
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import PropTypes from "prop-types";
import SessionsRunningTimeChart from "./SessionsRunningTimeChart";
import { loadSessionsRunningTime } from "../actionCreators";
const ServerChartsContainer = ({ actions, sessionRunningTime }) => {
useEffect(() => {
actions.loadSessionsRunningTime();
}, []);
return (
<>
<SessionsRunningTimeChart data={sessionRunningTime} />
</>
);
};
ServerChartsContainer.propTypes = {
actions: PropTypes.object.isRequired,
sessionRunningTime: PropTypes.array.isRequired
};
function mapStateToProps(state) {
return {
sessionRunningTime: state.charts.server.sessions.runningTime
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({ loadSessionsRunningTime }, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ServerChartsContainer);

View File

@ -0,0 +1,88 @@
import React from "react";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
import Spinner from "../../../../components/common/Spinner";
import {
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer
} from "recharts";
import Typography from "@material-ui/core/Typography";
import Grid from "@material-ui/core/Grid";
import SessionsRunningTimeChartTooltip from "./SessionsRunningTimeChartTooltip";
const SessionsRunningTimeChart = ({ data }) => {
const { t } = useTranslation();
const chartData = data.map((z) => {
return {
sessionId: z.sessionId,
name: `S${z.orderNo}`,
order: z.orderNo,
value: z.runningTime.hours,
label: z.runningTime.label
};
});
const CustomTooltip = ({ active, payload }) => {
if (!active) return null;
return <SessionsRunningTimeChartTooltip payload={payload[0].payload} />;
};
CustomTooltip.propTypes = {
active: PropTypes.bool,
payload: PropTypes.array
};
return (
<>
{data.loading || !data.loaded ? (
<Spinner />
) : (
<>
<Grid container justify="center">
<Grid item>
<Typography gutterBottom variant="h5">
{t("Charts.Server.Sessions.RunningTime.Title")}
</Typography>
</Grid>
</Grid>
<ResponsiveContainer width="100%" height={500}>
<BarChart
data={chartData}
margin={{
top: 5,
right: 30,
left: 20,
bottom: 5
}}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis type="number" dataKey="value" unit="h" />
<Tooltip content={<CustomTooltip />} />
<Legend />
<Bar
dataKey="value"
fill="#3f51b5"
name={t("Charts.Server.Sessions.RunningTime.X")}
/>
</BarChart>
</ResponsiveContainer>
</>
)}
</>
);
};
SessionsRunningTimeChart.propTypes = {
data: PropTypes.array.isRequired
};
export default SessionsRunningTimeChart;

View File

@ -0,0 +1,76 @@
import React from "react";
import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/core/styles";
import Chip from "@material-ui/core/Chip";
import Grid from "@material-ui/core/Grid";
import Divider from "@material-ui/core/Divider";
import Typography from "@material-ui/core/Typography";
import { useTranslation } from "react-i18next";
const useStyles = makeStyles((theme) => ({
root: {
width: "100%",
maxWidth: 360,
backgroundColor: theme.palette.background.paper,
borderStyle: "solid",
borderWidth: "1px",
borderColor: theme.palette.primary.main
},
chip: {
margin: theme.spacing(0.5)
},
section1: {
margin: theme.spacing(0, 1)
},
section2: {
margin: theme.spacing(1)
}
}));
const SessionsRunningTimeChartTooltip = ({ payload }) => {
const classes = useStyles();
const { t } = useTranslation();
return (
<div className={classes.root}>
<div className={classes.section1}>
<Grid container alignItems="center">
<Grid item xs>
<Typography gutterBottom variant="h6">
{`${t("Charts.Server.Sessions.RunningTime.Session")} ${
payload.order
}`}
</Typography>
</Grid>
</Grid>
<Typography color="textSecondary" variant="body2">
{`id: ${payload.sessionId}`}
</Typography>
</div>
<Divider variant="middle" />
<div className={classes.section2}>
<Typography gutterBottom variant="body2">
{t("Charts.Server.Sessions.RunningTime.X")}
</Typography>
<div>
{payload.label.split(" ").map((s) => {
return (
<Chip
key={s}
className={classes.chip}
color="primary"
label={s}
/>
);
})}
</div>
</div>
</div>
);
};
SessionsRunningTimeChartTooltip.propTypes = {
payload: PropTypes.object.isRequired
};
export default SessionsRunningTimeChartTooltip;

View File

@ -0,0 +1,33 @@
import * as types from "./actionTypes";
import initialState from "../../../redux/reducers/initialState";
export default function serverChartsReducer(
state = initialState.charts.server,
action
) {
switch (action.type) {
case types.LOAD_SERVER_CHART_SESSIONS_RUNNING_TIME_STARTED:
return {
...state,
sessions: {
...state.sessions,
runningTime: Object.assign([], { loading: true, loaded: false })
}
};
case types.LOAD_SERVER_CHART_SESSIONS_RUNNING_TIME_SUCCESS:
return {
...state,
sessions: {
...state.sessions,
runningTime: Object.assign(action.payload, {
loading: false,
loaded: true
})
}
};
default:
return state;
}
}

View File

@ -10,7 +10,6 @@ import {
Collapse,
Avatar,
IconButton,
Typography,
Tooltip
} from "@material-ui/core";
import ShareIcon from "@material-ui/icons/Share";
@ -21,6 +20,7 @@ import MessageRoundedIcon from "@material-ui/icons/MessageRounded";
import styles from "../../../components/common/styles/expandableCardStyles";
import ServerSummary from "./ServerSummary";
import { useTranslation } from "react-i18next";
import ServerChartsContainer from "../../charts/server/components/ServerChartsContainer";
const useStyles = makeStyles(styles);
@ -88,33 +88,7 @@ const ServerComponent = ({
</CardActions>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<CardContent>
<Typography paragraph>Method:</Typography>
<Typography paragraph>
Heat 1/2 cup of the broth in a pot until simmering, add saffron and
set aside for 10 minutes.
</Typography>
<Typography paragraph>
Heat oil in a (14- to 16-inch) paella pan or a large, deep skillet
over medium-high heat. Add chicken, shrimp and chorizo, and cook,
stirring occasionally until lightly browned, 6 to 8 minutes.
Transfer shrimp to a large plate and set aside, leaving chicken and
chorizo in the pan. Add pimentón, bay leaves, garlic, tomatoes,
onion, salt and pepper, and cook, stirring often until thickened and
fragrant, about 10 minutes. Add saffron broth and remaining 4 1/2
cups chicken broth; bring to a boil.
</Typography>
<Typography paragraph>
Add rice and stir very gently to distribute. Top with artichokes and
peppers, and cook without stirring, until most of the liquid is
absorbed, 15 to 18 minutes. Reduce heat to medium-low, add reserved
shrimp and mussels, tucking them down into the rice, and cook again
without stirring, until mussels have opened and rice is just tender,
5 to 7 minutes more. (Discard any mussels that dont open.)
</Typography>
<Typography>
Set aside off of the heat to let rest for 10 minutes, and then
serve.
</Typography>
<ServerChartsContainer />
</CardContent>
</Collapse>
</Card>

View File

@ -12,7 +12,7 @@ const store = configureStore();
render(
<ReduxProvider store={store}>
<Router basename={process.env.PUBLIC_URL}>
<Router basename={process.env.PUBLIC_URL || ""}>
<App />
</Router>
</ReduxProvider>,

View File

@ -8,6 +8,7 @@ import {
import releaseNotesReducer from "../../features/releaseNotes/reducer";
import frontendSessionReducer from "../../features/frontendSession/reducer";
import snackbarReducer from "../../features/snackbar/reducer";
import chartsReducer from "../../features/charts/chartsReducer";
const rootReducer = combineReducers({
frontendSession: frontendSessionReducer,
@ -15,6 +16,7 @@ const rootReducer = combineReducers({
sessions: sessionsReducer,
forwards: forwardsReducer,
releaseNotes: releaseNotesReducer,
charts: chartsReducer,
snackbar: snackbarReducer,
ajaxCallsInProgress: ajaxStatusReducer
});

View File

@ -7,6 +7,13 @@ export default {
sessions: Object.assign([], { loading: false, loaded: false }),
forwards: {},
releaseNotes: Object.assign([], { loading: false, loaded: false }),
charts: {
server: {
sessions: {
runningTime: Object.assign([], { loading: false, loaded: false })
}
}
},
snackbar: {
message: null,
type: null

View File

@ -83,7 +83,9 @@ i18n
}
},
backend: {
loadPath: `${process.env.PUBLIC_URL}/public/locales/{{lng}}/{{ns}}.json`
loadPath: `${
process.env.PUBLIC_URL || ""
}/public/locales/{{lng}}/{{ns}}.json`
}
},
() => {