Commit bfff5087 authored by TJHeeringa's avatar TJHeeringa

Updated Matchings

parent c8e6bc0f
......@@ -1261,6 +1261,34 @@
"prop-types": "^15.7.2"
}
},
"@devexpress/dx-react-scheduler": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/@devexpress/dx-react-scheduler/-/dx-react-scheduler-2.7.4.tgz",
"integrity": "sha512-JQVe7jR9n3KSErifcpL0E2c4D5A7/yLo/TrRieZmb1BtqN/e3CwrzelrUkHJ1QSwStVfOMRP86gNSmhNdKLW1g==",
"requires": {
"@devexpress/dx-scheduler-core": "2.7.4"
}
},
"@devexpress/dx-react-scheduler-material-ui": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/@devexpress/dx-react-scheduler-material-ui/-/dx-react-scheduler-material-ui-2.7.4.tgz",
"integrity": "sha512-YQ7mj4m47OiZ2xr3B2yXucnMzyOIWRnHaqkQshcAKKnfgYn9if5QSaMzLlj9DrzaDHSsWMxDN0yXfHHEYdP6Rw==",
"requires": {
"@date-io/moment": "^1.3.11",
"@material-ui/pickers": "^3.2.10",
"clsx": "^1.0.4",
"prop-types": "^15.7.2"
}
},
"@devexpress/dx-scheduler-core": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/@devexpress/dx-scheduler-core/-/dx-scheduler-core-2.7.4.tgz",
"integrity": "sha512-hy4RYXQ9UNRHckuWk+oh+EUr+c293S64mHEuskd/6vfTro9gbit0axEiJWipH7SJuP1QYBxYFlu6ku1Qwr7nxg==",
"requires": {
"moment": "^2.24.0",
"rrule": "2.6.4"
}
},
"@emotion/hash": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
......@@ -10764,6 +10792,12 @@
"yallist": "^2.1.2"
}
},
"luxon": {
"version": "1.25.0",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.25.0.tgz",
"integrity": "sha512-hEgLurSH8kQRjY6i4YLey+mcKVAWXbDNlZRmM6AgWDJ1cY3atl8Ztf5wEY7VBReFbmGnwQPz7KYJblL8B2k0jQ==",
"optional": true
},
"make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
......@@ -17008,9 +17042,9 @@
}
},
"react-big-calendar": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-0.27.0.tgz",
"integrity": "sha512-06kYUT/SLn8H/1LwHNXpZtARhMpjOb/IEy8OC/FrcNt//GGSSpAaCpdFz129PaLzhDT0A1JXbJzzP7d52SCn8A==",
"version": "0.31.0",
"resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-0.31.0.tgz",
"integrity": "sha512-GIWGQtT9sSP6IIn4CPn6ZWpaN7QtYZsBQlvnhkCTTu7OQ4nCAuj6UeNf6qZzLopjvLRXw3hhYQY8QOCzBRFscw==",
"requires": {
"@babel/runtime": "^7.1.5",
"clsx": "^1.0.4",
......@@ -17021,7 +17055,7 @@
"lodash-es": "^4.17.11",
"memoize-one": "^5.1.1",
"prop-types": "^15.7.2",
"react-overlays": "^2.0.0-0",
"react-overlays": "^4.1.1",
"uncontrollable": "^7.0.0"
}
},
......@@ -17462,23 +17496,32 @@
"integrity": "sha512-8ltIY3bC7oGhj2nPAvWOGi+xGFybPNhJM0V1H8hY/whNcXgmDeaeoCMPPd8VatrpTsUWjb/vGzrmu6SrXVty3A=="
},
"react-overlays": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-2.1.1.tgz",
"integrity": "sha512-gaQJwmb8Ij2IGVt4D1HmLtl4A0mDVYxlsv/8i0dHWK7Mw0kNat6ORelbbEWzaXTK1TqMeQtJw/jraL3WOADz3w==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-4.1.1.tgz",
"integrity": "sha512-WtJifh081e6M24KnvTQoNjQEpz7HoLxqt8TwZM7LOYIkYJ8i/Ly1Xi7RVte87ZVnmqQ4PFaFiNHZhSINPSpdBQ==",
"requires": {
"@babel/runtime": "^7.4.5",
"@restart/hooks": "^0.3.12",
"dom-helpers": "^5.1.0",
"popper.js": "^1.15.0",
"@babel/runtime": "^7.12.1",
"@popperjs/core": "^2.5.3",
"@restart/hooks": "^0.3.25",
"@types/warning": "^3.0.0",
"dom-helpers": "^5.2.0",
"prop-types": "^15.7.2",
"uncontrollable": "^7.0.0",
"warning": "^4.0.3"
},
"dependencies": {
"popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
"@babel/runtime": {
"version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.13.tgz",
"integrity": "sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"@popperjs/core": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.6.0.tgz",
"integrity": "sha512-cPqjjzuFWNK3BSKLm0abspP0sp/IGOli4p5I5fKFAzdS8fvjdOwDCfZqAaIiXd9lPkOWi3SUUfZof3hEb7J/uw=="
}
}
},
......@@ -18366,6 +18409,15 @@
"inherits": "^2.0.1"
}
},
"rrule": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/rrule/-/rrule-2.6.4.tgz",
"integrity": "sha512-sLdnh4lmjUqq8liFiOUXD5kWp/FcnbDLPwq5YAc/RrN6120XOPb86Ae5zxF7ttBVq8O3LxjjORMEit1baluahA==",
"requires": {
"luxon": "^1.21.3",
"tslib": "^1.10.0"
}
},
"rsvp": {
"version": "4.8.5",
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
......
import "@icon/open-iconic/open-iconic.css";
import { library } from "@fortawesome/fontawesome-svg-core";
import { fab } from "@fortawesome/free-brands-svg-icons";
import {fab, faDiscord, faSnapchat} from "@fortawesome/free-brands-svg-icons";
import {
faAt,
faBars,
......@@ -30,7 +30,7 @@ require("bootstrap");
library.add(faSignOutAlt, faUser, faIgloo, faBars, faCheck, faTimes, faHome,
faPlus, faChevronLeft, faWarehouse, faShoppingCart, faSocks, faExchangeAlt,
faNewspaper, faImage, faCookieBite, faAt);
faNewspaper, faImage, faCookieBite, faAt, faSnapchat, faDiscord);
const App = (props) => {
......
import PropTypes from "prop-types";
import {useState} from "react";
/**
* A custom hooks that provides function to manage a state that is a list of objects.
*
*
*/
const useCollection = (initial_collection) => {
const [collection, setCollection] = useState(initial_collection);
const add = (item) => setCollection(prevState => (
prevState.concat(item)
));
const edit = (edited_item, key) => setCollection(prevState => (
prevState.map(item=> item[key] === edited_item[key] ? edited_item : item)
));
const remove = (removed_item, key) => setCollection(prevState => (
prevState.filter(item=> item[key] !== removed_item[key])
));
return [collection, {set: setCollection, add: add, edit: edit, remove: remove}];
};
useCollection.propTypes = {
initial_collection: PropTypes.array.isRequired
};
export default useCollection;
\ No newline at end of file
import PropTypes from "prop-types";
import {useEffect} from "react";
import {useAlertHandler} from "../../Contexts/AlertHandler";
import {useAPI} from "../../Contexts/API";
import useCollection from "./useCollection";
const useCollectionAPI = (url, queryParams) => {
const API = useAPI();
const alerthandler = useAlertHandler();
const [collection, {set: setCollection, add: add, edit: edit, remove: remove}] = useCollection([]);
const list = () => collection;
const refresh = () => {
API.callv4({
url: url,
queryParams: queryParams,
method: "GET",
on_succes: (data)=> setCollection(data.results),
on_failure: () => alerthandler.handleAlertHandler("red", "Loading data failed")
});
};
// eslint-disable-line react-hooks/exhaustive-deps
useEffect(refresh, ["on_mount_only"]);
return (
{
add: add,
edit: edit,
remove: remove,
list: list,
set: setCollection,
refresh: refresh
}
);
};
useCollectionAPI.propTypes = {
url: PropTypes.string.isRequired,
queryParams: PropTypes.object.isRequired
};
export default useCollectionAPI;
\ No newline at end of file
import React from "react";
const useContextHook = (context, error_message) => {
const used_context = React.useContext(context);
if (used_context === undefined) {
throw new Error(error_message);
}
return used_context;
};
export default useContextHook;
\ No newline at end of file
import React, {useEffect, useRef} from "react";
/**
* Hook to check what properties have changed between rerenders.
*
* Taken from https://stackoverflow.com/a/51082563
*
* @param props
*/
const useTraceUpdate = (props) => {
const prev = useRef(props);
useEffect(() => {
const changedProps = Object.entries(props).reduce((ps, [k, v]) => {
if (prev.current[k] !== v) {
ps[k] = [prev.current[k], v];
}
return ps;
}, {});
if (Object.keys(changedProps).length > 0) {
console.log("Changed props:", changedProps);
}
prev.current = props;
});
};
export default useTraceUpdate;
\ No newline at end of file
import Collapse from "@material-ui/core/Collapse";
import ListItem from "@material-ui/core/ListItem";
import ListItemText from "@material-ui/core/ListItemText";
import {fade, makeStyles} from "@material-ui/core/styles";
import ExpandLess from "@material-ui/icons/ExpandLess";
import ExpandMore from "@material-ui/icons/ExpandMore";
import PropTypes from "prop-types";
import React from "react";
const useStyles = makeStyles(theme => ({
tableStriped: {
backgroundColor: fade(theme.palette.primary.main, 0.35),
},
}));
/**
* A list with an unfold option. When unfolded the component passed is shown.
*
* @param Component
* @param listItemText
* @param passThroughProps
* @returns {JSX.Element}
* @constructor
*/
const CollapsableListItem = ({ component: Component, listItemText, ...passThroughProps }) => {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const handleToggle = () => setOpen(!open);
return (
<React.Fragment>
<ListItem button onClick={handleToggle} className={classes.tableStriped}>
<ListItemText primary={listItemText} />
{ open ? <ExpandLess /> : <ExpandMore /> }
</ListItem>
<Collapse in={open} timeout={"auto"} unmountOnExit>
<Component {...passThroughProps} handleToggle={handleToggle}/>
</Collapse>
</React.Fragment>
);
};
CollapsableListItem.propTypes = {
listItemText: PropTypes.string.isRequired,
component: PropTypes.oneOfType([PropTypes.object, PropTypes.func]).isRequired,
};
export default CollapsableListItem;
\ No newline at end of file
......@@ -46,7 +46,8 @@ import { fade } from "@material-ui/core/styles/colorManipulator";
import { Helper } from "App/Helper";
import saveAs from "file-saver";
import PropTypes from "prop-types";
import React, { Component, useCallback,useRef, useState } from "react";
import React, {useCallback, useEffect, useRef, useState} from "react";
// ****** Styling ******
......@@ -73,20 +74,14 @@ const ExtremeTable = (props) => {
defaultSorting, defaultFilters, defaultHiddenColumnNames, pageSizes, showDetail, RowDetail,
showSelect, showSelectAll, showEditing, showExporter, showGrouping, selectByRowClick,
booleanColumns, choiceColumns, choiceSelectionOptions, currencyColumns, dateColumns, numberColumns,
rowSelectionEnabledFilter, editingStateColumnExtensions, selection: {selection, setSelection},
rowSelectionEnabledFilter, editingStateColumnExtensions, selection: {selection: selection, setSelection:setSelection},
// rowSelectionEnabledFilter, editingStateColumnExtensions, selection, setSelection,
allowAdd, allowDelete, allowEdit, onAdd, onDelete, onEdit, showColumnChooser, showSearch} = props;
// ****** Functions ******
const getHiddenColumnsFilteringExtensions = hiddenColumnNames => hiddenColumnNames
.map(columnName => ({
columnName,
predicate: () => false,
}));
const onHiddenColumnNamesChange = hiddenColumnNames => setFilteringColumnExtensions(
getHiddenColumnsFilteringExtensions(hiddenColumnNames),
);
.map(columnName => ({columnName: columnName, filteringEnabled: false}));
const rowSelectionEnabled = row => rowSelectionEnabledFilter(row, selection);
......@@ -96,9 +91,15 @@ const ExtremeTable = (props) => {
const [currentPage, setCurrentPage] = useState(0);
const [sorting, setSorting] = useState(defaultSorting);
const [grouping, setGrouping] = useState([]);
const [hiddenColumnNames, setHiddenColumnNames] = useState(defaultHiddenColumnNames);
const [filteringColumnExtensions, setFilteringColumnExtensions] = useState(getHiddenColumnsFilteringExtensions(defaultHiddenColumnNames));
const [columnOrder, setColumnOrder] = useState(headers.map(header=>header.name));
const [expandedRowIds, setExpandedRowIds] = useState([]);
const [filters, setFilters] = useState(defaultFilters);
const [searchValue, setSearchState] = useState("");
useEffect(()=>setColumnOrder(headers.map(header=>header.name)), [headers]);
useEffect(()=>setFilteringColumnExtensions(getHiddenColumnsFilteringExtensions(hiddenColumnNames)), [hiddenColumnNames]);
// ****** Export ******
......@@ -127,6 +128,17 @@ const ExtremeTable = (props) => {
// );
// };
// ****** Filter Operation ******
const NumberOperations = [
"equal",
"notEqual",
"greaterThan",
"greaterThanOrEqual",
"lessThan",
"lessThanOrEqual",
];
// ****** Editors ******
const BooleanEditor = ({ value, onValueChange, disabled }) => {
......@@ -223,6 +235,7 @@ const ExtremeTable = (props) => {
<DataTypeProvider
formatterComponent={CurrencyFormatter}
editorComponent={CurrencyEditor}
availableFilterOperations={NumberOperations}
{...props}
/>
);
......@@ -238,6 +251,7 @@ const ExtremeTable = (props) => {
const NumberTypeProvider = props => (
<DataTypeProvider
editorComponent={NumberEditor}
availableFilterOperations={NumberOperations}
{...props}
/>
);
......@@ -279,10 +293,12 @@ const ExtremeTable = (props) => {
onCurrentPageChange={setCurrentPage}
/>,
<FilteringState
defaultFilters={defaultFilters}
filters={filters}
onFiltersChange={setFilters}
/>,
<SearchState
defaultValue={""}
value={searchValue}
onValueChange={setSearchState}
/>
];
const integrated_type_plugins = [
......@@ -297,9 +313,6 @@ const ExtremeTable = (props) => {
<Table
tableComponent={TableComponent}
/>,
<TableFilterRow
showFilterSelector
/>,
<TableColumnReordering
order={columnOrder}
onOrderChange={setColumnOrder}
......@@ -309,9 +322,12 @@ const ExtremeTable = (props) => {
showGroupingControls={showGrouping}
/>,
<TableColumnVisibility
defaultHiddenColumnNames={defaultHiddenColumnNames}
onHiddenColumnNamesChange={onHiddenColumnNamesChange}
/>
hiddenColumnNames={hiddenColumnNames}
onHiddenColumnNamesChange={setHiddenColumnNames}
/>,
<TableFilterRow
showFilterSelector
/>,
];
const miscelaneous_plugins = [
<Toolbar />,
......@@ -486,6 +502,8 @@ ExtremeTable.propTypes = {
booleanColumns: PropTypes.arrayOf(PropTypes.string),
/* List of column names that determines which columns need to rendered as selection field */
choiceColumns: PropTypes.arrayOf(PropTypes.string),
/* Object that maps the available choices for choiceColumns */
choiceSelectionOptions: PropTypes.object,
/* List of column names that determines which columns need to rendered as currency */
currencyColumns: PropTypes.arrayOf(PropTypes.string),
/* List of column names that determines which columns need to rendered as dates */
......@@ -496,7 +514,7 @@ ExtremeTable.propTypes = {
rowSelectionEnabledFilter: PropTypes.func,
/* Object that handles the selection state for the table. It consists of a function that is called once the selection is updated and the selection, usually constructed using `const [selection, setSelection] = useState([])`*/
selection: PropTypes.exact({
selection: PropTypes.arrayOf(PropTypes.number),
selection: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])),
setSelection: PropTypes.func
}),
/* Component that is rendered when the detail of a row is requested; takes the row as input. */
......@@ -520,6 +538,7 @@ ExtremeTable.defaultProps = {
editingStateColumnExtensions: [],
booleanColumns: [],
choiceColumns: [],
choiceSelectionOptions: {},
currencyColumns: [],
dateColumns: [],
numberColumns: [],
......
import "react-table/react-table.css";
import PropTypes from "prop-types";
import ExtremeTable from "App/Components/Tables/ExtremeTable";
import { Helper } from "App/Helper";
import React, {Component, useState} from "react";
import PropTypes from "prop-types";
import React, {useState} from "react";
import {AlertHandlerContext, useAlertHandler} from "../../Contexts/AlertHandler";
import {useAPI} from "../../Contexts/API";
import {useMembers} from "../../Contexts/Members";
import {useDataFields, useMembers, useMemberTypes} from "../../Contexts/Members";
import {
deriveMemberTableHeadersFromDataFields,
flattenMembers,
toMemberTableRows
} from "../../Transformers/Members";
const MemberTable = ({ rows, headers, editingStateColumnExtensions, choiceSelectionOptions, ...rest_props }) => {
......@@ -33,25 +33,79 @@ const MemberTable = ({ rows, headers, editingStateColumnExtensions, choiceSelect
export default MemberTable;
export const MemberTable2 = ({ association, initial_headers }) => {
export const MemberTable2 = ({ initial_headers ,...rest_props }) => {
const MembersApi = useMembers();
const [selection, setSelection] = useState([]);
const DataFieldsApi = useDataFields();
const MemberTypesApi = useMemberTypes();
const members = MembersApi.list() || [];
const data_fields = DataFieldsApi.list() || [];
const member_types = MemberTypesApi.list() || [];
// first flatten the member, since otherwise they don't fit nicely inside a single table
const rows = toMemberTableRows(flattenMembers(members));
const headers = deriveMemberTableHeadersFromDataFields(data_fields);
const getRowId = (row) => row.slug;
let defaultHiddenColumnNames = [];
if (initial_headers.length > 0) {
defaultHiddenColumnNames = headers
.map(header=>header.name)
.filter(header_name => !initial_headers.includes(header_name));
}
const booleanColumns = ["is_master"];
const choiceColumns = ["type"];
const choiceSelectionOptions = {
"type": member_types.map(member_type=>member_type.type),
};
const currencyColumns = ["membership_fee"];
const numberColumns = [];
const dateColumns = ["date_joined", "date_left"];
for (const data_field of data_fields) {
switch (data_field.type) {
case "Boolean":
booleanColumns.push(data_field.name);
break;
case "Number":
numberColumns.push(data_field.name);
break;
case "Choice":
choiceColumns.push(data_field.name);
choiceSelectionOptions[data_field.name] = data_field.choices.split(",");
break;
case "String":
// This can do with the default
break;
default:
break;
}
}
return (
<ExtremeTable
selection={{selection: selection, setSelection: setSelection}}
rows={members}
headers={["given_name"]}
rows={rows}
headers={headers}
showExporter={true}
showEditing={true}
defaultHiddenColumnNames={defaultHiddenColumnNames}
booleanColumns={booleanColumns}
choiceColumns={choiceColumns}
choiceSelectionOptions={choiceSelectionOptions}
currencyColumns={currencyColumns}
numberColumns={numberColumns}
dateColumns={dateColumns}
getRowId={getRowId}
{...rest_props}
/>
);
};
MemberTable2.propTypes = {
initial_headers: PropTypes.array
};
MemberTable2.defaultProps = {
initial_headers: []
};
import React, {createContext, useContext, useEffect, useState} from "react";
import {useAPI} from "./API";
const MembersContext = createContext({
members: [],
add: ()=>{},
edit: ()=>{},
remove: ()=>{},
refresh: ()=>{},
});
export function useMembers() {
const context = React.useContext(MembersContext);
if (context === undefined) {
throw new Error("useMembers must be used within a Members block");
}
return context;
}
const Members = ({ children, association, current, statusses }) => {
const API = useAPI();
const [members, setMembers] = useState([]);
const listMembers = () => members;
const addMember = () => {};
const editMember = () => {};
const removeMember = () => {};
const refreshMembers = () => {
API.callv4({
url: "/memberships",
queryParams: {association__slug: association.slug},
method: "GET",
on_succes: (data) => setMembers(data.results),
on_failure: () => {}
});
};
// eslint-disable-line react-hooks/exhaustive-deps
useEffect(()=>refreshMembers(), [association]);