diff --git a/Plan/react/dashboard/src/components/cards/common/PlayerRetention.jsx b/Plan/react/dashboard/src/components/cards/common/PlayerRetention.jsx new file mode 100644 index 000000000..7f16b063d --- /dev/null +++ b/Plan/react/dashboard/src/components/cards/common/PlayerRetention.jsx @@ -0,0 +1,29 @@ +import LoadIn from "../../animation/LoadIn.jsx"; +import ExtendableRow from "../../layout/extension/ExtendableRow.jsx"; +import {Col} from "react-bootstrap"; +import PlayerRetentionGraphCard from "./PlayerRetentionGraphCard.jsx"; +import React, {useState} from "react"; +import {JoinAddressListContextProvider} from "../../../hooks/context/joinAddressListContextHook.jsx"; +import AddressGroupSelectorRow from "./AddressGroupSelectorRow.jsx"; + +const PlayerRetention = ({id, seeRetention, identifier}) => { + const [selectedGroupBy, setSelectedGroupBy] = useState('none'); + return ( + + {seeRetention &&
+ + + + + + + {selectedGroupBy === 'joinAddress' && } + +
} +
+ ) +}; + +export default PlayerRetention \ 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 94c853df8..002e1f161 100644 --- a/Plan/react/dashboard/src/components/cards/common/PlayerRetentionGraphCard.jsx +++ b/Plan/react/dashboard/src/components/cards/common/PlayerRetentionGraphCard.jsx @@ -17,6 +17,7 @@ import {useTheme} from "../../../hooks/themeHook"; import {useNavigation} from "../../../hooks/navigationHook"; import {FontAwesomeIcon as Fa} from "@fortawesome/react-fontawesome"; import {faQuestionCircle} from "@fortawesome/free-regular-svg-icons"; +import {useJoinAddressListContext} from "../../../hooks/context/joinAddressListContextHook.jsx"; const dayMs = 24 * 3600000; const getWeek = (date) => { @@ -26,7 +27,7 @@ const getWeek = (date) => { return Math.ceil(dayOfYear / 7) }; -const PlayerRetentionGraphCard = ({identifier}) => { +const PlayerRetentionGraphCard = ({identifier, selectedGroupBy, setSelectedGroupBy}) => { const {t} = useTranslation(); const {nightModeEnabled} = useTheme(); const {setHelpModalTopic} = useNavigation(); @@ -40,6 +41,8 @@ const PlayerRetentionGraphCard = ({identifier}) => { loadingError: joinAddressLoadingError } = useDataRequest(fetchPlayerJoinAddresses, [identifier]); + const {list, playerAddresses} = useJoinAddressListContext(); + const [selectedWindow, setSelectedWindow] = useState('days'); const windowOptions = useMemo(() => [ {name: 'hours', displayName: t('html.label.time.hours'), increment: 3600000}, @@ -57,7 +60,7 @@ const PlayerRetentionGraphCard = ({identifier}) => { {name: 'registered-2y', displayName: t('html.label.retention.inLast730d'), start: time - 2 * 365 * dayMs}, {name: 'registered-ever', displayName: t('html.label.retention.inAnytime'), start: 0}, ], [t, time]); - const [selectedGroupBy, setSelectedGroupBy] = useState('none'); + // State moved to higher level for join address group selection const groupByOptions = useMemo(() => [ {name: 'none', displayName: t('html.label.retention.groupByNone')}, {name: 'days', displayName: t('html.label.time.day')}, @@ -165,8 +168,12 @@ const PlayerRetentionGraphCard = ({identifier}) => { break; case 'joinAddress': const joinAddress = joinAddressData[point.playerUUID]; - if (!grouped[joinAddress]) grouped[joinAddress] = []; - grouped[joinAddress].push(point); + const joinAddressGroups = list.filter(g => g.addresses.includes(joinAddress)).map(g => g.name); + for (const joinAddressGroup of joinAddressGroups) { + if (!grouped[joinAddressGroup]) grouped[joinAddressGroup] = []; + grouped[joinAddressGroup].push(point); + } + break; case 'none': default: @@ -175,7 +182,7 @@ const PlayerRetentionGraphCard = ({identifier}) => { } } return grouped; - }, [groupByOptions, selectedGroupBy]); + }, [groupByOptions, selectedGroupBy, list]); const createSeries = useCallback(async (retentionData, joinAddressData) => { @@ -207,10 +214,10 @@ const PlayerRetentionGraphCard = ({identifier}) => { }, [nightModeEnabled, mapToData, groupOptions, selectedGroup, selectedYAxis, group]); useEffect(() => { - if (!data || !joinAddressData) return; + if (!data || !playerAddresses) return; - createSeries(data.player_retention, joinAddressData.joinAddressByPlayer).then(series => setSeries(series.flat())); - }, [data, joinAddressData, createSeries, setSeries]); + createSeries(data.player_retention, playerAddresses).then(series => setSeries(series.flat())); + }, [data, playerAddresses, createSeries, setSeries]); useEffect(() => { const windowName = windowOptions.find(option => option.name === selectedWindow).displayName; diff --git a/Plan/react/dashboard/src/hooks/context/joinAddressListContextHook.jsx b/Plan/react/dashboard/src/hooks/context/joinAddressListContextHook.jsx index 42fa3d26d..626511f5f 100644 --- a/Plan/react/dashboard/src/hooks/context/joinAddressListContextHook.jsx +++ b/Plan/react/dashboard/src/hooks/context/joinAddressListContextHook.jsx @@ -7,7 +7,7 @@ import {useTranslation} from "react-i18next"; const JoinAddressListContext = createContext({}); -export const JoinAddressListContextProvider = ({identifier, children}) => { +export const JoinAddressListContextProvider = ({identifier, children, loadIndividualAddresses}) => { const {t} = useTranslation(); const {updateRequested} = useNavigation(); const {preferencesLoaded, getKeyedPreference, setSomePreferences} = usePreferences(); @@ -46,17 +46,19 @@ export const JoinAddressListContextProvider = ({identifier, children}) => { }, [updateList, list]); const [allAddresses, setAllAddresses] = useState([]); + const [playerAddresses, setPlayerAddresses] = useState([]); const loadAddresses = useCallback(async () => { - const {data, error} = await fetchPlayerJoinAddresses(updateRequested, identifier, true); + const {data, error} = await fetchPlayerJoinAddresses(updateRequested, identifier, !loadIndividualAddresses); setAllAddresses(data?.joinAddresses || [error]); + setPlayerAddresses(data?.joinAddressByPlayer || {}); }, [setAllAddresses, identifier, updateRequested]); useEffect(() => { loadAddresses(); }, [loadAddresses]); const sharedState = useMemo(() => { - return {list, add, remove, replace, allAddresses}; - }, [list, add, remove, replace, allAddresses]); + return {list, add, remove, replace, allAddresses, playerAddresses}; + }, [list, add, remove, replace, allAddresses, playerAddresses]); return ( {children} diff --git a/Plan/react/dashboard/src/views/network/NetworkPlayerRetention.jsx b/Plan/react/dashboard/src/views/network/NetworkPlayerRetention.jsx index 3a79f379d..f1c6ebbf5 100644 --- a/Plan/react/dashboard/src/views/network/NetworkPlayerRetention.jsx +++ b/Plan/react/dashboard/src/views/network/NetworkPlayerRetention.jsx @@ -1,24 +1,12 @@ import React from 'react'; -import ExtendableRow from "../../components/layout/extension/ExtendableRow"; -import {Col} from "react-bootstrap"; -import LoadIn from "../../components/animation/LoadIn"; -import PlayerRetentionGraphCard from "../../components/cards/common/PlayerRetentionGraphCard"; import {useAuth} from "../../hooks/authenticationHook"; +import PlayerRetention from "../../components/cards/common/PlayerRetention.jsx"; const NetworkPlayerRetention = () => { const {hasPermission} = useAuth(); - const seeRetention = hasPermission('page.network.retention'); return ( - - {seeRetention &&
- - - - - -
} -
+ ) }; diff --git a/Plan/react/dashboard/src/views/server/ServerPlayerRetention.jsx b/Plan/react/dashboard/src/views/server/ServerPlayerRetention.jsx index 9ed35ceb6..967980edd 100644 --- a/Plan/react/dashboard/src/views/server/ServerPlayerRetention.jsx +++ b/Plan/react/dashboard/src/views/server/ServerPlayerRetention.jsx @@ -1,10 +1,7 @@ import React from 'react'; -import ExtendableRow from "../../components/layout/extension/ExtendableRow"; -import {Col} from "react-bootstrap"; -import LoadIn from "../../components/animation/LoadIn"; -import PlayerRetentionGraphCard from "../../components/cards/common/PlayerRetentionGraphCard"; import {useParams} from "react-router-dom"; import {useAuth} from "../../hooks/authenticationHook"; +import PlayerRetention from "../../components/cards/common/PlayerRetention.jsx"; const ServerPlayerRetention = () => { const {hasPermission} = useAuth(); @@ -12,15 +9,7 @@ const ServerPlayerRetention = () => { const seeRetention = hasPermission('page.server.retention'); return ( - -
- {seeRetention && - - - - } -
-
+ ) };