Commit a937116f authored by TJHeeringa's avatar TJHeeringa

Various fixes

parent 179b6417
import Button from "@material-ui/core/Button";
import PropTypes from "prop-types";
import React from "react";
const InfoFormStateButton = ({ formState, formStateToggle }) => {
return (
<Button color={"primary"} variant={"contained"} onClick={formStateToggle}>
{ formState === "form" ? "View" : "Edit" }
</Button>
);
};
InfoFormStateButton.propTypes = {
formState: PropTypes.string.isRequired,
formStateToggle: PropTypes.func.isRequired
};
export default InfoFormStateButton;
\ No newline at end of file
......@@ -11,12 +11,12 @@ import EventAvailableIcon from "@material-ui/icons/EventAvailable";
import EventBusyIcon from "@material-ui/icons/EventBusy";
import FacebookIcon from "@material-ui/icons/Facebook";
import GavelIcon from "@material-ui/icons/Gavel";
import InstagramIcon from "@material-ui/icons/Instagram";
import LineStyleIcon from "@material-ui/icons/LineStyle";
import LinkedInIcon from "@material-ui/icons/LinkedIn";
import LocationCityIcon from "@material-ui/icons/LocationCity";
import MarkunreadMailboxIcon from "@material-ui/icons/MarkunreadMailbox";
import MeetingRoomIcon from "@material-ui/icons/MeetingRoom";
import InstagramIcon from "@material-ui/icons/Instagram";
import PhoneIcon from "@material-ui/icons/Phone";
import PhotoIcon from "@material-ui/icons/Photo";
import PhotoSizeSelectLargeIcon from "@material-ui/icons/PhotoSizeSelectLarge";
......@@ -241,6 +241,7 @@ export const AssociationForm = (props) => {
<FileField
onChange={(event)=>handleAssociationChange("articles_of_association",event.target.files[0])}
name={"Articles of Association"}
helperText={"Dutch: Statuten."}
/>
</Wrapper>
<Wrapper>
......@@ -248,6 +249,7 @@ export const AssociationForm = (props) => {
<FileField
onChange={(event)=>handleAssociationChange("bylaws",event.target.files[0])}
name={"bylaws"}
helperText={"Dutch: Huishoudelijk Regelement."}
/>
</Wrapper>
<Wrapper>
......
......@@ -97,32 +97,17 @@ const ProductForm = ({ association, product: propProduct, onProductChange, updat
}
<Wrapper>
<IconHolder Icon={EuroIcon}/>
<SelectField
name={"price_fixed"}
value={product.price_fixed}
onChange={(event)=>handleProductChange("price_fixed",event.target.value)}
helperText={"Setting this to false means that members are able to set the price themselves when they " +
"purchase this product."}
>
<MenuItem value={"true"}>Yes</MenuItem>
<MenuItem value={"false"}>No</MenuItem>
</SelectField>
<NumberField
name={"price"}
value={product.price}
onChange={(event)=>handleProductChange("price",event.target.value)}
InputProps={{
inputProps: {
step: 0.1, min: 0
}
}}
/>
</Wrapper>
{ product.price_fixed &&
<Wrapper>
<IconHolder Icon={EuroIcon}/>
<NumberField
name={"price"}
value={product.price}
onChange={(event)=>handleProductChange("price",event.target.value)}
InputProps={{
inputProps: {
step: 0.1, min: 0
}
}}
/>
</Wrapper>
}
<Wrapper>
<div>
......
import PropTypes from "prop-types";
import {useState} from "react";
const useInfoFormState = ( initial_state ) => {
const [formType, setFormType] = useState(initial_state);
const toggleFormType = () => {
if (formType === "form") {
setFormType("info");
} else {
setFormType("form");
}
};
return [formType, toggleFormType];
};
useInfoFormState.propTypes = {
initial_state: PropTypes.string.isRequired
};
export default useInfoFormState;
\ No newline at end of file
import Info from "App/Components/Info/Info";
import PropTypes from "prop-types";
import React from "react";
const MembershipInfo = ({ membership }) => {
const morphMembershipToData = (membership) => {
console.log(membership);
console.log(membership.new_type !== null);
let data = {
"Date Joined:": membership.date_joined,
"Date Left:": membership.date_left,
"Current Membertype:": membership.type.type,
"Upcoming Membertype:": membership.new_type ? membership.new_type.type : "",
"Status": membership.status
};
if (membership.new_type !== null) {
data = {...data,
"Current Membertype:": membership.type.type,
"Upcoming Membertype:": membership.new_type.type
};
} else {
data = {...data,
"Membertype:": membership.type.type,
};
}
return data;
};
return (
<Info
headerless={true}
data={morphMembershipToData(membership)}
/>
);
};
MembershipInfo.propTypes = {
membership: PropTypes.object
};
export default MembershipInfo;
\ No newline at end of file
......@@ -26,7 +26,7 @@ const ProfileInfo = ({ profile }) => {
};
data["Education"] = {
"Phase:": profile.is_master ? "Master" : "Bachelor",
"Study": profile.study.name_en
"Study": profile.study ? profile.study.name_en : ""
};
return data;
};
......
import PropTypes from "prop-types";
import React, {useState} from "react";
const InfoForm = ({association, infoFormObject: propInfoFormObject, infoOrForm, InfoComponent, FormComponent, ...props}) => {
const [infoFormObject, setInfoFormObject] = useState(propInfoFormObject.association ? propInfoFormObject : {...propInfoFormObject, association: association.url});
const handleInfoFormObjectChange = (field, value) => {
setInfoFormObject(prevState => ({...prevState, [field]:value}));
};
if (infoOrForm === "info") {
return (
<InfoComponent
info={infoFormObject}
/>
);
} else {
return (
<FormComponent
association={association}
formObject={infoFormObject}
handleFormObjectChange={handleInfoFormObjectChange}
{...props}
/>
);
}
};
InfoForm.propTypes = {
infoOrForm: PropTypes.string.isRequired,
infoFormObject: PropTypes.object
};
InfoForm.defaultProps = {
infoFormObject: {
}
};
export default InfoForm;
import PropTypes from "prop-types";
import React, {useState} from "react";
import ProfileForm from "../Forms/ProfileForm";
import ProfileInfo from "../Info/ProfileInfo";
import {MembershipForm} from "../Forms/MembershipForm";
import MembershipInfo from "../Info/MembershipInfo";
export const Membership = ({membership: propMembership, infoOrForm, ...props}) => {
const [membership, setMembership] = useState(propMembership);
const handleMembershipChange = (field, value) => {
setMembership(prevState =>({...prevState, [field]: value}));
};
if (infoOrForm === "info") {
return (
<MembershipInfo
membership={membership}
/>
);
} else {
return (
<MembershipForm
membership={membership}
handleMembershipChange={handleMembershipChange}
{...props}
/>
);
}
};
Membership.propTypes = {
infoOrForm: PropTypes.string.isRequired,
membership: PropTypes.object
};
Membership.defaultProps = {
membership: {
}
};
\ No newline at end of file
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import AlternateEmailIcon from "@material-ui/icons/AlternateEmail";
import DateRangeIcon from "@material-ui/icons/DateRange";
import EmailIcon from "@material-ui/icons/Email";
import EuroIcon from "@material-ui/icons/Euro";
import EventIcon from "@material-ui/icons/Event";
......@@ -13,6 +15,7 @@ import PeopleRoundedIcon from "@material-ui/icons/PeopleRounded";
import PersonIcon from "@material-ui/icons/Person";
import PersonAddIcon from "@material-ui/icons/PersonAdd";
import PersonAddDisabledIcon from "@material-ui/icons/PersonAddDisabled";
import ReceiptIcon from "@material-ui/icons/Receipt";
import SettingsIcon from "@material-ui/icons/Settings";
import ShareIcon from "@material-ui/icons/Share";
import StorageIcon from "@material-ui/icons/Storage";
......@@ -22,9 +25,6 @@ import WcIcon from "@material-ui/icons/Wc";
import Drawer from "App/Components/Lists/Drawer/Drawer";
import React from "react";
import {useLocation} from "react-router-dom";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import DateRangeIcon from "@material-ui/icons/DateRange";
const AssociationBoardSideBar = (props) => {
return AssociationBoardDrawer(props);
......@@ -176,6 +176,11 @@ const AssociationBoardDrawer = (props) => {
icon: <PaymentIcon />,
primary: "Debt collections",
to: url + "/financial/debt_collections"
},
{
icon: <ReceiptIcon />,
primary: "Invoices",
to: url + "/financial/invoices"
}
]
},
......@@ -193,11 +198,11 @@ const AssociationBoardDrawer = (props) => {
primary: "Purchases",
to: url + "/inventory/purchases"
},
{
icon: <FontAwesomeIcon className={"fa-lg"} icon={["fas", "exchange-alt"]}/>,
primary: "Rented",
to: url + "/inventory/rented"
}
// {
// icon: <FontAwesomeIcon className={"fa-lg"} icon={["fas", "exchange-alt"]}/>,
// primary: "Rented",
// to: url + "/inventory/rented"
// }
]
},
];
......
import ExtremeTable from "App/Components/Tables/ExtremeTable";
import PropTypes from "prop-types";
import React, {useState} from "react";
import {useHistory} from "react-router-dom";
import {useDataFields, useMembers, useMemberTypes} from "../../Contexts/Members";
import {
......@@ -12,7 +13,7 @@ import {
const MemberTable = ({ rows, headers, editingStateColumnExtensions, choiceSelectionOptions, ...rest_props }) => {
const extremeHeaders = headers.map(header=>{return {"name": header.name, "title": header.title}; });
const history = useHistory();
const [selection, setSelection] = useState([]);
return (
......@@ -23,6 +24,9 @@ const MemberTable = ({ rows, headers, editingStateColumnExtensions, choiceSelect
headers={extremeHeaders}
showExporter={true}
showEditing={true}
allowEdit={false}
allowDelete={true}
onDelete={(deleted)=>history.push(rows[deleted[0]].slug)}
editingStateColumnExtensions={editingStateColumnExtensions}
choiceSelectionOptions={choiceSelectionOptions}
{...rest_props}
......
......@@ -68,44 +68,55 @@ const Matchings = ({ association }) => {
const data_fields = DataFieldsApi.list() || [];
const initial_headers = ["given_name"].concat(data_fields ? data_fields.map(data_field=>data_field.name) : []);
const checkForMatch = (match) => {
const { left_profile, right_profile } = match;
const same_way_matches = matches.filter(match_ => (match_.left_profile === left_profile && match_.right_profile === right_profile));
const swapped_matches = matches.filter(match_ => (match_.left_profile === right_profile && match_.right_profile === left_profile));
return (same_way_matches.length > 0 || swapped_matches.length > 0);
};
const createMatch = () => {
if (Object.keys(selectedLeft).length === 0 || Object.keys(selectedRight).length === 0 || selectedLeft === selectedRight) {
alertHandler.handleAlertHandler("yellow", "Please select one person from each list for the match.");
// either selectedLeft or selectedRight contains just a single element.
const left_profile_slugs = selectedLeft;
const right_profile_slugs = selectedRight;
let matchObjects;
if (left_profile_slugs.length > right_profile_slugs.length) {
const right_profile_slug = right_profile_slugs[0];
matchObjects = left_profile_slugs.map(left_profile_slug=>({
association: association.url,
left_profile: "/profiles/" + left_profile_slug,
right_profile: "/profiles/" + right_profile_slug,
}));
} else {
let match = {
const left_profile_slug = left_profile_slugs[0];
matchObjects = right_profile_slugs.map(right_profile_slug=>({
association: association.url,
left_profile: selectedLeft.url,
right_profile: selectedRight.url,
};
if (!checkForMatch(match)) {
API.callv4({
url: "/matchings",
method: "POST",
object: match,
on_succes: (created_match) => {
setMatches(prevMatches=> {
prevMatches.push(created_match);
return prevMatches;
});
setLeft([]);
setRight([]);
alertHandler.handleAlertHandler("green", "Successfully created matching.");
},
on_failure: () => {
alertHandler.handleAlertHandler("red", "Creation of matching failed.");
}}
);
} else {
alertHandler.handleAlertHandler("yellow", "These persons are already matched.");
}
left_profile: "/profiles/" + left_profile_slug,
right_profile: "/profiles/" + right_profile_slug,
}));
}
let matchObject;
if (matchObjects.length > 1) {
matchObject = matchObjects;
} else {
matchObject = matchObjects[0];
}
API.callv4({
url: "/matchings",
method: "POST",
object: matchObject,
on_succes: (created_matches) => {
console.log(created_matches);
if (Array.isArray(created_matches)) {
setMatches(prevMatches=>prevMatches.concat(created_matches));
} else {
// if it is not an array, then its a single object.
setMatches(prevMatches=>{prevMatches.push(created_matches); return prevMatches;});
}
setLeft([]);
setRight([]);
alertHandler.handleAlertHandler("green", "Successfully created " + created_matches.length + " matching(s).");
},
on_failure: () => {
alertHandler.handleAlertHandler("red", "Creation of matching failed.");
}}
);
};
const autoMatch = () => {
......@@ -149,22 +160,19 @@ const Matchings = ({ association }) => {
};
const onDelete = (to_be_deleted_matches, remaining_matches) => {
let deleted_matches = [];
setMatches(remaining_matches);
to_be_deleted_matches.forEach(match =>
API.callv4({
url: match.url,
method: "DELETE",
object: match,
on_succes: ()=>{deleted_matches.push(match);},
on_failure: ()=>{
alertHandler.handleAlertHandler("red", "Deletion of match between " + match.left_name + " and " + match.left_name + " failed.");
setMatches(prevMatches=> {
prevMatches.push(match);
return prevMatches;
}); }
})
);
const to_be_deleted_matches_slugs = to_be_deleted_matches.map(match=>match.slug);
API.callv4({
url: "/matchings",
queryParams: {slug__in: to_be_deleted_matches_slugs, association__slug: association.slug},
method: "DELETE",
on_succes: ()=>{
alertHandler.handleAlertHandler("red", "Succesfully deleted " + to_be_deleted_matches.length + " matches.");
setMatches(remaining_matches);
},
on_failure: ()=> {
alertHandler.handleAlertHandler("red", "Deletion of match(es) failed.");
}
});
};
const allowAutoMatch = matches && matches.length === 0;
......@@ -182,8 +190,12 @@ const Matchings = ({ association }) => {
matchesAssociationWithSelectedRight = matchesAssociationWithSelectedRightLTR.concat(matchesAssociationWithSelectedRightRTL);
}
const sentEmails = () => {
alertHandler.notImplemented();
};
return (
<>
<React.Fragment>
<Container>
<Block>
<Row>
......@@ -192,7 +204,7 @@ const Matchings = ({ association }) => {
</Col>
<Col>
<div className={"text-right"}>
<Button color={"secondary"} variant={"outlined"} onClick={alertHandler.notImplemented}>
<Button color={"secondary"} variant={"outlined"} onClick={sentEmails}>
Email
</Button>
&nbsp;
......@@ -246,7 +258,7 @@ const Matchings = ({ association }) => {
</div>
</Block>
</Container>
</>
</React.Fragment>
);
};
......
import { withStyles } from "@material-ui/core";
import Container from "@material-ui/core/Container";
import MemberTable, {MemberTable2} from "App/Components/Tables/MemberTable";
import { Helper } from "App/Helper";
import React, { Component } from "react";
import {AlertHandlerContext} from "../../Contexts/AlertHandler";
import Block from "../../Components/PageLayout/Content/Block";
import {AlertHandlerContext} from "../../Contexts/AlertHandler";
import Members from "../../Contexts/Members";
import Container from "@material-ui/core/Container";
const MembersCurrentStyles = theme => ({
......
import Button from "@material-ui/core/Button";
import Container from "@material-ui/core/Container";
import Grid from "@material-ui/core/Grid";
import {makeStyles} from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import PropTypes from "prop-types";
import React from "react";
import {useParams} from "react-router-dom";
import Sushi from "../../../img/Sushi-11-me.jpg"; // Import using relative path
import {BackButton} from "../../Components/BackButton";
import InfoFormStateButton from "../../Components/Buttons/InfoFormStateButton";
import Wrapper from "../../Components/Fields/Wrapper";
import useInfoFormState from "../../Components/Hooks/useInfoFormState";
import ProfileInfo from "../../Components/Info/ProfileInfo";
import {Membership} from "../../Components/InfoForms/Membership";
import Block from "../../Components/PageLayout/Content/Block";
import Photo from "../../Components/Photo";
import {useMembers} from "../../Contexts/Members";
const useStyles = makeStyles((theme) => ({
profileBlock: {
width: "100%"
},
growingGridItem: {
flexGrow: 1
}
}));
const MembersDetail = ({ association }) => {
const classes = useStyles();
const { slug } = useParams();
const [membershipInfoFormState, toggleMembershipInfoFormState] = useInfoFormState("info");
const MembersAPI = useMembers();
const current_member = MembersAPI.list().filter(member=>member.profile.slug === slug)[0];
console.log(MembersAPI.list());
console.log(current_member);
return (
<React.Fragment>
<Container>
<BackButton/>
<Grid container spacing={2}>
<Grid item lg={7}>
<Photo photo={Sushi}/>
</Grid>
<Grid item className={classes.growingGridItem}>
<Block>
<Typography variant={"h5"}>Profile</Typography>
<hr className={"box-title-separator"}/>
<ProfileInfo
profile={current_member ? current_member.profile : {}}
/>
</Block>
</Grid>
</Grid>
<Block>
<Wrapper>
<Typography variant={"h5"}>Membership Details</Typography>
<InfoFormStateButton formStateToggle={toggleMembershipInfoFormState} formState={membershipInfoFormState}/>
</Wrapper>
<hr className={"box-title-separator"}/>
{ current_member &&
<Membership
infoOrForm={membershipInfoFormState}
membership={current_member}
association={association}
/>
}
</Block>
<Block>
<Wrapper>
<Typography variant={"h5"}>Groups</Typography>
&nbsp;
<Button color={"primary"} variant={"outlined"}>Retrieve</Button>
</Wrapper>
<hr className={"box-title-separator"}/>
</Block>
<Block>
<Wrapper>
<Typography variant={"h5"}>Events</Typography>
&nbsp;
<Button color={"primary"} variant={"outlined"}>Retrieve</Button>
</Wrapper>
<hr className={"box-title-separator"}/>
</Block>
<Block>
<Wrapper>
<Typography variant={"h5"}>Purchases</Typography>
&nbsp;
<Button color={"primary"} variant={"outlined"}>Retrieve</Button>
</Wrapper>
<hr className={"box-title-separator"}/>
</Block>
<Block>
<Wrapper>
<Typography variant={"h5"}>Debt Collections</Typography>
&nbsp;
<Button color={"primary"} variant={"outlined"}>Retrieve</Button>
</Wrapper>
<hr className={"box-title-separator"}/>
</Block>
</Container>
</React.Fragment>
);
};
MembersDetail.propTypes = {