diff --git a/pb_migrations/1757710506_created_player_stats.js b/pb_migrations/1757710506_created_player_stats.js
new file mode 100644
index 0000000..828f08d
--- /dev/null
+++ b/pb_migrations/1757710506_created_player_stats.js
@@ -0,0 +1,235 @@
+///
+migrate((app) => {
+ const collection = new Collection({
+ "createRule": null,
+ "deleteRule": null,
+ "fields": [
+ {
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "text3208210256",
+ "max": 0,
+ "min": 0,
+ "name": "id",
+ "pattern": "^[a-z0-9]+$",
+ "presentable": false,
+ "primaryKey": true,
+ "required": true,
+ "system": true,
+ "type": "text"
+ },
+ {
+ "cascadeDelete": false,
+ "collectionId": "pbc_3072146508",
+ "hidden": false,
+ "id": "relation2582050271",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "player_id",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "relation"
+ },
+ {
+ "hidden": false,
+ "id": "json4231605813",
+ "maxSize": 1,
+ "name": "player_name",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "_clone_976G",
+ "max": 0,
+ "min": 0,
+ "name": "first_name",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": true,
+ "system": false,
+ "type": "text"
+ },
+ {
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "_clone_njLe",
+ "max": 0,
+ "min": 0,
+ "name": "last_name",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": false,
+ "system": false,
+ "type": "text"
+ },
+ {
+ "cascadeDelete": false,
+ "collectionId": "pbc_1568971955",
+ "hidden": false,
+ "id": "relation694999214",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "team_id",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "relation"
+ },
+ {
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "_clone_ZNMy",
+ "max": 0,
+ "min": 0,
+ "name": "team_name",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": true,
+ "system": false,
+ "type": "text"
+ },
+ {
+ "cascadeDelete": false,
+ "collectionId": "pbc_340646327",
+ "hidden": false,
+ "id": "relation869376999",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "tournament_id",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "relation"
+ },
+ {
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "_clone_nxTv",
+ "max": 0,
+ "min": 0,
+ "name": "tournament_name",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": true,
+ "system": false,
+ "type": "text"
+ },
+ {
+ "hidden": false,
+ "id": "number103159226",
+ "max": null,
+ "min": null,
+ "name": "matches",
+ "onlyInt": false,
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "number"
+ },
+ {
+ "hidden": false,
+ "id": "json2732118329",
+ "maxSize": 1,
+ "name": "wins",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "hidden": false,
+ "id": "json724428801",
+ "maxSize": 1,
+ "name": "losses",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "hidden": false,
+ "id": "json3041953980",
+ "maxSize": 1,
+ "name": "margin_of_victory",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "hidden": false,
+ "id": "json1531431708",
+ "maxSize": 1,
+ "name": "margin_of_loss",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "hidden": false,
+ "id": "json1062535948",
+ "maxSize": 1,
+ "name": "total_cups_won_by",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "hidden": false,
+ "id": "json4249694556",
+ "maxSize": 1,
+ "name": "total_cups_lost_by",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "hidden": false,
+ "id": "json3154249934",
+ "maxSize": 1,
+ "name": "total_cups_made",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "hidden": false,
+ "id": "json3227208027",
+ "maxSize": 1,
+ "name": "total_cups_against",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ }
+ ],
+ "id": "pbc_135889471",
+ "indexes": [],
+ "listRule": null,
+ "name": "player_stats",
+ "system": false,
+ "type": "view",
+ "updateRule": null,
+ "viewQuery": "\n SELECT\n (p.id || '_' || t.id || '_' || tour.id) as id,\n p.id as player_id,\n (p.first_name || ' ' || p.last_name) as player_name,\n p.first_name,\n p.last_name,\n t.id as team_id,\n t.name as team_name,\n tour.id as tournament_id,\n tour.name as tournament_name,\n COUNT(m.id) as matches,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) as wins,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups < m.away_cups) OR\n (m.away = t.id AND m.away_cups < m.home_cups)\n THEN 1 ELSE 0\n END) as losses,\n AVG(CASE\n WHEN m.home = t.id AND m.home_cups > m.away_cups\n THEN m.home_cups - m.away_cups\n WHEN m.away = t.id AND m.away_cups > m.home_cups\n THEN m.away_cups - m.home_cups\n ELSE NULL\n END) as margin_of_victory,\n AVG(CASE\n WHEN m.home = t.id AND m.home_cups < m.away_cups\n THEN m.away_cups - m.home_cups\n WHEN m.away = t.id AND m.away_cups < m.home_cups\n THEN m.home_cups - m.away_cups\n ELSE NULL\n END) as margin_of_loss,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_won_by,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_lost_by,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_made,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_against\n FROM players p\n JOIN teams t ON json_extract(t.players, '$[*]') LIKE '%' || p.id || '%'\n JOIN matches m ON (m.home = t.id OR m.away = t.id)\n JOIN tournaments tour ON m.tournament = tour.id\n WHERE m.status = 'ended'\n GROUP BY p.id, t.id, tour.id",
+ "viewRule": null
+ });
+
+ return app.save(collection);
+}, (app) => {
+ const collection = app.findCollectionByNameOrId("pbc_135889471");
+
+ return app.delete(collection);
+})
diff --git a/pb_migrations/1757710693_updated_player_stats.js b/pb_migrations/1757710693_updated_player_stats.js
new file mode 100644
index 0000000..35a4dae
--- /dev/null
+++ b/pb_migrations/1757710693_updated_player_stats.js
@@ -0,0 +1,194 @@
+///
+migrate((app) => {
+ const collection = app.findCollectionByNameOrId("pbc_135889471")
+
+ // update collection data
+ unmarshal({
+ "viewQuery": "SELECT\n (p.id || '_' || t.id || '_' || tour.id) as id,\n p.id as player_id,\n (p.first_name || ' ' || p.last_name) as player_name,\n t.id as team_id,\n t.name as team_name,\n tour.id as tournament_id,\n tour.name as tournament_name,\n COUNT(m.id) as matches,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) as wins,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups < m.away_cups) OR\n (m.away = t.id AND m.away_cups < m.home_cups)\n THEN 1 ELSE 0\n END) as losses,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_made,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_against\n FROM players p, teams t, matches m, tournaments tour\n WHERE\n t.players LIKE '%\"' || p.id || '\"%' AND\n (m.home = t.id OR m.away = t.id) AND\n m.tournament = tour.id AND\n m.status = 'ended'\n GROUP BY p.id, t.id, tour.id"
+ }, collection)
+
+ // remove field
+ collection.fields.removeById("_clone_976G")
+
+ // remove field
+ collection.fields.removeById("_clone_njLe")
+
+ // remove field
+ collection.fields.removeById("_clone_ZNMy")
+
+ // remove field
+ collection.fields.removeById("_clone_nxTv")
+
+ // remove field
+ collection.fields.removeById("json3041953980")
+
+ // remove field
+ collection.fields.removeById("json1531431708")
+
+ // remove field
+ collection.fields.removeById("json1062535948")
+
+ // remove field
+ collection.fields.removeById("json4249694556")
+
+ // add field
+ collection.fields.addAt(4, new Field({
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "_clone_YqC8",
+ "max": 0,
+ "min": 0,
+ "name": "team_name",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": true,
+ "system": false,
+ "type": "text"
+ }))
+
+ // add field
+ collection.fields.addAt(6, new Field({
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "_clone_jZTo",
+ "max": 0,
+ "min": 0,
+ "name": "tournament_name",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": true,
+ "system": false,
+ "type": "text"
+ }))
+
+ return app.save(collection)
+}, (app) => {
+ const collection = app.findCollectionByNameOrId("pbc_135889471")
+
+ // update collection data
+ unmarshal({
+ "viewQuery": "\n SELECT\n (p.id || '_' || t.id || '_' || tour.id) as id,\n p.id as player_id,\n (p.first_name || ' ' || p.last_name) as player_name,\n p.first_name,\n p.last_name,\n t.id as team_id,\n t.name as team_name,\n tour.id as tournament_id,\n tour.name as tournament_name,\n COUNT(m.id) as matches,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) as wins,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups < m.away_cups) OR\n (m.away = t.id AND m.away_cups < m.home_cups)\n THEN 1 ELSE 0\n END) as losses,\n AVG(CASE\n WHEN m.home = t.id AND m.home_cups > m.away_cups\n THEN m.home_cups - m.away_cups\n WHEN m.away = t.id AND m.away_cups > m.home_cups\n THEN m.away_cups - m.home_cups\n ELSE NULL\n END) as margin_of_victory,\n AVG(CASE\n WHEN m.home = t.id AND m.home_cups < m.away_cups\n THEN m.away_cups - m.home_cups\n WHEN m.away = t.id AND m.away_cups < m.home_cups\n THEN m.home_cups - m.away_cups\n ELSE NULL\n END) as margin_of_loss,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_won_by,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_lost_by,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_made,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_against\n FROM players p\n JOIN teams t ON json_extract(t.players, '$[*]') LIKE '%' || p.id || '%'\n JOIN matches m ON (m.home = t.id OR m.away = t.id)\n JOIN tournaments tour ON m.tournament = tour.id\n WHERE m.status = 'ended'\n GROUP BY p.id, t.id, tour.id"
+ }, collection)
+
+ // add field
+ collection.fields.addAt(3, new Field({
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "_clone_976G",
+ "max": 0,
+ "min": 0,
+ "name": "first_name",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": true,
+ "system": false,
+ "type": "text"
+ }))
+
+ // add field
+ collection.fields.addAt(4, new Field({
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "_clone_njLe",
+ "max": 0,
+ "min": 0,
+ "name": "last_name",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": false,
+ "system": false,
+ "type": "text"
+ }))
+
+ // add field
+ collection.fields.addAt(6, new Field({
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "_clone_ZNMy",
+ "max": 0,
+ "min": 0,
+ "name": "team_name",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": true,
+ "system": false,
+ "type": "text"
+ }))
+
+ // add field
+ collection.fields.addAt(8, new Field({
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "_clone_nxTv",
+ "max": 0,
+ "min": 0,
+ "name": "tournament_name",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": true,
+ "system": false,
+ "type": "text"
+ }))
+
+ // add field
+ collection.fields.addAt(12, new Field({
+ "hidden": false,
+ "id": "json3041953980",
+ "maxSize": 1,
+ "name": "margin_of_victory",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ }))
+
+ // add field
+ collection.fields.addAt(13, new Field({
+ "hidden": false,
+ "id": "json1531431708",
+ "maxSize": 1,
+ "name": "margin_of_loss",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ }))
+
+ // add field
+ collection.fields.addAt(14, new Field({
+ "hidden": false,
+ "id": "json1062535948",
+ "maxSize": 1,
+ "name": "total_cups_won_by",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ }))
+
+ // add field
+ collection.fields.addAt(15, new Field({
+ "hidden": false,
+ "id": "json4249694556",
+ "maxSize": 1,
+ "name": "total_cups_lost_by",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ }))
+
+ // remove field
+ collection.fields.removeById("_clone_YqC8")
+
+ // remove field
+ collection.fields.removeById("_clone_jZTo")
+
+ return app.save(collection)
+})
diff --git a/pb_migrations/1757710817_updated_player_stats.js b/pb_migrations/1757710817_updated_player_stats.js
new file mode 100644
index 0000000..18817fe
--- /dev/null
+++ b/pb_migrations/1757710817_updated_player_stats.js
@@ -0,0 +1,96 @@
+///
+migrate((app) => {
+ const collection = app.findCollectionByNameOrId("pbc_135889471")
+
+ // update collection data
+ unmarshal({
+ "name": "player_stats_per_tournament"
+ }, collection)
+
+ // remove field
+ collection.fields.removeById("_clone_YqC8")
+
+ // remove field
+ collection.fields.removeById("_clone_jZTo")
+
+ // add field
+ collection.fields.addAt(4, new Field({
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "_clone_XGbN",
+ "max": 0,
+ "min": 0,
+ "name": "team_name",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": true,
+ "system": false,
+ "type": "text"
+ }))
+
+ // add field
+ collection.fields.addAt(6, new Field({
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "_clone_uud6",
+ "max": 0,
+ "min": 0,
+ "name": "tournament_name",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": true,
+ "system": false,
+ "type": "text"
+ }))
+
+ return app.save(collection)
+}, (app) => {
+ const collection = app.findCollectionByNameOrId("pbc_135889471")
+
+ // update collection data
+ unmarshal({
+ "name": "player_stats"
+ }, collection)
+
+ // add field
+ collection.fields.addAt(4, new Field({
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "_clone_YqC8",
+ "max": 0,
+ "min": 0,
+ "name": "team_name",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": true,
+ "system": false,
+ "type": "text"
+ }))
+
+ // add field
+ collection.fields.addAt(6, new Field({
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "_clone_jZTo",
+ "max": 0,
+ "min": 0,
+ "name": "tournament_name",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": true,
+ "system": false,
+ "type": "text"
+ }))
+
+ // remove field
+ collection.fields.removeById("_clone_XGbN")
+
+ // remove field
+ collection.fields.removeById("_clone_uud6")
+
+ return app.save(collection)
+})
diff --git a/pb_migrations/1757710882_created_player_stats.js b/pb_migrations/1757710882_created_player_stats.js
new file mode 100644
index 0000000..5c6c723
--- /dev/null
+++ b/pb_migrations/1757710882_created_player_stats.js
@@ -0,0 +1,180 @@
+///
+migrate((app) => {
+ const collection = new Collection({
+ "createRule": null,
+ "deleteRule": null,
+ "fields": [
+ {
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "text3208210256",
+ "max": 0,
+ "min": 0,
+ "name": "id",
+ "pattern": "^[a-z0-9]+$",
+ "presentable": false,
+ "primaryKey": true,
+ "required": true,
+ "system": true,
+ "type": "text"
+ },
+ {
+ "cascadeDelete": false,
+ "collectionId": "pbc_3072146508",
+ "hidden": false,
+ "id": "relation2582050271",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "player_id",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "relation"
+ },
+ {
+ "hidden": false,
+ "id": "json4231605813",
+ "maxSize": 1,
+ "name": "player_name",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "cascadeDelete": false,
+ "collectionId": "pbc_1568971955",
+ "hidden": false,
+ "id": "relation694999214",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "team_id",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "relation"
+ },
+ {
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "_clone_fQu1",
+ "max": 0,
+ "min": 0,
+ "name": "team_name",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": true,
+ "system": false,
+ "type": "text"
+ },
+ {
+ "hidden": false,
+ "id": "number103159226",
+ "max": null,
+ "min": null,
+ "name": "matches",
+ "onlyInt": false,
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "number"
+ },
+ {
+ "hidden": false,
+ "id": "json2732118329",
+ "maxSize": 1,
+ "name": "wins",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "hidden": false,
+ "id": "json724428801",
+ "maxSize": 1,
+ "name": "losses",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "hidden": false,
+ "id": "json3154249934",
+ "maxSize": 1,
+ "name": "total_cups_made",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "hidden": false,
+ "id": "json3227208027",
+ "maxSize": 1,
+ "name": "total_cups_against",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "hidden": false,
+ "id": "json2379943496",
+ "maxSize": 1,
+ "name": "win_percentage",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "hidden": false,
+ "id": "json3165107022",
+ "maxSize": 1,
+ "name": "avg_cups_per_match",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "hidden": false,
+ "id": "json3041953980",
+ "maxSize": 1,
+ "name": "margin_of_victory",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ },
+ {
+ "hidden": false,
+ "id": "json1531431708",
+ "maxSize": 1,
+ "name": "margin_of_loss",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "json"
+ }
+ ],
+ "id": "pbc_1358894712",
+ "indexes": [],
+ "listRule": null,
+ "name": "player_stats",
+ "system": false,
+ "type": "view",
+ "updateRule": null,
+ "viewQuery": "SELECT\n (p.id || '_' || t.id) as id,\n p.id as player_id,\n (p.first_name || ' ' || p.last_name) as player_name,\n t.id as team_id,\n t.name as team_name,\n COUNT(m.id) as matches,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) as wins,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups < m.away_cups) OR\n (m.away = t.id AND m.away_cups < m.home_cups)\n THEN 1 ELSE 0\n END) as losses,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_made,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_against,\n -- Additional calculated stats\n ROUND((CAST(SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) AS REAL) / COUNT(m.id)) * 100, 2) as win_percentage,\n ROUND(CAST(SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) AS REAL) / COUNT(m.id), 2) as avg_cups_per_match,\n -- Margin calculations\n AVG(CASE\n WHEN m.home = t.id AND m.home_cups > m.away_cups\n THEN m.home_cups - m.away_cups\n WHEN m.away = t.id AND m.away_cups > m.home_cups\n THEN m.away_cups - m.home_cups\n ELSE NULL\n END) as margin_of_victory,\n AVG(CASE\n WHEN m.home = t.id AND m.home_cups < m.away_cups\n THEN m.away_cups - m.home_cups\n WHEN m.away = t.id AND m.away_cups < m.home_cups\n THEN m.home_cups - m.away_cups\n ELSE NULL\n END) as margin_of_loss\n FROM players p, teams t, matches m, tournaments tour\n WHERE\n t.players LIKE '%\"' || p.id || '\"%' AND\n (m.home = t.id OR m.away = t.id) AND\n m.tournament = tour.id AND\n m.status = 'ended'\n GROUP BY p.id, t.id",
+ "viewRule": null
+ });
+
+ return app.save(collection);
+}, (app) => {
+ const collection = app.findCollectionByNameOrId("pbc_1358894712");
+
+ return app.delete(collection);
+})
diff --git a/pb_migrations/1757711182_updated_player_stats.js b/pb_migrations/1757711182_updated_player_stats.js
new file mode 100644
index 0000000..ec9878a
--- /dev/null
+++ b/pb_migrations/1757711182_updated_player_stats.js
@@ -0,0 +1,57 @@
+///
+migrate((app) => {
+ const collection = app.findCollectionByNameOrId("pbc_1358894712")
+
+ // update collection data
+ unmarshal({
+ "viewQuery": "SELECT\n p.id as id,\n p.id as player_id,\n (p.first_name || ' ' || p.last_name) as player_name,\n COUNT(m.id) as matches,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) as wins,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups < m.away_cups) OR\n (m.away = t.id AND m.away_cups < m.home_cups)\n THEN 1 ELSE 0\n END) as losses,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_made,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_against,\n -- Win percentage\n ROUND((CAST(SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) AS REAL) / COUNT(m.id)) * 100, 2) as win_percentage,\n -- Average cups per match\n ROUND(CAST(SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) AS REAL) / COUNT(m.id), 2) as avg_cups_per_match,\n -- Margin of Victory\n ROUND(AVG(CASE\n WHEN m.home = t.id AND m.home_cups > m.away_cups\n THEN m.home_cups - m.away_cups\n WHEN m.away = t.id AND m.away_cups > m.home_cups\n THEN m.away_cups - m.home_cups\n ELSE NULL\n END), 2) as margin_of_victory,\n -- Margin of Loss\n ROUND(AVG(CASE\n WHEN m.home = t.id AND m.home_cups < m.away_cups\n THEN m.away_cups - m.home_cups\n WHEN m.away = t.id AND m.away_cups < m.home_cups\n THEN m.home_cups - m.away_cups\n ELSE NULL\n END), 2) as margin_of_loss\n FROM players p, teams t, matches m, tournaments tour\n WHERE\n t.players LIKE '%\"' || p.id || '\"%' AND\n (m.home = t.id OR m.away = t.id) AND\n m.tournament = tour.id AND\n m.status = 'ended'\n GROUP BY p.id"
+ }, collection)
+
+ // remove field
+ collection.fields.removeById("relation694999214")
+
+ // remove field
+ collection.fields.removeById("_clone_fQu1")
+
+ return app.save(collection)
+}, (app) => {
+ const collection = app.findCollectionByNameOrId("pbc_1358894712")
+
+ // update collection data
+ unmarshal({
+ "viewQuery": "SELECT\n (p.id || '_' || t.id) as id,\n p.id as player_id,\n (p.first_name || ' ' || p.last_name) as player_name,\n t.id as team_id,\n t.name as team_name,\n COUNT(m.id) as matches,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) as wins,\n SUM(CASE\n WHEN (m.home = t.id AND m.home_cups < m.away_cups) OR\n (m.away = t.id AND m.away_cups < m.home_cups)\n THEN 1 ELSE 0\n END) as losses,\n SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) as total_cups_made,\n SUM(CASE\n WHEN m.home = t.id THEN m.away_cups\n WHEN m.away = t.id THEN m.home_cups\n ELSE 0\n END) as total_cups_against,\n -- Additional calculated stats\n ROUND((CAST(SUM(CASE\n WHEN (m.home = t.id AND m.home_cups > m.away_cups) OR\n (m.away = t.id AND m.away_cups > m.home_cups)\n THEN 1 ELSE 0\n END) AS REAL) / COUNT(m.id)) * 100, 2) as win_percentage,\n ROUND(CAST(SUM(CASE\n WHEN m.home = t.id THEN m.home_cups\n WHEN m.away = t.id THEN m.away_cups\n ELSE 0\n END) AS REAL) / COUNT(m.id), 2) as avg_cups_per_match,\n -- Margin calculations\n AVG(CASE\n WHEN m.home = t.id AND m.home_cups > m.away_cups\n THEN m.home_cups - m.away_cups\n WHEN m.away = t.id AND m.away_cups > m.home_cups\n THEN m.away_cups - m.home_cups\n ELSE NULL\n END) as margin_of_victory,\n AVG(CASE\n WHEN m.home = t.id AND m.home_cups < m.away_cups\n THEN m.away_cups - m.home_cups\n WHEN m.away = t.id AND m.away_cups < m.home_cups\n THEN m.home_cups - m.away_cups\n ELSE NULL\n END) as margin_of_loss\n FROM players p, teams t, matches m, tournaments tour\n WHERE\n t.players LIKE '%\"' || p.id || '\"%' AND\n (m.home = t.id OR m.away = t.id) AND\n m.tournament = tour.id AND\n m.status = 'ended'\n GROUP BY p.id, t.id"
+ }, collection)
+
+ // add field
+ collection.fields.addAt(3, new Field({
+ "cascadeDelete": false,
+ "collectionId": "pbc_1568971955",
+ "hidden": false,
+ "id": "relation694999214",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "team_id",
+ "presentable": false,
+ "required": false,
+ "system": false,
+ "type": "relation"
+ }))
+
+ // add field
+ collection.fields.addAt(4, new Field({
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "_clone_fQu1",
+ "max": 0,
+ "min": 0,
+ "name": "team_name",
+ "pattern": "",
+ "presentable": false,
+ "primaryKey": false,
+ "required": true,
+ "system": false,
+ "type": "text"
+ }))
+
+ return app.save(collection)
+})
diff --git a/src/features/bracket/components/match-card.tsx b/src/features/bracket/components/match-card.tsx
index 5addac5..123bcad 100644
--- a/src/features/bracket/components/match-card.tsx
+++ b/src/features/bracket/components/match-card.tsx
@@ -30,7 +30,12 @@ export const MatchCard: React.FC = ({
from_loser: match.home_from_loser,
team: match.home,
seed: match.home_seed,
- cups: match.status === "ended" ? match.home_cups : undefined
+ cups: match.status === "ended" ? match.home_cups : undefined,
+ isWinner:
+ match.status === "ended" &&
+ match.home_cups !== undefined &&
+ match.away_cups !== undefined &&
+ match.home_cups > match.away_cups,
}),
[match]
);
@@ -40,7 +45,12 @@ export const MatchCard: React.FC = ({
from_loser: match.away_from_loser,
team: match.away,
seed: match.away_seed,
- cups: match.status === "ended" ? match.away_cups : undefined
+ cups: match.status === "ended" ? match.away_cups : undefined,
+ isWinner:
+ match.status === "ended" &&
+ match.away_cups !== undefined &&
+ match.home_cups !== undefined &&
+ match.away_cups > match.home_cups,
}),
[match]
);
@@ -81,37 +91,45 @@ export const MatchCard: React.FC = ({
}, [match]);
const handleFormSubmit = useCallback(
- async (data: { home_cups: number; away_cups: number; ot_count: number }) => {
- await end.mutate({ data: {
- ...data,
- matchId: match.id
- }});
+ async (data: {
+ home_cups: number;
+ away_cups: number;
+ ot_count: number;
+ }) => {
+ await end.mutate({
+ data: {
+ ...data,
+ matchId: match.id,
+ },
+ });
editSheet.close();
},
[match.id, editSheet]
);
const handleSpeakerClick = useCallback(() => {
- if ('speechSynthesis' in window && match.home?.name && match.away?.name) {
+ if ("speechSynthesis" in window && match.home?.name && match.away?.name) {
const utterance = new SpeechSynthesisUtterance(
`${match.home.name} vs. ${match.away.name}`
);
-
+
const voices = window.speechSynthesis.getVoices();
-
- const preferredVoice = voices.find(voice =>
- voice.lang.startsWith('en') &&
- voice.name.includes('Daniel')
- ) || voices.find(voice => voice.lang.startsWith('en') && voice.default);
-
+
+ const preferredVoice =
+ voices.find(
+ (voice) =>
+ voice.lang.startsWith("en") && voice.name.includes("Daniel")
+ ) ||
+ voices.find((voice) => voice.lang.startsWith("en") && voice.default);
+
if (preferredVoice) {
utterance.voice = preferredVoice;
}
-
+
utterance.rate = 0.9;
utterance.volume = 0.8;
utterance.pitch = 1.0;
-
+
window.speechSynthesis.speak(utterance);
}
}, [match.home?.name, match.away?.name]);
@@ -157,17 +175,19 @@ export const MatchCard: React.FC = ({
)}
-
-
-
+ {showControls && (
+
+
+
+ )}
diff --git a/src/features/bracket/components/match-slot.tsx b/src/features/bracket/components/match-slot.tsx
index 20a340a..4783804 100644
--- a/src/features/bracket/components/match-slot.tsx
+++ b/src/features/bracket/components/match-slot.tsx
@@ -1,5 +1,6 @@
import { Flex, Text } from "@mantine/core";
import React from "react";
+import { CrownIcon } from "@phosphor-icons/react";
import { SeedBadge } from "./seed-badge";
import { TeamInfo } from "@/features/teams/types";
@@ -9,6 +10,7 @@ interface MatchSlotProps {
team?: TeamInfo;
seed?: number;
cups?: number;
+ isWinner?: boolean;
}
export const MatchSlot: React.FC = ({
@@ -16,25 +18,49 @@ export const MatchSlot: React.FC = ({
from_loser,
team,
seed,
- cups
+ cups,
+ isWinner
}) => (
{(seed && seed > 0) ? : undefined}
-
- {team ? (
- {team.name}
- ) : from ? (
-
- {from_loser ? "Loser" : "Winner"} of Match {from}
-
- ) : (
-
- TBD
-
- )}
+
+
+ {team ? (
+ <>
+ 12 ? (team.name.length > 18 ? '10px' : '11px') : 'xs'}
+ truncate
+ style={{ minWidth: 0, flex: 1 }}
+ >
+ {team.name}
+
+ {isWinner && (
+
+ )}
+ >
+ ) : from ? (
+
+ {from_loser ? "Loser" : "Winner"} of Match {from}
+
+ ) : (
+
+ TBD
+
+ )}
+
{
cups !== undefined ? (
- {cups}
+ {cups}
) : undefined
}
diff --git a/src/features/matches/server.ts b/src/features/matches/server.ts
index 29eff21..aa7ed9e 100644
--- a/src/features/matches/server.ts
+++ b/src/features/matches/server.ts
@@ -58,8 +58,8 @@ export const generateTournamentBracket = createServerFn()
home_cups: 0,
away_cups: 0,
ot_count: 0,
- home_from_lid: match.home_from_lid,
- away_from_lid: match.away_from_lid,
+ home_from_lid: match.home_from_lid === null ? -1 : match.home_from_lid,
+ away_from_lid: match.away_from_lid === null ? -1 : match.away_from_lid,
home_from_loser: match.home_from_loser || false,
away_from_loser: match.away_from_loser || false,
is_losers_bracket: false,
@@ -102,8 +102,8 @@ export const generateTournamentBracket = createServerFn()
home_cups: 0,
away_cups: 0,
ot_count: 0,
- home_from_lid: match.home_from_lid,
- away_from_lid: match.away_from_lid,
+ home_from_lid: match.home_from_lid === null ? -1 : match.home_from_lid,
+ away_from_lid: match.away_from_lid === null ? -1 : match.away_from_lid,
home_from_loser: match.home_from_loser || false,
away_from_loser: match.away_from_loser || false,
is_losers_bracket: true,
@@ -190,6 +190,8 @@ export const endMatch = createServerFn()
// winner -> where to send match winner to, loser same
const { winner, loser } = await pbAdmin.getChildMatches(matchId);
+ console.log(winner, loser)
+
// reset match check
if (winner && winner.reset) {
const awayTeamWon = match.away === matchWinner;