Merge branch 'main' of https://git.immortal.host/clubesportsudes/team-tryouts into dev
This commit is contained in:
+266
-209
@@ -223,10 +223,219 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Disponibilities Card (Players only) -->
|
||||
{% if user.role == 'player' %}
|
||||
<div class="card" style="grid-column: 1 / -1;">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-clock"></i> My Disponibilities</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted small">Select your available time blocks for matches (5pm to 12am). Green = selected, Gray = available to select.</p>
|
||||
<div id="disponibilities-grid">
|
||||
<p class="text-muted">Loading...</p>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="button" class="btn btn-primary" onclick="saveDisponibilities()">
|
||||
<i class="fas fa-save"></i> Save Disponibilities
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="clearDisponibilities()">
|
||||
<i class="fas fa-trash"></i> Clear All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Coach Availability Card (Coaches only) -->
|
||||
{% if user.role == 'coach' %}
|
||||
<div class="card" style="grid-column: 1 / -1;">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-clock"></i> My Coaching Availability</h3>
|
||||
<p class="text-muted small">Select time slots when you're available for One on One sessions (8am to 10pm).</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="availability-grid" id="availability-grid">
|
||||
<p class="text-muted">Loading availability grid...</p>
|
||||
</div>
|
||||
<div class="form-actions mt-3">
|
||||
<button type="button" class="btn btn-secondary" onclick="clearAllAvailability()">
|
||||
<i class="fas fa-trash"></i> Clear All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{% if user.role == 'coach' %}
|
||||
<style>
|
||||
.availability-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 12px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.day-column {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
min-height: 300px;
|
||||
}
|
||||
.day-header {
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
margin-bottom: 10px;
|
||||
color: var(--primary);
|
||||
}
|
||||
.time-slot {
|
||||
padding: 6px 8px;
|
||||
margin: 4px 0;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
.time-slot:hover { background: var(--primary-light); border-color: var(--primary); }
|
||||
.time-slot.selected { background: var(--primary); color: white; border-color: var(--primary-dark); }
|
||||
.time-slot.selected:hover { background: var(--danger); }
|
||||
@media (max-width: 768px) { .availability-grid { grid-template-columns: repeat(3, 1fr); } }
|
||||
@media (max-width: 480px) { .availability-grid { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
<script>
|
||||
const COACH_TIME_SLOTS = [];
|
||||
for (let h = 8; h <= 22; h++) {
|
||||
for (let m = 0; m < 60; m += 30) {
|
||||
const timeStr = (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m;
|
||||
const displayHour = h > 12 ? h - 12 : h;
|
||||
const displayAmpm = h >= 12 ? 'PM' : 'AM';
|
||||
const displayTime = displayHour + ':' + (m < 10 ? '0' : '') + m + ' ' + displayAmpm;
|
||||
COACH_TIME_SLOTS.push({ time: timeStr, display: displayTime });
|
||||
}
|
||||
}
|
||||
const COACH_DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
||||
let coachSelectedSlots = {};
|
||||
|
||||
// Drag state for coach grid
|
||||
let coachDragMode = false;
|
||||
let coachDragAction = null;
|
||||
|
||||
function applyCoachSlotAction(dayOfWeek, timeStr, element) {
|
||||
if (!coachSelectedSlots[dayOfWeek]) coachSelectedSlots[dayOfWeek] = [];
|
||||
const index = coachSelectedSlots[dayOfWeek].indexOf(timeStr);
|
||||
if (coachDragAction === 'select' && index === -1) {
|
||||
coachSelectedSlots[dayOfWeek].push(timeStr);
|
||||
element.classList.add('selected');
|
||||
} else if (coachDragAction === 'deselect' && index > -1) {
|
||||
coachSelectedSlots[dayOfWeek].splice(index, 1);
|
||||
element.classList.remove('selected');
|
||||
}
|
||||
}
|
||||
|
||||
function loadCoachAvailability() {
|
||||
{% for av in existing_availability %}
|
||||
if (!coachSelectedSlots[{{ av.day_of_week }}]) coachSelectedSlots[{{ av.day_of_week }}] = [];
|
||||
coachSelectedSlots[{{ av.day_of_week }}].push('{{ av.start_time.strftime('%H:%M') }}');
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
function renderCoachGrid() {
|
||||
const grid = document.getElementById('availability-grid');
|
||||
let html = '';
|
||||
COACH_DAYS.forEach((day, dayIndex) => {
|
||||
html += '<div class="day-column">';
|
||||
html += '<div class="day-header">' + day.substring(0, 3) + '</div>';
|
||||
COACH_TIME_SLOTS.forEach(slot => {
|
||||
const isSelected = coachSelectedSlots[dayIndex] && coachSelectedSlots[dayIndex].includes(slot.time);
|
||||
const cssClass = isSelected ? 'time-slot selected' : 'time-slot';
|
||||
html += '<div class="' + cssClass + '" data-day="' + dayIndex + '" data-time="' + slot.time + '">' + slot.display + '</div>';
|
||||
});
|
||||
html += '</div>';
|
||||
});
|
||||
grid.innerHTML = html;
|
||||
|
||||
// Drag event listeners
|
||||
grid.addEventListener('mousedown', function(e) {
|
||||
const slot = e.target.closest('.time-slot');
|
||||
if (!slot) return;
|
||||
e.preventDefault();
|
||||
coachDragMode = true;
|
||||
const day = parseInt(slot.dataset.day);
|
||||
const time = slot.dataset.time;
|
||||
if (!coachSelectedSlots[day]) coachSelectedSlots[day] = [];
|
||||
const isSelected = coachSelectedSlots[day].indexOf(time) > -1;
|
||||
coachDragAction = isSelected ? 'deselect' : 'select';
|
||||
applyCoachSlotAction(day, time, slot);
|
||||
});
|
||||
|
||||
grid.addEventListener('mousemove', function(e) {
|
||||
if (!coachDragMode) return;
|
||||
const slot = e.target.closest('.time-slot');
|
||||
if (!slot) return;
|
||||
applyCoachSlotAction(parseInt(slot.dataset.day), slot.dataset.time, slot);
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', function() {
|
||||
if (coachDragMode) {
|
||||
coachDragMode = false;
|
||||
coachDragAction = null;
|
||||
saveCoachAvailability();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function saveCoachAvailability() {
|
||||
const slots = [];
|
||||
for (let day in coachSelectedSlots) {
|
||||
coachSelectedSlots[day].forEach(time => {
|
||||
slots.push({ day_of_week: parseInt(day), start_time: time });
|
||||
});
|
||||
}
|
||||
fetch('{{ url_for("users.manage_coach_availability") }}', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ slots: slots })
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const msg = document.createElement('div');
|
||||
msg.className = 'alert alert-success';
|
||||
msg.style.marginTop = '10px';
|
||||
msg.innerHTML = '<i class="fas fa-check"></i> Availability saved!';
|
||||
document.getElementById('availability-grid').appendChild(msg);
|
||||
setTimeout(() => msg.remove(), 3000);
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('Save error:', err));
|
||||
}
|
||||
|
||||
function clearAllAvailability() {
|
||||
if (!confirm('Are you sure you want to clear all your availability slots?')) return;
|
||||
fetch('{{ url_for("users.clear_coach_availability") }}', { method: 'POST' })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
coachSelectedSlots = {};
|
||||
renderCoachGrid();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadCoachAvailability();
|
||||
renderCoachGrid();
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% if user.role == 'player' %}
|
||||
<script>
|
||||
// Generate time slots from 5pm (17:00) to 12am (24:00)
|
||||
@@ -262,11 +471,30 @@ var DAYS = [
|
||||
{ value: 6, name: 'Sunday' }
|
||||
];
|
||||
|
||||
// Store selected slots: {day: [time, time, ...]}
|
||||
var selectedSlots = {};
|
||||
|
||||
// Drag state
|
||||
var dispDragMode = false;
|
||||
var dispDragAction = null; // 'select' or 'deselect'
|
||||
|
||||
function applySlotAction(block, action) {
|
||||
var day = parseInt(block.dataset.day);
|
||||
var time = block.dataset.time;
|
||||
if (!selectedSlots[day]) selectedSlots[day] = [];
|
||||
var index = selectedSlots[day].indexOf(time);
|
||||
if (action === 'select' && index === -1) {
|
||||
selectedSlots[day].push(time);
|
||||
block.classList.add('selected');
|
||||
} else if (action === 'deselect' && index > -1) {
|
||||
selectedSlots[day].splice(index, 1);
|
||||
block.classList.remove('selected');
|
||||
}
|
||||
}
|
||||
|
||||
function renderDisponibilityGrid() {
|
||||
var grid = document.getElementById('disponibilities-grid');
|
||||
grid.innerHTML = '<div style="margin-bottom: 10px;"><strong>Click time blocks to select your available hours</strong></div>';
|
||||
grid.innerHTML = '<div style="margin-bottom: 10px;"><strong>Click or click-and-drag to select your available hours</strong></div>';
|
||||
|
||||
var container = document.createElement('div');
|
||||
container.className = 'disponibility-grid';
|
||||
@@ -289,9 +517,6 @@ function renderDisponibilityGrid() {
|
||||
block.dataset.day = day.value;
|
||||
block.dataset.time = slot.time;
|
||||
block.textContent = slot.display;
|
||||
block.onclick = function() {
|
||||
toggleSlot(day.value, slot.time, block);
|
||||
};
|
||||
timeBlocks.appendChild(block);
|
||||
});
|
||||
|
||||
@@ -299,23 +524,39 @@ function renderDisponibilityGrid() {
|
||||
container.appendChild(dayRow);
|
||||
});
|
||||
|
||||
// Drag event listeners on the container
|
||||
container.addEventListener('mousedown', function(e) {
|
||||
var block = e.target.closest('.disponibility-time-block');
|
||||
if (!block) return;
|
||||
e.preventDefault();
|
||||
dispDragMode = true;
|
||||
var day = parseInt(block.dataset.day);
|
||||
var time = block.dataset.time;
|
||||
if (!selectedSlots[day]) selectedSlots[day] = [];
|
||||
var isSelected = selectedSlots[day].indexOf(time) > -1;
|
||||
dispDragAction = isSelected ? 'deselect' : 'select';
|
||||
applySlotAction(block, dispDragAction);
|
||||
});
|
||||
|
||||
container.addEventListener('mousemove', function(e) {
|
||||
if (!dispDragMode) return;
|
||||
var block = e.target.closest('.disponibility-time-block');
|
||||
if (!block) return;
|
||||
applySlotAction(block, dispDragAction);
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', function() {
|
||||
if (dispDragMode) {
|
||||
dispDragMode = false;
|
||||
dispDragAction = null;
|
||||
saveDisponibilities();
|
||||
}
|
||||
});
|
||||
|
||||
grid.appendChild(container);
|
||||
loadMyDisponibilities();
|
||||
}
|
||||
|
||||
function toggleSlot(day, time, element) {
|
||||
if (!selectedSlots[day]) selectedSlots[day] = [];
|
||||
|
||||
var index = selectedSlots[day].indexOf(time);
|
||||
if (index > -1) {
|
||||
selectedSlots[day].splice(index, 1);
|
||||
element.classList.remove('selected');
|
||||
} else {
|
||||
selectedSlots[day].push(time);
|
||||
element.classList.add('selected');
|
||||
}
|
||||
}
|
||||
|
||||
function loadMyDisponibilities() {
|
||||
fetch('{{ url_for("users.get_my_disponibilities") }}')
|
||||
.then(function(response) { return response.json(); })
|
||||
@@ -356,7 +597,8 @@ function saveDisponibilities() {
|
||||
fetch('{{ url_for("users.add_disponibilities_bulk") }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({ slots: slots })
|
||||
})
|
||||
@@ -380,7 +622,10 @@ function clearDisponibilities() {
|
||||
if (!confirm('Are you sure you want to clear all your disponibilities?')) return;
|
||||
|
||||
fetch('{{ url_for("users.clear_disponibilities") }}', {
|
||||
method: 'POST'
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRFToken': '{{ csrf_token() }}'
|
||||
}
|
||||
})
|
||||
.then(function(response) { return response.json(); })
|
||||
.then(function(data) {
|
||||
@@ -393,198 +638,10 @@ function clearDisponibilities() {
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
renderDisponibilityGrid();
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% if user.role == 'coach' %}
|
||||
<style>
|
||||
.availability-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 12px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.day-column {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.day-header {
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
margin-bottom: 10px;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.time-slot {
|
||||
padding: 6px 8px;
|
||||
margin: 4px 0;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.time-slot:hover {
|
||||
background: var(--primary-light);
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.time-slot.selected {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border-color: var(--primary-dark);
|
||||
}
|
||||
|
||||
.time-slot.selected:hover {
|
||||
background: var(--danger);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.availability-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.availability-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
const COACH_TIME_SLOTS = [];
|
||||
for (let h = 8; h <= 22; h++) {
|
||||
for (let m = 0; m < 60; m += 30) {
|
||||
const timeStr = (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m;
|
||||
const displayHour = h > 12 ? h - 12 : h;
|
||||
const displayAmpm = h >= 12 ? 'PM' : 'AM';
|
||||
const displayTime = displayHour + ':' + (m < 10 ? '0' : '') + m + ' ' + displayAmpm;
|
||||
COACH_TIME_SLOTS.push({ time: timeStr, display: displayTime });
|
||||
}
|
||||
}
|
||||
|
||||
const COACH_DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
||||
|
||||
let coachSelectedSlots = {};
|
||||
|
||||
function loadExistingAvailability() {
|
||||
{% for av in coach_availability %}
|
||||
if (!coachSelectedSlots[{{ av.day_of_week }}]) {
|
||||
coachSelectedSlots[{{ av.day_of_week }}] = [];
|
||||
}
|
||||
coachSelectedSlots[{{ av.day_of_week }}].push('{{ av.start_time.strftime('%H:%M') }}');
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
function renderCoachGrid() {
|
||||
const grid = document.getElementById('availability-grid');
|
||||
let html = '';
|
||||
|
||||
COACH_DAYS.forEach((day, dayIndex) => {
|
||||
html += '<div class="day-column">';
|
||||
html += '<div class="day-header">' + day.substring(0, 3) + '</div>';
|
||||
|
||||
COACH_TIME_SLOTS.forEach(slot => {
|
||||
const isSelected = coachSelectedSlots[dayIndex] && coachSelectedSlots[dayIndex].includes(slot.time);
|
||||
const cssClass = isSelected ? 'time-slot selected' : 'time-slot';
|
||||
html += '<div class="' + cssClass + '" data-day="' + dayIndex + '" data-time="' + slot.time + '" onclick="toggleCoachSlot(' + dayIndex + ', \'' + slot.time + '\', this)">' + slot.display + '</div>';
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
grid.innerHTML = html;
|
||||
}
|
||||
|
||||
function toggleCoachSlot(dayOfWeek, timeStr, element) {
|
||||
if (!coachSelectedSlots[dayOfWeek]) {
|
||||
coachSelectedSlots[dayOfWeek] = [];
|
||||
}
|
||||
|
||||
const index = coachSelectedSlots[dayOfWeek].indexOf(timeStr);
|
||||
if (index === -1) {
|
||||
coachSelectedSlots[dayOfWeek].push(timeStr);
|
||||
element.classList.add('selected');
|
||||
} else {
|
||||
coachSelectedSlots[dayOfWeek].splice(index, 1);
|
||||
element.classList.remove('selected');
|
||||
}
|
||||
}
|
||||
|
||||
function saveCoachAvailability() {
|
||||
const slots = [];
|
||||
for (let day in coachSelectedSlots) {
|
||||
coachSelectedSlots[day].forEach(time => {
|
||||
slots.push({ day_of_week: parseInt(day), start_time: time });
|
||||
});
|
||||
}
|
||||
|
||||
fetch('{{ url_for("users.manage_coach_availability") }}', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ slots: slots })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
var msg = document.createElement('div');
|
||||
msg.className = 'alert alert-success';
|
||||
msg.style.marginTop = '10px';
|
||||
msg.innerHTML = '<i class="fas fa-check"></i> Availability saved!';
|
||||
document.getElementById('availability-grid').parentElement.appendChild(msg);
|
||||
setTimeout(function() { msg.remove(); }, 3000);
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('Save error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function clearAllAvailability() {
|
||||
if (!confirm('Are you sure you want to clear all your availability slots?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('{{ url_for("users.clear_coach_availability") }}', { method: 'POST' })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
coachSelectedSlots = {};
|
||||
renderCoachGrid();
|
||||
var msg = document.createElement('div');
|
||||
msg.className = 'alert alert-success';
|
||||
msg.style.marginTop = '10px';
|
||||
msg.innerHTML = '<i class="fas fa-check"></i> Availability cleared!';
|
||||
document.getElementById('availability-grid').parentElement.appendChild(msg);
|
||||
setTimeout(function() { msg.remove(); }, 3000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let coachSaveTimeout;
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.classList.contains('time-slot')) {
|
||||
clearTimeout(coachSaveTimeout);
|
||||
coachSaveTimeout = setTimeout(saveCoachAvailability, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadExistingAvailability();
|
||||
renderCoachGrid();
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user