Implement a summary screen
This commit is contained in:
parent
2c9a2e88c7
commit
4e0506f607
40
src/components/SummaryTable.tsx
Normal file
40
src/components/SummaryTable.tsx
Normal 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>;
|
||||||
|
}
|
||||||
|
}
|
@ -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
8
src/models/review.ts
Normal 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;
|
||||||
|
};
|
@ -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)
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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>;
|
||||||
|
@ -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>
|
||||||
|
@ -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
60
src/pages/summary.tsx
Normal 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>;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user