Commit 9cafd08b authored by TJHeeringa's avatar TJHeeringa

No fixes; various updates to components

parent 2f1d9ba6
......@@ -24,23 +24,71 @@ import { ValidatorForm } from "react-material-ui-form-validator";
import NumberField from "../Fields/NumberField";
import SelectField from "../Fields/SelectField";
import {useAPI} from "../../Contexts/API";
const useStyles = makeStyles(theme => ({
}));
export const GroupForm = ({ group, parentable_groups, handleGroupChange}) => {
export const GroupForm = ({ group, parentable_groups, handleGroupChange, update, onSuccess}) => {
const theme = useTheme();
const classes = useStyles(theme);
const alertHandler = useAlertHandler();
const API = useAPI();
const alerthandler = useAlertHandler();
const handleValidSubmit = (event, values) => {
const handleSubmit = (event, values) => {
// make a copy of the group so that the null key operation don't accidentally change the group prop
let toBeSentGroup = {...group};
// Drop all field with value null
let null_keys = [];
Object.entries(group).forEach(([key, value])=> {
if (value === null) {
null_keys.push(key);
}
});
null_keys.forEach(null_key=> delete toBeSentGroup[null_key]);
if (update) {
patchGroup(group);
} else {
postGroup(group);
}
};
const postGroup = (group) => {
return API.callv3({
url: "/groups",
method: "POST",
object: group,
on_succes: (data) => {
alerthandler.handleAlertHandler("green", "Save successful");
onSuccess(data);
},
on_failure: (data) => {
alerthandler.handleAlertHandler("red", "Save failed");
}
});
};
const patchGroup = (group) => {
return API.callv3({
url: group.url,
method: "PUT",
object: group,
on_succes: (data) => {
alerthandler.handleAlertHandler("green", "Save successful");
onSuccess(data);
},
on_failure: () => {
alerthandler.handleAlertHandler("red", "Save failed");
},
});
};
console.log(group.parent_group);
return (
<ValidatorForm
onSubmit={handleValidSubmit}
onSubmit={handleSubmit}
onError={errors => console.log(errors)}
>
<Typography variant={"h5"}>General</Typography>
......@@ -50,6 +98,7 @@ export const GroupForm = ({ group, parentable_groups, handleGroupChange}) => {
name={"Full name"}
value={group.full_name}
onChange={(event)=>handleGroupChange("full_name",event.target.value)}
required
/>
</Wrapper>
<Wrapper>
......@@ -58,6 +107,7 @@ export const GroupForm = ({ group, parentable_groups, handleGroupChange}) => {
name={"Short name"}
value={group.short_name}
onChange={(event)=>handleGroupChange("short_name",event.target.value)}
required
/>
</Wrapper>
<Wrapper>
......@@ -66,6 +116,7 @@ export const GroupForm = ({ group, parentable_groups, handleGroupChange}) => {
name={"description"}
value={group.description}
onChange={(event)=>handleGroupChange("description",event.target.value)}
required
multiline
/>
</Wrapper>
......@@ -99,6 +150,8 @@ export const GroupForm = ({ group, parentable_groups, handleGroupChange}) => {
<IconHolder Icon={EmailIcon}/>
<TextField
name={"email"}
validators={["isEmail"]}
errorMessages={["This is not a valid email"]}
value={group.email}
onChange={(event)=>handleGroupChange("email",event.target.value)}
/>
......@@ -113,6 +166,7 @@ export const GroupForm = ({ group, parentable_groups, handleGroupChange}) => {
onChange={date => handleGroupChange("founding_date", date)}
helperText={""}
disableFuture
required
/>
</Wrapper>
<Wrapper>
......@@ -128,10 +182,10 @@ export const GroupForm = ({ group, parentable_groups, handleGroupChange}) => {
<SelectField
name={"Parent group"}
value={group.parent_group}
onChange={date => handleGroupChange("parent_group", date)}
onChange={event => handleGroupChange("parent_group", event.target.value)}
>
{ parentable_groups.map((parentable_group, g)=>(
<MenuItem key={g} value={parentable_group.slug}>{ parentable_group.full_name }</MenuItem>
<MenuItem key={g} value={parentable_group.url}>{ parentable_group.full_name }</MenuItem>
)) }
</SelectField>
</Wrapper>
......@@ -155,9 +209,11 @@ export const GroupForm = ({ group, parentable_groups, handleGroupChange}) => {
GroupForm.propTypes = {
group: PropTypes.object.isRequired,
handleGroupChange: PropTypes.func.isRequired,
parentable_groups: PropTypes.arrayOf(PropTypes.object).isRequired
parentable_groups: PropTypes.arrayOf(PropTypes.object).isRequired,
update: PropTypes.bool.isRequired,
onSuccess: PropTypes.func
};
GroupForm.defaultProps = {
onSuccess: ()=> {}
};
\ No newline at end of file
......@@ -128,6 +128,7 @@ const ProfileFormWithoutContainer = ({ update, profile, onSuccess, disabled, han
<Wrapper>
<TextField
name={"Given name"}
helperText={"This is the name you are commonly addressed with; in Dutch: roepnaam"}
value={profile.given_name}
onChange={(event) => handleProfileChange("given_name", event.target.value)}
disabled={disabled}
......
......@@ -13,7 +13,6 @@ export const AssociationInfo = ({ association }) => {
data["Contact"] = {
"Zip Code": association.zip_code + (" ") + association.city,
"Address": association.address,
"Country": association.country,
"Phone number": association.phone_number,
"Email": association.email
};
......
import PropTypes from "prop-types";
import React, {useState} from "react";
import {useAlertHandler} from "../../Contexts/AlertHandler";
import {useAPI} from "../../Contexts/API";
import {GroupForm} from "../Forms/GroupForm";
import GroupInfo from "../Info/GroupInfo";
const Group = ({group: propGroup, parentable_groups, infoOrForm, ...props}) => {
const alerthandler = useAlertHandler();
const API = useAPI();
const [group, setGroup] = useState(propGroup);
const Group = ({association, group: propGroup, parentable_groups, infoOrForm, ...props}) => {
const [group, setGroup] = useState(propGroup.association ? propGroup : {...propGroup, association: association.url});
const handleGroupChange = (field, value) => {
setGroup(prevState => ({...prevState, [field]:value}));
......@@ -43,6 +38,7 @@ Group.propTypes = {
Group.defaultProps = {
group: {
association: null,
full_name: null,
short_name: null,
description: null,
......
import PropTypes from "prop-types";
import React, {useState} from "react";
import {useAlertHandler} from "../../Contexts/AlertHandler";
import {useAPI} from "../../Contexts/API";
import ProfileForm from "../Forms/ProfileForm";
import ProfileInfo from "../Info/ProfileInfo";
export const Profile = ({profile: propProfile, infoOrForm, ...props}) => {
const alerthandler = useAlertHandler();
const API = useAPI();
const [profile, setProfile] = useState(propProfile);
const handleProfileChange = (field, value) => {
......
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import GroupWorkIcon from "@material-ui/icons/GroupWork";
import TreeItem from "@material-ui/lab/TreeItem";
import TreeView from "@material-ui/lab/TreeView";
import PropTypes from "prop-types";
import React from "react";
const useStyles = makeStyles(theme=>({
root: {
color: theme.palette.text.secondary,
"&:hover > $content": {
backgroundColor: theme.palette.action.hover,
},
"&:focus > $content, &$selected > $content": {
backgroundColor: `var(--tree-view-bg-color, ${theme.palette.grey[400]})`,
color: "var(--tree-view-color)",
},
"&:focus > $content $label, &:hover > $content $label, &$selected > $content $label": {
backgroundColor: "transparent",
},
},
content: {
color: theme.palette.text.secondary,
borderTopRightRadius: theme.spacing(2),
borderBottomRightRadius: theme.spacing(2),
paddingRight: theme.spacing(1),
fontWeight: theme.typography.fontWeightMedium,
"$expanded > &": {
fontWeight: theme.typography.fontWeightRegular,
},
},
group: {
marginLeft: 0,
"& $content": {
paddingLeft: theme.spacing(2),
},
},
expanded: {},
selected: {},
label: {
fontWeight: "inherit",
color: "inherit",
},
labelRoot: {
display: "flex",
alignItems: "center",
padding: theme.spacing(0.5, 0),
},
labelIcon: {
marginRight: theme.spacing(1),
},
labelText: {
fontWeight: "inherit",
flexGrow: 1,
},
}));
const RecursiveTreeView = ({ nodes }) => {
const classes = useStyles();
const renderTree = (nodes, id) => {
const Icon = nodes.LabelIcon || GroupWorkIcon;
return (
<TreeItem
key={id}
nodeId={nodes.id}
label={
<div className={classes.labelRoot}>
<Icon color={"inherit"} className={classes.labelIcon} />
<Typography variant={"body2"} className={classes.labelText}>
{ nodes.primary }
</Typography>
<Typography variant={"caption"} color={"inherit"}>
{ nodes.secondary }
</Typography>
</div>
}
>
{ (Array.isArray(nodes.children) && nodes.children.length > 0)
? nodes.children.map((node, n) => renderTree(node, n))
: null
}
</TreeItem>
);
};
return (
<TreeView
className={classes.root}
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpanded={["root"]}
defaultExpandIcon={<ChevronRightIcon />}
>
{ Array.isArray(nodes) ? nodes.map((node, n)=>renderTree(node, n)): renderTree(nodes) }
</TreeView>
);
};
RecursiveTreeView.propTypes = {
nodes: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)])
};
RecursiveTreeView.defaultProps = {
nodes: {
id: "root",
text: "Parent",
LabelIcon: GroupWorkIcon,
children: [
{
id: "1",
text: "Child - 1",
LabelIcon: GroupWorkIcon,
},
{
id: "3",
text: "Child - 3",
LabelIcon: GroupWorkIcon,
children: [
{
id: "4",
text: "Child - 4",
LabelIcon: GroupWorkIcon,
},
],
},
],
}
};
export default RecursiveTreeView;
\ No newline at end of file
......@@ -85,7 +85,7 @@ const API = ({ children }) => {
};
const postAuthentication = () => {
api_callv3({
api_callv4({
url: "/users/me",
method: "GET",
on_succes: (sunmember)=>{
......
......@@ -14,7 +14,16 @@ const useStyles = makeStyles(theme => ({
const DebtCollectionDetail = ({debtCollection, ...props}) => {
const classes = useStyles();
const headers = ["Who", "Amount", "Identifier", "Bank Account", "Email"];
const headers = ["Who", "Amount", "Identifier", "Bank Account", "Email", "Stornering"];
const editingStateColumnExtensions = [
{ columnName: "who", editingEnabled: false },
{ columnName: "amount", editingEnabled: false },
{ columnName: "identifier", editingEnabled: false },
{ columnName: "bank_account", editingEnabled: false },
{ columnName: "email", editingEnabled: false },
{ columnName: "stornering", editingEnabled: true },
];
const extremeHeaders = headers.map(header=>{return {name: snakeCase(header), title: header};});
......@@ -24,20 +33,23 @@ const DebtCollectionDetail = ({debtCollection, ...props}) => {
<ExtremeTable
headers={extremeHeaders}
rows={debtCollection.debt_entries || []}
booleanColumns={["stornering"]}
showExporter={true}
showGrouping={false}
showSelect={false}
editingStateColumnExtensions={editingStateColumnExtensions}
showEditing={true}
selection={{selection: selection, setSelection: setSelection}}
{...props}
/>
);
};
DebtCollectionDetail.propTypes = {
debt_collection: PropTypes.object
debtCollection: PropTypes.object
};
CardGrid.defaultProps = {
debt_collection: {
debtCollection: {
debt_entries: []
}
};
......
import MomentUtils from "@date-io/moment";
import Button from "@material-ui/core/Button";
import Grid from "@material-ui/core/Grid";
import List from "@material-ui/core/List";
......@@ -7,20 +6,19 @@ import ListItemText from "@material-ui/core/ListItemText";
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import CalendarToday from "@material-ui/icons/CalendarToday";
import { KeyboardDateTimePicker, MuiPickersUtilsProvider } from "@material-ui/pickers";
import FormModal from "App/Components/Modals/FormModal";
import { useAlertHandler } from "App/Contexts/AlertHandler";
import { Helper } from "App/Helper";
import moment from "moment";
import PropTypes from "prop-types";
import React from "react";
import React, {useEffect, useState} from "react";
import { useGet } from "restful-react";
import DebtCollectionDetail from "./DebtCollectionDetail";
import Block from "../../Components/PageLayout/Content/Block";
import CheckboxField from "../../Components/Fields/CheckboxField";
import DateTimeField from "../../Components/Fields/DateTimeField";
import Wrapper from "../../Components/Fields/Wrapper";
import Block from "../../Components/PageLayout/Content/Block";
import {useAPI} from "../../Contexts/API";
import DateTimeField from "../../Components/Fields/DateTimeField";
import DebtCollectionDetail from "./DebtCollectionDetail";
const useStyles = makeStyles((theme) => ({
......@@ -57,6 +55,8 @@ const DebtCollections = (props) => {
const alertHandler = useAlertHandler();
const API = useAPI();
const [checkboxes, setCheckboxes] = useState([]);
const default_new_debt_collection = {
association: props.association.url,
debt_collection_date: moment().add(1,"days")
......@@ -67,10 +67,38 @@ const DebtCollections = (props) => {
queryParams: {limit: 10000, association__slug: props.association.slug, ordering: "-debt_collection_date"},
resolve: data => data && data.results.map(morphDataToDebtCollection)
});
let { data: events } = useGet({
path: "/events",
queryParams: {limit: 10000, association__slug: props.association.slug},
resolve: data => data && data.results.map(morphDataToDebtCollection)
});
useEffect(()=>{
if (events !== null) {
setCheckboxes( events.reduce(
(options, option) => ({
...options,
[option.slug]: false
}),
{}
));
}
}, [events]);
const [newDebtCollection, setNewDebtCollection] = React.useState(default_new_debt_collection);
const [selectedDebtCollection, setSelectedDebtCollection] = React.useState({});
const [modalOpen, setModalOpen] = React.useState(false);
const [membershipFeeCheckbox, setMembershipFeeCheckbox] = React.useState(false);
const [finalDebtCollectionCheckbox, setFinalDebtCollectionCheckbox] = React.useState(false);
const handleCheckboxChange = (field, value) => {
console.log(events);
console.log(field, value);
setCheckboxes(prevState=>({
...prevState,
[field]: value
}));
};
const morphDataToDebtCollection = (data) => {
data.debt_collection_date = moment(data.debt_collection_date);
......@@ -117,7 +145,7 @@ const DebtCollections = (props) => {
};
const addDebtCollection = () => {
API.callv3({
API.callv4({
url: "/debt_collections",
method: "POST",
object: morphNewDebtCollectionToApiData(newDebtCollection),
......@@ -135,6 +163,7 @@ const DebtCollections = (props) => {
console.log("debtCollections", loadingdebtCollections, debtCollections);
console.log("selectedDebtCollection", selectedDebtCollection);
console.log(checkboxes);
return (
<div className={classes.root}>
......@@ -155,6 +184,36 @@ const DebtCollections = (props) => {
helperText={"This is the date you are planning on collection the debt collection."}
/>
</Wrapper>
<Typography>Please select the events that should be included in the debt collection</Typography>
{ events && events.map((event,e)=>(
<Wrapper key={e}>
<CheckboxField
label={event.name}
value={checkboxes[event.slug]}
checked={checkboxes[event.slug]}
onChange={checkEvent=>handleCheckboxChange(event.slug, checkEvent.target.checked)}
labelPlacement={"end"}
/>
</Wrapper>
)) }
<Wrapper>
<CheckboxField
label={"Membership fee"}
value={membershipFeeCheckbox}
checked={membershipFeeCheckbox}
onChange={event=>setMembershipFeeCheckbox(event.target.checked)}
labelPlacement={"end"}
/>
</Wrapper>
<Wrapper>
<CheckboxField
label={"Final debt collection of the year"}
value={finalDebtCollectionCheckbox}
checked={finalDebtCollectionCheckbox}
onChange={event=>setFinalDebtCollectionCheckbox(event.target.checked)}
labelPlacement={"end"}
/>
</Wrapper>
</FormModal>
<Grid container spacing={1} className={classes.paper}>
......@@ -180,7 +239,9 @@ const DebtCollections = (props) => {
<Block>
<Typography variant={"h4"}>Debt Collection { selectedDebtCollection.debt_collection_date && "of " + selectedDebtCollection.debt_collection_date.format("LLLL") }</Typography>
<hr className={"box-title-separator"}/>
<DebtCollectionDetail debtCollection={selectedDebtCollection}/>
<DebtCollectionDetail
debtCollection={selectedDebtCollection}
/>
</Block>
</Grid>
</Grid>
......
......@@ -27,7 +27,6 @@ class Boards extends Component {
}
boardArrayToTree(board_array) {
console.log(board_array);
let board_parent = board_array.filter((board)=>{return board.parent_group === null;})[0];
let boards = board_array.filter((board)=>{return board.parent_group !== null;});
if (this.state.hide_board_parent) {
......
import "react-simple-tree-menu/dist/main.css";
import "./Groups.css";
import Typography from "@material-ui/core/Typography";
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 ToggleButton from "@material-ui/lab/ToggleButton";
import ToggleButtonGroup from "@material-ui/lab/ToggleButtonGroup";
import { Helper } from "App/Helper";
import BootstrapSwitchButton from "bootstrap-switch-button-react";
import { orderBy } from "lodash";
import React, { Component } from "react";
import SearchBar from "material-ui-search-bar";
import React, {Component, useState} from "react";
import { NavLink, withRouter } from "react-router-dom";
import TreeMenu from "react-simple-tree-menu";
import { Button, Col, Row } from "reactstrap";
import Wrapper from "../../../Components/Fields/Wrapper";
import RecursiveTreeView from "../../../Components/Lists/Tree";
import Block from "../../../Components/PageLayout/Content/Block";
import { GroupRouter} from "./GroupRouter";
import CardGrid from "../../../Components/Card/CardGrid";
import GroupCard from "../../../Components/Card/GroupCardV2";
import Sushi from "../../../../img/Sushi-11-me.jpg";
const ToggleArchiveButtons = () => {
const [archive, setArchive] = useState(true);
const handleAlignment = (event, newArchive) => {
if (newArchive !== null) {
setArchive(newArchive);
}
};
return (
<ToggleButtonGroup
value={archive}
exclusive
onChange={handleAlignment}
aria-label={"show archived or not"}
>
<ToggleButton value={true} aria-label={"left aligned"}>
<ArchiveIcon />
</ToggleButton>
<ToggleButton value={false} aria-label={"right aligned"}>
<UnarchiveIcon />
</ToggleButton>
</ToggleButtonGroup>
);
};
const ToggleLayoutButtons = () => {
const [alignment, setAlignment] = useState("tree");