зеркало из https://github.com/mozilla/mentoring.git
Allow actually posting pairings
This commit is contained in:
Родитель
873819c468
Коммит
9adbcc5b67
2
TODO.txt
2
TODO.txt
|
@ -9,7 +9,7 @@ Stuff to do!
|
|||
* pairing
|
||||
* option to show existing pairings with "unpair" button
|
||||
* API method to create a pair
|
||||
* check in model that pair.mentor.role == MENTOR etc.
|
||||
* check in model that pair.mentor.role == MENTOR etc. and not already paired
|
||||
|
||||
* hardening
|
||||
* protect API with is_staff or something
|
||||
|
|
|
@ -6,6 +6,7 @@ import Toolbar from '@material-ui/core/Toolbar';
|
|||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import CogOutlineIcon from 'mdi-react/CogOutlineIcon';
|
||||
import CodeJsonIcon from 'mdi-react/CodeJsonIcon';
|
||||
import Home from './views/Home';
|
||||
import Pairing from './views/Pairing';
|
||||
import { ThemeProvider, createMuiTheme, makeStyles } from '@material-ui/core/styles';
|
||||
|
@ -78,6 +79,13 @@ export default function App(props) {
|
|||
</IconButton>
|
||||
</Tooltip>
|
||||
</a>
|
||||
<a href="/api/">
|
||||
<Tooltip placement="bottom" title="REST API">
|
||||
<IconButton>
|
||||
<CodeJsonIcon className={classes.appIcon} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</a>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<Switch>
|
||||
|
|
|
@ -2,18 +2,19 @@ import React, { Fragment } from "react";
|
|||
import LinearProgress from '@material-ui/core/LinearProgress';
|
||||
|
||||
// Show a spinner if any of the array of loads are still loading, or an error
|
||||
// if the load fails; otherwise show children.
|
||||
export default function Loading({ children, loads }) {
|
||||
// if the load fails; otherwise show children. errorOnly is similar, but ignoring
|
||||
// the loading state (for something that does not prevent rendering)
|
||||
export default function Loading({ children, loads, errorOnly }) {
|
||||
if (!loads.every(({ loading }) => !loading)) {
|
||||
return <LinearProgress />
|
||||
}
|
||||
|
||||
for (let {error} of loads) {
|
||||
for (let {error} of loads.concat(errorOnly)) {
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
<h2>Error</h2>
|
||||
{error}
|
||||
<pre>{error.toString()}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { Fragment, useState } from "react";
|
||||
import useAxios from 'axios-hooks'
|
||||
import Helmet from 'react-helmet';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import Button from '@material-ui/core/Button';
|
||||
|
@ -17,6 +18,7 @@ import CloseIcon from 'mdi-react/CloseIcon';
|
|||
import ChevronDownIcon from 'mdi-react/ChevronDownIcon';
|
||||
import Loading from '../components/Loading';
|
||||
import Participant from '../components/Participant';
|
||||
import LinearProgress from '@material-ui/core/LinearProgress';
|
||||
import { useParticipants } from '../data/participants';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
|
@ -58,7 +60,7 @@ function ParticipantColumn({ participants, title, filter, onSelect, selected })
|
|||
);
|
||||
}
|
||||
|
||||
function PairDrawer({ mentor, learner, open, onClose, onPair }) {
|
||||
function PairDrawer({ mentor, learner, open, pairing, onClose, onPair }) {
|
||||
const classes = useStyles();
|
||||
|
||||
// TODO: show time compatibility, shared interests, etc.
|
||||
|
@ -73,6 +75,7 @@ function PairDrawer({ mentor, learner, open, onClose, onPair }) {
|
|||
PaperProps={{ elevation: 4 }}>
|
||||
{open && (
|
||||
<Fragment>
|
||||
{pairing && <LinearProgress />}
|
||||
<DialogTitle>Proposed Pair: {mentor.full_name} / {learner.full_name}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
|
@ -94,24 +97,32 @@ function PairDrawer({ mentor, learner, open, onClose, onPair }) {
|
|||
}
|
||||
|
||||
export default function Home(props) {
|
||||
const [participants, refectParticipants] = useParticipants();
|
||||
const [participants, refecthParticipants] = useParticipants();
|
||||
const [mentor, setMentor] = useState(null);
|
||||
const [learner, setLearner] = useState(null);
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
|
||||
const [pair, postPairing] = useAxios({
|
||||
url: '/api/pairs',
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRFToken': csrftoken },
|
||||
}, { manual: true });
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Helmet>
|
||||
<title>Mozilla Mentorship Program - Pairing</title>
|
||||
</Helmet>
|
||||
<Loading loads={ [ participants ] }>
|
||||
<Loading loads={ [ participants ] } errorOnly={ [ pair ] }>
|
||||
<ParticipantColumn
|
||||
title="Mentors"
|
||||
participants={participants}
|
||||
filter={p => p.role == 'M'}
|
||||
onSelect={p => {
|
||||
setMentor(p);
|
||||
learner && setDrawerOpen(true);
|
||||
if (!pair.loading) {
|
||||
setMentor(p);
|
||||
learner && setDrawerOpen(true);
|
||||
}
|
||||
}}
|
||||
selected={mentor} />
|
||||
<ParticipantColumn
|
||||
|
@ -119,26 +130,35 @@ export default function Home(props) {
|
|||
participants={participants}
|
||||
filter={p => p.role == 'L'}
|
||||
onSelect={p => {
|
||||
setLearner(p);
|
||||
mentor && setDrawerOpen(true);
|
||||
if (!pair.loading) {
|
||||
setLearner(p);
|
||||
mentor && setDrawerOpen(true);
|
||||
}
|
||||
}}
|
||||
selected={learner} />
|
||||
<div style={{ clear: 'all' }} />
|
||||
<PairDrawer
|
||||
mentor={mentor}
|
||||
learner={learner}
|
||||
open={drawerOpen}
|
||||
open={mentor && learner && drawerOpen}
|
||||
pairing={pair.loading}
|
||||
onClose={() => {
|
||||
setMentor(null);
|
||||
setLearner(null);
|
||||
setDrawerOpen(false);
|
||||
if (!pair.loading) {
|
||||
setDrawerOpen(false);
|
||||
}
|
||||
}}
|
||||
onPair={(mentor, learner) => {
|
||||
console.log('pair', mentor.full_name, learner.full_name);
|
||||
setMentor(null);
|
||||
setLearner(null);
|
||||
setDrawerOpen(false);
|
||||
refetchParticipants();
|
||||
if (!pair.loading) {
|
||||
postPairing({
|
||||
data: { mentor: mentor.id, learner: learner.id },
|
||||
}).then(() => {
|
||||
// TODO: snackbar?
|
||||
setDrawerOpen(false);
|
||||
setMentor(null);
|
||||
setLearner(null);
|
||||
refetchParticipants();
|
||||
});
|
||||
}
|
||||
}} />
|
||||
</Loading>
|
||||
</Fragment>
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
<!-- React will load here -->
|
||||
</div>
|
||||
</body>
|
||||
{% csrf_token %}
|
||||
<script>
|
||||
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
|
||||
</script>
|
||||
{% load static %}
|
||||
<script src="{% static "frontend/main.js" %}"></script>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
from rest_framework import serializers, viewsets, permissions, mixins
|
||||
|
||||
from .models import Pair
|
||||
from ..participants.models import Participant
|
||||
|
||||
class PairSerializer(serializers.HyperlinkedModelSerializer):
|
||||
mentor = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Participant.objects.all().filter(role=Participant.MENTOR))
|
||||
learner = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Participant.objects.all().filter(role=Participant.LEARNER))
|
||||
|
||||
class Meta:
|
||||
model = Pair
|
||||
fields = [
|
||||
'mentor',
|
||||
'learner',
|
||||
]
|
||||
|
||||
|
||||
# ViewSets define the view behavior.
|
||||
class PairViewSet(mixins.CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
queryset = Pair.objects.all()
|
||||
serializer_class = PairSerializer
|
|
@ -1,6 +1,8 @@
|
|||
from rest_framework import serializers, viewsets, routers
|
||||
|
||||
from .participants.rest import ParticipantViewSet
|
||||
from .pairing.rest import PairViewSet
|
||||
|
||||
router = routers.DefaultRouter(trailing_slash=False)
|
||||
router.register(r'participants', ParticipantViewSet)
|
||||
router.register(r'pairs', PairViewSet)
|
||||
|
|
Загрузка…
Ссылка в новой задаче