Compare commits

...

80 Commits

Author SHA1 Message Date
87b2abbd80 Merge branch 'eventlineup-development' 2024-05-29 16:55:07 -05:00
b0de3fb221 add buttons to move slots
build css
2024-05-29 16:48:03 -05:00
149f0a411b evensheet fix when no availabilities or positions 2024-05-29 16:44:46 -05:00
e6d1d5a697 Merge branch 'simplification' 2024-05-29 16:42:46 -05:00
bf9d0c1a78 simplifications
compile css
2024-05-29 16:39:36 -05:00
64fa16740b enabled 2024-05-27 15:17:27 -05:00
f273677ba7 Merge branch 'eventlineup-availability-reminders'
# Conflicts:
#	src/public/js/eventlineup.js
2024-05-27 15:11:39 -05:00
ca194d516d email fixes, tweaks 2024-05-27 15:10:17 -05:00
ed18389bb2 auto restart of debugger 2024-05-27 15:10:17 -05:00
b346710496 fix embed svg (additional classes) 2024-05-27 15:10:16 -05:00
6d1588e80f statusCode fix 2024-05-27 15:10:16 -05:00
858cb24e3f cleanup
oops missing bracket

cleanup
2024-05-27 15:10:16 -05:00
196eb5f51d fixed adjacent lineup 2024-05-27 15:10:15 -05:00
07446570c1 add clear lineup/availabilities, availability reminders 2024-05-27 15:04:34 -05:00
a5c47ff9a7 Merge branch 'main' into eventlineup-availability-reminders 2024-05-26 11:44:58 -05:00
aa2ebf0b2e Merge branch 'fix-adjacent-lineup' 2024-05-26 11:40:36 -05:00
5f02ea4d5c caddy_data is not external 2024-05-26 11:36:21 -05:00
39380eaf03 persist caddy data, move domain to .env 2024-05-26 11:34:35 -05:00
cb131353dc Merge branch 'docker' 2024-05-26 10:42:45 -05:00
e1c9a7b81b cleanup to allow for debugging 2024-05-26 10:42:21 -05:00
c495b265ee Merge branch 'main' into docker 2024-05-25 19:49:20 -05:00
49864874fc dev script updates for scss watch 2024-05-24 13:40:59 -05:00
e9fd60e619 404 fix 2024-05-24 13:40:42 -05:00
84da372330 Merge branch 'main' into docker 2024-05-24 10:42:19 -05:00
c696ffb4bc Merge branch 'eventsheet-development' 2024-05-24 10:42:06 -05:00
a06807b028 fixed adjacent lineup 2024-05-23 08:03:30 -05:00
07be459781 cleanup some styling 2024-05-22 16:47:25 -05:00
769fa60196 incorporating changes for updated svg 2024-05-22 16:09:33 -05:00
d377399c10 incorporating changes for updated svg 2024-05-22 16:07:08 -05:00
fa50ab93dc incorporate letter sized sheet
accessible when url parameter ?sheet_size=letter
2024-05-22 13:41:17 -05:00
73e3afcecc Merge branch 'main' into docker 2024-05-20 09:11:25 -05:00
af9fa3bd9b fix for if availabilities aren't available 2024-05-20 09:11:08 -05:00
7e803cc8e3 fix for if availabilities aren't available 2024-05-20 09:07:54 -05:00
339d4c7923 Merge branch 'duplicate-checking' 2024-05-20 09:07:30 -05:00
58825b5bcd reloads the page on lineup save
realized that the duplicates come from saving the lineup more than once. this occurs when initially no one has an eventlineupentry, it gets one on save, but the front end is not updated on save, so it keeps creating entrires on save. simple fix is to refresh the page on save. "better" solution would be to have the front end update on save, but that's longer to implement. i started this by having `postEventLineup` return the newly fetched lineupentries.
2024-05-20 09:06:53 -05:00
6b9e6734fe Merge branch 'main' into docker 2024-05-19 12:35:31 -05:00
03a9ac3aae Merge branch 'eventsheet-development' 2024-05-19 12:35:14 -05:00
da159f2b13 urlencode needs an explicit extended option 2024-05-19 12:34:27 -05:00
c2c192bdc6 add blank lineupsheet 2024-05-19 08:53:26 -05:00
c2b1898b91 styling updates 2024-05-19 08:52:48 -05:00
4ee466e7cb Merge branch 'main' into eventsheet-development 2024-05-14 19:09:33 -05:00
6592f2eeae Merge branch 'main' into docker 2024-05-10 17:55:15 -05:00
1f2ba45e54 bug fix for initSlots 2024-05-10 17:55:03 -05:00
588c23ec3f refactoring eventsheet 2024-05-10 17:54:45 -05:00
1c3cafdcda Merge branch 'main' into docker 2024-05-06 16:51:00 -05:00
fb0ca76c29 reimplement eventlineup slots, eventsheet refinements
fixed the loop in eventlineup slots so they will show duplicate eventlineupentries if they exist
refined some styling in eventsheet
2024-05-06 16:49:21 -05:00
bdb6a77371 begin adding availability reminders 2024-05-06 16:46:29 -05:00
e4b981d676 reimplement eventlineup slots, eventsheet refinements
fixed the loop in eventlineup slots so they will show duplicate eventlineupentries if they exist

refined some styling in eventsheet
2024-05-06 16:45:56 -05:00
c4c0d0fb7d move eventlineup helpers to separate file 2024-05-06 10:41:44 -05:00
3695cd8975 fix for grouping items with no type
Fixed an error that caused the app to crash when trying to group a bulk set of items when there are items with no "type" property. Items something like "delete request" items had no type.
2024-05-06 10:40:15 -05:00
bcade85182 eventsheet styling improvements 2024-05-06 10:38:52 -05:00
d50f94acc8 update better-sqlite
needed to update better-sqlite to work with node 22 (i think? this fixed it anyways)
2024-05-06 10:36:45 -05:00
5e1facf24a Merge branch 'main' into docker 2024-05-03 07:47:05 -05:00
cf01bf9fff styling fixes
Fixes for event sheet, added availability bar to event lineup
2024-05-03 07:46:05 -05:00
83e722cdb9 add availability bar to lineup view 2024-05-03 07:41:54 -05:00
dd48aeca8d eventsheet styling fixes 2024-05-03 07:41:29 -05:00
f421089eb9 Merge branch 'main' into docker 2024-03-22 16:05:34 -05:00
d484f8cfdf csrf fix when added lineup 2024-03-22 16:03:57 -05:00
87735f76b5 fix submit to actual submitted lineup 2024-03-22 16:03:46 -05:00
6d4600a858 fix submit animation when there's more than one lineup 2024-03-22 16:03:19 -05:00
5c19a16f8b flags fix 2024-03-22 15:50:33 -05:00
aea6a64b69 add session expiry date (24 hours) 2024-03-22 15:49:52 -05:00
b9ead6770a styling changes 2024-03-22 15:49:17 -05:00
84cc1f651c Merge branch 'main' into docker 2024-03-18 20:22:15 -05:00
a1cb6fcf0a eventlineup enhancments (availability notes, reminders, flags popup) 2024-03-18 20:20:00 -05:00
e459a0688a re-add padding on main content in layout 2024-03-18 20:19:08 -05:00
8e98286f7c eventsheet styling fixes 2024-03-18 20:18:33 -05:00
87fb835590 gamesheet hide logo if not exists 2024-03-18 20:17:59 -05:00
1f897d2b1e styling fix on game sheet (front cover) 2024-03-18 20:17:20 -05:00
8c1b325532 hide "members" section for now, not implemented yet 2024-03-18 20:15:33 -05:00
4b56259b98 fix change game card -> game sheet 2024-03-18 20:14:52 -05:00
16b1402c6f re-add margin for panels of panels 2024-03-18 20:14:24 -05:00
e4f6576847 more fixes for flag set vs. array 2024-03-18 20:13:40 -05:00
2df02e7452 auth fix 2024-03-18 20:12:41 -05:00
99d376af4c re-enable 404 catchall 2024-03-18 20:12:12 -05:00
2b497a0227 fix recent, upcoming date picking 2024-03-18 20:11:52 -05:00
00b270e0f6 fix event sheet (flags using set, not array) 2024-03-18 20:10:54 -05:00
2a2eb07823 cleanup some auth stuff 2024-03-17 10:46:18 -05:00
39e6c2b5af Merge branch 'main' into docker 2024-03-15 19:14:01 -05:00
832fb654ec visual refinements for multi lineups 2024-03-15 19:09:17 -05:00
43 changed files with 2528 additions and 1620 deletions

20
.vscode/launch.json vendored
View File

@@ -4,10 +4,22 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{
"name": "Attach",
"port": 9229,
"request": "attach",
"restart": true,
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"localRoot": "${workspaceFolder}/src",
"remoteRoot": "/home/node/app/src"
},
{ {
"console": "integratedTerminal", "console": "integratedTerminal",
"internalConsoleOptions": "neverOpen", "internalConsoleOptions": "neverOpen",
"name": "nodemon", "name": "nodemon (dev)",
"program": "dev", "program": "dev",
"request": "launch", "request": "launch",
"restart": true, "restart": true,
@@ -16,8 +28,8 @@
"<node_internals>/**" "<node_internals>/**"
], ],
"type": "node", "type": "node",
"env": {"NODE_ENV": "development"}, "env": {"NODE_ENV": "development", "DEBUG": "app"},
"preLaunchTask": "npm: scss" "preLaunchTask": "npm: build-css"
} }
] ]
} }

16
.vscode/tasks.json vendored
View File

@@ -3,17 +3,21 @@
"tasks": [ "tasks": [
{ {
"type": "npm", "type": "npm",
"script": "scss", "script": "watch-scss",
"problemMatcher": [], "problemMatcher": [],
"label": "npm: scss watch", "label": "npm: watch-scss",
"detail": "sass --watch src/scss/application.scss public/css/application.css src/scss/eventsheet.scss:public/css/eventsheet.css" "detail": "npm run watch-css",
"icon": {
"id": "eye",
"color": "terminal.ansiBlue"
}
}, },
{ {
"type": "npm", "type": "npm",
"script": "scss", "script": "build-css",
"problemMatcher": [], "problemMatcher": [],
"label": "npm: scss", "label": "npm: build-css",
"detail": "sass src/scss/application.scss:public/css/application.css src/scss/eventsheet.scss:public/css/eventsheet.css" "detail": "npm build-css"
} }
] ]
} }

21
bin/www
View File

