diff --git a/package-lock.json b/package-lock.json
index 50ca489..408501d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
+ "@mui/icons-material": "^6.0.1",
"@mui/material": "^6.0.1",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
@@ -3394,6 +3395,31 @@
"url": "https://opencollective.com/mui-org"
}
},
+ "node_modules/@mui/icons-material": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.0.1.tgz",
+ "integrity": "sha512-CsgaF65jA3H1YzpDg6H2nFH/UHueVlmpEtPim7xF9VbjYnmnblG3aX0GflBahH96Pg0schrFWyRySlgbVAh5Kw==",
+ "dependencies": {
+ "@babel/runtime": "^7.25.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@mui/material": "^6.0.1",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@mui/material": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.0.1.tgz",
@@ -23069,6 +23095,14 @@
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.0.1.tgz",
"integrity": "sha512-TmKkCTwgtwvlFTF1tZzG4lYbi7v6NGweEJwFBZoIWZrkF1OLa0xu4umifmIyd+bVIScsEj//E2AD6bOJbPMOOQ=="
},
+ "@mui/icons-material": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.0.1.tgz",
+ "integrity": "sha512-CsgaF65jA3H1YzpDg6H2nFH/UHueVlmpEtPim7xF9VbjYnmnblG3aX0GflBahH96Pg0schrFWyRySlgbVAh5Kw==",
+ "requires": {
+ "@babel/runtime": "^7.25.0"
+ }
+ },
"@mui/material": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.0.1.tgz",
diff --git a/package.json b/package.json
index c45e3fd..552dcb2 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
+ "@mui/icons-material": "^6.0.1",
"@mui/material": "^6.0.1",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
diff --git a/public/index.html b/public/index.html
index aa069f2..b2ea4e0 100644
--- a/public/index.html
+++ b/public/index.html
@@ -2,42 +2,22 @@
-
-
-
-
+
-
- React App
+ OSRS Leagues Planner
diff --git a/src/App.js b/src/App.js
index 358d6ff..d28a231 100644
--- a/src/App.js
+++ b/src/App.js
@@ -6,6 +6,7 @@ import StepList from './components/StepList';
import StepDetails from './components/StepDetails';
import Inventory from './components/Inventory';
import MapView from './components/MapView';
+import SkillTable from './components/SkillTable';
import './App.css';
import useSteps from './hooks/useSteps';
import { handleFileUpload, handleDownloadCSV } from './utils/csvUtils';
@@ -80,6 +81,7 @@ function App() {
<>
+
{
+for (let i = xpTable.length - 1; i >= 0; i--) {
+ if (xp >= xpTable[i].xp) {
+ return xpTable[i].level;
+ }
+}
+return 1; // Default to level 1 if no match
+};
+
+const SkillTable = ({ steps, currentStep }) => {
+ const [open, setOpen] = useState(false);
+ const [skillXP, setSkillXP] = useState({});
+
+ useEffect(() => {
+ const skillXPMap = {};
+
+ // Calculate XP gains from all previous tasks
+ for (let i = 0; i < steps.length; i++) {
+ steps[i].description.split('\n').forEach(line => {
+ skills.forEach(skill => {
+ const regex = new RegExp(`${skill}\\s*\\+([0-9,]+)\\s*XP`, 'i');
+ const match = line.match(regex);
+ if (match) {
+ const xpValue = parseInt(match[1].replace(/,/g, ''), 10);
+ if (!skillXPMap[skill]) {
+ skillXPMap[skill] = { total: 0, includingCurrent: 0 };
+ }
+ if (i < currentStep) {
+ skillXPMap[skill].total += xpValue;
+ }
+ if (i <= currentStep) {
+ skillXPMap[skill].includingCurrent += xpValue;
+ }
+ }
+ });
+ });
+ }
+
+ setSkillXP(skillXPMap);
+ }, [steps, currentStep]);
+
+ // Filter skills with XP
+ const skillsWithXP = Object.keys(skillXP).filter(skill => skillXP[skill].total > 0 || skillXP[skill].includingCurrent > 0);
+
+ return (
+
+
+
+ setOpen(!open)} size="small">
+ {open ? : }
+ Skill XP Tracker
+
+
+
+
+
+
+ Skill
+ Total XP
+ Including Current
+
+
+
+ {skillsWithXP.map(skill => (
+
+ {skill}
+
+ {skillXP[skill]?.total.toLocaleString()} ({getLevelFromXP(skillXP[skill]?.total)})
+
+
+ {skillXP[skill]?.includingCurrent.toLocaleString()} ({getLevelFromXP(skillXP[skill]?.includingCurrent)})
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default SkillTable;
diff --git a/src/components/TaskTracker.js b/src/components/TaskTracker.js
new file mode 100644
index 0000000..f5533fa
--- /dev/null
+++ b/src/components/TaskTracker.js
@@ -0,0 +1,46 @@
+import React, { useState, useEffect } from 'react';
+import { Box, Typography } from '@mui/material';
+
+const TaskTracker = ({ steps, currentStep }) => {
+ const [tasksBefore, setTasksBefore] = useState(0);
+ const [tasksIncluding, setTasksIncluding] = useState(0);
+ const [pointsBefore, setPointsBefore] = useState(0);
+ const [pointsIncluding, setPointsIncluding] = useState(0);
+
+ useEffect(() => {
+ let beforeTasks = 0;
+ let beforePoints = 0;
+ let includingTasks = 0;
+ let includingPoints = 0;
+
+ for (let i = 0; i <= currentStep; i++) {
+ const taskMatches = steps[i].description.match(/<(\d+)>/g);
+ if (taskMatches) {
+ taskMatches.forEach(match => {
+ const points = parseInt(match.replace(/[<>]/g, ''), 10);
+ includingTasks += 1;
+ includingPoints += points;
+ if (i < currentStep) {
+ beforeTasks += 1;
+ beforePoints += points;
+ }
+ });
+ }
+ }
+
+ setTasksBefore(beforeTasks);
+ setPointsBefore(beforePoints);
+ setTasksIncluding(includingTasks);
+ setPointsIncluding(includingPoints);
+ }, [steps, currentStep]);
+
+ return (
+
+
+ {tasksIncluding} tasks incl. current ({pointsIncluding} pts)
+
+
+ );
+};
+
+export default TaskTracker;