Merge branch 'main' into docker

This commit is contained in:
2024-03-18 20:22:15 -05:00
22 changed files with 293 additions and 114 deletions

View File

@@ -169,9 +169,9 @@ app.use(function (err, req, res, next) {
});
// catch 404 and forward to error handler
// app.use(function (req, res, next) {
// next(createError(404));
// });
app.use(function (req, res, next) {
next(createError(404));
});
app.set('trust proxy')

View File

@@ -67,3 +67,22 @@ exports.getEvent = async (req, res, next) => {
};
res.render("event/show", context);
};
exports.sendAvailabilityReminders = async (req,res,next) => {
await Promise.all(req.promises)
if (req.params.event_id != req.body.eventId) {
// Load actual event. Do I want this to be an error? probably
res.status(500).send()
}
const {event} = req
const {eventId, memberIds} = req.body
const sendingMember = req.members.find(m=>m.userId==req.user.id)
try {
await teamsnap.sendAvailabilityReminders(event, sendingMember, memberIds)
res.status(200).send('OK')
} catch (err) {
res.status(500).send()
}
return
}

View File

@@ -14,6 +14,22 @@ const statusCodeIcons = {
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.helpers = {
flagsString: (flags) => {
return flags != null ? Array.from(flags).join(",") : ''
@@ -21,25 +37,8 @@ exports.helpers = {
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>`
},
avail_status_code_class: (status_code) => statusCodeButtonClasses[status_code],
avail_status_code_icon: (status_code) => statusCodeIcons[status_code],
positionLabelWithoutFlags: (label) => {
const {positionLabelWithoutFlags} = parsePositionLabel(label);
return positionLabelWithoutFlags
@@ -92,6 +91,12 @@ exports.helpers = {
statusShortLookup[NONE] = "UNK"
statusShortLookup[undefined] = "UNK"
return (statusShortLookup[availability?.statusCode])
},
filterNonPlayers: (members) => {
return members.filter(m=>!m.isNonPlayer)
},
joinMemberEmailAddresses: (members) => {
return members.map(m=>m.emailAddresses.join(',')).join(',')
}
}
@@ -121,9 +126,13 @@ exports.getAdjacentEventLineup = async (req, res) => {
} else {
throw new Error('Index must be positive or negative number')
}
if (!event) {
res.status(500).send()
return
}
const availabilitySummary = event.availabilitySummary
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)
attachBenchcoachPropertiesToMember(members, event_lineup_entries, availabilities)
members.sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName)
@@ -142,7 +151,7 @@ attachBenchcoachPropertiesToMember = (members, event_lineup_entries, availabilit
// 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.
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)
member.benchcoach.availability = availability
if (event_lineup_entry != null) {

View File

@@ -82,7 +82,7 @@ exports.getLineupCard = (req, res, next) => {
availabilities: items.filter((i) => i.type == "availability").sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName),
all_lineup_entries: items.filter((i) => i.type == "eventLineupEntry"),
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),
event_lineup_entries: items
.filter((i) => i.type == "eventLineupEntry" && i.eventId == event_id)

View File

@@ -7,7 +7,7 @@ exports.offenseLineup = (number_of_slots, event_lineup_entries, members, options
for (let i = 0; i < number_of_slots; i++){
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({
sequence: event_lineup_entry.sequence,
member: members.find(member=> event_lineup_entry.memberId == member.id || event_lineup_entry.memberName == `${member.firstName} ${member.lastName}`),

View File

@@ -50,17 +50,6 @@ teamsnapMembersSortAvailabilityLastName = (a, b) => {
}
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) => {
if (Array.isArray(result)){
types = new Set(result.map(i=>i.type))
@@ -167,7 +156,19 @@ exports.compilePositionLabel = (label, flags) => {
return label
}
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(',')}]`
}
}
function toFlagsSet(flags) {
let flags_set
if (typeof(flags) == 'string'){
const 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) => {
const {team_id, event_id} = req.params
const page_size = req.query.page_size ? Number(req.query.page_size) : 4
var subject_date = ""
var subject_date
if (event_id) {
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 {
subject_date = new Date().toISOString().slice(0,10)
subject_date = new Date()
}
req.promises.push(
teamsnap.bulkLoad({
teamId: team_id,
types: ["event", "availabilitySummary"],
scopeTo: "event",
event__startedAfter: subject_date,
event__pageSize: page_size + 1
event__startedAfter: new Date(subject_date.getTime()+10000),
event__pageSize: page_size
})
.then(items => tsUtils.groupTeamsnapItems(items))
.then((items)=>{
req.upcoming_events=items.events ? items.events.slice(1) : [];
req.upcoming_events=items.events ? items.events : [];
const availabilitySummaries=items.availabilitySummaries;
req.upcoming_events.forEach((event) => {
event.link('availabilitySummary', availabilitySummaries.find(a=>a.eventId==event.id))
@@ -32,7 +33,7 @@ exports.loadRecentAndUpcomingEvents = async (req, res, next) => {
teamId: team_id,
types: ["event", "availabilitySummary"],
scopeTo: "event",
event__startedBefore: subject_date,
event__startedBefore: new Date(subject_date.getTime()-10000),
event__pageSize: page_size,
event__sortStartDate: "desc"
})

View File

@@ -1004,21 +1004,21 @@ h6 {
color: #ffffff;
}
.Button--blue {
.Button--blue, button:has(+ .position-label-flags :checked) {
background-color: #1A6BAF;
border-color: #15568c;
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;
border-color: #134d7e;
color: #ffffff;
}
.Button--blue.is-active {
.Button--blue.is-active, button.is-active:has(+ .position-label-flags :checked) {
background-color: #17609e;
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;
border-color: #15568c;
color: #ffffff;
@@ -7137,8 +7137,7 @@ div[id^=event-lineup] .Panel.position-only .Panel-cell:has(.sequence), div[id^=e
}
.Panel .Panel {
border: none;
margin: 0;
margin: 8px;
}
.scroll-horizontal {

File diff suppressed because one or more lines are too long

View File

@@ -774,6 +774,7 @@ td.is-present-checkbox.available-status-code-None > span {
display: inline-flex;
width: 100%;
flex-grow: 1;
height: 100%;
}
#front-cover .opponent img, #front-cover .team img {
height: 115px;

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;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;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE","file":"eventsheet.css"}

View File

@@ -50,6 +50,7 @@ function initFlagsCheckboxes(){
).forEach((slot, i) => {
const flags = new Set(slot.querySelector("input[name*=flags]")?.value?.split(',')?.map(s=>s.trim())) || new Set()
if (flags.has('DHd')) {
console.log('dhd')
slot.querySelector('[name=flag-dhd]').checked = true;
}
@@ -509,14 +510,14 @@ function toggleChildSlots (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 ')
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=>{
cell.classList.toggle('u-hidden')
})
}
}
async function submitEmail () {
async function copyEmailTable (element) {
// range=document.createRange();
// window.getSelection().removeAllRanges();
// // range.selectNode(document.querySelector('.Modal').querySelector('.Modal-body'));
@@ -558,16 +559,25 @@ async function submitEmail () {
margin: 0;
}</style>
`
html_content = emailStyle+tinymce.activeEditor.getContent()
console.log(html_content)
// html_content = emailStyle+tinymce.activeEditor.getContent()
// console.log(html_content)
const table = element.closest('form').querySelector('.lineup-table table')
navigator.clipboard.write(
[new ClipboardItem(
{
'text/plain': new Blob([tinymce.activeEditor.getContent({format: "text"})], {type: 'text/plain'}),
'text/html': new Blob([html_content], {type: 'text/html'})
})
])
// navigator.clipboard.write(
// [new ClipboardItem(
// {
// // 'text/plain': new Blob([tinymce.activeEditor.getContent({format: "text"})], {type: 'text/plain'}),
// '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();
}
function insertLineup(direction, teamId, eventId, element) {
@@ -657,11 +667,77 @@ function initPage (){
}
for (lineup_slot of document.querySelectorAll("[id^=lineup-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=>{
cell.classList.add('u-hidden')
})
}
}
function mailToLink(el, protocol='mailto') {
console.log(el)
console.log(el.dataset)
const {to, bcc, subject} = el.dataset
const params = new URLSearchParams({
bcc, subject: encodeURIComponent(subject),
})
const url = `${protocol}:${to}?${params}`
console.log(url)
// location.href=`mailto:${to}${params}`
const windowRef = window.open(url, '_blank');
windowRef.focus();
}
function sparkMailToLink(el) {
const protocol = 'readdle-spark'
const {to, bcc, subject} = el.dataset
const url = `${protocol}://compose?recipient=${to}&bcc=${bcc}&subject=${encodeURIComponent(subject)}`
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)

View File

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

View File

@@ -3,6 +3,8 @@ const eventsController = require("../controllers/event");
const router = express.Router();
const tsUtils = require("../lib/utils")
const {teamsnapCallback} = require("../lib/utils")
const multer = require("multer");
const upload = multer()
// Middleware
const loadEvent = (req,res,next) => {
@@ -68,6 +70,7 @@ router.use("/:team_id([0-9]+)/event/:event_id([0-9]+)", loadEvent)
// Routes
router.get("/:team_id([0-9]+)/schedule", eventsController.getEvents);
router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)", eventsController.getEvent);
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", eventsController.getLineup);
// router.get("/:team_id([0-9]+)/event/:event_id([0-9]+)/lineup_card", eventsController.getLineupCard);

View File

@@ -354,12 +354,16 @@ div[id^="event-lineup"] .Panel {
.Panel .Panel{
// padding: 0;
// border-radius: 0;
border: none;
// border: none;
// border-top: 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
}

View File

@@ -880,7 +880,8 @@ td.is-present-checkbox.available-status-code-None > span {
font-family: 'Pacifico';
display: inline-flex;
width: 100%;
flex-grow:1
flex-grow:1;
height: 100%;
}
.opponent img, .team img{

View File

@@ -25,9 +25,9 @@
<span>Generate Email</span>
</a>
<hr class="Divider u-spaceEndsNone">
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="../sheet">
{{{embeddedSvgFromPath "/bootstrap-icons/book.svg"}}}
<span>Lineup Card</span>
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="/{{team.id}}/event/{{event.id}}/sheet">
<span>{{{embeddedSvgFromPath "/bootstrap-icons/file-earmark.svg"}}}</span>
<span class="u-hidden u-xs-inline">Game Sheet</span>
</a>
<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)">
@@ -39,6 +39,25 @@
{{{embeddedSvgFromPath "/bootstrap-icons/caret-left.svg"}}}
<span>Insert previous 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')">
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/send.svg"}}}
<span>Availability Reminders</span>
</span>
<hr class="Divider u-spaceEndsNone">
<span class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="console.log('not implemented yet')">
<span>Reset All Availabilities</span>
</span>
<hr class="Divider u-spaceEndsNone">
<span class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="console.log('not implemented yet')">
<span>Clear Lineup</span>
</span>
<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>

View File

@@ -1,5 +1,4 @@
<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 onclick="javascript:this.closest('.Modal').remove();tinymce.remove();">{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg" "Modal-iconDismiss"}}}</div>
<div class="Modal-header">
@@ -17,10 +16,21 @@
</div>
<div class="FieldGroup">
<label class="FieldGroup-label">Lineup</label>
<div class="lineup-email">{{>email_table}}</div>
<div class="lineup-email lineup-table">{{>email_table}}</div>
</div>
<div class="FieldGroup">
<button class="Button" role="button" onclick="submitEmail();">Submit</button>
<button class="Button" role="button" type="button" onclick="copyEmailTable(this)">
{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-fill.svg"}}}
{{{embeddedSvgFromPath "/bootstrap-icons/table.svg"}}}
Copy Table
</button>
<button class="Button" role="button" type="button" onclick="sparkMailToLink(this);",
data-to="{{user.email}}"
data-bcc="{{joinMemberEmailAddresses (filterNonPlayers members)}}"
data-subject="{{dateFormat event.startDate "ddd, MMM D, YYYY h:mm A" }}, {{ event.locationName }}, ({{#if (isAway event) }}@{{/if}}{{ event.opponentName }})">
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/mail.svg"}}}
Spark Mail
</button>
</div>
</form>
</div>

View File

@@ -17,13 +17,39 @@
</div>
<div class="Panel-cell u-padXs u-sizeFill u-flex">
<div
class="availability-status-code-{{
class="Popup availability-status-code-{{
member.benchcoach.availability?.statusCode
}}"
>
{{#if member.benchcoach.availability}}{{{avail_status_code_icon member.benchcoach.availability.statusCode}}}{{/if}}
{{#if member.benchcoach.availability}}
{{#with member.benchcoach.availability}}
<button class="Popup-toggle Button Button--smallSquare {{avail_status_code_class statusCode}}"
type="button"
data-control="popup"
data-open="availablility-popup-{{memberId}}-{{eventId}}"
onclick="this.closest('div').querySelector('.Popup-container').classList.toggle('is-open')"
>
{{#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-{{memberId}}-{{eventId}}">
<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>
{{/with}}
{{/if}}
</div>
<div class="u-fontSizeLg u-textNoWrap">
<div class="u-fontSizeLg u-textNoWrap">
<span class="lastname">
{{member.lastName}}
</span>
@@ -35,14 +61,21 @@
</span>
</div>
<div class="u-flexGrow1"></div>
<div class="position-label-flags u-textNoWrap">
<div class="Checkbox Checkbox--inline">
<input class="Checkbox-input" type="checkbox" name="flag-drd" id="flag-drd-{{member.id}}-{{member.benchcoach.eventLineupEntry.id}}" onclick="refreshLineup()">
<label class="Checkbox-label" for="flag-drd-{{member.id}}-{{member.benchcoach.eventLineupEntry.id}}">DR<small>d</small></label>
</div>
<div class="Checkbox Checkbox--inline">
<input class="Checkbox-input" type="checkbox" name="flag-dhd" id="flag-dhd-{{member.id}}-{{member.benchcoach.eventLineupEntry.id}}" onclick="refreshLineup()">
<label class="Checkbox-label" for="flag-dhd-{{member.id}}-{{member.benchcoach.eventLineupEntry.id}}">DH<small>d</small></label>
<div class="Popup">
<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 "/teamsnap-ui/assets/icons/flag.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="flag-drd" id="flag-drd-{{member.id}}-{{member.benchcoach.eventLineupEntry.id}}" onclick="refreshLineup()">
<label class="Checkbox-label" for="flag-drd-{{member.id}}-{{member.benchcoach.eventLineupEntry.id}}">DR<small>d</small></label>
</div>
<div class="Checkbox Checkbox--inline">
<input class="Checkbox-input" type="checkbox" name="flag-dhd" id="flag-dhd-{{member.id}}-{{member.benchcoach.eventLineupEntry.id}}" onclick="refreshLineup()">
<label class="Checkbox-label" for="flag-dhd-{{member.id}}-{{member.benchcoach.eventLineupEntry.id}}">DH<small>d</small></label>
</div>
</div>
</div>
</div>
</div>

View File

@@ -158,8 +158,9 @@
<div>
<span class="name">{{event.opponentName}}</span>
</div>
{{#if opponent_logo.mediumUrl }}
<img src="{{opponent_logo.mediumUrl}}">
{{!-- <%= ::Temple::Utils.escape_html((image_tag opponent_logos[event.opponent.id].medium_url)) %> --}}
{{/if}}
</div>
<div class="">
<table>

View File

@@ -25,7 +25,7 @@
{{> navbar }}
{{{_sections.header}}}
</header>
<main class="">
<main class="u-sm-spaceSidesMd">
{{{ body }}}
</main>
</body>

View File

@@ -17,7 +17,7 @@
<img src ="/teamsnap-ui/assets/icons/schedule.svg" class="Icon">
<span class="span u-hidden u-xs-inline">Schedule</span>
</a>
<a class="Button" href="members">
<a class="Button u-hidden" href="members">
<img src ="/teamsnap-ui/assets/icons/roster.svg" class="Icon">
<span class="span u-hidden u-xs-inline">Roster</span>
</a>