️ This Gitlab will be shut down at 2021-12-31 23:59:59.
Students and staff can migrate to gitlab.utwente.nl.
SNT members can migrate to gitlab.snt.utwente.nl.
Contact bestuur@snt.utwente.nl for more information.

Migrate your projects today!
Export your project using the webinterface or use a script.

Commit 57dccaa2 authored by TJHeeringa's avatar TJHeeringa

Various fixes and redux stuff

parent 218487b5
......@@ -1895,6 +1895,24 @@
"@react-hook/resize-observer": "^1.1.0"
}
},
"@reduxjs/toolkit": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.6.1.tgz",
"integrity": "sha512-pa3nqclCJaZPAyBhruQtiRwtTjottRrVJqziVZcWzI73i6L3miLTtUyWfauwv08HWtiXLx1xGyGt+yLFfW/d0A==",
"requires": {
"immer": "^9.0.1",
"redux": "^4.1.0",
"redux-thunk": "^2.3.0",
"reselect": "^4.0.0"
},
"dependencies": {
"immer": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.5.tgz",
"integrity": "sha512-2WuIehr2y4lmYz9gaQzetPR2ECniCifk4ORaQbU3g5EalLt+0IVTosEPJ5BoYl/75ky2mivzdRzV8wWgQGOSYQ=="
}
}
},
"@restart/context": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@restart/context/-/context-2.1.4.tgz",
......@@ -2210,6 +2228,17 @@
}
}
},
"@types/react-redux": {
"version": "7.1.18",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.18.tgz",
"integrity": "sha512-9iwAsPyJ9DLTRH+OFeIrm9cAbIj1i2ANL3sKQFATqnPWRbg+jEFXyZOKHiQK/N86pNRXbb4HRxAxo0SIX1XwzQ==",
"requires": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"@types/react-table": {
"version": "6.8.7",
"resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.8.7.tgz",
......@@ -17684,6 +17713,29 @@
}
}
},
"react-redux": {
"version": "7.2.4",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz",
"integrity": "sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==",
"requires": {
"@babel/runtime": "^7.12.1",
"@types/react-redux": "^7.1.16",
"hoist-non-react-statics": "^3.3.2",
"loose-envify": "^1.4.0",
"prop-types": "^15.7.2",
"react-is": "^16.13.1"
},
"dependencies": {
"@babel/runtime": {
"version": "7.15.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz",
"integrity": "sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
}
}
},
"react-resize-detector": {
"version": "6.7.6",
"resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-6.7.6.tgz",
......@@ -18112,6 +18164,25 @@
}
}
},
"redux": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.1.1.tgz",
"integrity": "sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw==",
"requires": {
"@babel/runtime": "^7.9.2"
}
},
"redux-devtools-extension": {
"version": "2.13.9",
"resolved": "https://registry.npmjs.org/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz",
"integrity": "sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A==",
"dev": true
},
"redux-thunk": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
"integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw=="
},
"reftools": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.5.tgz",
......@@ -18360,6 +18431,11 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
"reselect": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz",
"integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA=="
},
"resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
......
......@@ -18,12 +18,14 @@ import {
import CssBaseline from "@material-ui/core/CssBaseline";
import React from "react";
import { BrowserRouter as Router } from "react-router-dom";
import { Provider } from "react-redux";
import AlertHandlerProvider from "./Contexts/AlertHandler";
import API from "./Contexts/API";
import LocaleProvider from "./Contexts/Locale";
import ThemeProvider from "./Contexts/Theme";
import Routing from "./Routing/Routing";
import store from "./Store/store";
require("bootstrap");
......@@ -35,18 +37,20 @@ library.add(faSignOutAlt, faUser, faIgloo, faBars, faCheck, faTimes, faHome,
const App = () => {
return (
<div className={"App"}>
<Router>
<AlertHandlerProvider>
<API>
<ThemeProvider>
<CssBaseline/>
<LocaleProvider>
<Routing/>
</LocaleProvider>
</ThemeProvider>
</API>
</AlertHandlerProvider>
</Router>
<Provider store={store}>
<Router>
<AlertHandlerProvider>
<API>
<ThemeProvider>
<CssBaseline/>
<LocaleProvider>
<Routing/>
</LocaleProvider>
</ThemeProvider>
</API>
</AlertHandlerProvider>
</Router>
</Provider>
</div>
);
};
......
......@@ -42,9 +42,9 @@ const SpecificDataField = (props) =>{
validators={["required"]}
errorMessages={["This field is required"]}
label={field.name}
value={Boolean(value)}
checked={Boolean(value)}
onChange={(event)=>onChange(field.url, event.target.checked)}
value={value !== "False"}
checked={value !== "False"}
onChange={(event)=>onChange(field.url, event.target.checked ? "True" : "False")}
{...rest_props}
/>
);
......
import cx from "clsx";
import makeStyles from "@material-ui/core/styles/makeStyles";
import useTheme from "@material-ui/core/styles/useTheme";
import cx from "clsx";
import PropTypes from "prop-types";
import React from "react";
......@@ -25,7 +25,7 @@ const Wrapper = ({ children, className }) => {
Wrapper.propTypes = {
className: PropTypes.string,
children: PropTypes.node
children: PropTypes.any
};
export default Wrapper;
\ No newline at end of file
import Button from "@material-ui/core/Button";
import { isEmpty } from "lodash";
import PropTypes from "prop-types";
import React, {useMemo, useState} from "react";
import {ValidatorForm} from "react-material-ui-form-validator";
import { isEmpty } from "lodash";
import {useAlertHandler} from "../../Contexts/AlertHandler";
import {useAPI} from "../../Contexts/API";
......@@ -103,9 +103,10 @@ export const SpecificDataForm = ({ specificData: propSpecificData, association,
<Wrapper key={d}>
<SpecificDataField
field={dataField}
helperText={dataField.helper_text}
value={specificDataWithChanges[dataField.url] ? specificDataWithChanges[dataField.url].value : ""}
onChange={(field_url, value)=>handleDataChange("value", value, field_url)}
disabled={dataField.board_only && !association.is_board}
disabled={(dataField.board_only || (dataField.mandatory && specificDataWithChanges[dataField.url].value === "True")) && !association.is_board}
/>
</Wrapper>
)) }
......
import {useSelector} from "react-redux";
import {useLocation} from "react-router-dom";
import {selectAssociations} from "../../Store/slices/association";
const useCurrentAssociation = () => {
const { pathname } = useLocation();
const associations = useSelector((state) => selectAssociations(state));
const pathParts = pathname.split("/");
console.log(associations, pathParts);
if (!pathParts.includes("associations") || pathParts.length < 3) {
return {};
}
const slug = pathParts[3];
return associations.find(association=>association.slug === slug);
};
export default useCurrentAssociation;
\ No newline at end of file
......@@ -8,8 +8,6 @@ const SpecificData = ({ specific_data }) => {
const { collection: dataFields } = useDataFields();
const dataFieldToTypeDict = Object.fromEntries(dataFields.map(dataField=>[dataField.name, dataField.type]));
console.log(specific_data, dataFields);
const data = useMemo(()=>
Object.fromEntries(specific_data.map(sp=>[sp.name, dataFieldToTypeDict[sp.name] === "Boolean"
? (sp.value === "True" ? "Yes" : "No")
......
......@@ -3,7 +3,7 @@ import ExtremeTable from "App/Components/Tables/ExtremeTable";
import PropTypes from "prop-types";
import React, {useMemo} from "react";
import {useDataFields, useLoadedMembers, useMembers, useMemberTypes} from "../../Contexts/Members";
import {useDataFields, useLoadedMembers, useMemberTypes} from "../../Contexts/Members";
import {useStudies} from "../../Contexts/Studies";
import {
deriveColumnBoundsFromDataFields,
......
......@@ -37,7 +37,7 @@ ToggleGroup.propTypes = {
toggleButtons: PropTypes.arrayOf(PropTypes.shape({
value: PropTypes.any,
ariaLabel: PropTypes.string,
Icon: PropTypes.node
Icon: PropTypes.elementType
})).isRequired
};
......
......@@ -6,6 +6,8 @@ import {RestfulProvider} from "restful-react";
import {Helper} from "../Helper";
import {useAlertHandler} from "./AlertHandler";
import {useDispatch} from "react-redux";
import {addMany} from "../Store/slices/association";
export const APIContext = createContext({
authenticated: false,
......@@ -14,6 +16,7 @@ export const APIContext = createContext({
backend: undefined,
token: undefined,
sunmember: undefined,
setSunmember: ()=>{},
refetch: ()=>{},
call: ()=>{},
callv2: ()=>{},
......@@ -34,6 +37,7 @@ const API = ({ children }) => {
const [backend, setBackend] = useState(localStorage.getItem("SUN_backend"));
const [token, setToken] = useState(localStorage.getItem("SUN_api_token"));
const [sunmember, setSunmember] = useState(null);
const dispatch = useDispatch();
const history = useHistory();
const alerthandler = useAlertHandler();
......@@ -100,6 +104,7 @@ const API = ({ children }) => {
profile: sunmember.profiles.find(profile=>profile.url === association_membership.profile.url)
}))
});
dispatch(addMany(sunmember.association_memberships.map(membership=>membership.association)));
if (onSucces) {
onSucces(sunmember);
}
......@@ -209,6 +214,7 @@ const API = ({ children }) => {
backend: backend,
token: token,
sunmember: sunmember,
setSunmember: setSunmember,
refetch: postAuthentication,
login: handleLogin,
logout: handleLogout,
......
......@@ -60,7 +60,7 @@ const GroupMemberManagementAssociationMemberList = ({ group, association, onAdd,
/>
<Wrapper>
<div/>
<Button color={"primary"} variant={"outlined"} onClick={addSelectedMembers}>
<Button color={"primary"} variant={"contained"} onClick={addSelectedMembers}>
Add selection to group
</Button>
</Wrapper>
......
......@@ -33,7 +33,7 @@ const PopupBoard = ({ row, onChange, onApplyChanges, onCancelChanges, open }) =>
<DialogContent>
<Wrapper>
<TextField
name={"Duty"}
name={"duty"}
label={"Duty"}
value={row.duty || ""}
onChange={onChange}
......@@ -85,7 +85,6 @@ const PopupBoard = ({ row, onChange, onApplyChanges, onCancelChanges, open }) =>
value={row.description || ""}
onChange={onChange}
multiline
required
/>
</Wrapper>
</DialogContent>
......@@ -114,7 +113,7 @@ const PopupCommittee = ({ row, onChange, onApplyChanges, onCancelChanges, open }
<DialogContent>
<Wrapper>
<TextField
name={"Duty"}
name={"duty"}
label={"Duty"}
value={row.duty || ""}
onChange={onChange}
......
import "react-simple-tree-menu/dist/main.css";
import "./Groups.css";
import PropTypes from "prop-types";
import React from "react";
import {Route, useLocation, useRouteMatch} from "react-router-dom";
import { useGet } from "restful-react";
import CardGrid from "../../../Components/Card/CardGrid";
import GroupCard from "../../../Components/Card/GroupCardV2";
import PageContent from "../../../Components/PageLayout/Content/Content";
import Detail from "./Detail";
const Committees = ({ association }) => {
const location = useLocation();
const { path } = useRouteMatch();
let { data: committees, loading, error } = useGet({
path: "/groups",
queryParams: {limit: 10000, association__slug: association.slug, board_group: false},
resolve: data => data && data.results
});
const committeeToGroupCard = (committee) => ({
title: committee.full_name,
photo: committee.photo,
path: "committees/" + committee.slug
});
if (loading || error) {
return null;
} else {
const filteredCommittees = committees.filter(committee=> committee.parent_group === null);
const cards = filteredCommittees.map(committee=>committeeToGroupCard(committee));
return (
<>
<MyCommitteesRouter
groups={committees}
path={path}
/>
{ location.pathname === path &&
<PageContent title={"Groups and Committees"}>
<CardGrid
component={GroupCard}
card_grid={cards}
chunk_size={4}
/>
</PageContent>
}
</>
);
}
};
const MyCommitteesRouter = ({groups, path}) => {
return (
<>
{ groups.map((group, g) =>{
return (
<Route
key={g}
exact
path= {path+"/"+group.slug}
render={() =>
<Detail
{...group}
/>
}
/>
);
}) }
</>
);
};
MyCommitteesRouter.propTypes = {
groups: PropTypes.array.isRequired,
path: PropTypes.string.isRequired
};
export const MyCommittees = Committees;
\ No newline at end of file
......@@ -7,6 +7,7 @@ import AvatarGroup from "@material-ui/lab/AvatarGroup";
import PropTypes from "prop-types";
import React from "react";
import {loremIpsum} from "react-lorem-ipsum";
import {useParams} from "react-router-dom";
import {BackButton} from "../../../Components/BackButton";
import Info from "../../../Components/Info/Info";
......@@ -21,35 +22,44 @@ const useStyles = makeStyles(theme => ({
}
}));
const Detail = (props) => {
const GroupDetail = ({ committees }) => {
const theme = useTheme();
const classes = useStyles(theme);
const { short_name, full_name, groupmemberships, description, number, creed, founding_date, dissolution_date, email } = props;
const { slug } = useParams();
const committee = committees.find(committee=>committee.slug === slug);
if (!committee) {
return (
<PageContent title={""}>
<BackButton/>
</PageContent>
);
}
return (
<PageContent title={full_name}>
<PageContent title={committee.full_name || ""}>
<BackButton/>
<Container>
<Block>
<Typography variant={"h5"}>{ short_name || full_name }</Typography>
<Typography variant={"body1"}>{ description || loremIpsum() }</Typography>
<Typography variant={"h5"}>{ committee.short_name || committee.full_name }</Typography>
<Typography variant={"body1"}>{ committee.description || loremIpsum() }</Typography>
</Block>
<Block>
<Info
headerless={true}
data={{
number: number,
creed: creed || "",
founding_date: founding_date,
dissolution_date: dissolution_date,
email: email || ""
number: committee.number,
creed: committee.creed || "",
founding_date: committee.founding_date,
dissolution_date: committee.dissolution_date,
email: committee.email || ""
}}
/>
</Block>
<Block>
<AvatarGroup max={17}>
{ groupmemberships.map((membership, m)=>(
{ committee.groupmemberships && committee.groupmemberships.map((membership, m)=>(
<Avatar className={classes.avatar} key={m} src={membership.photo}/>
)) }
</AvatarGroup>
......@@ -59,20 +69,11 @@ const Detail = (props) => {
);
};
Detail.propTypes = {
short_name: PropTypes.string,
full_name: PropTypes.string,
description: PropTypes.string,
groupmemberships: PropTypes.array,
number: PropTypes.number,
creed: PropTypes.string,
founding_date: PropTypes.string,
dissolution_date: PropTypes.string,
email: PropTypes.string
GroupDetail.propTypes = {
committees: PropTypes.array.isRequired
};
Detail.defaultProps = {
full_name: "Commissie"
GroupDetail.defaultProps = {
};
export default Detail;
\ No newline at end of file
export default GroupDetail;
\ No newline at end of file
import "react-simple-tree-menu/dist/main.css";
import PropTypes from "prop-types";
import React from "react";
import Sushi from "../../../../img/Sushi-11-me.jpg";
import CardGrid from "../../../Components/Card/CardGrid";
import GroupCard from "../../../Components/Card/GroupCardV2";
import PageContent from "../../../Components/PageLayout/Content/Content";
const GroupList = ({ committees }) => {
const committeeToGroupCard = (committee) => ({
title: committee.full_name,
photo: committee.photo || Sushi,
path: "committees/" + committee.slug
});
const filteredCommittees = committees.filter(committee=> committee.parent_group === null);
const cards = filteredCommittees.map(committee=>committeeToGroupCard(committee));
return (
<PageContent title={"Groups and Committees"}>
<CardGrid
component={GroupCard}
card_grid={cards}
chunk_size={4}
/>
</PageContent>
);
};
GroupList.propTypes = {
committees: PropTypes.array.isRequired
};
export default GroupList;
\ No newline at end of file
import PropTypes from "prop-types";
import React from "react";
import {Route, Switch, useRouteMatch} from "react-router-dom";
import {useGet} from "restful-react";
import GroupDetail from "./GroupDetail";
import GroupList from "./GroupList";
export const GroupsRouter = ({ association }) => {
const { path } = useRouteMatch();
const { data: committees } = useGet({
path: "/groups",