diff --git a/admin/src/Membership/MemberBoxSpans.jsx b/admin/src/Membership/MemberBoxSpans.jsx index ead1cd04..1b140ae7 100644 --- a/admin/src/Membership/MemberBoxSpans.jsx +++ b/admin/src/Membership/MemberBoxSpans.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import "react-day-picker/lib/style.css"; import { Link } from "react-router-dom"; import CollectionTable from "../Components/CollectionTable"; @@ -11,113 +11,117 @@ import { get } from "../gateway"; import { confirmModal } from "../message"; import MembershipPeriodsInput from "./MembershipPeriodsInput"; -class MemberBoxSpans extends React.Component { - constructor(props) { - super(props); - this.collection = new Collection({ - type: Span, - url: `/membership/member/${props.match.params.member_id}/spans`, - pageSize: 0, - includeDeleted: true, - }); - this.state = { items: [], pending_labaccess_days: "?" }; - this.pending_actions = get({ - url: `/membership/member/${props.match.params.member_id}/pending_actions`, - }).then((r) => { - const sum_pending_labaccess_days = r.data.reduce((acc, value) => { - if (value.action.action === ADD_LABACCESS_DAYS) - return acc + value.action.value; - return acc; - }, 0); - this.setState({ - pending_labaccess_days: sum_pending_labaccess_days, +const MemberBoxSpans = (props) => { + const collection = new Collection({ + type: Span, + url: `/membership/member/${props.match.params.member_id}/spans`, + pageSize: 0, + includeDeleted: true, + }); + + const [pendingLabAccessDays, setPendingLabAccessDays] = useState("?"); + + useEffect(() => { + // Fetch pending actions + const fetchPendingActions = async () => { + const response = await get({ + url: `/membership/member/${props.match.params.member_id}/pending_actions`, }); - }); - } + const sumPendingLabAccessDays = response.data.reduce( + (acc, value) => { + if (value.action.action === ADD_LABACCESS_DAYS) + return acc + value.action.value; + return acc; + }, + 0, + ); + setPendingLabAccessDays(sumPendingLabAccessDays); + }; + + fetchPendingActions(); - componentDidMount() { - this.unsubscribe = this.collection.subscribe(({ items }) => { - this.setState({ items }); + // Subscribe to collection + const unsubscribe = collection.subscribe(() => { + // No need to handle the subscription data since it's unused }); - } - componentWillUnmount() { - this.unsubscribe(); - } + // Cleanup on component unmount + return () => { + unsubscribe(); + }; + }, [props.match.params.member_id]); - render() { - const deleteItem = (item) => - confirmModal(item.deleteConfirmMessage()) - .then(() => item.del()) - .then( - () => this.collection.fetch(), - () => null, - ); + const deleteItem = (item) => + confirmModal(item.deleteConfirmMessage()) + .then(() => item.del()) + .then( + () => collection.fetch(), + () => null, + ); - return ( -
-

Medlemsperioder

-

- {this.state.pending_labaccess_days} dagar labaccess - kommer läggas till vid en nyckelsynkronisering. -

-
- -

Spans

-
- ( - - - - {item.id} - - - - - {item.type} - - - - - - {item.creation_reason} - - - - - - - - - - - deleteItem(item)} - className="removebutton" - > - - - - - )} - /> -
- ); - } -} + return ( +
+

Medlemsperioder

+

+ {pendingLabAccessDays} dagar labaccess kommer läggas till + vid en nyckelsynkronisering. +

+
+ +

Spans

+
+ ( + + + + {item.id} + + + + + {item.type} + + + + + + {item.creation_reason} + + + + + + + + + + + deleteItem(item)} + className="removebutton" + > + + + + + )} + /> +
+ ); +}; export default MemberBoxSpans; diff --git a/admin/src/Membership/MemberExport.jsx b/admin/src/Membership/MemberExport.jsx index eff6e5f8..6de16010 100644 --- a/admin/src/Membership/MemberExport.jsx +++ b/admin/src/Membership/MemberExport.jsx @@ -1,17 +1,13 @@ -import React from "react"; +import React, { useState } from "react"; import { get } from "../gateway"; -class MemberExport extends React.Component { - constructor(props) { - super(props); - this.state = { - csv_content: null, - state: "none", - }; - } +function MemberExport() { + const [csvContent, setCsvContent] = useState(null); + const [state, setState] = useState("none"); - exportMembers() { - this.setState({ state: "loading" }); + // Function to export members + const exportMembers = () => { + setState("loading"); get({ url: "/membership/member/all_with_membership" }).then((data) => { const members = data.data; @@ -46,38 +42,34 @@ class MemberExport extends React.Component { ]); } - this.setState({ - state: "loaded", - csv_content: rows.map((r) => r.join(",")).join("\n"), - }); + setState("loaded"); + setCsvContent(rows.map((r) => r.join(",")).join("\n")); }); - } + }; - render() { - return ( -
-

Exportera medlemslista

- {this.state.csv_content && ( - - )} - {!this.state.csv_content && ( - this.exportMembers()} - > - Exportera alla aktiva medlemmar som CSV - {this.state.state === "loading" ? "..." : ""} - - )} -
- ); - } + return ( +
+

Exportera medlemslista

+ {csvContent && ( + + )} + {!csvContent && ( + + Exportera alla aktiva medlemmar som CSV + {state === "loading" ? "..." : ""} + + )} +
+ ); } export default MemberExport; diff --git a/admin/src/Membership/MembershipPeriodsInput.js b/admin/src/Membership/MembershipPeriodsInput.js index 45c27b90..e965494f 100644 --- a/admin/src/Membership/MembershipPeriodsInput.js +++ b/admin/src/Membership/MembershipPeriodsInput.js @@ -1,132 +1,118 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import CategoryPeriodsInput from "../Components/CategoryPeriodsInput"; import CategoryPeriods from "../Models/CategoryPeriods"; import { calculateSpanDiff, filterPeriods } from "../Models/Span"; import auth from "../auth"; import { post } from "../gateway"; -export default class MembershipPeriodsInput extends React.Component { - constructor(props) { - super(props); - this.unsubscribe = []; - this.categoryPeriodsList = [ - new CategoryPeriods({ category: "labaccess" }), - new CategoryPeriods({ category: "membership" }), - new CategoryPeriods({ category: "special_labaccess" }), - ]; - this.state = { showHistoric: true, saveDisabled: true }; - } +export default function MembershipPeriodsInput(props) { + const [showHistoric, setShowHistoric] = useState(true); + const [saveDisabled, setSaveDisabled] = useState(true); - canSave() { + const categoryPeriodsList = [ + new CategoryPeriods({ category: "labaccess" }), + new CategoryPeriods({ category: "membership" }), + new CategoryPeriods({ category: "special_labaccess" }), + ]; + + const canSave = () => { return ( - this.categoryPeriodsList.every((c) => c.isValid()) && - this.categoryPeriodsList.some((c) => c.isDirty()) + categoryPeriodsList.every((c) => c.isValid()) && + categoryPeriodsList.some((c) => c.isDirty()) ); - } + }; - componentDidMount() { - this.unsubscribe.push( - this.props.spans.subscribe(({ items }) => { - this.categoryPeriodsList.forEach((periods) => + useEffect(() => { + const unsubscribe = []; + unsubscribe.push( + props.spans.subscribe(({ items }) => { + categoryPeriodsList.forEach((periods) => periods.replace(filterPeriods(items, periods.category)), ); }), ); - this.categoryPeriodsList.forEach((cp) => { - this.unsubscribe.push( - cp.subscribe(() => - this.setState({ saveDisabled: !this.canSave() }), - ), - ); + categoryPeriodsList.forEach((cp) => { + unsubscribe.push(cp.subscribe(() => setSaveDisabled(!canSave()))); }); - } - - componentWillUnmount() { - this.unsubscribe.forEach((u) => u()); - } - render() { - const { showHistoric, saveDisabled } = this.state; - const { member_id, spans } = this.props; + return () => { + unsubscribe.forEach((u) => u()); + }; + }, [props.spans, categoryPeriodsList]); - const onSave = () => { - // Important, need to collect spans to delete and add before doing anything, when spans changes - // subscriptions on spans will start causing changes of category periods. - const deleteSpans = []; - const addSpans = []; - this.categoryPeriodsList.forEach((cp) => { - cp.merge(); - calculateSpanDiff({ - items: this.props.spans.items, - categoryPeriods: cp, - member_id, - deleteSpans, - addSpans, - }); + const onSave = () => { + const deleteSpans = []; + const addSpans = []; + categoryPeriodsList.forEach((cp) => { + cp.merge(); + calculateSpanDiff({ + items: props.spans.items, + categoryPeriods: cp, + member_id: props.member_id, + deleteSpans, + addSpans, }); + }); - const deleteIds = deleteSpans.map((s) => s.id).join(","); - const timestamp = new Date().getTime().toString(); - addSpans.forEach( - (s, i) => - (s.creation_reason = ( - timestamp + - i + - " gui_edit:" + - auth.getUsername() + - " replacing:" + - deleteIds - ).slice(0, 255)), - ); + const deleteIds = deleteSpans.map((s) => s.id).join(","); + const timestamp = new Date().getTime().toString(); + addSpans.forEach( + (s, i) => + (s.creation_reason = ( + timestamp + + i + + " gui_edit:" + + auth.getUsername() + + " replacing:" + + deleteIds + ).slice(0, 255)), + ); - const promises = []; - promises.push(...deleteSpans.map((s) => s.del())); - promises.push(...addSpans.map((s) => s.save())); - Promise.all(promises).then(() => { - spans.fetch(); + const promises = []; + promises.push(...deleteSpans.map((s) => s.del())); + promises.push(...addSpans.map((s) => s.save())); + Promise.all(promises).then(() => { + props.spans.fetch(); - post({ - url: `/webshop/member/${member_id}/ship_labaccess_orders`, - expectedDataStatus: "ok", - }); + post({ + url: `/webshop/member/${props.member_id}/ship_labaccess_orders`, + expectedDataStatus: "ok", }); - }; + }); + }; - return ( -
{ - e.preventDefault(); - onSave(); - return false; - }} - > - - - this.setState({ showHistoric: e.target.checked }) - } + return ( + { + e.preventDefault(); + onSave(); + return false; + }} + > + + setShowHistoric(e.target.checked)} + /> + {categoryPeriodsList.map((cp) => ( + - {this.categoryPeriodsList.map((cp) => ( - - ))} - - - ); - } + ))} + + + ); } diff --git a/admin/src/Membership/SpanShow.jsx b/admin/src/Membership/SpanShow.jsx index 2f8de734..2b331a7e 100644 --- a/admin/src/Membership/SpanShow.jsx +++ b/admin/src/Membership/SpanShow.jsx @@ -1,42 +1,31 @@ -import React from "react"; -import Span from "../Models/Span"; +import React, { useEffect, useState } from "react"; import * as _ from "underscore"; +import Span from "../Models/Span"; -class SpanShow extends React.Component { - constructor(props) { - super(props); - const { span_id } = props.match.params; - this.span = Span.get(span_id); - this.state = { data: {} }; - } +export default function SpanShow(props) { + const { span_id } = props.match.params; + const [data, setData] = useState({}); - componentDidMount() { - this.unsubscribe = this.span.subscribe(() => - this.setState({ data: this.span.saved }), - ); - } + const span = Span.get(span_id); - componentWillUnmount() { - this.unsubscribe(); - } + useEffect(() => { + const unsubscribe = span.subscribe(() => setData(span.saved)); - render() { - const { data } = this.state; + // Cleanup subscription on unmount + return () => unsubscribe(); + }, [span]); - return ( -
-

Medlemsperiod {data.span_id}

-
- {_.keys(data).map((key) => ( -
-
{key}:
-
{data[key]}
-
- ))} -
-
- ); - } + return ( +
+

Medlemsperiod {data.span_id}

+
+ {_.keys(data).map((key) => ( +
+
{key}:
+
{data[key]}
+
+ ))} +
+
+ ); } - -export default SpanShow;