diff --git a/spa/src/AggregatesShowcase.js b/spa/src/AggregatesShowcase.js
new file mode 100644
index 0000000..891954b
--- /dev/null
+++ b/spa/src/AggregatesShowcase.js
@@ -0,0 +1,98 @@
+import React from "react";
+import { useQuery } from "react-query";
+import axios from "axios";
+
+import {
+ AspectRatio,
+ Box,
+ Flex,
+ Heading,
+ HStack,
+ Icon,
+ SimpleGrid,
+ Stat,
+ StatLabel,
+ StatNumber,
+ StatHelpText,
+ StatArrow,
+ StatGroup,
+ Tooltip,
+} from "@chakra-ui/react";
+import { FaUserCircle, FaArrowAltCircleRight } from "react-icons/fa";
+import { Link } from "react-router-dom";
+import { AutoSizer, WindowScroller, List } from "react-virtualized";
+import prettifyStatisticName from "./PrettifyStatisticName";
+import StackedBar from "./StackedBar";
+
+const AggregatesShowcase = () => {
+ const aggregates = useQuery(
+ `aggregates`,
+ async () => {
+ const { data } = await axios.get(`http://localhost:7000/api/aggregates`);
+ return data;
+ },
+ {
+ placeholderData: [],
+ }
+ );
+
+ const killedAggregates = aggregates.data
+ .filter((x) => x.type === "minecraft:killed")
+ .sort((a, b) => b.value - a.value);
+ const killedTotal = killedAggregates.reduce((acc, val) => acc + val.value, 0);
+
+ const killedByAggregates = aggregates.data
+ .filter((x) => x.type === "minecraft:killed_by")
+ .sort((a, b) => b.value - a.value);
+ const killedByTotal = killedByAggregates.reduce(
+ (acc, val) => acc + val.value,
+ 0
+ );
+
+ const travelAggregates = aggregates.data
+ .filter((x) => x.type === "minecraft:custom" && x.name.endsWith("one_cm"))
+ .sort((a, b) => b.value - a.value);
+ const travelTotal = travelAggregates.reduce((acc, val) => acc + val.value, 0);
+
+ return aggregates.isFetched ? (
+ <>
+
+
+
+
+
+
+
+ {aggregates.data
+ .filter(
+ (x) =>
+ x.type !== "minecraft:killed" &&
+ x.type !== "minecraft:killed_by" &&
+ !x.name.endsWith("one_cm")
+ )
+ .map((x, i) => {
+ var value = x.value;
+ if (x.name === "minecraft:play_one_minute") {
+ value = x.value / 20 / 60;
+ }
+
+ return (
+
+ {prettifyStatisticName(x.type, x.name)}
+ {`${x.value
+ .toString()
+ .replace(
+ /\B(?
+
+ );
+ })}
+
+ >
+ ) : (
+ <>>
+ );
+};
+
+export default AggregatesShowcase;
diff --git a/spa/src/Search.js b/spa/src/Search.js
index d9aa71b..3fcdf54 100644
--- a/spa/src/Search.js
+++ b/spa/src/Search.js
@@ -6,6 +6,7 @@ import { FaUserCircle, FaArrowAltCircleRight } from "react-icons/fa";
import { Link } from "react-router-dom";
import { AutoSizer, WindowScroller, List } from "react-virtualized";
import prettifyStatisticName from "./PrettifyStatisticName";
+import AggregatesShowcase from "./AggregatesShowcase";
const Search = ({ statistics, players }) => {
const [searchTerm, setSearchTerm] = useState("");
@@ -100,23 +101,29 @@ const Search = ({ statistics, players }) => {
variant="filled"
value={searchTerm}
onChange={(event) => setSearchTerm(event.target.value)}
+ mb="8"
/>
+ {searchResults.length === 0 ? : <>>}
-
- {({ width }) => (
-
- )}
-
+ {searchResults.length > 0 ? (
+
+ {({ width }) => (
+
+ )}
+
+ ) : (
+ <>>
+ )}
>
)}
diff --git a/spa/src/StackedBar.js b/spa/src/StackedBar.js
new file mode 100644
index 0000000..de5dbe1
--- /dev/null
+++ b/spa/src/StackedBar.js
@@ -0,0 +1,76 @@
+import React, { useMemo } from "react";
+import useHover from "./useHover";
+import {
+ AspectRatio,
+ Box,
+ Button,
+ Heading,
+ HStack,
+ Icon,
+ Input,
+ Stat,
+ StatLabel,
+ StatNumber,
+ StatHelpText,
+ StatArrow,
+ StatGroup,
+ Tooltip,
+} from "@chakra-ui/react";
+import prettifyStatisticName from "./PrettifyStatisticName";
+
+const randomColor = () =>
+ `hsl(${Math.floor(Math.random() * 359)}, ${
+ 50 + Math.floor(Math.random() * 50)
+ }%, ${25 + Math.floor(Math.random() * 50)}%)`;
+
+const StackedBarSegment = ({ aggregate, total }) => {
+ const color = useMemo(randomColor, []);
+
+ const [hoverRef, isHovered] = useHover();
+
+ return (
+
+
+
+ );
+};
+
+const StackedBar = ({ heading, aggregates }) => {
+ const total = aggregates.reduce((acc, val) => acc + val.value, 0);
+
+ return (
+ <>
+
+ {heading}
+
+
+ {aggregates.map((x, i) => (
+
+ ))}
+
+ >
+ );
+};
+
+export default StackedBar;
diff --git a/spa/src/useHover.js b/spa/src/useHover.js
new file mode 100644
index 0000000..04a3bac
--- /dev/null
+++ b/spa/src/useHover.js
@@ -0,0 +1,25 @@
+import React, { useEffect, useRef, useState } from "react";
+
+function useHover() {
+ const [value, setValue] = useState(false);
+ const ref = useRef(null);
+ const handleMouseOver = () => setValue(true);
+ const handleMouseOut = () => setValue(false);
+ useEffect(
+ () => {
+ const node = ref.current;
+ if (node) {
+ node.addEventListener("mouseover", handleMouseOver);
+ node.addEventListener("mouseout", handleMouseOut);
+ return () => {
+ node.removeEventListener("mouseover", handleMouseOver);
+ node.removeEventListener("mouseout", handleMouseOut);
+ };
+ }
+ },
+ [ref.current] // Recall only if ref changes
+ );
+ return [ref, value];
+}
+
+export default useHover;