Ajout de fonctionnalités:
Ajout de notes personnels et de notes d'équipe avec historique. Ajout de prise de rendez-vous avec un coach selon ses disponibilités
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block title %}Add Note from Match - TryoutPro{% endblock %}
|
||||
{% block page_title %}Add Note from Match{% endblock %}
|
||||
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="{{ url_for('tryouts.list_tryouts') }}">Tryouts</a> / <a href="{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}">{{ tryout.title }}</a> / Add Note</span>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-futbol"></i> Add Note for Match: {{ match.title }}</h3>
|
||||
<span class="badge badge-info">{{ match.date.strftime('%m/%d/%Y') }}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('users.add_note_from_match', match_id=match.id) }}" class="form">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="player_id">Select Player</label>
|
||||
<select name="player_id" id="player_id" class="form-select" required>
|
||||
<option value="">-- Select a Player --</option>
|
||||
{% for player in players %}
|
||||
<option value="{{ player.id }}">{{ player.full_name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="content">Note Content</label>
|
||||
<textarea name="content" id="content" class="form-textarea" rows="4" placeholder="Enter feedback or coaching tips for this player from the match..." required></textarea>
|
||||
<p class="form-text">This note will be linked to this match and visible to the selected player.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save"></i> Add Note
|
||||
</button>
|
||||
<a href="{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Back to Tryout
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if team_notes %}
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-users"></i> Team Notes Reference</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="detail-grid">
|
||||
{% for note in team_notes %}
|
||||
<div class="detail-item full-width mb-3">
|
||||
<span class="detail-label">
|
||||
<i class="fas fa-user"></i> {{ note.coach.full_name if note.coach else 'Unknown Coach' }}
|
||||
</span>
|
||||
<span class="detail-value">{{ note.content | nl2br }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,63 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block title %}Add Note from Tryout - TryoutPro{% endblock %}
|
||||
{% block page_title %}Add Note from Tryout{% endblock %}
|
||||
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="{{ url_for('tryouts.list_tryouts') }}">Tryouts</a> / <a href="{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}">{{ tryout.title }}</a> / Add Note</span>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-calendar-alt"></i> Add Note for Tryout: {{ tryout.title }}</h3>
|
||||
<span class="badge badge-success">{{ tryout.date.strftime('%m/%d/%Y') }}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('users.add_note_from_tryout', tryout_id=tryout.id) }}" class="form">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="player_id">Select Player</label>
|
||||
<select name="player_id" id="player_id" class="form-select" required>
|
||||
<option value="">-- Select a Player --</option>
|
||||
{% for player in players %}
|
||||
<option value="{{ player.id }}">{{ player.full_name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="content">Note Content</label>
|
||||
<textarea name="content" id="content" class="form-textarea" rows="4" placeholder="Enter feedback or coaching tips for this player from the tryout..." required></textarea>
|
||||
<p class="form-text">This note will be linked to this tryout and visible to the selected player.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save"></i> Add Note
|
||||
</button>
|
||||
<a href="{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Back to Tryout
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if team_notes %}
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-users"></i> Team Notes Reference</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="detail-grid">
|
||||
{% for note in team_notes %}
|
||||
<div class="detail-item full-width mb-3">
|
||||
<span class="detail-label">
|
||||
<i class="fas fa-user"></i> {{ note.coach.full_name if note.coach else 'Unknown Coach' }}
|
||||
</span>
|
||||
<span class="detail-value">{{ note.content | nl2br }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,80 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block title %}Add Note - TryoutPro{% endblock %}
|
||||
{% block page_title %}Add Personal Note{% endblock %}
|
||||
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="{{ url_for('users.my_notes') }}">My Notes</a> / Add Note</span>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-sticky-note"></i> Add Personal Note</h3>
|
||||
{% if org_team %}
|
||||
<span class="badge badge-esport">{{ org_team.name }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('users.add_personal_note') }}" class="form">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="player_id">Select Player</label>
|
||||
<select name="player_id" id="player_id" class="form-select" required>
|
||||
<option value="">-- Select a Player --</option>
|
||||
{% for player in players %}
|
||||
<option value="{{ player.id }}">{{ player.full_name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="content">Note Content</label>
|
||||
<textarea name="content" id="content" class="form-textarea" rows="4" placeholder="Enter personal feedback or coaching tips for this player..." required></textarea>
|
||||
<p class="form-text">These notes will only be visible to the selected player.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="context">Context (Optional)</label>
|
||||
<p class="form-text text-muted">Link this note to a specific match, tryout, or team for better organization.</p>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="match_id">Match</label>
|
||||
<select name="match_id" id="match_id" class="form-select">
|
||||
<option value="">-- Select Match --</option>
|
||||
{% for match in matches %}
|
||||
<option value="{{ match.id }}">{{ match.title }} - {{ match.date.strftime('%m/%d/%Y') }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="tryout_id">Tryout</label>
|
||||
<select name="tryout_id" id="tryout_id" class="form-select">
|
||||
<option value="">-- Select Tryout --</option>
|
||||
{% for tryout in tryouts %}
|
||||
<option value="{{ tryout.id }}">{{ tryout.title }} - {{ tryout.date.strftime('%m/%d/%Y') }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="team_id">Team</label>
|
||||
<select name="team_id" id="team_id" class="form-select">
|
||||
<option value="">-- Select Team --</option>
|
||||
{% for team in teams %}
|
||||
<option value="{{ team.id }}">{{ team.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save"></i> Add Note
|
||||
</button>
|
||||
<a href="{{ url_for('users.my_notes') }}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Back to Notes
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,260 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block title %}Manage Availability - TryoutPro{% endblock %}
|
||||
{% block page_title %}Manage Availability{% endblock %}
|
||||
{% block breadcrumb %}<span class="breadcrumb">Home / Coach Availability</span>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-clock"></i> Set Your Weekly Availability</h3>
|
||||
<p class="text-muted small">Select time slots when you're available for One on One sessions</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-4">
|
||||
<button type="button" class="btn btn-secondary" onclick="clearAllAvailability()">
|
||||
<i class="fas fa-trash"></i> Clear All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-list"></i> Current Availability</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if existing_availability %}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Day</th>
|
||||
<th>Time</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for av in existing_availability %}
|
||||
<tr>
|
||||
<td>{{ ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'][av.day_of_week] }}</td>
|
||||
<td>{{ av.start_time.strftime('%I:%M %p') }} - {{ av.end_time.strftime('%I:%M %p') }}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-danger" onclick="deleteAvailability({{ av.id }})">
|
||||
<i class="fas fa-trash"></i> Remove
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="text-muted">No availability slots set. Use the grid above to add your available times.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<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: white;
|
||||
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>
|
||||
// Time slots from 8:00 AM to 10:00 PM (30-minute intervals)
|
||||
const 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;
|
||||
TIME_SLOTS.push({ time: timeStr, display: displayTime });
|
||||
}
|
||||
}
|
||||
|
||||
// Day names
|
||||
const DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
||||
|
||||
// Track selected slots: {day_of_week: [time_strings]}
|
||||
let selectedSlots = {};
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadExistingAvailability();
|
||||
renderGrid();
|
||||
});
|
||||
|
||||
function loadExistingAvailability() {
|
||||
// Load from existing data
|
||||
{% for av in existing_availability %}
|
||||
if (!selectedSlots[{{ av.day_of_week }}]) {
|
||||
selectedSlots[{{ av.day_of_week }}] = [];
|
||||
}
|
||||
selectedSlots[{{ av.day_of_week }}].push('{{ av.start_time.strftime('%H:%M') }}');
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
function renderGrid() {
|
||||
const grid = document.getElementById('availability-grid');
|
||||
let html = '';
|
||||
|
||||
DAYS.forEach((day, dayIndex) => {
|
||||
html += '<div class="day-column">';
|
||||
html += '<div class="day-header">' + day.substring(0, 3) + '</div>';
|
||||
|
||||
TIME_SLOTS.forEach(slot => {
|
||||
const isSelected = selectedSlots[dayIndex] && selectedSlots[dayIndex].includes(slot.time);
|
||||
const cssClass = isSelected ? 'time-slot selected' : 'time-slot';
|
||||
html += '<div class="' + cssClass + '" data-day="' + dayIndex + '" data-time="' + slot.time + '" onclick="toggleSlot(' + dayIndex + ', \'' + slot.time + '\', this)">' + slot.display + '</div>';
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
grid.innerHTML = html;
|
||||
}
|
||||
|
||||
function toggleSlot(dayOfWeek, timeStr, element) {
|
||||
if (!selectedSlots[dayOfWeek]) {
|
||||
selectedSlots[dayOfWeek] = [];
|
||||
}
|
||||
|
||||
const index = selectedSlots[dayOfWeek].indexOf(timeStr);
|
||||
if (index === -1) {
|
||||
selectedSlots[dayOfWeek].push(timeStr);
|
||||
element.classList.add('selected');
|
||||
} else {
|
||||
selectedSlots[dayOfWeek].splice(index, 1);
|
||||
element.classList.remove('selected');
|
||||
}
|
||||
}
|
||||
|
||||
function saveAvailability() {
|
||||
const slots = [];
|
||||
for (let day in selectedSlots) {
|
||||
selectedSlots[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) {
|
||||
flash('Availability saved successfully!', 'success');
|
||||
setTimeout(() => location.reload(), 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
selectedSlots = {};
|
||||
renderGrid();
|
||||
flash('Availability cleared!', 'success');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteAvailability(availabilityId) {
|
||||
fetch('{{ url_for("users.delete_coach_availability", availability_id=0) }}'.replace('/0', '/' + availabilityId), { method: 'POST' })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
flash('Availability slot removed!', 'success');
|
||||
setTimeout(() => location.reload(), 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function flash(message, type) {
|
||||
const flashContainer = document.querySelector('.flash-messages');
|
||||
const alert = document.createElement('div');
|
||||
alert.className = 'alert alert-' + type + ' alert-dismissible';
|
||||
alert.innerHTML = '<span>' + message + '</span><button type="button" class="alert-close" onclick="this.parentElement.remove()">×</button>';
|
||||
flashContainer.appendChild(alert);
|
||||
}
|
||||
|
||||
// Auto-save on change (debounced)
|
||||
let saveTimeout;
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.classList.contains('time-slot')) {
|
||||
clearTimeout(saveTimeout);
|
||||
saveTimeout = setTimeout(saveAvailability, 1000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -114,7 +114,19 @@
|
||||
<hr class="section-divider">
|
||||
<h4 class="section-title"><i class="fas fa-user-friends"></i> Select Players</h4>
|
||||
|
||||
<p class="text-muted small"><i class="fas fa-info-circle"></i> Click on players to assign them to Team 1 or Team 2. Players available in all selected time blocks are shown below.</p>
|
||||
<!-- Randomize Teams Section -->
|
||||
<div class="randomize-section">
|
||||
<div class="randomize-controls">
|
||||
<label><i class="fas fa-random"></i> Randomize Teams:</label>
|
||||
<input type="number" id="team1-size" class="randomize-input" min="1" value="1" onchange="updateRandomizePreview()">
|
||||
<span>vs</span>
|
||||
<span id="team2-size-preview" class="randomize-preview">0</span>
|
||||
<button type="button" class="btn btn-sm randomize-btn" onclick="randomizeTeams()">
|
||||
<i class="fas fa-random"></i> Randomize
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-muted small" id="randomize-hint">Select players then click Randomize to split them into teams.</p>
|
||||
</div>
|
||||
|
||||
<div class="pvp-layout">
|
||||
<div class="team-column">
|
||||
@@ -203,7 +215,7 @@
|
||||
font-size: 14px;
|
||||
}
|
||||
.team-selection .player-item:hover {
|
||||
background: var(--accent-color);
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
.player-pool {
|
||||
@@ -277,6 +289,41 @@
|
||||
.team-selection .player-item:hover .remove-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.randomize-section {
|
||||
margin: 15px 0;
|
||||
padding: 10px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.randomize-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.randomize-controls label {
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
.randomize-input {
|
||||
width: 60px;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.randomize-preview {
|
||||
font-weight: bold;
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.randomize-btn {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
.randomize-btn:hover {
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
// Player data as simple JS object (id -> full_name)
|
||||
@@ -645,6 +692,7 @@ function updatePlayerPool() {
|
||||
});
|
||||
|
||||
pool.innerHTML = html || '<p class="text-muted small">No players available</p>';
|
||||
updateRandomizePreview();
|
||||
}
|
||||
|
||||
function assignToTeam(playerId, teamSide) {
|
||||
@@ -727,5 +775,74 @@ function toggleMatchType() {
|
||||
scrimSection.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function updateRandomizePreview() {
|
||||
var checkedCount = document.querySelectorAll('#team1-selection [data-player-id], #team2-selection [data-player-id]').length;
|
||||
var team1Size = parseInt(document.getElementById('team1-size').value) || 1;
|
||||
var team2Size = checkedCount - team1Size;
|
||||
|
||||
if (team2Size < 0) team2Size = 0;
|
||||
|
||||
document.getElementById('team2-size-preview').textContent = team2Size;
|
||||
}
|
||||
|
||||
function randomizeTeams() {
|
||||
var allAssigned = [];
|
||||
|
||||
// Get all players currently in either team
|
||||
document.querySelectorAll('#team1-selection [data-player-id], #team2-selection [data-player-id]').forEach(function(el) {
|
||||
allAssigned.push(parseInt(el.getAttribute('data-player-id')));
|
||||
});
|
||||
|
||||
if (allAssigned.length === 0) {
|
||||
alert('Please assign at least one player before randomizing teams.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Shuffle the array using Fisher-Yates
|
||||
for (var i = allAssigned.length - 1; i > 0; i--) {
|
||||
var j = Math.floor(Math.random() * (i + 1));
|
||||
var temp = allAssigned[i];
|
||||
allAssigned[i] = allAssigned[j];
|
||||
allAssigned[j] = temp;
|
||||
}
|
||||
|
||||
var team1Size = parseInt(document.getElementById('team1-size').value) || 1;
|
||||
var team1Players = allAssigned.slice(0, team1Size);
|
||||
var team2Players = allAssigned.slice(team1Size);
|
||||
|
||||
// Clear both teams
|
||||
document.querySelectorAll('#team1-selection [data-player-id], #team2-selection [data-player-id]').forEach(function(el) {
|
||||
el.remove();
|
||||
});
|
||||
|
||||
// Assign shuffled players to teams
|
||||
team1Players.forEach(function(pid) {
|
||||
var teamDiv = document.getElementById('team1-selection');
|
||||
var playerName = playerDataById.player_data[pid];
|
||||
if (playerName) {
|
||||
var html = '<div class="player-item" data-player-id="' + pid + '" onclick="returnToPool(' + pid + ', event)">';
|
||||
html += playerName;
|
||||
html += '<span class="remove-btn">↺</span>';
|
||||
html += '</div>';
|
||||
teamDiv.insertAdjacentHTML('beforeend', html);
|
||||
}
|
||||
});
|
||||
|
||||
team2Players.forEach(function(pid) {
|
||||
var teamDiv = document.getElementById('team2-selection');
|
||||
var playerName = playerDataById.player_data[pid];
|
||||
if (playerName) {
|
||||
var html = '<div class="player-item" data-player-id="' + pid + '" onclick="returnToPool(' + pid + ', event)">';
|
||||
html += playerName;
|
||||
html += '<span class="remove-btn">↺</span>';
|
||||
html += '</div>';
|
||||
teamDiv.insertAdjacentHTML('beforeend', html);
|
||||
}
|
||||
});
|
||||
|
||||
updateHiddenInputs();
|
||||
updateRandomizePreview();
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
+429
-186
@@ -7,6 +7,7 @@
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-futbol"></i> Edit Match</h3>
|
||||
<p class="text-muted small">Match Type: <strong>{{ match.match_type.replace('_', ' ').title() }}</strong></p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('matches.edit_match', match_id=match.id) }}" class="form" id="editMatchForm">
|
||||
@@ -36,7 +37,7 @@
|
||||
{% if current_user.can_manage_teams() or current_user.can_schedule_matches() %}
|
||||
<hr class="section-divider">
|
||||
<h4 class="section-title"><i class="fas fa-clock"></i> Select Match Time</h4>
|
||||
<p class="text-muted small">Click time slots consecutively to set match duration. Available players will be auto-selected below.</p>
|
||||
<p class="text-muted small">Click time slots consecutively to set match duration. Players available in all selected time blocks are shown below.</p>
|
||||
|
||||
<div id="merged-disponibility-selected" class="merged-disponibility-selected-display hidden">
|
||||
<span>Selected time:</span>
|
||||
@@ -83,120 +84,111 @@
|
||||
</div>
|
||||
|
||||
<!-- Team vs Team Selection -->
|
||||
{% if match.match_type == 'team_vs_team' %}
|
||||
<hr class="section-divider">
|
||||
<h4 class="section-title"><i class="fas fa-users"></i> Teams</h4>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="team1_id">Team 1</label>
|
||||
<select name="team1_id" id="team1_id" class="form-select">
|
||||
<option value="">-- Select Team 1 --</option>
|
||||
{% for team in teams %}
|
||||
<option value="{{ team.id }}" {% if match.team1_id == team.id %}selected{% endif %}>{{ team.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div id="team-vs-team-section" {% if match.match_type != 'team_vs_team' %}class="hidden"{% endif %}>
|
||||
<hr class="section-divider">
|
||||
<h4 class="section-title"><i class="fas fa-users"></i> Teams</h4>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="team1_id">Team 1</label>
|
||||
<select name="team1_id" id="team1_id" class="form-select">
|
||||
<option value="">-- Select Team 1 --</option>
|
||||
{% for team in teams %}
|
||||
<option value="{{ team.id }}" {% if match.team1_id == team.id %}selected{% endif %}>{{ team.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="team2_id">Team 2</label>
|
||||
<select name="team2_id" id="team2_id" class="form-select">
|
||||
<option value="">-- Select Team 2 --</option>
|
||||
{% for team in teams %}
|
||||
<option value="{{ team.id }}" {% if match.team2_id == team.id %}selected{% endif %}>{{ team.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="team2_id">Team 2</label>
|
||||
<select name="team2_id" id="team2_id" class="form-select">
|
||||
<option value="">-- Select Team 2 --</option>
|
||||
{% for team in teams %}
|
||||
<option value="{{ team.id }}" {% if match.team2_id == team.id %}selected{% endif %}>{{ team.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<!-- Show team rosters -->
|
||||
<div class="team-rosters mt-3">
|
||||
{% if match.team1 %}
|
||||
<div class="team-roster">
|
||||
<h5>{{ match.team1.name }}</h5>
|
||||
<ul class="team-members-list">
|
||||
{% for member in match.team1.members %}
|
||||
<li>{{ member.player.full_name if member.player else 'Unknown Player' }}{% if member.position %} <span class="position-tag">{{ member.position }}</span>{% endif %}</li>
|
||||
{% else %}
|
||||
<li class="text-muted">No players assigned</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if match.team2 %}
|
||||
<div class="team-roster">
|
||||
<h5>{{ match.team2.name }}</h5>
|
||||
<ul class="team-members-list">
|
||||
{% for member in match.team2.members %}
|
||||
<li>{{ member.player.full_name if member.player else 'Unknown Player' }}{% if member.position %} <span class="position-tag">{{ member.position }}</span>{% endif %}</li>
|
||||
{% else %}
|
||||
<li class="text-muted">No players assigned</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Show team rosters -->
|
||||
<div class="team-rosters mt-3">
|
||||
{% if match.team1 %}
|
||||
<div class="team-roster">
|
||||
<h5>{{ match.team1.name }}</h5>
|
||||
<ul class="team-members-list">
|
||||
{% for member in match.team1.members %}
|
||||
<li>{{ member.player.full_name if member.player else 'Unknown Player' }}{% if member.position %} <span class="position-tag">{{ member.position }}</span>{% endif %}</li>
|
||||
{% else %}
|
||||
<li class="text-muted">No players assigned</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if match.team2 %}
|
||||
<div class="team-roster">
|
||||
<h5>{{ match.team2.name }}</h5>
|
||||
<ul class="team-members-list">
|
||||
{% for member in match.team2.members %}
|
||||
<li>{{ member.player.full_name if member.player else 'Unknown Player' }}{% if member.position %} <span class="position-tag">{{ member.position }}</span>{% endif %}</li>
|
||||
{% else %}
|
||||
<li class="text-muted">No players assigned</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Player vs Player Selection -->
|
||||
{% elif match.match_type == 'player_vs_player' %}
|
||||
<hr class="section-divider">
|
||||
<h4 class="section-title"><i class="fas fa-user-friends"></i> Select Players</h4>
|
||||
|
||||
<!-- Randomize Teams Section -->
|
||||
<div class="randomize-section">
|
||||
<div class="randomize-controls">
|
||||
<label><i class="fas fa-random"></i> Randomize Teams:</label>
|
||||
<input type="number" id="team1-size" class="randomize-input" min="1" value="1" onchange="updateRandomizePreview()">
|
||||
<span>vs</span>
|
||||
<span id="team2-size-preview" class="randomize-preview">0</span>
|
||||
<button type="button" class="btn btn-sm randomize-btn" onclick="randomizeTeams()">
|
||||
<i class="fas fa-random"></i> Randomize
|
||||
</button>
|
||||
<div id="player-vs-player-section" {% if match.match_type != 'player_vs_player' %}class="hidden"{% endif %}>
|
||||
<hr class="section-divider">
|
||||
<h4 class="section-title"><i class="fas fa-user-friends"></i> Select Players</h4>
|
||||
|
||||
<!-- Randomize Teams Section -->
|
||||
<div class="randomize-section">
|
||||
<div class="randomize-controls">
|
||||
<label><i class="fas fa-random"></i> Randomize Teams:</label>
|
||||
<input type="number" id="team1-size" class="randomize-input" min="1" value="1" onchange="updateRandomizePreview()">
|
||||
<span>vs</span>
|
||||
<span id="team2-size-preview" class="randomize-preview">0</span>
|
||||
<button type="button" class="btn btn-sm randomize-btn" onclick="randomizeTeams()">
|
||||
<i class="fas fa-random"></i> Randomize
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-muted small" id="randomize-hint">Select players then click Randomize to split them into teams.</p>
|
||||
</div>
|
||||
<p class="text-muted small" id="randomize-hint">Select players then click Randomize to split them into teams.</p>
|
||||
</div>
|
||||
|
||||
{% if current_user.can_manage_teams() or current_user.can_schedule_matches() %}
|
||||
<p class="text-muted small"><i class="fas fa-info-circle"></i> Green indicators show player availability for the match date/time</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label>Team 1 Players</label>
|
||||
<div class="checkbox-grid" id="team1-players-list">
|
||||
{% for player in all_players %}
|
||||
<label class="checkbox-label player-checkbox" data-player-id="{{ player.id }}">
|
||||
<input type="checkbox" name="team1_player_ids" value="{{ player.id }}" {% if player.id in team1_player_ids %}checked{% endif %} onchange="handleTeamSelection(this, 1)">
|
||||
{{ player.full_name }}
|
||||
<span class="disponibility-indicator" data-player-id="{{ player.id }}"></span>
|
||||
</label>
|
||||
{% endfor %}
|
||||
|
||||
<div class="pvp-layout">
|
||||
<div class="team-column">
|
||||
<h5 class="team-header">Team 1</h5>
|
||||
<div id="team1-selection" class="team-selection"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Team 2 Players</label>
|
||||
<div class="checkbox-grid" id="team2-players-list">
|
||||
{% for player in all_players %}
|
||||
<label class="checkbox-label player-checkbox" data-player-id="{{ player.id }}">
|
||||
<input type="checkbox" name="team2_player_ids" value="{{ player.id }}" {% if player.id in team2_player_ids %}checked{% endif %} onchange="handleTeamSelection(this, 2)">
|
||||
{{ player.full_name }}
|
||||
<span class="disponibility-indicator" data-player-id="{{ player.id }}"></span>
|
||||
</label>
|
||||
{% endfor %}
|
||||
|
||||
<div class="player-pool">
|
||||
<div class="player-pool-header">Available Players</div>
|
||||
<div id="available-players-pool" class="available-players-pool">
|
||||
<p class="text-muted small">All registered players are shown. Click time slots to filter available players.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="team-column">
|
||||
<h5 class="team-header">Team 2</h5>
|
||||
<div id="team2-selection" class="team-selection"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="team1_player_ids" id="team1-player-ids-input">
|
||||
<input type="hidden" name="team2_player_ids" id="team2-player-ids-input">
|
||||
</div>
|
||||
|
||||
<!-- Player Scrim Selection -->
|
||||
{% else %}
|
||||
<hr class="section-divider">
|
||||
<h4 class="section-title"><i class="fas fa-users"></i> Select Players</h4>
|
||||
|
||||
{% if current_user.can_manage_teams() or current_user.can_schedule_matches() %}
|
||||
<p class="text-muted small"><i class="fas fa-info-circle"></i> Green indicators show player availability for the match date/time</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-group">
|
||||
<label>Players</label>
|
||||
<div id="player-scrim-section" {% if match.match_type != 'player_scrim' %}class="hidden"{% endif %}>
|
||||
<hr class="section-divider">
|
||||
<h4 class="section-title"><i class="fas fa-users"></i> Select Players</h4>
|
||||
|
||||
{% if current_user.can_manage_teams() or current_user.can_schedule_matches() %}
|
||||
<p class="text-muted small"><i class="fas fa-info-circle"></i> Green indicators show player availability for the match date/time</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="checkbox-grid" id="scrim-players-list">
|
||||
{% for player in all_players %}
|
||||
<label class="checkbox-label player-checkbox" data-player-id="{{ player.id }}">
|
||||
@@ -207,7 +199,6 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
@@ -223,7 +214,169 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<style>
|
||||
.pvp-layout {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.team-column {
|
||||
flex: 1;
|
||||
min-width: 150px;
|
||||
}
|
||||
.team-header {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.team-selection {
|
||||
min-height: 200px;
|
||||
border: 2px dashed var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
}
|
||||
.team-selection .player-item {
|
||||
padding: 8px 12px;
|
||||
margin: 5px 0;
|
||||
background: var(--bg-tertiary);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
.team-selection .player-item:hover {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
.player-pool {
|
||||
flex: 2;
|
||||
min-width: 200px;
|
||||
}
|
||||
.player-pool-header {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.available-players-pool {
|
||||
min-height: 200px;
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.available-players-pool .player-item {
|
||||
padding: 8px 12px;
|
||||
margin: 5px 0;
|
||||
background: var(--success-light);
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
.available-players-pool .player-item:hover {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
border-color: var(--primary);
|
||||
}
|
||||
.available-players-pool .player-item.available {
|
||||
background: var(--success-light);
|
||||
border-color: var(--success);
|
||||
}
|
||||
.available-players-pool .player-item.unavailable {
|
||||
background: var(--gray-100);
|
||||
border-color: var(--gray-300);
|
||||
opacity: 0.6;
|
||||
}
|
||||
.player-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.player-item .remove-btn {
|
||||
opacity: 0.5;
|
||||
cursor: pointer;
|
||||
}
|
||||
.player-item .remove-btn:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.player-actions {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
.player-actions .btn {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.player-name {
|
||||
flex: 1;
|
||||
}
|
||||
.team-selection .player-actions {
|
||||
display: none;
|
||||
}
|
||||
.team-selection .player-item:hover .remove-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.randomize-section {
|
||||
margin: 15px 0;
|
||||
padding: 10px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.randomize-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.randomize-controls label {
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
.randomize-input {
|
||||
width: 60px;
|
||||
padding: 4px 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.randomize-preview {
|
||||
font-weight: bold;
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.randomize-btn {
|
||||
background: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
.randomize-btn:hover {
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
// Player data as simple JS object (id -> full_name)
|
||||
var playerDataById = {
|
||||
player_data: {
|
||||
{%- for p in all_players %}
|
||||
{{ p.id }}: "{{ p.full_name | escape }}",
|
||||
{%- endfor %}
|
||||
}
|
||||
};
|
||||
|
||||
// All registered player IDs
|
||||
var allRegisteredPlayers = [
|
||||
{%- for p in all_players %}
|
||||
{{ p.id }},
|
||||
{%- endfor %}
|
||||
];
|
||||
|
||||
// Current team assignments from server
|
||||
var initialTeam1Ids = {{ team1_player_ids|tojson }};
|
||||
var initialTeam2Ids = {{ team2_player_ids|tojson }};
|
||||
|
||||
// Time slots from 12pm (12:00) to 12am (24:00)
|
||||
var TIME_SLOTS = [];
|
||||
for (var h = 12; h <= 24; h++) {
|
||||
@@ -258,10 +411,11 @@ var DAYS = [
|
||||
];
|
||||
|
||||
var selectedDate = '';
|
||||
var selectedSlots = []; // Array of selected time slots: ['17:00', '17:30', '18:00']
|
||||
var selectedSlots = [];
|
||||
var allDisponibilities = {};
|
||||
var canViewDisponibilities = {% if current_user.can_manage_teams() or current_user.can_schedule_matches() %}true{% else %}false{% endif %};
|
||||
var totalPlayers = {{ all_players|length }};
|
||||
var availablePlayersForSlots = []; // Players available in ALL selected slots
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
@@ -273,6 +427,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
selectedDate = this.value;
|
||||
if (allDisponibilitiesInitialized()) {
|
||||
renderMergedDisponibilityGrid();
|
||||
updatePlayerPool();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -302,8 +457,34 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
}
|
||||
|
||||
renderMergedDisponibilityGrid();
|
||||
updateDisponibilityIndicators();
|
||||
// Initialize team assignments for player_vs_player matches
|
||||
// Populate Team 1
|
||||
initialTeam1Ids.forEach(function(pid) {
|
||||
var teamDiv = document.getElementById('team1-selection');
|
||||
var playerName = playerDataById.player_data[pid];
|
||||
if (playerName) {
|
||||
var html = '<div class="player-item" data-player-id="' + pid + '" onclick="returnToPool(' + pid + ', event)">';
|
||||
html += playerName;
|
||||
html += '<span class="remove-btn">↺</span>';
|
||||
html += '</div>';
|
||||
teamDiv.insertAdjacentHTML('beforeend', html);
|
||||
}
|
||||
});
|
||||
|
||||
// Populate Team 2
|
||||
initialTeam2Ids.forEach(function(pid) {
|
||||
var teamDiv = document.getElementById('team2-selection');
|
||||
var playerName = playerDataById.player_data[pid];
|
||||
if (playerName) {
|
||||
var html = '<div class="player-item" data-player-id="' + pid + '" onclick="returnToPool(' + pid + ', event)">';
|
||||
html += playerName;
|
||||
html += '<span class="remove-btn">↺</span>';
|
||||
html += '</div>';
|
||||
teamDiv.insertAdjacentHTML('beforeend', html);
|
||||
}
|
||||
});
|
||||
|
||||
updateHiddenInputs();
|
||||
updateRandomizePreview();
|
||||
});
|
||||
|
||||
@@ -318,6 +499,7 @@ function fetchDisponibilities() {
|
||||
.then(function(data) {
|
||||
allDisponibilities = data;
|
||||
renderMergedDisponibilityGrid();
|
||||
updatePlayerPool();
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('Error fetching disponibilities:', error);
|
||||
@@ -518,8 +700,10 @@ function fetchAvailablePlayersForSlots() {
|
||||
var jsDay = dateObj.getDay();
|
||||
var dayOfWeek = jsDay === 0 ? 6 : jsDay - 1;
|
||||
|
||||
// Find players available in ANY selected slot
|
||||
var availableIds = [];
|
||||
for (var playerId in allDisponibilities) {
|
||||
if (!allRegisteredPlayers.includes(parseInt(playerId))) continue;
|
||||
var playerSlots = getSlotsForPlayer(parseInt(playerId), dayOfWeek);
|
||||
var hasSlot = selectedSlots.some(function(slot) {
|
||||
return playerSlots.includes(slot);
|
||||
@@ -529,18 +713,8 @@ function fetchAvailablePlayersForSlots() {
|
||||
}
|
||||
}
|
||||
|
||||
var matchType = '{{ match.match_type }}';
|
||||
|
||||
if (matchType === 'player_vs_player') {
|
||||
document.querySelectorAll('input[name="team1_player_ids"], input[name="team2_player_ids"]').forEach(function(cb) {
|
||||
cb.checked = availableIds.includes(parseInt(cb.value));
|
||||
});
|
||||
} else if (matchType === 'player_scrim') {
|
||||
document.querySelectorAll('input[name="player_ids"]').forEach(function(cb) {
|
||||
cb.checked = availableIds.includes(parseInt(cb.value));
|
||||
});
|
||||
}
|
||||
|
||||
// Update player pool for PvP section
|
||||
updatePlayerPool();
|
||||
updateRandomizePreview();
|
||||
}
|
||||
|
||||
@@ -552,12 +726,111 @@ function clearTimeSelection() {
|
||||
document.querySelectorAll('.merged-disponibility-time-block').forEach(function(el) {
|
||||
el.classList.remove('selected');
|
||||
});
|
||||
availablePlayersForSlots = [];
|
||||
updatePlayerPool();
|
||||
}
|
||||
|
||||
function updatePlayerPool() {
|
||||
var pool = document.getElementById('available-players-pool');
|
||||
if (!pool) return;
|
||||
|
||||
var team1Ids = getSelectedTeamIds(1);
|
||||
var team2Ids = getSelectedTeamIds(2);
|
||||
var assignedIds = [...team1Ids, ...team2Ids];
|
||||
|
||||
// When time slots are selected, show only players available in all slots who aren't assigned yet
|
||||
// When no time slots selected, show all registered players
|
||||
var playersToShow = selectedSlots.length > 0 ? availablePlayersForSlots : allRegisteredPlayers;
|
||||
|
||||
if (playersToShow.length === 0) {
|
||||
pool.innerHTML = '<p class="text-muted small">No players available</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
var dateParts = selectedDate.split('-');
|
||||
var dateObj = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]);
|
||||
var jsDay = dateObj.getDay();
|
||||
var dayOfWeek = jsDay === 0 ? 6 : jsDay - 1;
|
||||
|
||||
var html = '';
|
||||
playersToShow.forEach(function(pid) {
|
||||
if (assignedIds.includes(pid)) return;
|
||||
|
||||
var playerName = playerDataById.player_data[pid];
|
||||
if (!playerName) return;
|
||||
|
||||
// Check if player is available during selected time slots
|
||||
var isAvailable = true;
|
||||
if (selectedSlots.length > 0 && allDisponibilities[pid]) {
|
||||
var playerSlots = getSlotsForPlayer(pid, dayOfWeek);
|
||||
isAvailable = selectedSlots.every(function(slot) {
|
||||
return playerSlots.includes(slot);
|
||||
});
|
||||
}
|
||||
|
||||
var availabilityClass = isAvailable ? 'available' : 'unavailable';
|
||||
|
||||
html += '<div class="player-item ' + availabilityClass + '" data-player-id="' + pid + '">';
|
||||
html += '<span class="player-name">' + playerName + '</span>';
|
||||
html += '<div class="player-actions">';
|
||||
html += '<button type="button" class="btn btn-sm btn-primary" onclick="assignToTeam(' + pid + ', 1)">T1</button>';
|
||||
html += '<button type="button" class="btn btn-sm btn-secondary" onclick="assignToTeam(' + pid + ', 2)">T2</button>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
pool.innerHTML = html || '<p class="text-muted small">No players available</p>';
|
||||
updateRandomizePreview();
|
||||
}
|
||||
|
||||
function assignToTeam(playerId, teamSide) {
|
||||
// First, remove player from any team they're already in
|
||||
document.querySelectorAll('[data-player-id="' + playerId + '"]').forEach(function(el) {
|
||||
el.remove();
|
||||
});
|
||||
|
||||
var teamDiv = document.getElementById('team' + teamSide + '-selection');
|
||||
var playerName = playerDataById.player_data[playerId];
|
||||
if (!playerName) return;
|
||||
|
||||
var html = '<div class="player-item" data-player-id="' + playerId + '" onclick="returnToPool(' + playerId + ', event)">';
|
||||
html += playerName;
|
||||
html += '<span class="remove-btn">↺</span>';
|
||||
html += '</div>';
|
||||
|
||||
teamDiv.insertAdjacentHTML('beforeend', html);
|
||||
updateHiddenInputs();
|
||||
updatePlayerPool();
|
||||
}
|
||||
|
||||
function returnToPool(playerId, event) {
|
||||
if (event) event.stopPropagation();
|
||||
document.querySelectorAll('[data-player-id="' + playerId + '"]').forEach(function(el) {
|
||||
el.remove();
|
||||
});
|
||||
updateHiddenInputs();
|
||||
updatePlayerPool();
|
||||
}
|
||||
|
||||
function getSelectedTeamIds(teamSide) {
|
||||
var ids = [];
|
||||
document.querySelectorAll('#team' + teamSide + '-selection [data-player-id]').forEach(function(el) {
|
||||
ids.push(parseInt(el.getAttribute('data-player-id')));
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
||||
function updateHiddenInputs() {
|
||||
var team1Ids = getSelectedTeamIds(1);
|
||||
var team2Ids = getSelectedTeamIds(2);
|
||||
document.getElementById('team1-player-ids-input').value = team1Ids.join(',');
|
||||
document.getElementById('team2-player-ids-input').value = team2Ids.join(',');
|
||||
}
|
||||
|
||||
function updateRandomizePreview() {
|
||||
var checkedCount = document.querySelectorAll('input[name="team1_player_ids"]:checked, input[name="team2_player_ids"]:checked').length;
|
||||
var assignedCount = document.querySelectorAll('#team1-selection [data-player-id], #team2-selection [data-player-id]').length;
|
||||
var team1Size = parseInt(document.getElementById('team1-size').value) || 1;
|
||||
var team2Size = checkedCount - team1Size;
|
||||
var team2Size = assignedCount - team1Size;
|
||||
|
||||
if (team2Size < 0) team2Size = 0;
|
||||
|
||||
@@ -565,92 +838,62 @@ function updateRandomizePreview() {
|
||||
}
|
||||
|
||||
function randomizeTeams() {
|
||||
var allChecked = [];
|
||||
var allAssigned = [];
|
||||
|
||||
document.querySelectorAll('input[name="team1_player_ids"]:checked, input[name="team2_player_ids"]:checked').forEach(function(cb) {
|
||||
allChecked.push(parseInt(cb.value));
|
||||
// Get all players currently in either team
|
||||
document.querySelectorAll('#team1-selection [data-player-id], #team2-selection [data-player-id]').forEach(function(el) {
|
||||
allAssigned.push(parseInt(el.getAttribute('data-player-id')));
|
||||
});
|
||||
|
||||
if (allChecked.length === 0) {
|
||||
alert('Please select at least one player before randomizing teams.');
|
||||
if (allAssigned.length === 0) {
|
||||
alert('Please assign at least one player before randomizing teams.');
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = allChecked.length - 1; i > 0; i--) {
|
||||
// Shuffle the array using Fisher-Yates
|
||||
for (var i = allAssigned.length - 1; i > 0; i--) {
|
||||
var j = Math.floor(Math.random() * (i + 1));
|
||||
var temp = allChecked[i];
|
||||
allChecked[i] = allChecked[j];
|
||||
allChecked[j] = temp;
|
||||
var temp = allAssigned[i];
|
||||
allAssigned[i] = allAssigned[j];
|
||||
allAssigned[j] = temp;
|
||||
}
|
||||
|
||||
var team1Size = parseInt(document.getElementById('team1-size').value) || 1;
|
||||
var team1Players = allChecked.slice(0, team1Size);
|
||||
var team2Players = allChecked.slice(team1Size);
|
||||
var team1Players = allAssigned.slice(0, team1Size);
|
||||
var team2Players = allAssigned.slice(team1Size);
|
||||
|
||||
document.querySelectorAll('input[name="team1_player_ids"], input[name="team2_player_ids"]').forEach(function(cb) {
|
||||
cb.checked = false;
|
||||
// Clear both teams
|
||||
document.querySelectorAll('#team1-selection [data-player-id], #team2-selection [data-player-id]').forEach(function(el) {
|
||||
el.remove();
|
||||
});
|
||||
|
||||
// Assign shuffled players to teams
|
||||
team1Players.forEach(function(pid) {
|
||||
var cb = document.querySelector('input[name="team1_player_ids"][value="' + pid + '"]');
|
||||
if (cb) cb.checked = true;
|
||||
var teamDiv = document.getElementById('team1-selection');
|
||||
var playerName = playerDataById.player_data[pid];
|
||||
if (playerName) {
|
||||
var html = '<div class="player-item" data-player-id="' + pid + '" onclick="returnToPool(' + pid + ', event)">';
|
||||
html += playerName;
|
||||
html += '<span class="remove-btn">↺</span>';
|
||||
html += '</div>';
|
||||
teamDiv.insertAdjacentHTML('beforeend', html);
|
||||
}
|
||||
});
|
||||
|
||||
team2Players.forEach(function(pid) {
|
||||
var cb = document.querySelector('input[name="team2_player_ids"][value="' + pid + '"]');
|
||||
if (cb) cb.checked = true;
|
||||
});
|
||||
|
||||
updateRandomizePreview();
|
||||
}
|
||||
|
||||
function handleTeamSelection(checkbox, teamSide) {
|
||||
var otherSide = teamSide === 1 ? 2 : 1;
|
||||
var otherCheckboxes = document.querySelectorAll('input[name="team' + otherSide + '_player_ids"]');
|
||||
var playerId = checkbox.value;
|
||||
|
||||
otherCheckboxes.forEach(function(other) {
|
||||
if (other.value === playerId) {
|
||||
other.checked = false;
|
||||
var teamDiv = document.getElementById('team2-selection');
|
||||
var playerName = playerDataById.player_data[pid];
|
||||
if (playerName) {
|
||||
var html = '<div class="player-item" data-player-id="' + pid + '" onclick="returnToPool(' + pid + ', event)">';
|
||||
html += playerName;
|
||||
html += '<span class="remove-btn">↺</span>';
|
||||
html += '</div>';
|
||||
teamDiv.insertAdjacentHTML('beforeend', html);
|
||||
}
|
||||
});
|
||||
|
||||
updateHiddenInputs();
|
||||
updateRandomizePreview();
|
||||
}
|
||||
|
||||
function updateDisponibilityIndicators() {
|
||||
if (!canViewDisponibilities) return;
|
||||
|
||||
var dateInput = document.getElementById('date').value;
|
||||
var startTimeInput = document.getElementById('start_time').value;
|
||||
|
||||
if (!dateInput || !startTimeInput) return;
|
||||
|
||||
var dateParts = dateInput.split('-');
|
||||
var dateObj = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]);
|
||||
var jsDay = dateObj.getDay();
|
||||
var dayOfWeek = jsDay === 0 ? 6 : jsDay - 1;
|
||||
|
||||
document.querySelectorAll('.disponibility-indicator').forEach(function(indicator) {
|
||||
var playerId = indicator.getAttribute('data-player-id');
|
||||
var playerData = allDisponibilities[playerId];
|
||||
|
||||
if (playerData && playerData.disponibilities) {
|
||||
var playerSlots = getSlotsForPlayer(parseInt(playerId), dayOfWeek);
|
||||
var isAvailable = selectedSlots.some(function(slot) {
|
||||
return playerSlots.includes(slot);
|
||||
});
|
||||
|
||||
indicator.style.display = 'inline-block';
|
||||
indicator.style.width = '12px';
|
||||
indicator.style.height = '12px';
|
||||
indicator.style.borderRadius = '50%';
|
||||
indicator.style.marginLeft = '8px';
|
||||
indicator.style.backgroundColor = isAvailable ? '#10b981' : '#9ca3af';
|
||||
} else {
|
||||
indicator.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,250 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block title %}One on One - TryoutPro{% endblock %}
|
||||
{% block page_title %}One on One{% endblock %}
|
||||
{% block breadcrumb %}<span class="breadcrumb">Home / One on One</span>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="dashboard-grid">
|
||||
<!-- Team Notes Section -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-users"></i> Team Notes</h3>
|
||||
{% if org_team %}
|
||||
<span class="badge badge-esport">{{ org_team.name }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if team_notes %}
|
||||
{% for note in team_notes %}
|
||||
<div class="detail-grid mt-4">
|
||||
<div class="detail-item full-width">
|
||||
<span class="detail-label"><i class="fas fa-user"></i> Coach: {{ note.coach.full_name if note.coach else 'Unknown Coach' }}</span>
|
||||
<span class="detail-value">{{ note.content | nl2br }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted small mt-2">Updated: {{ note.updated_at.strftime('%B %d, %Y at %I:%M %p') }}</p>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p class="text-muted">No team notes have been added yet. Your coach will post improvement suggestions here.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Personal Notes Section -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-user"></i> Personal Notes</h3>
|
||||
{% if coach %}
|
||||
<span class="badge badge-coach">From: {{ coach.full_name }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if personal_notes %}
|
||||
{% for note in personal_notes %}
|
||||
<div class="detail-grid mt-4">
|
||||
<div class="detail-item full-width">
|
||||
<span class="detail-label"><i class="fas fa-sticky-note"></i> Note from {{ note.coach.full_name if note.coach else 'Unknown Coach' }}</span>
|
||||
<span class="detail-value">{{ note.content | nl2br }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted small mt-2">Added: {{ note.created_at.strftime('%B %d, %Y at %I:%M %p') }}</p>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p class="text-muted">No personal notes have been added yet. Your coach may provide individual feedback here.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- One on One Request Section -->
|
||||
<div class="card" style="grid-column: 1 / -1;">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-calendar-check"></i> Request One on One Session</h3>
|
||||
{% if coach %}
|
||||
<span class="badge badge-info">Coach: {{ coach.full_name }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if coach %}
|
||||
<form method="POST" action="{{ url_for('users.one_on_one') }}" class="form" id="oneOnOneForm">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="date">Select Date</label>
|
||||
<select name="date" id="date" class="form-select" onchange="updateTimeSlots()" required>
|
||||
{% for d in dates %}
|
||||
<option value="{{ d.value }}" data-day="{{ d.day_of_week }}">{{ d.display }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="start_time">Start Time</label>
|
||||
<select name="start_time" id="start_time" class="form-select" onchange="updateEndTimeOptions()" required>
|
||||
<option value="">-- Select Date First --</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="end_time">End Time</label>
|
||||
<select name="end_time" id="end_time" class="form-select" required>
|
||||
<option value="">-- Select Start Time First --</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="points">Discussion Points <span class="text-muted">(What would you like to discuss?)</span></label>
|
||||
<textarea name="points" id="points" class="form-textarea" placeholder="Enter topics you'd like to cover in your One on One session..." rows="4"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-paper-plane"></i> Send Request
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<p class="text-muted">You need to be assigned to a team with a coach to request a One on One session.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if coach %}
|
||||
<!-- Hidden data for JavaScript -->
|
||||
<script id="coach-availability-data" type="application/json">
|
||||
{{ coach_availability | tojson }}
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Time slots from 8:00 AM to 10:00 PM
|
||||
const TIME_SLOTS = [];
|
||||
for (let h = 8; h <= 22; h++) {
|
||||
for (let m = 0; m < 60; m += 30) {
|
||||
const displayHour = h > 12 ? h - 12 : h;
|
||||
const displayAmpm = h >= 12 ? 'PM' : 'AM';
|
||||
const timeStr = (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m;
|
||||
const displayTime = displayHour + ':' + (m < 10 ? '0' : '') + m + ' ' + displayAmpm;
|
||||
TIME_SLOTS.push({ time: timeStr, display: displayTime });
|
||||
}
|
||||
}
|
||||
|
||||
// Coach availability data
|
||||
let coachAvailability = [];
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadCoachAvailability();
|
||||
updateTimeSlots();
|
||||
});
|
||||
|
||||
function loadCoachAvailability() {
|
||||
const dataEl = document.getElementById('coach-availability-data');
|
||||
if (!dataEl) return;
|
||||
|
||||
try {
|
||||
coachAvailability = JSON.parse(dataEl.textContent);
|
||||
} catch (e) {
|
||||
coachAvailability = [];
|
||||
}
|
||||
}
|
||||
|
||||
function updateTimeSlots() {
|
||||
const dateSelect = document.getElementById('date');
|
||||
const startTimeSelect = document.getElementById('start_time');
|
||||
|
||||
const selectedOption = dateSelect.options[dateSelect.selectedIndex];
|
||||
const dayOfWeek = parseInt(selectedOption.getAttribute('data-day'));
|
||||
|
||||
// Get available time slots for this day
|
||||
const dayAvailability = coachAvailability.filter(av => av.day_of_week === dayOfWeek);
|
||||
|
||||
// Build available slots - collect all available minutes then sort
|
||||
const availableSlots = [];
|
||||
const availableMinutes = [];
|
||||
dayAvailability.forEach(av => {
|
||||
const startMinutes = av.start_time.split(':').reduce((acc, val, i) => acc + parseInt(val) * (i === 0 ? 60 : 1), 0);
|
||||
const endMinutes = av.end_time.split(':').reduce((acc, val, i) => acc + parseInt(val) * (i === 0 ? 60 : 1), 0);
|
||||
|
||||
// Add 30-minute slots
|
||||
for (let m = startMinutes; m < endMinutes; m += 30) {
|
||||
availableMinutes.push(m);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort minutes and convert to time strings
|
||||
availableMinutes.sort((a, b) => a - b);
|
||||
availableMinutes.forEach(m => {
|
||||
const hour = Math.floor(m / 60);
|
||||
const minute = m % 60;
|
||||
const timeStr = (hour < 10 ? '0' : '') + hour + ':' + (minute < 10 ? '0' : '') + minute;
|
||||
availableSlots.push(timeStr);
|
||||
});
|
||||
|
||||
// Update start time options (sorted)
|
||||
startTimeSelect.innerHTML = '<option value="">-- Select Start Time --</option>';
|
||||
availableSlots.forEach(slot => {
|
||||
const slotData = TIME_SLOTS.find(s => s.time === slot);
|
||||
if (slotData) {
|
||||
const option = document.createElement('option');
|
||||
option.value = slotData.time;
|
||||
option.textContent = slotData.display;
|
||||
startTimeSelect.appendChild(option);
|
||||
}
|
||||
});
|
||||
|
||||
// Reset end time options
|
||||
updateEndTimeOptions();
|
||||
}
|
||||
|
||||
function updateEndTimeOptions() {
|
||||
const dateSelect = document.getElementById('date');
|
||||
const startTimeSelect = document.getElementById('start_time');
|
||||
const endTimeSelect = document.getElementById('end_time');
|
||||
|
||||
const selectedOption = dateSelect.options[dateSelect.selectedIndex];
|
||||
const dayOfWeek = parseInt(selectedOption.getAttribute('data-day'));
|
||||
const selectedStart = startTimeSelect.value;
|
||||
|
||||
if (!selectedStart) {
|
||||
endTimeSelect.innerHTML = '<option value="">-- Select Start Time First --</option>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Get available minutes for this day
|
||||
const dayAvailability = coachAvailability.filter(av => av.day_of_week === dayOfWeek);
|
||||
const availableMinutes = [];
|
||||
dayAvailability.forEach(av => {
|
||||
const startMinutes = av.start_time.split(':').reduce((acc, val, i) => acc + parseInt(val) * (i === 0 ? 60 : 1), 0);
|
||||
const endMinutes = av.end_time.split(':').reduce((acc, val, i) => acc + parseInt(val) * (i === 0 ? 60 : 1), 0);
|
||||
for (let m = startMinutes; m < endMinutes; m += 30) {
|
||||
availableMinutes.push(m);
|
||||
}
|
||||
});
|
||||
|
||||
// Convert selected start to minutes
|
||||
const startMinutesVal = selectedStart.split(':').reduce((acc, val, i) => acc + parseInt(val) * (i === 0 ? 60 : 1), 0);
|
||||
|
||||
// Filter end times that are after start time
|
||||
const validEndTimes = availableMinutes.filter(m => m > startMinutesVal);
|
||||
validEndTimes.sort((a, b) => a - b);
|
||||
|
||||
// Update end time options
|
||||
endTimeSelect.innerHTML = '<option value="">-- Select End Time --</option>';
|
||||
validEndTimes.forEach(m => {
|
||||
const hour = Math.floor(m / 60);
|
||||
const minute = m % 60;
|
||||
const timeStr = (hour < 10 ? '0' : '') + hour + ':' + (minute < 10 ? '0' : '') + minute;
|
||||
const slotData = TIME_SLOTS.find(s => s.time === timeStr);
|
||||
if (slotData) {
|
||||
const option = document.createElement('option');
|
||||
option.value = slotData.time;
|
||||
option.textContent = slotData.display;
|
||||
endTimeSelect.appendChild(option);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,64 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block title %}Personal Notes - TryoutPro{% endblock %}
|
||||
{% block page_title %}Personal Notes{% endblock %}
|
||||
{% block breadcrumb %}<span class="breadcrumb">Home / Personal Notes</span>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-user-friends"></i> Add Personal Note</h3>
|
||||
{% if org_team %}
|
||||
<span class="badge badge-esport">{{ org_team.name }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('users.manage_personal_notes') }}" class="form">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="player_id">Select Player</label>
|
||||
<select name="player_id" id="player_id" class="form-select" required>
|
||||
<option value="">-- Select a Player --</option>
|
||||
{% for player in players %}
|
||||
<option value="{{ player.id }}">{{ player.full_name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="content">Note Content</label>
|
||||
<textarea name="content" id="content" class="form-textarea" rows="4" placeholder="Enter personal feedback or coaching tips for this player..." required></textarea>
|
||||
<p class="form-text">These notes will only be visible to the selected player.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save"></i> Add Note
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if personal_notes %}
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-sticky-note"></i> Recent Notes</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="detail-grid">
|
||||
{% for note in personal_notes %}
|
||||
<div class="detail-item full-width mb-4">
|
||||
<span class="detail-label">
|
||||
<i class="fas fa-user"></i> {{ note.player.full_name if note.player else 'Unknown Player' }} -
|
||||
<span class="text-muted">{{ note.created_at.strftime('%B %d, %Y') if note.created_at else 'Unknown date' }}</span>
|
||||
</span>
|
||||
<span class="detail-value">{{ note.content | nl2br if note.content else '' }}</span>
|
||||
<span class="detail-label text-muted small">From: {{ note.coach.full_name if note.coach else 'Unknown Coach' }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,85 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block title %}My Notes - TryoutPro{% endblock %}
|
||||
{% block page_title %}My Notes{% endblock %}
|
||||
{% block breadcrumb %}<span class="breadcrumb">Home / One on One / My Notes</span>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="dashboard-grid">
|
||||
<!-- Personal Notes Section -->
|
||||
<div class="card" style="grid-column: 1 / -1;">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-user"></i> Personal Notes</h3>
|
||||
{% if org_team %}
|
||||
<span class="badge badge-coach">Team: {{ org_team.name }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if personal_notes %}
|
||||
{% for note in personal_notes %}
|
||||
<div class="detail-grid mt-4">
|
||||
<div class="detail-item full-width">
|
||||
<span class="detail-label">
|
||||
<i class="fas fa-sticky-note"></i> Note from {{ note.coach.full_name if note.coach else 'Unknown Coach' }}
|
||||
</span>
|
||||
<span class="detail-value">{{ note.content | nl2br }}</span>
|
||||
<div class="mt-2">
|
||||
{% if note.match_id and note.match %}
|
||||
<span class="badge badge-info" title="From match">
|
||||
<i class="fas fa-futbol"></i> Match: {{ note.match.title }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if note.team_id and note.team %}
|
||||
<span class="badge badge-warning" title="From team">
|
||||
<i class="fas fa-users"></i> Team: {{ note.team.name }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if note.tryout_id and note.tryout %}
|
||||
<span class="badge badge-success" title="From tryout">
|
||||
<i class="fas fa-calendar-alt"></i> Tryout: {{ note.tryout.title }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted small mt-2">Added: {{ note.created_at.strftime('%B %d, %Y at %I:%M %p') }}</p>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p class="text-muted">No personal notes have been added yet. Your coach may provide individual feedback here.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Team Notes Section -->
|
||||
<div class="card" style="grid-column: 1 / -1;">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-users"></i> Team Notes</h3>
|
||||
{% if org_team %}
|
||||
<span class="badge badge-esport">{{ org_team.name }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if team_notes %}
|
||||
{% for note in team_notes %}
|
||||
<div class="detail-grid mt-4">
|
||||
<div class="detail-item full-width">
|
||||
<span class="detail-label"><i class="fas fa-user"></i> Coach: {{ note.coach.full_name if note.coach else 'Unknown Coach' }}</span>
|
||||
<span class="detail-value">{{ note.content | nl2br }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted small mt-2">Updated: {{ note.updated_at.strftime('%B %d, %Y at %I:%M %p') }}</p>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p class="text-muted">No team notes have been added yet. Your coach will post improvement suggestions here.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-body text-center">
|
||||
<a href="{{ url_for('users.one_on_one') }}" class="btn btn-primary">
|
||||
<i class="fas fa-calendar-check"></i> Request One on One Session
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,58 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% block title %}Team Notes - TryoutPro{% endblock %}
|
||||
{% block page_title %}Team Notes{% endblock %}
|
||||
{% block breadcrumb %}<span class="breadcrumb">Home / Team Notes</span>{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-users"></i> Team Improvement Notes</h3>
|
||||
{% if org_team %}
|
||||
<span class="badge badge-esport">{{ org_team.name }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('users.manage_team_notes') }}" class="form">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="content">Team Notes Content</label>
|
||||
<textarea name="content" id="content" class="form-textarea" rows="6" placeholder="Enter improvement suggestions and notes for your team...">{{ team_notes[0].content if team_notes else '' }}</textarea>
|
||||
<p class="form-text">These notes will be visible to all players on your team.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-plus"></i> Add Team Notes
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if team_notes %}
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-history"></i> Note History</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Last Updated</th>
|
||||
<th>Content Preview</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for note in team_notes %}
|
||||
<tr>
|
||||
<td>{{ note.updated_at.strftime('%B %d, %Y at %I:%M %p') if note.updated_at else 'Unknown date' }}</td>
|
||||
<td>{{ note.content[:100] if note.content else '' }}{% if note.content and note.content|length > 100 %}...{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -6,7 +6,7 @@
|
||||
{% block content %}
|
||||
<div class="tryout-detail">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<div class="card-header">
|
||||
<h3>Tryout Details</h3>
|
||||
<div class="card-actions">
|
||||
{% if can_edit %}
|
||||
@@ -32,7 +32,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="detail-grid">
|
||||
<div class="detail-grid">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Game</span>
|
||||
<span class="detail-value">{{ tryout.game }}</span>
|
||||
@@ -249,9 +249,14 @@
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-futbol"></i> Schedule</h3>
|
||||
{% if can_edit %}
|
||||
<a href="{{ url_for('matches.create_match', tryout_id=tryout.id) }}" class="btn btn-sm btn-success">
|
||||
<i class="fas fa-plus"></i> Schedule Match
|
||||
</a>
|
||||
<div class="card-actions">
|
||||
<a href="{{ url_for('matches.create_match', tryout_id=tryout.id) }}" class="btn btn-sm btn-success">
|
||||
<i class="fas fa-plus"></i> Schedule Match
|
||||
</a>
|
||||
<a href="{{ url_for('users.add_note_from_tryout', tryout_id=tryout.id) }}" class="btn btn-sm btn-primary">
|
||||
<i class="fas fa-sticky-note"></i> Add Note
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@@ -266,7 +271,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if matches %}
|
||||
{% if matches %}
|
||||
<!-- Matches Table -->
|
||||
<div class="table-container mt-3">
|
||||
<table class="table">
|
||||
@@ -301,59 +306,59 @@
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ m.date.strftime('%m/%d/%Y') }}</td>
|
||||
<td>
|
||||
{% if m.match_type == 'team_vs_team' %}
|
||||
<div class="match-teams">
|
||||
<div class="match-team">
|
||||
<span class="team-name">{{ m.team1.name if m.team1 else 'Team 1' }}</span>
|
||||
{% if participants.team1_players %}
|
||||
<ul class="team-players-list">
|
||||
{% for pl in participants.team1_players %}
|
||||
<li>{{ pl.name }}{% if pl.position %} <span class="position-tag">{{ pl.position }}</span>{% endif %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="match-vs">vs</div>
|
||||
<div class="match-team">
|
||||
<span class="team-name">{{ m.team2.name if m.team2 else 'Team 2' }}</span>
|
||||
{% if participants.team2_players %}
|
||||
<ul class="team-players-list">
|
||||
{% for pl in participants.team2_players %}
|
||||
<li>{{ pl.name }}{% if pl.position %} <span class="position-tag">{{ pl.position }}</span>{% endif %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% elif m.match_type == 'player_vs_player' %}
|
||||
<div class="match-teams">
|
||||
<div class="match-team">
|
||||
<span class="team-name">Team 1</span>
|
||||
{% if participants.team1_players %}
|
||||
<ul class="team-players-list">
|
||||
{% for pl in participants.team1_players %}
|
||||
<li>{{ pl.name }}{% if pl.position %} <span class="position-tag">{{ pl.position }}</span>{% endif %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="match-vs">vs</div>
|
||||
<div class="match-team">
|
||||
<span class="team-name">Team 2</span>
|
||||
{% if participants.team2_players %}
|
||||
<ul class="team-players-list">
|
||||
{% for pl in participants.team2_players %}
|
||||
<li>{{ pl.name }}{% if pl.position %} <span class="position-tag">{{ pl.position }}</span>{% endif %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ participants | join(', ') }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if m.match_type == 'team_vs_team' %}
|
||||
<div class="match-teams">
|
||||
<div class="match-team">
|
||||
<span class="team-name">{{ m.team1.name if m.team1 else 'Team 1' }}</span>
|
||||
{% if participants.team1_players %}
|
||||
<ul class="team-players-list">
|
||||
{% for pl in participants.team1_players %}
|
||||
<li>{{ pl.name }}{% if pl.position %} <span class="position-tag">{{ pl.position }}</span>{% endif %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="match-vs">vs</div>
|
||||
<div class="match-team">
|
||||
<span class="team-name">{{ m.team2.name if m.team2 else 'Team 2' }}</span>
|
||||
{% if participants.team2_players %}
|
||||
<ul class="team-players-list">
|
||||
{% for pl in participants.team2_players %}
|
||||
<li>{{ pl.name }}{% if pl.position %} <span class="position-tag">{{ pl.position }}</span>{% endif %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% elif m.match_type == 'player_vs_player' %}
|
||||
<div class="match-teams">
|
||||
<div class="match-team">
|
||||
<span class="team-name">Team 1</span>
|
||||
{% if participants.team1_players %}
|
||||
<ul class="team-players-list">
|
||||
{% for pl in participants.team1_players %}
|
||||
<li>{{ pl.name }}{% if pl.position %} <span class="position-tag">{{ pl.position }}</span>{% endif %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="match-vs">vs</div>
|
||||
<div class="match-team">
|
||||
<span class="team-name">Team 2</span>
|
||||
{% if participants.team2_players %}
|
||||
<ul class="team-players-list">
|
||||
{% for pl in participants.team2_players %}
|
||||
<li>{{ pl.name }}{% if pl.position %} <span class="position-tag">{{ pl.position }}</span>{% endif %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ participants | join(', ') }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if m.start_time and m.end_time %}
|
||||
{{ m.start_time.strftime('%H:%M') }} - {{ m.end_time.strftime('%H:%M') }}
|
||||
@@ -369,6 +374,9 @@
|
||||
<a href="{{ url_for('matches.edit_match', match_id=m.id) }}" class="btn btn-sm btn-outline">
|
||||
<i class="fas fa-edit"></i> Edit
|
||||
</a>
|
||||
<a href="{{ url_for('users.add_note_from_match', match_id=m.id) }}" class="btn btn-sm btn-primary" title="Add Note for this Match">
|
||||
<i class="fas fa-sticky-note"></i> Note
|
||||
</a>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
@@ -433,8 +441,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script>
|
||||
function showCreateTeam() { document.getElementById('createTeamForm').classList.remove('hidden'); }
|
||||
function hideCreateTeam() { document.getElementById('createTeamForm').classList.add('hidden'); }
|
||||
|
||||
@@ -452,7 +461,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
events: '/matches/api/events/{{ tryout.id }}',
|
||||
height: '300px',
|
||||
eventClick: function(info) {
|
||||
// Could show match details or edit link
|
||||
if (info.event.extendedProps.match_id) {
|
||||
window.location.href = '/matches/' + info.event.extendedProps.match_id + '/edit';
|
||||
}
|
||||
@@ -462,4 +470,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user