Add PointsInput

This commit is contained in:
Mario Peters
2025-09-21 17:15:57 +02:00
parent 0d6cb570b4
commit 5e13acadab
6 changed files with 300 additions and 140 deletions

177
package-lock.json generated
View File

@@ -1,24 +1,24 @@
{
"name": "kt-vite",
"version": "3.5.0",
"version": "3.5.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "kt-vite",
"version": "3.5.0",
"version": "3.5.1",
"dependencies": {
"@mui/icons-material": "^7.3.1",
"@mui/material": "^7.3.1",
"@mui/material-pigment-css": "^7.3.1",
"@mui/icons-material": "^7.3.2",
"@mui/material": "^7.3.2",
"@mui/material-pigment-css": "^7.3.2",
"@pigment-css/react": "^0.0.30",
"@react-pdf/renderer": "^4.3.0",
"i18next": "^25.3.6",
"i18next": "^25.5.2",
"i18next-http-backend": "^3.0.2",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-i18next": "^15.6.1",
"react-router-dom": "^7.8.1",
"react-i18next": "^15.7.3",
"react-router-dom": "^7.9.1",
"source-map-explorer": "^2.5.3"
},
"devDependencies": {
@@ -42,7 +42,7 @@
"i18next-parser": "^9.3.0",
"jsdom": "^26.1.0",
"react-error-overlay": "6.1.0",
"vite": "^6.3.5",
"vite": "^6.3.6",
"vite-plugin-eslint": "^1.8.1",
"vitest": "^3.2.4"
}
@@ -460,9 +460,10 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.28.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz",
"integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==",
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
@@ -1434,20 +1435,22 @@
}
},
"node_modules/@mui/core-downloads-tracker": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.1.tgz",
"integrity": "sha512-+mIK1Z0BhOaQ0vCgOkT1mSrIpEHLo338h4/duuL4TBLXPvUMit732mnwJY3W40Avy30HdeSfwUAAGRkKmwRaEQ==",
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.2.tgz",
"integrity": "sha512-AOyfHjyDKVPGJJFtxOlept3EYEdLoar/RvssBTWVAvDJGIE676dLi2oT/Kx+FoVXFoA/JdV7DEMq/BVWV3KHRw==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
}
},
"node_modules/@mui/icons-material": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.1.tgz",
"integrity": "sha512-upzCtG6awpL6noEZlJ5Z01khZ9VnLNLaj7tb6iPbN6G97eYfUTs8e9OyPKy3rEms3VQWmVBfri7jzeaRxdFIzA==",
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.2.tgz",
"integrity": "sha512-TZWazBjWXBjR6iGcNkbKklnwodcwj0SrChCNHc9BhD9rBgET22J1eFhHsEmvSvru9+opDy3umqAimQjokhfJlQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.2"
"@babel/runtime": "^7.28.3"
},
"engines": {
"node": ">=14.0.0"
@@ -1457,7 +1460,7 @@
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@mui/material": "^7.3.1",
"@mui/material": "^7.3.2",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
@@ -1468,15 +1471,16 @@
}
},
"node_modules/@mui/material": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.1.tgz",
"integrity": "sha512-Xf6Shbo03YmcBedZMwSpEFOwpYDtU7tC+rhAHTrA9FHk0FpsDqiQ9jUa1j/9s3HLs7KWb5mDcGnlwdh9Q9KAag==",
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.2.tgz",
"integrity": "sha512-qXvbnawQhqUVfH1LMgMaiytP+ZpGoYhnGl7yYq2x57GYzcFL/iPzSZ3L30tlbwEjSVKNYcbiKO8tANR1tadjUg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.2",
"@mui/core-downloads-tracker": "^7.3.1",
"@mui/system": "^7.3.1",
"@mui/types": "^7.4.5",
"@mui/utils": "^7.3.1",
"@babel/runtime": "^7.28.3",
"@mui/core-downloads-tracker": "^7.3.2",
"@mui/system": "^7.3.2",
"@mui/types": "^7.4.6",
"@mui/utils": "^7.3.2",
"@popperjs/core": "^2.11.8",
"@types/react-transition-group": "^4.4.12",
"clsx": "^2.1.1",
@@ -1495,7 +1499,7 @@
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@mui/material-pigment-css": "^7.3.1",
"@mui/material-pigment-css": "^7.3.2",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -1516,12 +1520,13 @@
}
},
"node_modules/@mui/material-pigment-css": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@mui/material-pigment-css/-/material-pigment-css-7.3.1.tgz",
"integrity": "sha512-DiYfe2AgvJ7QR7VZN3msedZBNFk4wr61TfZ7sCYI0faQdHfDHWac1yZfh+NCFCTzof6IkVy9MD1kVGjTq54EMA==",
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mui/material-pigment-css/-/material-pigment-css-7.3.2.tgz",
"integrity": "sha512-VKdcFwOzlwnqe4m3U/O4fr7/YJiwJST5D5O2ALFjstnBf0cQAGg0yxFRnZAWy3MAGk+ocrgXBnvORSQacoEIsw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.2",
"@mui/system": "7.3.1"
"@babel/runtime": "^7.28.3",
"@mui/system": "7.3.2"
},
"engines": {
"node": ">=14.0.0"
@@ -1535,12 +1540,13 @@
}
},
"node_modules/@mui/private-theming": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.1.tgz",
"integrity": "sha512-WU3YLkKXii/x8ZEKnrLKsPwplCVE11yZxUvlaaZSIzCcI3x2OdFC8eMlNy74hVeUsYQvzzX1Es/k4ARPlFvpPQ==",
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.2.tgz",
"integrity": "sha512-ha7mFoOyZGJr75xeiO9lugS3joRROjc8tG1u4P50dH0KR7bwhHznVMcYg7MouochUy0OxooJm/OOSpJ7gKcMvg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.2",
"@mui/utils": "^7.3.1",
"@babel/runtime": "^7.28.3",
"@mui/utils": "^7.3.2",
"prop-types": "^15.8.1"
},
"engines": {
@@ -1561,11 +1567,12 @@
}
},
"node_modules/@mui/styled-engine": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.1.tgz",
"integrity": "sha512-Nqo6OHjvJpXJ1+9TekTE//+8RybgPQUKwns2Lh0sq+8rJOUSUKS3KALv4InSOdHhIM9Mdi8/L7LTF1/Ky6D6TQ==",
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.2.tgz",
"integrity": "sha512-PkJzW+mTaek4e0nPYZ6qLnW5RGa0KN+eRTf5FA2nc7cFZTeM+qebmGibaTLrgQBy3UpcpemaqfzToBNkzuxqew==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.2",
"@babel/runtime": "^7.28.3",
"@emotion/cache": "^11.14.0",
"@emotion/serialize": "^1.3.3",
"@emotion/sheet": "^1.4.0",
@@ -1594,15 +1601,16 @@
}
},
"node_modules/@mui/system": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.1.tgz",
"integrity": "sha512-mIidecvcNVpNJMdPDmCeoSL5zshKBbYPcphjuh6ZMjhybhqhZ4mX6k9zmIWh6XOXcqRQMg5KrcjnO0QstrNj3w==",
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.2.tgz",
"integrity": "sha512-9d8JEvZW+H6cVkaZ+FK56R53vkJe3HsTpcjMUtH8v1xK6Y1TjzHdZ7Jck02mGXJsE6MQGWVs3ogRHTQmS9Q/rA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.2",
"@mui/private-theming": "^7.3.1",
"@mui/styled-engine": "^7.3.1",
"@mui/types": "^7.4.5",
"@mui/utils": "^7.3.1",
"@babel/runtime": "^7.28.3",
"@mui/private-theming": "^7.3.2",
"@mui/styled-engine": "^7.3.2",
"@mui/types": "^7.4.6",
"@mui/utils": "^7.3.2",
"clsx": "^2.1.1",
"csstype": "^3.1.3",
"prop-types": "^15.8.1"
@@ -1633,11 +1641,12 @@
}
},
"node_modules/@mui/types": {
"version": "7.4.5",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.5.tgz",
"integrity": "sha512-ZPwlAOE3e8C0piCKbaabwrqZbW4QvWz0uapVPWya7fYj6PeDkl5sSJmomT7wjOcZGPB48G/a6Ubidqreptxz4g==",
"version": "7.4.6",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.6.tgz",
"integrity": "sha512-NVBbIw+4CDMMppNamVxyTccNv0WxtDb7motWDlMeSC8Oy95saj1TIZMGynPpFLePt3yOD8TskzumeqORCgRGWw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.2"
"@babel/runtime": "^7.28.3"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -1649,12 +1658,13 @@
}
},
"node_modules/@mui/utils": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.1.tgz",
"integrity": "sha512-/31y4wZqVWa0jzMnzo6JPjxwP6xXy4P3+iLbosFg/mJQowL1KIou0LC+lquWW60FKVbKz5ZUWBg2H3jausa0pw==",
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.2.tgz",
"integrity": "sha512-4DMWQGenOdLnM3y/SdFQFwKsCLM+mqxzvoWp9+x2XdEzXapkznauHLiXtSohHs/mc0+5/9UACt1GdugCX2te5g==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.2",
"@mui/types": "^7.4.5",
"@babel/runtime": "^7.28.3",
"@mui/types": "^7.4.6",
"@types/prop-types": "^15.7.15",
"clsx": "^2.1.1",
"prop-types": "^15.8.1",
@@ -1910,6 +1920,7 @@
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
@@ -2576,6 +2587,7 @@
"version": "4.4.12",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*"
}
@@ -3717,6 +3729,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
@@ -4045,6 +4058,7 @@
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
@@ -5545,9 +5559,9 @@
"integrity": "sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw=="
},
"node_modules/i18next": {
"version": "25.3.6",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.3.6.tgz",
"integrity": "sha512-dThZ0CTCM3sUG/qS0ZtQYZQcUI6DtBN8yBHK+SKEqihPcEYmjVWh/YJ4luic73Iq6Uxhp6q7LJJntRK5+1t7jQ==",
"version": "25.5.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.5.2.tgz",
"integrity": "sha512-lW8Zeh37i/o0zVr+NoCHfNnfvVw+M6FQbRp36ZZ/NyHDJ3NJVpp2HhAUyU9WafL5AssymNoOjMRB48mmx2P6Hw==",
"funding": [
{
"type": "individual",
@@ -5562,6 +5576,7 @@
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
}
],
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.27.6"
},
@@ -7350,15 +7365,16 @@
"dev": true
},
"node_modules/react-i18next": {
"version": "15.6.1",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.6.1.tgz",
"integrity": "sha512-uGrzSsOUUe2sDBG/+FJq2J1MM+Y4368/QW8OLEKSFvnDflHBbZhSd1u3UkW0Z06rMhZmnB/AQrhCpYfE5/5XNg==",
"version": "15.7.3",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.7.3.tgz",
"integrity": "sha512-AANws4tOE+QSq/IeMF/ncoHlMNZaVLxpa5uUGW1wjike68elVYr0018L9xYoqBr1OFO7G7boDPrbn0HpMCJxTw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.27.6",
"html-parse-stringify": "^3.0.1"
},
"peerDependencies": {
"i18next": ">= 23.2.3",
"i18next": ">= 25.4.1",
"react": ">= 16.8.0",
"typescript": "^5"
},
@@ -7389,9 +7405,10 @@
}
},
"node_modules/react-router": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.1.tgz",
"integrity": "sha512-5cy/M8DHcG51/KUIka1nfZ2QeylS4PJRs6TT8I4PF5axVsI5JUxp0hC0NZ/AEEj8Vw7xsEoD7L/6FY+zoYaOGA==",
"version": "7.9.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.1.tgz",
"integrity": "sha512-pfAByjcTpX55mqSDGwGnY9vDCpxqBLASg0BMNAuMmpSGESo/TaOUG6BllhAtAkCGx8Rnohik/XtaqiYUJtgW2g==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0"
@@ -7410,11 +7427,12 @@
}
},
"node_modules/react-router-dom": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.8.1.tgz",
"integrity": "sha512-NkgBCF3sVgCiAWIlSt89GR2PLaksMpoo3HDCorpRfnCEfdtRPLiuTf+CNXvqZMI5SJLZCLpVCvcZrTdtGW64xQ==",
"version": "7.9.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.1.tgz",
"integrity": "sha512-U9WBQssBE9B1vmRjo9qTM7YRzfZ3lUxESIZnsf4VjR/lXYz9MHjvOxHzr/aUm4efpktbVOrF09rL/y4VHa8RMw==",
"license": "MIT",
"dependencies": {
"react-router": "7.8.1"
"react-router": "7.9.1"
},
"engines": {
"node": ">=20.0.0"
@@ -7428,6 +7446,7 @@
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
"license": "BSD-3-Clause",
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
@@ -7761,7 +7780,8 @@
"node_modules/set-cookie-parser": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"license": "MIT"
},
"node_modules/set-function-length": {
"version": "1.2.2",
@@ -8824,10 +8844,11 @@
}
},
"node_modules/vite": {
"version": "6.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
"version": "6.3.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",

View File

@@ -19,17 +19,17 @@
"i18n": "i18next 'src/**/*.{js,jsx,ts,tsx}' --config i18next-parser.config.mjs"
},
"dependencies": {
"@mui/icons-material": "^7.3.1",
"@mui/material": "^7.3.1",
"@mui/material-pigment-css": "^7.3.1",
"@mui/icons-material": "^7.3.2",
"@mui/material": "^7.3.2",
"@mui/material-pigment-css": "^7.3.2",
"@pigment-css/react": "^0.0.30",
"@react-pdf/renderer": "^4.3.0",
"i18next": "^25.3.6",
"i18next": "^25.5.2",
"i18next-http-backend": "^3.0.2",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-i18next": "^15.6.1",
"react-router-dom": "^7.8.1",
"react-i18next": "^15.7.3",
"react-router-dom": "^7.9.1",
"source-map-explorer": "^2.5.3"
},
"devDependencies": {
@@ -53,7 +53,7 @@
"i18next-parser": "^9.3.0",
"jsdom": "^26.1.0",
"react-error-overlay": "6.1.0",
"vite": "^6.3.5",
"vite": "^6.3.6",
"vite-plugin-eslint": "^1.8.1",
"vitest": "^3.2.4"
}

