Feat: Saison-Report für Head Coaches
Implementiert einen druckbaren Saison-Report für Head Coaches, der eine Übersicht über die Spielerteilnahme an allen Spielen einer ausgewählten Saison bietet. Wesentliche Änderungen: - **Neue URL und View (`season_report` in `team_stats/views.py`):** Empfängt `team_id` und `season`, holt Spieler, Spiele und deren Teilnahmestatus und bereitet die Daten auf. Enthält eine Berechtigungsprüfung für den Head Coach. - **Neue Vorlage (`team_stats/templates/team_stats/season_report.html`):** Zeigt eine Tabelle mit Spielen als Zeilen und Spielern als Spalten an. Teilnahmestatus wird mit Symbolen (✔, ✖, ?) dargestellt. Enthält druckspezifisches CSS, um die Lesbarkeit und das Seitenlayout für DIN A4 (Querformat) zu optimieren, inklusive vertikal gedrehter Spielernamen in der Kopfzeile zur Platzersparnis. - **Integration (`team_statistics.html`):** Ein "Saison-Report generieren"-Button wurde zur Team-Statistikseite hinzugefügt, der den Report für die aktuell ausgewählte Saison öffnet.
This commit is contained in:
parent
d45fc54280
commit
5cc9b387b9
107
team_stats/templates/team_stats/season_report.html
Normal file
107
team_stats/templates/team_stats/season_report.html
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Season Report for {{ team.name }} - {{ season }}</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 4px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
thead th {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
.player-header {
|
||||||
|
height: 150px;
|
||||||
|
white-space: nowrap;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.player-header > div {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
transform-origin: 0 100%;
|
||||||
|
width: 150px; /* Should match height */
|
||||||
|
}
|
||||||
|
.status-attending {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
.status-rejected {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
.status-maybe {
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
.print-button {
|
||||||
|
margin: 1rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
@page {
|
||||||
|
size: A4 landscape;
|
||||||
|
margin: 1cm;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
.print-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<button onclick="window.print();" class="print-button">Print Report</button>
|
||||||
|
|
||||||
|
<h2>Season Report</h2>
|
||||||
|
<p><strong>Team:</strong> {{ team.name }}</p>
|
||||||
|
<p><strong>Season:</strong> {{ season }}</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Opponent</th>
|
||||||
|
{% for player in players %}
|
||||||
|
<th class="player-header"><div>{{ player.get_full_name }}</div></th>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for row in report_data %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ row.game.start_time|date:"d.m.Y" }}</td>
|
||||||
|
<td>{{ row.game.opponent }}</td>
|
||||||
|
{% for status in row.statuses %}
|
||||||
|
<td class="status-{{ status }}">
|
||||||
|
{% if status == 'attending' %}
|
||||||
|
✔
|
||||||
|
{% elif status == 'rejected' %}
|
||||||
|
✖
|
||||||
|
{% else %}
|
||||||
|
?
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="{{ players|length|add:2 }}">No games found for this season.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -22,6 +22,11 @@
|
|||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<button type="submit" class="btn btn-primary">Filter</button>
|
<button type="submit" class="btn btn-primary">Filter</button>
|
||||||
</div>
|
</div>
|
||||||
|
{% if selected_season %}
|
||||||
|
<div class="col-md-3">
|
||||||
|
<a href="{% url 'team_stats:season_report' team_id=team.id season=selected_season %}" class="btn btn-secondary" target="_blank">Generate Season Report</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -5,4 +5,5 @@ app_name = 'team_stats'
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('team/<int:team_id>/', views.team_statistics, name='team_statistics'),
|
path('team/<int:team_id>/', views.team_statistics, name='team_statistics'),
|
||||||
|
path('team/<int:team_id>/report/<str:season>/', views.season_report, name='season_report'),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -147,3 +147,50 @@ def team_statistics(request, team_id):
|
|||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'team_stats/team_statistics.html', context)
|
return render(request, 'team_stats/team_statistics.html', context)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def season_report(request, team_id, season):
|
||||||
|
team = get_object_or_404(Team, pk=team_id)
|
||||||
|
|
||||||
|
# Security check: only head coach can view
|
||||||
|
if request.user != team.head_coach:
|
||||||
|
return HttpResponseForbidden("You are not authorized to view this report.")
|
||||||
|
|
||||||
|
# 1. Get all players for the team, ordered
|
||||||
|
players = team.players.all().order_by('last_name', 'first_name')
|
||||||
|
player_ids = [p.id for p in players]
|
||||||
|
|
||||||
|
# 2. Get all games for the team and season
|
||||||
|
games = Game.objects.filter(team=team, season=season).order_by('start_time')
|
||||||
|
game_ids = [g.id for g in games]
|
||||||
|
|
||||||
|
# 3. Get all relevant participation data in one query
|
||||||
|
participations = EventParticipation.objects.filter(
|
||||||
|
event_id__in=game_ids,
|
||||||
|
user_id__in=player_ids
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4. Create a fast lookup map
|
||||||
|
participation_map = {
|
||||||
|
(p.event_id, p.user_id): p.status for p in participations
|
||||||
|
}
|
||||||
|
|
||||||
|
# 5. Build the final data structure for the template
|
||||||
|
report_data = []
|
||||||
|
for game in games:
|
||||||
|
statuses = []
|
||||||
|
for player in players:
|
||||||
|
status = participation_map.get((game.id, player.id), 'maybe')
|
||||||
|
statuses.append(status)
|
||||||
|
report_data.append({
|
||||||
|
'game': game,
|
||||||
|
'statuses': statuses
|
||||||
|
})
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'team': team,
|
||||||
|
'season': season,
|
||||||
|
'players': players,
|
||||||
|
'report_data': report_data,
|
||||||
|
}
|
||||||
|
return render(request, 'team_stats/season_report.html', context)
|
||||||
Loading…
x
Reference in New Issue
Block a user