Skip to content
Snippets Groups Projects
Commit c1e19eb2 authored by dominip89's avatar dominip89
Browse files

Merge branch '12-neuer-wissenssnack-neue-aktivitat' into 'master'

Resolve "Neuer Wissenssnack/Neue Aktivität"

Closes #12 and #11

See merge request swp-unisport/team-warumkeinrust/admin-frontend!8
parents 4ba7830c c2f79a36
No related branches found
No related tags found
No related merge requests found
...@@ -11,6 +11,7 @@ import { ScraperList } from './scraperList'; ...@@ -11,6 +11,7 @@ import { ScraperList } from './scraperList';
import { orderList } from './questionOrderList'; import { orderList } from './questionOrderList';
import { archiveList } from './archiveList'; import { archiveList } from './archiveList';
import { criteriaList } from './criteriaList'; import { criteriaList } from './criteriaList';
import { snackList, snackEdit, snackCreate } from './snackList';
import RestoreFromTrashIcon from '@material-ui/icons/RestoreFromTrash'; import RestoreFromTrashIcon from '@material-ui/icons/RestoreFromTrash';
import SportsKabaddiIcon from '@material-ui/icons/SportsKabaddi'; import SportsKabaddiIcon from '@material-ui/icons/SportsKabaddi';
...@@ -19,6 +20,8 @@ import SportsFootballIcon from '@material-ui/icons/SportsFootball'; ...@@ -19,6 +20,8 @@ import SportsFootballIcon from '@material-ui/icons/SportsFootball';
import ListAltIcon from '@material-ui/icons/ListAlt'; import ListAltIcon from '@material-ui/icons/ListAlt';
import YoutubeSearchedForIcon from '@material-ui/icons/YoutubeSearchedFor'; import YoutubeSearchedForIcon from '@material-ui/icons/YoutubeSearchedFor';
import FormatListNumberedIcon from '@material-ui/icons/FormatListNumbered'; import FormatListNumberedIcon from '@material-ui/icons/FormatListNumbered';
import RestaurantIcon from '@material-ui/icons/Restaurant';
import AnnouncementIcon from '@material-ui/icons/Announcement';
const App = () => ( const App = () => (
...@@ -88,6 +91,28 @@ const App = () => ( ...@@ -88,6 +91,28 @@ const App = () => (
options={{ label: 'Kriterienübersicht', "menuParent": "fragen" }} options={{ label: 'Kriterienübersicht', "menuParent": "fragen" }}
list={criteriaList} list={criteriaList}
/> />
<Resource name='snacks_n_co' options={{ "label": "Snacks & Co.", "isMenuParent": true }} />
<Resource
name='snack'
icon={RestaurantIcon}
options={{ label: 'Wissenssnacks', "menuParent": "snacks_n_co" }}
list={snackList}
edit={snackEdit}
create={snackCreate}
/>
<Resource
name='activity'
icon={AnnouncementIcon}
options={{ label: 'Aktivitäten', "menuParent": "snacks_n_co" }}
list={snackList}
edit={snackEdit}
create={snackCreate}
/>
</Admin> </Admin>
); );
......
import { stringify } from 'query-string';
import {
fetchUtils,
} from 'ra-core';
import dataProviderMapper from './dataProviderMapper';
/*
DEFAULT DATA PROVIDER TEMPLATE
Just for copy and pasting when other data providers are needed.
*/
// export {
// default as tokenAuthProvider,
// fetchJsonWithAuthToken,
// } from './tokenAuthProvider';
// export {
// default as jwtTokenAuthProvider,
// fetchJsonWithAuthJWTToken,
// } from './jwtTokenAuthProvider';
const getPaginationQuery = (pagination) => {
return {
page: pagination.page,
page_size: pagination.perPage,
};
};
const getFilterQuery = (filter) => {
const { q: search, ...otherSearchParams } = filter;
return {
...otherSearchParams,
search,
};
};
export const getOrderingQuery = (sort) => {
const { field, order } = sort;
return {
ordering: `${order === 'ASC' ? '' : '-'}${field}`,
};
};
export default (
apiUrl,
httpClient = fetchUtils.fetchJson
) => {
const getOneJson = (resource, id) =>
httpClient(`${apiUrl}/${resource}/${id}/`).then(
(response) => response.json
);
return {
getList: async (resource, params) => {
const query = {
...getFilterQuery(params.filter),
...getPaginationQuery(params.pagination),
...getOrderingQuery(params.sort),
};
const url = `${apiUrl}/${resource}/?${stringify(query)}`;
const { json } = await httpClient(url);
return {
data: json.results,
total: json.count,
};
},
getOne: async (resource, params) => {
const data = await getOneJson(resource, params.id);
return {
data
};
},
getMany: (resource, params) => {
return Promise.all(
params.ids.map(id => getOneJson(resource, id))
).then(data => ({ data }));
},
getManyReference: async (resource, params) => {
const query = {
...getFilterQuery(params.filter),
...getPaginationQuery(params.pagination),
...getOrderingQuery(params.sort),
[params.target]: params.id,
};
const url = `${apiUrl}/${resource}/?${stringify(query)}`;
const { json } = await httpClient(url);
return {
data: json.results,
total: json.count,
};
},
update: async (resource, params) => {
let data = {
text_de: params.data.text_de,
text_en: params.data.text_en,
}
if (params.data.new_image !== undefined) {
let image = params.data.new_image;
let image64 = await convertFileToBase64(image);
let new_image_type = params.data.new_image.rawFile.type;
data = {
...data,
image_type: new_image_type,
image: image64
}
}
const { json } = await httpClient(`${apiUrl}/${resource}/${params.id}/`, {
method: 'PATCH',
body: JSON.stringify(
data
),
});
return { data: json };
},
updateMany: (resource, params) =>
Promise.all(
params.ids.map(id =>
httpClient(`${apiUrl}/${resource}/${id}/`, {
method: 'PATCH',
body: JSON.stringify(params.data),
})
)
).then(responses => ({ data: responses.map(({ json }) => json.id) })),
create: async (resource, params) => {
let data = {
text_de: params.data.text_de,
text_en: params.data.text_en,
}
if (params.data.new_image !== undefined) {
let image = params.data.new_image;
let image64 = await convertFileToBase64(image);
let new_image_type = params.data.new_image.rawFile.type;
data = {
...data,
image_type: new_image_type,
image: image64
}
}
const { json } = await httpClient(`${apiUrl}/${resource}/`, {
method: 'POST',
body: JSON.stringify(data),
});
return {
data: { ...json },
};
},
delete: (resource, params) =>
httpClient(`${apiUrl}/${resource}/${params.id}/`, {
method: 'DELETE',
}).then(() => ({ data: params.previousData })),
deleteMany: (resource, params) =>
Promise.all(
params.ids.map(id =>
httpClient(`${apiUrl}/${resource}/${id}/`, {
method: 'DELETE',
})
)
).then(responses => ({ data: responses.map(({ json }) => json.id) })),
};
};
const convertFileToBase64 = file =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file.rawFile);
});
\ No newline at end of file
...@@ -2,6 +2,7 @@ import drfProvider from 'ra-data-django-rest-framework'; ...@@ -2,6 +2,7 @@ import drfProvider from 'ra-data-django-rest-framework';
import sportIncompleteProvider from './sportIncompleteProvider.js'; import sportIncompleteProvider from './sportIncompleteProvider.js';
import scraperDataProvider from './scraperDataProvider.js'; import scraperDataProvider from './scraperDataProvider.js';
import questionOrderProvider from './questionOrderProvider.js'; import questionOrderProvider from './questionOrderProvider.js';
import activitySnackProvider from './activitySnackProvider.js';
import { import {
GET_LIST, GET_LIST,
GET_ONE, GET_ONE,
...@@ -33,6 +34,10 @@ const dataProviders = [ ...@@ -33,6 +34,10 @@ const dataProviders = [
{ {
dataProvider: questionOrderProvider('http://localhost:8000/api/admin'), dataProvider: questionOrderProvider('http://localhost:8000/api/admin'),
resources: ['question-order'], resources: ['question-order'],
},
{
dataProvider: activitySnackProvider('http://localhost:8000/api/admin'),
resources: ['snack', 'activity'],
} }
]; ];
......
import * as React from 'react';
import {
List,
Datagrid,
TextField,
Edit,
SimpleForm,
BooleanField,
TextInput,
ImageInput,
ImageField,
Create,
required
} 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 AsideSnackList = () => (
<div style={{ width: 400, margin: '1em' }}>
<Card>
<CardContent>
<Typography
variant="h4"
align="center"
>
Tipps&Tricks
</Typography>
<br />
<ul>
<li>Klicke auf eine Datenreihe, um sie zu bearbeiten.</li>
<br />
<li>Wissenssnacks/Aktivitäten müssen eine deutsche Übersetzung und können eine englische Übersetzung haben.</li>
<br />
</ul>
</CardContent>
</Card>
</div>
);
const AsideSnackEdit = () => (
<div style={{ width: 400, margin: '1em' }}>
<Card>
<CardContent>
<Typography
variant="h4"
align="center"
>
Tipps&Tricks
</Typography>
<br />
<ul>
<li>Klicke auf eine Datenreihe, um sie zu bearbeiten.</li>
<br />
<li>Wissenssnacks/Aktivitäten müssen eine deutsche Übersetzung und können eine englische Übersetzung haben.</li>
<br />
<li>Es wird das Bild angezeigt, was momentan im Quiz benutzt wird. Wenn ein neues Bild hochgeladen wird, so wird das Alte überschrieben.</li>
<br />
</ul>
</CardContent>
</Card>
</div>
);
const AsideSnackCreate = () => (
<div style={{ width: 400, margin: '1em' }}>
<Card>
<CardContent>
<Typography
variant="h4"
align="center"
>
Tipps&Tricks
</Typography>
<br />
<ul>
<li>Klicke auf eine Datenreihe, um sie zu bearbeiten.</li>
<br />
<li>Wissenssnacks/Aktivitäten müssen eine deutsche Übersetzung und können eine englische Übersetzung haben.</li>
<br />
<li>Es kann außerdem das Bild hochgeladen werden, was im Quiz für diesen Snack/diese Aktivität angezeigt werden soll.</li>
<br />
</ul>
</CardContent>
</Card>
</div>
);
export const snackList = props => (
<List {...props}
mutationMode={"pessimistic"}
aside={<AsideSnackList />}
>
<Datagrid rowClick="edit">
<TextField
source="id"
label="Id"
sortable={false}
/>
<TextField
source="text_de"
label="Deutscher Text"
sortable={false}
/>
<TextField
source="text_en"
label="Englischer Text"
sortable={false}
/>
<BooleanField
source="has_image"
label="Hat Bild"
sortable={false}
/>
</Datagrid>
</List>
);
export const snackEdit = props => (
<Edit {...props}
mutationMode={"pessimistic"}
aside={<AsideSnackEdit />}
>
<SimpleForm>
<TextField
source="id"
label="ID"
/>
<TextInput
source="text_de"
label="Deutsche Übersetzung"
validate={[required("Deutsche Übersetzung ist benötigt")]}
fullWidth
/>
<TextInput
source="text_en"
label="Englische Übersetzung"
fullWidth
/>
<ImageField
source="image_url"
label="Momentanes Bild"
/>
<ImageInput
source="new_image"
accept="image/*"
label="Bild">
<ImageField
source="src"
title="Ausgewähltes Bild"
/>
</ImageInput>
</SimpleForm>
</Edit>
);
export const snackCreate = props => (
<Create {...props}
mutationMode={"pessimistic"}
aside={<AsideSnackCreate />}
>
<SimpleForm
redirect="list"
>
<TextInput
source="text_de"
label="Deutsche Übersetzung"
fullWidth
validate={[required("Deutsche Übersetzung ist benötigt")]}
/>
<TextInput
source="text_en"
label="Englische Übersetzung"
fullWidth
/>
<ImageInput
source="new_image"
accept="image/*"
label="Bild"
>
<ImageField
source="src"
title="Ausgewähltes Bild"
/>
</ImageInput>
</SimpleForm>
</Create>
);
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment