diff --git a/admin/src/Membership/MemberBoxSpans.jsx b/admin/src/Membership/MemberBoxSpans.jsx index ead1cd04f..1b140ae75 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 eff6e5f87..5835ba53f 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; +export default MemberExport; \ No newline at end of file diff --git a/admin/src/Membership/MembershipPeriodsInput.js b/admin/src/Membership/MembershipPeriodsInput.js index 45c27b90f..cdd3957e7 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) => ( - - ))} - - - ); - } -} + ))} + + + ); +} \ No newline at end of file diff --git a/admin/src/Membership/SpanShow.jsx b/admin/src/Membership/SpanShow.jsx index 2f8de734b..5be9116e3 100644 --- a/admin/src/Membership/SpanShow.jsx +++ b/admin/src/Membership/SpanShow.jsx @@ -1,42 +1,31 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import Span from "../Models/Span"; import * as _ from "underscore"; -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)); + + // Cleanup subscription on unmount + return () => unsubscribe(); + }, [span]); - render() { - const { data } = this.state; - - return ( -
-

Medlemsperiod {data.span_id}

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

Medlemsperiod {data.span_id}

+
+ {_.keys(data).map((key) => ( +
+
{key}:
+
{data[key]}
+
+ ))} +
+
+ ); +} \ No newline at end of file diff --git a/admin/src/Messages/MessageAdd.js b/admin/src/Messages/MessageAdd.js index fa28b42e2..1944b38f5 100644 --- a/admin/src/Messages/MessageAdd.js +++ b/admin/src/Messages/MessageAdd.js @@ -1,35 +1,28 @@ -import React from "react"; +import React, { useState } from "react"; import MessageForm from "../Components/MessageForm"; import Message from "../Models/Message"; import { notifySuccess } from "../message"; -import { withRouter } from "react-router"; +import { useHistory } from "react-router-dom"; -class MessageAdd extends React.Component { - constructor(props) { - super(props); - this.message = new Message(); - } +export default function MessageAdd() { + const [message] = useState(new Message()); + const navigate = useHistory(); - onSend() { - const { router } = this.props; - this.message.save().then(() => { - router.push("/messages"); + const onSend = () => { + message.save().then(() => { + navigate("/messages"); notifySuccess("Ditt meddelande har skickats"); }); - } + }; - render() { - return ( -
-

Skapa utskick

- this.onSend()} - /> -
- ); - } -} - -export default withRouter(MessageAdd); + return ( +
+

Skapa utskick

+ +
+ ); +} \ No newline at end of file diff --git a/admin/src/Messages/MessageShow.js b/admin/src/Messages/MessageShow.js index 6860fcc3f..e266f0c6f 100644 --- a/admin/src/Messages/MessageShow.js +++ b/admin/src/Messages/MessageShow.js @@ -1,74 +1,66 @@ -import React from "react"; -import { withRouter } from "react-router"; +import React, { useState, useEffect } from "react"; +import { useParams } from "react-router-dom"; import Message from "../Models/Message"; import DateTimeShow from "../Components/DateTimeShow"; -class MessageShow extends React.Component { - constructor(props) { - super(props); - const { id } = props.match.params; - this.message = Message.get(id); - this.state = { message: {} }; - } +export default function MessageShow() { + const { id } = useParams(); + const [message, setMessage] = useState({}); + + useEffect(() => { + const messageInstance = Message.get(id); + const unsubscribe = messageInstance.subscribe(() => { + setMessage(messageInstance); + }); - componentDidMount() { - this.unsubscribe = this.message.subscribe(() => - this.setState({ message: this.message }), - ); - } + return () => { + unsubscribe(); + }; + }, [id]); - componentWillUnmount() { - this.unsubscribe(); - } - - render() { - const { message } = this.state; - return ( -
-

Utskick

-
- - - - - - - - - - - - - - - - - - - - - - - -
Created - -
Status{Message.statusText(message)}
Sent - -
Recipient{message.recipient}
Template Used{message.template}
-
- -
-

-

-
-
-
+ return ( +
+

Utskick

+
+ + + + + + + + + + + + + + + + + + + + + + + +
Created + +
Status{Message.statusText(message)}
Sent + +
Recipient{message.recipient}
Template Used{message.template}
- ); - } -} -export default withRouter(MessageShow); +
+

+

+
+
+
+
+ ); +} \ No newline at end of file diff --git a/admin/src/Sales/AccountingBox.jsx b/admin/src/Sales/AccountingBox.jsx index 5e4a55a95..df5a761d5 100644 --- a/admin/src/Sales/AccountingBox.jsx +++ b/admin/src/Sales/AccountingBox.jsx @@ -1,34 +1,23 @@ import React from "react"; import { NavItem } from "../nav"; -import { withRouter } from "react-router"; -class AccountingBox extends React.Component { - constructor(props) { - super(props); - } +export default function AccountingBox({ children }) { + return ( +
+

Bokföring

- render() { - return ( -
-

Bokföring

+
    + Exportera + + Produkter + + Konton + + Kostnadsställen + +
-
    - - Exportera - - - Produkter - - Konton - - Kostnadsställen - -
- - {this.props.children} -
- ); - } -} - -export default withRouter(AccountingBox); + {children} +
+ ); +} \ No newline at end of file diff --git a/admin/src/Sales/AccountingProduct.js b/admin/src/Sales/AccountingProduct.js index f926488eb..3425fa625 100644 --- a/admin/src/Sales/AccountingProduct.js +++ b/admin/src/Sales/AccountingProduct.js @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { Link } from "react-router-dom"; import Select from "react-select"; import * as _ from "underscore"; @@ -6,125 +6,73 @@ import CollectionTable from "../Components/CollectionTable"; import Currency from "../Components/Currency"; import SearchBox from "../Components/SearchBox"; import Collection from "../Models/Collection"; -import CollectionNavigation from "../Models/CollectionNavigation"; import Product from "../Models/Product"; import ProductAccountsCostCenters from "../Models/ProductAccountsCostCenters"; import { get } from "../gateway"; import { showError } from "../message"; -const filterOptions_account = (items_account, options_account) => { - const current = new Set(items_account.map((i) => i.id)); - return options_account.filter((o) => !current.has(o.id)); -}; - -const filterOptions_cost_center = (items_cost_center, options_cost_center) => { - const current = new Set(items_cost_center.map((i) => i.id)); - return options_cost_center.filter((o) => !current.has(o.id)); -}; - -const updateOptions_account = (options_account) => (prevState) => { - let options = [ - { - account: "No modification", - id: 0, - description: "Do not change account", - }, - ].concat(filterOptions_account(prevState.items_account, options_account)); - return { - showOptions_account: options, - options_account, - }; -}; - -const updateOptions_cost_center = (options_cost_center) => (prevState) => { - let options = [ - { - cost_center: "No modification", - id: 0, - description: "Do not change cost center", - }, - ].concat( - filterOptions_cost_center( - prevState.items_cost_center, - options_cost_center, - ), - ); - return { - showOptions_cost_center: options, - options_cost_center, - }; -}; - -class AccountingProduct extends CollectionNavigation { - constructor(props) { - super(props); - const { search, page } = this.state; - this.collection = new Collection({ - type: Product, - url: "/webshop/product", - expand: "product_accounting", - search: search, - page: page, - pageSize: 0, - filter_out_key: "type", - filter_out_value: "debit", - includeDeleted: true, - }); - this.state = { - categories: null, - items_account: [], - options_account: [], - selectedOption_account: null, - items_cost_center: [], - options_cost_center: [], - selectedOption_cost_center: null, - accounts: null, - cost_centers: null, - selected_product_id: [], - latest_selected_row_index: null, - }; - - this.product = new Product(); - this.product_accounts_cost_centers = new ProductAccountsCostCenters(); - +const AccountingProduct = () => { + const [categories, setCategories] = useState(null); + const [transactionAccount, setTransactionAccount] = useState(null); + const [transactionCostCenter, setTransactionCostCenter] = useState(null); + const [selectedOptionAccount, setSelectedOptionAccount] = useState(null); + const [showOptionsAccount, setShowOptionsAccount] = useState([]); + const [selectedOptionCostCenter, setSelectedOptionCostCenter] = + useState(null); + const [showOptionsCostCenter, setShowOptionsCostCenter] = useState([]); + const [selectedProductId, setSelectedProductId] = useState([]); + + const collection = new Collection({ + type: Product, + url: "/webshop/product", + expand: "product_accounting", + page: 1, + pageSize: 0, + filter_out_key: "type", + filter_out_value: "debit", + includeDeleted: true, + }); + + useEffect(() => { + // Fetch categories get({ url: "/webshop/category", params: { page_size: 0 } }) - .then( - (data) => { - const categories = _.reduce( - data.data, - (obj, item) => { - obj[item.id] = item.name; - return obj; - }, - {}, - ); - this.setState({ categories }); - }, - () => null, - ) + .then((data) => { + const fetchedCategories = _.reduce( + data.data, + (obj, item) => { + obj[item.id] = item.name; + return obj; + }, + {}, + ); + setCategories(fetchedCategories); + }) .catch((error) => { showError("

Failed to find categories

" + error.message); }); - get({ - url: "/webshop/transaction_account", - params: { page_size: 0 }, - }) - .then( - (data) => { - const transaction_account = _.reduce( - data.data, - (obj, item) => { - obj[item.id] = item.account; - return obj; + // Fetch transaction accounts + get({ url: "/webshop/transaction_account", params: { page_size: 0 } }) + .then((data) => { + const fetchedAccounts = _.reduce( + data.data, + (obj, item) => { + obj[item.id] = item.account; + return obj; + }, + {}, + ); + setTransactionAccount(fetchedAccounts); + setShowOptionsAccount( + [ + { + account: "No modification", + id: 0, + description: "Do not change account", }, - {}, - ); - this.setState({ transaction_account }); - this.setState(updateOptions_account(data.data)); - }, - () => null, - ) + ].concat(data.data), + ); + }) .catch((error) => { showError( "

Failed to find transaction accounts

" + @@ -132,401 +80,179 @@ class AccountingProduct extends CollectionNavigation { ); }); + // Fetch transaction cost centers get({ url: "/webshop/transaction_cost_center", params: { page_size: 0 }, }) - .then( - (data) => { - const transaction_cost_center = _.reduce( - data.data, - (obj, item) => { - obj[item.id] = item.cost_center; - return obj; + .then((data) => { + const fetchedCostCenters = _.reduce( + data.data, + (obj, item) => { + obj[item.id] = item.cost_center; + return obj; + }, + {}, + ); + setTransactionCostCenter(fetchedCostCenters); + setShowOptionsCostCenter( + [ + { + cost_center: "No modification", + id: 0, + description: "Do not change cost center", }, - {}, - ); - this.setState({ transaction_cost_center }); - this.setState(updateOptions_cost_center(data.data)); - }, - () => null, - ) + ].concat(data.data), + ); + }) .catch((error) => { showError( "

Failed to find transaction cost centers

" + error.message, ); }); - } - - selectOptionAccount(account) { - this.setState({ selectedOption_account: account }); - } - - selectOptionCostCenter(cost_center) { - this.setState({ selectedOption_cost_center: cost_center }); - } - - deserialize(x) { - return x; - } - - changeColor(activate = true, table_index) { - const row = document.getElementsByTagName("tr"); - table_index.forEach(colorRows); - - function colorRows(id) { - if (activate) { - row[id + 1].style.backgroundColor = "#ddd"; - } else { - row[id + 1].style.backgroundColor = ""; - } - } - } + }, []); - onSave(account, cost_center) { - const productIds = this.state.selected_product_id; - - productIds.forEach((product_id) => { - get({ url: "/webshop/accounting", params: { page_size: 0 } }) - .then((data) => { + const onSave = (account, costCenter) => { + selectedProductId.forEach((productId) => { + get({ url: "/webshop/accounting", params: { page_size: 0 } }).then( + (data) => { const accountings = data.data; - const current_accounting = accountings.filter( - (p) => p.product_id === product_id, + const currentAccounting = accountings.find( + (p) => p.product_id === productId, ); - const accounting_element_debit = - new ProductAccountsCostCenters( - current_accounting.filter( - (p) => p.type === "debit", - )[0], - ); - accounting_element_debit.product_id = product_id; - accounting_element_debit.type = "debit"; - accounting_element_debit.account_id = 1; - accounting_element_debit.fraction = 100; - accounting_element_debit.save().then(() => { - accounting_element_debit.reset(); - }); - const accounting_element_credit = - new ProductAccountsCostCenters( - current_accounting.filter( - (p) => p.type === "credit", - )[0], - ); - accounting_element_credit.product_id = product_id; - accounting_element_credit.type = "credit"; - accounting_element_credit.fraction = 100; + + const accountingElementCredit = + new ProductAccountsCostCenters(currentAccounting); + accountingElementCredit.product_id = productId; + accountingElementCredit.type = "credit"; + accountingElementCredit.fraction = 100; if (account && account.id !== 0) { - accounting_element_credit.account_id = account.id; + accountingElementCredit.account_id = account.id; } - if (cost_center && cost_center.id !== 0) { - accounting_element_credit.cost_center_id = - cost_center.id; + if (costCenter && costCenter.id !== 0) { + accountingElementCredit.cost_center_id = costCenter.id; } - return accounting_element_credit; - }) - .then((accounting_element_credit) => { - accounting_element_credit.save().then(() => { - accounting_element_credit.reset(); - this.collection.fetch(); + + return accountingElementCredit.save().then(() => { + collection.fetch(); }); - return accounting_element_credit; - }); + }, + ); }); - if (this.state.selected_product_id.length > 0) { - const table_index = []; - this.state.selected_product_id.forEach((product_id) => { - table_index.push( - this.collection.items - .map(function (e) { - return e.saved.id; - }) - .indexOf(product_id), - ); - }); - this.changeColor(false, table_index); - } - - this.setState({ selected_product_id: [] }); - this.setState({ latest_selected_row_index: null }); - } + setSelectedProductId([]); + }; - updateAddSelectedProductId(current_product_ids, element) { - if (Array.isArray(element)) { - this.setState({ - selected_product_id: current_product_ids.concat(element), - }); + const setSelectedRow = (event, element) => { + // const tableIndex = collection.items.findIndex((item) => item.id === element.id); + if (selectedProductId.includes(element.id)) { + setSelectedProductId((prev) => + prev.filter((id) => id !== element.id), + ); } else { - this.setState({ - selected_product_id: current_product_ids.concat([element]), - }); - } - } - - updateRemoveSelectedProductId(current_product_ids, element) { - this.setState({ - selected_product_id: current_product_ids.filter( - (item) => item !== element, - ), - }); - } - - updateResetSelectedProductId(element = null, table_index = []) { - if (this.state.selected_product_id.length > 0) { - const table_index = []; - this.state.selected_product_id.forEach((product_id) => { - table_index.push( - this.collection.items - .map(function (e) { - return e.saved.id; - }) - .indexOf(product_id), - ); - }); - this.changeColor(false, table_index); + setSelectedProductId((prev) => [...prev, element.id]); } - this.setState({ selected_product_id: [element] }); - this.changeColor(true, [table_index]); - } - - updateLatestSelectedRowIndex(element) { - this.setState({ latest_selected_row_index: element }); - } + }; - setSelectedRow(evt, element) { - const table_index = this.collection.items - .map(function (e) { - return e.saved.id; - }) - .indexOf(element.id); - const selected_product_id = this.state.selected_product_id; - const latest_selected_row_index = this.state.latest_selected_row_index; + return ( +
+
+

Hantera produkter för bokföring

+

+ På denna sida kan du ange och ta bort konton och + kostnadsställen för produkter. Markera en eller flera + produkter i listan och välj vilket konto och kostnadsställe + du vill applicera. +

+
- if (selected_product_id.indexOf(element.id) > -1) { - this.changeColor(false, [table_index]); - this.updateRemoveSelectedProductId(selected_product_id, element.id); - } else { - if (evt.shiftKey) { - if (latest_selected_row_index === null) { - this.changeColor(true, [table_index]); - this.updateAddSelectedProductId( - selected_product_id, - element.id, - ); - } else { - const new_product_id = []; - const new_table_index = []; - if (table_index > latest_selected_row_index) { - for ( - let i = latest_selected_row_index + 1; - i <= table_index; - i++ - ) { - if ( - selected_product_id.indexOf( - this.collection.items[i].id, - ) === -1 - ) { - new_product_id.push( - this.collection.items[i].id, - ); - new_table_index.push(i); - } +
{ + e.preventDefault(); + onSave(selectedOptionAccount, selectedOptionCostCenter); + }} + > +
+ + g.id} - getOptionLabel={(g) => - g.account + " : " + g.description - } - onChange={(account) => - this.selectOptionAccount(account) - } - /> - - - g.id} + getOptionLabel={(g) => + `${g.cost_center} : ${g.description}` + } + onChange={(costCenter) => + setSelectedOptionCostCenter(costCenter) + } /> -
+ + + + + +
+ {}} /> + + ( + setSelectedRow(event, item)}> + + + {item.name} + + + + {categories + ? categories[item.category_id] + : item.category_id} + + + kr + + {item.unit} + {transactionAccount?.[item.account_id]} + + {transactionCostCenter?.[item.cost_center_id]} + + + )} + />
- ); - } -} +
+ ); +}; -export default AccountingProduct; +export default AccountingProduct; \ No newline at end of file diff --git a/admin/src/Sales/GiftCardShow.js b/admin/src/Sales/GiftCardShow.js index 451326e8d..28c8ef3ac 100644 --- a/admin/src/Sales/GiftCardShow.js +++ b/admin/src/Sales/GiftCardShow.js @@ -1,93 +1,81 @@ -import React from "react"; -import { Link } from "react-router-dom"; +import React, { useState, useEffect } from "react"; +import { Link, useParams } from "react-router-dom"; import GiftCard from "../Models/GiftCard"; import Collection from "../Models/Collection"; import CollectionTable from "../Components/CollectionTable"; import GiftCardRow from "../Models/GiftCardRow"; import Currency from "../Components/Currency"; -class GiftCardShow extends React.Component { - constructor(props) { - super(props); - const { id } = props.match.params; - this.gift_card = GiftCard.get(id); - this.state = {}; - this.gift_cardRows = new Collection({ - type: GiftCardRow, - url: `/webshop/gift-card/${id}/products`, - pageSize: 0, - expand: "product", - }); - } +const GiftCardShow = () => { + const { id } = useParams(); // Get id from URL params + const [giftCardDetails, setGiftCardDetails] = useState({ email: '', validation_code: '' }); + + const gift_card = GiftCard.get(id); + const gift_cardRows = new Collection({ + type: GiftCardRow, + url: `/webshop/gift-card/${id}/products`, + pageSize: 0, + expand: "product", + }); - componentDidMount() { - this.unsubscribe = this.gift_card.subscribe(() => { - const { email, validation_code } = this.gift_card; - this.setState({ email, validation_code }); + useEffect(() => { + const unsubscribe = gift_card.subscribe(() => { + const { email, validation_code } = gift_card; + setGiftCardDetails({ email, validation_code }); }); - } - componentWillUnmount() { - this.unsubscribe(); - } + return () => unsubscribe(); // Cleanup on component unmount + }, [gift_card]); - render() { - const { email, validation_code } = this.state; - const { id } = this.props.match.params; + const { email, validation_code } = giftCardDetails; - return ( -
-
-

Presentkort #{id}

-
-

Email

- {email} -
-
-

Valideringskod

- {validation_code} -
+ return ( +
+
+

Presentkort #{id}

+
+

Email

+ {email}
-
-

Orderrader

- ( - - - - {item.name} - - - - {" "} - kr - - {item.product_quantity} - - kr - - - )} - /> +
+

Valideringskod

+ {validation_code}
- ); - } -} +
+

