From 747824be79ba9bb974f80bc8e1249ed1e9b1cc60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marian=20=C5=A0=C3=A1mal?= Date: Sun, 11 Jun 2023 16:55:55 +0200 Subject: [PATCH] A lotta work (scripts n stuff) --- .gitignore | 8 + README.md | 18 +++ package-lock.json | 268 +++++++++++++++++++++++++++++-- package.json | 7 +- public/vite.svg | 1 - scripts/filldb.ts | 37 +++++ scripts/filldb/games.ts | 35 ++++ scripts/filldb/parse.ts | 51 ++++++ scripts/filldb/players.ts | 32 ++++ scripts/filldb/teams.ts | 43 +++++ src/App.tsx | 2 +- src/components/app-header.tsx | 4 +- src/components/current-match.tsx | 9 +- src/components/team-view.tsx | 2 +- src/logic.ts | 9 ++ src/pages/_router.tsx | 4 + src/pages/table.tsx | 97 +++++++++++ src/pages/voting.tsx | 9 ++ src/vite-env.d.ts | 9 ++ tsconfig.json | 2 +- 20 files changed, 627 insertions(+), 20 deletions(-) delete mode 100644 public/vite.svg create mode 100644 scripts/filldb.ts create mode 100644 scripts/filldb/games.ts create mode 100644 scripts/filldb/parse.ts create mode 100644 scripts/filldb/players.ts create mode 100644 scripts/filldb/teams.ts create mode 100644 src/pages/table.tsx create mode 100644 src/pages/voting.tsx diff --git a/.gitignore b/.gitignore index a7de629..a74b81a 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,11 @@ dist-ssr pocketbase pb_data/ pb_migrations/ + +# env +.env +.env.local +.env.* + +# scripts data +tymy.csv diff --git a/README.md b/README.md index 7f4d424..f5c532d 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,16 @@ GJP Cup was so great that there is GJP Cup 2 now 3. `./pocketbase serve` in one terminal 4. `npm run dev` in second terminal +# Scripts +## Fill DB +To fill the database: +1. Open the Týmy Google Sheet +2. Download it as a CSV +3. Put it to the root folder (right next to this `README.md`) as `tymy.csv` +4. Run `npm run filldb` +5. --> The db gets filled with players, teams and games (games are in a really shitty order). Including one game with teachers. +You can change credentials, db url and other stuff at `scripts/filldb.ts`. + # Good to know ## Custom `pocketbase-react` We're using our own distribution of `pocketbase-react` (available at https://github.com/radeksoft/pocketbase-react/tree/radeksoft), installed in the `package.json` as a Github repo. The installation should still be as simple as `npm install`. @@ -19,3 +29,11 @@ We're using our own distribution of `pocketbase-react` (available at https://git **Caveats:** - importing by `import { ... } from 'pocketbase-react/src';` because we can't import the package (by it's `package.json`), but we're loading the source code directly - React StrictMode is disabled, because `pocketbase-react` is not good at unsubscribing on component unmount (caused double entries on realtime updates) + +## no's +Numbering of games and teams (`games.no`, `teams.no` and `misc.currentGameNo`) starts from 0. Do +1 when presenting to final user (probably never?) + +## Deploy +build with `npx vite build` (not `npm run build`, coz we have typescript errors everywhere) + +deploy by sth like `rsync --info=progress2 -r dist/* root@mariansam.eu:/srv/www/gjpcup.radeksoft.cz/` diff --git a/package-lock.json b/package-lock.json index 733b5c5..4399168 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,14 +16,18 @@ "wouter": "^2.11.0" }, "devDependencies": { + "@types/node": "^20.3.0", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@typescript-eslint/eslint-plugin": "^5.57.1", "@typescript-eslint/parser": "^5.57.1", "@vitejs/plugin-react-swc": "^3.0.0", + "dotenv": "^16.1.4", "eslint": "^8.38.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.3.4", + "pocketbase": "^0.15.2", + "tsx": "^3.12.7", "typescript": "^5.0.2", "vite": "^4.3.2" } @@ -39,6 +43,36 @@ "node": ">=6.9.0" } }, + "node_modules/@esbuild-kit/cjs-loader": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@esbuild-kit/cjs-loader/-/cjs-loader-2.4.2.tgz", + "integrity": "sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==", + "dev": true, + "dependencies": { + "@esbuild-kit/core-utils": "^3.0.0", + "get-tsconfig": "^4.4.0" + } + }, + "node_modules/@esbuild-kit/core-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.1.0.tgz", + "integrity": "sha512-Uuk8RpCg/7fdHSceR1M6XbSZFSuMrxcePFuGgyvsBn+u339dk5OeL4jv2EojwTN2st/unJGsVm4qHWjWNmJ/tw==", + "dev": true, + "dependencies": { + "esbuild": "~0.17.6", + "source-map-support": "^0.5.21" + } + }, + "node_modules/@esbuild-kit/esm-loader": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.5.5.tgz", + "integrity": "sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw==", + "dev": true, + "dependencies": { + "@esbuild-kit/core-utils": "^3.0.0", + "get-tsconfig": "^4.4.0" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", @@ -535,6 +569,14 @@ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" } }, + "node_modules/@react-aria/ssr/node_modules/@swc/helpers": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", + "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@reduxjs/toolkit": { "version": "1.9.5", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz", @@ -792,9 +834,12 @@ } }, "node_modules/@swc/helpers": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", - "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz", + "integrity": "sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -814,6 +859,12 @@ "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, + "node_modules/@types/node": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.0.tgz", + "integrity": "sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ==", + "dev": true + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -1189,6 +1240,12 @@ "node": ">=8" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1326,6 +1383,18 @@ "csstype": "^3.0.2" } }, + "node_modules/dotenv": { + "version": "16.1.4", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.4.tgz", + "integrity": "sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, "node_modules/esbuild": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", @@ -1713,6 +1782,18 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/get-tsconfig": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.0.tgz", + "integrity": "sha512-lgbo68hHTQnFddybKbbs/RDRJnJT5YyGy2kQzVwbq+g67X73i+5MVTval34QxGkOe9X5Ujf1UYpCaphLyltjEg==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2208,9 +2289,10 @@ } }, "node_modules/pocketbase": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.8.1.tgz", - "integrity": "sha512-6ABZJ6gEvkfT46otZbyC+4JQ8kaV/I3tKP2HcqtBU4QIYKaha/THekVPq0dmpj88oGiTIGf+L1Hd5EXLL/T70w==" + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.15.2.tgz", + "integrity": "sha512-dGSwO2j3XqtFMANNNEVh/moRw0ItOraKC1p3J+I0y/tPEfgleCAUptMWhqHrFNS+jM5r21eSS1U48N9/OmHFzA==", + "dev": true }, "node_modules/pocketbase-react": { "version": "0.1.28", @@ -2227,6 +2309,11 @@ "pocketbase": "0.8.1" } }, + "node_modules/pocketbase-react/node_modules/pocketbase": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.8.1.tgz", + "integrity": "sha512-6ABZJ6gEvkfT46otZbyC+4JQ8kaV/I3tKP2HcqtBU4QIYKaha/THekVPq0dmpj88oGiTIGf+L1Hd5EXLL/T70w==" + }, "node_modules/postcss": { "version": "8.4.24", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", @@ -2482,6 +2569,15 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -2599,6 +2695,15 @@ "node": ">=8" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", @@ -2608,6 +2713,16 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -2688,6 +2803,23 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/tsx": { + "version": "3.12.7", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.12.7.tgz", + "integrity": "sha512-C2Ip+jPmqKd1GWVQDvz/Eyc6QJbGfE7NrR3fx5BpEHMZsEHoIxHL1j+lKdGobr8ovEyqeNkPLSKp6SCSOt7gmw==", + "dev": true, + "dependencies": { + "@esbuild-kit/cjs-loader": "^2.4.2", + "@esbuild-kit/core-utils": "^3.0.0", + "@esbuild-kit/esm-loader": "^2.5.5" + }, + "bin": { + "tsx": "dist/cli.js" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -2881,6 +3013,36 @@ "regenerator-runtime": "^0.13.11" } }, + "@esbuild-kit/cjs-loader": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@esbuild-kit/cjs-loader/-/cjs-loader-2.4.2.tgz", + "integrity": "sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==", + "dev": true, + "requires": { + "@esbuild-kit/core-utils": "^3.0.0", + "get-tsconfig": "^4.4.0" + } + }, + "@esbuild-kit/core-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.1.0.tgz", + "integrity": "sha512-Uuk8RpCg/7fdHSceR1M6XbSZFSuMrxcePFuGgyvsBn+u339dk5OeL4jv2EojwTN2st/unJGsVm4qHWjWNmJ/tw==", + "dev": true, + "requires": { + "esbuild": "~0.17.6", + "source-map-support": "^0.5.21" + } + }, + "@esbuild-kit/esm-loader": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.5.5.tgz", + "integrity": "sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw==", + "dev": true, + "requires": { + "@esbuild-kit/core-utils": "^3.0.0", + "get-tsconfig": "^4.4.0" + } + }, "@esbuild/android-arm": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", @@ -3133,6 +3295,16 @@ "integrity": "sha512-OFiYQdv+Yk7AO7IsQu/fAEPijbeTwrrEYvdNoJ3sblBBedD5j5fBTNWrUPNVlwC4XWWnWTCMaRIVsJujsFiWXg==", "requires": { "@swc/helpers": "^0.4.14" + }, + "dependencies": { + "@swc/helpers": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", + "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "requires": { + "tslib": "^2.4.0" + } + } } }, "@reduxjs/toolkit": { @@ -3267,9 +3439,12 @@ "optional": true }, "@swc/helpers": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", - "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz", + "integrity": "sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==", + "dev": true, + "optional": true, + "peer": true, "requires": { "tslib": "^2.4.0" } @@ -3289,6 +3464,12 @@ "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, + "@types/node": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.0.tgz", + "integrity": "sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ==", + "dev": true + }, "@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -3533,6 +3714,12 @@ "fill-range": "^7.0.1" } }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3638,6 +3825,12 @@ "csstype": "^3.0.2" } }, + "dotenv": { + "version": "16.1.4", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.4.tgz", + "integrity": "sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw==", + "dev": true + }, "esbuild": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", @@ -3934,6 +4127,15 @@ "dev": true, "optional": true }, + "get-tsconfig": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.0.tgz", + "integrity": "sha512-lgbo68hHTQnFddybKbbs/RDRJnJT5YyGy2kQzVwbq+g67X73i+5MVTval34QxGkOe9X5Ujf1UYpCaphLyltjEg==", + "dev": true, + "requires": { + "resolve-pkg-maps": "^1.0.0" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -4299,9 +4501,10 @@ "dev": true }, "pocketbase": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.8.1.tgz", - "integrity": "sha512-6ABZJ6gEvkfT46otZbyC+4JQ8kaV/I3tKP2HcqtBU4QIYKaha/THekVPq0dmpj88oGiTIGf+L1Hd5EXLL/T70w==" + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.15.2.tgz", + "integrity": "sha512-dGSwO2j3XqtFMANNNEVh/moRw0ItOraKC1p3J+I0y/tPEfgleCAUptMWhqHrFNS+jM5r21eSS1U48N9/OmHFzA==", + "dev": true }, "pocketbase-react": { "version": "git+ssh://git@github.com/radeksoft/pocketbase-react.git#111f0898a3ddc2ec03d6b33952267b98f5e14a00", @@ -4312,6 +4515,13 @@ "react-redux": "^8.0.4", "redux": "^4.2.0", "redux-persist": "^6.0.0" + }, + "dependencies": { + "pocketbase": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.8.1.tgz", + "integrity": "sha512-6ABZJ6gEvkfT46otZbyC+4JQ8kaV/I3tKP2HcqtBU4QIYKaha/THekVPq0dmpj88oGiTIGf+L1Hd5EXLL/T70w==" + } } }, "postcss": { @@ -4475,6 +4685,12 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, + "resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true + }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -4546,12 +4762,28 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, "source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "dev": true }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -4613,6 +4845,18 @@ } } }, + "tsx": { + "version": "3.12.7", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.12.7.tgz", + "integrity": "sha512-C2Ip+jPmqKd1GWVQDvz/Eyc6QJbGfE7NrR3fx5BpEHMZsEHoIxHL1j+lKdGobr8ovEyqeNkPLSKp6SCSOt7gmw==", + "dev": true, + "requires": { + "@esbuild-kit/cjs-loader": "^2.4.2", + "@esbuild-kit/core-utils": "^3.0.0", + "@esbuild-kit/esm-loader": "^2.5.5", + "fsevents": "~2.3.2" + } + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 3940d61..1096829 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "vite", "build": "tsc && vite build", "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview" + "preview": "vite preview", + "filldb": "tsx --tsconfig ./tsconfig.node.json scripts/filldb.ts" }, "dependencies": { "bootstrap": "^5.2.3", @@ -18,14 +19,18 @@ "wouter": "^2.11.0" }, "devDependencies": { + "@types/node": "^20.3.0", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@typescript-eslint/eslint-plugin": "^5.57.1", "@typescript-eslint/parser": "^5.57.1", "@vitejs/plugin-react-swc": "^3.0.0", + "dotenv": "^16.1.4", "eslint": "^8.38.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.3.4", + "pocketbase": "^0.15.2", + "tsx": "^3.12.7", "typescript": "^5.0.2", "vite": "^4.3.2" } diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/scripts/filldb.ts b/scripts/filldb.ts new file mode 100644 index 0000000..adbdc95 --- /dev/null +++ b/scripts/filldb.ts @@ -0,0 +1,37 @@ +import fs from 'fs'; +import assert from 'assert'; + +import PocketBase from 'pocketbase'; +import 'dotenv/config'; + +import { parseTeamsPlayers } from './filldb/parse'; +import { addMembersToTeams, createEmptyTeams } from './filldb/teams'; +import { createPlayers } from './filldb/players'; +import { createGames, createTeachersGame } from './filldb/games'; + +const { POCKETBASE_ENDPOINT, POCKETBASE_ADMIN_EMAIL, POCKETBASE_ADMIN_PASSWORD } = process.env; +assert(POCKETBASE_ENDPOINT, 'missing env var POCKETBASE_ENDPOINT'); +assert(POCKETBASE_ADMIN_EMAIL, 'missing env var POCKETBASE_ADMIN_EMAIL'); +assert(POCKETBASE_ADMIN_PASSWORD, 'missing env var POCKETBASE_ADMIN_PASSWORD'); + +const csv = fs.readFileSync('./tymy.csv', 'utf8'); +const teams = parseTeamsPlayers(csv); + +const pb = new PocketBase(POCKETBASE_ENDPOINT); +await pb.admins.authWithPassword(POCKETBASE_ADMIN_EMAIL, POCKETBASE_ADMIN_PASSWORD); + +const emptyTeams = await createEmptyTeams(teams, pb); +console.log({emptyTeams}); + +const updatedTeams = await createPlayers(emptyTeams, pb); +console.log({updatedTeams}); + +await addMembersToTeams(updatedTeams, pb); + +const studentTeams = emptyTeams.filter(t => !t.teachers); + +const nextNo = await createGames(studentTeams, pb); + +const teachersTeam = emptyTeams.find(t => t.teachers); +assert(teachersTeam); +await createTeachersGame(teachersTeam, nextNo, pb); diff --git a/scripts/filldb/games.ts b/scripts/filldb/games.ts new file mode 100644 index 0000000..182a1e5 --- /dev/null +++ b/scripts/filldb/games.ts @@ -0,0 +1,35 @@ +import type PocketBase from 'pocketbase'; +import { TeamWithRef } from './teams'; + +/** + * @returns the next games.no to be used for createTeachersGame + */ +export const createGames = async (studentTeams: TeamWithRef[], pb: PocketBase) => { + const gamesColl = pb.collection('games'); + let no = 0; + + for (let i = 0; i < studentTeams.length; i++) { + const team1 = studentTeams[i]; + for (let j = i+1; j < studentTeams.length; j++) { + const team2 = studentTeams[j]; + await gamesColl.create({ + team1: team1.dbRef, + team2: team2.dbRef, + no, + finished: false, + }); + no++; + } + } + + return no; +}; + +export const createTeachersGame = async (teachersTeam: TeamWithRef, no: number, pb: PocketBase) => { + const gamesColl = pb.collection('games'); + await gamesColl.create({ + team1: teachersTeam.dbRef, + no, + finished: false, + }); +}; diff --git a/scripts/filldb/parse.ts b/scripts/filldb/parse.ts new file mode 100644 index 0000000..808b4c7 --- /dev/null +++ b/scripts/filldb/parse.ts @@ -0,0 +1,51 @@ +import assert from 'assert'; +import { parse } from 'path'; + +export type ParsedTeam = { + name: string, + members: string[], + teachers: boolean, +}; + +export const parseTeamsPlayers = (csv: string): ParsedTeam[] => { + const parsedTeams: ParsedTeam[] = []; + const lines = csv.split('\r\n'); + console.log({lines}); + + const teamsTokens = lines[0].split(',').map(t => t.trim()); + assert(teamsTokens[0] === 'Týmy', 'table must start with Týmy on A1'); + + for (let i = 1; i < teamsTokens.length; i++) { + const name = teamsTokens[i]; + if (!name) + continue; + const isTeachers = name === 'Učitelé'; + parsedTeams.push({ + name, + members: [], + teachers: isTeachers, + }); + } + + console.log({parsedTeams}); + + for (let i = 1; true; i++) { + const lineTokens = lines[i]?.split(',').map(t => t.trim()); + if (!lineTokens) + break; + console.log({lineTokens}); + + for (let playerIndex = 1, teamIndex = 0; playerIndex < lineTokens.length; playerIndex++, teamIndex++) { + const team = parsedTeams[teamIndex]; + if (team.teachers) + playerIndex++; + const player = lineTokens[playerIndex]; + console.log({player, playerIndex, teamIndex}); + if (!player) + continue; + team.members.push(player); + } + } + + return parsedTeams; +}; diff --git a/scripts/filldb/players.ts b/scripts/filldb/players.ts new file mode 100644 index 0000000..b6e3feb --- /dev/null +++ b/scripts/filldb/players.ts @@ -0,0 +1,32 @@ +import type PocketBase from 'pocketbase'; +import { ParsedTeam } from "./parse"; +import { Player, ReferenceTo } from '../../src/types'; +import { TeamWithRef } from './teams'; + +export type TeamWithRefWithMemberRefs = TeamWithRef & { + memberRefs: ReferenceTo[], +}; + +export const createPlayers = async (parsedTeams: TeamWithRef[], pb: PocketBase) => { + const updatedTeams: TeamWithRefWithMemberRefs[] = []; + const playersColl = pb.collection('players'); + + for (let i = 0; i < parsedTeams.length; i++) { + const team = parsedTeams[i]; + const insertedRefs: ReferenceTo[] = []; + for (const memberName of team.members) { + const inserted = await playersColl.create({ + name: memberName, + goals: [], + team: team.dbRef, + }); + insertedRefs.push(inserted.id); + } + updatedTeams.push({ + ...team, + memberRefs: insertedRefs, + }); + } + + return updatedTeams; +}; diff --git a/scripts/filldb/teams.ts b/scripts/filldb/teams.ts new file mode 100644 index 0000000..0248f2c --- /dev/null +++ b/scripts/filldb/teams.ts @@ -0,0 +1,43 @@ +import type PocketBase from 'pocketbase'; +import { ReferenceTo, Team } from "../../src/types"; +import { ParsedTeam } from "./parse"; +import { TeamWithRefWithMemberRefs } from './players'; + +export type TeamWithRef = ParsedTeam & { + dbRef: ReferenceTo, +}; + +export const createEmptyTeams = async (parsedTeams: ParsedTeam[], pb: PocketBase) => { + const insertedTeams: TeamWithRef[] = []; + const teamsColl = pb.collection('teams'); + + for (let i = 0; i < parsedTeams.length; i++) { + const team = parsedTeams[i]; + + const inserted = await teamsColl.create({ + name: team.name, + no: i+1, + points: 0, + members: [], + teachers: team.teachers, + }); + + insertedTeams.push({ + ...team, + dbRef: inserted.id, + }); + } + + return insertedTeams; +}; + +export const addMembersToTeams = async (teamsWithMemberRefs: TeamWithRefWithMemberRefs[], pb: PocketBase) => { + const teamsColl = pb.collection('teams'); + + for (let i = 0; i < teamsWithMemberRefs.length; i++) { + const team = teamsWithMemberRefs[i]; + await teamsColl.update(team.dbRef, { + members: team.memberRefs, + }); + } +} diff --git a/src/App.tsx b/src/App.tsx index 8ba825f..1cf2b6a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,7 +7,7 @@ import './style.css'; import { useAppContent } from 'pocketbase-react/src'; import { Game, GameState, Goal, Player, Team } from './types'; -const serverURL = "http://127.0.0.1:8090"; +const serverURL = import.meta.env.VITE_POCKETBASE_ENDPOINT; // these get automatically prefetched & subscribed to updates const collections = ['games', 'goals', 'players', 'teams', 'misc']; const webRedirectURL = "mariansam.eu/webred"; diff --git a/src/components/app-header.tsx b/src/components/app-header.tsx index f80126b..0fdf902 100644 --- a/src/components/app-header.tsx +++ b/src/components/app-header.tsx @@ -16,9 +16,9 @@ export const AppHeader: React.FC = () => { Hlavní stránka Týmy a hráči Všechny zápasy - Velká tabulka + Velká tabulka Bufet - Hlasování + Hlasování Admin diff --git a/src/components/current-match.tsx b/src/components/current-match.tsx index 58857d0..dcf3a66 100644 --- a/src/components/current-match.tsx +++ b/src/components/current-match.tsx @@ -9,7 +9,14 @@ const vsInlineStyle: React.CSSProperties = { }; export const CurrentMatch: React.FC = () => { - const { currentGame: { game, team1, team2 } } = useGameLogic(); + const { currentGame: { game, team1, team2 }, gameState: { matchStarted } } = useGameLogic(); + + if (!matchStarted) { + return ( +

