Compare commits

...

18 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
a06807b028 fixed adjacent lineup 2024-05-23 08:03:30 -05:00
bdb6a77371 begin adding availability reminders 2024-05-06 16:46:29 -05:00
20 changed files with 654 additions and 600 deletions

1
.vscode/launch.json vendored
View File

@@ -8,6 +8,7 @@
"name": "Attach",
"port": 9229,
"request": "attach",
"restart": true,
"skipFiles": [
"<node_internals>/**"
],

View File

@@ -21,6 +21,10 @@ exports.helpers = {
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) => {
const {user, team, layout} = req
const bulkLoadTypes = ["event", "availabilitySummary"]
@@ -70,19 +74,50 @@ exports.getEvent = async (req, res, next) => {
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(500).send()
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 {
await teamsnap.sendAvailabilityReminders(event, sendingMember, memberIds)
res.status(200).send('OK')
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

@@ -38,14 +38,14 @@ exports.getAdjacentEventLineup = async (req, res) => {
return
}
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 availabilities = req.timeline.availabilities.filter(i=>i.eventId==event.id)
attachBenchcoachPropertiesToMember(members, event_lineup_entries, availabilities)
members.sort(tsUtils.teamsnapMembersSortLineupAvailabilityLastName)
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) => {
@@ -88,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})
}
exports.getAvailabilityRemindersModal = (req, res) => {
res.status(200).render("eventlineup/partials/availability_reminder_modal.hbs")
}
exports.getEventLineupEntries = async (req, res)=>{
const {event_lineup, event_lineup_entries} = req
res.setHeader('Content-Type', 'application/json').send(JSON.stringify(req.event_lineup_entries))
@@ -161,3 +165,32 @@ const processPostedEventLineupEntries = (body, eventLineupEntries, eventLineup)
})
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

@@ -95,25 +95,25 @@ 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 availability = availabilities?.find(a=>a.memberId==eventLineupEntry.memberId)
const member = members.find(m=>m.id==eventLineupEntry.memberId)
const {positionFlags} = parsePositionLabel(eventLineupEntry.label)
const initial_slotset = `lineup-${positionFlags.has('PO') ? 'positiononly' : 'starting'}-${event.id}`
const initial_lineup_segment = `${positionFlags.has('PO') ? 'position-only' : 'starting'}`
s+=options.fn({eventLineupEntry, availability, member, event, initial_slotset})
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_slotset
let initial_lineup_segment
if (availability?.statusCode == 0 || availability?.statusCode == null) {
initial_slotset =`lineup-out-${event.id}`
initial_lineup_segment =`out`
} else {
initial_slotset =`lineup-bench-${event.id}`
initial_lineup_segment =`bench`
}
s+=options.fn({availability, member, event, initial_slotset})
s+=options.fn({availability, member, event, initial_lineup_segment})
})
return s
}

View File

@@ -88,6 +88,9 @@ const positionGroups = {
}
exports.positionCapabilityFor = (member, position, options) => {
if (!member.position) {
return ""
}
const member_positions = member.position.split(",").map(s=>s.trim())
const member_position_groups = new Set(member.position.split(",").map(s=>positionGroups[s.trim()]))
@@ -139,7 +142,7 @@ exports.timepointForMember = (member, timeline, event, options) => {
value = parsePositionLabel(eventLineupEntry.label).positionLabelWithoutFlags
}
else {
value = availability.status[0]
value = availability?.status[0]
}
return options.fn({availability: availability, eventLineupEntry: eventLineupEntry, value}, {data: data })
}

View File