Orderrader

+ ( + + + + {item.name} + + + + {" "} + kr + + {item.product_quantity} + + kr + + + )} + /> +
+
+ ); +}; -export default GiftCardShow; +export default GiftCardShow; \ No newline at end of file diff --git a/admin/src/Sales/OrderShow.js b/admin/src/Sales/OrderShow.js index cd85ea056..504b41080 100644 --- a/admin/src/Sales/OrderShow.js +++ b/admin/src/Sales/OrderShow.js @@ -1,5 +1,5 @@ -import React from "react"; -import { Link } from "react-router-dom"; +import React, { useState, useEffect } from "react"; +import { Link, useParams } from "react-router-dom"; import Order from "../Models/Order"; import Collection from "../Models/Collection"; import CollectionTable from "../Components/CollectionTable"; @@ -8,117 +8,102 @@ import OrderAction from "../Models/OrderAction"; import Currency from "../Components/Currency"; import { dateTimeToStr } from "../utils"; -class OrderShow extends React.Component { - constructor(props) { - super(props); - const { id } = props.match.params; - this.order = Order.get(id); - this.state = {}; - this.orderRows = new Collection({ - type: OrderRow, - url: `/webshop/transaction/${id}/contents`, - pageSize: 0, - expand: "product", - }); - this.orderActions = new Collection({ - type: OrderAction, - url: `/webshop/transaction/${id}/actions`, - pageSize: 0, - }); - } +const OrderShow = () => { + const { id } = useParams(); // Get id from URL params + const [memberId, setMemberId] = useState(null); - componentDidMount() { - this.unsubscribe = this.order.subscribe(() => { - const { member_id } = this.order; - this.setState({ member_id }); - }); - } + const order = Order.get(id); + const orderRows = new Collection({ + type: OrderRow, + url: `/webshop/transaction/${id}/contents`, + pageSize: 0, + expand: "product", + }); + const orderActions = new Collection({ + type: OrderAction, + url: `/webshop/transaction/${id}/actions`, + pageSize: 0, + }); - componentWillUnmount() { - this.unsubscribe(); - } + useEffect(() => { + const unsubscribe = order.subscribe(() => { + const { member_id } = order; + setMemberId(member_id); + }); - render() { - const { member_id } = this.state; - const { id } = this.props.match.params; + return () => unsubscribe(); // Cleanup on component unmount + }, [order]); - return ( -
-
-

Order #{id}

-
-

Medlem

- - member_id {member_id} - -
-
-
-

Orderrader

- ( - - - - {item.name} - - - - {" "} - kr - - {item.count} - - kr - - - )} - /> -
-
-

Ordereffekter

- { - return ( - - {item.id} - {item.action_type} - - {item.value} - - - {item.completed_at - ? dateTimeToStr(item.completed_at) - : "pending"} - - - ); - }} - /> + return ( +
+
+

Order #{id}

+
+

Medlem

+ + member_id {memberId} +
- ); - } -} +
+

