️ 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 c877a77a authored by TJHeeringa's avatar TJHeeringa

Updated handling of calendar; updated my-data; created memberfield

parent 5d0e1abb
/* eslint-disable no-use-before-define */
import { makeStyles } from "@material-ui/core/styles";
import TextField from "@material-ui/core/TextField";
import Autocomplete from "@material-ui/lab/Autocomplete";
import React from "react";
import React, {useMemo} from "react";
import {useLoadedMembers} from "../../Contexts/Members";
const useStyles = makeStyles({
option: {
......@@ -16,23 +16,24 @@ const useStyles = makeStyles({
const MemberField = (props) => {
const classes = useStyles();
const { collection: memberships } = useLoadedMembers();
const profiles = useMemo(()=>memberships.map(membership=>({
url: membership.profile.url,
label: membership.profile.given_name + " " + membership.profile.surname + " (" + membership.profile.student_number + ")"
})), [memberships]);
return (
<Autocomplete
id={"country-select-demo"}
style={{ width: "100%" }}
options={countries}
options={profiles}
classes={{
option: classes.option,
}}
autoHighlight
getOptionLabel={(option) => option.label}
renderOption={(option) => (
<React.Fragment>
<span>{ countryToFlag(option.code) }</span>
{ option.label }
</React.Fragment>
)}
renderOption={(option) => (option.label)}
renderInput={(params) => (
<TextField
{...params}
......
......@@ -3,7 +3,6 @@ import Container from "@material-ui/core/Container";
import MenuItem from "@material-ui/core/MenuItem";
import Typography from "@material-ui/core/Typography";
import { Osiris } from "App/Components/Forms/Osiris";
import { getBIC } from "bic-from-iban";
import PropTypes from "prop-types";
import React, {useEffect, useMemo} from "react";
import { ValidatorForm } from "react-material-ui-form-validator";
......@@ -11,15 +10,13 @@ import { useGet } from "restful-react";
import { useAlertHandler} from "../../Contexts/AlertHandler";
import {useAPI} from "../../Contexts/API";
import CheckboxField from "../Fields/CheckboxField";
import CountryField, { countries } from "../Fields/CountryField";
import DateField from "../Fields/DateField";
import SelectField from "../Fields/SelectField";
import TextField from "../Fields/TextField";
import Wrapper from "../Fields/Wrapper";
import Block from "../PageLayout/Content/Block";
import isIban from "../ValidatorRules/isIban";
import isTruthy from "../ValidatorRules/isTruthy";
import CountryField, { countries } from "../Fields/CountryField";
const ProfileForm = ({container, ...rest}) => {
......
import { isFunction } from "lodash";
import PropTypes from "prop-types";
import {useEffect, useState} from "react";
......@@ -23,7 +24,7 @@ const useCollectionAPI = (url, queryParams, lazy, showLoadStatus) => {
queryParams: queryParams,
method: "GET",
on_succes: (data)=> {
const results = preRefresh ? preRefresh(data.results) : data.results;
const results = (preRefresh && isFunction(preRefresh)) ? preRefresh(data.results) : data.results;
setCollection(results);
setLoaded(true);
if (showLoadStatus) {
......
import PropTypes from "prop-types";
import {useEffect} from "react";
const useLoadedApi = ( api ) => {
useEffect(()=>{
if (!api.loaded) {
api.refresh();
}
}, [api.loaded]);
return api;
};
useLoadedApi.propTypes = {
api: PropTypes.object.isRequired
};
export default useLoadedApi;
\ No newline at end of file
......@@ -49,7 +49,7 @@ const PageContent = (props) => {
<div className={classes.photo_header} style={{width: size.width}}/>
<div className={classes.titlebar}>
<Typography className={classes.header} variant={"h3"}>{ title }</Typography>
<Typography className={classes.header} variant={"h3"}>{ title || "" }</Typography>
</div>
<div className={classes.sub_photo_container}>
......
......@@ -3,7 +3,7 @@ import ExtremeTable from "App/Components/Tables/ExtremeTable";
import PropTypes from "prop-types";
import React, {useMemo} from "react";
import {useDataFields, useMembers, useMemberTypes} from "../../Contexts/Members";
import {useDataFields, useLoadedMembers, useMembers, useMemberTypes} from "../../Contexts/Members";
import {useStudies} from "../../Contexts/Studies";
import {
deriveColumnBoundsFromDataFields,
......@@ -15,15 +15,10 @@ import {ToolbarButton} from "./Plugins/ToolbarButton";
const getRowId = (row) => row.slug;
const MemberTable = ({ initial_headers, row_filter, ...rest_props }) => {
const MembersApi = useMembers();
const DataFieldsApi = useDataFields();
const MemberTypesApi = useMemberTypes();
const StudyApi = useStudies();
const members = MembersApi.collection;
const data_fields = DataFieldsApi.collection;
const member_types = MemberTypesApi.collection;
const studies = StudyApi.collection;
const { collection: members, refresh: onRefresh } = useLoadedMembers();
const { collection: data_fields } = useDataFields();
const { collection: member_types } = useMemberTypes();
const { collection: studies } = useStudies();
// first flatten the member, since otherwise they don't fit nicely inside a single table
const rows = useMemo(()=> flattenMembers(members, studies).filter(row_filter), [members, row_filter, studies]);
......@@ -68,7 +63,6 @@ const MemberTable = ({ initial_headers, row_filter, ...rest_props }) => {
deleteCommand: "Detail"
};
const onRefresh = () => MembersApi.refresh();
const custom_miscelaneous_plugins = [
<ToolbarButton key={1} tooltip={"Reload table"} Icon={RefreshIcon} onClick={onRefresh}/>,
];
......
......@@ -3,11 +3,15 @@ import React, {createContext} from "react";
import useCollectionAPI from "../Components/Hooks/useCollectionAPI";
import useContextHook from "../Components/Hooks/useContextHook";
import useLoadedApi from "../Components/Hooks/useLoadedApi";
const MembersContext = createContext({});
const DataFieldsContext = createContext({});
const MemberTypesContext = createContext({});
export const useLoadedMembers = () => useLoadedApi(
useContextHook(MembersContext, "useMembers() hook must be used within a Members block")
);
export const useMembers = () => useContextHook(MembersContext, "useMembers() hook must be used within a Members block");
export const useDataFields = () => useContextHook(DataFieldsContext, "useDataFields() hook must be used within a Members block");
export const useMemberTypes = () => useContextHook(MemberTypesContext, "useMemberTypes() hook must be used within a Members block");
......
......@@ -34,7 +34,9 @@ const EventDetail = ({ association, eventsAPI, profile, enrollementsCollection,
const updateEventsAPI = (edited_event) => eventsAPI.edit(edited_event, "slug");
const [event, setEvent] = useState({association: association.url, event: "/events/" + slug, enrollment_option: ""});
const defaultEnrollment = useMemo(()=>({enrollment_option: "", association: association.url, event: "/events/" + slug}), [slug]);
const defaultEnrollment = useMemo(()=>(
{enrollment_option: "", association: association.url, event: "/events/" + slug}
), [association, slug]);
const [yourEnrollment, setYourEnrollment] = useState(defaultEnrollment);
const enrollmentOptions = enrollementOptionsCollection[0];
......@@ -62,7 +64,7 @@ const EventDetail = ({ association, eventsAPI, profile, enrollementsCollection,
const [formTypeEvent, setFormTypeEvent] = useState("info");
const [formTypeOption, setFormTypeOption] = useState("info");
const [open, setOpen] = useState();
const [open, setOpen] = useState(false);
const toggleOpen = () => setOpen(prevState=>!prevState);
const changeFormTypeEvent = () => {
......@@ -156,12 +158,12 @@ const EventDetail = ({ association, eventsAPI, profile, enrollementsCollection,
const youAreEnrolled = useMemo(()=>yourEnrollment.url !== undefined, [yourEnrollment]);
const youMayEnroll = useMemo(()=> {
const now = moment();
if (event.enrollable) {
if (event.enrollable && enrollmentOptions.length > 0) {
return (moment(event.enrollable_from) <= now && now <= moment(event.enrollable_until));
} else {
return false;
}
}, [event]);
}, [event, enrollmentOptions]);
const youMayUnenroll = useMemo(()=> {
const now = moment();
if (event.unenrollable) {
......
......@@ -15,10 +15,7 @@ const EventEnrollmentsManagement = ({ association, enrollementOptionsCollection
);
return (
<Members
association={association}
queryParam={{current: true, limit: 10000}}
>
<>
<BackButton/>
<EventEnrollmentsManagementEnrollments
enrollmentsAPI={enrollmentsAPI}
......@@ -32,7 +29,7 @@ const EventEnrollmentsManagement = ({ association, enrollementOptionsCollection
association={association}
event_slug={slug}
/>
</Members>
</>
);
};
......
import * as moment from "moment/moment";
import PropTypes from "prop-types";
import React from "react";
import React, {useCallback, useState} from "react";
import { Route, Switch } from "react-router-dom";
import useCollection from "../../Components/Hooks/useCollection";
......@@ -13,6 +14,8 @@ import Scheduler from "./Scheduler";
export const EventsRouter = ({ path, association, board, profile }) => {
const [currentCalendarDate, setCurrentCalendarDate] = useState(moment().format("YYYY-MM-DD"));
const queryParams = {limit: 10000, association__slug: association.slug};
const eventsQueryParams = board ? queryParams : {...queryParams, hidden_for_members: false};
......@@ -22,8 +25,8 @@ export const EventsRouter = ({ path, association, board, profile }) => {
const enrollementsCollection = useCollection([]);
const enrollementOptionsCollection = useCollection([]);
const addEvent = eventsAPI.add;
const updateEvent = (edited_event) => eventsAPI.edit(edited_event, "slug");
const addEvent = useCallback(()=>eventsAPI.add(), [eventsAPI]);
const updateEvent = useCallback((edited_event) => eventsAPI.edit(edited_event, "slug"), [eventsAPI]);
const event_types = eventTypesAPI.collection;
return (
......@@ -63,6 +66,8 @@ export const EventsRouter = ({ path, association, board, profile }) => {
<Route path={path}>
<Scheduler
board={board}
currentDate={currentCalendarDate}
setCurrentDate={setCurrentCalendarDate}
association={association}
event_types={event_types}
eventsAPI={eventsAPI}
......
......@@ -33,7 +33,7 @@ import Room from "@material-ui/icons/Room";
import WallpaperIcon from "@material-ui/icons/Wallpaper";
import classNames from "clsx";
import * as moment from "moment/moment";
import React, {useState} from "react";
import React, {useCallback, useMemo, useState} from "react";
import {ValidatorForm} from "react-material-ui-form-validator";
import {useHistory} from "react-router-dom";
......@@ -47,6 +47,7 @@ import TextField from "../../Components/Fields/TextField";
import Wrapper from "../../Components/Fields/Wrapper";
import {useAlertHandler} from "../../Contexts/AlertHandler";
import {useAPI} from "../../Contexts/API";
import useModalState from "../../Components/Hooks/useModalState";
const fromAPItoSchedulerFormat = (calendarEvent) => {
......@@ -170,7 +171,7 @@ const Content = withStyles(style, { name: "Content" })(({
});
const OverlayComponentBase = ({ visible, onCancel, onSave, onDelete, selectedEvent, event_types }) => {
const baseEvent = {enrollable: false, unenrollable: false};
const baseEvent = {enrollable: false, unenrollable: false, type: ""};
const [calendarEvent, setCalendarEvent] = useState({...baseEvent, ...selectedEvent});
const classes = useStyles();
......@@ -398,18 +399,16 @@ const OverlayComponentBase = ({ visible, onCancel, onSave, onDelete, selectedEve
);
};
const MySUScheduler = ({ association, event_types, eventsAPI, board }) => {
const dummyFunction = () => {};
const MySUScheduler = ({ association, event_types, eventsAPI, board, currentDate, setCurrentDate }) => {
const API = useAPI();
const alerthandler = useAlertHandler();
const events = eventsAPI.collection;
const [selectedCalendarEvent, setSelectedCalendarEvent] = useState({});
// const [currentDate, setCurrentDate] = useState("2021-01-05");
const [currentDate, setCurrentDate] = useState(moment().format("YYYY-MM-DD"));
const [dialogVisibility, setDialogVisibility] = useState(false);
const toggleDialogVisibility = () => setDialogVisibility(prevState => !prevState);
const [dialogVisibility, toggleDialogVisibility] = useModalState(false);
const onCancel = toggleDialogVisibility;
const onAdd = (added) => {
API.callv4({
url: "/events",
......@@ -439,7 +438,6 @@ const MySUScheduler = ({ association, event_types, eventsAPI, board }) => {
});
};
const onDelete = (deleted) => {
console.log(deleted);
API.callv4({
url: deleted.url,
method: "DELETE",
......@@ -452,30 +450,28 @@ const MySUScheduler = ({ association, event_types, eventsAPI, board }) => {
}
});
};
const onEditingAppointmentChange = (calendarEvent) => setSelectedCalendarEvent(fromSchedulertoAPIFormat(calendarEvent));
const onAddedAppointmentChange = (calendarEvent) => setSelectedCalendarEvent(fromSchedulertoAPIFormat(calendarEvent));
const OverlayComponent = connectProps(OverlayComponentBase, ()=>{
const OverlayComponent = useMemo(()=>connectProps(OverlayComponentBase, ()=>{
return ({
visible: dialogVisibility,
onCancel: onCancel,
onCancel: toggleDialogVisibility,
onSave: selectedCalendarEvent.slug ? onEdit : onAdd,
onDelete: onDelete,
selectedEvent: {...selectedCalendarEvent, association: association.url},
event_types: event_types
});
});
}), [dialogVisibility, toggleDialogVisibility, selectedCalendarEvent, event_types, association]);
const mapEvents = (events) => events.map(event=>fromAPItoSchedulerFormat(event));
const dummyFunction = () => {};
const calendarEvents = useMemo(() => events.map(event=>fromAPItoSchedulerFormat(event)), [events]);
return (
<Paper>
{ /*<ThemeSwitch/>*/ }
<Scheduler
data={mapEvents(events)}
data={calendarEvents}
height={876}
locale={"nl-NL"}
>
......
......@@ -28,17 +28,24 @@ import ExtremeTable from "../../Components/Tables/ExtremeTable";
import {ToolbarButton} from "../../Components/Tables/Plugins/ToolbarButton";
import {useAlertHandler} from "../../Contexts/AlertHandler";
import {useAPI} from "../../Contexts/API";
import {useMembers} from "../../Contexts/Members";
import {useLoadedMembers, useMembers} from "../../Contexts/Members";
import {Helper} from "../../Helper";
import ImportModal from "../Calendar/ImportModal";
import moment from "moment";
import MemberField from "../../Components/Fields/MemberField";
import CountryField, {countries} from "../../Components/Fields/CountryField";
import {orderBy} from "lodash";
/* eslint-disable no-shadow */
const Popup = ({ row, onChange, onApplyChanges, onCancelChanges, open }) => {
const MembersApi = useMembers();
const memberList = useMemo(()=>
MembersApi.collection.map(member=>[member.profile.url, member.profile.given_name + " " + member.profile.surname + " (" + member.profile.student_number + ")" ]),
[MembersApi.collection]);
const { collection: memberships } = useLoadedMembers();
const profiles = useMemo(()=>memberships.map(membership=>({
url: membership.profile.url,
label: membership.profile.given_name + " " + membership.profile.surname + " (" + membership.profile.student_number + ")"
})), [memberships]);
const profile = useMemo(()=>profiles.find(profile=>profile.url === row.profile),[row.profile]);
return (
<Dialog open={open} onClose={onCancelChanges} aria-labelledby={"form-dialog-title"} fullWidth={true}>
......@@ -50,17 +57,12 @@ const Popup = ({ row, onChange, onApplyChanges, onCancelChanges, open }) => {
<DialogContent>
<Wrapper>
<IconHolder Icon={PersonIcon}/>
<SelectField
name={"profile"}
<MemberField
label={"Member"}
value={row.profile || ""}
onChange={onChange}
value={profile}
onChange={(event, value)=>onChange({target: {name: "profile", value: value ? value.url : ""}})}
required
>
{ memberList.map(([value, option], index)=> (
<MenuItem key={index} value={value}>{ option }</MenuItem>
)) }
</SelectField>
/>
</Wrapper>
<Wrapper>
<IconHolder Icon={EventIcon}/>
......@@ -155,6 +157,7 @@ const Invoices = ({ association, DebtCollectionApi }) => {
), [MembersApi.collection]);
const onAdd = (to_be_new_row) => {
console.log(to_be_new_row);
API.callv4({
url: "/invoices",
method: "POST",
......@@ -248,10 +251,6 @@ const Invoices = ({ association, DebtCollectionApi }) => {
return {...entryObject, ...debtEntriesToThisCollection};
}, {});
console.log(debtCollections);
console.log(invoices);
console.log(entryToCollection);
const debtCollectionToDate = (url) => {
if (!url) {
return "";
......@@ -260,10 +259,12 @@ const Invoices = ({ association, DebtCollectionApi }) => {
return debt_collection ? moment(debt_collection.debt_collection_date).format("YYYY-MM-DD HH:mm") : "";
};
return invoices.map(invoice => ({
const invoiceWithEntries = invoices.map(invoice => ({
...invoice,
debt_entry: debtCollectionToDate(entryToCollection[invoice.debt_entry])
}));
return orderBy(invoiceWithEntries, ["date_issued"], ["desc"]);
}, [InvoiceAPI.collection, DebtCollectionApi.collection]);
return (
......
......@@ -6,7 +6,6 @@ import Typography from "@material-ui/core/Typography";
import * as moment from "moment/moment";
import PropTypes from "prop-types";
import React, {useCallback, useMemo} from "react";
import {useHistory} from "react-router-dom";
import Sushi from "../../../../img/Sushi-11-me.jpg"; // Import using relative path
import {BackButton} from "../../../Components/BackButton";
......@@ -27,6 +26,7 @@ import {useAlertHandler} from "../../../Contexts/AlertHandler";
import {useAPI} from "../../../Contexts/API";
import MemberOverview from "../../Financial/MemberOverview";
import {Profile} from "../../../Components/InfoForms/Profile";
import ExtremeTable from "../../../Components/Tables/ExtremeTable";
const useStyles = makeStyles((theme) => ({
profileBlock: {
......@@ -43,62 +43,80 @@ const MembersDetail = ({ association, current_member, onMembershipUpdate, onProf
const API = useAPI();
const AlertHandler = useAlertHandler();
const history = useHistory();
const [membershipInfoFormState, toggleMembershipInfoFormState] = useInfoFormState("info");
const [profileInfoFormState, toggleProfileInfoFormState] = useInfoFormState("info");
const [specificDataInfoFormState, toggleSpecificDataInfoFormState] = useInfoFormState("info");
const [modalOpen, toggleModal] = useModalState(false);
const your_slug = API.sunmember.preferred_profile.slug;
const slug = current_member.profile.slug;
const is_you = your_slug === slug;
const GroupMembershipsAPI = useCollectionAPI("/group_memberships",
{profile__slug: slug, association__slug: association.slug, unfolded: true}, true, true);
const EnrollementsAPI = useCollectionAPI("/enrollments",
{profile__slug: slug, association__slug: association.slug, expand: "event,enrollment_option"}, true, true);
const PurchasesAPI = useCollectionAPI("/purchases", {profile__slug: slug}, true, true);
const InvoicesAPI = useCollectionAPI("/invoices", {profile__slug: slug}, true, true);
const DebtCollectionAPI = useCollectionAPI("/debt_entries", {profile__slug: slug}, true, true);
const retrieveMemberships = () => GroupMembershipsAPI.refresh();
const retrieveEnrollments = () => EnrollementsAPI.refresh();
// const retrievePurchases = () => PurchasesAPI.refresh;
const retrievePurchases = AlertHandler.notImplemented;
const retrieveInvoices = () => InvoicesAPI.refresh();
const retrieveDebtCollections = () => DebtCollectionAPI.refresh();
const {collection: group_memberships, refresh: retrieveMemberships} = useCollectionAPI("/group_memberships",
{profile__slug: slug, association__slug: association.slug, unfolded: true}, false, true);
const {collection: enrollments, refresh: retrieveEnrollments} = useCollectionAPI("/enrollments",
{profile__slug: slug, association__slug: association.slug, expand: "event,enrollment_option"}, false, true);
const {collection: purchases, refresh: retrievePurchases} = useCollectionAPI("/purchases",
{profile__slug: slug}, true, true);
const {collection: invoices, refresh: retrieveInvoices} = useCollectionAPI("/invoices",
{profile__slug: slug}, false, true);
const {collection: debt_collections, refresh: retrieveDebtCollections} = useCollectionAPI("/debt_entries",
{profile__slug: slug}, true, true);
const group_memberships = GroupMembershipsAPI.collection;
const enrollments = EnrollementsAPI.collection;
const purchases = PurchasesAPI.collection;
const invoices = InvoicesAPI.collection;
const debt_collections = DebtCollectionAPI.collection;
console.log(group_memberships);
const group_memberships_rows = useMemo(()=>group_memberships.map(group_membership=>
[group_membership.name, group_membership.duty, group_membership.date_joined, group_membership.date_left]
),[group_memberships]);
const group_memberships_headers = ["Name", "Duty", "Date Joined", "Date Left"];
const group_memberships_rows = useMemo(()=>group_memberships.map(group_membership=> ({
name: group_membership.price,
duty: group_membership.duty,
date_joined: group_membership.date_joined,
date_left: group_membership.date_left
})),[invoices]);
const group_memberships_headers = [
{name: "name", title: "Name"},
{name: "duty", title: "Duty"},
{name: "date_joined", title: "Date Joined"},
{name: "date_left", title: "Date Left"}
];
const enrollments_table_rows = useMemo(()=>enrollments.map(enrollment=>
[enrollment.event.name, moment.utc(enrollment.event.start_date).local().format("YYYY-MM-DD HH:mm"), moment.utc(enrollment.event.end_date).local().format("YYYY-MM-DD HH:mm"), enrollment.enrollment_option.name]
),[enrollments]);
const enrollments_table_headers = ["Event", "Start Date", "End Date", "Enrollment Options"];
const enrollments_rows = useMemo(()=>enrollments.map(enrollment=>({
event: enrollment.event.name,
start_date: moment.utc(enrollment.event.start_date).local().format("YYYY-MM-DD HH:mm"),
end_date: moment.utc(enrollment.event.end_date).local().format("YYYY-MM-DD HH:mm"),
enrollment_option: enrollment.enrollment_option.name
})),[enrollments]);
const enrollments_headers = [
{name: "event", title: "Event"},
{name: "start_date", title: "Start Date"},
{name: "end_date", title: "End Date"},
{name: "enrollment_option", title: "Enrollment Option"}
];
const purchases_rows = useMemo(()=>purchases.map(purchase=>
[purchase.product, purchase.price, purchase.amount, purchase.total, purchase.purchase_date]
),[purchases]);
const purchases_headers = ["Product", "Price", "Amount", "Total", "When"];
const invoices_rows = useMemo(()=>invoices.map(invoice=>
[invoice.price, invoice.reason]
),[invoices]);
const invoice_headers = ["Price", "Reason"];
const invoices_rows = useMemo(()=>invoices.map(invoice=> ({
price: invoice.price,
date_issued: invoice.date_issued,
reason: invoice.reason
})),[invoices]);
const invoices_headers = [
{name: "price", title: "Price"},
{name: "date_issued", title: "Date issued"},
{name: "reason", title: "Reason"}
];
const debt_collections_rows = useMemo(()=>debt_collections.map(debt_collection=>
[debt_collection.debt_collection_date, debt_collection.total]
),[debt_collections]);
const debt_collection_headers = ["Date", "Price"];
const debt_collections_rows = useMemo(()=>debt_collections.map(debt_collection=>({
price: debt_collection.debt_collection_date,
amount: debt_collection.amount,
})),[debt_collections]);
const debt_collections_headers = [
{name: "date", title: "Date"},
{name: "amount", title: "Amount"},
];
const membership = {
...current_member,
......@@ -231,29 +249,27 @@ const MembersDetail = ({ association, current_member, onMembershipUpdate, onProf
<Block>
<Wrapper>
<Typography variant={"h5"}>Groups</Typography>
&nbsp;
<Button color={"primary"} variant={"outlined"} onClick={retrieveMemberships}>
Retrieve
</Button>
</Wrapper>
<hr className={"box-title-separator"}/>
<SimpleTable