Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion backend/src/emissions/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ class Query(UserQuery, MeQuery, ObjectType):
researchfields = graphene.List(ResearchFieldType)
institutions = graphene.List(InstitutionType)
workinggroup_users = graphene.List(UserType)
join_requests = graphene.List(WorkingGroupJoinRequestType)

# Aggregated data
heating_aggregated = graphene.List(
Expand Down Expand Up @@ -283,7 +284,7 @@ def resolve_workinggroup_users(self, info, **kawrgs):
def resolve_join_requests(self, info, **kwargs):
"""Yields all institution objects"""
id = info.context.user.working_group.id
return WorkingGroupJoinRequest.objects.all(working_group__id=id)
return WorkingGroupJoinRequest.objects.filter(working_group__id=id)

def resolve_institutions(self, info, **kwargs):
"""Yields all institution objects"""
Expand Down
1 change: 1 addition & 0 deletions frontend/src/api/Queries/me.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const getUserProfile = gql`
verified
firstName
lastName
isRepresentative
workingGroup {
id
name
Expand Down
19 changes: 18 additions & 1 deletion frontend/src/api/Queries/working-groups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,23 @@ query {
workinggroupUsers {
id
email
firstName
lastName
}
}
`
`

export const resolveWorkingGroupJoinRequests = gql`
query {
joinRequests {
id
status
timestamp
user {
email
firstName
lastName
}
}
}
`
35 changes: 35 additions & 0 deletions frontend/src/api/mutations/working-group-mutations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,39 @@ mutation createWorkingGroup($name: String!, $institution: String!, $field: Int!,
}
}
}
`

export const REQUEST_JOIN_WORKING_GROUP = gql`
mutation requestJoinWorkingGroup ($id: String!){
requestJoinWorkingGroup (input: {
workinggroupId: $id
}
) {
success
joinRequest {
status
id
workingGroup {
id
}
}
}
}
`

export const ANSWER_JOIN_REQUEST = gql`
mutation ($requestId: String!, $approve: Boolean!){
answerJoinRequest (input: {
approve: $approve
requestId: $requestId
}
) {
success
requestingUser {
workingGroup {
id
}
}
}
}
`
135 changes: 135 additions & 0 deletions frontend/src/components/WorkingGroups/JoinRequestsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { useMutation } from "@apollo/client";
import { IconButton, Paper, Snackbar, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, ThemeProvider, Tooltip } from "@material-ui/core";
import { green, grey, red } from "@material-ui/core/colors";
import ThumbDownIcon from '@material-ui/icons/ThumbDown';
import ThumbUpIcon from '@material-ui/icons/ThumbUp';
import { Alert } from "@mui/material";
import React, { useState } from "react";
import { ANSWER_JOIN_REQUEST } from "../../api/mutations/working-group-mutations";
import theme from "../../theme";

interface IWorkingGroupJoinRequestsTableProps {
data: any;
}

interface IRequestUser {
firstName: string,
lastName: string,
email: string
}

interface IRequestTableRow {
user: IRequestUser,
timestamp: string,
status: string,
id: string

}


export const WorkingGroupJoinRequestsTable = (props: IWorkingGroupJoinRequestsTableProps) => {

const {data: requestsTableData} = props;

const sortedRequestsTableData = requestsTableData ? [...requestsTableData] : []

sortedRequestsTableData?.sort((a: IRequestTableRow, b: IRequestTableRow) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); // maybe do this in the backend already


const [successState, setSuccessState] = useState(false)
const [errorState, setErrorState] = useState(false);

const [answerJoinRequest] = useMutation(ANSWER_JOIN_REQUEST, {
onCompleted(res){
if(res.answerJoinRequest.success){
setSuccessState(true)
window.location.reload();
}
else {
setErrorState(true)
}
},
onError(error){
console.log(error);
}
})

function formatUserString(user: IRequestUser){
return (
<div>
<span>{`${user.firstName} ${user.lastName}`}</span>
<br></br>
<span>{`${user.email}`}</span>
</div>
)
}

const handleRequest = (row: IRequestTableRow, approve: boolean) => {
answerJoinRequest({variables: {
requestId: row.id,
approve: approve
}})
}



return (
<React.Fragment>
<TableContainer component={Paper}>
<Table sx={{minWidth: 650}} ariea-label="simple table">
<TableHead>
<TableRow>
<TableCell>ID</TableCell>
<TableCell>User</TableCell>
<TableCell>Timestamp</TableCell>
<TableCell>Status</TableCell>
<TableCell align="right">Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{sortedRequestsTableData?.map((row: IRequestTableRow) => (
<TableRow key={row.id}>
<TableCell component="th" scope="row">
{row.id}
</TableCell>
<TableCell component="th" scope="row">
{formatUserString(row.user)}
</TableCell>
<TableCell component="th" scope="row">
{new Date(row.timestamp).toLocaleString()}
</TableCell>
<TableCell>
{row.status}
</TableCell>
<TableCell align="right">
<ThemeProvider theme={theme}>
<IconButton style={{color: row.status === "PENDING" ? green[500] : grey[500]}} onClick={() => handleRequest(row, true)} disabled={row.status !== "PENDING"}>
<Tooltip title="Approve join request">
<ThumbUpIcon />
</Tooltip>
</IconButton>
<IconButton style={{color: row.status === "PENDING" ? red[500] : grey[500]}} onClick={() => handleRequest(row, false)} disabled={row.status !== "PENDING"}>
<Tooltip title="Decline join request">
<ThumbDownIcon />
</Tooltip>
</IconButton>
</ThemeProvider>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
<Snackbar open={successState} autoHideDuration={6000} onClose={() => setSuccessState(false)}>
<Alert onClose={() => setSuccessState(false)} severity="success" sx={{ width: '100%' }}>
Successfully answered request!
</Alert>
</Snackbar>
<Snackbar open={errorState} autoHideDuration={6000} onClose={() => setErrorState(false)}>
<Alert onClose={() => setErrorState(false)} severity="error" sx={{ width: '100%' }}>
Failed answer request, try again!
</Alert>
</Snackbar>
</React.Fragment>
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Card, CardContent, IconButton, makeStyles, Tooltip, Typography } from "@material-ui/core";
import { IWorkingGroup } from "../interfaces/IWorkingGroup";
import { IWorkingGroup } from "../../interfaces/IWorkingGroup";
import GroupAddIcon from '@mui/icons-material/GroupAdd';

const useStyles = makeStyles(() => ({
Expand Down
73 changes: 73 additions & 0 deletions frontend/src/components/WorkingGroups/WorkingGroupUsersTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { IconButton, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, ThemeProvider, Tooltip } from "@material-ui/core";
import { red } from '@mui/material/colors';
import PersonRemoveIcon from '@mui/icons-material/PersonRemove';
import React, { useState } from "react";
import { UnderConstructionDialog } from "../UnderConstructionDialog";
import theme from "../../theme";

interface IWorkingGroupUsersTableProps {
data: any
}

interface IUserTableRow {
firstName: string,
lastName: string,
id: string,
email: string,
__typename: string
}



export const WorkingGroupUsersTable = (props: IWorkingGroupUsersTableProps) => {

const {data: tableData} = props;

const [showDialog, setShowDialog] = useState(false);

function formatUserName(row: IUserTableRow){
return `${row.firstName} ${row.lastName}`
}

const handleUserRemove = (row: IUserTableRow) => {
setShowDialog(true);
}

return (
<React.Fragment>
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>User Name</TableCell>
<TableCell align="right">User eMail</TableCell>
<TableCell align="right">Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{tableData?.map((row: IUserTableRow) => (
<TableRow
key={row.id}
>
<TableCell component="th" scope="row">
{formatUserName(row)}
</TableCell>
<TableCell align="right">{row.email}</TableCell>
<TableCell align="right">
<ThemeProvider theme={theme}>
<IconButton style={{color: red[500]}} onClick={() => handleUserRemove(row)}>
<Tooltip title="Remove user from working group">
<PersonRemoveIcon />
</Tooltip>
</IconButton>
</ThemeProvider>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
<UnderConstructionDialog feature="Removal of users from working groups" isOpen={showDialog} handleDialogClose={() => setShowDialog(false)}/>
</React.Fragment>
)
}
40 changes: 32 additions & 8 deletions frontend/src/views/WorkingGroupManagement/FindWorkingGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useQuery } from '@apollo/client';
import { Grid, TextField } from '@mui/material';
import { useMutation, useQuery } from '@apollo/client';
import { Alert, Grid, Snackbar, TextField } from '@mui/material';
import React, { useRef, useState } from 'react';
import { REQUEST_JOIN_WORKING_GROUP } from '../../api/mutations/working-group-mutations';
import { getWorkingGroups } from '../../api/Queries/working-groups';
import { UnderConstructionDialog } from '../../components/UnderConstructionDialog';
import WorkingGroupCard from '../../components/WorkingGroupCard';
import WorkingGroupCard from '../../components/WorkingGroups/WorkingGroupCard';
import { IWorkingGroup } from '../../interfaces/IWorkingGroup';


Expand All @@ -12,15 +12,30 @@ export default function FindWorkingGroupView(){
const {data: workingGroupData} = useQuery(getWorkingGroups)

const [searchInput, setSearchInput] = useState('')
const [ showAlert, setShowAlert ] = useState(false);
const [successState, setSuccessState ] = useState(false);
const [errorState, setErrorState ] = useState(false);

const [sendRequestJoinWorkingGroup] = useMutation(REQUEST_JOIN_WORKING_GROUP, {
onCompleted(result) {
if(result.requestJoinWorkingGroup.success === true){
setSuccessState(true);
} else {

}
}
});

const handleSearchInput = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchInput(event.target.value)
}

const requestJoinWorkingGroup = (workingGroup: IWorkingGroup) => {
// add here endpoint to request working group joining
setShowAlert(true)
console.log(workingGroup)
sendRequestJoinWorkingGroup({
variables: {
id: workingGroup.id
}
})
}

const parentRef = useRef(null);
Expand Down Expand Up @@ -49,7 +64,16 @@ export default function FindWorkingGroupView(){
)
})}
</Grid>
<UnderConstructionDialog feature='Joining Working Group Feature' isOpen={showAlert} handleDialogClose={() => setShowAlert(false)}></UnderConstructionDialog>
<Snackbar open={successState} autoHideDuration={6000} onClose={() => setSuccessState(false)}>
<Alert onClose={() => setSuccessState(false)} severity="success" sx={{ width: '100%' }}>
Request sent successfully!
</Alert>
</Snackbar>
<Snackbar open={errorState} autoHideDuration={6000} onClose={() => setErrorState(false)}>
<Alert onClose={() => setErrorState(false)} severity="error" sx={{ width: '100%' }}>
Failt to send join request. Try again or Contact the website administrators for help!
</Alert>
</Snackbar>
</>
)
}
Expand Down
Loading