Orderrader

+ ( + + + + {item.name} + + + + {" "} + kr + + {item.count} + + kr + + + )} + /> +
+
+

Ordereffekter

+ ( + + {item.id} + {item.action_type} + {item.value} + + {item.completed_at + ? dateTimeToStr(item.completed_at) + : "pending"} + + + )} + /> +
+
+ ); +}; -export default OrderShow; +export default OrderShow; \ No newline at end of file diff --git a/admin/src/Sales/ProductAdd.js b/admin/src/Sales/ProductAdd.js index e71ecd094..ef406efcd 100644 --- a/admin/src/Sales/ProductAdd.js +++ b/admin/src/Sales/ProductAdd.js @@ -1,34 +1,24 @@ import React from "react"; - -import { browserHistory } from "../browser_history"; +import { useHistory } from "react-router-dom"; import Product from "../Models/Product"; import ProductForm from "../Components/ProductForm"; -class ProductAdd extends React.Component { - constructor(props) { - super(props); - this.product = new Product(); - } +const ProductAdd = () => { + const product = new Product(); + const history = useHistory(); + + const handleSave = () => { + product.save().then(() => { + history.replace(`/sales/product/${product.id}`); + }); + }; - render() { - return ( -
-

Skapa produkt

- - this.product - .save() - .then(() => - browserHistory.replace( - "/sales/product/" + this.product.id, - ), - ) - } - /> -
- ); - } -} + return ( +
+

Skapa produkt

+ +
+ ); +}; -export default ProductAdd; +export default ProductAdd; \ No newline at end of file diff --git a/admin/src/Sales/ProductEdit.jsx b/admin/src/Sales/ProductEdit.jsx index 10501ce2a..8e2e2a870 100644 --- a/admin/src/Sales/ProductEdit.jsx +++ b/admin/src/Sales/ProductEdit.jsx @@ -1,37 +1,52 @@ -import React from "react"; -import { withRouter } from "react-router"; +import React, { useEffect, useState } from "react"; +import { useHistory, useParams } from "react-router-dom"; import Product from "../Models/Product"; import ProductForm from "../Components/ProductForm"; import { confirmModal } from "../message"; -import { browserHistory } from "../browser_history"; -class ProductEdit extends React.Component { - constructor(props) { - super(props); - const { id } = props.match.params; - this.product = Product.getWithRelated(id); - } +const ProductEdit = () => { + const { id } = useParams(); // Use useParams to get the route parameter + const [product, setProduct] = useState(null); + const history = useHistory(); + + // Fetch the product on mount + useEffect(() => { + const fetchedProduct = Product.getWithRelated(id); + setProduct(fetchedProduct); + }, [id]); + + const handleSave = () => { + if (product) { + product.save(); + } + }; - render() { - return ( -
-

Redigera produkt

- this.product.save()} - onDelete={() => { - return confirmModal(this.product.deleteConfirmMessage()) - .then(() => this.product.del()) - .then(() => { - browserHistory.push("/sales/product/"); - }) - .catch(() => null); - }} - /> -
- ); + const handleDelete = () => { + if (product) { + confirmModal(product.deleteConfirmMessage()) + .then(() => product.del()) + .then(() => { + history.push("/sales/product/"); + }) + .catch(() => null); + } + }; + + // Ensure the component has loaded the product before rendering + if (!product) { + return
Loading...
; } -} -export default withRouter(ProductEdit); + return ( +
+

Redigera produkt

+ +
+ ); +}; + +export default ProductEdit; \ No newline at end of file diff --git a/admin/src/Settings/ServiceTokenList.jsx b/admin/src/Settings/ServiceTokenList.jsx index 5e507b11d..7d9272c34 100644 --- a/admin/src/Settings/ServiceTokenList.jsx +++ b/admin/src/Settings/ServiceTokenList.jsx @@ -1,11 +1,10 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; +import CollectionTable from "../Components/CollectionTable"; import Collection from "../Models/Collection"; import ServiceAccessToken from "../Models/ServiceAccessToken"; -import CollectionTable from "../Components/CollectionTable"; import { del } from "../gateway"; -const Row = (collection) => (props) => { - const { item } = props; +const Row = ({ item, collection }) => { return ( @@ -27,25 +26,25 @@ const Row = (collection) => (props) => { ); }; -export default class ServiceTokenList extends React.Component { - constructor(props) { - super(props); - this.collection = new Collection({ type: ServiceAccessToken }); - this.state = {}; - } +const ServiceTokenList = () => { + const [collection] = useState(new Collection({ type: ServiceAccessToken })); + + useEffect(() => { + collection.fetch(); + }, [collection]); + + return ( + } + /> + ); +}; - render() { - return ( - - ); - } -} +export default ServiceTokenList; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a362ad61b..7a4c0123f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,7 +4,6 @@ "requires": true, "packages": { "": { - "name": "makeradmin", "devDependencies": { "prettier": "3.1.1" }