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 LevelPage from "../pages/level";
import ReviewPage from "../pages/review";
import SummaryPage from "../pages/summary";
import { ILevel } from "../models/level";
import { IVocab } from "../models/vocab";
import { IVocab, VocabType } from "../models/vocab";
import { IReviewMetadata } from "../models/review";
interface IState {
loggedIn: boolean;
@ -71,6 +73,15 @@ export default class Application extends React.Component<{}, IState> {
return levels;
}
getLastReview(): IReviewMetadata {
// TODO: Actually fetch this
return {
correct: 5,
nearly: 5,
wrong: 5,
};
}
getLearners(): ILearner[] {
return [{
username: "Polynomdivision",
@ -104,23 +115,27 @@ export default class Application extends React.Component<{}, IState> {
latin: "Vinum",
german: "Wein",
hint: "Worte auf '-um' sind meistens NeutrUM"
type: VocabType.NOMEN,
id: 0
}, {
latin: "Vinum (Genitiv)",
german: "<Wortbedeutung>",
german: "Vini",
type: VocabType.NOMEN,
id: 1
}, {
latin: "Vici",
german: "<Wortbedeutung>",
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",
id: 2
}, {
latin: "fuga",
german: "Flucht",
hint: "Worte auf \"-a\" sind FeminA",
id: 3
}] as IVocab[];
}/* , {
* latin: "Vici",
* german: "<Wortbedeutung>",
* 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",
* type: VocabType.NOMEN,
* id: 2
}, {
* latin: "fuga",
* german: "Flucht",
* hint: "Worte auf \"-a\" sind FeminA",
* type: VocabType.NOMEN,
* id: 3
} */] as IVocab[];
}
login(username: string, password: string): Promise<boolean> {
@ -233,7 +248,12 @@ export default class Application extends React.Component<{}, IState> {
<AuthRoute
isAuth={this.isAuthenticated}
path="/dashboard"
component={() => <Dashboard nextLevel={this.getNextLevel} learners={this.getLearners()} />} />
component={() => {
return <Dashboard
nextLevel={this.getNextLevel}
lastReview={this.getLastReview}
learners={this.getLearners()} />
}} />
<AuthRoute
isAuth={this.isAuthenticated}
path="/levelList"
@ -257,6 +277,12 @@ export default class Application extends React.Component<{}, IState> {
return <Redirect to="/login" />;
}
}} />
<AuthRoute
isAuth={this.isAuthenticated}
path="/review/summary"
component={() => {
return <SummaryPage reviewMeta={this.getLastReview} />
}} />
</div>
</div>
</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,
}
export enum VocabType {
NOMEN,
VERB,
ADJEKTIV,
ADVERB
}
export interface IVocab {
german: string;
latin: string;
hint?: string;
mnemonic?: string;
type: VocabType;
// This number is lesson specific
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 Scoreboard from "../components/scoreboard";
import SummaryTable from "../components/SummaryTable";
import { ILevel } from "../models/level";
import { ILearner } from "../models/learner";
import { IReviewMetadata } from "../models/review";
interface IProps {
nextLevel: () => ILevel;
lastReview: () => IReviewMetadata;
learners: ILearner[];
}
@ -72,8 +75,11 @@ export default class Dashboard extends React.Component<IProps, IState> {
</Grid>
<Grid item lg={4}>
<Paper className="paper">
Some stuff
</Paper>
<Typography variant="title">
Letzte Wiederholung
</Typography>
<SummaryTable reviewMeta={this.props.lastReview()} />
</Paper>
</Grid>
</Grid>
</div>;

View File

@ -60,6 +60,15 @@ export default class LevelPage extends React.Component<IProps, IState> {
</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() {
const { currentVocab } = this.state;
@ -69,9 +78,7 @@ export default class LevelPage extends React.Component<IProps, IState> {
<List>
{this.props.levelVocab(this.props.id).map(this.renderVocabListItem)}
{/* TODO*/}
<ListItem button onClick={() => this.setState({
toReview: true,
})}>
<ListItem button onClick={this.toReview}>
<ListItemText>
Zur Übung
</ListItemText>

View File

@ -1,12 +1,17 @@
import * as React from "react";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import TextField from "@material-ui/core/TextField";
import Grid from "@material-ui/core/Grid";
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 {
levelId: number;
@ -16,25 +21,38 @@ interface IProps {
interface IState {
input: string;
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[] = [];
// Used for positioning the popover
private buttonRef: HTMLButtonElement;
constructor(props: any) {
super(props);
this.state = {
input: "",
current: 0,
toSummary: false,
popoverOpen: false,
popoverText: "",
popoverColor: "red",
};
const { vocabByLevel, levelId } = this.props;
this.vocab = vocabByLevel(levelId);
this.currentVocab = this.currentVocab.bind(this);
}
currentVocab() {
currentVocab = () => {
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
// TODO: Levensthein-Distance?
if (this.state.input === current.german) {
if (this.state.input.toLowerCase() === current.german.toLowerCase()) {
// TODO: Show it's correct
console.log("Hell yeah");
// Show the next vocab word
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 {
// Increase the vocab
this.setState({
current: this.state.current + 1,
input: "",
});
}
} else {
// TODO: Show it's wrong
console.log("Hell no");
this.setState({
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() {
return <div style={{ margin: 12 }}>
return <div>
{
this.state.toSummary ? (
<Redirect to="/review/summary" />
) : undefined
}
<Grid container justify="center">
<Grid item>
<Card>
<Grid container direction="column">
<h1>
{this.vocab[this.state.current].latin}
</h1>
<TextField
value={this.state.input}
onChange={(ev) => this.setState({
input: ev.target.value,
})} />
<Button onClick={this.checkInput}>
Prüfen
<CardContent>
<Grid container direction="column">
<Typography variant="display2">
{this.currentVocab().latin}
</Typography>
<TextField
margin="normal"
fullWidth={true}
value={this.state.input}
onChange={(ev) => this.setState({
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>
</Grid>
</Grid>
</CardContent>
</Card>
</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>;
}
}