diff --git a/README.md b/README.md index 136fecf..664e951 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,9 @@ const billetBank = { ## TODO +- [ ] Add tutorial for updating Pie Charts - [ ] Add tutorial for updating Statistics - [ ] Change cache warning on client to display time since cache refresh - [ ] Investigate the feasibility of sorting by rank after billet sorting (Subsorting? Consider using QuickSort) - [ ] Add graphical data visualization +- [ ] Code Refactorizing \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index 6299861..8682fbf 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -11,8 +11,10 @@ "@testing-library/jest-dom": "^5.16.1", "@testing-library/react": "^12.1.2", "@testing-library/user-event": "^13.5.0", + "apexcharts": "^3.42.0", "express": "^4.17.2", "react": "^17.0.2", + "react-apexcharts": "^1.4.1", "react-collapsible": "^2.8.4", "react-dom": "^17.0.2", "react-scripts": "5.0.0", @@ -3859,6 +3861,11 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, + "node_modules/@yr/monotone-cubic-spline": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", + "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==" + }, "node_modules/abab": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", @@ -4110,6 +4117,20 @@ "node": ">= 8" } }, + "node_modules/apexcharts": { + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.42.0.tgz", + "integrity": "sha512-hYhzZqh2Efny9uiutkGU2M/EarJ4Nn8s6dxZ0C7E7N+SV4d1xjTioXi2NLn4UKVJabZkb3HnpXDoumXgtAymwg==", + "dependencies": { + "@yr/monotone-cubic-spline": "^1.0.3", + "svg.draggable.js": "^2.2.2", + "svg.easing.js": "^2.0.0", + "svg.filter.js": "^2.0.2", + "svg.pathmorphing.js": "^0.1.3", + "svg.resize.js": "^1.4.3", + "svg.select.js": "^3.0.1" + } + }, "node_modules/arg": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.1.tgz", @@ -12787,6 +12808,18 @@ "node": ">=0.10.0" } }, + "node_modules/react-apexcharts": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.4.1.tgz", + "integrity": "sha512-G14nVaD64Bnbgy8tYxkjuXEUp/7h30Q0U33xc3AwtGFijJB9nHqOt1a6eG0WBn055RgRg+NwqbKGtqPxy15d0Q==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "apexcharts": "^3.41.0", + "react": ">=0.13" + } + }, "node_modules/react-app-polyfill": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz", @@ -14158,6 +14191,89 @@ "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" }, + "node_modules/svg.draggable.js": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", + "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", + "dependencies": { + "svg.js": "^2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.easing.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", + "integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==", + "dependencies": { + "svg.js": ">=2.3.x" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.filter.js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", + "integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", + "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" + }, + "node_modules/svg.pathmorphing.js": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", + "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", + "dependencies": { + "svg.js": "^2.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", + "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", + "dependencies": { + "svg.js": "^2.6.5", + "svg.select.js": "^2.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js/node_modules/svg.select.js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", + "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.select.js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", + "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", + "dependencies": { + "svg.js": "^2.6.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/svgo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", @@ -18523,6 +18639,11 @@ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, + "@yr/monotone-cubic-spline": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", + "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==" + }, "abab": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", @@ -18701,6 +18822,20 @@ "picomatch": "^2.0.4" } }, + "apexcharts": { + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.42.0.tgz", + "integrity": "sha512-hYhzZqh2Efny9uiutkGU2M/EarJ4Nn8s6dxZ0C7E7N+SV4d1xjTioXi2NLn4UKVJabZkb3HnpXDoumXgtAymwg==", + "requires": { + "@yr/monotone-cubic-spline": "^1.0.3", + "svg.draggable.js": "^2.2.2", + "svg.easing.js": "^2.0.0", + "svg.filter.js": "^2.0.2", + "svg.pathmorphing.js": "^0.1.3", + "svg.resize.js": "^1.4.3", + "svg.select.js": "^3.0.1" + } + }, "arg": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.1.tgz", @@ -24876,6 +25011,14 @@ "object-assign": "^4.1.1" } }, + "react-apexcharts": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.4.1.tgz", + "integrity": "sha512-G14nVaD64Bnbgy8tYxkjuXEUp/7h30Q0U33xc3AwtGFijJB9nHqOt1a6eG0WBn055RgRg+NwqbKGtqPxy15d0Q==", + "requires": { + "prop-types": "^15.8.1" + } + }, "react-app-polyfill": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz", @@ -25893,6 +26036,70 @@ "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" }, + "svg.draggable.js": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", + "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", + "requires": { + "svg.js": "^2.0.1" + } + }, + "svg.easing.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", + "integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==", + "requires": { + "svg.js": ">=2.3.x" + } + }, + "svg.filter.js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", + "integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==", + "requires": { + "svg.js": "^2.2.5" + } + }, + "svg.js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", + "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" + }, + "svg.pathmorphing.js": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", + "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", + "requires": { + "svg.js": "^2.4.0" + } + }, + "svg.resize.js": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", + "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", + "requires": { + "svg.js": "^2.6.5", + "svg.select.js": "^2.1.2" + }, + "dependencies": { + "svg.select.js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", + "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", + "requires": { + "svg.js": "^2.2.5" + } + } + } + }, + "svg.select.js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", + "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", + "requires": { + "svg.js": "^2.6.5" + } + }, "svgo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", diff --git a/client/package.json b/client/package.json index c0e7092..e67c333 100644 --- a/client/package.json +++ b/client/package.json @@ -6,8 +6,10 @@ "@testing-library/jest-dom": "^5.16.1", "@testing-library/react": "^12.1.2", "@testing-library/user-event": "^13.5.0", + "apexcharts": "^3.42.0", "express": "^4.17.2", "react": "^17.0.2", + "react-apexcharts": "^1.4.1", "react-collapsible": "^2.8.4", "react-dom": "^17.0.2", "react-scripts": "5.0.0", diff --git a/client/src/App.js b/client/src/App.js index bdacb6b..da6eee7 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -3,8 +3,9 @@ import "./App.css"; import Collapsible from "react-collapsible"; import lists from "./modules/Generic/BilletBank"; import MilpacParse from "./modules/Generic/MilpacParse"; -import StrengthCount from "./modules/Generic/StrengthCount"; import ErrorMessage from "./errorMessage"; +import Statistics from "./modules/Generic/Statistics"; + const CLIENT_TOKEN = process.env.REACT_APP_CLIENT_TOKEN; const combatApiUrl = process.env.REACT_APP_COMBAT_API_URL; const reserveApiUrl = process.env.REACT_APP_RESERVE_API_URL; @@ -27,7 +28,7 @@ function MilpacRequest() { t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i; y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y); })(window, document, "clarity", "script", "dig85agbqz"); -`; + `; // Reusable API fetching function async function fetchData(url, setFunction) { @@ -464,136 +465,105 @@ function MilpacRequest() { triggerOpenedClassName="Title" open={true} > - {/* We need to do a count for combined, 1-7, 2-7 and acd in that order*/} -
- +
+ 7th Cavalry Regiment {"("}Active Duty and Line Billets only + {")"} +
+ - - - - -
-
- - - - - -
-
- - - - -
-
- +
First Battalion
+ +
+
+
Second Battalion
+ +
+
+
Auxillary Combat Division
+
diff --git a/client/src/modules/Generic/Statistics.js b/client/src/modules/Generic/Statistics.js new file mode 100644 index 0000000..5782e23 --- /dev/null +++ b/client/src/modules/Generic/Statistics.js @@ -0,0 +1,166 @@ +// This document utilizes Apex charts. If you plan on changing how the pie charts look, you can do it here. Apex charts docs can be found here: https://apexcharts.com/docs/ + +import React, { Component } from "react"; +import Chart from "react-apexcharts"; + +class Statistics extends Component { + constructor(props) { + super(props); + + let useRegiLogic = props.useRegiLogic; + let milpacArray = props.milpacArray; + let billetIDs = props.billetIDs; + let labelArray = props.labelArray; + let piechartData = []; + let combinedArray = Array(billetIDs.length) + .fill() + .map(() => []); + let centerLabel = props.centerLabel; + + for (let milpacIdCombat in milpacArray[0].combat.profiles) { + var primary = milpacArray[0].combat.profiles[milpacIdCombat].primary; + + for (let billetIdArray in billetIDs) { + if (billetIDs[billetIdArray].includes(primary.positionId)) { + combinedArray[billetIdArray].push(primary.positionTitle); + } + } + } + + combinedArray.forEach((subArray) => { + piechartData.push(subArray.length); + }); + + if (useRegiLogic !== true) { + this.state = { + options: { + labels: labelArray, + legend: { + show: false, + }, + dataLabels: { + enabled: false, + }, + tooltip: { + enabled: false, + }, + stroke: { + curve: ["smooth", "monotoneCubic"], + colors: ["#222222"], + width: 1.5, + }, + colors: ["#109640", "#b61217", "#2a487c", "#ebc729", "#e68c08"], + plotOptions: { + pie: { + donut: { + size: "85%", + background: "transparent", + labels: { + fontFamily: "Segoe UI", + show: true, + name: { + fontFamily: "Segoe UI", + }, + value: { + fontFamily: "Segoe UI", + fontWeight: "bold", + color: "#a1a1a1", + }, + total: { + color: "#f1f1f1", + show: true, + showAlways: false, + label: centerLabel, + fontFamily: "Segoe UI", + fontWeight: "bold", + }, + }, + }, + }, + }, + }, + series: piechartData, + }; + } else if (useRegiLogic === true) { + this.state = { + options: { + labels: labelArray, + legend: { + show: false, + }, + dataLabels: { + enabled: false, + }, + tooltip: { + enabled: false, + }, + stroke: { + curve: ["smooth", "monotoneCubic"], + colors: ["#222222"], + width: 1.5, + }, + colors: [ + "#109640", + "#b61217", + "#b61217", + "#b61217", + "#b61217", + "#2a487c", + "#2a487c", + "#2a487c", + "#2a487c", + "#ebc729", + "#ebc729", + "#ebc729", + "#ebc729", + "#ebc729", + ], + plotOptions: { + pie: { + donut: { + size: "85%", + background: "transparent", + labels: { + fontFamily: "Segoe UI", + show: true, + name: { + fontFamily: "Segoe UI", + }, + value: { + fontFamily: "Segoe UI", + fontWeight: "bold", + color: "#a1a1a1", + }, + total: { + color: "#f1f1f1", + show: true, + showAlways: false, + label: "Total Active Duty", + fontFamily: "Segoe UI", + fontWeight: "bold", + }, + }, + }, + }, + }, + }, + series: piechartData, + }; + } + } + + render() { + return ( +
+ +
+ ); + } +} + +export default Statistics; diff --git a/client/src/modules/Generic/StrengthCount.js b/client/src/modules/Generic/StrengthCount.js deleted file mode 100644 index 8e53629..0000000 --- a/client/src/modules/Generic/StrengthCount.js +++ /dev/null @@ -1,61 +0,0 @@ -import React from "react"; - -function StrengthCount(props) { - var useCompanyLevelLogic = props.useCompanyLevelLogic; - - if (useCompanyLevelLogic !== true) { - var combinedArray = []; - var milpacArray = props.milpacArray; - var billetIDs = props.billetIDs; - var subtitle = props.subtitle; - - for (var milpacIdCombat in milpacArray[0].combat.profiles) { - var primary = milpacArray[0].combat.profiles[milpacIdCombat].primary; - - for (var billetIdArray in billetIDs) { - if (billetIDs[billetIdArray].includes(primary.positionId)) { - combinedArray.push({ position: primary }); - } - } - } - - return ( -
-
-
{subtitle}
-
- Overall Strength{": "} - {combinedArray.length} -
-
-
- ); - } else if (useCompanyLevelLogic === true) { - combinedArray = []; - milpacArray = props.milpacArray; - billetIDs = props.billetIDs; - var subSubtitle = props.subSubtitle; - - for (milpacIdCombat in milpacArray[0].combat.profiles) { - primary = milpacArray[0].combat.profiles[milpacIdCombat].primary; - - for (billetIdArray in billetIDs) { - if (billetIDs[billetIdArray].includes(primary.positionId)) { - combinedArray.push({ position: primary }); - } - } - } - - return ( -
-
- {subSubtitle} - {": "} - {combinedArray.length} -
-
- ); - } -} - -export default StrengthCount;