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
userRouter.post("/dashboard", async (req, res) => {
userRouter.get("/dashboard", async (req, res) => {
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
res.send({
error: "0",
error: "200",
data: {
nextLevel: {},
topTen: {},
lastReview: {},
nextLevel: {
name: "Test level",
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 function setTopTen(topTen: TopTen[]) {
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 function setDidLogin(state: boolean) {
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 function setReviewDialog(state: boolean) {
return {
@ -200,3 +176,11 @@ export function setReviewDialog(state: boolean) {
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> => {
return new Promise((res, rej) => {
fetch(`${BACKEND_URL}/api/login`, {
@ -233,9 +254,7 @@ export default class Application extends React.Component<IProps> {
path="/dashboard"
component={() => {
return <Dashboard
getNextLevel={this.getNextLevel}
getLastReview={this.getLastReview}
getTopTen={this.getTopTenLearners} />
getDashboard={this.getDashboard} />
}} />
<AuthRoute
isAuth={this.isAuthenticated}

View File

@ -1,8 +1,7 @@
import { connect } from "react-redux";
import {
setNextLevel, setDashboardNLLoading, setTopTen, setDashboardTTLoading,
setDashboardLRLoading, setLastReview,
setNextLevel, setTopTen, setLastReview, setDashboardLoading
} from "../actions";
import { ILevel } from "../models/level";
@ -13,22 +12,18 @@ import DashboardPage from "../pages/dashboard";
const mapStateToProps = state => {
return {
loading: state.review.loading,
nextLevel: state.nextLevel,
loadingNextLevel: state.dashboard.loadingNL,
loadingTopTen: state.dashboard.loadingTT,
topTen: state.topTen,
loadingLastReview: state.dashboard.loadingLR,
lastReview: state.lastReview,
};
};
const mapDispatchToProps = dispatch => {
return {
setLoadingNL: (state: boolean) => dispatch(setDashboardNLLoading(state)),
setLoading: (state: boolean) => dispatch(setDashboardLoading(state)),
setNextLevel: (level: ILevel) => dispatch(setNextLevel(level)),
setTopTen: (topTen: ILearner[]) => dispatch(setTopTen(topTen)),
setLoadingTT: (state: boolean) => dispatch(setDashboardTTLoading(state)),
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";
interface IProps {
getNextLevel: () => Promise<ILevel>;
getLastReview: () => Promise<IReviewMetadata>;
getTopTen: () => Promise<ILearner[]>;
getDashboard: () => Promise<any>;
loading: boolean;
setLoading: (state: boolean) => void;
nextLevel: ILevel;
loadingNextLevel: boolean;
setLoadingNL: (state: boolean) => void;
setNextLevel: (level: ILevel) => void;
topTen: TopTen[];
loadingTopTen: boolean;
setLoadingTT: (state: boolean) => void;
setTopTen: (topten: TopTen[]) => void;
lastReview: IReviewMetadata;
loadingLastReview: boolean;
setLoadingLR: (state: boolean) => void;
setLastReview: (review: IReviewMetadata) => void;
}
export default class Dashboard extends React.Component<IProps> {
componentDidMount() {
this.props.setLoadingNL(true);
this.props.getNextLevel().then(res => {
this.props.setLoadingNL(false);
this.props.setNextLevel(res);
}, err => {
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");
});
this.props.setLoading(true);
this.props.getDashboard().then(res => {
this.props.setNextLevel(res.nextLevel);
this.props.setTopTen(res.topTen);
this.props.setLastReview(res.lastReview);
this.props.setLoading(false);
})
}
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 direction = small ? "column" : "row";
@ -70,63 +69,48 @@ export default class Dashboard extends React.Component<IProps> {
<Grid container direction={direction} spacing={16}>
<Grid item lg={4}>
<Paper className="paper">
{this.props.loadingNextLevel ? (
<CircularProgress />
) : (
<div>
<Typography variant="title">{`Level ${level.level}`}</Typography>
<Typography variant="title" component="p">{level.name}</Typography>
<br />
<Typography component="p">
{level.desc}
</Typography>
<Button
component={Link}
to={`/level/${level.level}`}
className="lesson-card-btn">
Zum Level
</Button>
</div>
)}
<div>
<Typography variant="title">{`Level ${level.level}`}</Typography>
<Typography variant="title" component="p">{level.name}</Typography>
<br />
<Typography component="p">
{level.desc}
</Typography>
<Button
component={Link}
to={`/level/${level.level}`}
className="lesson-card-btn">
Zum Level
</Button>
</div>
</Paper>
</Grid>
<Grid item lg={4}>
<Paper className="paper">
{this.props.loadingTopTen ? (
<CircularProgress />
) : (
<div>
<Typography variant="title" component="p">
Rangliste: Top 10
</Typography>
<div>
<Typography variant="title" component="p">
Rangliste: Top 10
</Typography>
<Scoreboard topTen={this.props.topTen} />
</div>
)}
<Scoreboard topTen={this.props.topTen} />
</div>
</Paper>
</Grid>
<Grid item lg={4}>
<Paper className="paper">
{
this.props.loadingLastReview ? (
<CircularProgress />
) : (
<div>
<Typography variant="title">
Letzte Wiederholung
</Typography>
<SummaryTable reviewMeta={this.props.lastReview} />
<div>
<Typography variant="title">
Letzte Wiederholung
</Typography>
<SummaryTable reviewMeta={this.props.lastReview} />
<Button
component={Link}
to="/review/queue"
className="lesson-card-btn">
Vokabeln üben
</Button>
</div>
)
}
<Button
component={Link}
to="/review/queue"
className="lesson-card-btn">
Vokabeln üben
</Button>
</div>
</Paper>
</Grid>
</Grid>

View File

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

View File

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