@@ -10,8 +10,7 @@ var https = require("https");
var fs = require("fs"); var fs = require("fs");
var debug = require("debug")("https"); var debug = require("debug")("https");
const path = require("path"); const path = require("path");
var livereload = require("livereload");
/** /**
* Get port from environment and store in Express. * Get port from environment and store in Express.
@@ -19,30 +18,16 @@ const path = require("path");
var port = normalizePort(process.env.PORT || "3000"); var port = normalizePort(process.env.PORT || "3000");
app.set("port", port); app.set("port", port);
var server = http.createServer(app);
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
/** const liveReloadServer = livereload.createServer({port:35729});
* Create HTTPS server.
*/
const https_options = {
key: fs.readFileSync("certs/key.pem"),
cert: fs.readFileSync("certs/cert.pem"),
};
var livereload = require("livereload");
const liveReloadServer = livereload.createServer({https: https_options});
liveReloadServer.watch(path.join(__dirname, "../src/views")); liveReloadServer.watch(path.join(__dirname, "../src/views"));
liveReloadServer.server.once("connection", () => { liveReloadServer.server.once("connection", () => {
setTimeout(() => { setTimeout(() => {
liveReloadServer.refresh("/"); liveReloadServer.refresh("/");
}, 100); }, 100);
}); });
var server = https.createServer(https_options, app);
}
else {
var server = http.createServer(app);
} }
/** /**

View File

@@ -1,3 +1,26 @@
{
{$LOG_LEVEL} # Set via environment variable
}
localhost {
# Development configuration
@notProd {
expression {env.ENVIRONMENT} == 'development'
}
handle @notProd {
# Configuration that only applies when not in production
reverse_proxy app-dev:3000
}
}
{$DOMAIN} { {$DOMAIN} {
reverse_proxy app:3000 # Production configuration
@prod {
expression {env.ENVIRONMENT} == 'production'
}
handle @prod {
# Configuration that only applies in production
# header Strict-Transport-Security "max-age=31536000;"
reverse_proxy app:3000
}
} }

View File

@@ -1,14 +1,32 @@
version: "3.3"
services: services:
app: app: &app
env_file: env_file:
- .env - .env
build: . build: .
networks: networks:
- web - web
profiles:
- production
expose: expose:
- 3000 - 3000
app-dev:
<<: *app
ports:
- 9229:9229 #debugger
- 35729:35729 #livereload
profiles:
- development
command: npm run dev
volumes:
- ./src:/home/node/app/src
- ./package.json:/home/node/app/package.json
- ./package-lock.json:/home/node/app/package-lock.json
- ./certs:/home/node/app/certs
- ./bin/www:/home/node/app/bin/www
environment:
DEBUG: "app"
NODE_ENV: "development"
caddy: caddy:
image: caddy image: caddy
@@ -17,10 +35,16 @@ services:
- 443:443 - 443:443
volumes: volumes:
- ./caddy/Caddyfile:/etc/caddy/Caddyfile - ./caddy/Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
networks: networks:
- web - web
env_file: env_file:
- .env - .env
networks: networks:
web: web:
volumes:
caddy_data:
caddy_config:

71
package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@teamsnap/teamsnap-ui": "^3.12.3", "@teamsnap/teamsnap-ui": "^3.12.3",
"better-sqlite3": "^9.4.1", "better-sqlite3": "^9.6.0",
"better-sqlite3-session-store": "^0.1.0", "better-sqlite3-session-store": "^0.1.0",
"bootstrap": "^5.3.1", "bootstrap": "^5.3.1",
"bootstrap-icons": "^1.10.5", "bootstrap-icons": "^1.10.5",
@@ -33,6 +33,7 @@
"passport-teamsnap": "^1.1.1", "passport-teamsnap": "^1.1.1",
"pluralize": "^8.0.0", "pluralize": "^8.0.0",
"pug": "^3.0.2", "pug": "^3.0.2",
"sass": "^1.77.2",
"sortablejs": "^1.15.0", "sortablejs": "^1.15.0",
"teamsnap.js": "github:anthonyscorrea/teamsnap-javascript-sdk#link-with-null-link", "teamsnap.js": "github:anthonyscorrea/teamsnap-javascript-sdk#link-with-null-link",
"tinymce": "^6.8.3", "tinymce": "^6.8.3",
@@ -663,7 +664,6 @@
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"dependencies": { "dependencies": {
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
"picomatch": "^2.0.4" "picomatch": "^2.0.4"
@@ -813,9 +813,9 @@
} }
}, },
"node_modules/better-sqlite3": { "node_modules/better-sqlite3": {
"version": "9.4.1", "version": "9.6.0",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.4.1.tgz", "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.6.0.tgz",
"integrity": "sha512-QpqiQeMI4WkE+dQ68zTMX5OzlPGc7lXIDP1iKUt4Omt9PdaVgzKYxHIJRIzt1E+RUBQoFmkip/IbvzyrxehAIg==", "integrity": "sha512-yR5HATnqeYNVnkaUTf4bOP2dJSnyhP4puJN/QPRyx4YkBEEUxib422n2XzPqDEHjQQqazoYoADdAm5vE15+dAQ==",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"bindings": "^1.5.0", "bindings": "^1.5.0",
@@ -834,7 +834,6 @@
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@@ -997,7 +996,6 @@
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"dependencies": { "dependencies": {
"fill-range": "^7.0.1" "fill-range": "^7.0.1"
}, },
@@ -1169,7 +1167,6 @@
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"dependencies": { "dependencies": {
"anymatch": "~3.1.2", "anymatch": "~3.1.2",
"braces": "~3.0.2", "braces": "~3.0.2",
@@ -1618,9 +1615,9 @@
} }
}, },
"node_modules/detect-libc": { "node_modules/detect-libc": {
"version": "2.0.2", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
"integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@@ -2040,7 +2037,6 @@
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
}, },
@@ -2195,7 +2191,6 @@
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"optional": true, "optional": true,
"os": [ "os": [
@@ -2304,7 +2299,6 @@
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": { "dependencies": {
"is-glob": "^4.0.1" "is-glob": "^4.0.1"
}, },
@@ -2564,6 +2558,11 @@
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
"dev": true "dev": true
}, },
"node_modules/immutable": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz",
"integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ=="
},
"node_modules/inherits": { "node_modules/inherits": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
@@ -2632,7 +2631,6 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"dependencies": { "dependencies": {
"binary-extensions": "^2.0.0" "binary-extensions": "^2.0.0"
}, },
@@ -2704,7 +2702,6 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -2722,7 +2719,6 @@
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"dependencies": { "dependencies": {
"is-extglob": "^2.1.1" "is-extglob": "^2.1.1"
}, },
@@ -2745,7 +2741,6 @@
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"engines": { "engines": {
"node": ">=0.12.0" "node": ">=0.12.0"
} }
@@ -3270,9 +3265,9 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
}, },
"node_modules/node-abi": { "node_modules/node-abi": {
"version": "3.54.0", "version": "3.62.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.54.0.tgz", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.62.0.tgz",
"integrity": "sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==", "integrity": "sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==",
"dependencies": { "dependencies": {
"semver": "^7.3.5" "semver": "^7.3.5"
}, },
@@ -3373,7 +3368,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -3667,7 +3661,6 @@
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"engines": { "engines": {
"node": ">=8.6" "node": ">=8.6"
}, },
@@ -3719,9 +3712,9 @@
} }
}, },
"node_modules/prebuild-install": { "node_modules/prebuild-install": {
"version": "7.1.1", "version": "7.1.2",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz",
"integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==",
"dependencies": { "dependencies": {
"detect-libc": "^2.0.0", "detect-libc": "^2.0.0",
"expand-template": "^2.0.3", "expand-template": "^2.0.3",
@@ -4078,7 +4071,6 @@
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"dependencies": { "dependencies": {
"picomatch": "^2.2.1" "picomatch": "^2.2.1"
}, },
@@ -4228,6 +4220,22 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
}, },
"node_modules/sass": {
"version": "1.77.2",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.77.2.tgz",
"integrity": "sha512-eb4GZt1C3avsX3heBNlrc7I09nyT00IUuo4eFhAbeXWU2fvA7oXI53SxODVAA+zgZCk9aunAZgO+losjR3fAwA==",
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/semver": { "node_modules/semver": {
"version": "7.6.0", "version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
@@ -4466,6 +4474,14 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/source-map-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": { "node_modules/source-map-support": {
"version": "0.5.21", "version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
@@ -4735,7 +4751,6 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"dependencies": { "dependencies": {
"is-number": "^7.0.0" "is-number": "^7.0.0"
}, },

View File

@@ -24,13 +24,17 @@
}, },
"scripts": { "scripts": {
"start": "node ./bin/www", "start": "node ./bin/www",
"dev": "nodemon . & npm run scss", "dev": "nodemon --inspect=0.0.0.0 ./bin/www",
"scss": "sass src/scss/application.scss:src/public/css/application.css src/scss/eventsheet.scss:src/public/css/eventsheet.css", "build-css": "sass src/scss:src/public/css",
"scss watch": "sass --watch src/scss/application.scss:src/public/css/application.css src/scss/eventsheet.scss:src/public/css/eventsheet.css" "watch-scss": "nodemon -e scss -x \"npm run build-css\""
},
"nodemonConfig": {
"ext": "js,hbs,scss",
"watch": ["src"]
}, },
"dependencies": { "dependencies": {
"@teamsnap/teamsnap-ui": "^3.12.3", "@teamsnap/teamsnap-ui": "^3.12.3",
"better-sqlite3": "^9.4.1", "better-sqlite3": "^9.6.0",
"better-sqlite3-session-store": "^0.1.0", "better-sqlite3-session-store": "^0.1.0",
"bootstrap": "^5.3.1", "bootstrap": "^5.3.1",
"bootstrap-icons": "^1.10.5", "bootstrap-icons": "^1.10.5",
@@ -54,6 +58,7 @@
"passport-teamsnap": "^1.1.1", "passport-teamsnap": "^1.1.1",
"pluralize": "^8.0.0", "pluralize": "^8.0.0",
"pug": "^3.0.2", "pug": "^3.0.2",
"sass": "^1.77.2",
"sortablejs": "^1.15.0", "sortablejs": "^1.15.0",
"teamsnap.js": "github:anthonyscorrea/teamsnap-javascript-sdk#link-with-null-link", "teamsnap.js": "github:anthonyscorrea/teamsnap-javascript-sdk#link-with-null-link",
"tinymce": "^6.8.3", "tinymce": "^6.8.3",

View File

@@ -62,12 +62,14 @@ app.set("view engine", "hbs");
app.locals.pluralize = require("pluralize"); app.locals.pluralize = require("pluralize");
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
console.log('adding connectLiveReload')
var connectLiveReload = require("connect-livereload"); var connectLiveReload = require("connect-livereload");
app.use(connectLiveReload()); app.use("/scss", express.static(path.join(__dirname, "scss")));
app.use(connectLiveReload({port: 35729, src:"http://localhost:35729/livereload.js?snipver=1"}));
} }
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(bodyParser.urlencoded()); app.use(bodyParser.urlencoded({extended: true }));
app.use(logger("dev")); app.use(logger("dev"));
app.use(cors(corsOptions)) app.use(cors(corsOptions))
app.use(cookieParser()); app.use(cookieParser());
@@ -118,8 +120,10 @@ app.use(
intervalMs: 900000 //ms = 15min intervalMs: 900000 //ms = 15min
} }
}), }),
cookie: { maxAge: 86400000 }, // value of maxAge is defined in milliseconds.
teamsnap_token: "", teamsnap_token: "",
current_team: "", current_team: "",
csrfToken:"",
secret: process.env['SECRET'], secret: process.env['SECRET'],
resave: false, // don't save session if unmodified resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored saveUninitialized: false, // don't create session until something stored
@@ -169,9 +173,10 @@ app.use(function (err, req, res, next) {
}); });
// catch 404 and forward to error handler // catch 404 and forward to error handler
// app.use(function (req, res, next) { app.use(function (req, res, next) {
// next(createError(404)); // next(createError(404));
// }); res.status(404).send('not found')
});
app.set('trust proxy') app.set('trust proxy')

View File

@@ -21,6 +21,10 @@ exports.helpers = {
exports.partials = path.join(__dirname, "../views/event/partials") exports.partials = path.join(__dirname, "../views/event/partials")
exports.confirmModalAvailabilityReminders = async (req, res) => {
res.status(200).render("event/partials/modal_availability_reminders")
}
exports.getEvents = async (req, res, next) => { exports.getEvents = async (req, res, next) => {
const {user, team, layout} = req const {user, team, layout} = req
const bulkLoadTypes = ["event", "availabilitySummary"] const bulkLoadTypes = ["event", "availabilitySummary"]
@@ -67,3 +71,53 @@ exports.getEvent = async (req, res, next) => {
}; };
res.render("event/show", context); res.render("event/show", context);
}; };
exports.sendAvailabilityReminders = async (req,res,next) => {
await Promise.all(req.promises)
if (!req.body || ! (req.body.event_id && req.body.memberIds)) {
res.status(400).send('Malformed post')
}
if (req.params.event_id != req.body.eventId) {
// Load actual event. Do I want this to be an error? probably
res.status(400).send('Event ID parameter does not match the POST body')
return;
}
const {event} = req
const {eventId, memberIds} = req.body
const sendingMember = req.members.find(m=>m.userId==req.user.id)
try {
const promise = teamsnap.sendAvailabilityReminders(event, sendingMember, memberIds)
await promise
.then (res.status(200).send('OK'))
} catch (err) {
res.status(500).send()
}
return
}
exports.submitResetAvailabilities = async (req,res,next) => {
await Promise.all(req.promises)
if (!req.body || ! (req.body.event_id && req.body.memberIds)) {
res.status(400).send('Malformed post')
}
if (req.params.event_id != req.body.event_id) {
// Load actual event. Do I want this to be an error? probably
res.status(400).send('Event ID parameter does not match the POST body');
return
}
const {event_id, memberIds} = req.body
const reset_promises = []
const availabilities = await teamsnap.loadAvailabilities({eventId: event_id}, teamsnapCallback);
availabilities.filter(availability =>memberIds.includes(availability.memberId.toString())).forEach( availability => {
availability.statusCode = teamsnap.AVAILABILITIES.NONE
const promise = teamsnap.saveAvailability(availability, teamsnapCallback)
reset_promises.push(promise)
})
await Promise.all(reset_promises)
.then(res.status(200).send('OK'))
}

View File

@@ -1,99 +1,11 @@
const path = require('path') const path = require('path')
const fs = require('fs') const fs = require('fs')
const {embeddedSvgFromPath, parsePositionLabel, compilePositionLabel} = require("../lib/utils") const {groupTeamsnapItems, parsePositionLabel, compilePositionLabel, teamsnapCallback} = require("../lib/utils")
const tsUtils = require('../lib/utils') const tsUtils = require('../lib/utils')
const { loadEventLineupEntries } = require('teamsnap.js') const { loadEventLineupEntries } = require('teamsnap.js')
exports.partials = path.join(__dirname, "../views/eventlineup/partials") exports.partials = path.join(__dirname, "../views/eventlineup/partials")
exports.helpers = require('../helpers/eventlineup.js')
const statusCodeIcons = {
1: embeddedSvgFromPath("/teamsnap-ui/assets/icons/check.svg"),
0: embeddedSvgFromPath("/teamsnap-ui/assets/icons/dismiss.svg"),
2: embeddedSvgFromPath("/bootstrap-icons/question-lg.svg"),
null: embeddedSvgFromPath("/bootstrap-icons/question.svg"),
undefined: embeddedSvgFromPath("/bootstrap-icons/question-lg.svg")
}
exports.helpers = {
flagsString: (flags) => {
return flags != null ? Array.from(flags).join(",") : ''
},
plus1: (i) => Number(i)+1,
positions: () => ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "EH", "DH", "DR"],
defense_positions: () => ["C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "P"],
avail_status_code_icon: (status_code) => {
const icon_classes = {
1: "u-colorPositive",
0: "u-colorNegative",
2: "u-colorPrimary",
null: "u-colorGrey",
undefined: "u-colorGrey"
}
const button_classes = {
1: "Button--yes",
0: "Button--no",
2: "Button--maybe",
null: "",
undefined: ""
}
return `<button class="Button Button--smallSquare ${button_classes[status_code]}" type="button"><span class="">${statusCodeIcons[status_code]}</span></button>`
},
positionLabelWithoutFlags: (label) => {
const {positionLabelWithoutFlags} = parsePositionLabel(label);
return positionLabelWithoutFlags
},
positionLabelWithoutPOFlag: (label) => {
const {positionLabelWithoutFlags, positionFlags} = parsePositionLabel(label);
positionFlags.delete('PO')
return compilePositionLabel(positionLabelWithoutFlags, positionFlags)
},
positionFlags: (label)=> {
const {positionFlags} = parsePositionLabel(label);
return `[${Array.from(positionFlags).join(",")}]`
},
hasPositionFlags: (label) => {
const {positionLabelWithoutFlags, positionFlags} = parsePositionLabel(label);
return positionFlags.size > 0;
},
comparePositionWithFlags: (labelWithoutFlags, eventLineupEntry, options) => {
labelWithFlags = eventLineupEntry?.label
const {positionLabelWithoutFlags} = parsePositionLabel(labelWithFlags);
return positionLabelWithoutFlags == labelWithoutFlags;
},
isStarting: (member) => {
return (member.benchcoach?.eventLineupEntry != null);
},
isInStartingLineup: (member) => {
if (member.benchcoach.eventLineupEntry == null || member.benchcoach.eventLineupEntry.label == '') return false;
const {positionFlags} = parsePositionLabel(member.benchcoach.eventLineupEntry?.label);
return (!positionFlags.has("PO"))
},
isInPositionOnly: (member) => {
if (!member.benchcoach || member.benchcoach.eventLineupEntry == null) return false;
const {positionFlags} = parsePositionLabel(member.benchcoach.eventLineupEntry?.label);
return (member.benchcoach.eventLineupEntry != null && positionFlags.has("PO"))
},
isInBench: (member) => {
if ((member.benchcoach.eventLineupEntry != null && member.benchcoach.eventLineupEntry.label != '') || member.isNonPlayer) return false;
return (member.benchcoach.availability?.statusCode != 0 && member.benchcoach.availability?.statusCode != null)
},
isInOut: (member) => {
if ((member.benchcoach.eventLineupEntry != null && member.benchcoach.eventLineupEntry.label != '') || member.isNonPlayer) return false;
return (member.benchcoach.availability?.statusCode == 0 || member.benchcoach.availability?.statusCode == null)
},
availabilityStatusShort: (availability) => {
const {YES, MAYBE, NO, NONE} = teamsnap.AVAILABILITIES
const statusShortLookup = {}
statusShortLookup[YES] = "YES"
statusShortLookup[MAYBE] = "MAY"
statusShortLookup[NO] = "NO"
statusShortLookup[NONE] = "UNK"
statusShortLookup[undefined] = "UNK"
return (statusShortLookup[availability?.statusCode])
}
}
exports.getEventLineup = async (req, res)=>{ exports.getEventLineup = async (req, res)=>{
await Promise.all(req.promises) await Promise.all(req.promises)
@@ -105,7 +17,7 @@ exports.getEventLineup = async (req, res)=>{
"/js/eventlineup.js", "/js/eventlineup.js",
"/js/tinymce.min.js" "/js/tinymce.min.js"
] ]
res.render("eventlineup/edit", {user, team, members, event, scripts, layout, event_lineup, event_lineup_entries, availabilitySummary, csrfToken}) res.render("eventlineup/edit", {user, team, members, event, availabilities, scripts, layout, event_lineup, event_lineup_entries, availabilitySummary, csrfToken})
} }
exports.getAdjacentEventLineup = async (req, res) => { exports.getAdjacentEventLineup = async (req, res) => {
@@ -121,15 +33,19 @@ exports.getAdjacentEventLineup = async (req, res) => {
} else { } else {
throw new Error('Index must be positive or negative number') throw new Error('Index must be positive or negative number')
} }
if (!event) {
res.status(500).send()
return
}
const availabilitySummary = event.availabilitySummary const availabilitySummary = event.availabilitySummary
const event_lineup = req.timeline.event_lineups.find(i=>i.eventId==event.id) const event_lineup = req.timeline.event_lineups?.find(i=>i.eventId==event.id)
const event_lineup_entries = req.timeline.event_lineup_entries.filter(i=>i.eventId==event.id) const event_lineup_entries = req.timeline.event_lineup_entries?.filter(i=>i.eventId==event.id)
const availabilities = req.timeline.availabilities.filter(i=>i.eventId==event.id) const availabilities = req.timeline.availabilities.filter(i=>i.eventId==event.id)
attachBenchcoachPropertiesToMember(members, event_lineup_entries, availabilities) attachBenchcoachPropertiesToMember(members, event_lineup_entries, availabilities)
members.sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName) members.sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName)
console.log() console.log()
// res.status(200).send('Received')
res.render("eventlineup/edit", {user, team, members, event, layout: null, event_lineup, event_lineup_entries, availabilitySummary, csrfToken}) res.render("eventlineup/edit", {user, team, members, event, layout: null, event_lineup, event_lineup_entries, availabilitySummary, availabilities, csrfToken})
} }
attachBenchcoachPropertiesToMember = (members, event_lineup_entries, availabilities) => { attachBenchcoachPropertiesToMember = (members, event_lineup_entries, availabilities) => {
@@ -142,7 +58,7 @@ attachBenchcoachPropertiesToMember = (members, event_lineup_entries, availabilit
// as far as I can tell, member_name should consistently be formulated from first and last name // as far as I can tell, member_name should consistently be formulated from first and last name
// perhaps could have some edge cases if first or last names change, but this *should be* exceedingly rare. // perhaps could have some edge cases if first or last names change, but this *should be* exceedingly rare.
const member_name = `${member.firstName} ${member.lastName}` const member_name = `${member.firstName} ${member.lastName}`
const event_lineup_entry = event_lineup_entries.find(e=> e.memberId == member.id || e.memberName == member_name) const event_lineup_entry = event_lineup_entries?.find(e=> e.memberId == member.id || e.memberName == member_name)
const availability = availabilities.find(e=>e.memberId == member.id) const availability = availabilities.find(e=>e.memberId == member.id)
member.benchcoach.availability = availability member.benchcoach.availability = availability
if (event_lineup_entry != null) { if (event_lineup_entry != null) {
@@ -172,6 +88,10 @@ exports.getEventLineupEmail = async (req, res)=>{
res.status(200).render("eventlineup/partials/email_modal.hbs", {layout:null, user, team, members, event, event_lineup, event_lineup_entries: newEventLineupEntries, availabilities, availabilitySummary}) res.status(200).render("eventlineup/partials/email_modal.hbs", {layout:null, user, team, members, event, event_lineup, event_lineup_entries: newEventLineupEntries, availabilities, availabilitySummary})
} }
exports.getAvailabilityRemindersModal = (req, res) => {
res.status(200).render("eventlineup/partials/availability_reminder_modal.hbs")
}
exports.getEventLineupEntries = async (req, res)=>{ exports.getEventLineupEntries = async (req, res)=>{
const {event_lineup, event_lineup_entries} = req const {event_lineup, event_lineup_entries} = req
res.setHeader('Content-Type', 'application/json').send(JSON.stringify(req.event_lineup_entries)) res.setHeader('Content-Type', 'application/json').send(JSON.stringify(req.event_lineup_entries))
@@ -189,14 +109,20 @@ exports.postEventLineup = async (req,res) => {
const eventLineupEntries = req.event_lineup.eventLineupEntries const eventLineupEntries = req.event_lineup.eventLineupEntries
const {newEventLineupEntries, deleteEventLineupEntries} = processPostedEventLineupEntries(body, eventLineupEntries, req.event_lineup) const {newEventLineupEntries, deleteEventLineupEntries} = processPostedEventLineupEntries(body, eventLineupEntries, req.event_lineup)
newEventLineupEntries.forEach(e=>{ newEventLineupEntries.forEach(e=>{
teamsnap.saveEventLineupEntry(e) teamsnap.saveEventLineupEntry(e, teamsnapCallback)
}) })
deleteEventLineupEntries.forEach(e=>{ deleteEventLineupEntries.forEach(e=>{
teamsnap.deleteEventLineupEntry(e) teamsnap.deleteEventLineupEntry(e, teamsnapCallback)
}) })
eventLineup = await teamsnap.loadEventLineups(req.params.event_id) const bulk_items = await teamsnap.bulkLoad(
res.status(201).end() {teamId: req.params.team_id, types: ['eventLineup', 'eventLineupEntry'], scopeTo:'event', event__id:req.params.event_id,},
null,
(err, items) => {teamsnapCallback(err, items, {req, source:"postEventLineup", method:'bulkLoad'})}
)
groupedReturnedItems = groupTeamsnapItems(bulk_items)
returnedEventLineupEntries = groupedReturnedItems.eventLineupEntries
res.status(201).end(JSON.stringify(returnedEventLineupEntries))
} }
const processPostedEventLineupEntries = (body, eventLineupEntries, eventLineup) => { const processPostedEventLineupEntries = (body, eventLineupEntries, eventLineup) => {
@@ -210,10 +136,14 @@ const processPostedEventLineupEntries = (body, eventLineupEntries, eventLineup)
const lineupEntryFlags = body.flags[i] const lineupEntryFlags = body.flags[i]
if (lineupEntryId != '' && lineupEntryLabel != '') { if (lineupEntryId != '' && lineupEntryLabel != '') {
// Update lineup entry // Update lineup entry
const eventLineupEntry = eventLineupEntries.find((e)=>e.id==Number(lineupEntryId)) try {
eventLineupEntry.sequence = lineupEntrySequence const eventLineupEntry = eventLineupEntries.find((e)=>e.id==Number(lineupEntryId))
eventLineupEntry.label = compilePositionLabel(lineupEntryLabel, lineupEntryFlags) eventLineupEntry.sequence = lineupEntrySequence
newEventLineupEntries.push(eventLineupEntry) eventLineupEntry.label = compilePositionLabel(lineupEntryLabel, lineupEntryFlags)
newEventLineupEntries.push(eventLineupEntry)
} catch {
console.log
}
} }
else if (lineupEntryId != '') { else if (lineupEntryId != '') {
// Delete lineup entry // Delete lineup entry
@@ -234,4 +164,33 @@ const processPostedEventLineupEntries = (body, eventLineupEntries, eventLineup)
} }
}) })
return {newEventLineupEntries, eventLineupEntries, deleteEventLineupEntries} return {newEventLineupEntries, eventLineupEntries, deleteEventLineupEntries}
}
exports.submitDeleteEventLineupEntries = async (req,res) => {
await Promise.all(req.promises);
const {event_lineup, event_lineup_entries} = req
let event_id
let memberIds
if (!req.body || ! (req.body.event_id && req.body.memberIds)) {
res.status(400).send('Malformed post')
} else if (req.params.event_id != req.body.event_id) {
// Load actual event. Do I want this to be an error? probably
res.status(400).send('Event ID parameter does not match the POST body');
return
} else {
event_id = req.body.event_id
memberIds = req.body.memberIds
}
const deletion_promises = []
event_lineup_entries.filter(entry =>memberIds.includes(entry.memberId.toString())).forEach( entry => {
const promise = teamsnap.deleteEventLineupEntry(entry, teamsnapCallback)
deletion_promises.push(promise)
})
await Promise.all(deletion_promises)
.then(res.status(202).send('OK'))
} }

View File

@@ -23,8 +23,14 @@ exports.getEventSheet = async (req,res) =>{
) )
) )
await Promise.all(req.promises) await Promise.all(req.promises)
const {sheet_size, sheet_layout} = req.query
const {user, team, team_preferences, members, event, event_lineup, event_lineup_entries, availabilities, availabilitySummary, timeline, recent_events, opponent_logo, upcoming_events} = req const {user, team, team_preferences, members, event, event_lineup, event_lineup_entries, availabilities, availabilitySummary, timeline, recent_events, opponent_logo, upcoming_events} = req
res.render('eventsheet/sheet', {user, team, team_preferences, members, event, event_lineup, event_lineup_entries, availabilities, availabilitySummary, timeline, recent_events, opponent_logo,upcoming_events}) res.render('eventsheet/sheet', {sheet_size, sheet_layout, user, team, team_preferences, members, event, event_lineup, event_lineup_entries, availabilities, availabilitySummary, timeline, recent_events, opponent_logo,upcoming_events})
}
exports.getEventSheetBlank = (req,res) => {
res.render('eventsheet/sheet_blank')
} }
exports.getLineupCard = (req, res, next) => { exports.getLineupCard = (req, res, next) => {
@@ -82,7 +88,7 @@ exports.getLineupCard = (req, res, next) => {
availabilities: items.filter((i) => i.type == "availability").sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName), availabilities: items.filter((i) => i.type == "availability").sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName),
all_lineup_entries: items.filter((i) => i.type == "eventLineupEntry"), all_lineup_entries: items.filter((i) => i.type == "eventLineupEntry"),
event_lineup_entries_offense: items event_lineup_entries_offense: items
.filter((i) => i.type == "eventLineupEntry" && i.eventId == event_id && !i.label.includes("[PO]")) .filter((i) => i.type == "eventLineupEntry" && i.eventId == event_id && !i.label.has("[PO]"))
.sort((a, b) => a.sequence - b.sequence), .sort((a, b) => a.sequence - b.sequence),
event_lineup_entries: items event_lineup_entries: items
.filter((i) => i.type == "eventLineupEntry" && i.eventId == event_id) .filter((i) => i.type == "eventLineupEntry" && i.eventId == event_id)

119
src/helpers/eventlineup.js Normal file
View File

@@ -0,0 +1,119 @@
const {embeddedSvgFromPath, parsePositionLabel, compilePositionLabel} = require("../lib/utils")
var hb = require('hbs').create();
const statusCodeIcons = {
1: embeddedSvgFromPath("/teamsnap-ui/assets/icons/check.svg"),
0: embeddedSvgFromPath("/teamsnap-ui/assets/icons/dismiss.svg"),
2: embeddedSvgFromPath("/bootstrap-icons/question-lg.svg"),
null: embeddedSvgFromPath("/bootstrap-icons/question.svg"),
undefined: embeddedSvgFromPath("/bootstrap-icons/question-lg.svg")
}
const statusCodeClasses = {
1: "u-colorPositive",
0: "u-colorNegative",
2: "u-colorPrimary",
null: "u-colorGrey",
undefined: "u-colorGrey"
}
const statusCodeButtonClasses = {
1: "Button--yes",
0: "Button--no",
2: "Button--maybe",
null: "",
undefined: ""
}
exports.flagsString = (flags) => {
return flags != null ? Array.from(flags).join(",") : ''
};
exports.plus1 = (i) => Number(i)+1;
exports.positions = () => ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "EH", "DH", "DR"];
exports.defense_positions = () => ["C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "P"];
exports.avail_status_code_class = (status_code) => statusCodeButtonClasses[status_code];
exports.avail_status_code_icon = (status_code) => statusCodeIcons[status_code];
exports.positionLabelWithoutFlags = (label) => {
const {positionLabelWithoutFlags} = parsePositionLabel(label);
return positionLabelWithoutFlags
};
exports.positionLabelWithoutPOFlag = (label) => {
const {positionLabelWithoutFlags, positionFlags} = parsePositionLabel(label);
positionFlags.delete('PO')
return compilePositionLabel(positionLabelWithoutFlags, positionFlags)
};
exports.positionFlags = (label)=> {
const {positionFlags} = parsePositionLabel(label);
return `[${Array.from(positionFlags).join(",")}]`
};
exports.hasPositionFlags = (label) => {
const {positionLabelWithoutFlags, positionFlags} = parsePositionLabel(label);
return positionFlags.size > 0;
};
exports.comparePositionWithFlags = (labelWithoutFlags, eventLineupEntry, options) => {
labelWithFlags = eventLineupEntry?.label
const {positionLabelWithoutFlags} = parsePositionLabel(labelWithFlags);
return positionLabelWithoutFlags == labelWithoutFlags;
};
exports.isStarting = (member) => {
return (member.benchcoach?.eventLineupEntry != null);
};
exports.isInStartingLineup = (member) => {
if (member.benchcoach.eventLineupEntry == null || member.benchcoach.eventLineupEntry.label == '') return false;
const {positionFlags} = parsePositionLabel(member.benchcoach.eventLineupEntry?.label);
return (!positionFlags.has("PO"))
};
exports.isInPositionOnly = (member) => {
if (!member.benchcoach || member.benchcoach.eventLineupEntry == null) return false;
const {positionFlags} = parsePositionLabel(member.benchcoach.eventLineupEntry?.label);
return (member.benchcoach.eventLineupEntry != null && positionFlags.has("PO"))
};
exports.isInBench = (member) => {
if ((member.benchcoach.eventLineupEntry != null && member.benchcoach.eventLineupEntry.label != '') || member.isNonPlayer) return false;
return (member.benchcoach.availability?.statusCode != 0 && member.benchcoach.availability?.statusCode != null)
};
exports. isInOut = (member) => {
if ((member.benchcoach.eventLineupEntry != null && member.benchcoach.eventLineupEntry.label != '') || member.isNonPlayer) return false;
return (member.benchcoach.availability?.statusCode == 0 || member.benchcoach.availability?.statusCode == null)
};
exports.availabilityStatusShort = (availability) => {
const {YES, MAYBE, NO, NONE} = teamsnap.AVAILABILITIES
const statusShortLookup = {}
statusShortLookup[YES] = "YES"
statusShortLookup[MAYBE] = "MAY"
statusShortLookup[NO] = "NO"
statusShortLookup[NONE] = "UNK"
statusShortLookup[undefined] = "UNK"
return (statusShortLookup[availability?.statusCode])
};
exports.filterNonPlayers = (members) => {
return members.filter(m=>!m.isNonPlayer)
};
exports.joinMemberEmailAddresses = (members) => {
return members.map(m=>m.emailAddresses.join(',')).join(',')
}
exports.loadSlots = (options) =>{
var s = ""
const {members, event_lineup, event_lineup_entries, event, availabilities} = options.data.root
event_lineup_entries.forEach(eventLineupEntry =>{
const availability = availabilities?.find(a=>a.memberId==eventLineupEntry.memberId)
const member = members.find(m=>m.id==eventLineupEntry.memberId)
const {positionFlags} = parsePositionLabel(eventLineupEntry.label)
const initial_lineup_segment = `${positionFlags.has('PO') ? 'position-only' : 'starting'}`
s+=options.fn({eventLineupEntry, availability, member, event, initial_lineup_segment})
})
const players_without_lineup_entry = members.filter(
member=>!event_lineup_entries.map(lue=>lue.memberId).includes(member.id) && !member.isNonPlayer
)
players_without_lineup_entry.forEach(member =>{
const availability = availabilities?.find(a=>a.memberId==member.id)
let initial_lineup_segment
if (availability?.statusCode == 0 || availability?.statusCode == null) {
initial_lineup_segment =`out`
} else {
initial_lineup_segment =`bench`
}
s+=options.fn({availability, member, event, initial_lineup_segment})
})
return s
}

View File

@@ -1,5 +1,6 @@
const { parsePositionLabel, teamsnapMembersSortLineupAvailabilityLastName, teamsnapMembersSortAvailabilityLastName } = require('../lib/utils') const { parsePositionLabel, teamsnapMembersSortLineupAvailabilityLastName, teamsnapMembersSortAvailabilityLastName } = require('../lib/utils')
const {attachBenchcoachPropertiesToMember} = require('../controllers/eventlineup') const {attachBenchcoachPropertiesToMember} = require('../controllers/eventlineup')
const Handlebars = require("handlebars");
exports.offenseLineup = (number_of_slots, event_lineup_entries, members, options) => { exports.offenseLineup = (number_of_slots, event_lineup_entries, members, options) => {
var results = "" var results = ""
@@ -7,7 +8,7 @@ exports.offenseLineup = (number_of_slots, event_lineup_entries, members, options
for (let i = 0; i < number_of_slots; i++){ for (let i = 0; i < number_of_slots; i++){
const event_lineup_entry = event_lineup_entries ? event_lineup_entries[i] : null const event_lineup_entry = event_lineup_entries ? event_lineup_entries[i] : null
if (event_lineup_entry && !parsePositionLabel(event_lineup_entry.label).positionFlags.includes('PO')){ if (event_lineup_entry && !parsePositionLabel(event_lineup_entry.label).positionFlags.has('PO')){
results += options.fn({ results += options.fn({
sequence: event_lineup_entry.sequence, sequence: event_lineup_entry.sequence,
member: members.find(member=> event_lineup_entry.memberId == member.id || event_lineup_entry.memberName == `${member.firstName} ${member.lastName}`), member: members.find(member=> event_lineup_entry.memberId == member.id || event_lineup_entry.memberName == `${member.firstName} ${member.lastName}`),
@@ -60,7 +61,7 @@ exports.rosterHistory = (event, event_lineup_entries, members, availabilities, o
// const {event, event_lineup_entries, members, availabilities} = options.data.root // const {event, event_lineup_entries, members, availabilities} = options.data.root
const players = members.filter(m=>!m.isNonPlayer) const players = members.filter(m=>!m.isNonPlayer)
attachBenchcoachPropertiesToMember(players, event_lineup_entries ? event_lineup_entries.filter(i=>i.eventId==event.id) : [], availabilities.filter(i=>i.eventId==event.id)) attachBenchcoachPropertiesToMember(players, event_lineup_entries ? event_lineup_entries.filter(i=>i.eventId==event.id) : [], availabilities.filter(i=>i.eventId==event.id))
players.sort(teamsnapMembersSortAvailabilityLastName) players.sort(teamsnapMembersSortLineupAvailabilityLastName)
players.forEach(member=>{ players.forEach(member=>{
const {firstName, lastName, jerseyNumber, benchcoach, position, id} = member const {firstName, lastName, jerseyNumber, benchcoach, position, id} = member
@@ -87,6 +88,9 @@ const positionGroups = {
} }
exports.positionCapabilityFor = (member, position, options) => { exports.positionCapabilityFor = (member, position, options) => {
if (!member.position) {
return ""
}
const member_positions = member.position.split(",").map(s=>s.trim()) const member_positions = member.position.split(",").map(s=>s.trim())
const member_position_groups = new Set(member.position.split(",").map(s=>positionGroups[s.trim()])) const member_position_groups = new Set(member.position.split(",").map(s=>positionGroups[s.trim()]))
@@ -112,8 +116,15 @@ exports.repeat = (n, options) => {
exports.loopEvents = (events, options) => { exports.loopEvents = (events, options) => {
var results = ""; var results = "";
events.forEach(event => { if (options.data) {
results += options.fn(event) data = Handlebars.createFrame(options.data);
}
events.forEach((event,i) => {
if (data) {
data.index = i;
}
results += options.fn(event, {data: data })
} }
) )
return results; return results;
@@ -121,6 +132,9 @@ exports.loopEvents = (events, options) => {
exports.timepointForMember = (member, timeline, event, options) => { exports.timepointForMember = (member, timeline, event, options) => {
var results = "" var results = ""
if (options.data) {
data = Handlebars.createFrame(options.data);
}
const availability = timeline.availabilities.find(a=>a.memberId==member.id && a.eventId==event.id) const availability = timeline.availabilities.find(a=>a.memberId==member.id && a.eventId==event.id)
const eventLineupEntry = timeline.event_lineup_entries.find(a=>(a.memberId==member.id || a.memberName == `${member.firstName} ${member.lastName}`) && a.eventId==event.id) const eventLineupEntry = timeline.event_lineup_entries.find(a=>(a.memberId==member.id || a.memberName == `${member.firstName} ${member.lastName}`) && a.eventId==event.id)
var value = "" var value = ""
@@ -128,7 +142,15 @@ exports.timepointForMember = (member, timeline, event, options) => {
value = parsePositionLabel(eventLineupEntry.label).positionLabelWithoutFlags value = parsePositionLabel(eventLineupEntry.label).positionLabelWithoutFlags
} }
else { else {
value = availability.status[0] value = availability?.status[0]
}
return options.fn({availability: availability, eventLineupEntry: eventLineupEntry, value}, {data: data })
}
exports.ifEquals = (testValue, targetValue, options) => {
if (testValue === targetValue) {
return options.fn();
} else {
return '';
} }
return options.fn({availability: availability, eventLineupEntry: eventLineupEntry, value})
} }

View File

@@ -50,17 +50,6 @@ teamsnapMembersSortAvailabilityLastName = (a, b) => {
} }
exports.teamsnapMembersSortAvailabilityLastName = teamsnapMembersSortAvailabilityLastName exports.teamsnapMembersSortAvailabilityLastName = teamsnapMembersSortAvailabilityLastName
exports.initTeamsnap = (req, res, next) => {
if (!teamsnap.isAuthed()) {
teamsnap.init(process.env["TEAMSNAP_CLIENT_ID"]);
teamsnap.auth(req.user.accessToken);
}
teamsnap.loadCollections((err) => {
teamsnap.enablePersistence();
next(req, res, next);
});
};
exports.teamsnapCallback = (err,result, d) => { exports.teamsnapCallback = (err,result, d) => {
if (Array.isArray(result)){ if (Array.isArray(result)){
types = new Set(result.map(i=>i.type)) types = new Set(result.map(i=>i.type))
@@ -95,6 +84,10 @@ const getPluralType = (type) =>{
// is not generated in the lookup. this is a // is not generated in the lookup. this is a
// kludge around that. (specifically availabilitySummary) // kludge around that. (specifically availabilitySummary)
plural = teamsnap.getPluralType(type) || (function() { plural = teamsnap.getPluralType(type) || (function() {
if (type === undefined){
return type
}
switch (type.slice(-1)) { switch (type.slice(-1)) {
case 'y': case 'y':
return type.slice(0, -1) + 'ies'; return type.slice(0, -1) + 'ies';
@@ -120,7 +113,7 @@ exports.groupTeamsnapItems = (items, types = [], params = {}) => {
return result; return result;
} }
exports.embeddedSvgFromPath = (svg_path, additional_classes = "") => { exports.embeddedSvgFromPath = (svg_path, additional_classes, options) => {
const iconStaticPaths = { const iconStaticPaths = {
"/teamsnap-ui/assets":path.join(__dirname, "/../../node_modules/@teamsnap/teamsnap-ui/src/assets"), "/teamsnap-ui/assets":path.join(__dirname, "/../../node_modules/@teamsnap/teamsnap-ui/src/assets"),
"/bootstrap-icons":path.join(__dirname, "/../../node_modules/bootstrap-icons/icons"), "/bootstrap-icons":path.join(__dirname, "/../../node_modules/bootstrap-icons/icons"),
@@ -133,6 +126,8 @@ exports.embeddedSvgFromPath = (svg_path, additional_classes = "") => {
} }
} }
if (!options) {options=additional_classes; additional_classes=''}
const svg = fs.readFileSync(`${svg_path}`, 'utf8'); const svg = fs.readFileSync(`${svg_path}`, 'utf8');
svgRegExWithClass = new RegExp(/<svg(.*)class="(.*?)"(.*)>/) svgRegExWithClass = new RegExp(/<svg(.*)class="(.*?)"(.*)>/)
@@ -167,7 +162,19 @@ exports.compilePositionLabel = (label, flags) => {
return label return label
} }
else { else {
const flags_set = new Set(flags.split(',').map(s=>s.trim())) const flags_set = toFlagsSet(flags)
return `${label} [${Array.from(flags_set).sort().join(',')}]` return `${label} [${Array.from(flags_set).sort().join(',')}]`
} }
}
function toFlagsSet(flags) {
let flags_set
if (typeof(flags) == 'string'){
flags_set = new Set(flags.split(',').map(s=>s.trim()))
} else if (flags.constructor === Array){
flags_set = new Set(flags)
} else if (flags.constructor === Set){
flags_set = flags
}
return flags_set
} }

View File

@@ -1,25 +1,26 @@
exports.loadRecentAndUpcomingEvents = async (req, res, next) => { exports.loadRecentAndUpcomingEvents = async (req, res, next) => {
const {team_id, event_id} = req.params const {team_id, event_id} = req.params
const page_size = req.query.page_size ? Number(req.query.page_size) : 4 const page_size = req.query.page_size ? Number(req.query.page_size) : 4
var subject_date = "" var subject_date
if (event_id) { if (event_id) {
const event = await teamsnap.loadEvents({id: event_id}).pop() const event = await teamsnap.loadEvents({id: event_id}).pop()
subject_date = event.startDate.toISOString().slice(0,10) const new_date = new Date(event.startDate.getTime()+10000);
subject_date = event.startDate
} }
else { else {
subject_date = new Date().toISOString().slice(0,10) subject_date = new Date()
} }
req.promises.push( req.promises.push(
teamsnap.bulkLoad({ teamsnap.bulkLoad({
teamId: team_id, teamId: team_id,
types: ["event", "availabilitySummary"], types: ["event", "availabilitySummary"],
scopeTo: "event", scopeTo: "event",
event__startedAfter: subject_date, event__startedAfter: new Date(subject_date.getTime()+10000),
event__pageSize: page_size + 1 event__pageSize: page_size
}) })
.then(items => tsUtils.groupTeamsnapItems(items)) .then(items => tsUtils.groupTeamsnapItems(items))
.then((items)=>{ .then((items)=>{
req.upcoming_events=items.events ? items.events.slice(1) : []; req.upcoming_events=items.events ? items.events : [];
const availabilitySummaries=items.availabilitySummaries; const availabilitySummaries=items.availabilitySummaries;
req.upcoming_events.forEach((event) => { req.upcoming_events.forEach((event) => {
event.link('availabilitySummary', availabilitySummaries.find(a=>a.eventId==event.id)) event.link('availabilitySummary', availabilitySummaries.find(a=>a.eventId==event.id))
@@ -32,7 +33,7 @@ exports.loadRecentAndUpcomingEvents = async (req, res, next) => {
teamId: team_id, teamId: team_id,
types: ["event", "availabilitySummary"], types: ["event", "availabilitySummary"],
scopeTo: "event", scopeTo: "event",
event__startedBefore: subject_date, event__startedBefore: new Date(subject_date.getTime()-10000),
event__pageSize: page_size, event__pageSize: page_size,
event__sortStartDate: "desc" event__sortStartDate: "desc"
}) })

View File

@@ -1004,21 +1004,21 @@ h6 {
color: #ffffff; color: #ffffff;
} }
.Button--blue { .Button--blue, button:has(+ .position-label-flags :checked) {
background-color: #1A6BAF; background-color: #1A6BAF;
border-color: #15568c; border-color: #15568c;
color: #ffffff; color: #ffffff;
} }
.Button--blue:hover, .Button--blue:active, .Button--blue:focus { .Button--blue:hover, button:hover:has(+ .position-label-flags :checked), .Button--blue:active, button:active:has(+ .position-label-flags :checked), .Button--blue:focus, button:focus:has(+ .position-label-flags :checked) {
background-color: #17609e; background-color: #17609e;
border-color: #134d7e; border-color: #134d7e;
color: #ffffff; color: #ffffff;
} }
.Button--blue.is-active { .Button--blue.is-active, button.is-active:has(+ .position-label-flags :checked) {
background-color: #17609e; background-color: #17609e;
color: #ffffff; color: #ffffff;
} }
.Button--blue.is-disabled, .Button--blue.is-disabled:hover, .Button--blue.is-disabled:active, .Button--blue:disabled, .Button--blue:disabled:hover, .Button--blue:disabled:active { .Button--blue.is-disabled, button.is-disabled:has(+ .position-label-flags :checked), .Button--blue.is-disabled:hover, button.is-disabled:hover:has(+ .position-label-flags :checked), .Button--blue.is-disabled:active, button.is-disabled:active:has(+ .position-label-flags :checked), .Button--blue:disabled, button:disabled:has(+ .position-label-flags :checked), .Button--blue:disabled:hover, button:disabled:hover:has(+ .position-label-flags :checked), .Button--blue:disabled:active, button:disabled:active:has(+ .position-label-flags :checked) {
background-color: #1A6BAF; background-color: #1A6BAF;
border-color: #15568c; border-color: #15568c;
color: #ffffff; color: #ffffff;
@@ -2312,13 +2312,13 @@ input:-webkit-autofill:focus {
} }
.Popup-container::before { .Popup-container::before {
top: calc( 100% - 6px ); top: calc(100% - 6px);
border: solid #d6d6d6 1px; border: solid #d6d6d6 1px;
box-shadow: 0 0 2px rgba(56, 56, 56, 0.15); box-shadow: 0 0 2px rgba(56, 56, 56, 0.15);
} }
.Popup-container::after { .Popup-container::after {
top: calc( 100% - 7px ); top: calc(100% - 7px);
} }
.Popup-content { .Popup-content {
@@ -2757,7 +2757,7 @@ input:-webkit-autofill:focus {
.StepNav-stepTitle { .StepNav-stepTitle {
width: 150px; width: 150px;
left: calc( 0px - 43px); left: calc(0px - 43px);
padding-top: 4px; padding-top: 4px;
} }
@@ -2875,7 +2875,7 @@ input:-webkit-autofill:focus {
} }
.StepNav--small .StepNav-stepTitle { .StepNav--small .StepNav-stepTitle {
width: 150px; width: 150px;
left: calc( 0px - 53px); left: calc(0px - 53px);
padding-top: 2px; padding-top: 2px;
} }
.StepNav--small .StepNav-stepIcon { .StepNav--small .StepNav-stepIcon {
@@ -2910,7 +2910,7 @@ input:-webkit-autofill:focus {
} }
.StepNav--xsmall .StepNav-stepTitle { .StepNav--xsmall .StepNav-stepTitle {
width: 150px; width: 150px;
left: calc( 0px - 62px); left: calc(0px - 62px);
padding-top: 1px; padding-top: 1px;
} }
.StepNav--xsmall .StepNav-stepIcon { .StepNav--xsmall .StepNav-stepIcon {
@@ -7014,7 +7014,7 @@ a.Panel-row {
align-items: center; align-items: center;
} }
div[id^=event-lineup] { div.event-lineup {
max-width: 576px; max-width: 576px;
counter-reset: lineup-sequence-counter 0; counter-reset: lineup-sequence-counter 0;
margin-left: 8px; margin-left: 8px;
@@ -7093,9 +7093,23 @@ li .availability-status-code- {
padding: 0; padding: 0;
} }
div[id^=event-lineup] .Panel.position-only .Panel-cell:has(.sequence), div[id^=event-lineup] .Panel.bench .Panel-cell:has(.sequence), div[id^=event-lineup] .Panel.out .Panel-cell:has(.sequence) { div.event-lineup .lineup-segment:has(input.Toggle-input:not(:checked)).out .Panel-cell:has(.SelectBox),
div.event-lineup .lineup-segment:has(input.Toggle-input:not(:checked)).out .Panel-cell:has(.drag-handle),
div.event-lineup .lineup-segment:has(input.Toggle-input:not(:checked)).out button:has(+ .position-label-flags),
div.event-lineup .lineup-segment:has(input.Toggle-input:not(:checked)).out button.addToStarting,
div.event-lineup .lineup-segment:has(input.Toggle-input:not(:checked)).out button.addToBench {
display: none; display: none;
} }
div.event-lineup .lineup-segment.bench .Panel-cell:has(.sequence), div.event-lineup .lineup-segment.position-only .Panel-cell:has(.sequence), div.event-lineup .lineup-segment.out .Panel-cell:has(.sequence) {
display: none;
}
div.event-lineup .lineup-segment.bench.bench button.addToBench, div.event-lineup .lineup-segment.position-only.bench button.addToBench, div.event-lineup .lineup-segment.out.bench button.addToBench {
display: none;
}
div.event-lineup .lineup-segment.starting button.addToStarting, div.event-lineup .lineup-segment.position-only button.addToStarting {
display: none;
}
.Tooltip:after { .Tooltip:after {
padding: 2px !important; padding: 2px !important;
font-size: inherit !important; font-size: inherit !important;
@@ -7104,8 +7118,6 @@ div[id^=event-lineup] .Panel.position-only .Panel-cell:has(.sequence), div[id^=e
@media (max-width: 480px) { @media (max-width: 480px) {
.Panel--full { .Panel--full {
border-radius: 0; border-radius: 0;
margin-right: -16px;
margin-left: -16px;
border-right: none; border-right: none;
border-left: none; border-left: none;
} }
@@ -7139,8 +7151,11 @@ div[id^=event-lineup] .Panel.position-only .Panel-cell:has(.sequence), div[id^=e
} }
.Panel .Panel { .Panel .Panel {
border: none; margin: 8px;
margin: 0; }
.scroll-horizontal {
overflow-x: scroll;
} }
/*# sourceMappingURL=application.css.map */ /*# sourceMappingURL=application.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["../../scss/eventsheet.scss"],"names":[],"mappings":";AAAQ;AACA;AACA;AACA;AACA;AAER;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;AACA;EACE;IACE;IACA;;;AAIJ;AACA;EACE;IACE;;EAEF;IACE;;EAEF;IACE;IACA;;;AAIJ;EACE;EACA;EACA;EACA;;;AAGF;AACA;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAKA;EACE;EACA;;AAEF;EACE;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAQR;EACE;;;AAGF;EACE;;;AAOF;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;AACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;AAAA;EAEE;EACA;EACA;EACA,qBACE;;;AAIJ;AAAA;EAEE;EACA;EACA;EACA,qBACE;;;AAIJ;AAAA;AAAA;AAAA;EAIE;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAKF;EACE;;AAEA;EACE;EACA;;AAEF;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAQN;EACE;;;AAGF;AACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;AAAA;AAAA;AAAA;AAAA;EAKE;;;AAGF;AAAA;AAAA;AAAA;AAAA;EAKE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;AAAA;EAEE;;;AAGF;AAAA;EAEE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAKA;EACE;EACA;EACA;EACA,qBACE;;AAIJ;EACE;;AAGF;EACE;;AAGF;AACE;EACA;EACA;EACA;AACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;EACA;;AACA;EACE;;AAQJ;EACE;EACA;EACA;;AAEF;EACE;;;AAON;EACE;;AAGF;EACE;;AAIA;EACE;EACA;;AAMF;EACE;;AAGF;EAOE;EACA;EACA;EACA;;AATA;EACE;;AACA;EACE;;;AAeV;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAMI;EACE,SALM;;AAIR;EACE,SALM;;AAIR;EACE,SALM;;AAIR;EACE,SALM;;AAIR;EACE,SALM;;AAIR;EACE,SALM;;AAIR;EACE,SALM;;AAIR;EACE,SALM;;AAIR;EACE,SALM;;AAUZ;EACE;EAEA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EAIE;EACA;EACA;;AALA;EACE;;AAKF;EACI;;;AASV;EACE;AACA;EACA;EAEA;EACA;AACA;;AAEA;EACE;EACA;;AAGF;EACE;;AACA;EACE;;;AAON;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;AACA;AACA;AACA;EACA;EAEA;EACA;EACA;EACA;EACA;;;AAKF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAEF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;AAAA;EAEE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AASA;AACE;EACA;EACA;AACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EAEE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACI;EACA;EACA;EACA;EACA;;AAEJ;EACI;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;EAEE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAMN;EACE;EACA;EACA;;AAEF;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EAEE;;AAGF;EACE;EACA;EACA;;AAEA;EACE;;AAQJ;EACE;;AAGF;EACE;;AAGF;EACE;;;AAOJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;AACA;AAAA;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACI;EACA;EACA;EACA;;AAGJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE","file":"eventsheet.css"} {"version":3,"sourceRoot":"","sources":["../../scss/eventsheet.scss"],"names":[],"mappings":";AAAQ;AACA;AACA;AACA;AACA;AACA;AAER;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIF;AACA;EACE;IACE;;EAEF;IACE;;EAEF;IACE;IACA;;;AAIJ;AACA;EACE;IACE;;EAEF;IACE;;EAEF;IACE;IACA;IACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;;AAGF;AACA;EAA+B;EAAc;;;AAC7C;EAA+B;EAAc;;;AAC7C;EAA+B;EAAc;;;AAE7C;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;;AAEF;EACE;EACA;;AAEA;EACE;;AAMkB;EAChB;;AAGiB;EACjB;;;AAOR;EACE;;;AAGF;EACE;;;AAGF;EACE;;AACA;EACE;;AACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAGF;EACE;;AAGF;EACE;;;AAMN;EACI;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;;;AAIN;EACE;;;AAGF;EACE;EACE;EACA;EACA;EACA;;AACA;EACE;;;AAIN;EACE;EACE;EACA;EACA;EACA;;AACA;EACE;;;AAIN;EACE;EAEA;EAEA;;AAEA;EACE;EACA;EACA;EACA;;AACA;EACE;;AAIJ;EACE;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;;AAMN;EACE;EACA;EACA;;AAGF;EAEE;;AAGF;AACE;;AACA;EACE;;AACA;EACE;;AAGJ;EACE;;AAEF;EACE;EACA;EACA;EACA;EACA;;AAKF;EAEA;;AAIA;EACE;EACA;;AAEF;EACE;;AAKF;EACE;;AACA;EACE;;AAGJ;EACE;;AAGF;EACE;;;AAMJ;EACE;;AAEF;EACE;;AACA;EACE;;;AAOJ;EACE;EACA;EACA;EACA,qBACE;;AAIJ;EACE;;AAGF;EACE;;AAGF;AACE;EACA;EACA;EACA;AACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGE;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;EACA;;AACA;EACE;;AAQJ;EACE;EACA;EACA;;AAEF;EACE;;;AAON;EACE;;AAGF;EACE;;AAIA;EACE;EACA;;AAMF;EACE;;AAGF;EAOE;EACA;EACA;EACA;;AATA;EACE;;AACA;EACE;;;AAcV;EACE;;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA;;AAIA;EACE;;AAGF;EACE;EACA;EACA;;AAEE;EACE;;AAEM;EACN;;AAEF;EACA;EACA;EACA;;AAQA;EACE;;AACA;EACA,SA/BM;;AA4BR;EACE;;AACA;EACA,SA/BM;;AA4BR;EACE;;AACA;EACA,SA/BM;;AA4BR;EACE;;AACA;EACA,SA/BM;;AA4BR;EACE;;AACA;EACA,SA/BM;;AA4BR;EACE;;AACA;EACA,SA/BM;;AA4BR;EACE;;AACA;EACA,SA/BM;;AA4BR;EACE;;AACA;EACA,SA/BM;;AA4BR;EACE;;AACA;EACA,SA/BM;;AAoCZ;EACE;EAEA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EAIE;EACA;EACA;;AALA;EACE;;AAKF;EACE;;AACA;EACE;;;AASV;EACE;AACA;EACA;EAEA;EACA;AACA;;AAEA;EACE;EACA;;AAGF;EACE;;AACA;EACE;;;AAMN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;;;AAEF;EACE;;;AAGF;EACE;;;AAKF;EACE;;AAME;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EAEE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAIJ;EACE;EACA;;AAEF;AACE;EACA;EACA;AACA;;AAIA;EACA;;AAKF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAOF;EACE;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EAEE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACI;EACA;EACA;EACA;EACA;;AAEJ;EACE;;AACA;EACE;;AAGJ;EACI;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;EAEE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAMN;EACE;EACA;EACA;;AAEF;EACE;;AAIJ;EACE;EACA;EACA;;AAEF;EACE;EACA;EACA;;AAGF;EAEE;;AAGF;EACE;EACA;;AAEA;EACE;AACA;EACA;EACA;EACA;;AAQJ;EACE;;AAGF;EACE;;AAGa;EACb;;;AAMF;EACE;;AAEF;EACE;;AAEF;EACE;;AAIA;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EAQE;EACA;;AARA;EACE;;AACA;EACE;EACA;;AAWJ;EACE;EACA;;AAQJ;EACE;;;AAKN;EACE;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;EACA;AACA;AAAA;;;AAIF;EACE;;;AAGF;EACE;;;AAGF;EACE;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAIJ;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE","file":"eventsheet.css"}

View File

@@ -1,4 +1,42 @@
/* Project specific Javascript goes here. */ const lineupChangedEvent = new Event('bc:lineupChanged')
document.querySelectorAll('.event-lineup').forEach(lineup=>{
lineup.addEventListener('bc:lineupChanged', (evt)=>{
console.log(`lineup changed`, evt.target)
const lineup = evt.target
colorPositions(lineup)
lineup.querySelectorAll(".lineup-slot").forEach((slot, i) => {
const lineup_segment = determineLineupSegment(slot)
if (lineup_segment != 'bench' && lineup_segment != 'out'){
slot.querySelector("input[name=sequence]").value = i;
} else {
slot.querySelector("input[name=sequence]").value = null;
}
updateFlagInput(slot)
updatePositionInput(slot)
});
}
)
})
document.querySelectorAll('[data-control=popup]').forEach(popup_control=>{
console.log(popup_control)
popup_control.addEventListener('click', (evt)=>{
const popup = evt.target.closest(".Popup")
const to_open = popup.querySelector(".Popup-toggle").dataset.open
popup.querySelectorAll(`[data-popup=${to_open}]`).forEach(popup_container => {
console.log(evt, evt.target, popup, popup_container)
popup_container.classList.toggle('is-open')
evt.stopPropagation()
})
})
})
document.querySelectorAll('.position-label-flags input[type="checkbox"]').forEach(flagCheckbox => {
const lineup = flagCheckbox.closest('.event-lineup')
flagCheckbox.addEventListener('click', ()=>{lineup.dispatchEvent(lineupChangedEvent)})
})
function onPositionSelectChange(elem) { function onPositionSelectChange(elem) {
elem.querySelectorAll("option").forEach((option) => { elem.querySelectorAll("option").forEach((option) => {
if (option.innerText.trim() == elem.value) { if (option.innerText.trim() == elem.value) {
@@ -7,251 +45,247 @@ function onPositionSelectChange(elem) {
option.removeAttribute("selected"); option.removeAttribute("selected");
} }
}); });
colorPositions(); const lineup = elem.closest('.event-lineup')
refreshLineup(); lineup.dispatchEvent(lineupChangedEvent)
elem
} }
function togglePopup(el) { function colorPositions(lineup) {
el.querySelector(".Popup-container").classList.toggle("is-open"); const class_none = "u-colorNegative"
} const class_good = "u-colorPositive"
const class_over = "u-colorHighlight"
function colorPositions() { lineup.querySelectorAll('.position-status').forEach(
for (bcLineup of document.querySelectorAll("[id^=event-lineup]")) { position_status=>{
selected_lineup_positions = Array.from( position_status.classList.remove(class_over, class_good, class_none);
bcLineup.querySelectorAll(".position-select-box option:checked") const occurences = lineup.querySelectorAll(`.position-select-box option:checked[value="${position_status.dataset.value}"]`)
).map((el) => el.value); switch (occurences.length){
case 0:
for (position_status of bcLineup.querySelectorAll(".position-status")) { position_status.classList.add(class_none)
for (class_name of ["u-colorNegative", "u-colorHighlight", "u-colorPositive"]) { break;
position_status.classList.remove(class_name); case 1:
position_status.classList.add(class_good)
break;
default:
position_status.classList.add(class_over)
break;
} }
})
occurrences = selected_lineup_positions.filter((s) => s == position_status.innerText).length;
if (occurrences == 1) {
position_status.classList.add("u-colorPositive");
} else if (occurrences > 1) {
position_status.classList.add("u-colorHighlight");
} else {
position_status.classList.add("u-colorNegative");
}
}
}
} }
function initFlagsCheckboxes(){ function initFlagsCheckboxes(){
Array.from(document.querySelectorAll("[id^=event-lineup]")).forEach((bcLineup) => { document.querySelectorAll(".lineup-slot").forEach(lineup_slot=>{
Array.from( const possible_flags = ['DHd', 'DRd']
bcLineup.querySelectorAll( const flags_string = lineup_slot.querySelector("input[name=flags]")?.value
".starting .lineup-slot, \ const flags = flagSetFromString(flags_string)
.position-only .lineup-slot, \
.bench .lineup-slot" possible_flags.forEach(flag=>{
if (flags.has(flag)){
lineup_slot.querySelector(`input[type=checkbox][name=${flag}]`).checked = true
}
})
})
}
const flagSetFromString = (s) => {
if (!s) {return new Set()}
const array = s.split(',').map(item=>item.trim())
return new Set(array)
}
const flagSetFromSlot = (slot) => {
const inputs = slot.querySelectorAll('.position-label-flags input[type=checkbox]:checked')
const set = new Set()
inputs.forEach(i=>set.add(i.name))
return set
}
const flagSetToString = (set) => {
return Array.from(set).join(",");
}
const updateFlagInput = (slot) => {
const flags = flagSetFromSlot(slot)
const lineup_segment = slot.closest('.lineup-segment')
lineup_segment.classList.contains('position-only') ? flags.add('PO') : flags.delete('PO')
slot.querySelector('input[name="flags"]').value = flagSetToString(flags);
}
const updatePositionInput = (slot) => {
const selected_position = slot.querySelector(".position-select-box option:checked");
const lineup_segment = slot.closest('.lineup-segment')
if (selected_position && selected_position.text != "--" && !lineup_segment.classList.contains('bench')) {
slot.querySelector("input[name=label]").value = selected_position.text;
} else {
slot.querySelector("input[name=label]").value = null;
}
}
const determineLineupSegment = (slot) => {
const lineup_segments = ['starting', 'position-only', 'bench', 'out']
const lineup_segment = slot.closest('.lineup-segment')
const classList = Array.from(lineup_segment.classList)
const segments = classList.filter(c=>lineup_segments.includes(c))
if (segments.length == 1) {
return segments[0]
} else {
return ''
}
}
function openAvailabilityReminderModal (el, team_id, event_id) {
const url = `/${team_id}/event/${event_id}/modal-confirm-availability-reminders/`
const form = el.closest('form')
const form_data = new FormData (form)
fetch(url)
.then((response) => {
if (response.ok) {
return response.text();
} else {
return Promise.reject(response.text());
}
})
.then((html) => {
const parser = new DOMParser()
const modal = parser.parseFromString(html, 'text/html')
const modal_node = modal.firstElementChild.querySelector('#modal')
modal_node.classList.add('is-open')
const modal_node_accept = modal.querySelector('Button[data-confirm=yes]')
const checked = Array.from(el.querySelectorAll('input:checked')).map
const body = document.querySelector('body')
body.appendChild(modal_node)
modal_node_accept.addEventListener(
"click", ()=>{
// const memberIds = form_data.getAll('memberId')
const csrf_token = form_data.get('csrfToken')
const selected_status_codes = Array.from(document.querySelectorAll('input:checked')).map(e=>e.value)
const slots = Array.from(document.querySelectorAll('.lineup-slot')).filter(
slot =>{
const slot_status_code = slot.querySelector('input[name=availabilityStatusCode]').value
return selected_status_codes.includes(slot_status_code)
}
)
const memberIds = slots.map(
slot => slot.querySelector('input[name=memberId]').value
)
console.log("sending reminders", el, event_id, memberIds, csrf_token)
sendAvailabilityReminder(el, event_id, memberIds, csrf_token)
body.removeChild(modal_node)
}
) )
).forEach((slot, i) => { })
const flags = new Set(slot.querySelector("input[name*=flags]")?.value?.split(',')?.map(s=>s.trim())) || new Set()
if (flags.has('DHd')) {
slot.querySelector('[name=flag-dhd]').checked = true;
}
if (flags.has('DRd') ) {
slot.querySelector('[name=flag-drd]').checked = true;
}
})}
)
} }
function refreshLineup() { function confirmModal(el, prompt, fn, options) {
Array.from(document.querySelectorAll("[id^=event-lineup]")).forEach((bcLineup) => { const url = "/modal-confirm"
Array.from( const params = new URLSearchParams(prompt)
bcLineup.querySelectorAll( url.search = params.toString()
".starting .lineup-slot, \ fetch(url+"?"+params.toString(), {method:"GET"})
.position-only .lineup-slot, \ .then((response) => {
.bench .lineup-slot" if (response.ok) {
) return response.text();
).forEach((slot, i) => { } else {
slot.querySelector("input[name*=sequence]").value = i; return Promise.reject(response.text());
selected_position = slot.querySelector(".position-select-box option:checked"); }
const flags = new Set(slot.querySelector("input[name*=flags]")?.value?.split(',')?.map(s=>s.trim())) || new Set() })
.then((html) => {
if (slot.querySelector('[name=flag-dhd]').checked) { const parser = new DOMParser()
flags.add('DHd') const modal = parser.parseFromString(html, 'text/html')
} else { const modal_node = modal.firstElementChild.querySelector('#modal')
flags.delete('DHd') modal_node.classList.add('is-open')
} const modal_node_accept = modal.querySelector('Button[data-confirm=yes]')
const body = document.querySelector('body')
if (slot.querySelector('[name=flag-drd]').checked) { body.appendChild(modal_node)
flags.add('DRd') modal_node_accept.addEventListener("click", ()=>{fn(modal_node, options)})
} else { })
flags.delete('DRd')
}
if (selected_position && selected_position.text != "--") {
slot.querySelector("input[name*=label]").value = selected_position.text;
} else {
slot.querySelector("input[name*=label]").value = null;
}
if (slot.closest('.position-only')){
flags.add('PO');flags.delete('')
}
else {
flags.delete('PO');flags.delete('')
}
if (slot.closest('.bench')){
slot.querySelector("input[name*=sequence]").value = '';
slot.querySelector("input[name*=label]").value = '';
}
slot.querySelector("input[name*=flags]").value = Array.from(flags).join(",");
});
});
} }
function refreshFlags(){ const toggleShowAndHideLoading = (el) => {
console.log(el)
el.querySelectorAll('.hideOnLoading').forEach((element)=>{
element.classList.add('u-hidden')
})
el.querySelectorAll('.showOnLoading').forEach((element)=>{
element.classList.remove('u-hidden')
})
} }
function copyEmailTable(itemEl, subject, recipients) { const completeLoad = (el, success) => {
// Create container for the HTML el.querySelectorAll('.hideOnLoading, .showOnLoading, .showOnFailure, .showOnSuccess').forEach((element)=>{
// [1] element.classList.add('u-hidden')
let bcLineup = itemEl.closest(".benchcoach-lineup"); })
var container = document.createElement("div"); if (success) {
var tbl = document.createElement("table"); el.querySelectorAll('.showOnSuccess').forEach((element) => {
element.classList.remove('u-hidden')
})
} else {
el.querySelectorAll('.showOnFailure').forEach((element) => {
element.classList.remove('u-hidden')
})
}
}
let thead = tbl.createTHead(); function submitClearLineup(modal, options){
let thead_row = thead.insertRow(); console.log('clearing lineup...')
let thead_row_cell = thead_row.insertCell(); toggleShowAndHideLoading(modal)
thead_row_cell.appendChild(document.createElement("h3").appendChild(document.createTextNode("STARTING LINEUP"))); const {team_id, event_id, event_lineup_id} = options
thead_row_cell.colSpan = 3; const url = `/${team_id}/event/${event_id}/lineup/${event_lineup_id}/delete`
thead_row_cell.classList.add("title-cell"); const form = document.querySelector(`#event-lineup-${event_id} form`);
var tbody = tbl.createTBody(); const data = new FormData(form);
for (row of bcLineup.querySelector(".table-benchcoach-startinglineup").rows) { const memberIds = data.getAll('memberId')
let tr = tbody.insertRow(); console.log(url)
cell = tr.insertCell(); fetch(url, {method:"POST", body: JSON.stringify({memberIds, event_id}), headers: {"Content-Type": "application/json"}})
cell.classList.add("sequence-cell"); .then((response) => {
cell.appendChild(document.createTextNode(parseInt(row.dataset.order) + 1)); if (response.ok) {
cell = tr.insertCell(); completeLoad(modal, true);
cell.appendChild(document.createTextNode(row.dataset.playerName)); return response.text();
cell.classList.add("name-cell"); } else {
tr.insertCell().appendChild(document.createTextNode(row.dataset.position)); completeLoad(modal, false);
} return Promise.reject(response.text());
if (bcLineup.querySelector(".table-benchcoach-startingpositionalonly").rows.length > 0) {
var tr = tbody.insertRow();
cell = tr.insertCell();
cell.colSpan = 3;
cell.appendChild(document.createTextNode("STARTING (POS. ONLY)"));
cell.classList.add("title-cell");
for (row of bcLineup.querySelector(".table-benchcoach-startingpositionalonly").rows) {
var tr = tbody.insertRow();
cell = tr.insertCell();
cell.classList.add("sequence-cell");
cell.appendChild(document.createTextNode(""));
cell = tr.insertCell();
cell.appendChild(document.createTextNode(row.dataset.playerName));
cell.classList.add("name-cell");
tr.insertCell().appendChild(document.createTextNode(row.dataset.position));
} }
} })
.finally(()=>{
setTimeout(function (){
location.reload()
}, 500)
});//refresh page
}
if (bcLineup.querySelector(".table-benchcoach-bench").rows.length > 0) { function submitResetAvailabilities(modal, options){
var tr = tbody.insertRow(); const {team_id, event_id} = options
cell = tr.insertCell(); toggleShowAndHideLoading(modal)
cell.colSpan = 3; const url = `/${team_id}/event/${event_id}/reset_availabilities`
cell.appendChild(document.createTextNode("SUBS")); const form = document.querySelector(`#event-lineup-${event_id} form`);
cell.classList.add("title-cell"); const data = new FormData(form);
const memberIds = data.getAll('memberId')
for (row of bcLineup.querySelector(".table-benchcoach-bench").rows) { console.log('submitting...', url)
var tr = tbody.insertRow(); fetch(url, {method:"POST", body: JSON.stringify({memberIds, event_id}), headers: {"Content-Type": "application/json"}})
cell = tr.insertCell(); .then((response) => {
cell.classList.add("sequence-cell"); if (response.ok) {
availability_status = { completeLoad(modal, true);
None: "UNK", return response.text();
0: "NO", } else {
2: "MAY", completeLoad(modal, false);
1: "YES", return Promise.reject(response.text());
}[row.dataset.availabilityStatuscode];
cell.appendChild(document.createTextNode(availability_status));
cell = tr.insertCell();
cell.appendChild(document.createTextNode(row.dataset.playerName));
cell.classList.add("name-cell");
tr.insertCell().appendChild(document.createTextNode(""));
} }
} })
.finally(()=>{
if (bcLineup.querySelector(".table-benchcoach-out").rows.length > 0) { setTimeout(function (){
var tr = tbody.insertRow(); location.reload()
cell = tr.insertCell(); }, 500)
cell.colSpan = 3; });//refresh page
cell.appendChild(document.createTextNode("OUT"));
cell.classList.add("title-cell");
for (row of bcLineup.querySelector(".table-benchcoach-out").rows) {
var tr = tbody.insertRow();
cell = tr.insertCell();
cell.classList.add("sequence-cell");
availability_status = {
None: "UNK",
0: "NO",
1: "MAY",
2: "YES",
}[row.dataset.availabilityStatuscode];
cell.appendChild(document.createTextNode(availability_status));
tr.insertCell().appendChild(document.createTextNode(row.dataset.playerName));
tr.insertCell().appendChild(document.createTextNode(""));
}
}
container.appendChild(tbl);
for (cell of container.getElementsByClassName("title-cell")) {
cell.setAttribute("style", "font-weight:bold;background-color:#323669;color:#fff;padding:2px 5px;");
}
for (cell of container.getElementsByClassName("sequence-cell")) {
cell.setAttribute("style", "font-weight:bold;padding:2px 5px;");
}
for (cell of container.getElementsByClassName("name-cell")) {
cell.setAttribute("style", "width:200px;");
}
// Detect all style sheets of the page
var activeSheets = Array.prototype.slice.call(document.styleSheets).filter(function (sheet) {
return !sheet.disabled;
});
// Mount the container to the DOM to make `contentWindow` available
// [3]
document.body.appendChild(container);
// Copy to clipboard
// [4]
window.getSelection().removeAllRanges();
var range = document.createRange();
range.selectNode(container);
window.getSelection().addRange(range);
// [5.1]
document.execCommand("copy");
// [5.2]
for (var i = 0; i < activeSheets.length; i++) activeSheets[i].disabled = true;
// [5.3]
// document.execCommand('copy')
// [5.4]
for (var i = 0; i < activeSheets.length; i++) activeSheets[i].disabled = false;
// Remove the container
// [6]
document.body.removeChild(container);
subject_encoded = encodeURIComponent(subject);
window.open("readdle-spark://compose?recipient=manager@chihounds.com&subject=" + subject + "&bcc=" + recipients);
} }
function emailModal(el, url) { function emailModal(el, url) {
form = el.closest("form"); form = el.closest("form");
console.log(form)
data = new FormData(form); data = new FormData(form);
fetch(url, { fetch(url, {
@@ -277,32 +311,28 @@ function emailModal(el, url) {
email_modal_node.classList.add('is-open') email_modal_node.classList.add('is-open')
body.appendChild(email_modal_node) body.appendChild(email_modal_node)
tinymce.init({ tinymce.init({
selector:`#lineup-email-data-${data.get('event_lineup_id')} #email-editor`, selector:`textarea#email-editor`,
content_css:"/css/application.css", content_css:"/css/application.css",
plugins: 'image', plugins: 'image',
menubar: false, menubar: false,
toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | outdent indent | image', toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | outdent indent | image',
paste_data_images: true, paste_data_images: true,
statusbar:false}) statusbar:false})
// tinymce.activeEditor.setContent("Team,") tinymce.remove();
// lineup_table_div.innerHTML = lineup_table
// email_modal.classList.add("is-open");
// email_modal.querySelector(".Modal-body").innerHTML = html;
}); });
} }
async function onSubmit(form, event) { async function submitEventLineup(form, event) {
event.preventDefault(); event.preventDefault();
console.log(event) console.log(event)
teamsnap_icon = document.querySelector("#teamsnap-icon"); teamsnap_icon = form.querySelector("#teamsnap-icon");
waiting_icon = document.querySelector("#waiting-icon"); waiting_icon = form.querySelector("#waiting-icon");
success_icon = document.querySelector("#success-icon"); success_icon = form.querySelector("#success-icon");
failure_icon = document.querySelector("#failure-icon"); failure_icon = form.querySelector("#failure-icon");
data = new FormData(form); data = new FormData(form);
console.log(form) console.log(form)
url = form.attributes.action.textContent; url = form.attributes.action.textContent;
teamsnap_icon.classList.add("u-hidden") toggleShowAndHideLoading(form)
waiting_icon.classList.remove("u-hidden");
await fetch(url, { await fetch(url, {
method: "POST", method: "POST",
body: data, body: data,
@@ -311,8 +341,8 @@ async function onSubmit(form, event) {
} }
}) })
.then((response) => { .then((response) => {
waiting_icon.classList.add("u-hidden");
if (response.ok) { if (response.ok) {
return response.text(); return response.text();
} else { } else {
return Promise.reject(response.text()); return Promise.reject(response.text());
@@ -320,202 +350,22 @@ async function onSubmit(form, event) {
}) })
.then((text) => { .then((text) => {
event.submitter.blur() event.submitter.blur()
waiting_icon.classList.add("u-hidden"); completeLoad(form, true)
success_icon.classList.remove("u-hidden"); console.log(text);
// success_icon.querySelector("span.message").innerHTML = text;
}) })
.catch((error) => { .catch((error) => {
event.submitter.blur() event.submitter.blur()
waiting_icon.classList.add("u-hidden"); completeLoad(form, false)
failure_icon.classList.remove("u-hidden");
console.log(error); console.log(error);
// success_icon.querySelector("span.message").innerHTML = error; })
}); .finally(()=>{location.reload()});//refresh page
setTimeout(() => { setTimeout(() => {
[waiting_icon, success_icon, failure_icon].forEach(e=>e.classList.add('u-hidden')) [waiting_icon, success_icon, failure_icon].forEach(e=>e.classList.add('u-hidden'))
teamsnap_icon.classList.remove('u-hidden') teamsnap_icon.classList.remove('u-hidden')
}, 3000) }, 3000)
} }
function copyEmailTable(itemEl, subject, recipients) { async function copyEmailTable (element) {
// Create container for the HTML
// [1]
let bcLineup = itemEl.closest(".event-lineup");
var container = document.createElement("div");
var tbl = document.createElement("table");
let thead = tbl.createTHead();
let thead_row = thead.insertRow();
let thead_row_cell = thead_row.insertCell();
thead_row_cell.appendChild(document.createElement("h3").appendChild(document.createTextNode("STARTING LINEUP")));
thead_row_cell.colSpan = 3;
thead_row_cell.classList.add("title-cell");
var tbody = tbl.createTBody();
lineup_slots_starting = bcLineup.querySelectorAll(".starting .slot-set .lineup-slot");
for (node of lineup_slots_starting) {
console.log("node", node);
let tr = tbody.insertRow();
cell = tr.insertCell();
cell.classList.add("sequence-cell");
sequence = node.querySelector("input[name*='sequence']").value;
console.log(sequence);
cell.appendChild(document.createTextNode(parseInt(sequence) + 1));
name = node.querySelector("div:has(.lastname)");
cell = tr.insertCell();
cell.appendChild(document.createTextNode(name.textContent));
cell.classList.add("name-cell");
position_label = node.querySelector("input[name*='label']").value;
tr.insertCell().appendChild(document.createTextNode(position_label));
}
lineup_slots_position_only = bcLineup.querySelector(".position-only .slot-set .lineup-slot");
console.log("lineup slots position", lineup_slots_position_only);
if (lineup_slots_position_only.length > 0) {
var tr = tbody.insertRow();
cell = tr.insertCell();
cell.colSpan = 3;
cell.appendChild(document.createTextNode("STARTING (POS. ONLY)"));
cell.classList.add("title-cell");
for (node of lineup_slots_position_only) {
var tr = tbody.insertRow();
cell = tr.insertCell();
cell.classList.add("sequence-cell");
cell.appendChild(document.createTextNode(""));
cell = tr.insertCell();
name = node.querySelector("div:has(.lastname)");
cell.appendChild(document.createTextNode(name.textCotent));
cell.classList.add("name-cell");
position_label = node.querySelector("input[name*='label']").value;
tr.insertCell().appendChild(document.createTextNode(position_label));
}
}
lineup_slots_bench = bcLineup.querySelector(".bench .slot-set .lineup-slot");
if (lineup_slots_bench > 0) {
var tr = tbody.insertRow();
cell = tr.insertCell();
cell.colSpan = 3;
cell.appendChild(document.createTextNode("SUBS"));
cell.classList.add("title-cell");
for (node of lineup_slots_bench) {
var tr = tbody.insertRow();
cell = tr.insertCell();
cell.classList.add("sequence-cell");
div_avail_code = node.querySelector("div[class*='availability-status-code']");
if (div_with_avail_code.classList.includes("availability-status-code-1")) {
cell.appendChild(document.createTextNode("YES"));
} else if (div_with_avail_code.classList.includes("availability-status-code-2")) {
cell.appendChild(document.createTextNode("MAY"));
} else if (div_with_avail_code.classList.includes("availability-status-code-0")) {
cell.appendChild(document.createTextNode("NO"));
} else {
cell.appendChild(document.createTextNode("UNK"));
}
cell = tr.insertCell();
name = node.querySelector("div:has(.lastname)");
cell.appendChild(document.createTextNode(name.textCotent));
cell.classList.add("name-cell");
tr.insertCell().appendChild(document.createTextNode(""));
}
}
lineup_slots_out = bcLineup.querySelector(".out .slot-set .lineup-slot");
if (lineup_slots_out > 0) {
var tr = tbody.insertRow();
cell = tr.insertCell();
cell.colSpan = 3;
cell.appendChild(document.createTextNode("OUT"));
cell.classList.add("title-cell");
for (node of lineup_slots_out) {
var tr = tbody.insertRow();
cell = tr.insertCell();
cell.classList.add("sequence-cell");
div_avail_code = node.querySelector("div[class*='availability-status-code']");
if (div_with_avail_code.classList.includes("availability-status-code-1")) {
cell.appendChild(document.createTextNode("YES"));
} else if (div_with_avail_code.classList.includes("availability-status-code-2")) {
cell.appendChild(document.createTextNode("MAY"));
} else if (div_with_avail_code.classList.includes("availability-status-code-0")) {
cell.appendChild(document.createTextNode("NO"));
} else {
cell.appendChild(document.createTextNode("UNK"));
}
cell = tr.insertCell();
name = node.querySelector("div:has(.lastname)");
cell.appendChild(document.createTextNode(name.textCotent));
cell.classList.add("name-cell");
tr.insertCell().appendChild(document.createTextNode(""));
}
}
container.appendChild(tbl);
for (cell of container.getElementsByClassName("title-cell")) {
cell.setAttribute("style", "font-weight:bold;background-color:#323669;color:#fff;padding:2px 5px;");
}
for (cell of container.getElementsByClassName("sequence-cell")) {
cell.setAttribute("style", "font-weight:bold;padding:2px 5px;");
}
for (cell of container.getElementsByClassName("name-cell")) {
cell.setAttribute("style", "width:200px;");
}
// Detect all style sheets of the page
var activeSheets = Array.prototype.slice.call(document.styleSheets).filter(function (sheet) {
return !sheet.disabled;
});
// Mount the container to the DOM to make `contentWindow` available
// [3]
document.body.appendChild(container);
// Copy to clipboard
// [4]
window.getSelection().removeAllRanges();
var range = document.createRange();
range.selectNode(container);
window.getSelection().addRange(range);
// [5.1]
document.execCommand("copy");
// [5.2]
for (var i = 0; i < activeSheets.length; i++) activeSheets[i].disabled = true;
// [5.3]
// document.execCommand('copy')
// [5.4]
for (var i = 0; i < activeSheets.length; i++) activeSheets[i].disabled = false;
// Remove the container
// [6]
document.body.removeChild(container);
subject_encoded = encodeURIComponent(subject);
window.open("readdle-spark://compose?recipient=manager@chihounds.com&subject=" + subject + "&bcc=" + recipients);
}
function toggleChildSlots (element) {
console.log(element);
console.log(element.closest(".slot-set"))
for (lineup_slot of document.querySelectorAll("[id^=lineup-out] .lineup-slot")) {
console.log(lineup_slot)
const cells = lineup_slot.querySelectorAll('.Panel-cell:has(.sequence), .Panel-cell:has(.drag-handle), .Panel-cell:has(.position-select-box), div.position-label-flags ')
Array.from(cells).forEach(cell=>{
cell.classList.toggle('u-hidden')
})
}
}
async function submitEmail () {
// range=document.createRange(); // range=document.createRange();
// window.getSelection().removeAllRanges(); // window.getSelection().removeAllRanges();
// // range.selectNode(document.querySelector('.Modal').querySelector('.Modal-body')); // // range.selectNode(document.querySelector('.Modal').querySelector('.Modal-body'));
@@ -557,16 +407,63 @@ async function submitEmail () {
margin: 0; margin: 0;
}</style> }</style>
` `
html_content = emailStyle+tinymce.activeEditor.getContent() // html_content = emailStyle+tinymce.activeEditor.getContent()
console.log(html_content) // console.log(html_content)
const table = element.closest('form').querySelector('.lineup-table table')
navigator.clipboard.write( // navigator.clipboard.write(
[new ClipboardItem( // [new ClipboardItem(
{ // {
'text/plain': new Blob([tinymce.activeEditor.getContent({format: "text"})], {type: 'text/plain'}), // // 'text/plain': new Blob([tinymce.activeEditor.getContent({format: "text"})], {type: 'text/plain'}),
'text/html': new Blob([html_content], {type: 'text/html'}) // 'text/plain': new Blob([table.innerText], {type: 'text/plain'}),
}) // 'text/html': new Blob([emailStyle+table.outerHTML], {type: 'text/html'})
]) // })
// ])
window.getSelection().removeAllRanges();
var range = document.createRange();
range.selectNode(table);
window.getSelection().addRange(range);
document.execCommand("copy");
window.getSelection().removeAllRanges();
}
moveToLineupSegment = (slot, segment_name) => {
if (!slot.classList.contains('lineup-slot')) {
slot = slot.closest('.lineup-slot')
if (!slot) {return}
}
const current_lineup_segment = slot.closest('.lineup-segment')
if (current_lineup_segment.classList.contains(segment_name)) {
return
}
const lineup = slot.closest('.event-lineup')
const newParent = lineup.querySelector(`.lineup-segment.${segment_name} .slot-set`)
newParent.append(slot)
}
function initSlots () {
document.querySelectorAll('.lineup-slot').forEach(slot=>{
if (slot.dataset.initialLineupSegment) {
moveToLineupSegment(slot, slot.dataset.initialLineupSegment)
slot.removeAttribute('data-initial-lineup-segment')
}
})
}
addToStarting = (el) => {
const slot = el.closest('.lineup-slot')
this.blur()
}
removeToBench = (el) => {
const slot = el.closest('.lineup-slot')
this.blue()
} }
function insertLineup(direction, teamId, eventId, element) { function insertLineup(direction, teamId, eventId, element) {
@@ -597,21 +494,38 @@ function insertLineup(direction, teamId, eventId, element) {
.then((html) =>{ .then((html) =>{
const parser = new DOMParser(); const parser = new DOMParser();
const new_lineup_doc = parser.parseFromString(html, 'text/html') const new_lineup_doc = parser.parseFromString(html, 'text/html')
const new_lineup_doc_node = new_lineup_doc.firstElementChild.querySelector('[id*=event-lineup]') const new_lineup_doc_node = new_lineup_doc.firstElementChild.querySelector('.event-lineup')
const lineup_container = document.querySelector("#lineup-container") const main = document.querySelector("main")
const new_csrf_token = new_lineup_doc.querySelector('form input[name=csrfToken]').value
direction > 0 ? main.appendChild(new_lineup_doc_node) : main.insertBefore(new_lineup_doc_node, element.closest('[id*=event-lineup]'))
main.classList.remove(...main.classList)
main.classList.add('scroll-horizontal', 'u-spaceSidesSm', 'u-flex')
Array.from(document.querySelectorAll(".event-lineup")).forEach((bcLineup) => {
// main.classList.remove('.u-max1200', 'u-flexExpandSides')
bcLineup.classList.remove('u-spaceSidesNone', 'u-sm-spaceSidesAuto')
}
)
Array.from(document.querySelectorAll(".event-lineup .Panel")).forEach((bcLineupPanel) => {
bcLineupPanel.classList.remove('Panel--full')
})
for (input of document.querySelectorAll("form input[name=csrfToken]")){
input.value = new_csrf_token
}
direction > 0 ? lineup_container.appendChild(new_lineup_doc_node) : lineup_container.insertBefore(new_lineup_doc_node, element.closest('[id*=event-lineup]'))
initPage(); initPage();
}) })
} }
function initPage (){ function initPage (){
colorPositions(); initSlots();
initFlagsCheckboxes(); initFlagsCheckboxes();
refreshLineup(); for (bcLineup of document.querySelectorAll(".event-lineup")) {
for (bcLineup of document.querySelectorAll("[id^=event-lineup]")) { bcLineup.dispatchEvent(lineupChangedEvent)
options = { options = {
animation: 150, animation: 150,
handle: ".Panel-cell:has(.drag-handle), .Panel-cell:has(.sequence)", handle: ".Panel-cell:has(.drag-handle), .Panel-cell:has(.sequence)",
@@ -622,32 +536,77 @@ function initPage (){
pull: [bcLineup.id], pull: [bcLineup.id],
}, },
onAdd: function (/**Event*/ evt) { onAdd: function (/**Event*/ evt) {
console.log("added to lineup"); bcLineup.dispatchEvent(lineupChangedEvent)
// Add to Lineup
var itemEl = evt.item; // dragged HTMLElement
refreshLineup();
}, },
onUpdate: function (/**Event*/ evt) { onUpdate: function (/**Event*/ evt) {
console.log("update to lineup"); bcLineup.dispatchEvent(lineupChangedEvent)
// var itemEl = evt.item; // dragged HTMLElement
// refresh_lineup_order(itemEl);
refreshLineup();
}, },
}; };
new Sortable.create(bcLineup.querySelector("[id^=lineup-starting] .slot-set"), options); new Sortable.create(bcLineup.querySelector(".lineup-segment.starting .slot-set"), options);
new Sortable.create(bcLineup.querySelector("[id^=lineup-positiononly] .slot-set"), options); new Sortable.create(bcLineup.querySelector(".lineup-segment.position-only .slot-set"), options);
options["sort"] = false; new Sortable.create(bcLineup.querySelector(".lineup-segment.bench .slot-set"), {...options, sort:false});
new Sortable.create(bcLineup.querySelector("[id^=lineup-bench] .slot-set"), options); new Sortable.create(bcLineup.querySelector(".lineup-segment.out .slot-set"), {...options, sort:false, group:{...options.group, put:[]}});
new Sortable.create(bcLineup.querySelector("[id^=lineup-out] .slot-set"), {...options, group:{...options.group, put:[]}});
} }
for (lineup_slot of document.querySelectorAll("[id^=lineup-out] .lineup-slot")) { // for (lineup_slot of document.querySelectorAll(".lineup-segment.out .lineup-slot")) {
const cells = lineup_slot.querySelectorAll('.Panel-cell:has(.sequence), .Panel-cell:has(.drag-handle), .Panel-cell:has(.position-select-box), div.position-label-flags') // const cells = lineup_slot.querySelectorAll('.Panel-cell:has(.sequence), .Panel-cell:has(.drag-handle), .Panel-cell:has(.position-select-box), button:has(+.position-label-flags)')
Array.from(cells).forEach(cell=>{ // Array.from(cells).forEach(cell=>{
cell.classList.add('u-hidden') // cell.classList.add('u-hidden')
}) // })
// }
}
function mailToLink(el, protocol) {
const {to, bcc} = el.dataset
const subject = document.getElementById('email-subject').value
const email_body = document.getElementById('email-editor').value
const url = `${protocol}://compose?recipient=${to}&bcc=${bcc}&subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(email_body)}`
console.log(url)
// location.href=`mailto:${to}${params}`
const windowRef = window.open(url, '_blank');
windowRef.focus();
}
function sendAvailabilityReminder(element, eventId, memberIds, csrf_token) {
const icon = element.querySelector('svg')
const button_text = element.querySelector('span')
icon.classList.toggle('u-hidden')
button_text.classList.toggle('u-hidden')
const loader = '<span class="PulseAnimation"><span class="PulseAnimation-dot"></span><span class="PulseAnimation-dot"></span><span class="PulseAnimation-dot"></span></span>'
const loader_node = new DOMParser().parseFromString(loader, "text/html").firstChild.querySelector('span');
element.appendChild(loader_node)
element.blur();
const data = new FormData();
const url = "../availability_reminders"
data.append('eventId', eventId)
for (var i = 0; i < memberIds.length; i++) {
data.append('memberIds[]', memberIds[i]);
} }
console.log(data)
fetch(url, {
method: "POST",
body: data,
headers: {
'CSRF-Token': csrf_token
}
})
.then((response) => {
if (response.ok) {
console.log(response)
return response.text();
} else {
return Promise.reject(response.text());
}
})
.finally(()=>{
loader_node.remove()
icon.classList.toggle('u-hidden')
button_text.classList.toggle('u-hidden')
})
console.log(element, eventId, memberIds)
} }
document.addEventListener('DOMContentLoaded', initPage) document.addEventListener('DOMContentLoaded', initPage)

View File

@@ -1,6 +1,7 @@
var express = require("express"); const express = require("express");
var passport = require("passport"); const passport = require("passport");
var TeamsnapStrategy = require("passport-teamsnap"); const TeamsnapStrategy = require("passport-teamsnap");
const {teamsnapCallback} = require('../lib/utils')
// const {teamsnap} = require("../app"); // const {teamsnap} = require("../app");
// Configure the TeamSnap strategy for use by Passport. // Configure the TeamSnap strategy for use by Passport.
// //
@@ -21,20 +22,17 @@ passport.use(
proxy: true proxy: true
}, },
async function (req, accessToken, refreshToken, profile, done) { async function (req, accessToken, refreshToken, profile, done) {
json = JSON.parse(profile._raw); // json = JSON.parse(profile._raw);
field_from_collection = (field_name) => { const new_profile = {
return json.collection.items[0].data.filter( access_token: accessToken,
(e) => e.name == field_name };
)[0].value; ['id', 'email', 'first_name', 'last_name', 'managed_team_ids'].forEach(
} k => {
const new_profile = { access_token: accessToken }; new_profile[k] = profile.data[0].get(k)
new_profile["id"] = field_from_collection("id") })
new_profile["email"] = field_from_collection("email")
new_profile["first_name"] = field_from_collection("first_name")
new_profile["last_name"] = field_from_collection("last_name")
req.session.teamsnap_access_token = accessToken; req.session.teamsnap_access_token = accessToken;
await initTeamsnap(process.env["TEAMSNAP_CLIENT_ID"], accessToken) await initTeamsnap(accessToken)
return done(null, new_profile); return done(null, new_profile);
} }
) )
@@ -59,6 +57,7 @@ passport.serializeUser(function (user, cb) {
first_name: user.first_name, first_name: user.first_name,
last_name: user.last_name, last_name: user.last_name,
accessToken: user.access_token, accessToken: user.access_token,
managed_team_ids: user.managed_team_ids
}); });
}); });
}); });
@@ -66,10 +65,12 @@ passport.serializeUser(function (user, cb) {
passport.deserializeUser(function (user, cb) { passport.deserializeUser(function (user, cb) {
process.nextTick(async function () { process.nextTick(async function () {
console.log("L#68 deserializing user id", user.id); console.log("L#68 deserializing user id", user.id);
if (!teamsnap.isAuthed()){ try {
await initTeamsnap(process.env["TEAMSNAP_CLIENT_ID"], user.accessToken) await initTeamsnap(user.accessToken)
return cb(null, user);
} catch (err) {
return cb(err)
} }
return cb(null, user);
}); });
}); });
@@ -86,9 +87,11 @@ var router = express.Router();
router.get("/login", function (req, res, next) { router.get("/login", function (req, res, next) {
// https://stackoverflow.com/a/73056806/20522015 // https://stackoverflow.com/a/73056806/20522015
returnTo = req.session.returnTo; returnTo = req.session.returnTo;
// req.session.regenerate(); // this is not working right as of now... if (req.user?.accessToken){
req.session.returnTo = returnTo; res.redirect(returnTo || "/");
res.render("login", {layout:"layouts/main"}); } else {
res.render("login", {layout:"layouts/main"});
}
}); });
/* GET /login/federated/teamsnap /* GET /login/federated/teamsnap
@@ -125,10 +128,9 @@ router.get(
}) })
); );
const initTeamsnap = async (clientID, accessToken) => { const initTeamsnap = async (accessToken) => {
teamsnap.init(clientID); await teamsnap.auth(accessToken);
teamsnap.auth(accessToken); await teamsnap.loadCollections(teamsnapCallback);
await teamsnap.loadCollections();
await teamsnap.enablePersistence(); await teamsnap.enablePersistence();
} }
@@ -138,7 +140,7 @@ const ensureLoggedIn = (req, res, next) => {
res.redirect("/login"); res.redirect("/login");
// return next(); // return next();
} }
else{ else {
req.user = req.session.passport.user req.user = req.session.passport.user
next(); next();
} }

View File

@@ -3,6 +3,8 @@ const eventsController = require("../controllers/event");
const router = express.Router(); const router = express.Router();
const tsUtils = require("../lib/utils") const tsUtils = require("../lib/utils")
const {teamsnapCallback} = require("../lib/utils") const {teamsnapCallback} = require("../lib/utils")
const multer = require("multer");
const upload = multer()
// Middleware // Middleware
const loadEvent = (req,res,next) => { const loadEvent = (req,res,next) => {
@@ -68,7 +70,9 @@ router.use("/:team_id([0-9]+)/event/:event_id([0-9]+)", loadEvent)
// Routes // Routes
router.get("/:team_id([0-9]+)/schedule", eventsController.getEvents); router.get("/:team_id([0-9]+)/schedule", eventsController.getEvents);
router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)", eventsController.getEvent); router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)", eventsController.getEvent);
// router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup", eventsController.getLineup); router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/availability_reminders", upload.none(), eventsController.sendAvailabilityReminders)
// router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup_card", eventsController.getLineupCard);
router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/modal-confirm-availability-reminders/", eventsController.confirmModalAvailabilityReminders)
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/reset_availabilities",upload.none(), eventsController.submitResetAvailabilities)
module.exports = {router, loadEvent, loadEvents} module.exports = {router, loadEvent, loadEvents}

View File

@@ -57,6 +57,7 @@ router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/adjacent", doubleCs
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)/email", upload.none(), doubleCsrfProtection, eventsLineupController.getEventLineupEmail ) router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)/email", upload.none(), doubleCsrfProtection, eventsLineupController.getEventLineupEmail )
router.get ("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)", upload.none(), doubleCsrfProtection, eventsLineupController.getEventLineup); router.get ("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)", upload.none(), doubleCsrfProtection, eventsLineupController.getEventLineup);
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)", upload.none(), doubleCsrfProtection, eventsLineupController.postEventLineup); router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)", upload.none(), doubleCsrfProtection, eventsLineupController.postEventLineup);
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)/delete", upload.none(), eventsLineupController.submitDeleteEventLineupEntries);
module.exports = {router, loadEventLineup} module.exports = {router, loadEventLineup}

View File

@@ -30,6 +30,8 @@ router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/sheet", async (req,res) =>
} }
) )
router.get("/lineup/sheet/blank", eventsSheetController.getEventSheetBlank )
router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)/sheet", upload.none(), eventsSheetController.getEventSheet ) router.post("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup/:event_lineup_id([0-9]+)/sheet", upload.none(), eventsSheetController.getEventSheet )
module.exports = {router} module.exports = {router}

View File

@@ -27,4 +27,10 @@ router.get("/", (req,res,next) => {
router.get("/:team_id([0-9]+)/members", membersController.getMembers); router.get("/:team_id([0-9]+)/members", membersController.getMembers);
router.get("/modal-confirm/", (req,res) => {
const {title, body} = req.query
res.render('modal_confirm', {title, body} )
}
)
module.exports = {router, partials}; module.exports = {router, partials};

View File

@@ -189,7 +189,7 @@ a.Panel-row {
align-items: center; align-items: center;
} }
div[id^="event-lineup"] { div.event-lineup {
max-width: 576px; max-width: 576px;
counter-reset: lineup-sequence-counter 0; counter-reset: lineup-sequence-counter 0;
margin-left: 8px; margin-left: 8px;
@@ -289,17 +289,31 @@ li .availability-status-code- {
} }
div[id^="event-lineup"] .Panel { div.event-lineup {
&.position-only .Panel-cell:has(.sequence), &.bench .Panel-cell:has(.sequence), &.out .Panel-cell:has(.sequence){ .lineup-segment {
display: none; &:has(input.Toggle-input:not(:checked)) {
} &.out {
.Panel-cell:has(.SelectBox),
&.out { .Panel-cell:has(.drag-handle),
.Panel-cell { button:has(+.position-label-flags),
&:has(.sequence), .drag-handle, .SelectBox { button.addToStarting,
// display: none; button.addToBench
{
display: none;
}
} }
} }
&.bench, &.position-only, &.out {
.Panel-cell:has(.sequence) {
display: none;
}
&.bench button.addToBench {
display: none;
}
}
&.starting button.addToStarting, &.position-only button.addToStarting {
display: none;
}
} }
} }
@@ -311,8 +325,8 @@ div[id^="event-lineup"] .Panel {
@media (max-width: 480px){ @media (max-width: 480px){
.Panel--full { .Panel--full {
border-radius: 0; border-radius: 0;
margin-right: -16px; // margin-right: -16px;
margin-left: -16px; // margin-left: -16px;
border-right: none; border-right: none;
border-left: none; border-left: none;
}} }}
@@ -354,8 +368,16 @@ div[id^="event-lineup"] .Panel {
.Panel .Panel{ .Panel .Panel{
// padding: 0; // padding: 0;
// border-radius: 0; // border-radius: 0;
border: none; // border: none;
// border-top: 1px solid #d6d6d6; // border-top: 1px solid #d6d6d6;
// border-bottom: 1px solid #d6d6d6; // border-bottom: 1px solid #d6d6d6;
margin: 0; margin: 8px;
} }
.scroll-horizontal {
overflow-x: scroll;
}
button:has(+.position-label-flags :checked) {
@extend .Button--blue
}

File diff suppressed because it is too large Load Diff

View File

@@ -12,21 +12,23 @@
</div> </div>
</div> </div>
<div class=" Panel-footer u-flex u-flexJustifyAround u-padSm"> <div class=" Panel-footer u-flex u-flexJustifyAround u-padSm">
<a class="Button" href="/{{team.id}}/event/{{event.id}}"> <div class="u-maxWidthXs">
<span>{{{embeddedSvgFromPath "/bootstrap-icons/calendar.svg"}}}</span> <a class="Button" href="/{{team.id}}/event/{{event.id}}">
<span class="u-hidden u-xs-inline">Details</span> <span>{{{embeddedSvgFromPath "/bootstrap-icons/calendar.svg"}}}</span>
</a> <span class="u-hidden">Details</span>
<a class="Button" href="/{{team.id}}/event/{{event.id}}/lineup"> </a>
<span>{{{embeddedSvgFromPath "/bootstrap-icons/clipboard.svg"}}}</span> <a class="Button" href="/{{team.id}}/event/{{event.id}}/lineup">
<span class="u-hidden u-xs-inline">Lineup</span> <span>{{{embeddedSvgFromPath "/bootstrap-icons/clipboard.svg"}}}</span>
</a> <span class="u-hidden">Lineup</span>
<a class="Button" href="/{{team.id}}/event/{{event.id}}/sheet"> </a>
<span>{{{embeddedSvgFromPath "/bootstrap-icons/file-earmark.svg"}}}</span> <a class="Button" href="/{{team.id}}/event/{{event.id}}/sheet">
<span class="u-hidden u-xs-inline">Sheet</span> <span>{{{embeddedSvgFromPath "/bootstrap-icons/file-earmark.svg"}}}</span>
</a> <span class="u-hidden">Sheet</span>
<a class="Button" href="https://go.teamsnap.com/{{team.id}}/schedule/view_game/{{event.id}}"> </a>
<span>{{{embeddedSvgFromPath "/media/teamsnap_star.svg"}}}</span> <a class="Button" href="https://go.teamsnap.com/{{team.id}}/schedule/view_game/{{event.id}}">
<span class="u-hidden u-xs-inline">TeamSnap</span> <span>{{{embeddedSvgFromPath "/media/teamsnap_star.svg"}}}</span>
</a> <span class="u-hidden">TeamSnap</span>
</a>
</div>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,38 @@
<div id="modal" class="Modal Modal--clickableBg">
<div class="Modal-content">
<div onclick="javascript:this.closest('.Modal').remove();">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg" "Modal-iconDismiss"}}}</div>
<div class="Modal-header">
<div class="Modal-title">Send Reminders</div>
</div>
<div class="Modal-body">
<div class="u-padSidesMd">
<strong>Send to players who have selected:</strong>
<div class="u-spaceTopSm"><div class="Checkbox">
<input class="Checkbox-input" type="checkbox" name="undecidedCheckBox" id="undecidedCheckBox" checked="" value>
<label class="Checkbox-label" for="undecidedCheckBox">Undecided</label>
</div>
<div class="Checkbox">
<input class="Checkbox-input" type="checkbox" name="maybeCheckbox" id="maybeCheckbox" value="2">
<label class="Checkbox-label" for="maybeCheckbox">Maybe</label>
</div>
<div class="Checkbox">
<input class="Checkbox-input" type="checkbox" name="attendingCheckbox" id="attendingCheckbox" value="1">
<label class="Checkbox-label" for="attendingCheckbox">Attending</label>
</div>
<div class="Checkbox u-padBottomNone">
<input class="Checkbox-input" type="checkbox" name="notAttendingCheckbox" id="notAttendingCheckbox" value="0">
<label class="Checkbox-label" for="notAttendingCheckbox">Not Attending</label>
</div>
</div>
</div>
</div>
<div class="Modal-footer">
<button class="Button Button--negative" role="button" type="button" onclick="javascript:this.closest('.Modal').remove();" data-confirm="cancel">
Cancel
</button>
<button class="Button Button--primary" role="button" type="button" data-confirm="yes">
Send
</button>
</div>
</div>
</div>

View File

View File

@@ -1,34 +1,34 @@
<div class="u-flex" id="lineup-container"> <div class="u-spaceSidesNone u-sm-spaceSidesAuto event-lineup" id="event-lineup-{{event.id}}" data-event-lineup-id="{{event_lineup.id}}" data-event-id="{{event.id}}">
<div class="u-spaceSidessAuto" id="event-lineup-{{event.id}}" data-event-lineup-id="{{event_lineup.id}}" data-event-id="{{event.id}}"> <form onsubmit="submitEventLineup(this,event)" action="/{{team.id}}/event/{{event.id}}/lineup/{{event_lineup.id}}">
<form onsubmit="onSubmit(this,event)" action="#"> <input type="hidden" name="event_lineup_id" value="{{event_lineup.id}}">
<input type="hidden" name="event_lineup_id" value="{{event_lineup.id}}"> {{!-- <input type="hidden" name="_csrf" value="{{csrfToken}}"> --}}
{{!-- <input type="hidden" name="_csrf" value="{{csrfToken}}"> --}} <input type="hidden" name="csrfToken" value="{{csrfToken}}">
<input type="hidden" name="csrfToken" value="{{csrfToken}}"> <div class="Panel Panel--full">
<div class="Panel Panel--full"> <div class="Panel-header u-padEndsSm">
<div class="Panel-header u-padEndsSm"> <h3 style="flex: 1 1 0%;">{{event.formattedTitle}}</h3>
<h3 style="flex: 1 1 0%;">{{event.formattedTitle}}</h3> <div class="Popup">
<div class="ButtonGroup"> <div class="ButtonGroup">
<button class="Button Button--orange" type="submit" formmethod="post"> <button class="Button Button--orange" type="submit" formmethod="post">
<div> <div>
<span id="teamsnap-icon">{{{embeddedSvgFromPath "/media/teamsnap_star.svg"}}}</span> <span id="teamsnap-icon" class="hideOnLoading">{{{embeddedSvgFromPath "/media/teamsnap_star.svg"}}}</span>
<span id="waiting-icon" class="u-hidden">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/loader.svg" "Icon--loader"}}}</span> <span id="waiting-icon" class="u-hidden showOnLoading">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/loader.svg" "Icon--loader"}}}</span>
<span id="success-icon" class="u-hidden">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/check.svg"}}}</span> <span id="success-icon" class="u-hidden showOnSuccess">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/check.svg"}}}</span>
<span id="failure-icon" class="u-hidden">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg"}}}</span> <span id="failure-icon" class="u-hidden showOnFailure">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg"}}}</span>
Save Save
</div> </div>
</button> </button>
<div class="Button Button--orange .u-padSidesXs Popup" onclick="togglePopup(this)"> <div class="Button Button--orange .u-padSidesXs Popup-toggle" data-control="popup" data-open="event-lineup-more-actions-{{event_lineup.id}}">
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/caret-down.svg"}}} {{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/caret-down.svg"}}}
<div class="Popup-container Popup-container--down Popup-container--right" style="width: 200px"> <div class="Popup-container Popup-container--down Popup-container--right" style="width: 200px" data-popup="event-lineup-more-actions-{{event_lineup.id}}">
<div class="Popup-content u-textDecorationNone"> <div class="Popup-content u-textDecorationNone">
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="emailModal(this, '{{event_lineup.id}}/email')"> <a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="emailModal(this, '{{event_lineup.id}}/email')">
{{{embeddedSvgFromPath "/bootstrap-icons/envelope.svg"}}} {{{embeddedSvgFromPath "/bootstrap-icons/envelope.svg"}}}
<span>Generate Email</span> <span>Generate Email</span>
</a> </a>
<hr class="Divider u-spaceEndsNone"> <hr class="Divider u-spaceEndsNone">
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="../sheet"> <a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="/{{team.id}}/event/{{event.id}}/sheet">
{{{embeddedSvgFromPath "/bootstrap-icons/book.svg"}}} <span>{{{embeddedSvgFromPath "/bootstrap-icons/file-earmark.svg"}}}</span>
<span>Lineup Card</span> <span class="u-hidden u-xs-inline">Game Sheet</span>
</a> </a>
<hr class="Divider u-spaceEndsNone"> <hr class="Divider u-spaceEndsNone">
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="insertLineup(1, {{team.id}}, {{event.id}}, this)"> <a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="insertLineup(1, {{team.id}}, {{event.id}}, this)">
@@ -40,85 +40,93 @@
{{{embeddedSvgFromPath "/bootstrap-icons/caret-left.svg"}}} {{{embeddedSvgFromPath "/bootstrap-icons/caret-left.svg"}}}
<span>Insert previous lineup</span> <span>Insert previous lineup</span>
</a> </a>
<hr class="Divider u-spaceEndsNone">
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="openAvailabilityReminderModal(this, {{team.id}}, {{event.id}})">
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/send.svg"}}}
<span>Availability Reminders</span>
</a>
<hr class="Divider u-spaceEndsNone">
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="confirmModal(this, {title:'Reset Availabilities',body:'Are sure you want to reset availabilities?'}, submitResetAvailabilities, {team_id:{{team.id}}, event_id:{{event.id}} })";>
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/refresh.svg"}}}
<span>Reset All Availabilities</span>
</a>
<hr class="Divider u-spaceEndsNone">
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="confirmModal(this, {title:'Clear Lineup',body:'Are sure you want to clear lineup?'}, submitClearLineup, {team_id:{{team.id}}, event_id:{{event.id}}, event_lineup_id:{{event_lineup.id}} })">
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/trash.svg"}}}
<span>Clear Lineup</span>
</a>
<div class="u-hidden">
<hr class="Divider u-spaceEndsNone">
<span class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="console.log('not implemented yet')">
<span>Publish</span>
</span>
</div>
</div> </div>
</div> </div>
</div>
</div>
</div>
<div class=" Panel-body u-padEndsSm">
<div class=" u-padSidesSm">
<div class="date">{{dateFormat event.startDate "ddd, MMM D h:mm A" }}</div>
<div class="location">{{event.locationName}}</div>
</div>
<div class=" availability-bar fullwidth">
</div>
</div>
</div>
<div id="lineup-starting-{{event.id}}" class="Panel u-maxWidthSm starting Panel--fullWidthMobile Panel--full">
<div class="Panel-body">
<div class="Panel-row Panel-title u-padXs">
<i>{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-check.svg"}}}</i>
<span>Starting Lineup</span>
</div>
<div class=" Panel-row Grid Grid--fit u-textBold u-textCenter u-padXs">
{{#each (positions)}}
<div class="Grid-cell position-status">{{this}}</div>
{{/each}}
</div>
<div class="slot-set">
{{#each members}}
{{#if (isInStartingLineup this)}}
{{> slot member=this}}
{{/if}}
{{/each}}
</div>
</div>
</div>
<div id="lineup-positiononly-{{event.id}}" class="Panel u-maxWidthSm position-only Panel--full">
<div class="Panel-row Panel-title u-padXs">
{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-check.svg"}}}
<span>Position Only</span>
</div>
<div class="slot-set">
{{#each members}}
{{#if (isInPositionOnly this)}}
{{> slot member=this}}
{{/if}}
{{/each}}
</div>
</div>
<div id="lineup-bench-{{event.id}}" class="Panel u-maxWidthSm bench Panel--full">
<div class="Panel-row Panel-title u-padXs">
{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-minus.svg"}}}
<span>Bench</span>
</div>
<div class="slot-set">
{{#each members}}
{{#if (isInBench this)}}
{{> slot member=this event=../event}}
{{/if}}
{{/each}}
</div>
</div>
<div id="lineup-out-{{event.id}}" class="Panel u-maxWidthSm out Panel--full">
<div class="Panel-row Panel-title u-padXs u-flex">
<div><span style="flex: 1 1 0%;">{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-x.svg"}}}Out</span></div>
<div class="u-flexGrow1"></div>
<div class="Toggle">
<input class="Toggle-input" type="checkbox" id="enable-slots" onclick="toggleChildSlots(this);">
<label class="Toggle-label" for="enable-slots"></label>
</div> </div>
</div> </div>
<div class="slot-set">
{{#each members}}
{{#if (isInOut this)}}
{{> slot member=this}}
{{/if}}
{{/each}}
</div> </div>
</div> </div>
</form> <div class=" Panel-body u-padEndsSm">
<div class=" u-padSidesSm">
<div class="date">{{dateFormat event.startDate "ddd, MMM D h:mm A" }}</div>
<div class="location">{{event.locationName}}</div>
</div>
<div class="availability-bar fullwidth">
{{> availability_bar availabilitySummary=availabilitySummary}}
</div>
</div>
</div> </div>
<div class="Panel u-maxWidthSm lineup-segment starting Panel--fullWidthMobile Panel--full">
<div class="Panel-body">
<div class="Panel-row Panel-title u-padXs">
<i>{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-check.svg"}}}</i>
<span>Starting Lineup</span>
</div>
<div class=" Panel-row Grid Grid--fit u-textBold u-textCenter u-padXs">
{{#each (positions)}}
<div class="Grid-cell position-status" data-value="{{this}}">{{this}}</div>
{{/each}}
</div>
<div class="slot-set">
</div>
</div>
</div>
<div class="Panel u-maxWidthSm lineup-segment position-only Panel--full">
<div class="Panel-row Panel-title u-padXs">
{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-check.svg"}}}
<span>Position Only</span>
</div>
<div class="slot-set">
</div>
</div>
<div class="Panel u-maxWidthSm lineup-segment bench Panel--full">
<div class="Panel-row Panel-title u-padXs">
{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-minus.svg"}}}
<span>Bench</span>
</div>
<div class="slot-set">
{{#loadSlots}}
{{>slot member=member event_lineup=event_event_lineup availablity=availability}}
{{/loadSlots}}
</div>
</div>
<div class="Panel u-maxWidthSm lineup-segment out Panel--full">
<div class="Panel-row Panel-title u-padXs u-flex">
<div><span style="flex: 1 1 0%;">{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-x.svg"}}}Out</span></div>
<div class="u-flexGrow1"></div>
<div class="Toggle">
<input class="Toggle-input" type="checkbox" id="enable-slots">
<label class="Toggle-label" for="enable-slots"></label>
</div>
</div>
<div class="slot-set">
</div>
</div>
</form>
</div> </div>

View File

@@ -0,0 +1,10 @@
<div id="availability-reminder-modal-{{event.id}}" class="Modal Modal--clickableBg">
<div class="Modal-content">
<div onclick="javascript:this.closest('.Modal').remove();">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg" "Modal-iconDismiss"}}}</div>
<div class="Modal-header">
<div class="Modal-title">Send Reminders</div>
</div>
<div class="Modal-body">
</div>
</div>
</div>

View File

@@ -1,5 +1,4 @@
<div id="modal" class="Modal Modal--clickableBg"> <div id="modal" class="Modal Modal--clickableBg">
<div class="Modal-bgDismiss" onclick="javascript:this.closest('.Modal').remove();tinymce.remove();"></div>
<div class="Modal-content"> <div class="Modal-content">
<div onclick="javascript:this.closest('.Modal').remove();tinymce.remove();">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg" "Modal-iconDismiss"}}}</div> <div onclick="javascript:this.closest('.Modal').remove();tinymce.remove();">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg" "Modal-iconDismiss"}}}</div>
<div class="Modal-header"> <div class="Modal-header">
@@ -9,21 +8,37 @@
<form> <form>
<div class="FieldGroup"> <div class="FieldGroup">
<label class="FieldGroup-label">Subject</label> <label class="FieldGroup-label">Subject</label>
<input class="Input" type="text" value="{{dateFormat event.startDate "ddd, MMM D, YYYY h:mm A" }}, {{ event.locationName }}, ({{#if (isAway event) }}@{{/if}}{{ event.opponentName }})"> <input class="Input" id="email-subject" type="text" value="{{dateFormat event.startDate "ddd, MMM D, YYYY h:mm A" }}, {{ event.locationName }}, ({{#if (isAway event) }}@{{/if}}{{ event.opponentName }})">
</div> </div>
<div class="FieldGroup"> <div class="FieldGroup">
<label class="FieldGroup-label">Body</label> <label class="FieldGroup-label">Body</label>
<textarea id="email-editor" class="Input"></textarea> <textarea id="email-editor" class="Input"></textarea>
</div> </div>
<div class="FieldGroup"> <div class="FieldGroup">
<label class="FieldGroup-label">Lineup</label> <label class="FieldGroup-label">
<div class="lineup-email">{{>email_table}}</div> Lineup
</div> <button class="Button Button--smallSquare" role="button" type="button" onclick="copyEmailTable(this)">
<div class="FieldGroup"> {{{embeddedSvgFromPath "/bootstrap-icons/clipboard-fill.svg"}}}
<button class="Button" role="button" onclick="submitEmail();">Submit</button> </button>
</label>
<div class="lineup-email lineup-table">{{>email_table}}</div>
</div> </div>
</form> </form>
</div> </div>
<div class="Modal-footer">
<button class="Button" role="button" type="button" onclick="mailToLink(this, 'readdle-spark');",
data-to="{{user.email}}"
data-bcc="{{joinMemberEmailAddresses (filterNonPlayers members)}}">
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/mail.svg"}}}
Spark Mail
</button>
<button class="Button" role="button" type="button" onclick="mailToLink(this, 'mailto');",
data-to="{{user.email}}"
data-bcc="{{joinMemberEmailAddresses (filterNonPlayers members)}}">
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/mail.svg"}}}
Mail
</button>
</div>
</div> </div>
</div> </div>
</script>

View File

@@ -1,4 +1,4 @@
<table> <table class="lineup-table">
<thead> <thead>
<tr> <tr>
<th class="title-cell" colSpan=3> <th class="title-cell" colSpan=3>

View File

@@ -1,10 +1,10 @@
<div class="Panel-expandableRow lineup-slot"> <div class="Panel-expandableRow lineup-slot" data-initial-lineup-segment="{{initial_lineup_segment}}">
<input type="hidden" name="label" value="{{member.benchcoach.eventLineupEntry.label}}"> <input type="hidden" name="label" value="{{eventLineupEntry.label}}">
<input type="hidden" name="flags" value="{{flagsString member.benchcoach.eventLineupEntry.flags}}"> <input type="hidden" name="flags" value="{{flagsString eventLineupEntry.flags}}">
<input type="hidden" name="sequence" value="{{member.benchcoach.eventLineupEntry.sequence}}"> <input type="hidden" name="sequence" value="{{eventLineupEntry.sequence}}">
<input type="hidden" name="eventId" value="{{event.id}}"> <input type="hidden" name="eventId" value="{{event.id}}">
<input type="hidden" name="eventLineupEntryId" value="{{member.benchcoach.eventLineupEntry.id}}"> <input type="hidden" name="eventLineupEntryId" value="{{eventLineupEntry.id}}">
<input type="hidden" name="availabilityStatusCode", value="{{member.benchcoach.availability?.statusCode}}"> <input type="hidden" name="availabilityStatusCode", value="{{#if availability}}{{availability.statusCode}}{{/if}}">
<input type="hidden" name="memberId" value="{{member.id}}"> <input type="hidden" name="memberId" value="{{member.id}}">
<input type="hidden" name="lastName" value="{{member.lastName}}"> <input type="hidden" name="lastName" value="{{member.lastName}}">
<input type="hidden" name="firstName" value="{{member.firstName}}"> <input type="hidden" name="firstName" value="{{member.firstName}}">
@@ -17,13 +17,40 @@
</div> </div>
<div class="Panel-cell u-padXs u-sizeFill u-flex"> <div class="Panel-cell u-padXs u-sizeFill u-flex">
<div <div
class="availability-status-code-{{ class="Popup availability-status-code-{{
member.benchcoach.availability?.statusCode availability?.statusCode
}}" }}"
> >
{{#if member.benchcoach.availability}}{{{avail_status_code_icon member.benchcoach.availability.statusCode}}}{{/if}} {{#if availability}}
{{#with availability}}
<div class="Popup">
<button class="Popup-toggle Button Button--smallSquare {{avail_status_code_class statusCode}}"
type="button"
data-control="popup"
data-open="availablility-popup-{{eventId}}-{{memberId}}"
>
{{#if notes}}{{{embeddedSvgFromPath "/bootstrap-icons/asterisk.svg"}}}{{else}}{{{avail_status_code_icon statusCode}}}{{/if}}
</button>
<div class="Popup-container Popup-container--left" data-popup="availablility-popup-{{eventId}}-{{memberId}}">
<div class="Popup-content u-padSm u-textCenter">
<h3 class="u-spaceBottomSm">Availability</h3>
{{#if notes}}
<p class="u-textLeft">“ <i>{{notes}}</i> ”</p>
{{else}}
<p class="u-textLeft">No notes.</p>
{{/if}}
<button type="button" class="Button u-spaceTopSm" onclick="sendAvailabilityReminder(this, {{eventId}}, ['{{memberId}}'], {{csrfToken}})">
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/send.svg"}}}
<span>Send Reminder</span>
</button>
</div>
</div>
</div>
{{/with}}
{{/if}}
</div> </div>
<div class="u-fontSizeLg u-textNoWrap">
<div class="u-fontSizeLg u-textNoWrap">
<span class="lastname"> <span class="lastname">
{{member.lastName}} {{member.lastName}}
</span> </span>
@@ -35,14 +62,27 @@
</span> </span>
</div> </div>
<div class="u-flexGrow1"></div> <div class="u-flexGrow1"></div>
<div class="position-label-flags"> <button type="button" class="Button Button--smallSquare addToBench" onclick="moveToLineupSegment(this, 'bench');this.blur()">
<div class="Checkbox Checkbox--inline"> {{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg"}}}
<input class="Checkbox-input" type="checkbox" name="flag-drd" id="flag-drd-{{member.id}}-{{member.benchcoach.eventLineupEntry.id}}" onclick="refreshLineup()"> </button>
<label class="Checkbox-label" for="flag-drd-{{member.id}}-{{member.benchcoach.eventLineupEntry.id}}">DR<small>d</small></label> <button type="button" class="Button Button--smallSquare addToStarting" onclick="moveToLineupSegment(this, 'starting');this.blur()">
</div> {{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/plus.svg"}}}
<div class="Checkbox Checkbox--inline"> </button>
<input class="Checkbox-input" type="checkbox" name="flag-dhd" id="flag-dhd-{{member.id}}-{{member.benchcoach.eventLineupEntry.id}}" onclick="refreshLineup()"> <div class="Popup">
<label class="Checkbox-label" for="flag-dhd-{{member.id}}-{{member.benchcoach.eventLineupEntry.id}}">DH<small>d</small></label> <button type="button" class="Popup-toggle Button Button--smallSquare" onclick="this.closest('div').querySelector('.Popup-container').classList.toggle('is-open');this.blur();" href="javascript:void(0)">
{{{embeddedSvgFromPath "/bootstrap-icons/three-dots.svg"}}}
</button>
<div class="Popup-container Popup-container--rightHang position-label-flags">
<div class="Popup-content u-padSm u-textCenter">
<div class="Checkbox Checkbox--inline">
<input class="Checkbox-input" type="checkbox" name="DRd" id="flag-drd-{{member.id}}-{{eventLineupEntry.id}}">
<label class="Checkbox-label" for="flag-drd-{{member.id}}-{{eventLineupEntry.id}}">DR<small>d</small></label>
</div>
<div class="Checkbox Checkbox--inline">
<input class="Checkbox-input" type="checkbox" name="DHd" id="flag-dhd-{{member.id}}-{{eventLineupEntry.id}}">
<label class="Checkbox-label" for="flag-dhd-{{member.id}}-{{eventLineupEntry.id}}">DH<small>d</small></label>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -53,7 +93,7 @@
-- --
</option> </option>
{{#each (positions)}} {{#each (positions)}}
<option value="{{this}}" {{#if (comparePositionWithFlags this ../member.benchcoach.eventLineupEntry)}}selected{{/if}}> <option value="{{this}}" {{#if (comparePositionWithFlags this ../eventLineupEntry)}}selected{{/if}}>
{{this}} {{this}}
</option> </option>
{{/each}} {{/each}}

View File

@@ -1,8 +1,8 @@
<div class="field-container"> <div class="field-container">
<img src="/media/baseball-diamond.svg" /> {{{embeddedSvgFromPath "/media/baseball-diamond.svg" 'baseball-diamond'}}}
{{#defenseLineup event_lineup_entries members}} {{#defenseLineup event_lineup_entries members}}
<div class="slot-set pos-{{this.position}}"> <div class="slot-set pos-{{this.position}}">
<table> <table class="striped">
<tbody> <tbody>
<tr class="slot"> <tr class="slot">
<th class="position"></th> <th class="position"></th>

View File

@@ -1,14 +1,16 @@
<table> <table>
<colgroup><col span="3" class="player"></colgroup> {{!-- <colgroup><col span="4" class="player"></colgroup> --}}
{{!-- <colgroup><col span="0" class="player-stats"></colgroup> --}} {{!-- <colgroup><col span="1" class="spacer"></colgroup> --}}
<colgroup><col span="4" class="position-capability"></colgroup> {{!-- <colgroup><col span="1" class="player-stats"></colgroup> --}}
{{!-- <colgroup><col span="4" class="position-capability"></colgroup>
<colgroup><col span="4" class="availability-on-day future"></colgroup> <colgroup><col span="4" class="availability-on-day future"></colgroup>
<colgroup><col span="4" class="availability-on-day past"></colgroup> <colgroup><col span="4" class="availability-on-day past"></colgroup> --}}
<thead> <thead>
<tr> <tr>
<th colspan="3" id="today-availability"> <th colspan="4" id="today-availability">
Available ({{availabilitySummary.playerGoingCount}}|{{availabilitySummary.playerMaybeCount}}) Available ({{availabilitySummary.playerGoingCount}}|{{availabilitySummary.playerMaybeCount}})
</th> </th>
<th class="spacer first-of-group last-of-group"></th>
<th class="player-stats"> <th class="player-stats">
<span class="decimal-point">.</span>AVG <span class="decimal-point">.</span>AVG
<span class="delimiter">/</span> <span class="delimiter">/</span>
@@ -17,34 +19,38 @@
<span class="decimal-point">.</span>SLG <span class="decimal-point">.</span>SLG
<span class="delimiter">:</span>PA <span class="delimiter">:</span>PA
</th> </th>
<th class="position-capability pitcher">P</th> <th class="position-capability pitcher first-of-group">P</th>
<th class="position-capability catcher">C</th> <th class="position-capability catcher">C</th>
<th class="position-capability infield">I</th> <th class="position-capability infield">I</th>
<th class="position-capability outfield">O</th> <th class="position-capability outfield last-of-group">O</th>
{{!-- <% for timepoint, i in timeline.select{|tp| tp[:comparison_to_selected]>0}.sort{|tp| -tp[:comparison_to_selected]}.each_with_index do%> --}} {{!-- <% for timepoint, i in timeline.select{|tp| tp[:comparison_to_selected]>0}.sort{|tp| -tp[:comparison_to_selected]}.each_with_index do%> --}}
{{#loopEvents upcoming_events}} {{#loopEvents upcoming_events}}
<th class="availability-on-day avail-today-plus-{{@index}}" date="{{this.startDate}}"><div>{{dateFormat this.startDate "ddd" }}</div></th> <th class="availability-on-day avail-today-plus-{{@index}} {{#ifEquals @index 0}}first-of-group{{/ifEquals}}{{#ifEquals @index 3}}last-of-group{{/ifEquals}}" date="{{this.startDate}}"><div>{{dateFormat this.startDate "ddd" }}</div></th>
{{/loopEvents}} {{/loopEvents}}
{{#loopEvents recent_events}} {{#loopEvents recent_events}}
<th class="availability-on-day avail-today-minus-{{@index}}" date="{{this.startDate}}"><div>{{dateFormat this.startDate "ddd" }}</div></th> <th class="availability-on-day avail-today-minus-{{@index}} {{#ifEquals @index 0}}first-of-group{{/ifEquals}}{{#ifEquals @index 3}}last-of-group{{/ifEquals}}" date="{{this.startDate}}"><div>{{dateFormat this.startDate "ddd" }}</div></th>
{{/loopEvents}} {{/loopEvents}}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{!-- <% by_member.select{|m,d| !m.is_non_player}.each_with_index do |(member, d), i|%> --}} {{!-- <% by_member.select{|m,d| !m.is_non_player}.each_with_index do |(member, d), i|%> --}}
{{#rosterHistory event event_lineup_entries members availabilities}} {{#rosterHistory event event_lineup_entries members availabilities}}
<tr id="roster-history-slot-<%= ::Temple::Utils.escape_html((i)) %>"> <tr class="roster-history-slot{{#if (isStarting this)}} starting-today{{/if}}">
<td class="is-present-checkbox available-status-code-{{this.benchcoach.availability.statusCode}}"> <td class="is-present-checkbox available-status-code-{{this.benchcoach.availability.statusCode}} first-of-group">
<span>■</span> <span>■</span>
</td> </td>
<td class="jersey-number available-status-code-{{this.benchcoach.availability.statusCode}}{{#if (isStarting this)}} starting{{/if}}"> <td class="jersey-number available-status-code-{{this.benchcoach.availability.statusCode}}">
{{this.jerseyNumber}} {{this.jerseyNumber}}
</td> </td>
<td class="player-name available-status-code-{{this.benchcoach.availability.statusCode}}{{#if (isStarting this)}} starting{{/if}}"> <td class="player-name available-status-code-{{this.benchcoach.availability.statusCode}}">
{{this.lastName}} {{this.lastName}}
</td> </td>
<td class="player-stats border-left border-right"> <td class="position available-status-code-{{this.benchcoach.availability.statusCode}} last-of-group">
<span>{{this.benchcoach.eventLineupEntry.label}}</span>
</td>
<td class="spacer"></td>
<td class="player-stats first-of-group last-of-group">
<span class="decimal-point">.</span> <span class="decimal-point">.</span>
<span class="avg">000</span> <span class="avg">000</span>
<span class="delimiter">/</span> <span class="delimiter">/</span>
@@ -56,20 +62,20 @@
<span class="delimiter">:</span> <span class="delimiter">:</span>
<span class="pa">00</span> <span class="pa">00</span>
</td> </td>
<td class="position-capability pitcher">{{positionCapabilityFor this "P"}}</td> <td class="position-capability pitcher first-of-group">{{positionCapabilityFor this "P"}}</td>
<td class="position-capability catcher">{{positionCapabilityFor this "C"}}</td> <td class="position-capability catcher">{{positionCapabilityFor this "C"}}</td>
<td class="position-capability infield">{{positionCapabilityFor this "IF"}}</td> <td class="position-capability infield">{{positionCapabilityFor this "IF"}}</td>
<td class="position-capability outfield">{{positionCapabilityFor this "OF"}}</td> <td class="position-capability outfield last-of-group">{{positionCapabilityFor this "OF"}}</td>
{{#loopEvents ../upcoming_events}} {{#loopEvents ../upcoming_events}}
{{#timepointForMember ../this ../../timeline this}} {{#timepointForMember ../this ../../timeline this}}
<td class="availability-on-day future available-status-code-{{this.availability.statusCode}}"> <td class="availability-on-day future available-status-code-{{this.availability.statusCode}} {{this.value}} {{#ifEquals @index 0}}first-of-group{{/ifEquals}}{{#ifEquals @index 3}}last-of-group{{/ifEquals}}">
{{this.value}} {{this.value}}
</td> </td>
{{/timepointForMember}} {{/timepointForMember}}
{{/loopEvents}} {{/loopEvents}}
{{#loopEvents ../recent_events}} {{#loopEvents ../recent_events}}
{{#timepointForMember ../this ../../timeline this}} {{#timepointForMember ../this ../../timeline this}}
<td class="availability-on-day past available-status-code-{{this.availability.statusCode}}"> <td class="availability-on-day past available-status-code-{{this.availability.statusCode}} {{this.value}} {{#ifEquals @index 0}}first-of-group{{/ifEquals}}{{#ifEquals @index 3}}last-of-group{{/ifEquals}}">
{{this.value}} {{this.value}}
</td> </td>
{{/timepointForMember}} {{/timepointForMember}}

View File

@@ -1,11 +1,11 @@
<link rel="stylesheet" href="/css/eventsheet.css"> <link rel="stylesheet" href="/css/eventsheet.css">
<body class="B5"> <body class="{{#if sheet_size}}{{sheet_size}}{{else}}B5{{/if}}">
<div class="sheet eventsheet" id="page-1"> <div class="sheet eventsheet {{#if sheet_layout}}{{sheet_layout}}{{else}}quarters{{/if}}" id="page-1">
<section id="defense-card"> <section class="NE" id="defense-card">
<header> <header>
<div class="event-title float-left"> <div class="event-title float-left">
{{event.formattedTitle}} {{dateFormat event.startDate "ddd, MMM D h:mm A" }} {{event.formattedTitle}}
</div> </div>
<div class="homeaway float-right"> <div class="homeaway float-right">
{{event.gameType}} {{event.gameType}}
@@ -31,18 +31,18 @@
</div> </div>
</div> </div>
</section> </section>
<section id="roster-and-history"> <section class="SW" id="roster-and-history">
<div class="roster-and-history"> <div class="container">
{{> roster_and_history {{> roster_and_history
event=event event=event
event_lineup_entries=event_lineup_entries event_lineup_entries=event_lineup_entries
members=members availabilities=availabilities members=members availabilities=availabilities
recent_events=recent_events recent_events=recent_events
upcoming_events=upcoming_events upcoming_events=upcoming_events
}} }}
</div> </div>
</section> </section>
<section class="lineup-card" id="lineup-card-dugout"> <section class="NW lineup-card dugout" id="lineup-card-dugout">
<header> <header>
<div class="float-left event-title">{{event.formattedTitle}}</div> <div class="float-left event-title">{{event.formattedTitle}}</div>
<div class="float-right homeaway">{{event.gameType}}</div> <div class="float-right homeaway">{{event.gameType}}</div>
@@ -80,9 +80,9 @@
</table> </table>
</div> </div>
</section> </section>
<section class="lineup-card" id="lineup-card-exchange"> <section class="SE lineup-card exchange" id="lineup-card-exchange">
<header> <header>
<div class="float-left event-title">{{event.formattedTitle}}</div> <div class="float-left event-title">{{event.formattedTitleForMultiTeam}}</div>
<div class="float-right homeaway">{{event.gameType}}</div> <div class="float-right homeaway">{{event.gameType}}</div>
</header> </header>
<div class="starting-lineup-table"> <div class="starting-lineup-table">
@@ -119,24 +119,18 @@
</div> </div>
</section> </section>
</div> </div>
<div class="sheet eventsheet" id="page-2"> <div class="sheet eventsheet {{#if sheet_layout}}{{sheet_layout}}{{else}}quarters{{/if}}" id="page-2">
<section id="defense-card" style="border:solid black;"> <section class="SE" id="front-cover">
<div id="defense-pane">
{{> defense_pane}}
</div>
</section>
<section id="front-cover">
<header> <header>
<div class="homeaway"> <div class="game-number">
<span>{{firstLetter event.gameType}}</span> {{event.label}}
</div> </div>
<div class="title"> <div class="title">
<span class="date-time">{{dateFormat event.startDate "ddd, MMM D h:mm A" }}</span> <span class="date-time">{{dateFormat event.startDate "ddd, MMM D h:mm A" }}</span>
<span class="location">{{event.locationName}}</span> <span class="location">{{event.locationName}}</span>
</div> </div>
<div class="game-number"> <div class="homeaway">
<span class="label">Game #</span> <span>{{firstLetter event.gameType}}</span>
<span class="value">XX</span>
</div> </div>
</header> </header>
<div style="display:block;max-height: 1em;background-color: lightgray;border-bottom: solid 2px black;"> <div style="display:block;max-height: 1em;background-color: lightgray;border-bottom: solid 2px black;">
@@ -149,17 +143,16 @@
</div> </div>
</div> </div>
{{# if event.opponentName}} {{# if event.opponentName}}
<div> <div class="conjuction">
<div style="text-align: center;font-family: &#39;Pacifico&#39;;">
<span>vs</span> <span>vs</span>
</div>
</div> </div>
<div class="opponent"> <div class="opponent">
<div> <div>
<span class="name">{{event.opponentName}}</span> <span class="name">{{event.opponentName}}</span>
</div> </div>
{{#if opponent_logo.mediumUrl }}
<img src="{{opponent_logo.mediumUrl}}"> <img src="{{opponent_logo.mediumUrl}}">
{{!-- <%= ::Temple::Utils.escape_html((image_tag opponent_logos[event.opponent.id].medium_url)) %> --}} {{/if}}
</div> </div>
<div class=""> <div class="">
<table> <table>
@@ -183,7 +176,70 @@
{{/if}} {{/if}}
</div> </div>
</section> </section>
<section class="lineup-card" id="lineup-card-dugout-blank"> <section class="NW blank" id="defense-card">
<header>
<div class="event-title float-left">
&nbsp;
</div>
<div class="homeaway float-right">
&nbsp;
</div>
</header>
<div>
<div id="defense-pane">
{{> defense_pane event_lineup_entries=null members=null}}
</div>
<div class="footer">
<table class="notes">
<tbody>
<tr>
<th>Notes</th>
</tr>
{{#repeat 3}}
<tr>
<td></td>
</tr>
{{/repeat}}
</tbody>
</table>
</div>
</div>
</section>
<section class="SW lineup-card exchange blank" id="lineup-card-exchange-blank">
<header></header>
<div class="starting-lineup-table">
<table>
<thead>
<tr>
<th colspan="4">
Starting
</th>
<th class="substitution">
Substitution
</th>
</tr>
</thead>
<tbody>
{{#repeat 12}}
<tr class="slot">
<th class="sequence">
</th>
<td class="player-name">
</td>
<td class="jersey-number">
</td>
<td class="position">
</td>
<td class="substitution">
</td>
</tr>
{{/repeat}}
</tbody>
</table>
</div>
</section>
<section class="NE lineup-card dugout blank" id="lineup-card-dugout-blank">
<header></header>
<div class="starting-lineup-table"> <div class="starting-lineup-table">
<table> <table>
<thead> <thead>
@@ -217,38 +273,5 @@
</table> </table>
</div> </div>
</section> </section>
<section class="lineup-card" id="lineup-card-exchange-blank">
<div class="starting-lineup-table">
<table>
<thead>
<tr>
<th colspan="4">
Starting
</th>
<th class="substitution">
Substitution
</th>
</tr>
</thead>
<tbody>
{{#repeat 12}}
<tr class="slot">
<th class="sequence">
</th>
<td class="player-name">
</td>
<td class="jersey-number">
</td>
<td class="position">
</td>
<td class="substitution">
</td>
</tr>
{{/repeat}}
</tbody>
</table>
</div>
</section>
</div> </div>
</body> </body>

View File

@@ -0,0 +1,248 @@
<link rel="stylesheet" href="/css/eventsheet.css">
<body class="{{page_size}} ">
<div class="sheet eventsheet {{layout}}" id="page-1">
<section class="NW" id="roster-and-history">
<div class="roster-and-history">
{{> roster_and_history
event=event
event_lineup_entries=event_lineup_entries
members=members availabilities=availabilities
recent_events=recent_events
upcoming_events=upcoming_events
}}
</div>
</section>
<section class="NE blank" id="defense-card">
<header>
<div class="event-title float-left">
</div>
<div class="homeaway float-right">
</div>
</header>
<div>
<div id="defense-pane">
{{> defense_pane event_lineup_entries=event_lineup_entries members=members}}
</div>
<div class="footer">
<table class="notes">
<tbody>
<tr>
<th>Notes</th>
</tr>
{{#repeat 3}}
<tr>
<td></td>
</tr>
{{/repeat}}
</tbody>
</table>
</div>
</div>
</section>
<section class="SW lineup-card dugout blank" id="lineup-card-dugout">
<header>
<div class="float-left event-title">{{event.formattedTitle}}</div>
<div class="float-right homeaway">{{event.gameType}}</div>
</header>
<div class="starting-lineup-table">
<table>
<thead>
<tr>
<th colspan="4">Starting</th>
<th class="substitution">Substitution</th>
</tr>
</thead>
<tbody>
{{#offenseLineup 11 event_lineup_entries members}}
<tr class="slot">
<th class="sequence{{#if this.member.lastName}} counter{{/if}}"></th>
<td class="player-name">{{this.member.lastName}}</td>
<td class="jersey-number">{{this.member.jerseyNumber}}</td>
<td class="position">{{this.label}}</td>
<td class="substitution"></td>
</tr>
{{/offenseLineup }}
{{#defenseLineup event_lineup_entries members}}
<tr class="slot">
{{#if (isInPositionOnly this.member)}}{{#if (comparePositionWithFlags "P" this.eventLineupEntry)}}
<th class="sequence">PO</th>
<td class="player-name">{{this.member.lastName}}</td>
<td class="jersey-number">{{this.member.jerseyNumber}}</td>
<td class="position">{{positionLabelWithoutFlags this.eventLineupEntry.label}}</td>
<td class="substitution"></td>
{{/if}}{{/if}}
</tr>
{{/defenseLineup}}
</tbody>
</table>
</div>
</section>
<section class="SE lineup-card exchange blank" id="lineup-card-exchange">
<header>
<div class="float-left event-title">{{event.formattedTitle}}</div>
<div class="float-right homeaway">{{event.gameType}}</div>
</header>
<div class="starting-lineup-table">
<table>
<thead>
<tr>
<th colspan="4">Starting</th>
<th class="substitution">Substitution</th>
</tr>
</thead>
<tbody>
{{#offenseLineup 11 event_lineup_entries members}}
<tr class="slot">
<th class="sequence {{#if this.member.lastName}}counter{{/if}}"></th>
<td class="player-name">{{this.member.lastName}}</td>
<td class="jersey-number">{{this.member.jerseyNumber}}</td>
<td class="position">{{this.label}}</td>
<td class="substitution"></td>
</tr>
{{/offenseLineup}}
{{#defenseLineup event_lineup_entries members}}
<tr class="slot">
{{#if (isInPositionOnly this.member)}}{{#if (comparePositionWithFlags "P" this.eventLineupEntry)}}
<th class="sequence">PO</th>
<td class="player-name">{{this.member.lastName}}</td>
<td class="jersey-number">{{this.member.jerseyNumber}}</td>
<td class="position">{{positionLabelWithoutFlags this.eventLineupEntry.label}}</td>
<td class="substitution"></td>
{{/if}}{{/if}}
</tr>
{{/defenseLineup}}
</tbody>
</table>
</div>
</section>
</div>
<div class="sheet eventsheet {{layout}}" id="page-2">
<section class="NW blank" id="defense-card">
<header>
<div class="event-title float-left">
&nbsp;
</div>
<div class="homeaway float-right">
&nbsp;
</div>
</header>
<div>
<div id="defense-pane">
{{> defense_pane event_lineup_entries=null members=null}}
</div>
<div class="footer">
<table class="notes">
<tbody>
<tr>
<th>Notes</th>
</tr>
{{#repeat 3}}
<tr>
<td></td>
</tr>
{{/repeat}}
</tbody>
</table>
</div>
</div>
</section>
<section class="NE blank" id="defense-card">
<header>
<div class="event-title float-left">
&nbsp;
</div>
<div class="homeaway float-right">
&nbsp;
</div>
</header>
<div>
<div id="defense-pane">
{{> defense_pane event_lineup_entries=null members=null}}
</div>
<div class="footer">
<table class="notes">
<tbody>
<tr>
<th>Notes</th>
</tr>
{{#repeat 3}}
<tr>
<td></td>
</tr>
{{/repeat}}
</tbody>
</table>
</div>
</div>
</section>
<section class="SW lineup-card exchange blank" id="lineup-card-exchange-blank">
<header></header>
<div class="starting-lineup-table">
<table>
<thead>
<tr>
<th colspan="4">
Starting
</th>
<th class="substitution">
Substitution
</th>
</tr>
</thead>
<tbody>
{{#repeat 12}}
<tr class="slot">
<th class="sequence">
</th>
<td class="player-name">
</td>
<td class="jersey-number">
</td>
<td class="position">
</td>
<td class="substitution">
</td>
</tr>
{{/repeat}}
</tbody>
</table>
</div>
</section>
<section class="SE lineup-card dugout blank" id="lineup-card-dugout-blank">
<header></header>
<div class="starting-lineup-table">
<table>
<thead>
<tr>
<th colspan="4">
Starting
</th>
<th class="substitution">
Substitution
</th>
</tr>
</thead>
<tbody>
{{!-- <% for i in (0...12) do%> --}}
{{#repeat 12}}
<tr class="slot">
<th class="sequence">
</th>
<td class="player-name">
</td>
<td class="jersey-number">
</td>
<td class="position">
</td>
<td class="substitution">
</td>
</tr>
{{/repeat}}
{{!-- <% end %> --}}
</tbody>
</table>
</div>
</section>
</div>
</body>

View File

@@ -21,15 +21,13 @@
</head> </head>
<body> <body>
<header> <header class="u-spaceBottomMd">
{{> navbar }} {{> navbar }}
{{{_sections.header}}} {{{_sections.header}}}
</header> </header>
<div class="u-padSidesMd u-xs-padSidesLg"> <main class="u-xs-spaceSidesMd">
<div class="u-max1200 u-flexExpandSides u-xs-size5of6 u-sm-size2of3 u-md-sizeFull u-padEndsLg u-sm-padEndsXl"> {{{ body }}}
{{{ body }}} </main>
</div>
</div>
</body> </body>
{{{script_tags scripts}}} {{{script_tags scripts}}}
</html> </html>

View File

@@ -0,0 +1,26 @@
<div id="modal" class="Modal Modal--clickableBg">
<div class="Modal-content">
<div onclick="javascript:this.closest('.Modal').remove();">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg" "Modal-iconDismiss"}}}</div>
<div class="Modal-header">
<div class="Modal-title">{{title}}</div>
</div>
<div class="Modal-body">
{{body}}
</div>
<div class="Modal-footer">
<button class="Button Button--negative" role="button" type="button" onclick="javascript:this.closest('.Modal').remove();" data-confirm="cancel">
Cancel
</button>
<button class="Button Button--primary" role="button" type="button" data-confirm="yes">
<span class="hideOnLoading">Yes</span>
<span id="success-icon" class="u-hidden showOnSuccess">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/check.svg"}}}</span>
<span id="failure-icon" class="u-hidden showOnFailure">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg"}}}</span>
<span class="PulseAnimation showOnLoading u-hidden">
<span class="PulseAnimation-dot"></span>
<span class="PulseAnimation-dot"></span>
<span class="PulseAnimation-dot"></span>
</span>
</button>
</div>
</div>
</div>

View File

@@ -1,5 +1,6 @@
<h1> <h1>
{{title}} {{title}}
<hr class="Divider" />
</h1> </h1>
<div class="Panel"> <div class="Panel">
<div class="Panel-body"> <div class="Panel-body">

View File

@@ -17,7 +17,7 @@
<img src ="/teamsnap-ui/assets/icons/schedule.svg" class="Icon"> <img src ="/teamsnap-ui/assets/icons/schedule.svg" class="Icon">
<span class="span u-hidden u-xs-inline">Schedule</span> <span class="span u-hidden u-xs-inline">Schedule</span>
</a> </a>
<a class="Button" href="members"> <a class="Button u-hidden" href="members">
<img src ="/teamsnap-ui/assets/icons/roster.svg" class="Icon"> <img src ="/teamsnap-ui/assets/icons/roster.svg" class="Icon">
<span class="span u-hidden u-xs-inline">Roster</span> <span class="span u-hidden u-xs-inline">Roster</span>
</a> </a>
@@ -29,24 +29,23 @@
</div> </div>
</div> </div>
<div class=" Panel Panel--full">
<div class=" Panel-header"> <h2 class=" ">Upcoming Events</h2>
<h2 class=" Panel-title">Upcoming Events</h2> <hr class="Divider" />
</div> <div class="Grid Grid--withGutter">
<div class=" Panel-body">
{{#each upcoming_events}} {{#each upcoming_events}}
<div class="Grid-cell u-xs-size1of2 u-sm-size1of3">
{{>event_panel event=this}} {{>event_panel event=this}}
{{/each}}
</div> </div>
{{/each}}
</div> </div>
<div class=" Panel Panel--full"> <h2 class="">Recent Events</h2>
<div class=" Panel-header"> <hr class="Divider" />
<h2 class=" Panel-title">Recent Events</h2> <div class="Grid Grid--withGutter">
</div>
<div class=" Panel-body">
{{#each recent_events}} {{#each recent_events}}
<div class="Grid-cell u-xs-size1of2 u-sm-size1of3">
{{>event_panel event=this}} {{>event_panel event=this}}
{{/each}}
</div> </div>
{{/each}}
</div> </div>