remodulation du projet et des classes

This commit is contained in:
cedrick2711
2026-07-28 23:33:31 -04:00
parent 90ac3eb804
commit 3feae80767
94 changed files with 2644 additions and 4366 deletions
+361
View File
@@ -0,0 +1,361 @@
{% extends "layouts/base.html" %}
{% block title %}Calendar - TryoutPro{% endblock %}
{% block page_title %}Calendar{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / Calendar</span>{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h3><i class="fas fa-calendar-alt"></i> Schedule</h3>
<div class="header-actions">
<div class="btn-group" role="group">
<button type="button" class="btn btn-sm btn-outline" onclick="changeView('dayGridMonth')">
<i class="fas fa-calendar"></i> Month
</button>
<button type="button" class="btn btn-sm btn-outline" onclick="changeView('timeGridWeek')">
<i class="fas fa-calendar-week"></i> Week
</button>
<button type="button" class="btn btn-sm btn-outline" onclick="changeView('timeGridDay')">
<i class="fas fa-calendar-day"></i> Day
</button>
<button type="button" class="btn btn-sm btn-outline" onclick="changeView('listMonth')">
<i class="fas fa-list"></i> List
</button>
</div>
</div>
</div>
<div class="card-body">
<div id="calendar"></div>
</div>
</div>
<!-- Create Event Modal (for clicking empty days) -->
<div id="createEventModal" class="modal hidden">
<div class="modal-backdrop" onclick="hideCreateEventModal()"></div>
<div class="modal-content">
<div class="modal-header">
<h3><i class="fas fa-plus-circle"></i> Create New Event</h3>
<button class="modal-close" onclick="hideCreateEventModal()">&times;</button>
</div>
<div class="modal-body">
<p class="mb-3"><strong>Date:</strong> <span id="createEventDate"></span></p>
<input type="hidden" id="createEventDateInput"/>
<div class="card mb-3">
<div class="card-body">
<h5><i class="fas fa-futbol"></i> Schedule Tryout Match</h5>
<p class="text-muted small">Add a scrim/match inside an existing tryout</p>
<div class="form-inline">
<select id="createTryoutSelect" class="form-select" style="flex:1;">
<option value="">-- Select a tryout --</option>
</select>
<button class="btn btn-sm btn-primary ml-2" onclick="goToTryoutMatch()">
<i class="fas fa-arrow-right"></i> Go
</button>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-body">
<h5><i class="fas fa-users"></i> Schedule Team Match</h5>
<p class="text-muted small">Regular season match for an org team</p>
<div class="form-inline">
<select id="createTeamSelect" class="form-select" style="flex:1;">
<option value="">-- Select a team --</option>
</select>
<button class="btn btn-sm btn-success ml-2" onclick="goToTeamMatch()">
<i class="fas fa-arrow-right"></i> Go
</button>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<h5><i class="fas fa-calendar-plus"></i> Create New Tryout</h5>
<p class="text-muted small">Create a brand new tryout event</p>
<button class="btn btn-sm btn-info" onclick="goToCreateTryout()">
<i class="fas fa-plus"></i> Create Tryout
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Event Details Modal -->
<div id="eventModal" class="modal hidden">
<div class="modal-backdrop" onclick="hideEventModal()"></div>
<div class="modal-content">
<div class="modal-header">
<h3 id="modalTitle">Event Details</h3>
<button class="modal-close" onclick="hideEventModal()">&times;</button>
</div>
<div class="modal-body">
<div id="modalContent"></div>
<div id="modalPresenceToggle" class="form-actions mt-3" style="display: none; justify-content: center;">
<button class="btn btn-sm btn-outline" id="calPresenceBtn">Confirm</button>
</div>
<div id="modalActions" class="form-actions mt-3" style="display: none;">
<button class="btn btn-sm btn-danger" id="deleteMatchBtn" style="display: none;">
<i class="fas fa-trash"></i> Delete Match
</button>
<button class="btn btn-sm btn-primary" id="editMatchBtn" style="display: none;">
<i class="fas fa-edit"></i> Edit Match
</button>
<button class="btn btn-sm btn-primary" id="viewTryoutBtn" style="display: none;">
<i class="fas fa-eye"></i> View Tryout
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<link href="https://cdn.jsdelivr.net/npm/[email protected]/index.global.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/index.global.min.js"></script>
<script>
var canScheduleMatches = {% if current_user.can_schedule_matches() %}true{% else %}false{% endif %};
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
initialView: 'dayGridMonth',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listMonth'
},
events: '/matches/api/events',
eventClick: function(info) {
showEventModal(info.event);
},
dateClick: function(info) {
if (canScheduleMatches) {
var dateStr = info.dateStr;
showCreateEventModal(dateStr);
}
},
selectable: true,
select: function(info) {
if (canScheduleMatches) {
var dateStr = info.startStr;
showCreateEventModal(dateStr);
calendar.unselect();
}
},
slotMinTime: '12:00:00',
slotMaxTime: '24:00:00'
});
calendar.render();
window.fcCalendar = calendar;
// Pre-load tryout and team options for the create modal
if (canScheduleMatches) {
fetchTryoutOptions();
fetchTeamOptions();
}
});
function changeView(viewName) {
if (window.fcCalendar) {
window.fcCalendar.changeView(viewName);
}
}
// --- Create Event Modal ---
function showCreateEventModal(dateStr) {
document.getElementById('createEventDate').textContent = dateStr;
document.getElementById('createEventDateInput').value = dateStr;
// Reset dropdowns
document.getElementById('createTryoutSelect').value = '';
document.getElementById('createTeamSelect').value = '';
document.getElementById('createEventModal').classList.remove('hidden');
}
function hideCreateEventModal() {
document.getElementById('createEventModal').classList.add('hidden');
}
function goToTryoutMatch() {
var tryoutId = document.getElementById('createTryoutSelect').value;
if (!tryoutId) { alert('Please select a tryout.'); return; }
var date = document.getElementById('createEventDateInput').value;
window.location.href = '/matches/create/' + tryoutId + '?date=' + date;
}
function goToTeamMatch() {
var teamId = document.getElementById('createTeamSelect').value;
if (!teamId) { alert('Please select a team.'); return; }
var date = document.getElementById('createEventDateInput').value;
window.location.href = '/team-matches/' + teamId + '/create?date=' + date;
}
function goToCreateTryout() {
// Tryout creation doesn't support pre-filling date easily, just navigate
window.location.href = '/tryouts/create';
}
// Pre-fetch data for dropdowns
function fetchTryoutOptions() {
fetch('/matches/api/manageable-tryouts')
.then(function(r) { return r.json(); })
.then(function(data) {
var sel = document.getElementById('createTryoutSelect');
sel.innerHTML = '<option value="">-- Select a tryout --</option>';
data.forEach(function(t) {
sel.innerHTML += '<option value="' + t.id + '">' + t.title + ' (' + t.date + ')</option>';
});
})
.catch(function() {});
}
function fetchTeamOptions() {
fetch('/team-matches/api/manageable-teams')
.then(function(r) { return r.json(); })
.then(function(data) {
var sel = document.getElementById('createTeamSelect');
sel.innerHTML = '<option value="">-- Select a team --</option>';
data.forEach(function(t) {
sel.innerHTML += '<option value="' + t.id + '">' + t.name + '</option>';
});
})
.catch(function() {});
}
function showEventModal(event) {
var props = event.extendedProps;
var title = event.title;
var type = props.type;
var date = event.start ? event.start.toDateString() : '';
var content = '<div class="detail-grid">';
content += '<div class="detail-item"><span class="detail-label">Type</span><span class="detail-value">';
content += '<span class="badge badge-' + (type === 'tryout' ? 'info' : (props.match_type === 'team_vs_team' || props.match_type === 'player_vs_player' ? 'success' : 'warning')) + '">';
content += (type === 'tryout' ? 'Tryout' : (props.match_type === 'team_vs_team' ? 'Team Match' : (props.match_type === 'player_vs_player' ? 'Player Match' : 'Player Scrim'))) + '</span>';
content += '</span></div>';
content += '<div class="detail-item"><span class="detail-label">Title</span><span class="detail-value">' + title + '</span></div>';
content += '<div class="detail-item"><span class="detail-label">Date</span><span class="detail-value">' + date + '</span></div>';
content += '<div class="detail-item"><span class="detail-label">Location</span><span class="detail-value">' + (props.location || 'TBD') + '</span></div>';
content += '<div class="detail-item"><span class="detail-label">Status</span><span class="detail-value">';
content += '<span class="badge badge-' + (props.status || 'scheduled') + '">' + (props.status || 'scheduled') + '</span>';
content += '</span></div>';
// Add team separation for matches
if (type === 'match' && props.participants) {
content += '<div class="detail-item full-width"><span class="detail-label">Teams</span><span class="detail-value">';
if (props.match_type === 'team_vs_team') {
// For team vs team, participants is "Team1 vs Team2"
var teams = props.participants.split(' vs ');
if (teams.length >= 2) {
content += '<div class="match-teams">';
content += '<div class="match-team"><span class="team-name">' + teams[0] + '</span></div>';
content += '<div class="match-vs">vs</div>';
content += '<div class="match-team"><span class="team-name">' + teams[1] + '</span></div>';
content += '</div>';
} else {
content += props.participants;
}
} else if (props.match_type === 'player_vs_player') {
// For player vs player, we need to parse the participants
// The format is "player1, player2 vs player3, player4"
var parts = props.participants.split(' vs ');
if (parts.length >= 2) {
content += '<div class="match-teams">';
content += '<div class="match-team"><span class="team-name">Team 1</span><ul class="team-players-list"><li>' + parts[0].split(', ').join('</li><li>') + '</li></ul></div>';
content += '<div class="match-vs">vs</div>';
content += '<div class="match-team"><span class="team-name">Team 2</span><ul class="team-players-list"><li>' + parts[1].split(', ').join('</li><li>') + '</li></ul></div>';
content += '</div>';
} else {
content += props.participants;
}
} else {
// For player scrim, just show the list
content += props.participants;
}
content += '</span></div>';
}
if (props.description) {
content += '<div class="detail-item full-width"><span class="detail-label">Description</span><span class="detail-value">' + props.description + '</span></div>';
}
content += '</div>';
document.getElementById('modalTitle').textContent = type === 'tryout' ? 'Tryout Details' : 'Match Details';
document.getElementById('modalContent').innerHTML = content;
// Reset buttons
document.getElementById('deleteMatchBtn').style.display = 'none';
document.getElementById('editMatchBtn').style.display = 'none';
document.getElementById('viewTryoutBtn').style.display = 'none';
// Show action buttons for matches (coaches and above)
if (type === 'match' && canScheduleMatches) {
document.getElementById('modalActions').style.display = 'flex';
document.getElementById('editMatchBtn').style.display = 'inline-flex';
document.getElementById('editMatchBtn').onclick = function() {
window.location.href = '/matches/' + props.match_id + '/edit';
};
} else if (type === 'tryout' && canScheduleMatches) {
document.getElementById('modalActions').style.display = 'flex';
document.getElementById('viewTryoutBtn').style.display = 'inline-flex';
document.getElementById('viewTryoutBtn').onclick = function() {
window.location.href = '/tryouts/' + props.tryout_id;
};
} else {
document.getElementById('modalActions').style.display = 'none';
}
// Show presence toggle for matches where user is a participant
var presenceDiv = document.getElementById('modalPresenceToggle');
if (type === 'match' && props.user_participant_id) {
presenceDiv.style.display = 'flex';
var confirmed = props.user_attendance_confirmed || false;
var toggleBtn = document.getElementById('calPresenceBtn');
toggleBtn.textContent = confirmed ? '✅ Confirmed' : 'Confirm';
toggleBtn.className = 'btn btn-sm ' + (confirmed ? 'btn-success' : 'btn-outline');
toggleBtn.onclick = function() {
toggleCalendarPresence(props.match_id, props.user_participant_id, toggleBtn);
};
} else {
presenceDiv.style.display = 'none';
}
document.getElementById('eventModal').classList.remove('hidden');
}
function toggleCalendarPresence(matchId, participantId, btn) {
fetch('/matches/' + matchId + '/toggle-presence/' + participantId, {
method: 'POST',
headers: {
'X-CSRFToken': '{{ csrf_token() }}',
'Content-Type': 'application/json'
}
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.attendance_confirmed) {
btn.classList.add('btn-success');
btn.classList.remove('btn-outline');
btn.textContent = '✅ Confirmed';
} else {
btn.classList.remove('btn-success');
btn.classList.add('btn-outline');
btn.textContent = 'Confirm';
}
})
.catch(function(err) {
console.error('Error toggling presence:', err);
});
}
function hideEventModal() {
document.getElementById('eventModal').classList.add('hidden');
}
</script>
{% endblock %}