Implement a summary screen

This commit is contained in:
Alexander Polynomdivision 2018-09-12 19:23:00 +02:00
parent 2c9a2e88c7
commit 4e0506f607
8 changed files with 273 additions and 46 deletions

View File

@ -0,0 +1,40 @@
import * as React from "react";
import Table from "@material-ui/core/Table";
import TableHead from "@material-ui/core/TableHead";
import TableBody from "@material-ui/core/TableBody";
import TableRow from "@material-ui/core/TableRow";
import TableCell from "@material-ui/core/TableCell";
import { IReviewMetadata } from "../models/review";
interface IProps {
reviewMeta: IReviewMetadata;
}
export default class SummaryTable extends React.Component<IProps> {
render() {
return <Table>
<TableHead>
<TableRow>
<TableCell>Antworten</TableCell>
<TableCell>Anzahl</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow>
<TableCell>Korrekt</TableCell>
<TableCell numeric>{this.props.reviewMeta.correct}</TableCell>
</TableRow>
<TableRow>
<TableCell>Fast richtig</TableCell>
<TableCell numeric>{this.props.reviewMeta.nearly}</TableCell>
</TableRow>
<TableRow>
<TableCell>Falsch</TableCell>
<TableCell numeric>{this.props.reviewMeta.wrong}</TableCell>
</TableRow>
</TableBody>
</Table>;
}
}

View File

