Commit c8e6bc0f authored by TJHeeringa's avatar TJHeeringa

Added various pages (financial overview, purchases, products)

parent 4856959e
......@@ -3,11 +3,12 @@ import "@icon/open-iconic/open-iconic.css";
import { library } from "@fortawesome/fontawesome-svg-core";
import { fab } from "@fortawesome/free-brands-svg-icons";
import {
faAt,
faBars,
faCheck,
faChevronLeft, faExchangeAlt,
faChevronLeft, faCookieBite, faExchangeAlt,
faHome,
faIgloo, faNewspaper,
faIgloo, faImage, faNewspaper,
faPlus, faShoppingCart,
faSignOutAlt, faSocks,
faTimes,
......@@ -27,7 +28,9 @@ import Routing from "./Routing/Routing";
require("bootstrap");
library.add(fab,faSignOutAlt, faUser, faIgloo, faBars, faCheck, faTimes, faHome, faPlus, faChevronLeft, faWarehouse, faShoppingCart, faSocks, faExchangeAlt, faNewspaper);
library.add(faSignOutAlt, faUser, faIgloo, faBars, faCheck, faTimes, faHome,
faPlus, faChevronLeft, faWarehouse, faShoppingCart, faSocks, faExchangeAlt,
faNewspaper, faImage, faCookieBite, faAt);
const App = (props) => {
......
import {MenuItem} from "@material-ui/core";
import Button from "@material-ui/core/Button";
import AccessTimeIcon from "@material-ui/icons/AccessTime";
import CreateIcon from "@material-ui/icons/Create";
import EuroIcon from "@material-ui/icons/Euro";
import SwapHorizIcon from "@material-ui/icons/SwapHoriz";
import IconHolder from "App/Components/Fields/IconHolder";
import TextField from "App/Components/Fields/TextField";
import Wrapper from "App/Components/Fields/Wrapper";
import { useAlertHandler } from "App/Contexts/AlertHandler";
import PropTypes from "prop-types";
import React, { useState } from "react";
import { ValidatorForm } from "react-material-ui-form-validator";
import {useAPI} from "../../Contexts/API";
import FileField from "../Fields/FileField";
import FontAwesomeIconHolder from "../Fields/FontAwesomeIconHolder";
import NumberField from "../Fields/NumberField";
import SelectField from "../Fields/SelectField";
const ProductForm = ({ association, product: propProduct, onProductChange}) => {
const API = useAPI();
const alertHandler = useAlertHandler();
let [product, setProduct] = useState(propProduct);
const resetAssociation = () => setProduct(propProduct);
const handleProductChange = (field, value) => setProduct(prevState=>(
{...prevState, [field]: value}
));
const handleSubmit = () => {
API.callv4({
url: association.url,
method: "PATCH",
object: {
[product.what_it_is_for+"_subject"]: product.subject,
[product.what_it_is_for+"_message"]: product.message,
},
on_succes: (association_with_updated_product) => {
onProductChange(association_with_updated_product);
},
on_failure: () => {
alertHandler.handleAlertHandler("red", "Update product failed");
}
});
};
return (
<ValidatorForm
onSubmit={handleSubmit}
onError={errors => console.log(errors)}
>
<Wrapper>
<IconHolder Icon={CreateIcon}/>
<TextField
name={"name"}
value={product.name}
onChange={(event)=>handleProductChange("name",event.target.value)}
/>
</Wrapper>
<Wrapper>
<FontAwesomeIconHolder icon={["fas", "image"]}/>
<FileField
name={"thumbnail"}
value={product.thumbnail}
onChange={(event)=>handleProductChange("thumbnail",event.target.value)}
/>
</Wrapper>
<Wrapper>
<IconHolder Icon={SwapHorizIcon}/>
<SelectField
name={"type"}
value={product.type}
onChange={(event)=>handleProductChange("type",event.target.value)}
>
<MenuItem value={"rentable"}>Rentable</MenuItem>
<MenuItem value={"purchasable"}>Purchasable</MenuItem>
</SelectField>
</Wrapper>
{ product.type === "rentable" &&
<Wrapper>
<IconHolder Icon={AccessTimeIcon}/>
<NumberField
name={"rental_period"}
value={product.rental_period}
onChange={(event)=>handleProductChange("rental_period",event.target.value)}
InputProps={{
inputProps: {
step: 1, min: 0
}
}}
/>
</Wrapper>
}
<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>
</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>
<Button variant={"outlined"} color={"secondary"}>
Remove from listing
</Button>
</div>
<div>
<Button type={"submit"} variant={"contained"} color={"primary"}>Save</Button>
&nbsp;
<Button variant={"contained"} onClick={resetAssociation}>Cancel</Button>
</div>
</Wrapper>
</ValidatorForm>
);
};
ProductForm.propTypes = {
association: PropTypes.object.isRequired,
onProductChange: PropTypes.func.isRequired,
product: PropTypes.shape({
name: PropTypes.string,
thumbnail: PropTypes.string,
type: PropTypes.string,
rental_period: PropTypes.number,
price_fixed: PropTypes.bool,
price: PropTypes.number,
}).isRequired,
};
export default ProductForm;
\ No newline at end of file
import {MenuItem} from "@material-ui/core";
import Button from "@material-ui/core/Button";
import AccessTimeIcon from "@material-ui/icons/AccessTime";
import EuroIcon from "@material-ui/icons/Euro";
import TodayIcon from "@material-ui/icons/Today";
import IconHolder from "App/Components/Fields/IconHolder";
import Wrapper from "App/Components/Fields/Wrapper";
import { useAlertHandler } from "App/Contexts/AlertHandler";
import PropTypes from "prop-types";
import React, { useState } from "react";
import { ValidatorForm } from "react-material-ui-form-validator";
import {useAPI} from "../../Contexts/API";
import DateField from "../Fields/DateField";
import FontAwesomeIconHolder from "../Fields/FontAwesomeIconHolder";
import NumberField from "../Fields/NumberField";
import SelectField from "../Fields/SelectField";
const PurchaseForm = ({ association, purchase: propPurchase, onPurchaseChange}) => {
const API = useAPI();
const alertHandler = useAlertHandler();
let [purchase, setPurchase] = useState(propPurchase);
const resetAssociation = () => setPurchase(propPurchase);
const handlePurchaseChange = (field, value) => setPurchase(prevState=>(
{...prevState, [field]: value}
));
const handleSubmit = () => {
API.callv4({
url: association.url,
method: "PATCH",
object: {
[purchase.what_it_is_for+"_subject"]: purchase.subject,
[purchase.what_it_is_for+"_message"]: purchase.message,
},
on_succes: (association_with_updated_purchase) => {
onPurchaseChange(association_with_updated_purchase);
},
on_failure: () => {
alertHandler.handleAlertHandler("red", "Update purchase failed");
}
});
};
return (
<ValidatorForm
onSubmit={handleSubmit}
onError={errors => console.log(errors)}
>
<Wrapper>
<FontAwesomeIconHolder icon={["fas", "cookie-bite"]}/>
<SelectField
name={"product"}
value={purchase.product}
onChange={(event)=>handlePurchaseChange("product",event.target.value)}
>
<MenuItem value={"misc"}>misc</MenuItem>
<MenuItem value={"misc"}>cookie</MenuItem>
<MenuItem value={"misc"}>pie</MenuItem>
<MenuItem value={"misc"}>chocolate milk</MenuItem>
</SelectField>
</Wrapper>
<Wrapper>
<IconHolder Icon={AccessTimeIcon}/>
<NumberField
name={"amount"}
value={purchase.amount}
onChange={(event)=>handlePurchaseChange("amount",event.target.value)}
InputProps={{
inputProps: {
step: 1, min: 0
}
}}
/>
</Wrapper>
<Wrapper>
<IconHolder Icon={EuroIcon}/>
<NumberField
name={"price"}
value={purchase.price}
onChange={(event)=>handlePurchaseChange("price",event.target.value)}
InputProps={{
inputProps: {
step: 0.1, min: 0
}
}}
/>
</Wrapper>
<Wrapper>
<IconHolder Icon={TodayIcon}/>
<DateField
name={"purchase date"}
value={purchase.purchase_date}
onChange={(date)=>handlePurchaseChange("purchase_date", date)}
/>
</Wrapper>
<Wrapper>
<div>
<Button variant={"outlined"} color={"secondary"}>
Delete
</Button>
</div>
<div>
<Button type={"submit"} variant={"contained"} color={"primary"}>Save</Button>
&nbsp;
<Button variant={"contained"} onClick={resetAssociation}>Cancel</Button>
</div>
</Wrapper>
</ValidatorForm>
);
};
PurchaseForm.propTypes = {
association: PropTypes.object.isRequired,
onPurchaseChange: PropTypes.func.isRequired,
purchase: PropTypes.shape({
product: PropTypes.string,
amount: PropTypes.string,
price: PropTypes.string,
rental_period: PropTypes.number,
purchase_date: PropTypes.string,
}).isRequired,
};
export default PurchaseForm;
\ No newline at end of file
import AccountBoxIcon from "@material-ui/icons/AccountBox";
import BookIcon from "@material-ui/icons/Book";
import EuroIcon from "@material-ui/icons/Euro";
import EventIcon from "@material-ui/icons/Event";
import GradeIcon from "@material-ui/icons/Grade";
import HomeIcon from "@material-ui/icons/Home";
......@@ -59,6 +60,11 @@ const AssociationMemberDrawer = (props) => {
primary: "Calendar",
to: url + "/calendar"
},
{
icon: <EuroIcon />,
primary: "Financial",
to: url + "/financial"
},
// {
// icon: <BookIcon />,
// primary: "Education",
......
This diff is collapsed.
......@@ -10,9 +10,7 @@ import {useAPI} from "../../Contexts/API";
import {useMembers} from "../../Contexts/Members";
const MemberTable = props => {
const { rows, headers, editingStateColumnExtensions, choiceSelectionOptions } = props;
const MemberTable = ({ rows, headers, editingStateColumnExtensions, choiceSelectionOptions, ...rest_props }) => {
const extremeHeaders = headers.map(header=>{return {"name": header.name, "title": header.title}; });
const [selection, setSelection] = useState([]);
......@@ -27,7 +25,7 @@ const MemberTable = props => {
showEditing={true}
editingStateColumnExtensions={editingStateColumnExtensions}
choiceSelectionOptions={choiceSelectionOptions}
{...props}
{...rest_props}
/>
</div>
);
......
......@@ -13,6 +13,9 @@ import {useAPI} from "../../Contexts/API";
import TextField from "../../Components/Fields/TextField";
import SelectField from "../../Components/Fields/SelectField";
import {MenuItem} from "@material-ui/core";
import FontAwesomeIconHolder from "../../Components/Fields/FontAwesomeIconHolder";
import IconHolder from "../../Components/Fields/IconHolder";
import ShareIcon from "@material-ui/icons/Share";
const Settings = ({ association: propAssociation }) => {
const alerthandler = useAlertHandler();
......@@ -69,6 +72,7 @@ const Settings = ({ association: propAssociation }) => {
onError={(errors)=>console.error(errors)}
>
<Wrapper>
<FontAwesomeIconHolder icon={"at"}/>
<TextField
name={"Domain"}
value={emailSettings.domain}
......@@ -81,6 +85,7 @@ const Settings = ({ association: propAssociation }) => {
/>
</Wrapper>
<Wrapper>
<IconHolder Icon={ShareIcon}/>
<SelectField
name={"email_on_create_matching"}
value={emailSettings.email_on_create_matching}
......
import Container from "@material-ui/core/Container";
import Typography from "@material-ui/core/Typography";
import {capitalCase} from "change-case";
import PropTypes from "prop-types";
import React, {useEffect, useState} from "react";
import { v4 as uuidv4 } from "uuid";
import CollapsableList from "../../Components/Lists/CollapsableList";
import Block from "../../Components/PageLayout/Content/Block";
import {useAPI} from "../../Contexts/API";
import {useAlertHandler} from "../../Contexts/AlertHandler";
import PurchaseForm from "../../Components/Forms/PurchaseForm";
import ExtremeTable from "../../Components/Tables/ExtremeTable";
import SimpleTable from "../../Components/Tables/SimpleTable";
import {padStart} from "lodash/string";
const MemberOverview = ({ association }) => {
const API = useAPI();
const alerthandler = useAlertHandler();
const purchases = Array.from({length: 12}, (_, i) => (
{debt_collection: uuidv4(), amount: Math.random()*100, debt_collection_date: `2020-${padStart(String(i+1), 2, "0")}-15`, cancelled: false, id: i}
));
const onPurchaseChange = () => {};
const RowDetail = ({ row }) => (
<div>
<Typography variant={"h5"}>Events</Typography>
<SimpleTable
headers={["event", "enrollment option", "cost"]}
rows={[["pie day", "eater", 2.5], ["pie day", "eater", 2.5], ["pie day", "eater", 2.5], ["pie day", "eater", 2.5]]}
/>
<Typography variant={"h5"}>Products</Typography>
<SimpleTable
headers={["product", "amount", "cost"]}
rows={[["pie", 3, 48], ["misc", 1, 16.38]]}
/>
<Typography variant={"h5"}>Membership fee</Typography>
<SimpleTable
headers={["type", "cost"]}
rows={[["General", 8.5]]}
/>
</div>
);
return (
<Container>
<Block>
<Typography variant={"h5"}>Financial Overview</Typography>
<hr className={"box-title-separator"}/>
<ExtremeTable
headers={[{name: "debt_collection_date"}, {name: "amount"}, {name: "cancelled"}]}
rows={purchases}
showGrouping={false}
showSelect={false}
showSearch={false}
showColumnChooser={false}
booleanColumns={["cancelled"]}
currencyColumns={["amount"]}
dateColumns={["debt_collection_date"]}
showDetail={true}
RowDetail={RowDetail}
/>
</Block>
</Container>
);
};
MemberOverview.propTypes = {
association: PropTypes.object.isRequired
};
export default MemberOverview;
import Button from "@material-ui/core/Button";
import Container from "@material-ui/core/Container";
import Typography from "@material-ui/core/Typography";
import EuroIcon from "@material-ui/icons/Euro";
import PropTypes from "prop-types";
import React, {useEffect, useState} from "react";
import {ValidatorForm} from "react-material-ui-form-validator";
import IconHolder from "../../Components/Fields/IconHolder";
import NumberField from "../../Components/Fields/NumberField";
import Wrapper from "../../Components/Fields/Wrapper";
import Block from "../../Components/PageLayout/Content/Block";
......@@ -66,6 +68,7 @@ const Settings = ({ association: propAssociation }) => {
onError={(errors)=>console.error(errors)}
>
<Wrapper>
<IconHolder Icon={EuroIcon}/>
<NumberField
name={"Debt Collection Threshold"}
value={financialSettings.debt_collection_threshold}
......
......@@ -9,6 +9,11 @@ import {useAlertHandler} from "../../../Contexts/AlertHandler";
import {useAPI} from "../../../Contexts/API";
import { GroupMemberManagementAssociationMemberList } from "./GroupMemberManagementAssociationMemberList";
import { GroupMemberManagementGroupMemberList } from "./GroupMemberManagementGroupMemberList";
import Button from "@material-ui/core/Button";
import ArchiveIcon from "@material-ui/icons/Archive";
import UnarchiveIcon from "@material-ui/icons/Unarchive";
import ToggleGroup from "../../../Components/Toggle/ToggleGroup";
import Wrapper from "../../../Components/Fields/Wrapper";
const GroupMemberManagement = ({ association }) => {
......@@ -16,6 +21,7 @@ const GroupMemberManagement = ({ association }) => {
const alerthandler = useAlertHandler();
const { slug } = useParams();
const [archive, setArchive] = useState(true);
const [group, setGroup] = useState({});
const [memberships, setMemberships] = useState([]);
......@@ -71,13 +77,25 @@ const GroupMemberManagement = ({ association }) => {
return !profile_urls.includes(row.url);
};
const archiveButtons = [
{ value: true, ariaLabel: "Archived", Icon: ArchiveIcon },
{ value: false, ariaLabel: "Not Archived", Icon: UnarchiveIcon }
];
return (
<div className={"CommitteeMemberManagement"}>
<BackButton container={true}/>
<Container style={{maxWidth: "100%"}}>
<Block>
<Typography variant={"h5"}>IN { group.board_group ? "BOARD" : "COMMITTEE" }</Typography>
<Wrapper>
<Typography variant={"h5"}>IN { group.board_group ? "BOARD" : "COMMITTEE" }</Typography>
<ToggleGroup
toggle={archive}
setToggle={setArchive}
toggleButtons={archiveButtons}
/>
</Wrapper>
<hr className={"box-title-separator"}/>
<GroupMemberManagementGroupMemberList
current_board={group.board_group}
......
......@@ -10,6 +10,7 @@ import { useGet } from "restful-react";
import Block from "../../../Components/PageLayout/Content/Block";
import { useAPI } from "../../../Contexts/API";
import Typography from "@material-ui/core/Typography";
const useStyles = makeStyles(theme=>({
......@@ -115,14 +116,14 @@ export const GroupMemberManagementAssociationMemberList = ({ group, association,
if (loadingDataFields) {
return (
<Block>
<h4>ALL MEMBERS</h4>
<Typography variant={"h5"}>ALL MEMBERS</Typography>
<hr className={"box-title-separator"}/>
</Block>
);
} else {
return (
<Block>
<h4>ALL MEMBERS</h4>
<Typography variant={"h5"}>ALL MEMBERS</Typography>
<hr className={"box-title-separator"}/>
<ExtremeTable
rowSelectionEnabledFilter={rowSelectionEnabledFilter}
......
......@@ -77,7 +77,7 @@ export const GroupMemberManagementGroupMemberList = ({ memberships: initRows, bo
defaultFilters={defaultFilters}
defaultSorting={defaultSorting}
editingStateColumnExtensions={editingStateColumnExtensions}
onChange={onChange}
onEdit={onChange}
/>
);
};
......
import Container from "@material-ui/core/Container";
import Typography from "@material-ui/core/Typography";
import {capitalCase} from "change-case";
import PropTypes from "prop-types";
import React, {useEffect, useState} from "react";
import CollapsableList from "../../../Components/Lists/CollapsableList";
import Block from "../../../Components/PageLayout/Content/Block";
import {useAPI} from "../../../Contexts/API";
import {useAlertHandler} from "../../../Contexts/AlertHandler";
import ProductForm from "../../../Components/Forms/ProductForm";
const Products = ({ association }) => {
const API = useAPI();
const alerthandler = useAlertHandler();
const product = {
name: "Misc",
thumbnail: null,
type: "purchasable",
rental_period: 30,
price_fixed: true,
price: 0
};
const onProductChange = () => {};
return (
<Container>
<Block>
<Typography variant={"h5"}>Products</Typography>
<hr className={"box-title-separator"}/>
<CollapsableList
component={ProductForm}
listItemText={"Misc"}
association={association}
product={product}
onProductChange={onProductChange}
/>
</Block>
</Container>
);
};
Products.propT