feat: Implement the new /dashboard API

This commit is contained in:
Alexander Polynomdivision 2018-09-24 18:29:29 +02:00
parent 7339e1ccac
commit 24f35be058
7 changed files with 127 additions and 143 deletions

View File

@ -114,16 +114,35 @@ userRouter.post("/level/:id", async (req, res) => {
}); });
// Get the data needed for the dashboard // Get the data needed for the dashboard
userRouter.post("/dashboard", async (req, res) => { userRouter.get("/dashboard", async (req, res) => {
console.log("STUB(post): /user/dashboard"); console.log("STUB(post): /user/dashboard");
let users: any[] = [];
let nr = 10;
for (let i = 0; i < 10; i++)
users = users.concat({
username: `Test User ${i}`,
score: 100 * i,
level: Math.floor(Math.random() * Math.floor(10)),
nr: nr--,
});
// TODO: Stub // TODO: Stub
res.send({ res.send({
error: "0", error: "200",
data: { data: {
nextLevel: {}, nextLevel: {
topTen: {}, name: "Test level",
lastReview: {}, desc: "Just a test",
level: 3,
done: false,
},
topTen: users,
lastReview: {
correct: 0,
wrong: 0,
},
}, },
}); });
}); });

View File

@ -153,14 +153,6 @@ export function setNextLevel(level: ILevel) {
}; };
}; };
export const DASHBOARD_SET_NL_LOADING = "DASHBOARD_SET_NL_LOADING";
export function setDashboardNLLoading(state: boolean) {
return {
type: DASHBOARD_SET_NL_LOADING,
state,
};
};
export const SET_TOP_TEN = "SET_TOP_TEN"; export const SET_TOP_TEN = "SET_TOP_TEN";
export function setTopTen(topTen: TopTen[]) { export function setTopTen(topTen: TopTen[]) {
return { return {
@ -169,14 +161,6 @@ export function setTopTen(topTen: TopTen[]) {
}; };
}; };
export const DASHBOARD_SET_TT_LOADING = "DASHBOARD_SET_TT_LOADING";
export function setDashboardTTLoading(state: boolean) {
return {
type: DASHBOARD_SET_TT_LOADING,
state,
};
};
export const SET_DID_LOGIN = "SET_DID_LOGIN"; export const SET_DID_LOGIN = "SET_DID_LOGIN";
export function setDidLogin(state: boolean) { export function setDidLogin(state: boolean) {
return { return {
@ -185,14 +169,6 @@ export function setDidLogin(state: boolean) {
}; };
}; };
export const DASHBOARD_SET_LR_LOADING = "DASHBOARD_SET_LR_LOADING";
export function setDashboardLRLoading(state: boolean) {
return {
type: DASHBOARD_SET_LR_LOADING,
state,
};
};
export const REVIEW_SET_DIALOG = "REVIEW_SET_DIALOG"; export const REVIEW_SET_DIALOG = "REVIEW_SET_DIALOG";
export function setReviewDialog(state: boolean) { export function setReviewDialog(state: boolean) {
return { return {
@ -200,3 +176,11 @@ export function setReviewDialog(state: boolean) {
state, state,
}; };
}; };
export const DASHBOARD_SET_LOADING = "DASHBOARD_SET_LOADING";
export function setDashboardLoading(state: boolean) {
return {
type: DASHBOARD_SET_LOADING,
state,
};
};

View File

@ -173,6 +173,27 @@ export default class Application extends React.Component<IProps> {
}); });
} }
// TODO: Type?
getDashboard = (): Promise<any> => {
return new Promise((res, rej) => {
fetch(`${BACKEND_URL}/api/user/dashboard`, {
headers: new Headers({
"Content-Type": "application/json",
"Token": this.props.user.sessionToken,
}),
})
.then(resp => resp.json(), err => rej(err))
.then(data => {
if (data.error === "200") {
res(data.data);
} else {
console.log("Application::getDashboard: Failed to get dashboard");
rej(data);
}
});
});
}
login = (username: string, password: string): Promise<IUser | IResponse> => { login = (username: string, password: string): Promise<IUser | IResponse> => {
return new Promise((res, rej) => { return new Promise((res, rej) => {
fetch(`${BACKEND_URL}/api/login`, { fetch(`${BACKEND_URL}/api/login`, {
@ -233,9 +254,7 @@ export default class Application extends React.Component<IProps> {
path="/dashboard" path="/dashboard"
component={() => { component={() => {
return <Dashboard return <Dashboard
getNextLevel={this.getNextLevel} getDashboard={this.getDashboard} />
getLastReview={this.getLastReview}
getTopTen={this.getTopTenLearners} />
}} /> }} />
<AuthRoute <AuthRoute
isAuth={this.isAuthenticated} isAuth={this.isAuthenticated}

View File

@ -1,8 +1,7 @@
import { connect } from "react-redux"; import { connect } from "react-redux";
import { import {
setNextLevel, setDashboardNLLoading, setTopTen, setDashboardTTLoading, setNextLevel, setTopTen, setLastReview, setDashboardLoading
setDashboardLRLoading, setLastReview,
} from "../actions"; } from "../actions";
import { ILevel } from "../models/level"; import { ILevel } from "../models/level";
@ -13,22 +12,18 @@ import DashboardPage from "../pages/dashboard";
const mapStateToProps = state => { const mapStateToProps = state => {
return { return {
loading: state.review.loading,
nextLevel: state.nextLevel, nextLevel: state.nextLevel,
loadingNextLevel: state.dashboard.loadingNL,
loadingTopTen: state.dashboard.loadingTT,
topTen: state.topTen, topTen: state.topTen,
loadingLastReview: state.dashboard.loadingLR,
lastReview: state.lastReview, lastReview: state.lastReview,
}; };
}; };
const mapDispatchToProps = dispatch => { const mapDispatchToProps = dispatch => {
return { return {
setLoadingNL: (state: boolean) => dispatch(setDashboardNLLoading(state)), setLoading: (state: boolean) => dispatch(setDashboardLoading(state)),
setNextLevel: (level: ILevel) => dispatch(setNextLevel(level)), setNextLevel: (level: ILevel) => dispatch(setNextLevel(level)),
setTopTen: (topTen: ILearner[]) => dispatch(setTopTen(topTen)), setTopTen: (topTen: ILearner[]) => dispatch(setTopTen(topTen)),
setLoadingTT: (state: boolean) => dispatch(setDashboardTTLoading(state)),
setLastReview: (review: IReviewMetadata) => dispatch(setLastReview(review)), setLastReview: (review: IReviewMetadata) => dispatch(setLastReview(review)),
setLoadingLR: (state: boolean) => dispatch(setDashboardLRLoading(state)),
} }
}; };

View File

@ -16,51 +16,50 @@ import { ILearner, TopTen } from "../models/learner";
import { IReviewMetadata } from "../models/review"; import { IReviewMetadata } from "../models/review";
interface IProps { interface IProps {
getNextLevel: () => Promise<ILevel>; getDashboard: () => Promise<any>;
getLastReview: () => Promise<IReviewMetadata>;
getTopTen: () => Promise<ILearner[]>;
loading: boolean;
setLoading: (state: boolean) => void;
nextLevel: ILevel; nextLevel: ILevel;
loadingNextLevel: boolean;
setLoadingNL: (state: boolean) => void;
setNextLevel: (level: ILevel) => void; setNextLevel: (level: ILevel) => void;
topTen: TopTen[]; topTen: TopTen[];
loadingTopTen: boolean;
setLoadingTT: (state: boolean) => void;
setTopTen: (topten: TopTen[]) => void; setTopTen: (topten: TopTen[]) => void;
lastReview: IReviewMetadata; lastReview: IReviewMetadata;
loadingLastReview: boolean;
setLoadingLR: (state: boolean) => void;
setLastReview: (review: IReviewMetadata) => void; setLastReview: (review: IReviewMetadata) => void;
} }
export default class Dashboard extends React.Component<IProps> { export default class Dashboard extends React.Component<IProps> {
componentDidMount() { componentDidMount() {
this.props.setLoadingNL(true); this.props.setLoading(true);
this.props.getDashboard().then(res => {
this.props.getNextLevel().then(res => { this.props.setNextLevel(res.nextLevel);
this.props.setLoadingNL(false); this.props.setTopTen(res.topTen);
this.props.setNextLevel(res); this.props.setLastReview(res.lastReview);
}, err => { this.props.setLoading(false);
console.log("Failed to fetch next level!", err); })
});
this.props.getTopTen().then(res => {
this.props.setLoadingTT(false);
this.props.setTopTen(res);
}, err => {
console.log("Failed to fetch Top Ten");
});
this.props.getLastReview().then(res => {
this.props.setLoadingLR(false);
this.props.setLastReview(res);
}, err => {
console.log("Failed to fetch Last Review");
});
} }
render() { render() {
if (this.props.loading) {
return <div>
<Grid
container
spacing={0}
direction="column"
alignItems="center"
justify="center"
style={{ minHeight: '100vh' }}>
<Grid item xs={12}>
<Paper className="paper">
<Grid container direction="column" spacing={8}>
<CircularProgress />
</Grid>
</Paper>
</Grid>
</Grid>
</div>;
}
const small = window.matchMedia("(max-width: 700px)").matches; const small = window.matchMedia("(max-width: 700px)").matches;
const direction = small ? "column" : "row"; const direction = small ? "column" : "row";
@ -70,63 +69,48 @@ export default class Dashboard extends React.Component<IProps> {
<Grid container direction={direction} spacing={16}> <Grid container direction={direction} spacing={16}>
<Grid item lg={4}> <Grid item lg={4}>
<Paper className="paper"> <Paper className="paper">
{this.props.loadingNextLevel ? ( <div>
<CircularProgress /> <Typography variant="title">{`Level ${level.level}`}</Typography>
) : ( <Typography variant="title" component="p">{level.name}</Typography>
<div> <br />
<Typography variant="title">{`Level ${level.level}`}</Typography> <Typography component="p">
<Typography variant="title" component="p">{level.name}</Typography> {level.desc}
<br /> </Typography>
<Typography component="p"> <Button
{level.desc} component={Link}
</Typography> to={`/level/${level.level}`}
<Button className="lesson-card-btn">
component={Link} Zum Level
to={`/level/${level.level}`} </Button>
className="lesson-card-btn"> </div>
Zum Level
</Button>
</div>
)}
</Paper> </Paper>
</Grid> </Grid>
<Grid item lg={4}> <Grid item lg={4}>
<Paper className="paper"> <Paper className="paper">
{this.props.loadingTopTen ? ( <div>
<CircularProgress /> <Typography variant="title" component="p">
) : ( Rangliste: Top 10
<div> </Typography>
<Typography variant="title" component="p">
Rangliste: Top 10
</Typography>
<Scoreboard topTen={this.props.topTen} /> <Scoreboard topTen={this.props.topTen} />
</div> </div>
)}
</Paper> </Paper>
</Grid> </Grid>
<Grid item lg={4}> <Grid item lg={4}>
<Paper className="paper"> <Paper className="paper">
{ <div>
this.props.loadingLastReview ? ( <Typography variant="title">
<CircularProgress /> Letzte Wiederholung
) : ( </Typography>
<div> <SummaryTable reviewMeta={this.props.lastReview} />
<Typography variant="title">
Letzte Wiederholung
</Typography>
<SummaryTable reviewMeta={this.props.lastReview} />
<Button <Button
component={Link} component={Link}
to="/review/queue" to="/review/queue"
className="lesson-card-btn"> className="lesson-card-btn">
Vokabeln üben Vokabeln üben
</Button> </Button>
</div> </div>
)
}
</Paper> </Paper>
</Grid> </Grid>
</Grid> </Grid>

View File

@ -51,7 +51,6 @@ export default class Dashboard extends React.Component<IProps> {
</Paper> </Paper>
</Grid> </Grid>
</Grid> </Grid>
</div>; </div>;
} }

View File

@ -37,9 +37,7 @@ interface IState {
}; };
dashboard: { dashboard: {
loadingNL: boolean; loading: boolean;
loadingTT: boolean;
loadingLR: boolean;
}; };
review: { review: {
@ -97,9 +95,7 @@ const initialState: IState = {
}, },
dashboard: { dashboard: {
loadingNL: true, loading: true,
loadingTT: true,
loadingLR: true,
}, },
review: { review: {
@ -227,38 +223,26 @@ export function LateinicusApp(state: IState = initialState, action: any) {
return Object.assign({}, state, { return Object.assign({}, state, {
nextLevel: action.level, nextLevel: action.level,
}); });
case Actions.DASHBOARD_SET_NL_LOADING:
return Object.assign({}, state, {
dashboard: Object.assign({}, state.dashboard, {
loadingNL: action.state,
}),
});
case Actions.SET_TOP_TEN: case Actions.SET_TOP_TEN:
return Object.assign({}, state, { return Object.assign({}, state, {
topTen: action.topTen, topTen: action.topTen,
}); });
case Actions.DASHBOARD_SET_TT_LOADING:
return Object.assign({}, state, {
dashboard: Object.assign({}, state.dashboard, {
loadingTT: action.state,
}),
});
case Actions.SET_DID_LOGIN: case Actions.SET_DID_LOGIN:
return Object.assign({}, state, { return Object.assign({}, state, {
didLogin: state, didLogin: state,
}); });
case Actions.DASHBOARD_SET_LR_LOADING:
return Object.assign({}, state, {
dashboard: Object.assign({}, state.dashboard, {
loadingLR: action.state,
}),
});
case Actions.REVIEW_SET_DIALOG: case Actions.REVIEW_SET_DIALOG:
return Object.assign({}, state, { return Object.assign({}, state, {
review: Object.assign({}, state.review, { review: Object.assign({}, state.review, {
dialogOpen: action.state, dialogOpen: action.state,
}), }),
}); });
case Actions.DASHBOARD_SET_LOADING:
return Object.assign({}, state, {
review: Object.assign({}, state.review, {
loading: action.state,
}),
});
default: default:
// Ignore the initialization call to the reducer. By that we can // Ignore the initialization call to the reducer. By that we can
// catch all actions that are not implemented // catch all actions that are not implemented