@@ -113,7 +113,7 @@ exports.groupTeamsnapItems = (items, types = [], params = {}) => {
return result;
}
exports.embeddedSvgFromPath = (svg_path, additional_classes = "") => {
exports.embeddedSvgFromPath = (svg_path, additional_classes, options) => {
const iconStaticPaths = {
"/teamsnap-ui/assets":path.join(__dirname, "/../../node_modules/@teamsnap/teamsnap-ui/src/assets"),
"/bootstrap-icons":path.join(__dirname, "/../../node_modules/bootstrap-icons/icons"),
@@ -126,6 +126,8 @@ exports.embeddedSvgFromPath = (svg_path, additional_classes = "") => {
}
}
if (!options) {options=additional_classes; additional_classes=''}
const svg = fs.readFileSync(`${svg_path}`, 'utf8');
svgRegExWithClass = new RegExp(/<svg(.*)class="(.*?)"(.*)>/)

View File

@@ -7014,7 +7014,7 @@ a.Panel-row {
align-items: center;
}
div[id^=event-lineup] {
div.event-lineup {
max-width: 576px;
counter-reset: lineup-sequence-counter 0;
margin-left: 8px;
@@ -7093,9 +7093,23 @@ li .availability-status-code- {
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;
}
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 {
padding: 2px !important;
font-size: inherit !important;

File diff suppressed because one or more lines are too long

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) {
elem.querySelectorAll("option").forEach((option) => {
if (option.innerText.trim() == elem.value) {
@@ -7,248 +45,242 @@ function onPositionSelectChange(elem) {
option.removeAttribute("selected");
}
});
colorPositions();
refreshLineup();
const lineup = elem.closest('.event-lineup')
lineup.dispatchEvent(lineupChangedEvent)
elem
}
function togglePopup(el) {
el.querySelector(".Popup-container").classList.toggle("is-open");
}
function colorPositions(lineup) {
const class_none = "u-colorNegative"
const class_good = "u-colorPositive"
const class_over = "u-colorHighlight"
function colorPositions() {
for (bcLineup of document.querySelectorAll("[id^=event-lineup]")) {
selected_lineup_positions = Array.from(
bcLineup.querySelectorAll(".position-select-box option:checked")
).map((el) => el.value);
for (position_status of bcLineup.querySelectorAll(".position-status")) {
for (class_name of ["u-colorNegative", "u-colorHighlight", "u-colorPositive"]) {
position_status.classList.remove(class_name);
}
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");
}
}
lineup.querySelectorAll('.position-status').forEach(
position_status=>{
position_status.classList.remove(class_over, class_good, class_none);
const occurences = lineup.querySelectorAll(`.position-select-box option:checked[value="${position_status.dataset.value}"]`)
switch (occurences.length){
case 0:
position_status.classList.add(class_none)
break;
case 1:
position_status.classList.add(class_good)
break;
default:
position_status.classList.add(class_over)
break;
}
})
}
function initFlagsCheckboxes(){
Array.from(document.querySelectorAll("[id^=event-lineup]")).forEach((bcLineup) => {
Array.from(
bcLineup.querySelectorAll(
".starting .lineup-slot, \
.position-only .lineup-slot, \
.bench .lineup-slot"
)
).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;
document.querySelectorAll(".lineup-slot").forEach(lineup_slot=>{
const possible_flags = ['DHd', 'DRd']
const flags_string = lineup_slot.querySelector("input[name=flags]")?.value
const flags = flagSetFromString(flags_string)
possible_flags.forEach(flag=>{
if (flags.has(flag)){
lineup_slot.querySelector(`input[type=checkbox][name=${flag}]`).checked = true
}
})
})
}
if (flags.has('DRd') ) {
slot.querySelector('[name=flag-drd]').checked = true;
}
})}
)
const flagSetFromString = (s) => {
if (!s) {return new Set()}
const array = s.split(',').map(item=>item.trim())
return new Set(array)
}
function refreshLineup() {
Array.from(document.querySelectorAll("[id^=event-lineup]")).forEach((bcLineup) => {
Array.from(
bcLineup.querySelectorAll(
".starting .lineup-slot, \
.position-only .lineup-slot, \
.bench .lineup-slot"
)
).forEach((slot, i) => {
slot.querySelector("input[name*=sequence]").value = i;
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()
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
}
if (slot.querySelector('[name=flag-dhd]').checked) {
flags.add('DHd')
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 {
flags.delete('DHd')
slot.querySelector("input[name=label]").value = null;
}
}
if (slot.querySelector('[name=flag-drd]').checked) {
flags.add('DRd')
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 {
flags.delete('DRd')
return ''
}
}
if (selected_position && selected_position.text != "--") {
slot.querySelector("input[name*=label]").value = selected_position.text;
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 {
slot.querySelector("input[name*=label]").value = null;
return Promise.reject(response.text());
}
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(",");
});
});
})
.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
)
function refreshFlags(){
console.log("sending reminders", el, event_id, memberIds, csrf_token)
sendAvailabilityReminder(el, event_id, memberIds, csrf_token)
body.removeChild(modal_node)
}
)
})
}
function copyEmailTable(itemEl, subject, recipients) {
// Create container for the HTML
// [1]
let bcLineup = itemEl.closest(".benchcoach-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();
for (row of bcLineup.querySelector(".table-benchcoach-startinglineup").rows) {
let tr = tbody.insertRow();
cell = tr.insertCell();
cell.classList.add("sequence-cell");
cell.appendChild(document.createTextNode(parseInt(row.dataset.order) + 1));
cell = tr.insertCell();
cell.appendChild(document.createTextNode(row.dataset.playerName));
cell.classList.add("name-cell");
tr.insertCell().appendChild(document.createTextNode(row.dataset.position));
function confirmModal(el, prompt, fn, options) {
const url = "/modal-confirm"
const params = new URLSearchParams(prompt)
url.search = params.toString()
fetch(url+"?"+params.toString(), {method:"GET"})
.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 body = document.querySelector('body')
body.appendChild(modal_node)
modal_node_accept.addEventListener("click", ()=>{fn(modal_node, options)})
})
}
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");
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')
})
}
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));
const completeLoad = (el, success) => {
el.querySelectorAll('.hideOnLoading, .showOnLoading, .showOnFailure, .showOnSuccess').forEach((element)=>{
element.classList.add('u-hidden')
})
if (success) {
el.querySelectorAll('.showOnSuccess').forEach((element) => {
element.classList.remove('u-hidden')
})
} else {
el.querySelectorAll('.showOnFailure').forEach((element) => {
element.classList.remove('u-hidden')
})
}
}
if (bcLineup.querySelector(".table-benchcoach-bench").rows.length > 0) {
var tr = tbody.insertRow();
cell = tr.insertCell();
cell.colSpan = 3;
cell.appendChild(document.createTextNode("SUBS"));
cell.classList.add("title-cell");
for (row of bcLineup.querySelector(".table-benchcoach-bench").rows) {
var tr = tbody.insertRow();
cell = tr.insertCell();
cell.classList.add("sequence-cell");
availability_status = {
None: "UNK",
0: "NO",
2: "MAY",
1: "YES",
}[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(""));
function submitClearLineup(modal, options){
console.log('clearing lineup...')
toggleShowAndHideLoading(modal)
const {team_id, event_id, event_lineup_id} = options
const url = `/${team_id}/event/${event_id}/lineup/${event_lineup_id}/delete`
const form = document.querySelector(`#event-lineup-${event_id} form`);
const data = new FormData(form);
const memberIds = data.getAll('memberId')
console.log(url)
fetch(url, {method:"POST", body: JSON.stringify({memberIds, event_id}), headers: {"Content-Type": "application/json"}})
.then((response) => {
if (response.ok) {
completeLoad(modal, true);
return response.text();
} else {
completeLoad(modal, false);
return Promise.reject(response.text());
}
})
.finally(()=>{
setTimeout(function (){
location.reload()
}, 500)
});//refresh page
}
if (bcLineup.querySelector(".table-benchcoach-out").rows.length > 0) {
var tr = tbody.insertRow();
cell = tr.insertCell();
cell.colSpan = 3;
cell.appendChild(document.createTextNode("OUT"));
cell.classList.add("title-cell");
function submitResetAvailabilities(modal, options){
const {team_id, event_id} = options
toggleShowAndHideLoading(modal)
const url = `/${team_id}/event/${event_id}/reset_availabilities`
const form = document.querySelector(`#event-lineup-${event_id} form`);
const data = new FormData(form);
const memberIds = data.getAll('memberId')
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(""));
console.log('submitting...', url)
fetch(url, {method:"POST", body: JSON.stringify({memberIds, event_id}), headers: {"Content-Type": "application/json"}})
.then((response) => {
if (response.ok) {
completeLoad(modal, true);
return response.text();
} else {
completeLoad(modal, false);
return Promise.reject(response.text());
}
}
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);
})
.finally(()=>{
setTimeout(function (){
location.reload()
}, 500)
});//refresh page
}
function emailModal(el, url) {
@@ -279,21 +311,18 @@ function emailModal(el, url) {
email_modal_node.classList.add('is-open')
body.appendChild(email_modal_node)
tinymce.init({
selector:`#lineup-email-data-${data.get('event_lineup_id')} #email-editor`,
selector:`textarea#email-editor`,
content_css:"/css/application.css",
plugins: 'image',
menubar: false,
toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | outdent indent | image',
paste_data_images: true,
statusbar:false})
// tinymce.activeEditor.setContent("Team,")
// lineup_table_div.innerHTML = lineup_table
// email_modal.classList.add("is-open");
// email_modal.querySelector(".Modal-body").innerHTML = html;
tinymce.remove();
});
}
async function onSubmit(form, event) {
async function submitEventLineup(form, event) {
event.preventDefault();
console.log(event)
teamsnap_icon = form.querySelector("#teamsnap-icon");
@@ -303,8 +332,7 @@ async function onSubmit(form, event) {
data = new FormData(form);
console.log(form)
url = form.attributes.action.textContent;
teamsnap_icon.classList.add("u-hidden")
waiting_icon.classList.remove("u-hidden");
toggleShowAndHideLoading(form)
await fetch(url, {
method: "POST",
body: data,
@@ -313,8 +341,8 @@ async function onSubmit(form, event) {
}
})
.then((response) => {
waiting_icon.classList.add("u-hidden");
if (response.ok) {
return response.text();
} else {
return Promise.reject(response.text());
@@ -322,17 +350,13 @@ async function onSubmit(form, event) {
})
.then((text) => {
event.submitter.blur()
waiting_icon.classList.add("u-hidden");
success_icon.classList.remove("u-hidden");
completeLoad(form, true)
console.log(text);
// success_icon.querySelector("span.message").innerHTML = text;
})
.catch((error) => {
event.submitter.blur()
waiting_icon.classList.add("u-hidden");
failure_icon.classList.remove("u-hidden");
completeLoad(form, false)
console.log(error);
// success_icon.querySelector("span.message").innerHTML = error;
})
.finally(()=>{location.reload()});//refresh page
setTimeout(() => {
@@ -341,184 +365,6 @@ async function onSubmit(form, event) {
}, 3000)
}
function copyEmailTable(itemEl, subject, recipients) {
// 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), button:has(+.position-label-flags)')
Array.from(cells).forEach(cell=>{
cell.classList.toggle('u-hidden')
})
}
}
async function copyEmailTable (element) {
// range=document.createRange();
// window.getSelection().removeAllRanges();
@@ -582,6 +428,44 @@ async function copyEmailTable (element) {
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) {
const currentUrl = window.location.href;
let search_params
@@ -610,7 +494,7 @@ function insertLineup(direction, teamId, eventId, element) {
.then((html) =>{
const parser = new DOMParser();
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 main = document.querySelector("main")
const new_csrf_token = new_lineup_doc.querySelector('form input[name=csrfToken]').value
@@ -619,13 +503,13 @@ function insertLineup(direction, teamId, eventId, element) {
main.classList.remove(...main.classList)
main.classList.add('scroll-horizontal', 'u-spaceSidesSm', 'u-flex')
Array.from(document.querySelectorAll("[id^=event-lineup]")).forEach((bcLineup) => {
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("[id^=event-lineup] .Panel")).forEach((bcLineupPanel) => {
Array.from(document.querySelectorAll(".event-lineup .Panel")).forEach((bcLineupPanel) => {
bcLineupPanel.classList.remove('Panel--full')
})
for (input of document.querySelectorAll("form input[name=csrfToken]")){
@@ -637,20 +521,11 @@ function insertLineup(direction, teamId, eventId, element) {
}
function initSlots () {
const slots = Array.from(document.querySelectorAll('.lineup-slot'))
slots.forEach(slot=>{
const parent = document.querySelector(`#${slot.dataset.initialSlotset} .slot-set`)
parent.appendChild(slot)
slot.removeAttribute('data-initial-slotset')
})
}
function initPage (){
colorPositions();
initSlots();
initFlagsCheckboxes();
refreshLineup();
for (bcLineup of document.querySelectorAll("[id^=event-lineup]")) {
for (bcLineup of document.querySelectorAll(".event-lineup")) {
bcLineup.dispatchEvent(lineupChangedEvent)
options = {
animation: 150,
handle: ".Panel-cell:has(.drag-handle), .Panel-cell:has(.sequence)",
@@ -661,52 +536,31 @@ function initPage (){
pull: [bcLineup.id],
},
onAdd: function (/**Event*/ evt) {
console.log("added to lineup");
// Add to Lineup
var itemEl = evt.item; // dragged HTMLElement
refreshLineup();
bcLineup.dispatchEvent(lineupChangedEvent)
},
onUpdate: function (/**Event*/ evt) {
console.log("update to lineup");
// var itemEl = evt.item; // dragged HTMLElement
// refresh_lineup_order(itemEl);
refreshLineup();
bcLineup.dispatchEvent(lineupChangedEvent)
},
};
new Sortable.create(bcLineup.querySelector("[id^=lineup-starting] .slot-set"), options);
new Sortable.create(bcLineup.querySelector("[id^=lineup-positiononly] .slot-set"), options);
options["sort"] = false;
new Sortable.create(bcLineup.querySelector("[id^=lineup-bench] .slot-set"), options);
new Sortable.create(bcLineup.querySelector("[id^=lineup-out] .slot-set"), {...options, group:{...options.group, put:[]}});
new Sortable.create(bcLineup.querySelector(".lineup-segment.starting .slot-set"), options);
new Sortable.create(bcLineup.querySelector(".lineup-segment.position-only .slot-set"), options);
new Sortable.create(bcLineup.querySelector(".lineup-segment.bench .slot-set"), {...options, sort:false});
new Sortable.create(bcLineup.querySelector(".lineup-segment.out .slot-set"), {...options, sort:false, group:{...options.group, put:[]}});
}
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), button:has(+.position-label-flags)')
Array.from(cells).forEach(cell=>{
cell.classList.add('u-hidden')
})
}
// 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), 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)}`
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');

View File

@@ -71,7 +71,8 @@ router.use("/:team_id([0-9]+)/event/:event_id([0-9]+)", loadEvent)
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);
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}

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.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]+)/delete", upload.none(), eventsLineupController.submitDeleteEventLineupEntries);
module.exports = {router, loadEventLineup}

View File

@@ -27,4 +27,10 @@ router.get("/", (req,res,next) => {
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};

View File

@@ -189,7 +189,7 @@ a.Panel-row {
align-items: center;
}
div[id^="event-lineup"] {
div.event-lineup {
max-width: 576px;
counter-reset: lineup-sequence-counter 0;
margin-left: 8px;
@@ -289,17 +289,31 @@ li .availability-status-code- {
}
div[id^="event-lineup"] .Panel {
&.position-only .Panel-cell:has(.sequence), &.bench .Panel-cell:has(.sequence), &.out .Panel-cell:has(.sequence){
div.event-lineup {
.lineup-segment {
&:has(input.Toggle-input:not(:checked)) {
&.out {
.Panel-cell:has(.SelectBox),
.Panel-cell:has(.drag-handle),
button:has(+.position-label-flags),
button.addToStarting,
button.addToBench
{
display: none;
}
&.out {
.Panel-cell {
&:has(.sequence), .drag-handle, .SelectBox {
// 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;
}
}
}

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

@@ -1,24 +1,25 @@
<div class="u-spaceSidesNone u-sm-spaceSidesAuto" id="event-lineup-{{event.id}}" data-event-lineup-id="{{event_lineup.id}}" data-event-id="{{event.id}}">
<form onsubmit="onSubmit(this,event)" action="/{{team.id}}/event/{{event.id}}/lineup/{{event_lineup.id}}">
<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}}">
<form onsubmit="submitEventLineup(this,event)" action="/{{team.id}}/event/{{event.id}}/lineup/{{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="csrfToken" value="{{csrfToken}}">
<div class="Panel Panel--full">
<div class="Panel-header u-padEndsSm">
<h3 style="flex: 1 1 0%;">{{event.formattedTitle}}</h3>
<div class="Popup">
<div class="ButtonGroup">
<button class="Button Button--orange" type="submit" formmethod="post">
<div>
<span id="teamsnap-icon">{{{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="success-icon" class="u-hidden">{{{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="teamsnap-icon" class="hideOnLoading">{{{embeddedSvgFromPath "/media/teamsnap_star.svg"}}}</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 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>
Save
</div>
</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"}}}
<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">
<a class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="emailModal(this, '{{event_lineup.id}}/email')">
{{{embeddedSvgFromPath "/bootstrap-icons/envelope.svg"}}}
@@ -39,20 +40,22 @@
{{{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')">
<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>
</span>
</a>
<hr class="Divider u-spaceEndsNone">
<span class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="console.log('not implemented yet')">
<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>
</span>
</a>
<hr class="Divider u-spaceEndsNone">
<span class="u-padEndsSm u-padSidesMd u-textDecorationNone" href="javascript:void(0)" onclick="console.log('not implemented yet')">
<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>
</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>
@@ -63,6 +66,7 @@
</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>
@@ -73,7 +77,7 @@
</div>
</div>
</div>
<div id="lineup-starting-{{event.id}}" class="Panel u-maxWidthSm starting Panel--fullWidthMobile Panel--full">
<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>
@@ -81,7 +85,7 @@
</div>
<div class=" Panel-row Grid Grid--fit u-textBold u-textCenter u-padXs">
{{#each (positions)}}
<div class="Grid-cell position-status">{{this}}</div>
<div class="Grid-cell position-status" data-value="{{this}}">{{this}}</div>
{{/each}}
</div>
<div class="slot-set">
@@ -89,7 +93,7 @@
</div>
</div>
</div>
<div id="lineup-positiononly-{{event.id}}" class="Panel u-maxWidthSm position-only Panel--full">
<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>
@@ -98,7 +102,7 @@
</div>
</div>
<div id="lineup-bench-{{event.id}}" class="Panel u-maxWidthSm bench Panel--full">
<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>
@@ -109,12 +113,12 @@
{{/loadSlots}}
</div>
</div>
<div id="lineup-out-{{event.id}}" class="Panel u-maxWidthSm out Panel--full">
<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" onclick="toggleChildSlots(this);">
<input class="Toggle-input" type="checkbox" id="enable-slots">
<label class="Toggle-label" for="enable-slots"></label>
</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

@@ -8,32 +8,37 @@
<form>
<div class="FieldGroup">
<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 class="FieldGroup">
<label class="FieldGroup-label">Body</label>
<textarea id="email-editor" class="Input"></textarea>
</div>
<div class="FieldGroup">
<label class="FieldGroup-label">Lineup</label>
<label class="FieldGroup-label">
Lineup
<button class="Button Button--smallSquare" role="button" type="button" onclick="copyEmailTable(this)">
{{{embeddedSvgFromPath "/bootstrap-icons/clipboard-fill.svg"}}}
</button>
</label>
<div class="lineup-email lineup-table">{{>email_table}}</div>
</div>
<div class="FieldGroup">
<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);",
</form>
</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)}}"
data-subject="{{dateFormat event.startDate "ddd, MMM D, YYYY h:mm A" }}, {{ event.locationName }}, ({{#if (isAway event) }}@{{/if}}{{ event.opponentName }})">
data-bcc="{{joinMemberEmailAddresses (filterNonPlayers members)}}">
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/mail.svg"}}}
Spark Mail
</button>
</div>
</form>
<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>
</script>

View File

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

View File

@@ -1,10 +1,10 @@
<div class="Panel-expandableRow lineup-slot" data-initial-slotset="{{initial_slotset}}">
<div class="Panel-expandableRow lineup-slot" data-initial-lineup-segment="{{initial_lineup_segment}}">
<input type="hidden" name="label" value="{{eventLineupEntry.label}}">
<input type="hidden" name="flags" value="{{flagsString eventLineupEntry.flags}}">
<input type="hidden" name="sequence" value="{{eventLineupEntry.sequence}}">
<input type="hidden" name="eventId" value="{{event.id}}">
<input type="hidden" name="eventLineupEntryId" value="{{eventLineupEntry.id}}">
<input type="hidden" name="availabilityStatusCode", value="{{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="lastName" value="{{member.lastName}}">
<input type="hidden" name="firstName" value="{{member.firstName}}">
@@ -23,15 +23,15 @@
>
{{#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-{{memberId}}-{{eventId}}"
onclick="this.closest('div').querySelector('.Popup-container').classList.toggle('is-open')"
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-{{memberId}}-{{eventId}}">
<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}}
@@ -45,6 +45,7 @@
</button>
</div>
</div>
</div>
{{/with}}
{{/if}}
</div>
@@ -61,6 +62,12 @@
</span>
</div>
<div class="u-flexGrow1"></div>
<button type="button" class="Button Button--smallSquare addToBench" onclick="moveToLineupSegment(this, 'bench');this.blur()">
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/dismiss.svg"}}}
</button>
<button type="button" class="Button Button--smallSquare addToStarting" onclick="moveToLineupSegment(this, 'starting');this.blur()">
{{{embeddedSvgFromPath "/teamsnap-ui/assets/icons/plus.svg"}}}
</button>
<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 "/bootstrap-icons/three-dots.svg"}}}
@@ -68,11 +75,11 @@
<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}}-{{eventLineupEntry.id}}" onclick="refreshLineup()">
<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="flag-dhd" id="flag-dhd-{{member.id}}-{{eventLineupEntry.id}}" onclick="refreshLineup()">
<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>

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>