Skip to content
Snippets Groups Projects
Commit 4ba7830c authored by fu2662cw's avatar fu2662cw :speech_balloon:
Browse files

Merge branch '8-archivierte-sportarten' into 'master'

Added Sport Archive and Criteria View

Closes #6 and #8

See merge request swp-unisport/team-warumkeinrust/admin-frontend!7
parents 98f51585 806cb24a
No related branches found
No related tags found
No related merge requests found
......@@ -1178,6 +1178,11 @@
"to-fast-properties": "^2.0.0"
}
},
"@bb-tech/ra-treemenu": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@bb-tech/ra-treemenu/-/ra-treemenu-1.0.5.tgz",
"integrity": "sha512-uYRWfGBmgSd5/qcBC560NreX6OVCNw8x+lKyvmqFtVqpCX2M0OH5mQx7IV6jZMRK3L5G2lZVLgwNxHNoGFB/Sw=="
},
"@bcoe/v8-coverage": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
......
......@@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@bb-tech/ra-treemenu": "^1.0.5",
"@testing-library/jest-dom": "^5.12.0",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
......@@ -17,7 +18,7 @@
"web-vitals": "^1.1.2"
},
"scripts": {
"start": "react-scripts start",
"start": "PORT=4000 react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
......@@ -40,4 +41,4 @@
"last 1 safari version"
]
}
}
}
\ No newline at end of file
......@@ -2,13 +2,18 @@ import * as React from 'react';
import { Admin, Resource } from 'react-admin';
import dataProviderMapper from './dataProviderMapper';
import TreeMenu from '@bb-tech/ra-treemenu';
import { SportList, SportEdit, SportCreate } from './sportList';
import { IncompleteList, IncompleteEdit } from './incompleteList';
import { QuestionList, QuestionEdit, QuestionCreate } from './questionList';
import { ScraperList } from './scraperList';
import { orderList } from './questionOrderList';
import { archiveList } from './archiveList';
import { criteriaList } from './criteriaList';
import RestoreFromTrashIcon from '@material-ui/icons/RestoreFromTrash';
import SportsKabaddiIcon from '@material-ui/icons/SportsKabaddi';
import QuestionAnswerIcon from '@material-ui/icons/QuestionAnswer';
import SportsFootballIcon from '@material-ui/icons/SportsFootball';
import ListAltIcon from '@material-ui/icons/ListAlt';
......@@ -20,47 +25,69 @@ const App = () => (
<Admin
dataProvider={dataProviderMapper} // A custom mapper is used because different resources need different dataProviders
disableTelemetry
menu={TreeMenu}
>
<Resource
name='sportarten'
options={{ "label": "Sportarten", "isMenuParent": true }}
/>
<Resource
name='sport' // name of the API endpoint
icon={SportsFootballIcon}
options={{ label: 'Sport Management' }} // if we do not rename the resource, the API name will be used
options={{ label: 'Management', "menuParent": "sportarten" }} // if we do not rename the resource, the API name will be used
list={SportList}
edit={SportEdit}
create={SportCreate}
/>
<Resource
name='sport-scraper'
icon={YoutubeSearchedForIcon}
options={{ label: 'Buchsys Auslesen', "menuParent": "sportarten" }}
list={ScraperList}
/>
<Resource
name='sport-incomplete'
icon={ListAltIcon}
options={{ label: 'Incomplete Sports' }}
options={{ label: 'Fehlende Kriterien', "menuParent": "sportarten" }}
list={IncompleteList}
edit={IncompleteEdit}
/>
<Resource
name='sport-archive'
icon={RestoreFromTrashIcon}
options={{ label: 'Archiv', "menuParent": "sportarten" }}
list={archiveList}
/>
<Resource name='fragen' options={{ "label": "Fragen", "isMenuParent": true }} />
<Resource
name='question'
icon={QuestionAnswerIcon}
options={{ label: 'Question Management' }}
options={{ label: 'Management', "menuParent": "fragen" }}
list={QuestionList}
edit={QuestionEdit}
create={QuestionCreate}
/>
<Resource
name='sport-scraper'
icon={YoutubeSearchedForIcon}
options={{ label: 'Sportarten auslesen' }}
list={ScraperList}
/>
<Resource
name='question-order'
icon={FormatListNumberedIcon}
options={{ label: 'Fragenreihenfolge' }}
options={{ label: 'Reihenfolge', "menuParent": "fragen" }}
list={orderList}
/>
<Resource
name='criteria'
icon={SportsKabaddiIcon}
options={{ label: 'Kriterienübersicht', "menuParent": "fragen" }}
list={criteriaList}
/>
</Admin>
);
......
import {
List,
Datagrid,
UrlField,
TextField,
DateField,
Button,
useDataProvider,
useNotify,
useRefresh,
} from 'react-admin';
import Typography from '@material-ui/core/Typography'
import Card from '@material-ui/core/Card'
import CardContent from '@material-ui/core/CardContent'
const ArchiveCriterionList = () => (
<div style={{
width: 400,
margin: '1em'
}}>
<Card>
<CardContent>
<Typography
variant="h4"
align="center"
>
Tipps&Tricks
</Typography>
<br />
<ul>
<li>Dies ist das Archiv für inaktive Sportarten.</li>
<br />
<li>Dazu zählen Sportarten, die beim letzten Auslesen von <a href="https://www.buchsys.de/fu-berlin/angebote/aktueller_zeitraum/index.html">buchsys.de</a> nicht mehr erkannt worden sind und deswegen aus der Liste von aktiven Sportarten genommen wurden.</li>
<br />
<li>Die Sportarten können trotzdem manuell per Knopfdruck wieder der aktiven Sportartenliste zugeordnet werden.</li>
<br />
</ul>
</CardContent>
</Card>
</div>
);
const ActivateButton = props => {
const notify = useNotify();
const refresh = useRefresh();
const dataProvider = useDataProvider();
let id = props.record.id;
const activate_sport = props => {
dataProvider.update('sport', { id: id, data: { currently_active: true } }).then(
response => {
notify("Erfolgreich Aktiviert!");
refresh();
}).catch(error => {
notify("Etwas ist schief gelaufen!", "warning");
});
}
return <Button label="Aktivieren!" onClick={activate_sport} {...props} />
}
export const archiveList = props => (
<List {...props}
aside={<ArchiveCriterionList />}
bulkActionButtons={false}>
<Datagrid>
<TextField source="id" sortable={false} />
<TextField source="name" sortable={false} />
<UrlField source="url" sortable={false} />
<DateField source="last_used" sortable={false} />
<ActivateButton />
</Datagrid>
</List>
);
import {
List,
Datagrid,
TextField,
ReferenceField,
ChipField
} from 'react-admin';
import Typography from '@material-ui/core/Typography'
import Card from '@material-ui/core/Card'
import CardContent from '@material-ui/core/CardContent'
const AsideCriterionList = () => (
<div style={{
width: 400,
margin: '1em'
}}>
<Card>
<CardContent>
<Typography
variant="h4"
align="center"
>
Tipps&Tricks
</Typography>
<br />
<ul>
<li>Hier wird jedes Kriterium aufgelistet</li>
<br />
<ul>
<li><i>Anzahl von aktiven Sportarten</i>: <br />Gibt an, wie viele Sportarten ein Rating größer als 1 für dieses Kriterium haben.</li>
<br />
<li><i>Summe der Gewichte</i>: <br />Gibt die Summe der Gewichte von aktiven Sportarten für dieses Kriterium an.</li>
<br />
</ul>
</ul>
</CardContent>
</Card>
</div>
);
export const criteriaList = props => (
<List {...props}
aside={<AsideCriterionList />}
bulkActionButtons={false}>
<Datagrid>
<TextField source="id" sortable={false} />
<TextField source="name" sortable={false} />
<TextField
source="number_of_sports_active"
label="Anzahl von aktiven Sportarten"
sortable={false} />
<TextField
source="sum_of_weights"
label="Summe der Gewichte"
sortable={false} />
<ReferenceField source="question_id" reference="question" label="Frage" sortable={false}>
<ChipField source="text_de" sortable={false} />
</ReferenceField>
</Datagrid>
</List>
);
......@@ -20,7 +20,7 @@ import {
const dataProviders = [
{
dataProvider: drfProvider('http://localhost:8000/api/admin'),
resources: ['sport', 'question'],
resources: ['sport', 'question', 'sport-archive', 'criteria'],
},
{
dataProvider: sportIncompleteProvider('http://localhost:8000/api/admin'),
......
......@@ -117,6 +117,7 @@ export const IncompleteEdit = props => {
{...props}
mutationMode="pessimistic"
onSuccess={onSuccess}
title={" "}
>
<SimpleForm toolbar={<IncompleteEditToolbar />}>
......
......@@ -6,7 +6,8 @@ import {
Edit,
SimpleForm,
TextInput,
Create
Create,
NumberField
} from 'react-admin';
import Typography from '@material-ui/core/Typography'
......@@ -31,6 +32,8 @@ const AsideQuestionList = () => (
<li>Fragen müssen eine deutsche Übersetzung und können eine englische Übersetzung haben.</li>
<br />
<li>Kriterien sind einzigartig. Das heißt, dass ein Kriterium nicht doppelt vergeben werden kann!</li>
<br />
<li>Wenn eine Frage mit ihrem Kriterium gelöscht wird, gehen auch alle Gewichtungen zu diesem Kriterium verloren!</li>
</ul>
</CardContent>
......@@ -110,7 +113,14 @@ export const QuestionList = props => (
>
<Datagrid
rowClick="edit"
style={{ tableLayout: "fixed", }}>
>
<NumberField
source="id"
label="ID"
sortable={false}
/>
<TextField
source="text_de"
label="Deutscher Fragetext"
......@@ -135,7 +145,7 @@ export const QuestionList = props => (
export const QuestionEdit = props => (
<Edit
{...props}
mutationMode="pessimistic"
mutationMode="undoable"
aside={<AsideQuestionEdit />}
>
<SimpleForm>
......
......@@ -16,9 +16,13 @@ import {
RadioButtonGroupInput,
useRefresh,
useListContext,
Pagination
Pagination,
} from 'react-admin';
import Chip from '@material-ui/core/Chip';
import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
......@@ -162,6 +166,37 @@ const DirEditButton = props => {
return <Button label={props.custom_label} onClick={action} />
}
const useStyles = makeStyles({
question: { color: 'white', background: 'blue' },
snack: { color: 'black', background: 'lightGreen' },
activity: { color: 'white', background: 'darkRed' },
});
const QuestionTypeChip = props => {
const classes = useStyles();
if (props.record === undefined) {
return null;
};
let verbose_type = "";
if (props.record.type === "question") {
verbose_type = "Frage";
} else if (props.record.type === "snack") {
verbose_type = "Wissenssnack";
} else {
verbose_type = "Aktivität";
};
return (
<Chip label={verbose_type} className={classes[props.record.type]} />
);
};
const QuestionOrderPagination = props => <Pagination rowsPerPageOptions={[]} {...props} />;
export const orderList = props => (
......@@ -174,9 +209,9 @@ export const orderList = props => (
pagination={<QuestionOrderPagination />}
perPage={1000}
bulkActionButtons={false}
empty={false}
>
<Datagrid
rowClick="expand"
expand={orderEdit}
>
<TextField
......@@ -184,11 +219,12 @@ export const orderList = props => (
source="id"
sortable={false}
/>
<TextField
<QuestionTypeChip
label="Eintragstyp"
source="type"
sortable={false}
/>
<TextField
label="Fragen-ID"
source="question_id"
......@@ -261,7 +297,7 @@ const CategorySensitiveQuestionField = (props) => {
export const orderEdit = props => (
<Edit {...props} mutationMode={"optimistic"}>
<Edit {...props} mutationMode={"optimistic"} title={" "}>
<SimpleForm toolbar={<OrderEditToolbar />}>
<RadioButtonGroupInput
......
......@@ -61,8 +61,6 @@ export default (
...getOrderingQuery(params.sort),
};
console.log("cached_order in getList is: ", cached_order)
if (cached_order == undefined) {
const url = `${apiUrl}/${resource}/?${stringify(query)}`;
......@@ -162,8 +160,6 @@ export default (
cached_order.data[index].id = index + 1;
}
console.log(cached_order);
return { data: {} };
},
......
......@@ -2,11 +2,16 @@ import {
List,
Datagrid,
TextField,
NumberField,
DateField,
Pagination,
ReferenceField,
ChipField
} from 'react-admin';
import Chip from '@material-ui/core/Chip';
import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
......@@ -31,10 +36,10 @@ const AsideSportScrape = () => (
</p>
<p>Diese werden verglichen mit den Sportarten, die momentan in der Sportliste stehen. Hierbei kann es zu vier verschiedenen Fällen kommen:</p>
<ul>
<li><i>Same:</i> Der gefundene Sport steht bereits in der Datenbank und wird bis auf die URL nicht aktualisiert.</li>
<li><i>New:</i> Der gefundene Sport existiert in der Datenbank noch nicht und wird vollkommen neu angelegt. Die Kriterien müssen hier noch ausgefüllt werden.</li>
<li><i>From Archive:</i> Der gefundene Sport existiert zwar, liegt momentan aber in dem Archiv. Der Sport wird wieder aktiviert werden, und die alten Kriterienbewertungen werden von diesem Sport übernommen werden. </li>
<li><i>To be archived:</i> Es wurde kein neuer Sport gefunden, der dem Sport in der Datenbank zu gleichen scheint, also wird der Sport in das Archiv geschoben, um nicht mehr als Sportempfehlung angezeigt zu werden.</li>
<li><i style={{ color: 'black', background: 'lightGrey' }}>Gleicher Sport:</i> <br /> Der gefundene Sport steht bereits in der Datenbank und wird bis auf die URL nicht aktualisiert.</li>
<li><i style={{ color: 'black', background: '#A5FF7E' }}>Neuer Sport:</i> <br /> Der gefundene Sport existiert in der Datenbank noch nicht und wird vollkommen neu angelegt. Die Kriterien müssen hier noch ausgefüllt werden.</li>
<li><i style={{ color: 'black', background: '#FFFB8C' }}>Sport aus Archiv:</i> <br /> Der gefundene Sport existiert zwar, liegt momentan aber in dem Archiv. Der Sport wird wieder aktiviert werden, und die alten Kriterienbewertungen werden von diesem Sport übernommen werden. </li>
<li><i style={{ color: 'black', background: '#FF8EAA' }}>Sport ins Archiv:</i> <br /> Es wurde kein neuer Sport gefunden, der dem Sport in der Datenbank zu gleichen scheint, also wird der Sport in das Archiv geschoben, um nicht mehr als Sportempfehlung angezeigt zu werden.</li>
</ul>
</li>
<li>
......@@ -46,6 +51,40 @@ const AsideSportScrape = () => (
</div>
);
const useStyles = makeStyles({
same: { color: 'black', background: 'lightGrey' },
new: { color: 'black', background: '#A5FF7E' },
from_archive: { color: 'black', background: '#FFFB8C' },
to_be_archived: { color: 'black', background: '#FF8EAA' },
});
const DiffTypeChip = props => {
const classes = useStyles();
let verbose_type = "";
if (props.record.kind_of_diff === "new") {
verbose_type = "Neuer Sport";
} else if (props.record.kind_of_diff === "same") {
verbose_type = "Gleicher Sport";
} else if (props.record.kind_of_diff === "from_archive") {
verbose_type = "Sport aus Archiv";
} else {
verbose_type = "Sport ins Archiv";
};
return (
<Chip label={verbose_type} className={classes[props.record.kind_of_diff]} />
);
};
const ScraperPagination = props => <Pagination rowsPerPageOptions={[]} {...props} />;
/* bulkActionButtons should theoretically be able to take a whole Fragment worth of buttons, but React hates us and we return the feeling*/
......@@ -66,19 +105,24 @@ export const ScraperList = props => (
source="id"
sortable={false}
/>
<TextField
<DiffTypeChip
source="kind_of_diff"
label="Art der Differenz"
sortable={false}
/>
<NumberField
source="old_sport.id"
label="Alte ID"
sortable={false}
/>
<ReferenceField reference="sport" source="old_sport.id" label="Sport ID">
<ChipField
source="id"
label="Alte ID"
sortable={false}
/>
</ReferenceField>
<DateField
source="old_sport.last_used"
label="Zuletzt aktiviert"
locales="fr-FR"
sortable={false}
/>
<TextField
......
......@@ -43,7 +43,7 @@ const AsideSportList = () => (
<br />
<li>Neue Sportarten können durch "Create" hinzugefügt werden.</li>
<br />
<li>Die Sportarten können inkl. ihrer Kriterienwertungen über "Export" als .csv-Datei heruntergeladen werden.</li> {/* Das Implementieren */}
<li>Die Sportarten können (momentan noch ohne Kriterien) über "Export" als .csv-Datei heruntergeladen werden.</li> {/* Das Implementieren */}
</ul>
</CardContent>
......@@ -134,7 +134,11 @@ export const SportList = props => (
export const SportEdit = props => (
<Edit {...props} aside={<AsideSportEdit />}>
<Edit
{...props}
aside={<AsideSportEdit />}
mutationMode='pessimistic'
>
<SimpleForm>
<Typography
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment