From 3c83cf6baa03dfa9757bdb9d6dec42b471a76190 Mon Sep 17 00:00:00 2001
From: Aurora Lahtela <24460436+AuroraLS3@users.noreply.github.com>
Date: Sun, 24 Mar 2024 11:12:10 +0200
Subject: [PATCH] Implement most of the requirements for this feature
---
.../datatransfer/PlayerJoinAddresses.java | 67 ++++++++++
.../delivery/rendering/json/JSONFactory.java | 20 ++-
.../json/PlayerJoinAddressJSONResolver.java | 9 +-
.../queries/objects/JoinAddressQueries.java | 32 +++++
.../cards/common/AddressListCard.jsx | 53 ++++++++
.../components/cards/common/JoinAddresses.jsx | 48 +++++++
.../cards/common/PlayerRetentionGraphCard.jsx | 2 +-
.../server/graphs/JoinAddressGraphCard.jsx | 70 +++++++++--
.../context/joinAddressListContextHook.jsx | 44 +++++++
.../dashboard/src/service/serverService.js | 14 +--
.../views/network/NetworkJoinAddresses.jsx | 118 ++----------------
.../src/views/server/ServerJoinAddresses.jsx | 28 +----
12 files changed, 348 insertions(+), 157 deletions(-)
create mode 100644 Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/PlayerJoinAddresses.java
create mode 100644 Plan/react/dashboard/src/components/cards/common/AddressListCard.jsx
create mode 100644 Plan/react/dashboard/src/components/cards/common/JoinAddresses.jsx
create mode 100644 Plan/react/dashboard/src/hooks/context/joinAddressListContextHook.jsx
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/PlayerJoinAddresses.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/PlayerJoinAddresses.java
new file mode 100644
index 000000000..276c90387
--- /dev/null
+++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/PlayerJoinAddresses.java
@@ -0,0 +1,67 @@
+/*
+ * This file is part of Player Analytics (Plan).
+ *
+ * Plan is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License v3 as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Plan is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Plan. If not, see .
+ */
+package com.djrapitops.plan.delivery.domain.datatransfer;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * Represents data returned by {@link com.djrapitops.plan.delivery.webserver.resolver.json.PlayerJoinAddressJSONResolver}.
+ *
+ * @author AuroraLS3
+ */
+public class PlayerJoinAddresses {
+
+ private final List joinAddresses;
+ private final Map joinAddressByPlayer;
+
+ public PlayerJoinAddresses(List joinAddresses, Map joinAddressByPlayer) {
+ this.joinAddresses = joinAddresses;
+ this.joinAddressByPlayer = joinAddressByPlayer;
+ }
+
+ public List getJoinAddresses() {
+ return joinAddresses;
+ }
+
+ public Map getJoinAddressByPlayer() {
+ return joinAddressByPlayer;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PlayerJoinAddresses that = (PlayerJoinAddresses) o;
+ return Objects.equals(getJoinAddresses(), that.getJoinAddresses()) && Objects.equals(getJoinAddressByPlayer(), that.getJoinAddressByPlayer());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getJoinAddresses(), getJoinAddressByPlayer());
+ }
+
+ @Override
+ public String toString() {
+ return "PlayerJoinAddresses{" +
+ "joinAddresses=" + joinAddresses +
+ ", joinAddressByPlayer=" + joinAddressByPlayer +
+ '}';
+ }
+}
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/JSONFactory.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/JSONFactory.java
index c2c2afe39..b86102408 100644
--- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/JSONFactory.java
+++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/JSONFactory.java
@@ -18,6 +18,7 @@ package com.djrapitops.plan.delivery.rendering.json;
import com.djrapitops.plan.delivery.domain.DateObj;
import com.djrapitops.plan.delivery.domain.RetentionData;
+import com.djrapitops.plan.delivery.domain.datatransfer.PlayerJoinAddresses;
import com.djrapitops.plan.delivery.domain.datatransfer.ServerDto;
import com.djrapitops.plan.delivery.domain.mutators.PlayerKillMutator;
import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator;
@@ -160,14 +161,25 @@ public class JSONFactory {
return db.query(PlayerRetentionQueries.fetchRetentionData());
}
- public Map playerJoinAddresses(ServerUUID serverUUID) {
+ public PlayerJoinAddresses playerJoinAddresses(ServerUUID serverUUID, boolean includeByPlayerMap) {
Database db = dbSystem.getDatabase();
- return db.query(JoinAddressQueries.latestJoinAddressesOfPlayers(serverUUID));
+ if (includeByPlayerMap) {
+ Map addresses = db.query(JoinAddressQueries.latestJoinAddressesOfPlayers(serverUUID));
+ return new PlayerJoinAddresses(
+ addresses.values().stream().distinct().sorted().collect(Collectors.toList()),
+ addresses
+ );
+ } else {
+ return new PlayerJoinAddresses(db.query(JoinAddressQueries.uniqueJoinAddresses(serverUUID)), null);
+ }
}
- public Map playerJoinAddresses() {
+ public PlayerJoinAddresses playerJoinAddresses(boolean includeByPlayerMap) {
Database db = dbSystem.getDatabase();
- return db.query(JoinAddressQueries.latestJoinAddressesOfPlayers());
+ return new PlayerJoinAddresses(
+ db.query(JoinAddressQueries.uniqueJoinAddresses()),
+ includeByPlayerMap ? db.query(JoinAddressQueries.latestJoinAddressesOfPlayers()) : null
+ );
}
public List> serverSessionsAsJSONMap(ServerUUID serverUUID) {
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/PlayerJoinAddressJSONResolver.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/PlayerJoinAddressJSONResolver.java
index 5299d545f..045426158 100644
--- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/PlayerJoinAddressJSONResolver.java
+++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/PlayerJoinAddressJSONResolver.java
@@ -17,6 +17,7 @@
package com.djrapitops.plan.delivery.webserver.resolver.json;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
+import com.djrapitops.plan.delivery.domain.datatransfer.PlayerJoinAddresses;
import com.djrapitops.plan.delivery.formatting.Formatter;
import com.djrapitops.plan.delivery.rendering.json.JSONFactory;
import com.djrapitops.plan.delivery.web.resolver.MimeType;
@@ -34,6 +35,7 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
+import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.ws.rs.GET;
@@ -42,7 +44,6 @@ import org.jetbrains.annotations.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
-import java.util.Collections;
import java.util.Optional;
/**
@@ -79,7 +80,7 @@ public class PlayerJoinAddressJSONResolver extends JSONResolver {
@Operation(
description = "Get join address information of players for server or network",
responses = {
- @ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON)),
+ @ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON, schema = @Schema(implementation = PlayerJoinAddresses.class))),
@ApiResponse(responseCode = "400", description = "If 'server' parameter is not an existing server")
},
parameters = @Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for (optional)", examples = {
@@ -105,12 +106,12 @@ public class PlayerJoinAddressJSONResolver extends JSONResolver {
if (request.getQuery().get("server").isPresent()) {
ServerUUID serverUUID = identifiers.getServerUUID(request);
return jsonResolverService.resolve(timestamp, DataID.PLAYER_JOIN_ADDRESSES, serverUUID,
- theUUID -> Collections.singletonMap("join_address_by_player", jsonFactory.playerJoinAddresses(theUUID))
+ serverUUID1 -> jsonFactory.playerJoinAddresses(serverUUID1, request.getQuery().get("listOnly").isEmpty())
);
}
// Assume network
return jsonResolverService.resolve(timestamp, DataID.PLAYER_JOIN_ADDRESSES,
- () -> Collections.singletonMap("join_address_by_player", jsonFactory.playerJoinAddresses())
+ () -> jsonFactory.playerJoinAddresses(request.getQuery().get("listOnly").isEmpty())
);
}
}
diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/JoinAddressQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/JoinAddressQueries.java
index 33386e85e..a8413b4aa 100644
--- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/JoinAddressQueries.java
+++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/JoinAddressQueries.java
@@ -142,6 +142,28 @@ public class JoinAddressQueries {
};
}
+ public static QueryStatement> allJoinAddresses(ServerUUID serverUUID) {
+ String sql = SELECT + DISTINCT + JoinAddressTable.JOIN_ADDRESS +
+ FROM + JoinAddressTable.TABLE_NAME + " j" +
+ INNER_JOIN + SessionsTable.TABLE_NAME + " s ON s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
+ WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
+ ORDER_BY + JoinAddressTable.JOIN_ADDRESS + " ASC";
+
+ return new QueryStatement<>(sql, 100) {
+ @Override
+ public void prepare(PreparedStatement statement) throws SQLException {
+ statement.setString(1, serverUUID.toString());
+ }
+
+ @Override
+ public List processResults(ResultSet set) throws SQLException {
+ List joinAddresses = new ArrayList<>();
+ while (set.next()) joinAddresses.add(set.getString(JoinAddressTable.JOIN_ADDRESS));
+ return joinAddresses;
+ }
+ };
+ }
+
public static Query> uniqueJoinAddresses() {
return db -> {
List addresses = db.query(allJoinAddresses());
@@ -152,6 +174,16 @@ public class JoinAddressQueries {
};
}
+ public static Query> uniqueJoinAddresses(ServerUUID serverUUID) {
+ return db -> {
+ List addresses = db.query(allJoinAddresses(serverUUID));
+ if (!addresses.contains(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP)) {
+ addresses.add(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
+ }
+ return addresses;
+ };
+ }
+
public static Query> userIdsOfPlayersWithJoinAddresses(@Untrusted List joinAddresses) {
String sql = SELECT + DISTINCT + SessionsTable.USER_ID +
FROM + JoinAddressTable.TABLE_NAME + " j" +
diff --git a/Plan/react/dashboard/src/components/cards/common/AddressListCard.jsx b/Plan/react/dashboard/src/components/cards/common/AddressListCard.jsx
new file mode 100644
index 000000000..3e74eabf6
--- /dev/null
+++ b/Plan/react/dashboard/src/components/cards/common/AddressListCard.jsx
@@ -0,0 +1,53 @@
+import {useTranslation} from "react-i18next";
+import React, {useCallback, useEffect, useState} from "react";
+import {Card, Form} from "react-bootstrap";
+import CardHeader from "../CardHeader.jsx";
+import {faCheck, faList, faPencil} from "@fortawesome/free-solid-svg-icons";
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
+import MultiSelect from "../../input/MultiSelect.jsx";
+import {faTrashAlt} from "@fortawesome/free-regular-svg-icons";
+
+const AddressListCard = ({n, group, editGroup, allAddresses, remove}) => {
+ const {t} = useTranslation();
+ const [selectedIndexes, setSelectedIndexes] = useState([]);
+ const [editingName, setEditingName] = useState(false);
+ const [name, setName] = useState(group.name);
+
+ const isUpToDate = group.addresses === allAddresses.filter((a, i) => selectedIndexes.includes(i));
+ const applySelected = useCallback(() => {
+ editGroup({...group, addresses: allAddresses.filter((a, i) => selectedIndexes.includes(i))})
+ }, [editGroup, group, allAddresses, selectedIndexes]);
+ const editName = useCallback(newName => {
+ editGroup({...group, name: newName});
+ }, [editGroup, group]);
+ useEffect(() => {
+ if (!editingName && name !== group.name) editName(name);
+ }, [editName, editingName, name])
+
+ return (
+
+ setName(e.target.value)}/> : group.name
+ }>
+ setEditingName(!editingName)}>
+
+
+
+
+
+
+ {t('html.label.apply')}
+
+
+
+
+
+
+ )
+}
+export default AddressListCard;
\ No newline at end of file
diff --git a/Plan/react/dashboard/src/components/cards/common/JoinAddresses.jsx b/Plan/react/dashboard/src/components/cards/common/JoinAddresses.jsx
new file mode 100644
index 000000000..8e90a1396
--- /dev/null
+++ b/Plan/react/dashboard/src/components/cards/common/JoinAddresses.jsx
@@ -0,0 +1,48 @@
+import LoadIn from "../../animation/LoadIn.jsx";
+import ExtendableRow from "../../layout/extension/ExtendableRow.jsx";
+import JoinAddressGraphCard from "../server/graphs/JoinAddressGraphCard.jsx";
+import {Col, Row} from "react-bootstrap";
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
+import {faPlus} from "@fortawesome/free-solid-svg-icons";
+import React from "react";
+import {useAuth} from "../../../hooks/authenticationHook.jsx";
+import {useTranslation} from "react-i18next";
+import {useJoinAddressListContext} from "../../../hooks/context/joinAddressListContextHook.jsx";
+import AddressListCard from "./AddressListCard.jsx";
+
+const JoinAddresses = ({id, permission, identifier}) => {
+ const {t} = useTranslation();
+ const {hasPermission} = useAuth();
+ const {list, add, remove, replace, allAddresses} = useJoinAddressListContext();
+
+ const seeTime = hasPermission(permission);
+
+ return (
+
+
+
+
+ {seeTime && }
+
+
+
+ {list.map((group, i) =>
+
+ replace(replacement, i)}
+ allAddresses={allAddresses}
+ remove={() => remove(i)}/>
+ )}
+
+
+ Add address group
+
+
+
+
+
+ )
+}
+
+export default JoinAddresses;
\ No newline at end of file
diff --git a/Plan/react/dashboard/src/components/cards/common/PlayerRetentionGraphCard.jsx b/Plan/react/dashboard/src/components/cards/common/PlayerRetentionGraphCard.jsx
index 1e0786a01..0eba802b2 100644
--- a/Plan/react/dashboard/src/components/cards/common/PlayerRetentionGraphCard.jsx
+++ b/Plan/react/dashboard/src/components/cards/common/PlayerRetentionGraphCard.jsx
@@ -209,7 +209,7 @@ const PlayerRetentionGraphCard = ({identifier}) => {
useEffect(() => {
if (!data || !joinAddressData) return;
- createSeries(data.player_retention, joinAddressData.join_address_by_player).then(series => setSeries(series.flat()));
+ createSeries(data.player_retention, joinAddressData.joinAddressByPlayer).then(series => setSeries(series.flat()));
}, [data, joinAddressData, createSeries, setSeries]);
useEffect(() => {
diff --git a/Plan/react/dashboard/src/components/cards/server/graphs/JoinAddressGraphCard.jsx b/Plan/react/dashboard/src/components/cards/server/graphs/JoinAddressGraphCard.jsx
index d92182740..73fc80255 100644
--- a/Plan/react/dashboard/src/components/cards/server/graphs/JoinAddressGraphCard.jsx
+++ b/Plan/react/dashboard/src/components/cards/server/graphs/JoinAddressGraphCard.jsx
@@ -1,23 +1,73 @@
-import React, {useState} from 'react';
+import React, {useCallback, useEffect, useState} from 'react';
import {useTranslation} from "react-i18next";
-import {useDataRequest} from "../../../../hooks/dataFetchHook";
import {fetchJoinAddressByDay} from "../../../../service/serverService";
import {ErrorViewCard} from "../../../../views/ErrorView";
-import {CardLoader} from "../../../navigation/Loader";
+import {ChartLoader} from "../../../navigation/Loader";
import {Card} from "react-bootstrap";
import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome";
import {faChartColumn} from "@fortawesome/free-solid-svg-icons";
import JoinAddressGraph from "../../../graphs/JoinAddressGraph";
import Toggle from "../../../input/Toggle";
+import {useJoinAddressListContext} from "../../../../hooks/context/joinAddressListContextHook.jsx";
+import {useNavigation} from "../../../../hooks/navigationHook.jsx";
-const JoinAddressGraphCard = ({id, identifier, addresses}) => {
+const JoinAddressGraphCard = ({identifier}) => {
const {t} = useTranslation();
const [stack, setStack] = useState(true);
+ const {updateRequested} = useNavigation();
- const {data, loadingError} = useDataRequest(fetchJoinAddressByDay, [addresses || [], identifier]);
+ const {list} = useJoinAddressListContext();
+ const noSelectedAddresses = !list.filter(group => group.addresses.length).length;
+
+ const [data, setData] = useState(undefined);
+ const [loadingError, setLoadingError] = useState(undefined);
+ const loadAddresses = useCallback(async () => {
+ if (noSelectedAddresses) return;
+
+ let colors = ['#4ab4de'];
+ const dataByGroup = [];
+ for (const group of list) {
+ const {data, error} = await fetchJoinAddressByDay(updateRequested, group.addresses, identifier);
+ if (error) {
+ setLoadingError(error);
+ return;
+ }
+ colors = data?.colors;
+ dataByGroup.push({...group, data: data?.join_addresses_by_date || []});
+ }
+
+ // First group points from endpoint into frontend based groups
+ const points = {};
+ for (const group of dataByGroup) {
+ const groupName = group.name;
+ for (const point of group.data || []) {
+ if (!points[point.date]) points[point.date] = [];
+
+ const count = point.joinAddresses.map(j => j.count).reduce((partialSum, a) => partialSum + a, 0);
+ points[point.date].push({date: point.date, joinAddresses: [{joinAddress: groupName, count}]})
+ }
+ }
+
+ // expected output: [{date: number, addresses: [{joinAddress: "name", count: number}]}]
+ const flattened = Object.entries(points)
+ .sort((a, b) => Number(b.date) - Number(a.date))
+ .map(([date, pointList]) => {
+ return {
+ date: Number(date), joinAddresses: pointList.map(point => point.joinAddresses).flat()
+ }
+ });
+
+ setData({
+ join_addresses_by_date: flattened,
+ colors
+ });
+ }, [setData, setLoadingError, identifier, updateRequested, list]);
+
+ useEffect(() => {
+ loadAddresses();
+ }, [loadAddresses]);
if (loadingError) return
- if (!data) return ;
return (
@@ -27,8 +77,12 @@ const JoinAddressGraphCard = ({id, identifier, addresses}) => {
{t('html.label.stacked')}
-
+ {data &&
+ }
+ {!data && noSelectedAddresses &&
+ }
+ {!data && !noSelectedAddresses && }
)
};
diff --git a/Plan/react/dashboard/src/hooks/context/joinAddressListContextHook.jsx b/Plan/react/dashboard/src/hooks/context/joinAddressListContextHook.jsx
new file mode 100644
index 000000000..dc9b51b4c
--- /dev/null
+++ b/Plan/react/dashboard/src/hooks/context/joinAddressListContextHook.jsx
@@ -0,0 +1,44 @@
+import {createContext, useCallback, useContext, useEffect, useMemo, useState} from "react";
+import {randomUuid} from "../../util/uuid.js";
+import {fetchPlayerJoinAddresses} from "../../service/serverService.js";
+import {useNavigation} from "../navigationHook.jsx";
+
+const JoinAddressListContext = createContext({});
+
+export const JoinAddressListContextProvider = ({identifier, children}) => {
+ const {updateRequested} = useNavigation();
+ const [list, setList] = useState([]);
+
+ const add = useCallback(() => {
+ setList([...list, {name: "Address group " + (list.length + 1), addresses: [], uuid: randomUuid()}])
+ }, [list, setList]);
+ const remove = useCallback(index => {
+ setList(list.filter((f, i) => i !== index));
+ }, [setList, list]);
+ const replace = useCallback((replacement, index) => {
+ const newList = [...list];
+ newList[index] = replacement;
+ setList(newList)
+ }, [setList, list]);
+
+ const [allAddresses, setAllAddresses] = useState([]);
+ const loadAddresses = useCallback(async () => {
+ const {data, error} = await fetchPlayerJoinAddresses(updateRequested, identifier, true);
+ setAllAddresses(data?.joinAddresses || [error]);
+ }, [setAllAddresses, identifier, updateRequested]);
+ useEffect(() => {
+ loadAddresses();
+ }, [loadAddresses]);
+
+ const sharedState = useMemo(() => {
+ return {list, add, remove, replace, allAddresses};
+ }, [list, add, remove, replace]);
+ return (
+ {children}
+
+ )
+}
+
+export const useJoinAddressListContext = () => {
+ return useContext(JoinAddressListContext);
+}
\ No newline at end of file
diff --git a/Plan/react/dashboard/src/service/serverService.js b/Plan/react/dashboard/src/service/serverService.js
index 63e450522..51229f82b 100644
--- a/Plan/react/dashboard/src/service/serverService.js
+++ b/Plan/react/dashboard/src/service/serverService.js
@@ -321,22 +321,22 @@ const fetchNetworkRetentionData = async (timestamp) => {
return doGetRequest(url, timestamp);
}
-export const fetchPlayerJoinAddresses = async (timestamp, identifier) => {
+export const fetchPlayerJoinAddresses = async (timestamp, identifier, justList) => {
if (identifier) {
- return await fetchServerPlayerJoinAddresses(timestamp, identifier);
+ return await fetchServerPlayerJoinAddresses(timestamp, identifier, justList);
} else {
- return await fetchNetworkPlayerJoinAddresses(timestamp);
+ return await fetchNetworkPlayerJoinAddresses(timestamp, justList);
}
}
-const fetchServerPlayerJoinAddresses = async (timestamp, identifier) => {
- let url = `/v1/joinAddresses?server=${identifier}`;
+const fetchServerPlayerJoinAddresses = async (timestamp, identifier, justList) => {
+ let url = `/v1/joinAddresses?server=${identifier}${justList ? "&listOnly=true" : ""}`;
if (staticSite) url = `/data/joinAddresses-${identifier}.json`;
return doGetRequest(url, timestamp);
}
-const fetchNetworkPlayerJoinAddresses = async (timestamp) => {
- let url = `/v1/joinAddresses`;
+const fetchNetworkPlayerJoinAddresses = async (timestamp, justList) => {
+ let url = `/v1/joinAddresses${justList ? "?listOnly=true" : ""}`;
if (staticSite) url = `/data/joinAddresses.json`;
return doGetRequest(url, timestamp);
}
diff --git a/Plan/react/dashboard/src/views/network/NetworkJoinAddresses.jsx b/Plan/react/dashboard/src/views/network/NetworkJoinAddresses.jsx
index 9fb4f83b0..586ede325 100644
--- a/Plan/react/dashboard/src/views/network/NetworkJoinAddresses.jsx
+++ b/Plan/react/dashboard/src/views/network/NetworkJoinAddresses.jsx
@@ -1,117 +1,13 @@
-import React, {useCallback, useEffect, useState} from 'react';
-import {Card, Col, Form, Row} from "react-bootstrap";
-import JoinAddressGraphCard from "../../components/cards/server/graphs/JoinAddressGraphCard";
-import LoadIn from "../../components/animation/LoadIn";
-import ExtendableRow from "../../components/layout/extension/ExtendableRow";
-import {useAuth} from "../../hooks/authenticationHook";
-import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
-import {faCheck, faList, faPencil, faPlus} from "@fortawesome/free-solid-svg-icons";
-import MultiSelect from "../../components/input/MultiSelect.jsx";
-import {useDataRequest} from "../../hooks/dataFetchHook.js";
-import {fetchPlayerJoinAddresses} from "../../service/serverService.js";
-import {useTranslation} from "react-i18next";
-import {faTrashAlt} from "@fortawesome/free-regular-svg-icons";
-import CardHeader from "../../components/cards/CardHeader.jsx";
-import {randomUuid} from "../../util/uuid.js";
-
-const AddressListCard = ({n, group, editGroup, allAddresses, remove}) => {
- const {t} = useTranslation();
- const [selectedIndexes, setSelectedIndexes] = useState([]);
- const [editingName, setEditingName] = useState(false);
- const [name, setName] = useState(group.name);
-
- const isUpToDate = group.addresses === allAddresses.filter((a, i) => selectedIndexes.includes(i));
- const applySelected = useCallback(() => {
- editGroup({...group, addresses: allAddresses.filter((a, i) => selectedIndexes.includes(i))})
- }, [editGroup, group, allAddresses, selectedIndexes]);
- const editName = useCallback(newName => {
- editGroup({...group, name: newName});
- }, [editGroup, group]);
- useEffect(() => {
- if (!editingName && name !== group.name) editName(name);
- }, [editName, editingName, name])
-
- return (
-
-
- setName(e.target.value)}/> : group.name
- }>
- setEditingName(!editingName)}>
-
-
-
-
-
-
- {t('html.label.apply')}
-
-
-
-
-
-
-
- )
-}
+import React from 'react';
+import JoinAddresses from "../../components/cards/common/JoinAddresses.jsx";
+import {JoinAddressListContextProvider} from "../../hooks/context/joinAddressListContextHook.jsx";
const NetworkJoinAddresses = () => {
- const identifier = undefined;
- const {hasPermission} = useAuth();
-
- const seeTime = false && hasPermission('page.network.join.addresses.graphs.time');
- const seeLatest = hasPermission('page.network.join.addresses.graphs.pie');
-
- // TODO Move to context
- const [list, setList] = useState([]);
- const add = useCallback(() => {
- setList([...list, {name: "Address group " + (list.length + 1), addresses: [], uuid: randomUuid()}])
- }, [list, setList]);
- const remove = useCallback(index => {
- setList(list.filter((f, i) => i !== index));
- }, [setList, list]);
- const replace = useCallback((replacement, index) => {
- const newList = [...list];
- newList[index] = replacement;
- setList(newList)
- }, [setList, list]);
-
- const {
- data: joinAddressData,
- loadingError: joinAddressLoadingError
- } = useDataRequest(fetchPlayerJoinAddresses, [identifier]);
-
- let allAddresses = joinAddressData ? Object.values(joinAddressData.join_address_by_player) : [];
-
- function onlyUnique(value, index, array) {
- return array.indexOf(value) === index;
- }
-
- allAddresses = allAddresses.filter(onlyUnique);
return (
-
-
-
- {seeTime && }
-
-
- {list.map((group, i) =>
- replace(replacement, i)}
- allAddresses={allAddresses}
- remove={() => remove(i)}/>)}
-
-
- Add address group
-
-
-
-
-
+
+
+
)
};
diff --git a/Plan/react/dashboard/src/views/server/ServerJoinAddresses.jsx b/Plan/react/dashboard/src/views/server/ServerJoinAddresses.jsx
index 6164c3974..316eb30f1 100644
--- a/Plan/react/dashboard/src/views/server/ServerJoinAddresses.jsx
+++ b/Plan/react/dashboard/src/views/server/ServerJoinAddresses.jsx
@@ -1,31 +1,15 @@
import React from 'react';
-import {Col} from "react-bootstrap";
-import JoinAddressGroupCard from "../../components/cards/server/graphs/JoinAddressGroupCard";
-import JoinAddressGraphCard from "../../components/cards/server/graphs/JoinAddressGraphCard";
import {useParams} from "react-router-dom";
-import LoadIn from "../../components/animation/LoadIn";
-import ExtendableRow from "../../components/layout/extension/ExtendableRow";
-import {useAuth} from "../../hooks/authenticationHook";
+import {JoinAddressListContextProvider} from "../../hooks/context/joinAddressListContextHook.jsx";
+import JoinAddresses from "../../components/cards/common/JoinAddresses.jsx";
const ServerJoinAddresses = () => {
- const {hasPermission} = useAuth();
const {identifier} = useParams();
-
- const seeTime = hasPermission('page.server.join.addresses.graphs.time');
- const seeLatest = hasPermission('page.server.join.addresses.graphs.pie');
return (
-
-
-
- {seeTime &&
-
- }
- {seeLatest &&
-
- }
-
-
-
+
+
+
)
};