@ -25,9 +25,11 @@ import LoginPage from "../pages/login";
import LevelListPage from "../pages/levelList"; import LevelListPage from "../pages/levelList";
import LevelPage from "../pages/level"; import LevelPage from "../pages/level";
import ReviewPage from "../pages/review"; import ReviewPage from "../pages/review";
import SummaryPage from "../pages/summary";
import { ILevel } from "../models/level"; import { ILevel } from "../models/level";
import { IVocab } from "../models/vocab"; import { IVocab, VocabType } from "../models/vocab";
import { IReviewMetadata } from "../models/review";
interface IState { interface IState {
loggedIn: boolean; loggedIn: boolean;
@ -71,6 +73,15 @@ export default class Application extends React.Component<{}, IState> {
return levels; return levels;
} }
getLastReview(): IReviewMetadata {
// TODO: Actually fetch this
return {
correct: 5,
nearly: 5,
wrong: 5,
};
}
getLearners(): ILearner[] { getLearners(): ILearner[] {
return [{ return [{
username: "Polynomdivision", username: "Polynomdivision",
@ -104,23 +115,27 @@ export default class Application extends React.Component<{}, IState> {
latin: "Vinum", latin: "Vinum",
german: "Wein", german: "Wein",
hint: "Worte auf '-um' sind meistens NeutrUM" hint: "Worte auf '-um' sind meistens NeutrUM"
type: VocabType.NOMEN,
id: 0 id: 0
}, { }, {
latin: "Vinum (Genitiv)", latin: "Vinum (Genitiv)",
german: "<Wortbedeutung>", german: "Vini",
type: VocabType.NOMEN,
id: 1 id: 1
}, { }/* , {
latin: "Vici", * latin: "Vici",
german: "<Wortbedeutung>", * german: "<Wortbedeutung>",
hint: "Wird \"Viki\" und nicht \"Vichi\" ausgesprochen", * hint: "Wird \"Viki\" und nicht \"Vichi\" ausgesprochen",
mnemonic: "Merk dir das Wort mit Caesars berühmten Worten: \"Veni Vidi Vici\"; Er kam, sah und siegte", * mnemonic: "Merk dir das Wort mit Caesars berühmten Worten: \"Veni Vidi Vici\"; Er kam, sah und siegte",
id: 2 * type: VocabType.NOMEN,
}, { * id: 2
latin: "fuga", }, {
german: "Flucht", * latin: "fuga",
hint: "Worte auf \"-a\" sind FeminA", * german: "Flucht",
id: 3 * hint: "Worte auf \"-a\" sind FeminA",
}] as IVocab[]; * type: VocabType.NOMEN,
* id: 3
} */] as IVocab[];
} }
login(username: string, password: string): Promise<boolean> { login(username: string, password: string): Promise<boolean> {
@ -233,7 +248,12 @@ export default class Application extends React.Component<{}, IState> {
<AuthRoute <AuthRoute
isAuth={this.isAuthenticated} isAuth={this.isAuthenticated}
path="/dashboard" path="/dashboard"
component={() => <Dashboard nextLevel={this.getNextLevel} learners={this.getLearners()} />} /> component={() => {
return <Dashboard
nextLevel={this.getNextLevel}
lastReview={this.getLastReview}
learners={this.getLearners()} />
}} />
<AuthRoute <AuthRoute
isAuth={this.isAuthenticated} isAuth={this.isAuthenticated}
path="/levelList" path="/levelList"
@ -257,6 +277,12 @@ export default class Application extends React.Component<{}, IState> {
return <Redirect to="/login" />; return <Redirect to="/login" />;
} }
}} /> }} />
<AuthRoute
isAuth={this.isAuthenticated}
path="/review/summary"
component={() => {
return <SummaryPage reviewMeta={this.getLastReview} />
}} />
</div> </div>
</div> </div>
</BrowserRouter>; </BrowserRouter>;

8
src/models/review.ts Normal file
View File

@ -0,0 +1,8 @@
export interface IReviewMetadata {
// Number of correct answers
correct: number;
// Number of wrong answers
wrong: number;
// Number of nearly correct answers
nearly: number;
};

View File

@ -3,12 +3,28 @@ export enum ReviewMode {
LAT_TO_GER, LAT_TO_GER,
} }
export enum VocabType {
NOMEN,
VERB,
ADJEKTIV,
ADVERB
}
export interface IVocab { export interface IVocab {
german: string; german: string;
latin: string; latin: string;
hint?: string; hint?: string;
mnemonic?: string; mnemonic?: string;
type: VocabType;
// This number is lesson specific // This number is lesson specific
id: number; id: number;
}; };
export const typeToPoints = (VocabType type) => {
switch (type) {
// Nomen: 2P + 1 (Wenn richtig)
//
}
}

View File

@ -8,12 +8,15 @@ import Button from "@material-ui/core/Button";
import Paper from "@material-ui/core/Paper"; import Paper from "@material-ui/core/Paper";
import Scoreboard from "../components/scoreboard"; import Scoreboard from "../components/scoreboard";
import SummaryTable from "../components/SummaryTable";
import { ILevel } from "../models/level"; import { ILevel } from "../models/level";
import { ILearner } from "../models/learner"; import { ILearner } from "../models/learner";
import { IReviewMetadata } from "../models/review";
interface IProps { interface IProps {
nextLevel: () => ILevel; nextLevel: () => ILevel;
lastReview: () => IReviewMetadata;
learners: ILearner[]; learners: ILearner[];
} }
@ -72,8 +75,11 @@ export default class Dashboard extends React.Component<IProps, IState> {
</Grid> </Grid>
<Grid item lg={4}> <Grid item lg={4}>
<Paper className="paper"> <Paper className="paper">
Some stuff <Typography variant="title">
</Paper> Letzte Wiederholung
</Typography>
<SummaryTable reviewMeta={this.props.lastReview()} />
</Paper>
</Grid> </Grid>
</Grid> </Grid>
</div>; </div>;

View File

@ -60,6 +60,15 @@ export default class LevelPage extends React.Component<IProps, IState> {
</ListItem>; </ListItem>;
} }
toReview = () => {
// Only go to the review if all vocabulary item have been looked at
if (this.props.levelVocab().length === this.state.lookedAt.length) {
this.setState({
toReview: true,
});
}
}
render() { render() {
const { currentVocab } = this.state; const { currentVocab } = this.state;
@ -69,9 +78,7 @@ export default class LevelPage extends React.Component<IProps, IState> {
<List> <List>
{this.props.levelVocab(this.props.id).map(this.renderVocabListItem)} {this.props.levelVocab(this.props.id).map(this.renderVocabListItem)}
{/* TODO*/} {/* TODO*/}
<ListItem button onClick={() => this.setState({ <ListItem button onClick={this.toReview}>
toReview: true,
})}>
<ListItemText> <ListItemText>
Zur Übung Zur Übung
</ListItemText> </ListItemText>

View File

@ -1,12 +1,17 @@
import * as React from "react"; import * as React from "react";
import Card from "@material-ui/core/Card"; import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import TextField from "@material-ui/core/TextField"; import TextField from "@material-ui/core/TextField";
import Grid from "@material-ui/core/Grid"; import Grid from "@material-ui/core/Grid";
import Button from "@material-ui/core/Button"; import Button from "@material-ui/core/Button";
import Typography from "@material-ui/core/Button"; import Typography from "@material-ui/core/Typography";
import Popover from "@material-ui/core/Popover";
import Paper from "@material-ui/core/Paper";
import { IVocab, ReviewMode } from "../models/vocab"; import { Redirect } from "react-router-dom";
import { IVocab, ReviewMode, VocabType } from "../models/vocab";
interface IProps { interface IProps {
levelId: number; levelId: number;
@ -16,25 +21,38 @@ interface IProps {
interface IState { interface IState {
input: string; input: string;
current: number; current: number;
toSummary: boolean;
popoverOpen: boolean;
popoverText: string;
popoverColor: string;
} }
export default class ReviewPage extends React.Component<IProps> { export default class ReviewPage extends React.Component<IProps, IState> {
private vocab: IVocab[] = []; private vocab: IVocab[] = [];
// Used for positioning the popover
private buttonRef: HTMLButtonElement;
constructor(props: any) { constructor(props: any) {
super(props); super(props);
this.state = { this.state = {
input: "", input: "",
current: 0, current: 0,
toSummary: false,
popoverOpen: false,
popoverText: "",
popoverColor: "red",
}; };
const { vocabByLevel, levelId } = this.props; const { vocabByLevel, levelId } = this.props;
this.vocab = vocabByLevel(levelId); this.vocab = vocabByLevel(levelId);
this.currentVocab = this.currentVocab.bind(this);
} }
currentVocab() { currentVocab = () => {
return this.vocab[this.state.current]; return this.vocab[this.state.current];
} }
@ -43,45 +61,91 @@ export default class ReviewPage extends React.Component<IProps> {
// Check if the user's answer was correct // Check if the user's answer was correct
// TODO: Levensthein-Distance? // TODO: Levensthein-Distance?
if (this.state.input === current.german) { if (this.state.input.toLowerCase() === current.german.toLowerCase()) {
// TODO: Show it's correct // TODO: Show it's correct
console.log("Hell yeah");
// Show the next vocab word // Show the next vocab word
if (this.state.current + 1 >= this.vocab.length) { if (this.state.current + 1 >= this.vocab.length) {
// TODO: Go to a summary screen // TODO: Set some data that the summary screen will show
this.setState({
toSummary: true,
});
} else { } else {
// Increase the vocab // Increase the vocab
this.setState({ this.setState({
current: this.state.current + 1, current: this.state.current + 1,
input: "",
}); });
} }
} else { } else {
// TODO: Show it's wrong this.setState({
console.log("Hell no"); popoverOpen: true,
popoverText: "Das war nicht richtig",
popoverColor: "red",
// TODO: Or maybe don't reset the text
input: "",
});
} }
// TODO: Show a snackbar for showing the updated score // TODO(?): Show a snackbar for showing the updated score
} }
render() { render() {
return <div style={{ margin: 12 }}> return <div>
{
this.state.toSummary ? (
<Redirect to="/review/summary" />
) : undefined
}
<Grid container justify="center"> <Grid container justify="center">
<Grid item> <Grid item>
<Card> <Card>
<Grid container direction="column"> <CardContent>
<h1> <Grid container direction="column">
{this.vocab[this.state.current].latin} <Typography variant="display2">
</h1> {this.currentVocab().latin}
<TextField </Typography>
value={this.state.input} <TextField
onChange={(ev) => this.setState({ margin="normal"
input: ev.target.value, fullWidth={true}
})} /> value={this.state.input}
<Button onClick={this.checkInput}> onChange={(ev) => this.setState({
Prüfen input: ev.target.value,
})}
onKeyPress={(ev) => {
// Allow checking of the answer by pressing Enter
if (ev.key === "Enter")
this.checkInput();
}} />
<Popover
open={this.state.popoverOpen}
anchorOrigin={{
vertical: "center",
horizontal: "left"
}}
transformOrigin={{
vertical: "bottom",
horizontal: "left"
}}
anchorEl={this.buttonRef}
onClose={() => this.setState({
popoverOpen: false,
})}
PaperProps={{
style: {
backgroundColor: this.state.popoverColor,
padding: 10,
color: "white"
}
}}>
<Typography variant="button" color="inherit">
{this.state.popoverText}
</Typography>
</Popover>
<Button onClick={this.checkInput} buttonRef={node => this.buttonRef = node}>
Prüfen
</Button> </Button>
</Grid> </Grid>
</CardContent>
</Card> </Card>
</Grid> </Grid>
</Grid> </Grid>

60
src/pages/summary.tsx Normal file
View File

@ -0,0 +1,60 @@
import * as React from "react";
import Typography from "@material-ui/core/Typography";
import Paper from "@material-ui/core/Paper";
import Grid from "@material-ui/core/Grid";
import Button from "@material-ui/core/Button";
import SummaryTable from "../components/SummaryTable";
import { Redirect } from "react-router-dom";
import { IReviewMetadata } from "../models/review";
interface IProps {
reviewMeta: () => IReviewMetadata;
}
interface IState {
toDashboard: boolean;
}
export default class SummaryPage extends React.Component<IProps, IState> {
constructor(props: any) {
super(props);
this.state = {
toDashboard: false,
};
}
render() {
return <div>
{
this.state.toDashboard ? (
<Redirect to="/dashboard" />
) : undefined
}
<Grid
container
spacing={0}
direction="column"
alignItems="center"
justify="center"
style={{ minHeight: '100vh' }}>
<Grid item xs={12}>
<Paper className="paper">
<Typography variant="title">Zusammenfassung</Typography>
<Grid container direction="column">
<SummaryTable reviewMeta={this.props.reviewMeta} />
<Button onClick={() => this.setState({
toDashboard: true,
})}>
Zum Dashboard
</Button>
</Grid>
</Paper>
</Grid>
</Grid>
</div>;
}
}