diff --git a/package-lock.json b/package-lock.json index 594e0863..22938843 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "eslint-plugin-react": "7.37.5", "eslint-plugin-react-hooks": "6.1.1", "npm-check-updates": "19.0.0", + "npm-run-all": "4.1.5", "plop": "4.0.4", "prettier": "3.6.2", "typescript": "5.9.3", @@ -2904,6 +2905,16 @@ "dev": true, "license": "MIT" }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-abstract": { "version": "1.24.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", @@ -4065,6 +4076,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -4201,6 +4219,13 @@ "node": ">=0.10.0" } }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true, + "license": "ISC" + }, "node_modules/iconv-lite": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", @@ -4369,6 +4394,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, "node_modules/is-async-function": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", @@ -4925,6 +4957,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -5030,6 +5069,22 @@ "node": ">=10.13.0" } }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -5113,6 +5168,15 @@ "node": ">= 0.4" } }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -5305,6 +5369,13 @@ } } }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true, + "license": "MIT" + }, "node_modules/node-plop": { "version": "0.32.3", "resolved": "https://registry.npmjs.org/node-plop/-/node-plop-0.32.3.tgz", @@ -5334,6 +5405,29 @@ "dev": true, "license": "MIT" }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/npm-check-updates": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-19.0.0.tgz", @@ -5349,6 +5443,183 @@ "npm": ">=8.12.1" } }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-run-all/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/npm-run-all/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-run-all/node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/npm-run-all/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/npm-run-all/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-run-all/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5636,6 +5907,20 @@ "node": ">=0.8" } }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/parse-passwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", @@ -5696,6 +5981,19 @@ "node": ">=0.10.0" } }, + "node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5715,6 +6013,29 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/plop": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/plop/-/plop-4.0.4.tgz", @@ -5901,6 +6222,21 @@ "dev": true, "license": "MIT" }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -6311,6 +6647,19 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -6413,6 +6762,42 @@ "node": ">=0.10.0" } }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -6509,6 +6894,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/string.prototype.padend": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", + "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/string.prototype.repeat": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", @@ -7063,6 +7467,17 @@ "node": ">= 10.13.0" } }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", diff --git a/package.json b/package.json index 69872d37..480b9f76 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,10 @@ "build": "next build", "start": "next start", "lint": "next lint", + "lint-fix": "npm-run-all --continue-on-error lint-fix:** prettier", + "lint-fix:js": "eslint . --fix", "link": "npm link @amsterdam/design-system-assets @amsterdam/design-system-css @amsterdam/design-system-react @amsterdam/design-system-react-icons @amsterdam/design-system-tokens", + "prettier": "prettier --write .", "unlink": "npm unlink @amsterdam/design-system-assets @amsterdam/design-system-css @amsterdam/design-system-react @amsterdam/design-system-react-icons @amsterdam/design-system-tokens", "update": "npm-check-updates --upgrade && npm install", "update:minor": "npm-check-updates --target minor --upgrade && npm install", @@ -49,6 +52,7 @@ "eslint-plugin-react": "7.37.5", "eslint-plugin-react-hooks": "6.1.1", "npm-check-updates": "19.0.0", + "npm-run-all": "4.1.5", "plop": "4.0.4", "prettier": "3.6.2", "typescript": "5.9.3", diff --git a/src/app/amsterdam/amsterdam.css b/src/app/amsterdam/amsterdam.css index 23d2028e..78b2d8ab 100644 --- a/src/app/amsterdam/amsterdam.css +++ b/src/app/amsterdam/amsterdam.css @@ -6,4 +6,3 @@ break-inside: avoid; } } - diff --git a/src/app/page.tsx b/src/app/page.tsx index 7a360986..f7fb31da 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -24,6 +24,9 @@ export default function Signalen() { Amsterdam + + Rijke tabellen + Signalen diff --git a/src/app/rijke-tabellen/common/RankingTableBody.tsx b/src/app/rijke-tabellen/common/RankingTableBody.tsx new file mode 100644 index 00000000..c4452d98 --- /dev/null +++ b/src/app/rijke-tabellen/common/RankingTableBody.tsx @@ -0,0 +1,32 @@ +import { Table } from '@amsterdam/design-system-react' + +import type { Ranking } from './ranking' + +type RankingTableBodyProps = { + ranking: Ranking +} + +export const RankingTableBody = ({ ranking }: RankingTableBodyProps) => ( + + {ranking.length ? ( + ranking.map(({ drawn, goal_difference, goals_against, goals_for, lost, name, played, points, position, won }) => ( + + {position} + {name} + {played} + {won} + {drawn} + {lost} + {points} + {goals_for} + {goals_against} + {goal_difference} + + )) + ) : ( + + Geen gegevens + + )} + +) diff --git a/src/app/rijke-tabellen/common/RankingTableHeaderRow.tsx b/src/app/rijke-tabellen/common/RankingTableHeaderRow.tsx new file mode 100644 index 00000000..cf1533ed --- /dev/null +++ b/src/app/rijke-tabellen/common/RankingTableHeaderRow.tsx @@ -0,0 +1,18 @@ +import { Table } from '@amsterdam/design-system-react' + +export const RankingTableHeaderRow = () => ( + + Positie + + Team + + Gespeeld + Gewonnen + Gelijk + Verloren + Punten + Doelpunten voor + Doelpunten tegen + Doelsaldo + +) diff --git a/src/app/rijke-tabellen/common/getSortedRanking.ts b/src/app/rijke-tabellen/common/getSortedRanking.ts new file mode 100644 index 00000000..cda005e7 --- /dev/null +++ b/src/app/rijke-tabellen/common/getSortedRanking.ts @@ -0,0 +1,23 @@ +import type { Ranking, Team } from './ranking' + +type SortDirection = 'asc' | 'desc' +export type SortOrder = `${keyof Team}-${SortDirection}` + +export function getSortedRanking(ranking: Ranking, sortOrder: SortOrder) { + const [field, direction] = sortOrder.split('-') as [keyof Team, SortDirection] + + return [...ranking].sort((a, b) => { + const aValue = typeof a[field] === 'string' ? a[field].toLowerCase() : a[field] + const bValue = typeof b[field] === 'string' ? b[field].toLowerCase() : b[field] + + if (typeof aValue === 'string' && typeof bValue === 'string') { + return direction === 'desc' ? bValue.localeCompare(aValue) : aValue.localeCompare(bValue) + } + + if (typeof aValue === 'number' && typeof bValue === 'number') { + return direction === 'desc' ? bValue - aValue : aValue - bValue + } + + return 0 + }) +} diff --git a/src/app/rijke-tabellen/common/index.ts b/src/app/rijke-tabellen/common/index.ts new file mode 100644 index 00000000..d8668ec0 --- /dev/null +++ b/src/app/rijke-tabellen/common/index.ts @@ -0,0 +1,3 @@ +export * from './getSortedRanking' +export * from './ranking' +export * from './RankingTableBody' diff --git a/src/app/rijke-tabellen/common/ranking.ts b/src/app/rijke-tabellen/common/ranking.ts new file mode 100644 index 00000000..e321cff8 --- /dev/null +++ b/src/app/rijke-tabellen/common/ranking.ts @@ -0,0 +1,233 @@ +export type Team = { + drawn: number + goal_difference: number + goals_against: number + goals_for: number + lost: number + name: string + played: number + points: number + position: number + won: number +} + +export type Ranking = Team[] + +export const ranking: Ranking = [ + { + drawn: 4, + goal_difference: 64, + goals_against: 39, + goals_for: 103, + lost: 5, + name: 'PSV', + played: 34, + points: 79, + position: 1, + won: 25, + }, + { + drawn: 6, + goal_difference: 35, + goals_against: 32, + goals_for: 67, + lost: 4, + name: 'Ajax', + played: 34, + points: 78, + position: 2, + won: 24, + }, + { + drawn: 8, + goal_difference: 38, + goals_against: 38, + goals_for: 76, + lost: 6, + name: 'Feyenoord', + played: 34, + points: 68, + position: 3, + won: 20, + }, + { + drawn: 10, + goal_difference: 17, + goals_against: 45, + goals_for: 62, + lost: 6, + name: 'FC Utrecht', + played: 34, + points: 64, + position: 4, + won: 18, + }, + { + drawn: 9, + goal_difference: 21, + goals_against: 37, + goals_for: 58, + lost: 9, + name: 'AZ', + played: 34, + points: 57, + position: 5, + won: 16, + }, + { + drawn: 9, + goal_difference: 13, + goals_against: 49, + goals_for: 62, + lost: 10, + name: 'FC Twente', + played: 34, + points: 54, + position: 6, + won: 15, + }, + { + drawn: 9, + goal_difference: 2, + goals_against: 55, + goals_for: 57, + lost: 11, + name: 'Go Ahead Eagles', + played: 34, + points: 51, + position: 7, + won: 14, + }, + { + drawn: 7, + goal_difference: 5, + goals_against: 46, + goals_for: 51, + lost: 15, + name: 'N.E.C.', + played: 34, + points: 43, + position: 8, + won: 12, + }, + { + drawn: 7, + goal_difference: -15, + goals_against: 57, + goals_for: 42, + lost: 15, + name: 'sc Heerenveen', + played: 34, + points: 43, + position: 9, + won: 12, + }, + { + drawn: 11, + goal_difference: -8, + goals_against: 51, + goals_for: 43, + lost: 13, + name: 'PEC Zwolle', + played: 34, + points: 41, + position: 10, + won: 10, + }, + { + drawn: 8, + goal_difference: -17, + goals_against: 54, + goals_for: 37, + lost: 15, + name: 'Fortuna Sittard', + played: 34, + points: 41, + position: 11, + won: 11, + }, + { + drawn: 12, + goal_difference: -4, + goals_against: 43, + goals_for: 39, + lost: 13, + name: 'Sparta Rotterdam', + played: 34, + points: 39, + position: 12, + won: 9, + }, + { + drawn: 9, + goal_difference: -13, + goals_against: 53, + goals_for: 40, + lost: 15, + name: 'FC Groningen', + played: 34, + points: 39, + position: 13, + won: 10, + }, + { + drawn: 11, + goal_difference: -21, + goals_against: 63, + goals_for: 42, + lost: 14, + name: 'Heracles Almelo', + played: 34, + points: 38, + position: 14, + won: 9, + }, + { + drawn: 9, + goal_difference: -24, + goals_against: 58, + goals_for: 34, + lost: 17, + name: 'NAC Breda', + played: 34, + points: 33, + position: 15, + won: 8, + }, + { + drawn: 8, + goal_difference: -22, + goals_against: 56, + goals_for: 34, + lost: 20, + name: 'Willem II', + played: 34, + points: 26, + position: 16, + won: 6, + }, + { + drawn: 7, + goal_difference: -30, + goals_against: 74, + goals_for: 44, + lost: 21, + name: 'RKC Waalwijk', + played: 34, + points: 25, + position: 17, + won: 6, + }, + { + drawn: 10, + goal_difference: -41, + goals_against: 64, + goals_for: 23, + lost: 20, + name: 'Almere City FC', + played: 34, + points: 22, + position: 18, + won: 4, + }, +] diff --git a/src/app/rijke-tabellen/filteren-op-tekst/page.tsx b/src/app/rijke-tabellen/filteren-op-tekst/page.tsx new file mode 100644 index 00000000..06eccead --- /dev/null +++ b/src/app/rijke-tabellen/filteren-op-tekst/page.tsx @@ -0,0 +1,59 @@ +'use client' + +import { Breadcrumb, Grid, Heading, Label, Paragraph, Table, TextInput } from '@amsterdam/design-system-react' +import NextLink from 'next/link' +import { useState } from 'react' + +import { ranking, RankingTableBody } from '../common' +import { RankingTableHeaderRow } from '../common/RankingTableHeaderRow' + +export default function FilterenOpTekst() { + const [filter, setFilter] = useState('') + const filteredRanking = ranking.filter(({ name }) => name.toLowerCase().includes(filter.toLowerCase())) + + return ( + + + + + Rijke tabellen + + + + Eredivisie 2024/2025 + + + + + Eindstand + + + + + + + + + setFilter(e.target.value)} + style={{ minInlineSize: '6rem' }} + value={filter} + /> + + + + + +
+ {filter.length ? ( + + {filteredRanking.length} {filteredRanking.length === 1 ? 'team' : 'teams'} met ‘{filter}’ + + ) : undefined} +
+
+ ) +} diff --git a/src/app/rijke-tabellen/knop-in-cel/page.tsx b/src/app/rijke-tabellen/knop-in-cel/page.tsx new file mode 100644 index 00000000..b84c3c30 --- /dev/null +++ b/src/app/rijke-tabellen/knop-in-cel/page.tsx @@ -0,0 +1,86 @@ +'use client' + +import { Breadcrumb, Grid, Heading, IconButton, Row, Table } from '@amsterdam/design-system-react' +import { PencilIcon, TrashBinIcon } from '@amsterdam/design-system-react-icons' +import NextLink from 'next/link' +import { useState } from 'react' + +import { ranking } from '../common' + +export default function KnopInCel() { + const [deleted, setDeleted] = useState([]) + const filteredRanking = ranking.filter((team) => !deleted.includes(team.position)) + + return ( + + + + + Rijke tabellen + + + + Eredivisie 2024/2025 + + + + + Eindstand + + + + + Positie + + Team + + + Gespeeld + Gewonnen + Gelijk + Verloren + Punten + Doelpunten voor + Doelpunten tegen + Doelsaldo + + + + {filteredRanking.length ? ( + filteredRanking.map( + ({ drawn, goal_difference, goals_against, goals_for, lost, name, played, points, position, won }) => ( + + {position} + {name} + + + void 0} svg={PencilIcon} /> + setDeleted([position, ...deleted])} + svg={TrashBinIcon} + /> + + + {played} + {won} + {drawn} + {lost} + {points} + {goals_for} + {goals_against} + {goal_difference} + + ), + ) + ) : ( + + Geen gegevens + + )} + +
+
+
+ ) +} diff --git a/src/app/rijke-tabellen/layout.tsx b/src/app/rijke-tabellen/layout.tsx new file mode 100644 index 00000000..cae42de7 --- /dev/null +++ b/src/app/rijke-tabellen/layout.tsx @@ -0,0 +1,52 @@ +'use client' + +import { + Grid, + Heading, + Logo, + Page, + PageFooter, + PageHeader, + Paragraph, + Row, + SkipLink, + Switch, +} from '@amsterdam/design-system-react' +import NextLink from 'next/link' +import { PropsWithChildren, useState } from 'react' + +export default function RijkeTabellen({ children }: PropsWithChildren) { + const [compactMode, setCompactMode] = useState(false) + + return ( + + + + Direct naar inhoud + + + } + logoLinkTitle="Naar de homepage van Rijke tabellen" + /> +
{children}
+ + + Over deze website + + + + Prototypes + +
  • + + Compact + setCompactMode(!compactMode)} /> + +
  • + {/* Append footer link here */} +
    +
    +
    + ) +} diff --git a/src/app/rijke-tabellen/link-in-cel/page.tsx b/src/app/rijke-tabellen/link-in-cel/page.tsx new file mode 100644 index 00000000..c443cd5e --- /dev/null +++ b/src/app/rijke-tabellen/link-in-cel/page.tsx @@ -0,0 +1,75 @@ +'use client' + +import { Breadcrumb, Grid, Heading, Link, Table } from '@amsterdam/design-system-react' +import NextLink from 'next/link' +import { useSearchParams } from 'next/navigation' +import { Suspense } from 'react' + +import { ranking } from '../common' +import { RankingTableHeaderRow } from '../common/RankingTableHeaderRow' + +function LinkInCelContent() { + const searchParams = useSearchParams() + const selection = Number(searchParams.get('selectie')) + + return ( + + + + + Rijke tabellen + + + + Eredivisie 2024/2025 + + + + + Eindstand + + + + + + + {ranking.length ? ( + ranking.map( + ({ drawn, goal_difference, goals_against, goals_for, lost, name, played, points, position, won }) => ( + + {position} + + + {name} + + + {played} + {won} + {drawn} + {lost} + {points} + {goals_for} + {goals_against} + {goal_difference} + + ), + ) + ) : ( + + Geen gegevens + + )} + +
    +
    +
    + ) +} + +export default function LinkInCel() { + return ( + + + + ) +} diff --git a/src/app/rijke-tabellen/overrides.css b/src/app/rijke-tabellen/overrides.css new file mode 100644 index 00000000..dd2ca320 --- /dev/null +++ b/src/app/rijke-tabellen/overrides.css @@ -0,0 +1,58 @@ +.ams-theme { + --ams-link-text-decoration-thickness: 0.125rem; + --ams-link-text-underline-offset: 0.15625rem; + --ams-link-hover-text-decoration-thickness: 0.1875rem; + --ams-link-hover-text-underline-offset: 0.09375rem; +} + +.ams-theme--compact { + --ams-switch-inline-size: 2.4rem; + --ams-switch-thumb-block-size: 1.2rem; + --ams-switch-thumb-inline-size: 1.2rem; + --ams-select-border-width: var(--ams-border-width-s); + --ams-text-input-border-width: var(--ams-border-width-s); +} + +.ams-select { + inline-size: initial; + max-inline-size: 100%; +} + +.ams-switch { + display: flex; +} + +.ams-table td, +.ams-table th { + text-align: center; +} + +.ams-table__row { + position: relative; +} + +.ams-table__row:has(a:hover) { + background-color: #e4effa; +} + +.ams-table__row a::after { + content: ""; + display: block; + inset-block: 0; + inset-inline: 0; + position: absolute; +} + +.ams-table__row--selected td, +.ams-table__row--selected th { + background-color: #ffe600; + border-block-end-color: #c6b200; +} + +.ams-table .ams-table__cell--align-start { + text-align: start; +} + +.ams-table__cell--nowrap { + white-space: nowrap; +} diff --git a/src/app/rijke-tabellen/page.tsx b/src/app/rijke-tabellen/page.tsx new file mode 100644 index 00000000..185fe26f --- /dev/null +++ b/src/app/rijke-tabellen/page.tsx @@ -0,0 +1,39 @@ +'use client' + +import { Grid, Heading, LinkList } from '@amsterdam/design-system-react' +import '@amsterdam/design-system-tokens/dist/compact.theme.css' + +import './overrides.css' +import NextLink from 'next/link' + +export default function HomePage() { + return ( + + + + Rijke tabellen + + + + Sorteren via Select + + + Sorteren via Buttons + + + Filteren op tekst + + + Pagineren + + + Link in cel + + + Knop in cel + + + + + ) +} diff --git a/src/app/rijke-tabellen/pagineren/page.tsx b/src/app/rijke-tabellen/pagineren/page.tsx new file mode 100644 index 00000000..0b2185b8 --- /dev/null +++ b/src/app/rijke-tabellen/pagineren/page.tsx @@ -0,0 +1,73 @@ +'use client' + +import { Breadcrumb, Grid, Heading, Label, Pagination, Row, Select, Table } from '@amsterdam/design-system-react' +import NextLink from 'next/link' +import { useSearchParams } from 'next/navigation' +import { Suspense, useState } from 'react' + +import { ranking, RankingTableBody } from '../common' +import { RankingTableHeaderRow } from '../common/RankingTableHeaderRow' + +function PaginerenContent() { + const [pageSize, setPageSize] = useState(2) + const totalPages = Math.ceil(ranking.length / pageSize) + + const searchParams = useSearchParams() + const page = Number(searchParams.get('pagina')) || 1 + const range = `${(page - 1) * pageSize + 1}–${Math.min(page * pageSize, ranking.length)}` + + const paginatedRanking = ranking.slice((page - 1) * pageSize, page * pageSize) + const LinkComponent = (props: any) => + + return ( + + + + + Rijke tabellen + + + + Eredivisie 2024/2025 + + + + + Eindstand {range} + + + + + + +
    + + `/rijke-tabellen/pagineren?pagina=${page}`} + page={page} + totalPages={totalPages} + /> +
    + + +
    +
    +
    +
    + ) +} + +export default function Pagineren() { + return ( + + + + ) +} diff --git a/src/app/rijke-tabellen/sorteren-via-buttons/page.tsx b/src/app/rijke-tabellen/sorteren-via-buttons/page.tsx new file mode 100644 index 00000000..9d977b49 --- /dev/null +++ b/src/app/rijke-tabellen/sorteren-via-buttons/page.tsx @@ -0,0 +1,84 @@ +'use client' + +import { Breadcrumb, Grid, Heading, IconButton, Row, Table } from '@amsterdam/design-system-react' +import { ArrowDownIcon, ArrowUpIcon } from '@amsterdam/design-system-react-icons' +import NextLink from 'next/link' +import { useState } from 'react' + +import type { SortOrder, Team } from '../common' + +import { getSortedRanking, ranking, RankingTableBody } from '../common' + +export default function SorterenViaButtons() { + const [sortOrder, setSortOrder] = useState('position-asc') + const sortedRanking = getSortedRanking(ranking, sortOrder) + + const SortableHeader = ({ field, header }: { field: keyof Team; header: string }) => ( + + {header} + setSortOrder(sortOrder === `${field}-asc` ? `${field}-desc` : `${field}-asc`)} + size="small" + svg={sortOrder === `${field}-asc` ? ArrowDownIcon : ArrowUpIcon} + /> + + ) + + return ( + + + + + Rijke tabellen + + + + Eredivisie 2024/2025 + + + + + Eindstand + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + ) +} diff --git a/src/app/rijke-tabellen/sorteren-via-select/page.tsx b/src/app/rijke-tabellen/sorteren-via-select/page.tsx new file mode 100644 index 00000000..adcce423 --- /dev/null +++ b/src/app/rijke-tabellen/sorteren-via-select/page.tsx @@ -0,0 +1,60 @@ +'use client' + +import { Breadcrumb, Grid, Heading, Label, Row, Select, Table } from '@amsterdam/design-system-react' +import NextLink from 'next/link' +import { useState } from 'react' + +import type { SortOrder } from '../common' + +import { getSortedRanking, ranking, RankingTableBody } from '../common' +import { RankingTableHeaderRow } from '../common/RankingTableHeaderRow' + +const sortOptions: Array<{ label: string; value: SortOrder }> = [ + { label: 'Positie', value: 'position-asc' }, + { label: 'Team oplopend', value: 'name-asc' }, + { label: 'Team aflopend', value: 'name-desc' }, + { label: 'Gewonnen', value: 'won-desc' }, + { label: 'Gelijk', value: 'drawn-desc' }, + { label: 'Verloren', value: 'lost-desc' }, +] + +export default function SorterenViaSelect() { + const [sortOrder, setSortOrder] = useState('position-asc') + const sortedRanking = getSortedRanking(ranking, sortOrder) + + return ( + + + + + Rijke tabellen + + + + Eredivisie 2024/2025 + + + + + + + + + Eindstand + + + + + + +
    +
    +
    + ) +}