├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── client ├── .gitignore ├── README.md ├── craco.config.js ├── package-lock.json ├── package.json ├── public │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── index.html │ ├── logo256.png │ ├── logo512.png │ ├── mstile-150x150.png │ ├── robots.txt │ ├── safari-pinned-tab.svg │ ├── site.webmanifest │ └── sitemap.xml ├── src │ ├── App.tsx │ ├── ThemeProvider.tsx │ ├── components │ │ ├── country │ │ │ ├── CountriesTable.tsx │ │ │ ├── CountryContributors.tsx │ │ │ ├── CountryDetails.tsx │ │ │ ├── CountryGraphs.tsx │ │ │ └── CountryPlayers.tsx │ │ ├── graphs │ │ │ ├── CompareGraph.tsx │ │ │ ├── CountryPlayersGraph.tsx │ │ │ ├── CustomResponsiveContainer.tsx │ │ │ ├── GraphDropdown.tsx │ │ │ ├── TimeScatterGraph.tsx │ │ │ ├── TimeSeriesChart.tsx │ │ │ └── util.ts │ │ ├── misc │ │ │ ├── ChangeLog.tsx │ │ │ ├── SimpleSummaryAccordion.tsx │ │ │ ├── TopPlays.tsx │ │ │ └── TopPlaysDatePicker.tsx │ │ ├── navigation │ │ │ ├── DarkMode.tsx │ │ │ ├── Footer.tsx │ │ │ ├── Header.tsx │ │ │ ├── HomeLink.tsx │ │ │ ├── Menu.tsx │ │ │ ├── Search.tsx │ │ │ └── SearchResults.tsx │ │ ├── pages │ │ │ ├── All.tsx │ │ │ ├── Compare.tsx │ │ │ ├── Country.tsx │ │ │ ├── Historic.tsx │ │ │ ├── Home.tsx │ │ │ ├── Loading.tsx │ │ │ ├── Redirect.tsx │ │ │ ├── Stats.tsx │ │ │ └── User.tsx │ │ ├── stats │ │ │ ├── PPBarriers.tsx │ │ │ ├── TopMapsets.tsx │ │ │ └── TopPlay.tsx │ │ └── user │ │ │ ├── UserDetails.tsx │ │ │ ├── UserGraphs.tsx │ │ │ └── UsersTable.tsx │ ├── index.scss │ ├── index.tsx │ ├── react-app-env.d.ts │ ├── resources │ │ ├── changelog.json │ │ └── collection-helper.png │ └── util │ │ ├── parseUrl.ts │ │ └── selectTheme.ts ├── tailwind.config.js ├── tsconfig.json └── yarn.lock ├── interfaces ├── country.ts ├── search.ts └── stats.ts ├── models ├── Beatmap.model.ts ├── BeatmapCount.model.ts ├── BeatmapSetCount.model.ts ├── Country.model.ts ├── CountryPlayers.model.ts ├── CountryPlays.model.ts ├── CountryStat.model.ts ├── HistoricTop.model.ts ├── OverallStats.model.ts ├── PPBarrier.model.ts ├── Score.ts ├── TopPlayCount.model.ts ├── User.model.ts ├── UserPlays.model.ts └── UserStat.model.ts ├── package-lock.json ├── package.json ├── routes ├── countries.ts ├── search.ts ├── stats.ts └── users.ts └── server.ts /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | client/node_modules 4 | client/build -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | client/node_modules 4 | client/build -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | WORKDIR /usr/src/app 3 | COPY package*.json . 4 | RUN npm install 5 | COPY . . 6 | RUN cd client && npm install 7 | RUN cd client && npm run build 8 | EXPOSE 8080 9 | CMD ["npm", "run", "start"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Logo 4 | 5 | 6 | [osuTracker.com](https://osutracker.com) 7 |

8 | 9 |
10 | API Docs 11 | · 12 | Report a Bug 13 | · 14 | Request a Feature 15 |
16 | 17 |
18 |
19 | 20 | [![codefactor](https://img.shields.io/codefactor/grade/github/nzbasic/osutracker)](https://github.com/nzbasic/osutracker) 21 | [![stars](https://img.shields.io/github/stars/nzbasic/osutracker?style=flat-square)](https://github.com/nzbasic/osutracker) 22 | [![lastcommit](https://img.shields.io/github/last-commit/nzbasic/osutracker)](https://github.com/nzbasic/osutracker) 23 | 24 | 25 | [![typescript](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white)](https://github.com/microsoft/TypeScript) 26 | [![react](https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB)](https://github.com/facebook/react) 27 | [![mongodb](https://img.shields.io/badge/MongoDB-4EA94B?style=for-the-badge&logo=mongodb&logoColor=white)](https://github.com/mongodb/mongo) 28 | [![tailwind](https://img.shields.io/badge/Tailwind_CSS-38B2AC?style=for-the-badge&logo=tailwind-css&logoColor=white)](https://github.com/tailwindlabs/tailwindcss) 29 | [![nodejs](https://img.shields.io/badge/Node.js-339933?style=for-the-badge&logo=nodedotjs&logoColor=white)](https://github.com/nodejs/node) 30 | 31 | 32 | [![coffee](https://img.shields.io/badge/Buy_Me_A_Coffee-FFDD00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black)](https://www.buymeacoffee.com/nzbasic) 33 | 34 |
35 | 36 |
37 | Table of Contents 38 | 39 | - [About](#about) 40 | - [Screenshots](#screenshots) 41 | - [Contributing](#contributing) 42 | - [Support](#support) 43 | - [Donate](#donate) 44 | 45 |
46 | 47 | --- 48 | 49 | ## About 50 | 51 | 52 | 53 | 69 | 70 |
54 | 55 | osuTracker is a tool that tracks osu! statistics of any player who signs up. It also combines the players of every country to give custom country statistics such as the top 100 play history and top 10 players over time. Data is collected once a day; when it is collected, the most common beatmaps seen in profiles are counted up to find the most popular mapsets in the game which is used to track the farm percentage of each player. The API for osuTracker is public and has [full documentation](https://wiki.nzbasic.com/docs/osuTracker/aboutOsuTracker). 56 | 57 | The key features of **osuTracker**: 58 | 59 | - Tracking user stats 60 | - Tracking country stats 61 | - Allows any user to add themselves automatically 62 | - Tracking history of top 100 plays for each country (and global) 63 | - Tracking history of top 10 plays for each user 64 | - Compare user/country stats 65 | - Public API for devs 66 | - Optimised for mobile and desktop views 67 | 68 |
71 | 72 | ### Screenshots 73 | 74 | Home 75 | ![image](https://cdn.discordapp.com/attachments/627267590862929961/904477419413602385/unknown.png) 76 | 77 | User Profile: [Link](https://osutracker.com/user/7562902) 78 | ![image](https://cdn.discordapp.com/attachments/627267590862929961/904477586846019594/unknown.png) 79 | 80 | Comparing Users: [Link](https://osutracker.com/compare?one=13767572&two=15293080&three=6934358&four=13192231&) 81 | ![image](https://media.discordapp.net/attachments/627267590862929961/904477753598951474/unknown.png) 82 | 83 | Dark Mode: 84 | ![image](https://cdn.discordapp.com/attachments/627267590862929961/904477893185400852/unknown.png) 85 | 86 | ## Contributing 87 | 88 | First off, thanks for taking the time to contribute! Contributions are what makes the open-source community such an amazing place to learn, inspire, and create. Any contributions you make will benefit everybody else and are **greatly appreciated**. 89 | 90 | Please try to create bug reports that are: 91 | 92 | - _Reproducible._ Include steps to reproduce the problem. 93 | - _Specific._ Include as much detail as possible: which version, what environment, etc. 94 | - _Unique._ Do not duplicate existing opened issues. 95 | - _Scoped to a Single Bug._ One bug per report. 96 | 97 | ## Support 98 | 99 | Reach out to the maintainer at one of the following places: 100 | 101 | - Discord: basic#7373 102 | - Twitter: @nzbasic 103 | - osu!: YEP 104 | - Email: jamescoppard024@gmail.com 105 | 106 | ## Donate 107 | 108 | If you would like to support me I would greatly appreciate it. 109 | 110 | [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/nzbasic) 111 | 112 | Crypto 113 | - NANO: nano_3ymx5ymxgwrsfc53mem7bfmwjgzwxhtzp41wdkepnxmjdzzhhf3dgiiif8qc 114 | - ETH: 0x46cB2b27C5607282BAdAaf9973EFd728D202A1d3 115 | - BTC: bc1q0f0xtmmf7n05qgnmeun6ytc8z676j8tryszrr3 116 | - DOGE: DRRhYtaFFoyGUaU1h8MyE8LBbMETjDU5AR 117 | 118 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /client/craco.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | style: { 3 | postcss: { 4 | plugins: [require("tailwindcss"), require("autoprefixer")], 5 | }, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "proxy": "http://localhost:8080", 6 | "dependencies": { 7 | "@craco/craco": "^6.3.0", 8 | "@emotion/react": "^11.5.0", 9 | "@emotion/styled": "^11.3.0", 10 | "@material-ui/core": "^4.12.3", 11 | "@material-ui/icons": "^4.11.2", 12 | "@mui/material": "^5.0.6", 13 | "@testing-library/jest-dom": "^5.11.4", 14 | "@testing-library/react": "^11.1.0", 15 | "@testing-library/user-event": "^12.1.10", 16 | "@types/cors": "^2.8.12", 17 | "@types/jest": "^26.0.15", 18 | "@types/lodash": "^4.14.176", 19 | "@types/node": "^12.0.0", 20 | "@types/react": "^17.0.32", 21 | "@types/react-datepicker": "^4.3.4", 22 | "@types/react-dom": "^17.0.0", 23 | "@types/react-helmet": "^6.1.4", 24 | "@types/react-router-dom": "^5.3.1", 25 | "@types/recharts": "^1.8.23", 26 | "@types/uuid": "^8.3.1", 27 | "axios": "^0.23.0", 28 | "hamburger-react": "^2.4.1", 29 | "history": "^5.0.1", 30 | "lodash": "^4.17.21", 31 | "moment": "^2.29.1", 32 | "node-sass": "^5.0.0", 33 | "query-string": "^7.0.1", 34 | "react": "^17.0.2", 35 | "react-animated-css": "^1.2.1", 36 | "react-contextmenu": "^2.14.0", 37 | "react-datepicker": "^4.5.0", 38 | "react-dom": "^17.0.2", 39 | "react-helmet": "^6.1.0", 40 | "react-router-dom": "^6.0.0-beta.8", 41 | "react-scripts": "4.0.3", 42 | "react-select": "^5.1.0", 43 | "react-timezone-select": "^1.1.15", 44 | "react-toastify": "^8.0.3", 45 | "recharts": "^2.1.5", 46 | "string-to-color": "^2.2.2", 47 | "typescript": "^4.1.2", 48 | "use-debounce": "^7.0.0", 49 | "uuid": "^8.3.2", 50 | "web-vitals": "^1.0.1" 51 | }, 52 | "scripts": { 53 | "start": "craco start", 54 | "build": "craco build", 55 | "test": "craco test", 56 | "eject": "react-scripts eject" 57 | }, 58 | "eslintConfig": { 59 | "extends": [ 60 | "react-app", 61 | "react-app/jest" 62 | ] 63 | }, 64 | "browserslist": { 65 | "production": [ 66 | ">0.2%", 67 | "not dead", 68 | "not op_mini all" 69 | ], 70 | "development": [ 71 | "last 1 chrome version", 72 | "last 1 firefox version", 73 | "last 1 safari version" 74 | ] 75 | }, 76 | "devDependencies": { 77 | "autoprefixer": "^9.8.8", 78 | "postcss": "^7.0.39", 79 | "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /client/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nzbasic/osutracker/057a912b3e779fb71866a6c71b7d816d7cdbb18d/client/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /client/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nzbasic/osutracker/057a912b3e779fb71866a6c71b7d816d7cdbb18d/client/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /client/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nzbasic/osutracker/057a912b3e779fb71866a6c71b7d816d7cdbb18d/client/public/apple-touch-icon.png -------------------------------------------------------------------------------- /client/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /client/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nzbasic/osutracker/057a912b3e779fb71866a6c71b7d816d7cdbb18d/client/public/favicon-16x16.png -------------------------------------------------------------------------------- /client/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nzbasic/osutracker/057a912b3e779fb71866a6c71b7d816d7cdbb18d/client/public/favicon-32x32.png -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nzbasic/osutracker/057a912b3e779fb71866a6c71b7d816d7cdbb18d/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 33 | osuTracker 34 | 35 | 36 | 37 |
38 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /client/public/logo256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nzbasic/osutracker/057a912b3e779fb71866a6c71b7d816d7cdbb18d/client/public/logo256.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nzbasic/osutracker/057a912b3e779fb71866a6c71b7d816d7cdbb18d/client/public/logo512.png -------------------------------------------------------------------------------- /client/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nzbasic/osutracker/057a912b3e779fb71866a6c71b7d816d7cdbb18d/client/public/mstile-150x150.png -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/public/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /client/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /client/public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | https://osutracker.com/ 10 | 11 | 12 | https://osutracker.com/historic 13 | 14 | 15 | https://osutracker.com/compare 16 | 17 | 18 | https://osutracker.com/stats 19 | 20 | 21 | https://osutracker.com/country/Global 22 | 23 | -------------------------------------------------------------------------------- /client/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Route, Routes } from "react-router-dom"; 2 | import { Footer } from "./components/navigation/Footer"; 3 | import { Header } from "./components/navigation/Header"; 4 | import { All } from "./components/pages/All"; 5 | import { Compare } from "./components/pages/Compare"; 6 | import { Country } from "./components/pages/Country"; 7 | import { Historic } from "./components/pages/Historic"; 8 | import { Home } from "./components/pages/Home"; 9 | import { Redirect } from "./components/pages/Redirect"; 10 | import { Stats } from "./components/pages/Stats"; 11 | import { User } from "./components/pages/User"; 12 | import ThemeProvider from "./ThemeProvider"; 13 | 14 | export const App = () => { 15 | 16 | return ( 17 | 18 | 19 |
20 |
21 |
22 | 23 | } /> 24 | } /> 25 | } /> 26 | } /> 27 | } /> 28 | } /> 29 | } /> 30 | } /> 31 | } /> 32 | 33 |
35 |
36 |
37 | ); 38 | } 39 | 40 | export default App; 41 | -------------------------------------------------------------------------------- /client/src/ThemeProvider.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, createContext, useEffect } from "react"; 2 | 3 | export interface Theme { 4 | mode: string, 5 | toggle: () => void 6 | } 7 | 8 | export const ThemeContext = createContext(null); 9 | 10 | const ThemeProvider = ({ children }: any) => { 11 | const [mode, setTheme] = useState("light"); 12 | 13 | useEffect(() => { 14 | if (localStorage.getItem("theme") === "dark") { 15 | document.documentElement.classList.add("dark"); 16 | setTheme("dark"); 17 | } 18 | }, []); 19 | 20 | const toggleDarkMode = () => { 21 | const doc = document.documentElement; 22 | 23 | if (mode === "light") { 24 | doc.classList.add("dark"); 25 | setTheme("dark") 26 | localStorage.setItem("theme", "dark"); 27 | } else { 28 | doc.classList.remove("dark"); 29 | setTheme("light") 30 | localStorage.setItem("theme", "light"); 31 | } 32 | }; 33 | 34 | return ( 35 | toggleDarkMode() 39 | }} 40 | > 41 | {children} 42 | 43 | ); 44 | }; 45 | 46 | export default ThemeProvider; -------------------------------------------------------------------------------- /client/src/components/country/CountriesTable.tsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { useEffect, useState } from "react"; 3 | import { Country } from "../../../../models/Country.model"; 4 | 5 | interface Header { 6 | name: string 7 | value: string 8 | mobile?: boolean 9 | } 10 | 11 | const headers: Header[] = [ 12 | { name: "Rank", value: "rank", mobile: true }, 13 | { name: "Name", value: "name", mobile: true }, 14 | { name: "pp (Plays)", value: "pp", mobile: true }, 15 | { name: "pp (Players)", value: "playerWeighting", mobile: true }, 16 | { name: "Acc", value: "acc", mobile: true }, 17 | { name: "Farm", value: "farm" }, 18 | { name: "Range", value: "range" }, 19 | { name: "Objects/Play", value: "averageObjects" } 20 | ] 21 | 22 | export const CountriesTable = () => { 23 | const [data, setData] = useState([]); 24 | const [sorting, setSorting] = useState("pp") 25 | const [order, setOrder] = useState("desc") 26 | 27 | useEffect(() => { 28 | document.title = "All Countries" 29 | axios 30 | .get("/api/countries/limitedAll") 31 | .then((data) => { 32 | const countries = data.data.sort((a, b) => parseFloat(b.pp) - parseFloat(a.pp)) 33 | countries.forEach((country, index) => { 34 | country.rank = index 35 | }) 36 | setData(countries) 37 | }); 38 | }, []) 39 | 40 | useEffect(() => { 41 | if (order === "asc") { 42 | setData(data.sort((a: any, b: any) => parseFloat(a[sorting]) - parseFloat(b[sorting]))) 43 | } else { 44 | setData(data.sort((a: any, b: any) => parseFloat(b[sorting]) - parseFloat(a[sorting]))) 45 | } 46 | }, [sorting, order, data]) 47 | 48 | const parseSorting = (value: string) => { 49 | if (value === sorting) { 50 | if (order === "desc") { 51 | setOrder("asc") 52 | } else { 53 | setOrder("desc") 54 | } 55 | } else { 56 | setSorting(value) 57 | } 58 | } 59 | 60 | return ( 61 |
62 | 63 | 64 | 65 | {headers.map((header) => ( 66 | 67 | ))} 68 | 69 | 70 | {data.map((d, index) => ( 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | ))} 84 |
parseSorting(header.value)} className={`${!header.mobile && 'hidden md:table-cell'} hover:underline cursor-pointer`} key={header.name}>{header.name}
{d?.rank??0}{d.name}{parseFloat(d.pp).toFixed(0)}{(d?.playerWeighting??0).toFixed(0)}{(d.acc*100).toFixed(2)}%{d.farm}%{parseFloat(d.range).toFixed(0)}{d.averageObjects.toFixed(0)}
85 |
86 | ) 87 | } -------------------------------------------------------------------------------- /client/src/components/country/CountryContributors.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import { Contributor } from "../../../../models/Country.model"; 3 | 4 | export const CountryContributors = ({ contributors }: { contributors: Contributor[] }) => { 5 | return ( 6 |
7 | {contributors.sort((a, b) => b.pp - a.pp).map((contributor, index) => ( 8 |
9 | {contributor.name === "Bonus pp" ? Bonus pp : ( 10 | {contributor.name} 11 | )} 12 | {contributor.pp.toFixed(2)} 13 |
14 | ))} 15 |
16 | ) 17 | } -------------------------------------------------------------------------------- /client/src/components/country/CountryDetails.tsx: -------------------------------------------------------------------------------- 1 | import { Country } from "../../../../models/Country.model" 2 | 3 | export const CountryDetails = ({ details }: { details: Country }) => { 4 | return ( 5 |
6 | {details.name === "Global" ? ( 7 | flag 8 | ) : ( 9 | flag 10 | )} 11 |
12 | {details.name} 13 | {parseFloat(details.pp).toFixed(0)}pp 14 | Acc {(details.acc*100).toFixed(2)}% 15 | Range {parseFloat(details.range).toFixed(0)}pp 16 | Farm {details.farm}% 17 |
18 |
19 | ) 20 | } -------------------------------------------------------------------------------- /client/src/components/country/CountryGraphs.tsx: -------------------------------------------------------------------------------- 1 | import { CircularProgress } from "@material-ui/core" 2 | import axios from "axios" 3 | import { useEffect, useState } from "react" 4 | import { CountryStat } from "../../../../models/CountryStat.model" 5 | import GraphDropdown, { Option } from '../graphs/GraphDropdown' 6 | import { TimeSeriesChart } from "../graphs/TimeSeriesChart" 7 | import { GraphData } from "../graphs/util" 8 | 9 | export const countryOptions: Option[] = [ 10 | { value: "pp", label: "Performance", reversed: false }, 11 | { value: "acc", label: "Accuracy", reversed: false }, 12 | { value: "farm", label: "Farm", reversed: false }, 13 | { value: "range", label: "Range", reversed: false }, 14 | { value: "playerWeighting", label: "Player Weighted pp", reversed: false }, 15 | ]; 16 | 17 | export const CountryGraphs = ({ name }: { name: string }) => { 18 | const [graphDataMap, setGraphDataMap] = useState>(new Map()) 19 | const [isLoading, setLoading] = useState(true) 20 | const [graphType, setGraphType] = useState