za chvíli začínáme! TODO: show just following games

+ ) + } + if (!game || !team1 || !team2) { return ( diff --git a/src/components/team-view.tsx b/src/components/team-view.tsx index a2cebbf..727e103 100644 --- a/src/components/team-view.tsx +++ b/src/components/team-view.tsx @@ -23,7 +23,7 @@ export const TeamView: React.FC = props => { }, [players, team.id]); const totalGoals = useMemo(() => { - return members.map(m => m.goals.length).reduce((prev, curr) => prev + curr); + return members.map(m => m.goals.length).reduce((prev, curr) => prev + curr, 0); }, [members]); return ( diff --git a/src/logic.ts b/src/logic.ts index 5422199..e88b092 100644 --- a/src/logic.ts +++ b/src/logic.ts @@ -29,6 +29,13 @@ export const useGameLogic = () => { }; }, [games.records, gameState.currentGameNo, teams.records]); + const getGameByTeams = (team1: Team, team2: Team) => { + return games.records.find(game => ( + (game.team1 == team1.id || game.team1 == team2.id) && + (game.team2 == team1.id || game.team2 == team2.id) + )); + }; + return { gameState, currentGame, @@ -43,5 +50,7 @@ export const useGameLogic = () => { teamsActions: teams.actions, goalsActions: goals.actions, gameMiscActions: gameMisc.actions, + + getGameByTeams, }; }; diff --git a/src/pages/_router.tsx b/src/pages/_router.tsx index 13f8bff..3731b74 100644 --- a/src/pages/_router.tsx +++ b/src/pages/_router.tsx @@ -6,6 +6,8 @@ import { TeamsPage } from './teams'; import { AdminHomePage } from './admin/home'; import { GamesPage } from './games'; import { AdminRouter } from './admin/_router'; +import { VotingPage } from './voting'; +import { TablePage } from './table'; export const Router: React.FC = () => { return ( @@ -14,6 +16,8 @@ export const Router: React.FC = () => { + + diff --git a/src/pages/table.tsx b/src/pages/table.tsx new file mode 100644 index 0000000..f1383ce --- /dev/null +++ b/src/pages/table.tsx @@ -0,0 +1,97 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import { Table } from "react-bootstrap"; +import {Team} from '../types'; +import { useGameLogic } from '../logic'; + +export const TablePage: React.FC = () => { + const { + games, + teams, + } = useGameLogic(); + + useEffect(() => { + console.log({games, teams}); + }, [games, teams]); + + return ( + + + + + {teams.map(team => ( + + ))} + + + + {teams.map(teamLeft => ( + + + {teams.map(teamTop => ( + + ))} + + ))} + +
levý : horní{team.name}
{teamLeft.name}
+ ); +}; + +type GameCellProps = { + teamLeft: Team, + teamTop: Team, +}; + +const GameCell: React.FC = props => { + const { + teamLeft, + teamTop, + } = props; + + const { + games, + gameState, + getGameByTeams, + } = useGameLogic(); + + const game = useMemo(() => { + // console.log({teamLeft, teamTop}); + // console.log('useMemo game'); + const theGame = getGameByTeams(teamLeft, teamTop); + // console.log({theGame}); + return theGame; + }, [teamLeft, teamTop, games]); + + const pointsLeft = useMemo(() => { + if (!game) + return 0; + + if (game.team1 == teamLeft.id) + return game.goals1; + else + return game.goals2; + }, [teamLeft, game]); + + const pointsTop = useMemo(() => { + if (!game) + return 0; + + if (game.team1 == teamTop.id) + return game.goals1; + else + return game.goals2; + }, [teamLeft, game]); + + if (!game || !gameState) + return (-); + + if (gameState.currentGameNo == game.no) { + return ( + {pointsLeft} : {pointsTop} + ); + } + + return ( + {pointsLeft} : {pointsTop} + ); +}; diff --git a/src/pages/voting.tsx b/src/pages/voting.tsx new file mode 100644 index 0000000..3ccd9f4 --- /dev/null +++ b/src/pages/voting.tsx @@ -0,0 +1,9 @@ +import React from 'react'; + +export const VotingPage: React.FC = () => { + return ( +
+ minule nebylo, co? :) +
+ ); +}; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 11f02fe..b98d12f 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1 +1,10 @@ /// + +interface ImportMetaEnv { + readonly VITE_POCKETBASE_ENDPOINT: string + // more env variables... +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/tsconfig.json b/tsconfig.json index c81ef9f..8f399b9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,6 +19,6 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src"], + "include": ["src", "scripts"], "references": [{ "path": "./tsconfig.node.json" }] }