Compare commits
22 Commits
cdf11fc2ba
...
7ab818dd48
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ab818dd48 | |||
| 1d7dff1b53 | |||
| 18efab45d3 | |||
| f75c880c25 | |||
| 20dfc45f89 | |||
| 3b710bd846 | |||
| 5485425213 | |||
| a9252e92f7 | |||
| baaa5f1c61 | |||
| 6239cdef5d | |||
| a05c0e6882 | |||
| 756004476d | |||
| 0e0203c4f7 | |||
| 7e24a43312 | |||
| 4bfa15f4aa | |||
| f3eb710403 | |||
| bc158b6060 | |||
| 2fa2d7006b | |||
| 36b4aea1c2 | |||
| 4a497aefb4 | |||
| 22a6da55fa | |||
| b0859ff147 |
294
package-lock.json
generated
294
package-lock.json
generated
@ -21,6 +21,7 @@
|
|||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
"@types/node": "^22.13.10",
|
"@types/node": "^22.13.10",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
|
"cheerio": "^1.0.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"pocketbase": "^0.25.2",
|
"pocketbase": "^0.25.2",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
@ -1839,6 +1840,13 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/boolbase": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||||
@ -1926,6 +1934,50 @@
|
|||||||
],
|
],
|
||||||
"license": "CC-BY-4.0"
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/cheerio": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cheerio-select": "^2.1.0",
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"domutils": "^3.1.0",
|
||||||
|
"encoding-sniffer": "^0.2.0",
|
||||||
|
"htmlparser2": "^9.1.0",
|
||||||
|
"parse5": "^7.1.2",
|
||||||
|
"parse5-htmlparser2-tree-adapter": "^7.0.0",
|
||||||
|
"parse5-parser-stream": "^7.1.2",
|
||||||
|
"undici": "^6.19.5",
|
||||||
|
"whatwg-mimetype": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.17"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cheerio-select": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0",
|
||||||
|
"css-select": "^5.1.0",
|
||||||
|
"css-what": "^6.1.0",
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"domutils": "^3.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||||
@ -2045,6 +2097,36 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-select": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0",
|
||||||
|
"css-what": "^6.1.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"domutils": "^3.0.1",
|
||||||
|
"nth-check": "^2.0.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-what": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cssesc": {
|
"node_modules/cssesc": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
@ -2127,6 +2209,65 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/dom-serializer": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.2",
|
||||||
|
"entities": "^4.2.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domelementtype": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/domhandler": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/domutils": {
|
||||||
|
"version": "3.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
||||||
|
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eastasianwidth": {
|
"node_modules/eastasianwidth": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
@ -2148,6 +2289,33 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/encoding-sniffer": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"iconv-lite": "^0.6.3",
|
||||||
|
"whatwg-encoding": "^3.1.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/entities": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
|
||||||
@ -2405,6 +2573,39 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/htmlparser2": {
|
||||||
|
"version": "9.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
|
||||||
|
"integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/fb55/htmlparser2?sponsor=1",
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domelementtype": "^2.3.0",
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"domutils": "^3.1.0",
|
||||||
|
"entities": "^4.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/iconv-lite": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/import-meta-resolve": {
|
"node_modules/import-meta-resolve": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
|
||||||
@ -2756,6 +2957,19 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nth-check": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object-assign": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
@ -2783,6 +2997,46 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BlueOak-1.0.0"
|
"license": "BlueOak-1.0.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/parse5": {
|
||||||
|
"version": "7.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
|
||||||
|
"integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"entities": "^4.5.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/parse5-htmlparser2-tree-adapter": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"parse5": "^7.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/parse5-parser-stream": {
|
||||||
|
"version": "7.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
|
||||||
|
"integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"parse5": "^7.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-key": {
|
"node_modules/path-key": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
@ -3280,6 +3534,13 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/semver": {
|
"node_modules/semver": {
|
||||||
"version": "7.7.1",
|
"version": "7.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||||
@ -3834,6 +4095,16 @@
|
|||||||
"node": ">=14.17"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/undici": {
|
||||||
|
"version": "6.21.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz",
|
||||||
|
"integrity": "sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.20.0",
|
"version": "6.20.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||||
@ -3984,6 +4255,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/whatwg-encoding": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"iconv-lite": "0.6.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-mimetype": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
"@types/node": "^22.13.10",
|
"@types/node": "^22.13.10",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
|
"cheerio": "^1.0.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"pocketbase": "^0.25.2",
|
"pocketbase": "^0.25.2",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
|
|||||||
@ -8,17 +8,20 @@
|
|||||||
/** The columns the table should have. */
|
/** The columns the table should have. */
|
||||||
columns: TableColumn[];
|
columns: TableColumn[];
|
||||||
|
|
||||||
|
/** Optional height classes */
|
||||||
|
height?: string;
|
||||||
|
|
||||||
/** An optional function handling clicking on a table row */
|
/** An optional function handling clicking on a table row */
|
||||||
handler?: (event: Event, id: string) => Promise<void>;
|
handler?: (event: Event, id: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { data, columns, handler = undefined }: TableProps = $props();
|
let { data, columns, height = "", handler = undefined }: TableProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if data.length > 0}
|
{#if data.length > 0}
|
||||||
<div class="table-container bg-white shadow">
|
<div class="table-container bg-white shadow {height}">
|
||||||
<table class="table table-compact bg-white">
|
<table class="table table-compact !overflow-scroll bg-white">
|
||||||
<thead>
|
<thead class="sticky top-0">
|
||||||
<tr class="bg-surface-500">
|
<tr class="bg-surface-500">
|
||||||
{#each columns as col}
|
{#each columns as col}
|
||||||
<th class="!px-3">{col.label}</th>
|
<th class="!px-3">{col.label}</th>
|
||||||
|
|||||||
@ -10,6 +10,9 @@ import type {
|
|||||||
RacePickPoints,
|
RacePickPoints,
|
||||||
RacePickPointsAcc,
|
RacePickPointsAcc,
|
||||||
RaceResult,
|
RaceResult,
|
||||||
|
ScrapedDriverStanding,
|
||||||
|
ScrapedRaceResult,
|
||||||
|
ScrapedTeamStanding,
|
||||||
SeasonPick,
|
SeasonPick,
|
||||||
SeasonPickedUser,
|
SeasonPickedUser,
|
||||||
Substitution,
|
Substitution,
|
||||||
@ -289,3 +292,42 @@ export const fetch_racepickpointsacc = async (
|
|||||||
|
|
||||||
return racepickpointsacc;
|
return racepickpointsacc;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all [ScrapedDriverStandings] from the database, ordered ascendingly by position.
|
||||||
|
*/
|
||||||
|
export const fetch_scraped_driverstandings = async (
|
||||||
|
fetch: (_: any) => Promise<Response>,
|
||||||
|
): Promise<ScrapedDriverStanding[]> => {
|
||||||
|
const scraped_driverstandings: ScrapedDriverStanding[] = await pb
|
||||||
|
.collection("scraped_driverstandings")
|
||||||
|
.getFullList({ fetch: fetch, sort: "+position" });
|
||||||
|
|
||||||
|
return scraped_driverstandings;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all [ScrapedTeamStandings] from the database, ordered ascendingly by position.
|
||||||
|
*/
|
||||||
|
export const fetch_scraped_teamstandings = async (
|
||||||
|
fetch: (_: any) => Promise<Response>,
|
||||||
|
): Promise<ScrapedTeamStanding[]> => {
|
||||||
|
const scraped_teamstandings: ScrapedTeamStanding[] = await pb
|
||||||
|
.collection("scraped_teamstandings")
|
||||||
|
.getFullList({ fetch: fetch, sort: "+position" });
|
||||||
|
|
||||||
|
return scraped_teamstandings;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all [ScrapedRaceResults] from the database, ordered descendingly by race step.
|
||||||
|
*/
|
||||||
|
export const fetch_scraped_raceresults = async (
|
||||||
|
fetch: (_: any) => Promise<Response>,
|
||||||
|
): Promise<ScrapedRaceResult[]> => {
|
||||||
|
const scraped_raceresults: ScrapedRaceResult[] = await pb
|
||||||
|
.collection("scraped_raceresults")
|
||||||
|
.getFullList({ fetch: fetch, sort: "-race_step,+position" });
|
||||||
|
|
||||||
|
return scraped_raceresults;
|
||||||
|
};
|
||||||
|
|||||||
@ -145,3 +145,28 @@ export interface RacePickPointsAcc {
|
|||||||
total_dnf_points: number;
|
total_dnf_points: number;
|
||||||
total_points: number;
|
total_points: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scraped Data
|
||||||
|
|
||||||
|
export interface ScrapedRaceResult {
|
||||||
|
id: string;
|
||||||
|
race_step: number; // This maps to races
|
||||||
|
driver_code: string; // This maps to drivers
|
||||||
|
position: number;
|
||||||
|
status: string; // Either contains time to leader or DNF/DSQ...
|
||||||
|
points: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScrapedDriverStanding {
|
||||||
|
id: string;
|
||||||
|
driver_code: string; // This maps to drivers
|
||||||
|
position: number;
|
||||||
|
points: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScrapedTeamStanding {
|
||||||
|
id: string;
|
||||||
|
team_fullname: string; // TODO: This does NOT map to teams! Add fullname to team data!
|
||||||
|
position: number;
|
||||||
|
points: number;
|
||||||
|
}
|
||||||
|
|||||||
122
src/lib/server/scrape.ts
Normal file
122
src/lib/server/scrape.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import type { ScrapedDriverStanding, ScrapedRaceResult, ScrapedTeamStanding } from "$lib/schema";
|
||||||
|
import * as cheerio from "cheerio";
|
||||||
|
|
||||||
|
// TODO: Validate the generated stuff
|
||||||
|
|
||||||
|
export const base_url: string = "https://www.formula1.com/en/results/2025";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of links to all past races of the season,
|
||||||
|
* based on official f1.com data.
|
||||||
|
*/
|
||||||
|
export const scrape_race_links = async (): Promise<string[]> => {
|
||||||
|
const races_response = await fetch(`${base_url}/races`);
|
||||||
|
const races_text = await races_response.text();
|
||||||
|
|
||||||
|
const $ = cheerio.load(races_text);
|
||||||
|
|
||||||
|
const race_links: string[] = [];
|
||||||
|
$("tbody > tr > td:first-child > p > a[href]", "div.f1-inner-wrapper table.f1-table").each(
|
||||||
|
(_, element) => {
|
||||||
|
race_links.push(element.attribs["href"]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
console.log(`Found ${race_links.length} races...`);
|
||||||
|
|
||||||
|
return race_links;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of [ScrapedRaceResults] for all races contained in [race_links],
|
||||||
|
* based on official f1.com data.
|
||||||
|
*/
|
||||||
|
export const scrape_race_results = async (race_links: string[]): Promise<ScrapedRaceResult[]> => {
|
||||||
|
const race_results: ScrapedRaceResult[] = [];
|
||||||
|
await Promise.all(
|
||||||
|
race_links.map(async (link: string, index: number) => {
|
||||||
|
console.log(`Fetching race results from ${base_url}/${link}...`);
|
||||||
|
const race_response = await fetch(`${base_url}/${link}`);
|
||||||
|
const race_text = await race_response.text();
|
||||||
|
|
||||||
|
const $ = cheerio.load(race_text);
|
||||||
|
|
||||||
|
// Obtain the results for this race for each driver
|
||||||
|
$("tbody > tr", "div.f1-inner-wrapper table.f1-table").each((driver_index, element) => {
|
||||||
|
const $$ = cheerio.load(element);
|
||||||
|
|
||||||
|
let result: ScrapedRaceResult = {
|
||||||
|
id: "",
|
||||||
|
race_step: index + 1,
|
||||||
|
driver_code: $$("td:nth-child(3) > p > span:last-child").text(),
|
||||||
|
position: driver_index + 1, // parseInt($$("td:nth-child(1) > p").text()),
|
||||||
|
status: $$("td:nth-child(6) > p").text(),
|
||||||
|
points: parseInt($$("td:nth-child(7) > p").text()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// DSQ'd/DNF'd drivers have NaN positions
|
||||||
|
// if (Number.isNaN(result.position)) {
|
||||||
|
// result.position = driver_index;
|
||||||
|
// }
|
||||||
|
|
||||||
|
race_results.push(result);
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
console.log(`Scraped ${race_results.length} race results...`);
|
||||||
|
|
||||||
|
return race_results;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of [ScrapedDriverStandings], based on official f1.com data.
|
||||||
|
*/
|
||||||
|
export const scrape_driver_standings = async (): Promise<ScrapedDriverStanding[]> => {
|
||||||
|
const standings_response = await fetch(`${base_url}/drivers`);
|
||||||
|
const standings_text = await standings_response.text();
|
||||||
|
|
||||||
|
const $ = cheerio.load(standings_text);
|
||||||
|
|
||||||
|
const driver_standings: ScrapedDriverStanding[] = [];
|
||||||
|
$("tbody > tr", "div.f1-inner-wrapper table.f1-table").each((driver_index, element) => {
|
||||||
|
const $$ = cheerio.load(element);
|
||||||
|
|
||||||
|
let standing: ScrapedDriverStanding = {
|
||||||
|
id: "",
|
||||||
|
driver_code: $$("td:nth-child(2) > p > a > span:last-child").text(),
|
||||||
|
position: driver_index + 1,
|
||||||
|
points: parseInt($$("td:nth-child(5) > p").text()),
|
||||||
|
};
|
||||||
|
|
||||||
|
driver_standings.push(standing);
|
||||||
|
});
|
||||||
|
console.log(`Scraped ${driver_standings.length} driver standings...`);
|
||||||
|
|
||||||
|
return driver_standings;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of [ScrapedTeamStandings], based on official f1.com data.
|
||||||
|
*/
|
||||||
|
export const scrape_team_standings = async (): Promise<ScrapedTeamStanding[]> => {
|
||||||
|
const standings_response = await fetch(`${base_url}/team`);
|
||||||
|
const standings_text = await standings_response.text();
|
||||||
|
|
||||||
|
const $ = cheerio.load(standings_text);
|
||||||
|
|
||||||
|
const team_standings: ScrapedTeamStanding[] = [];
|
||||||
|
$("tbody > tr", "div.f1-inner-wrapper table.f1-table").each((team_index, element) => {
|
||||||
|
const $$ = cheerio.load(element);
|
||||||
|
|
||||||
|
let standing: ScrapedTeamStanding = {
|
||||||
|
id: "",
|
||||||
|
team_fullname: $$("td:nth-child(2) > p > a").text(),
|
||||||
|
position: team_index + 1,
|
||||||
|
points: parseInt($$("td:nth-child(3) > p").text()),
|
||||||
|
};
|
||||||
|
|
||||||
|
team_standings.push(standing);
|
||||||
|
});
|
||||||
|
console.log(`Scraped ${team_standings.length} team standings...`);
|
||||||
|
|
||||||
|
return team_standings;
|
||||||
|
};
|
||||||
@ -327,6 +327,9 @@
|
|||||||
"seasonpicks",
|
"seasonpicks",
|
||||||
"substitutions",
|
"substitutions",
|
||||||
"teams",
|
"teams",
|
||||||
|
"scraped_raceresults",
|
||||||
|
"scraped_driverstandings",
|
||||||
|
"scraped_teamstandings",
|
||||||
|
|
||||||
// The view collections do not receive realtime events
|
// The view collections do not receive realtime events
|
||||||
]),
|
]),
|
||||||
@ -342,6 +345,9 @@
|
|||||||
"seasonpicks",
|
"seasonpicks",
|
||||||
"substitutions",
|
"substitutions",
|
||||||
"teams",
|
"teams",
|
||||||
|
"scraped_raceresults",
|
||||||
|
"scraped_driverstandings",
|
||||||
|
"scraped_teamstandings",
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
@ -395,6 +401,15 @@
|
|||||||
<Button href="/data/users" onclick={close_drawer} color="surface" width="w-full" shadow>
|
<Button href="/data/users" onclick={close_drawer} color="surface" width="w-full" shadow>
|
||||||
Users
|
Users
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
href="/data/official/driverstandings"
|
||||||
|
onclick={close_drawer}
|
||||||
|
color="surface"
|
||||||
|
width="w-full"
|
||||||
|
shadow
|
||||||
|
>
|
||||||
|
Official
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{:else if $drawerStore.id === "login_drawer"}
|
{:else if $drawerStore.id === "login_drawer"}
|
||||||
<!-- Login Drawer -->
|
<!-- Login Drawer -->
|
||||||
|
|||||||
125
src/routes/api/scrape/+server.ts
Normal file
125
src/routes/api/scrape/+server.ts
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import {
|
||||||
|
fetch_scraped_driverstandings,
|
||||||
|
fetch_scraped_raceresults,
|
||||||
|
fetch_scraped_teamstandings,
|
||||||
|
} from "$lib/fetch";
|
||||||
|
import { pb } from "$lib/pocketbase";
|
||||||
|
import type { ScrapedDriverStanding, ScrapedRaceResult, ScrapedTeamStanding } from "$lib/schema";
|
||||||
|
import {
|
||||||
|
scrape_driver_standings,
|
||||||
|
scrape_race_links,
|
||||||
|
scrape_race_results,
|
||||||
|
scrape_team_standings,
|
||||||
|
} from "$lib/server/scrape";
|
||||||
|
import type { RequestHandler } from "./$types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This route is available at /api/scrape.
|
||||||
|
* It will fetch current statistics from f1.com and insert them into the database.
|
||||||
|
*/
|
||||||
|
// TODO: If this function aborts, it will leave the official data in an inconsistent state...
|
||||||
|
// Would be nice to use transactions for this, do I need to implement this as PB extension?
|
||||||
|
export const POST: RequestHandler = async ({ request }) => {
|
||||||
|
console.log("Fetching race results from f1.com...");
|
||||||
|
|
||||||
|
// Obtain the results for each race
|
||||||
|
const racelinks: string[] = await scrape_race_links();
|
||||||
|
const raceresults: ScrapedRaceResult[] = await scrape_race_results(racelinks);
|
||||||
|
const driverstandings: ScrapedDriverStanding[] = await scrape_driver_standings();
|
||||||
|
const teamstandings: ScrapedTeamStanding[] = await scrape_team_standings();
|
||||||
|
|
||||||
|
// Clear existing PocketBase data
|
||||||
|
let deleted: number = 0;
|
||||||
|
const scraped_raceresults: ScrapedRaceResult[] = await fetch_scraped_raceresults(fetch);
|
||||||
|
for (const result of scraped_raceresults) {
|
||||||
|
try {
|
||||||
|
await pb.collection("scraped_raceresults").delete(result.id);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
return new Response(); // TODO: Return error
|
||||||
|
}
|
||||||
|
deleted++;
|
||||||
|
}
|
||||||
|
console.log(`Deleted ${deleted}/${scraped_raceresults.length} race results.`);
|
||||||
|
|
||||||
|
deleted = 0;
|
||||||
|
const scraped_driverstandings: ScrapedDriverStanding[] =
|
||||||
|
await fetch_scraped_driverstandings(fetch);
|
||||||
|
for (const standing of scraped_driverstandings) {
|
||||||
|
try {
|
||||||
|
await pb.collection("scraped_driverstandings").delete(standing.id);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
return new Response(); // TODO: Return error
|
||||||
|
}
|
||||||
|
deleted++;
|
||||||
|
}
|
||||||
|
console.log(`Deleted ${deleted}/${scraped_driverstandings.length} driver standings.`);
|
||||||
|
|
||||||
|
deleted = 0;
|
||||||
|
const scraped_teamstandings: ScrapedTeamStanding[] = await fetch_scraped_teamstandings(fetch);
|
||||||
|
for (const standing of scraped_teamstandings) {
|
||||||
|
try {
|
||||||
|
await pb.collection("scraped_teamstandings").delete(standing.id);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
return new Response(); // TODO: Return error
|
||||||
|
}
|
||||||
|
deleted++;
|
||||||
|
}
|
||||||
|
console.log(`Deleted ${deleted}/${scraped_teamstandings.length} team standings.`);
|
||||||
|
|
||||||
|
// Submit new data to PocketBase
|
||||||
|
let submissions: number = 0;
|
||||||
|
for (const result of raceresults) {
|
||||||
|
try {
|
||||||
|
// TODO: Authenticate this
|
||||||
|
await pb.collection("scraped_raceresults").create(result);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error occured while submitting scraped data to PocketBase:");
|
||||||
|
console.log(e);
|
||||||
|
console.log("Error occured for this race result:");
|
||||||
|
console.log(result);
|
||||||
|
console.log("Aborting submissions...");
|
||||||
|
return new Response(); // TODO: Return error
|
||||||
|
}
|
||||||
|
submissions++;
|
||||||
|
}
|
||||||
|
console.log(`Submitted ${submissions}/${raceresults.length} race results.`);
|
||||||
|
|
||||||
|
submissions = 0;
|
||||||
|
for (const standing of driverstandings) {
|
||||||
|
try {
|
||||||
|
// TODO: Authenticate this
|
||||||
|
await pb.collection("scraped_driverstandings").create(standing);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error occured while submitting scraped data to PocketBase:");
|
||||||
|
console.log(e);
|
||||||
|
console.log("Error occured for this driver standing:");
|
||||||
|
console.log(standing);
|
||||||
|
console.log("Aborting submissions...");
|
||||||
|
return new Response(); // TODO: Return error
|
||||||
|
}
|
||||||
|
submissions++;
|
||||||
|
}
|
||||||
|
console.log(`Submitted ${submissions}/${driverstandings.length} driver standings.`);
|
||||||
|
|
||||||
|
submissions = 0;
|
||||||
|
for (const standing of teamstandings) {
|
||||||
|
try {
|
||||||
|
// TODO: Authenticate this
|
||||||
|
await pb.collection("scraped_teamstandings").create(standing);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error occured while submitting scraped data to PocketBase:");
|
||||||
|
console.log(e);
|
||||||
|
console.log("Error occured for this team standing:");
|
||||||
|
console.log(standing);
|
||||||
|
console.log("Aborting submissions...");
|
||||||
|
return new Response(); // TODO: Return error
|
||||||
|
}
|
||||||
|
submissions++;
|
||||||
|
}
|
||||||
|
console.log(`Submitted ${submissions}/${teamstandings.length} team standings.`);
|
||||||
|
|
||||||
|
return new Response(); // TODO: Return success
|
||||||
|
};
|
||||||
31
src/routes/data/official/+layout.svelte
Normal file
31
src/routes/data/official/+layout.svelte
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Button } from "$lib/components";
|
||||||
|
import type { Snippet } from "svelte";
|
||||||
|
|
||||||
|
let { children }: { children: Snippet } = $props();
|
||||||
|
|
||||||
|
const scrape_official_data = async () => {
|
||||||
|
// TODO: Success/error toast
|
||||||
|
const response: Response = await fetch("/api/scrape", { method: "POST" });
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="fixed left-0 right-0 top-14 z-10 flex justify-center">
|
||||||
|
<div
|
||||||
|
class="mx-2 flex w-full justify-center gap-2 bg-primary-500 pb-2 pt-3 shadow rounded-bl-container-token rounded-br-container-token"
|
||||||
|
>
|
||||||
|
<Button href="driverstandings" color="primary" activate_href>Drivers</Button>
|
||||||
|
<Button href="teamstandings" color="primary" activate_href>Teams</Button>
|
||||||
|
<Button href="raceresults" color="primary" activate_href>Race Results</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Each child's contents will be inserted here -->
|
||||||
|
<div style="margin-top: 56px;">
|
||||||
|
<div class="pb-2">
|
||||||
|
<Button width="w-full" color="tertiary" onclick={scrape_official_data} shadow>
|
||||||
|
<span class="font-bold">Refresh All Data</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{@render children()}
|
||||||
|
</div>
|
||||||
37
src/routes/data/official/driverstandings/+page.svelte
Normal file
37
src/routes/data/official/driverstandings/+page.svelte
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Table, type TableColumn } from "$lib/components";
|
||||||
|
import type { PageData } from "./$types";
|
||||||
|
|
||||||
|
let { data }: { data: PageData } = $props();
|
||||||
|
|
||||||
|
const standings_columns: TableColumn[] = $derived([
|
||||||
|
{
|
||||||
|
data_value_name: "driver_code",
|
||||||
|
label: "Driver",
|
||||||
|
valuefun: async (value: string): Promise<string> =>
|
||||||
|
`<span class='badge variant-filled-surface'>${value}</span>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data_value_name: "position",
|
||||||
|
label: "Position",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data_value_name: "points",
|
||||||
|
label: "Points",
|
||||||
|
valuefun: async (value: string): Promise<string> =>
|
||||||
|
`<span class='badge variant-filled-surface'>${value}</span>`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Formula 11 - Official Driver Standings</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
{#await data.scraped_driverstandings then standings}
|
||||||
|
<Table
|
||||||
|
data={standings}
|
||||||
|
columns={standings_columns}
|
||||||
|
height="h-[calc(100vh-260px)] lg:h-[calc(100vh-180px)]"
|
||||||
|
/>
|
||||||
|
{/await}
|
||||||
11
src/routes/data/official/driverstandings/+page.ts
Normal file
11
src/routes/data/official/driverstandings/+page.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { fetch_drivers, fetch_scraped_driverstandings } from "$lib/fetch";
|
||||||
|
import type { PageLoad } from "../../../$types";
|
||||||
|
|
||||||
|
export const load: PageLoad = async ({ fetch, depends }) => {
|
||||||
|
depends("data:scraped_driverstandings", "data:drivers");
|
||||||
|
|
||||||
|
return {
|
||||||
|
scraped_driverstandings: fetch_scraped_driverstandings(fetch),
|
||||||
|
drivers: fetch_drivers(fetch),
|
||||||
|
};
|
||||||
|
};
|
||||||
55
src/routes/data/official/raceresults/+page.svelte
Normal file
55
src/routes/data/official/raceresults/+page.svelte
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Table, type TableColumn } from "$lib/components";
|
||||||
|
import type { PageData } from "./$types";
|
||||||
|
import { get_by_value } from "$lib/database";
|
||||||
|
import type { Race } from "$lib/schema";
|
||||||
|
|
||||||
|
let { data }: { data: PageData } = $props();
|
||||||
|
|
||||||
|
const results_columns: TableColumn[] = $derived([
|
||||||
|
{
|
||||||
|
data_value_name: "race_step",
|
||||||
|
label: "Race",
|
||||||
|
valuefun: async (value: string): Promise<string> => {
|
||||||
|
const racename: string = get_by_value(await data.races, "step", value)?.name ?? "Invalid";
|
||||||
|
return `<span class='badge variant-filled-surface'>${racename}</span>`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data_value_name: "race_step",
|
||||||
|
label: "Step",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data_value_name: "driver_code",
|
||||||
|
label: "Driver",
|
||||||
|
valuefun: async (value: string): Promise<string> =>
|
||||||
|
`<span class='badge variant-filled-surface'>${value}</span>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data_value_name: "position",
|
||||||
|
label: "Position",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data_value_name: "status",
|
||||||
|
label: "Status",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data_value_name: "points",
|
||||||
|
label: "Points",
|
||||||
|
valuefun: async (value: string): Promise<string> =>
|
||||||
|
`<span class='badge variant-filled-surface'>${value}</span>`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Formula 11 - Official Race Results</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
{#await data.scraped_raceresults then results}
|
||||||
|
<Table
|
||||||
|
data={results}
|
||||||
|
columns={results_columns}
|
||||||
|
height="h-[calc(100vh-260px)] lg:h-[calc(100vh-180px)]"
|
||||||
|
/>
|
||||||
|
{/await}
|
||||||
12
src/routes/data/official/raceresults/+page.ts
Normal file
12
src/routes/data/official/raceresults/+page.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { fetch_drivers, fetch_races, fetch_scraped_raceresults } from "$lib/fetch";
|
||||||
|
import type { PageLoad } from "../../../$types";
|
||||||
|
|
||||||
|
export const load: PageLoad = async ({ fetch, depends }) => {
|
||||||
|
depends("data:scraped_raceresults", "data:races", "data:drivers");
|
||||||
|
|
||||||
|
return {
|
||||||
|
scraped_raceresults: fetch_scraped_raceresults(fetch),
|
||||||
|
races: fetch_races(fetch),
|
||||||
|
drivers: fetch_drivers(fetch),
|
||||||
|
};
|
||||||
|
};
|
||||||
37
src/routes/data/official/teamstandings/+page.svelte
Normal file
37
src/routes/data/official/teamstandings/+page.svelte
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { Table, type TableColumn } from "$lib/components";
|
||||||
|
import type { PageData } from "./$types";
|
||||||
|
|
||||||
|
let { data }: { data: PageData } = $props();
|
||||||
|
|
||||||
|
const standings_columns: TableColumn[] = $derived([
|
||||||
|
{
|
||||||
|
data_value_name: "team_fullname",
|
||||||
|
label: "Team",
|
||||||
|
valuefun: async (value: string): Promise<string> =>
|
||||||
|
`<span class='badge variant-filled-surface'>${value}</span>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data_value_name: "position",
|
||||||
|
label: "Position",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data_value_name: "points",
|
||||||
|
label: "Points",
|
||||||
|
valuefun: async (value: string): Promise<string> =>
|
||||||
|
`<span class='badge variant-filled-surface'>${value}</span>`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Formula 11 - Official Team Standings</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
{#await data.scraped_teamstandings then standings}
|
||||||
|
<Table
|
||||||
|
data={standings}
|
||||||
|
columns={standings_columns}
|
||||||
|
height="h-[calc(100vh-260px)] lg:h-[calc(100vh-180px)]"
|
||||||
|
/>
|
||||||
|
{/await}
|
||||||
11
src/routes/data/official/teamstandings/+page.ts
Normal file
11
src/routes/data/official/teamstandings/+page.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { fetch_scraped_teamstandings, fetch_teams } from "$lib/fetch";
|
||||||
|
import type { PageLoad } from "../../../$types";
|
||||||
|
|
||||||
|
export const load: PageLoad = async ({ fetch, depends }) => {
|
||||||
|
depends("data:scraped_teamstandings", "data:teams");
|
||||||
|
|
||||||
|
return {
|
||||||
|
scraped_teamstandings: fetch_scraped_teamstandings(fetch),
|
||||||
|
teams: fetch_teams(fetch),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -97,5 +97,10 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{#await data.raceresults then results}
|
{#await data.raceresults then results}
|
||||||
<Table data={results} columns={results_columns} handler={result_handler} />
|
<Table
|
||||||
|
data={results}
|
||||||
|
columns={results_columns}
|
||||||
|
handler={result_handler}
|
||||||
|
height="h-[calc(100vh-210px)] lg:h-[calc(100vh-126px)]"
|
||||||
|
/>
|
||||||
{/await}
|
{/await}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { fetch_drivers, fetch_raceresults, fetch_races, fetch_substitutions } fr
|
|||||||
import type { PageLoad } from "../../$types";
|
import type { PageLoad } from "../../$types";
|
||||||
|
|
||||||
export const load: PageLoad = async ({ fetch, depends }) => {
|
export const load: PageLoad = async ({ fetch, depends }) => {
|
||||||
depends("data:drivers", "data:races", "data:raceresults");
|
depends("data:drivers", "data:races", "data:raceresults", "data:substitutions");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
drivers: fetch_drivers(fetch),
|
drivers: fetch_drivers(fetch),
|
||||||
|
|||||||
@ -61,5 +61,10 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{#await data.drivers then drivers}
|
{#await data.drivers then drivers}
|
||||||
<Table data={drivers} columns={drivers_columns} handler={driver_handler} />
|
<Table
|
||||||
|
data={drivers}
|
||||||
|
columns={drivers_columns}
|
||||||
|
handler={driver_handler}
|
||||||
|
height="h-[calc(100vh-260px)] lg:h-[calc(100vh-180px)]"
|
||||||
|
/>
|
||||||
{/await}
|
{/await}
|
||||||
|
|||||||
@ -69,5 +69,10 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{#await data.races then races}
|
{#await data.races then races}
|
||||||
<Table data={races} columns={races_columns} handler={race_handler} />
|
<Table
|
||||||
|
data={races}
|
||||||
|
columns={races_columns}
|
||||||
|
handler={race_handler}
|
||||||
|
height="h-[calc(100vh-260px)] lg:h-[calc(100vh-180px)]"
|
||||||
|
/>
|
||||||
{/await}
|
{/await}
|
||||||
|
|||||||
@ -69,5 +69,10 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{#await data.substitutions then substitutions}
|
{#await data.substitutions then substitutions}
|
||||||
<Table data={substitutions} columns={substitutions_columns} handler={substitution_handler} />
|
<Table
|
||||||
|
data={substitutions}
|
||||||
|
columns={substitutions_columns}
|
||||||
|
handler={substitution_handler}
|
||||||
|
height="h-[calc(100vh-260px)] lg:h-[calc(100vh-180px)]"
|
||||||
|
/>
|
||||||
{/await}
|
{/await}
|
||||||
|
|||||||
@ -52,5 +52,10 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{#await data.teams then teams}
|
{#await data.teams then teams}
|
||||||
<Table data={teams} columns={teams_columns} handler={team_handler} />
|
<Table
|
||||||
|
data={teams}
|
||||||
|
columns={teams_columns}
|
||||||
|
handler={team_handler}
|
||||||
|
height="h-[calc(100vh-260px)] lg:h-[calc(100vh-180px)]"
|
||||||
|
/>
|
||||||
{/await}
|
{/await}
|
||||||
|
|||||||
@ -43,4 +43,9 @@
|
|||||||
<title>Formula 11 - Users</title>
|
<title>Formula 11 - Users</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<Table data={data.users} columns={users_columns} handler={users_handler} />
|
<Table
|
||||||
|
data={data.users}
|
||||||
|
columns={users_columns}
|
||||||
|
handler={users_handler}
|
||||||
|
height="h-[calc(100vh-160px)] lg:h-[calc(100vh-76px)]"
|
||||||
|
/>
|
||||||
|
|||||||
@ -12,12 +12,13 @@ import type { PageLoad } from "../$types";
|
|||||||
|
|
||||||
export const load: PageLoad = async ({ fetch, depends }) => {
|
export const load: PageLoad = async ({ fetch, depends }) => {
|
||||||
depends(
|
depends(
|
||||||
"data:racepicks",
|
|
||||||
"data:user",
|
"data:user",
|
||||||
|
"data:racepicks",
|
||||||
"data:users",
|
"data:users",
|
||||||
"data:raceresults",
|
"data:raceresults",
|
||||||
"data:drivers",
|
"data:drivers",
|
||||||
"data:races",
|
"data:races",
|
||||||
|
"data:substitutions",
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
import type { PageLoad } from "../$types";
|
import type { PageLoad } from "../$types";
|
||||||
|
|
||||||
export const load: PageLoad = async ({ fetch, depends }) => {
|
export const load: PageLoad = async ({ fetch, depends }) => {
|
||||||
depends("data:teams", "data:drivers", "data:seasonpicks", "data:user");
|
depends("data:teams", "data:drivers", "data:seasonpicks", "data:user", "data:users");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
teams: fetch_teams(fetch),
|
teams: fetch_teams(fetch),
|
||||||
|
|||||||
Reference in New Issue
Block a user