Commit 3b96e9ea authored by TJHeeringa's avatar TJHeeringa

Various fixes

parent 9cafd08b
......@@ -30,7 +30,7 @@ import {useAPI} from "../../Contexts/API";
const useStyles = makeStyles(theme => ({
}));
export const GroupForm = ({ group, parentable_groups, handleGroupChange, update, onSuccess}) => {
export const GroupForm = ({ group, parentable_groups, handleGroupChange, update, onSucces, onCancel}) => {
const theme = useTheme();
const classes = useStyles(theme);
......@@ -63,7 +63,7 @@ export const GroupForm = ({ group, parentable_groups, handleGroupChange, update,
object: group,
on_succes: (data) => {
alerthandler.handleAlertHandler("green", "Save successful");
onSuccess(data);
onSucces(data);
},
on_failure: (data) => {
alerthandler.handleAlertHandler("red", "Save failed");
......@@ -78,7 +78,7 @@ export const GroupForm = ({ group, parentable_groups, handleGroupChange, update,
object: group,
on_succes: (data) => {
alerthandler.handleAlertHandler("green", "Save successful");
onSuccess(data);
onSucces(data);
},
on_failure: () => {
alerthandler.handleAlertHandler("red", "Save failed");
......@@ -199,7 +199,7 @@ export const GroupForm = ({ group, parentable_groups, handleGroupChange, update,
<div>
<Button type={"submit"} variant={"contained"} color={"primary"}>Save</Button>
&nbsp;
<Button variant={"contained"} onClick={()=>{}}>Cancel</Button>
<Button variant={"contained"} onClick={onCancel}>Cancel</Button>
</div>
</Wrapper>
</ValidatorForm>
......@@ -211,9 +211,9 @@ GroupForm.propTypes = {
handleGroupChange: PropTypes.func.isRequired,
parentable_groups: PropTypes.arrayOf(PropTypes.object).isRequired,
update: PropTypes.bool.isRequired,
onSuccess: PropTypes.func
onSucces: PropTypes.func
};
GroupForm.defaultProps = {
onSuccess: ()=> {}
onSucces: ()=> {}
};
\ No newline at end of file
import ToggleButton from "@material-ui/lab/ToggleButton";
import ToggleButtonGroup from "@material-ui/lab/ToggleButtonGroup";
import PropTypes from "prop-types";
import React from "react";
const ToggleGroup = ({ toggle, setToggle, toggleButtons }) => {
const handleToggle = (event, newToggleValue) => {
// using this makes sure that always some value is selected
if (newToggleValue !== null) {
setToggle(newToggleValue);
}
};
return (
<ToggleButtonGroup
value={toggle}
exclusive
onChange={handleToggle}
>
{ toggleButtons.map((toggleButton, id)=>
<ToggleButton
key={id}
value={toggleButton.value}
aria-label={toggleButton.ariaLabel}
>
<toggleButton.Icon/>
</ToggleButton>
) }
</ToggleButtonGroup>
);
};
ToggleGroup.propTypes = {
toggle: PropTypes.any.isRequired,
setToggle: PropTypes.func.isRequired,
toggleButtons: PropTypes.arrayOf(PropTypes.shape({
value: PropTypes.any,
ariaLabel: PropTypes.string,
Icon: PropTypes.node
})).isRequired
};
export default ToggleGroup;
\ No newline at end of file
import Button from "@material-ui/core/Button";
import Container from "@material-ui/core/Container";
import Typography from "@material-ui/core/Typography";
import PropTypes from "prop-types";
import React, {useState} from "react";
import {ValidatorForm} from "react-material-ui-form-validator";
......@@ -8,14 +9,25 @@ import NumberField from "../../Components/Fields/NumberField";
import Wrapper from "../../Components/Fields/Wrapper";
import Block from "../../Components/PageLayout/Content/Block";
import {useAlertHandler} from "../../Contexts/AlertHandler";
import {useAPI} from "../../Contexts/API";
const Settings = (props) => {
const Settings = ({ association: propAssociation }) => {
const alerthandler = useAlertHandler();
const API = useAPI();
const [association, setAssociation] = useState({debt_collection_threshold: 0});
const [association, setAssociation] = useState({...propAssociation});
const handleSubmit = () => {
alerthandler.handleAlertHandler("info", "Not implemented");
API.callv4({
url: propAssociation.url,
method: "PATCH",
object: {debt_collection_threshold: association.debt_collection_threshold},
on_succes: (response) => setAssociation({...association}),
on_failure: () => {
alerthandler.handleAlertHandler("error", "Something went wrong");
}
});
};
const handleAssociationChange = (field, value) => {
......@@ -67,7 +79,7 @@ const Settings = (props) => {
};
Settings.propTypes = {
association: PropTypes.object.isRequired
};
export default Settings;
\ No newline at end of file
This diff is collapsed.
......@@ -6,8 +6,6 @@ import { useHistory } from "react-router-dom";
import GroupInfoForm from "../../../Components/InfoForms/Group";
import Block from "../../../Components/PageLayout/Content/Block";
import { useAlertHandler} from "../../../Contexts/AlertHandler";
import {useAPI} from "../../../Contexts/API";
const GroupAdd = ({ association, addGroup }) => {
......@@ -15,19 +13,24 @@ const GroupAdd = ({ association, addGroup }) => {
const onAdd = (group) => {
addGroup(group);
history.push("/protected/associations/student-union/boardmember/committees/" + group.slug);
history.push("./" + group.slug);
};
const onCancel = () => {
history.push("./");
};
return (
<Container>
<Block>
<Typography variant={"h5"}>New Committee</Typography>
<Typography variant={"h5"}>New</Typography>
<hr className={"box-title-separator"}/>
<GroupInfoForm
association={association}
infoOrForm={"form"}
parentable_groups={[]}
onSuccess={onAdd}
onCancel={onCancel}
update={false}
/>
</Block>
......
import {Grid} from "@material-ui/core";
import { Grid } from "@material-ui/core";
import Avatar from "@material-ui/core/Avatar";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
......@@ -8,23 +8,31 @@ import Typography from "@material-ui/core/Typography";
import { BackButton } from "App/Components/BackButton";
import Photo from "App/Components/Photo";
import { orderBy } from "lodash";
import React, {useState} from "react";
import { NavLink,withRouter } from "react-router-dom";
import {Button, Col, Container, Row} from "reactstrap";
import PropTypes from "prop-types";
import React, { useState } from "react";
import { NavLink,useParams,withRouter } from "react-router-dom";
import { Button, Col, Container, Row } from "reactstrap";
import Sushi from "../../../../img/Sushi-11-me.jpg"; // Import using relative path
import GroupInfoForm from "../../../Components/InfoForms/Group";
import Block from "../../../Components/PageLayout/Content/Block";
import {useAlertHandler} from "../../../Contexts/AlertHandler";
import {useAPI} from "../../../Contexts/API";
import { useAlertHandler } from "../../../Contexts/AlertHandler";
import { useAPI } from "../../../Contexts/API";
const GroupDetail = ({ association, group, groups, initialFormType, url }) => {
export const GroupDetail = ({ association, groups, initialFormType, url, onUpdate }) => {
const API = useAPI();
const alerthandler = useAlertHandler();
const { slug } = useParams();
const group = groups ? groups.find(group=>group.slug===slug) : undefined;
console.log(groups);
console.log(group);
console.log(slug);
const [formType, setFormType] = useState(initialFormType);
const [boardPermissionsGroup, setBoardPermissionsGroup] = useState(initialFormType);
const [boardPermissionsGroup, setBoardPermissionsGroup] = useState(group ? group.current_board: false);
const changeFormType = () => {
if (formType === "form") {
......@@ -38,14 +46,14 @@ const GroupDetail = ({ association, group, groups, initialFormType, url }) => {
API.callv4({
url: group.url,
method: "PATCH",
object: {groupobjectpermission_set: newPermissions},
object: {permissions: newPermissions},
onSuccess: onSuccess,
onFailure: onFailure
});
};
const grantBoardPermissions = () => {
changeBoardPermissions(
[{association: association, permission: "board"}],
["board"],
alerthandler.handleAlertHandler("success", "Granted group board permissions"),
alerthandler.handleAlertHandler("red", "Granting group board permissions failed"),
);
......@@ -58,9 +66,10 @@ const GroupDetail = ({ association, group, groups, initialFormType, url }) => {
);
};
// we don't want to do anything particular after the update
const onUpdate = () => {};
if (group === undefined) {
return null;
}
return (
<div className={"Committee"}>
<BackButton/>
......@@ -135,7 +144,11 @@ const GroupDetail = ({ association, group, groups, initialFormType, url }) => {
};
GroupDetail.propTypes = {
association: PropTypes.object.isRequired,
groups: PropTypes.arrayOf(PropTypes.object).isRequired,
url: PropTypes.string.isRequired,
onUpdate: PropTypes.func.isRequired,
initialFormType: PropTypes.string
};
GroupDetail.defaultProps = {
......
import Container from "@material-ui/core/Container";
import Divider from "@material-ui/core/Divider";
import Typography from "@material-ui/core/Typography";
import AccountTreeIcon from "@material-ui/icons/AccountTree";
import ArchiveIcon from "@material-ui/icons/Archive";
import UnarchiveIcon from "@material-ui/icons/Unarchive";
import ViewListIcon from "@material-ui/icons/ViewList";
import ViewModuleIcon from "@material-ui/icons/ViewModule";
import SearchBar from "material-ui-search-bar";
import PropTypes from "prop-types";
import React, {useState} from "react";
import { NavLink,useRouteMatch } from "react-router-dom";
import Sushi from "../../../../img/Sushi-11-me.jpg";
import CardGrid from "../../../Components/Card/CardGrid";
import GroupCard from "../../../Components/Card/GroupCardV2";
import Wrapper from "../../../Components/Fields/Wrapper";
import RecursiveTreeView from "../../../Components/Lists/Tree";
import Block from "../../../Components/PageLayout/Content/Block";
import ToggleGroup from "../../../Components/Toggle/ToggleGroup";
import Button from "@material-ui/core/Button";
const GroupList = ({ groups, boards }) => {
const { path } = useRouteMatch();
const [archive, setArchive] = useState(true);
const [layout, setLayout] = useState("blocks");
const [searchTerm, setSearchTerm] = useState("");
const KEY_TO_FILTER = "full_name";
const createFilter = (searchTerm, key) => item=>item[key].toLowerCase().includes(searchTerm.toLowerCase());
const resetSearchTerm = () => setSearchTerm("");
const groupToGroupCard = (group) => ({
title: group.full_name,
photo: group.photo || Sushi,
path: (boards ? "boards/" : "committees/") + group.slug
});
// Function behind groupsToTree function
// taken from https://stackoverflow.com/a/41145788/10967600
// added comments, edited to use arrow functions and added computation of leaves
const MiracleGrow = (treeData, key, parentKey) => {
let keys = [];
treeData.map(x=>{
x.children = [];
keys.push(x[key]);
});
// roots are al the nodes that have no parent inside treeData
let roots = treeData.filter(x => keys.indexOf(x[parentKey]) === -1);
// leaves are all the nodes that have no children; computed later
let leaves = [];
// create a queue for traversing the nodes
let nodes = [];
// start the queue with the nodes
roots.map(x=>nodes.push(x));
// for each node in the queue: find its children, link the parent to the
// children using its children attribute, and then add the children to
// the queue
while(nodes.length > 0)
{
let node = nodes.pop();
let children = treeData.filter(x=> x[parentKey] === node[key]);
if (children.length > 0) {
children.map(x=>{
node.children.push(x);
nodes.push(x);
});
} else {
leaves.push(node);
}
}
return {roots: roots, leaves: leaves};
};
const groupToGroupInTreeViewFormat = (group) => ({
primary: group.full_name,
secondary: 0,
LabelIcon: group.logo,
id: group.slug,
url: group.url,
parent_group: group.parent_group,
...group
});
const groupsToTree = (groups) => {
return MiracleGrow(groups, "url", "parent_group");
};
const filteredGroups = groups ? groups.filter(createFilter(searchTerm, KEY_TO_FILTER)) : [];
const tree = groupsToTree(filteredGroups.map(committee=>groupToGroupInTreeViewFormat(committee)));
const cards = tree.leaves.map(board=>groupToGroupCard(board));
const archiveButtons = [
{ value: true, ariaLabel: "Archived", Icon: ArchiveIcon },
{ value: false, ariaLabel: "Not Archived", Icon: UnarchiveIcon }
];
const layoutButtons = [
{ value: "tree", ariaLabel: "Tree", Icon: AccountTreeIcon},
{ value: "flat", ariaLabel: "Flat list", Icon: ViewListIcon},
{ value: "blocks", ariaLabel: "Card Grid", Icon: ViewModuleIcon}
];
return (
<Container>
<Block>
<Wrapper>
<Typography variant={"h5"}>{ boards ? "Boards" : "Committees" }</Typography>
<div>
<ToggleGroup
toggle={archive}
setToggle={setArchive}
toggleButtons={archiveButtons}
/>
&nbsp;
<ToggleGroup
toggle={layout}
setToggle={setLayout}
toggleButtons={layoutButtons}
/>
</div>
</Wrapper>
<Divider/>
<br/>
<SearchBar
placeholder={"Search"}
value={searchTerm}
onChange={setSearchTerm}
onCancelSearch={resetSearchTerm}
/>
<Wrapper>
<div/>
<NavLink to={path+"/add"}>
<Button variant={"contained"} color={"primary"}>
<Typography>Add</Typography>
</Button>
</NavLink>
</Wrapper>
</Block>
{ layout === "tree" &&
<Block>
<RecursiveTreeView
nodes={tree.roots}
/>
</Block>
}
{ layout === "flat" &&
<Block>
<RecursiveTreeView
nodes={tree.leaves}
/>
</Block>
}
{ layout === "blocks" &&
<CardGrid
component={GroupCard}
card_grid={cards}
chunk_size={4}
/>
}
</Container>
);
};
GroupList.propTypes = {
groups: PropTypes.arrayOf(PropTypes.object).isRequired,
boards: PropTypes.bool.isRequired
};
export default GroupList;
\ No newline at end of file
......@@ -2,6 +2,7 @@ import Container from "@material-ui/core/Container";
import Typography from "@material-ui/core/Typography";
import { BackButton } from "App/Components/BackButton";
import React, {useEffect, useState} from "react";
import { useParams } from "react-router-dom";
import Block from "../../../Components/PageLayout/Content/Block";
import {useAlertHandler} from "../../../Contexts/AlertHandler";
......@@ -10,9 +11,10 @@ import { GroupMemberManagementAssociationMemberList } from "./GroupMemberManagem
import { GroupMemberManagementGroupMemberList } from "./GroupMemberManagementGroupMemberList";
const GroupMemberManagement = ({group: propGroup, association}) => {
const GroupMemberManagement = ({ association }) => {
const API = useAPI();
const alerthandler = useAlertHandler();
const { slug } = useParams();
const [group, setGroup] = useState({});
......@@ -20,7 +22,7 @@ const GroupMemberManagement = ({group: propGroup, association}) => {
useEffect(()=>{
console.log("This is called");
API.callv4({
url: propGroup.url,
url: "/groups/" + slug,
method: "GET",
on_succes: (data) => {
console.log("This is called too");
......@@ -30,7 +32,7 @@ const GroupMemberManagement = ({group: propGroup, association}) => {
alerthandler.handleAlertHandler("red", "Loading group failed");
},
});
}, [propGroup]);
}, [slug]); // eslint-disable-line react-hooks/exhaustive-deps
const morphDataToGroupMemberList = (data) => {
data = data.map(member_profile => Object({
......
import React, {Component} from "react";
import {Route} from "react-router-dom";
import { v4 as uuidv4 } from "uuid";
import PropTypes from "prop-types";
import React, {Component, useEffect, useState} from "react";
import { Route, Switch } from "react-router-dom";
import {useAlertHandler} from "../../../Contexts/AlertHandler";
import {useAPI} from "../../../Contexts/API";
import GroupList from "./GroupList";
import GroupAdd from "./GroupAdd";
import {GroupDetailWithRouter} from "./GroupDetail";
import { GroupDetail, GroupDetailWithRouter } from "./GroupDetail";
import GroupMemberManagement from "./GroupMemberManagement";
export const GroupsRouter = ({ path, association, boards }) => {
const alerthandler = useAlertHandler();
const API = useAPI();
const [groups, setGroups] = useState([]);
// Get all the groups on mount and put them into the state
useEffect(()=>{
API.callv4({
url: "/groups",
queryParams: {board_group: boards, association__slug: association.slug, limit:1000},
method: "GET",
on_succes: (response) => setGroups(response.results),
on_failure: () => {
alerthandler.handleAlertHandler("red", "Loading committees failed");
},
});
}, [boards]); // eslint-disable-line react-hooks/exhaustive-deps
const addGroup = (group) => setGroups(prevState => prevState.concat(group));
const updateGroup = (edited_group) => setGroups(prevState =>
prevState.map(group=>group.slug === group.slug ? edited_group : group)
);
return (
<Switch>
<Route exact path={path+"/add"}>
<GroupAdd
association={association}
board_page={boards}
addGroup = {addGroup}
/>
</Route>
<Route path={path+"/:slug/membermanagement"}>
<GroupMemberManagement
association={association}
url={path}
/>
</Route>
<Route path={path+"/:slug"}>
<GroupDetail
association={association}
onUpdate={updateGroup}
groups={groups}
url={path}
/>
</Route>
<Route path={path}>
<GroupList
association={association}
boards={boards}
groups={groups}
path={path}
/>
</Route>
</Switch>
);
};
GroupsRouter.propTypes = {
path: PropTypes.string.isRequired,
association: PropTypes.object.isRequired,
boards: PropTypes.bool.isRequired
};
export class GroupRouter extends Component {
render() {
const {groups, path, association, board_page} = this.props;
return (
<>
{ groups.map((group) =>{
return <x-fragment key={uuidv4()}>
{ groups.map((group, id) =>{
return <x-fragment key={id}>
<Route
exact
path= {path+"/"+group.slug}
......
......@@ -39,6 +39,9 @@ const Committees = ({ association, path }) => {
if (loading || error) {
return null;
} else {
const filteredCommittees = committees.filter(committee=> committee.parent_group === null);
const cards = filteredCommittees.map(committee=>committeeToGroupCard(committee));
return (
<>
<MyCommitteesRouter
......@@ -49,7 +52,7 @@ const Committees = ({ association, path }) => {
<PageContent title={"Groups and Committees"}>
<CardGrid
component={GroupCard}
card_grid={committees.map(committee=>committeeToGroupCard(committee))}
card_grid={cards}
chunk_size={4}
/>
</PageContent>
......
......@@ -10,8 +10,6 @@ import EmailLists from "App/Pages/Email/EmailLists";
import EmailSender from "App/Pages/Email/EmailSender";
import EmailUsers from "App/Pages/Email/EmailUsers";
import DebtCollections from "App/Pages/Financial/DebtCollections";
import { BoardsWithRouter } from "App/Pages/Groups/Board/Boards";
import { CommitteesWithRouter } from "App/Pages/Groups/Board/Committees";
import { MembersCurrentWithRouter } from "App/Pages/Members/MembersCurrent";
import { MembersDisputedWithRouter } from "App/Pages/Members/MembersDisputed";
import MembersIncoming from "App/Pages/Members/MembersIncoming";
......@@ -19,114 +17,87 @@ import { MembersLeavingWithRouter } from "App/Pages/Members/MembersLeaving";
import MembershipEnd from "App/Pages/Profile/MembershipEnd";
import PropTypes from "prop-types";
import React from "react";
import { Route } from "react-router-dom";
import { Route, Switch } from "react-router-dom";
import Settings from "../../../Pages/Financial/Settings";
import {GroupsRouter} from "../../../Pages/Groups/Board/GroupRouter";
const BoardMemberRoutes = (props) => {
const {path, association_membership, association} = props;
return (
<>
{ /*ROUTES*/ }
<Route
exact