View File

@@ -1,16 +1,21 @@
import { FormControl, TextField } from '@mui/material'
export default function Points(props) {
const { num, average, id, results, myRef, handlePointsChange } = props
export default function Points({ num, average, id, results, myRef, onInputClick, rowIndex }) {
const PointsField = () => {
let result = []
for (let i = 0; i < num; i++) {
result.push(
<FormControl key={id + i} sx={{ m: 1, maxWidth: 60 }} size="small">
<TextField label={'# ' + (i + 1)} name={id + '/' + i} defaultValue={results ? results[i] : average} size="small" onChange={handlePointsChange} slotProps={{
htmlInput: { ref: myRef }
}} />
<TextField
label={'# ' + (i + 1)}
name={id + '/' + i}
value={results ? results[i] : average}
size="small"
inputRef={(el) => myRef && myRef(el, i)}
// inputRef={(el) => myRef && myRef(el, i, rowIndex)} // <--- rowIndex mitgeben!
onFocus={() => onInputClick && onInputClick(i)}
readOnly
/>
</FormControl>,
)
}

View File

@@ -0,0 +1,49 @@
import Grid from '@mui/material/Grid'
import Button from '@mui/material/Button'
import { styled } from '@mui/material-pigment-css'
import { grey } from '@mui/material/colors'
import Box from '@mui/material/Box'
const Item = styled(Button)(({ theme }) => ({
backgroundColor: grey[300],
color: theme.palette.text.secondary,
}))
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
p: 4,
}
export default function PointsInput({ average, onSelect }) {
const steps = []
const min = Math.round((+average - 1) * 10)
const max = Math.round((+average + 1) * 10)
for (let i = min; i <= max; i++) {
steps.push((i / 10).toFixed(1))
}
const handleClick = (e) => {
if (onSelect) onSelect(Number(e.target.innerText))
}
return (
<Box sx={style}>
<p>Average: {average}</p>
<Grid container spacing={{ xs: 2, md: 3 }} columns={{ xs: 4, sm: 8, md: 12 }}>
{steps.map((val, index) => (
<Grid key={index} item xs={2} sm={4} md={4}>
<Item onClick={handleClick}>{val}</Item>
</Grid>
))}
</Grid>
</Box>
)
}

View File

@@ -3,8 +3,10 @@ import { useEffect, useRef, useState } from 'react'
import KataSelect from '../Kata/KataSelect'
import useFetch from '../UseFetch/UseFetch'
import Points from './Points'
import PointsInput from './PointsInput'
import { useUser } from '../../context/UserContext'
import { styled } from '@mui/material-pigment-css'
import Modal from '@mui/material/Modal'
const PREFIX = 'PointsView'
@@ -14,9 +16,9 @@ const classes = {
}
const Root = styled('div')({
margin: '2rem',
[`& .${classes.row}`]: {
display: 'table',
margin: '2rem',
display: 'flex',
alignItems: 'center',
},
[`& .${classes.row} > div`]: {
@@ -31,17 +33,41 @@ const Root = styled('div')({
})
export default function PointsView({ groupData, allParticipants, allTeams }) {
const { token, apiServer } = useUser()
const { token, apiServer } = useUser()
const { data: groupEncounters, loading } = useFetch(apiServer + '/group/encounters/' + groupData.id + token)
const [encounter, setEncounter] = useState(null)
const [judges, setJudges] = useState(5)
const [average, setAverage] = useState('')
const inputRef = useRef()
const [average, setAverage] = useState(5)
const [open, setOpen] = useState(false)
const [modalIndex, setModalIndex] = useState({ participantId: null, pointIndex: null })
const [points, setPoints] = useState({})
const inputRefs = useRef([])
const prevOpen = useRef(open)
useEffect(() => {
!loading && setEncounter(groupEncounters)
}, [groupEncounters, loading])
useEffect(() => {
if (encounter) {
setPoints((prev) => {
// Nur initialisieren, wenn noch keine Werte für diesen Teilnehmer existieren!
const initialPoints = { ...prev }
encounter.forEach((element) => {
// aka
if (element.aka && !initialPoints[element.aka]) {
initialPoints[element.aka] = element.wertungen ? [...element.wertungen] : []
}
// shiro
if (element.shiro && !initialPoints[element.shiro]) {
initialPoints[element.shiro] = element.wertungen ? [...element.wertungen] : []
}
})
return initialPoints
})
}
}, [encounter])
// Hilfsfunktion: Teilnehmerdaten für die Anzeige aufbereiten
const getEncounterData = () => {
if (!encounter) return []
@@ -86,31 +112,26 @@ export default function PointsView({ groupData, allParticipants, allTeams }) {
if (name === 'average') setAverage(value)
}
// Handler für Punkteänderung
const handlePointsChange = ({ target: { name, value } }) => {
const [id, number] = name.split('/')
let index = encounter.findIndex((i) => i.shiro === +id)
if (index === -1) index = encounter.findIndex((i) => i.aka === +id)
if (index === -1) return
// Kopie für State-Update!
const newEncounter = [...encounter]
if (!newEncounter[index].wertungen) newEncounter[index].wertungen = []
newEncounter[index].wertungen[parseInt(number)] = value
// Durchschnitt berechnen (ohne bestes/schlechtestes Ergebnis)
const sumArr = [...newEncounter[index].wertungen].map(Number).sort((a, b) => a - b)
if (sumArr.length > 2) {
sumArr.shift()
sumArr.pop()
function calcSum(arr) {
if (!Array.isArray(arr)) return ''
const nums = arr.map(Number).sort((a, b) => a - b)
if (nums.length > 2) {
nums.shift()
nums.pop()
}
let avg = sumArr.reduce((acc, el) => acc + el, 0)
newEncounter[index].wertungen.sum = avg
setEncounter(newEncounter)
if (nums.length === 0) return ''
const sum = nums.reduce((acc, el) => acc + el, 0)
return sum.toFixed(1)
}
// PointsView.jsx
const setInputRef = (rowIdx, i) => (el) => {
if (!inputRefs.current[rowIdx]) inputRefs.current[rowIdx] = []
inputRefs.current[rowIdx][i] = el
}
// Teilnehmer-Komponente
const Participant = ({ participants }) => {
const Participant = ({ participants, index }) => {
if (participants.id === 0) return null
return (
<div className={classes.row}>
@@ -120,38 +141,102 @@ export default function PointsView({ groupData, allParticipants, allTeams }) {
<strong>{participants.club}</strong>
</div>
<KataSelect val={participants.kata} />
<Points num={judges} average={average} id={participants.id} results={participants?.results} myRef={inputRef} handlePointsChange={handlePointsChange} />
<Points
onInputClick={(i) => {
setModalIndex({ participantId: participants.id, pointIndex: i, rowIndex: index })
setOpen(true)
}}
num={judges}
id={participants.id}
results={points[participants.id]}
myRef={setInputRef(index)}
rowIndex={index}
/>
<FormControl sx={{ m: 1, maxWidth: 80 }} size="small">
<TextField label="Sum" variant="filled" value={participants?.results ? participants.results.sum : ''} disabled size="small" />
<TextField label="Sum" variant="filled" value={calcSum(points[participants.id])} disabled size="small" />
</FormControl>
</div>
)
}
const handleModal = () => setOpen(!open)
const handleModalSelect = (value) => {
setPoints((prev) => {
const updated = { ...prev }
const arr = [...updated[modalIndex.participantId]]
arr[modalIndex.pointIndex] = value
updated[modalIndex.participantId] = arr
return updated
})
// Fokussiere das nächste Feld oder schließe das Modal (nur wenn nicht letztes Feld)
if (modalIndex.pointIndex < judges - 1) {
setModalIndex({
participantId: modalIndex.participantId,
pointIndex: modalIndex.pointIndex + 1,
rowIndex: modalIndex.rowIndex, // <--- Zeilenindex beibehalten!
})
setTimeout(() => {
inputRefs.current[modalIndex.rowIndex]?.[modalIndex.pointIndex + 1]?.focus()
}, 0)
} else {
setOpen(false)
}
}
const flatParticipants = []
encounterData.forEach((el) => {
if (el.aka?.id) flatParticipants.push(el.aka)
if (el.shiro?.id) flatParticipants.push(el.shiro)
})
useEffect(() => {
// Nur wenn das Modal gerade von offen auf geschlossen gewechselt ist
if (prevOpen.current && !open && modalIndex && points[modalIndex.participantId]?.length === judges) {
setEncounter((prevEncounter) => {
if (!prevEncounter) return prevEncounter
const newEncounter = [...prevEncounter]
// Nur den einen Teilnehmer aktualisieren!
const participantId = modalIndex.participantId
const encounterIdx = newEncounter.findIndex((e) => e.aka === participantId || e.shiro === participantId)
if (encounterIdx !== -1) {
newEncounter[encounterIdx].wertungen = [...points[participantId]]
newEncounter[encounterIdx].wertungen.sum = calcSum(points[participantId])
}
return newEncounter
})
}
prevOpen.current = open
// eslint-disable-next-line
}, [open, points])
return (
<Root>
{/* Judges-Auswahl */}
<FormControl sx={{ m: 1, minWidth: 120 }} size="small">
<InputLabel id="judges">judges</InputLabel>
<Select labelId="judges" id="judges" name="judges" value={judges} label="judges" onChange={changeValue}>
{[3, 4, 5, 6, 7].map((el) => (
<MenuItem key={'judges' + el} value={el}>
{el}
</MenuItem>
))}
</Select>
</FormControl>
{/* Average */}
<FormControl sx={{ m: 1, maxWidth: 100 }}>
<TextField name="average" type="number" label="average" value={average} size="small" onChange={changeValue} />
</FormControl>
<div>
{/* Judges-Auswahl */}
<FormControl sx={{ m: 1, minWidth: 120 }} size="small">
<InputLabel id="judges">judges</InputLabel>
<Select labelId="judges" id="judges" name="judges" value={judges} label="judges" onChange={changeValue}>
{[3, 4, 5, 6, 7].map((el) => (
<MenuItem key={'judges' + el} value={el}>
{el}
</MenuItem>
))}
</Select>
</FormControl>
{/* Average */}
<FormControl sx={{ m: 1, maxWidth: 100 }}>
<TextField name="average" type="number" label="average" value={average} size="small" onChange={changeValue} />
</FormControl>
</div>
{/* Begegnungen */}
{encounterData.map((el, i) => (
<div key={i}>
<Participant participants={el.aka} />
<Participant participants={el.shiro} />
</div>
{flatParticipants.map((participant, idx) => (
<Participant key={participant.id + '-' + idx} participants={participant} index={idx} />
))}
<Modal open={open} onClose={handleModal} aria-labelledby="modal-modal-title" aria-describedby="modal-modal-description" size="md">
<PointsInput average={average} onSelect={handleModalSelect} />
</Modal>
</Root>
)
}

View File

@@ -33,7 +33,7 @@ export default function GroupTeams({ groupId, reload }) {
return (
<>
{groupTeams.map((el, index) => (
<Root key={`${el.id}-${groupId}`} data-tid={el.id} data-gid={groupId} draggable onDragStart={handleDragStart}>
<Root key={`${el.id}-${groupId}-${index}`} data-tid={el.id} data-gid={groupId} draggable onDragStart={handleDragStart}>
<span className={classes.id}>{index + 1}.</span>
<strong>{el.teamName}</strong>({el.id})
</Root>