Add billing-ending banner, events CRUD, RBIHF import, public API, team photos, and sponsors

A large batch of club-management features built up over one session:

- Club dashboard banner warning admins 1 month before billing ends
- Full Events/EventSeries CRUD (recurrence builder, occurrence lifecycle,
  per-team permissions), with match->game rename and game-specific fields
  (score, competition, live status, external game ID)
- Django-admin competition dropdown, gated per-club by feature flag
- Auto-import of RBIHF fixtures (scrape -> diff -> preview -> confirm),
  with location/opponent dropdowns suggested from existing club data
- Feature-flag-gated Shop/Forms nav sections, reusing the same flag
  machinery for the RBIHF import button
- Team roster now scoped to members active this season or next, sorted and
  grouped by position
- Club sport type (ice hockey / other), shown in the control panel's club
  subtitle
- Per-season team photo upload from the team page
- New public read-only API (Django Ninja) at /api/v1/: news, team rosters,
  upcoming/live/per-team games, and sponsors -- auto-documented via Swagger
  UI, CORS-enabled for a club's own external website
- Club sponsors: admin-only CRUD (logo, URL, active date window) plus a
  date-windowed, optionally randomized API endpoint
- Assorted fixes: NullBooleanField dropdown rendering, cross-club event
  validation timing, searchable-select chip placement, btn-neutral ->
  default button style sweep, calendar-month chart windows

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R1gj3J1QPfP38XWpnpbFpy
This commit is contained in:
2026-08-06 17:36:04 +02:00
parent 6ad0d6658c
commit 98b8002a04
101 changed files with 6165 additions and 135 deletions

View File

@@ -45,11 +45,11 @@ leaves it unset, since staying on the family page is already the right place the
{% if is_club_admin %}
<div class="flex justify-end gap-1">
{% if person.grant_login_form %}
<button class="btn btn-sm btn-outline btn-neutral" type="button" onclick="document.getElementById('grant_login_modal_{{ group.family.pk }}_{{ person.pk }}').showModal()" aria-label="{% trans 'Grant login' %}">
<button class="btn btn-outline btn-sm" type="button" onclick="document.getElementById('grant_login_modal_{{ group.family.pk }}_{{ person.pk }}').showModal()" aria-label="{% trans 'Grant login' %}">
{% lucide "key-round" size=14 %} {% trans "Grant login" %}
</button>
{% endif %}
<a class="btn btn-sm btn-outline btn-neutral" href="{% url 'management:member_detail' person.pk %}" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</a>
<a class="btn btn-outline btn-sm" href="{% url 'management:member_detail' person.pk %}" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</a>
<button class="btn btn-sm btn-outline btn-error" type="button" onclick="document.getElementById('remove_family_modal_{{ group.family.pk }}_{{ person.pk }}').showModal()" aria-label="{% trans 'Remove from family' %}">
{% lucide "user-x" size=14 %} {% trans "Remove" %}
</button>

View File

@@ -30,19 +30,25 @@
<li class="menu-title">{% trans "Calendar" %}</li>
<li><a class="{% if nav == 'event_list' %}menu-active{% endif %}" href="{% url 'management:event_list' %}">{% lucide "calendar" size=16 %} {% trans "Events" %}</a></li>
<li><a class="{% if nav == 'event_series_list' %}menu-active{% endif %}" href="{% url 'management:event_series_list' %}">{% lucide "repeat" size=16 %} {% trans "Event series" %}</a></li>
{% if has_management_position %}
<li><a class="{% if nav == 'location_list' %}menu-active{% endif %}" href="{% url 'management:location_list' %}">{% lucide "map-pin" size=16 %} {% trans "Locations" %}</a></li>
<li><a class="{% if nav == 'opponent_list' %}menu-active{% endif %}" href="{% url 'management:opponent_list' %}">{% lucide "swords" size=16 %} {% trans "Opponents" %}</a></li>
{% endif %}
{% if is_club_admin %}
<li class="menu-title">{% trans "Sponsors" %}</li>
<li><a class="{% if nav == 'sponsor_list' %}menu-active{% endif %}" href="{% url 'management:sponsor_list' %}">{% lucide "handshake" size=16 %} {% trans "Sponsors" %}</a></li>
{% endif %}
{% if is_club_admin and shop_enabled %}
<li class="menu-title">{% trans "Shop" %}</li>
<li><a class="{% if nav == 'product_list' %}menu-active{% endif %}" href="{% url 'management:product_list' %}">{% lucide "package" size=16 %} {% trans "Products" %}</a></li>
<li><a class="{% if nav == 'order_list' %}menu-active{% endif %}" href="{% url 'management:order_list' %}">{% lucide "shopping-cart" size=16 %} {% trans "Orders" %}</a></li>
<li><a class="{% if nav == 'discount_list' %}menu-active{% endif %}" href="{% url 'management:discount_list' %}">{% lucide "percent" size=16 %} {% trans "Discounts" %}</a></li>
<li><a class="{% if nav == 'invoice_list' %}menu-active{% endif %}" href="{% url 'management:invoice_list' %}">{% lucide "receipt" size=16 %} {% trans "Invoices" %}</a></li>
{% endif %}
{% if is_club_admin and forms_enabled %}
<li class="menu-title">{% trans "Forms" %}</li>
<li><a class="{% if nav == 'form_list' %}menu-active{% endif %}" href="{% url 'management:form_list' %}">{% lucide "clipboard-list" size=16 %} {% trans "Forms" %}</a></li>
{% endif %}

View File

@@ -0,0 +1,215 @@
{% extends "management/base.html" %}
{% load i18n lucide %}
{% block heading %}{{ event.title }}{% endblock heading %}
{% block subheading %}{{ event.get_kind_display }}{% endblock subheading %}
{% block actions %}
{% if can_manage %}
<a class="btn btn-outline gap-2" href="{% url 'management:event_update' event.pk %}">{% lucide "pencil" size=16 %} {% trans "Edit" %}</a>
{% if event.series_id and not event.detached %}
<form method="post" action="{% url 'management:event_detach' event.pk %}">
{% csrf_token %}
<button class="btn btn-outline gap-2" type="submit">{% lucide "unlink" size=16 %} {% trans "Detach from series" %}</button>
</form>
{% endif %}
<button class="btn btn-outline btn-error gap-2" type="button" onclick="document.getElementById('event_delete_modal').showModal()">
{% lucide "trash-2" size=16 %}
{% if event.series_id %}{% trans "Cancel occurrence" %}{% else %}{% trans "Delete" %}{% endif %}
</button>
{% endif %}
{% endblock actions %}
{% block panel %}
{% if event.series_id %}
<div class="alert alert-info mb-6">
{% lucide "repeat" size=20 %}
<span>
{% blocktrans with series=event.series %}Part of the recurring series “{{ series }}”.{% endblocktrans %}
<a class="link" href="{% url 'management:event_series_detail' event.series_id %}">{% trans "View series" %}</a>
{% if event.detached %}<span class="badge badge-sm badge-neutral ml-2">{% trans "Detached" %}</span>{% endif %}
</span>
</div>
{% endif %}
<div class="mb-6 grid gap-4 lg:grid-cols-2">
<div class="card bg-base-100 shadow">
<div class="card-body">
<h2 class="card-title text-base">{% lucide "info" size=18 %} {% trans "Details" %}</h2>
<dl class="divide-y divide-base-200">
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "Start" %}</dt>
<dd>{{ event.start|date:"j M Y H:i" }}</dd>
</div>
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "End" %}</dt>
<dd>{{ event.end|date:"j M Y H:i"|default:"—" }}</dd>
</div>
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "Gathering" %}</dt>
<dd>{{ event.gathering|date:"j M Y H:i"|default:"—" }}</dd>
</div>
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "Registration deadline" %}</dt>
<dd>{{ event.deadline|date:"j M Y H:i"|default:"—" }}</dd>
</div>
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "Location" %}</dt>
<dd>{{ event.location|default:"—" }}</dd>
</div>
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "Opponent" %}</dt>
<dd>{{ event.opponent|default:"—" }}</dd>
</div>
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "Teams" %}</dt>
<dd>{% for team in event.teams.all %}{{ team.name }}{% if not forloop.last %}, {% endif %}{% empty %}—{% endfor %}</dd>
</div>
</dl>
</div>
</div>
<div class="card bg-base-100 shadow">
<div class="card-body">
<div class="flex items-center justify-between">
<h2 class="card-title text-base">{% lucide "user-check" size=18 %} {% trans "RSVPs" %}</h2>
{% if has_attendance_rows %}
<button class="btn btn-outline btn-sm gap-2" type="button" onclick="document.getElementById('rsvp_modal').showModal()">
{% lucide "list" size=14 %} {% trans "View responses" %}
</button>
{% endif %}
</div>
<dl class="divide-y divide-base-200">
{% for group in attendance_groups %}
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{{ group.label }}</dt>
<dd class="font-semibold tabular-nums font-mono">{{ group.rows|length }}</dd>
</div>
{% endfor %}
</dl>
{% if not has_attendance_rows %}
<p class="text-sm opacity-60">{% trans "No one invited yet." %}</p>
{% endif %}
</div>
</div>
</div>
{% if event.kind == "game" %}
<div class="mb-6 card bg-base-100 shadow">
<div class="card-body">
<div class="flex items-center justify-between">
<h2 class="card-title text-base">
{% lucide "trophy" size=18 %} {% trans "Game" %}
{% if event.is_live %}<span class="badge badge-error gap-1 animate-pulse">{% lucide "circle" size=10 %} {% trans "Live" %}</span>{% endif %}
</h2>
{% if can_manage and event.competition %}
<form method="post" action="{% url 'management:event_fetch_game_info' event.pk %}">
{% csrf_token %}
<button class="btn btn-outline btn-sm gap-2" type="submit">{% lucide "refresh-cw" size=14 %} {% trans "Fetch new game info" %}</button>
</form>
{% endif %}
</div>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
<div>
<div class="text-sm opacity-70">{% trans "Score" %}</div>
<div class="text-sm font-semibold tabular-nums font-mono">
{% if event.score_for is not None and event.score_against is not None %}{{ event.score_for }} - {{ event.score_against }}{% else %}—{% endif %}
</div>
</div>
<div>
<div class="text-sm opacity-70">{% trans "Competition" %}</div>
<div class="text-sm">{{ event.competition|default:"—" }}</div>
</div>
<div>
<div class="text-sm opacity-70">{% trans "External game ID" %}</div>
<div class="font-mono text-sm">{{ event.external_game_id|default:"—" }}</div>
</div>
</div>
</div>
</div>
{% endif %}
{% endblock panel %}
{% block extra_body %}
{% if can_manage %}
<dialog id="event_delete_modal" class="modal">
<div class="modal-box">
<h3 class="text-lg font-bold">
{% if event.series_id %}{% trans "Cancel occurrence" %}{% else %}{% trans "Delete event" %}{% endif %}
</h3>
<form method="post" action="{% url 'management:event_delete' event.pk %}" id="event_delete_form">
{% csrf_token %}
{% if event.series_id %}
<p class="py-2 text-sm opacity-70">{% trans "Removes this occurrence from the schedule. It won't be regenerated." %}</p>
<label class="label cursor-pointer justify-start gap-2">
<input type="checkbox" name="keep_record" class="checkbox">
<span class="label-text">{% trans "Keep a record of it (marks it cancelled instead of deleting it)" %}</span>
</label>
{% else %}
<p class="py-2 text-sm opacity-70">{% trans "This cannot be undone." %}</p>
{% endif %}
</form>
<div class="modal-action">
<form method="dialog">
<button class="btn btn-outline gap-2">{% lucide "x" size=16 %} {% trans "Cancel" %}</button>
</form>
<button class="btn btn-error gap-2" type="submit" form="event_delete_form">{% lucide "trash-2" size=16 %} {% trans "Confirm" %}</button>
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
{% endif %}
{% if has_attendance_rows %}
<dialog id="rsvp_modal" class="modal">
<div class="modal-box max-w-2xl">
<h3 class="text-lg font-bold">{% trans "Who responded" %}</h3>
<div class="mt-2 space-y-2">
{% for group in attendance_groups %}
{% if group.rows %}
<details class="collapse collapse-arrow border border-base-300 bg-base-100">
<summary class="collapse-title text-sm font-medium">
<span class="badge badge-sm
{% if group.value == "present" %}badge-success
{% elif group.value == "absent" %}badge-error
{% elif group.value == "excused" %}badge-warning
{% elif group.value == "selected" %}badge-info
{% elif group.value == "maybe" %}badge-warning badge-outline
{% elif group.value == "not_selected" %}badge-neutral
{% else %}badge-outline{% endif %}">
{{ group.label }}
</span>
<span class="opacity-60">({{ group.rows|length }})</span>
</summary>
<div class="collapse-content">
<div class="overflow-x-auto">
<table class="table table-sm">
<tbody>
{% for row in group.rows %}
<tr>
<td>{{ row.member }}</td>
<td class="text-sm opacity-70">{{ row.note|default:"—" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</details>
{% endif %}
{% endfor %}
</div>
<div class="modal-action">
<form method="dialog">
<button class="btn btn-outline gap-2">{% lucide "x" size=16 %} {% trans "Close" %}</button>
</form>
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
{% endif %}
{% endblock extra_body %}

View File

@@ -0,0 +1,96 @@
{% extends "management/base.html" %}
{% load i18n lucide static ui %}
{% block heading %}{% if update_view %}{% blocktrans %}Edit {{ object }}{% endblocktrans %}{% else %}{% trans "New event" %}{% endif %}{% endblock heading %}
{% block panel %}
<div class="card w-full bg-base-100 shadow">
<div class="card-body">
<form method="post">
{% csrf_token %}
{% for error in form.non_field_errors %}
<div class="alert alert-error my-2">
<span>{{ error }}</span>
</div>
{% endfor %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{% form_field form.title %}
{% form_field form.kind %}
</div>
<div class="divider"></div>
<h3 class="text-lg font-semibold mb-3">{% trans "Audience" %}</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
{% form_field form.teams %}
{% form_field form.invited_members %}
{% form_field form.excluded_members %}
</div>
<div class="divider"></div>
<h3 class="text-lg font-semibold mb-3">{% trans "Where" %}</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{% form_field form.location %}
<div class="game-only">
{% form_field form.opponent %}
</div>
</div>
<div class="divider"></div>
<h3 class="text-lg font-semibold mb-3">{% trans "When" %}</h3>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
{% form_field form.start %}
{% form_field form.end %}
{% form_field form.gathering %}
{% form_field form.deadline %}
</div>
<div id="game-details-section" class="game-only">
<div class="divider"></div>
<h3 class="text-lg font-semibold mb-3">{% trans "Game" %}</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{% form_field form.competition %}
{% form_field form.external_game_id %}
</div>
{% if update_view %}
{% comment %}
Score/live status only exist to record once a game has
actually been created -- the add form leaves them out
entirely (see EventForm's editing=False path).
{% endcomment %}
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
{% form_field form.score_for %}
{% form_field form.score_against %}
{% form_field form.is_live %}
</div>
{% endif %}
</div>
<div class="card-actions justify-start pt-2 mt-2">
<a class="btn btn-outline gap-2" href="{% if update_view %}{% url "management:event_detail" object.pk %}{% else %}{% url "management:event_list" %}{% endif %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<button class="btn btn-primary gap-2" type="submit">{% lucide "save" size=16 %} {% trans "Save" %}</button>
</div>
</form>
</div>
</div>
{% endblock panel %}
{% block extra_body %}
<script src="{% static 'js/searchable-select.js' %}"></script>
<script>
(() => {
// Progressive enhancement only -- the fields stay in the form and post
// normally either way, this just hides them when they don't apply so a
// training/social/etc. event isn't cluttered with opponent/score/competition
// inputs. Anything gated on "only makes sense for a game" carries this class.
const kindField = document.getElementById("id_kind");
const gameOnlyElements = document.querySelectorAll(".game-only");
if (!kindField || !gameOnlyElements.length) return;
const sync = () => gameOnlyElements.forEach((element) => element.classList.toggle("hidden", kindField.value !== "game"));
kindField.addEventListener("change", sync);
sync();
})();
</script>
{% endblock extra_body %}

View File

@@ -0,0 +1,118 @@
{% extends "management/base.html" %}
{% load i18n lucide ui %}
{% block heading %}{% trans "Events" %}{% endblock heading %}
{% block actions %}
{% if is_club_admin and rbihf_enabled %}
<a class="btn btn-outline gap-2" href="{% url 'management:rbihf_import' %}">{% lucide "download" size=16 %} {% trans "Import from RBIHF" %}</a>
{% endif %}
{% if can_create %}
<a class="btn btn-outline gap-2" href="{% url 'management:event_series_create' %}">{% lucide "repeat" size=16 %} {% trans "New series" %}</a>
<a class="btn btn-outline gap-2" href="{% url 'management:event_create' %}">{% lucide "plus" size=16 %} {% trans "New event" %}</a>
{% endif %}
{% endblock actions %}
{% block panel %}
<form method="get" class="mb-4">
<div class="flex flex-row flex-wrap items-center gap-2">
<select name="season" class="select select-bordered">
{% for season in seasons %}
<option value="{{ season.pk }}" {% if season.pk == selected_season.pk %}selected{% endif %}>{% trans "Season" %} {{ season.start_date|date:"Y" }} - {{ season.end_date|date:"Y" }}</option>
{% endfor %}
</select>
<select name="kind" class="select select-bordered">
<option value="">{% trans "All kinds" %}</option>
{% for value, label in event_kinds %}
<option value="{{ value }}" {% if value == selected_kind %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
<label class="label cursor-pointer gap-2">
<input type="checkbox" name="show_past" value="1" class="checkbox" {% if show_past %}checked{% endif %} onchange="this.form.submit()">
<span class="label-text">{% trans "Show past events" %}</span>
</label>
<button class="btn btn-outline gap-2" type="submit">{% lucide "filter" size=16 %} {% trans "View" %}</button>
</div>
</form>
<div class="card bg-base-100 shadow">
<div class="card-body">
<div class="overflow-x-auto">
<table class="table">
<thead>
<tr>
<th>{% trans "Title" %}</th>
<th>{% trans "Kind" %}</th>
<th>{% trans "When" %}</th>
<th>{% trans "Teams" %}</th>
<th>{% trans "Location" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for event in events %}
<tr>
<td class="flex flex-row items-center gap-2">
<a class="link link-hover" href="{% url 'management:event_detail' event.pk %}">{{ event.title }}</a>
{% if event.series_id %}<span class="tooltip" data-tip="{% trans 'Part of a series' %}">{% lucide "repeat" size=12 %}</span>{% endif %}
</td>
<td>{{ event.get_kind_display }}</td>
<td>{{ event.start|date:"j M Y H:i" }}</td>
<td>{% for team in event.teams.all %}{{ team.short_name }}{% if not forloop.last %}, {% endif %}{% empty %}—{% endfor %}</td>
<td>{{ event.location.name|default:"—" }}</td>
<td class="text-right">
{% if event.can_manage %}
<div class="flex justify-end gap-1">
<a class="btn btn-outline btn-sm" href="{% url 'management:event_detail' event.pk %}" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</a>
<button class="btn btn-sm btn-outline btn-error" type="button" onclick="document.getElementById('{{ event.pk|dom_id:"event_delete_modal" }}').showModal()" aria-label="{% trans 'Delete' %}">{% lucide "trash-2" size=14 %} {% trans "Delete" %}</button>
</div>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center opacity-60">{% trans "No events." %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% comment %} Dialogs live outside the table: <tbody> may only contain <tr> elements. {% endcomment %}
{% for event in events %}
{% if event.can_manage %}
<dialog id="{{ event.pk|dom_id:"event_delete_modal" }}" class="modal">
<div class="modal-box">
<h3 class="text-lg font-bold">
{% if event.series_id %}{% trans "Cancel occurrence" %}{% else %}{% trans "Delete event" %}{% endif %}
</h3>
<form method="post" action="{% url 'management:event_delete' event.pk %}" id="{{ event.pk|dom_id:"event_delete_form" }}">
{% csrf_token %}
{% if event.series_id %}
{% blocktrans with name=event.title asvar delete_body %}Removes “{{ name }}” from the schedule. It won't be regenerated.{% endblocktrans %}
<p class="py-2 text-sm opacity-70">{{ delete_body }}</p>
<label class="label cursor-pointer justify-start gap-2">
<input type="checkbox" name="keep_record" class="checkbox">
<span class="label-text">{% trans "Keep a record of it (marks it cancelled instead of deleting it)" %}</span>
</label>
{% else %}
{% blocktrans with name=event.title asvar delete_body %}Delete “{{ name }}”? This cannot be undone.{% endblocktrans %}
<p class="py-2 text-sm opacity-70">{{ delete_body }}</p>
{% endif %}
</form>
<div class="modal-action">
<form method="dialog">
<button class="btn btn-outline gap-2">{% lucide "x" size=16 %} {% trans "Cancel" %}</button>
</form>
<button class="btn btn-error gap-2" type="submit" form="{{ event.pk|dom_id:"event_delete_form" }}">{% lucide "trash-2" size=16 %} {% trans "Confirm" %}</button>
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
{% endif %}
{% endfor %}
{% endblock panel %}

View File

@@ -0,0 +1,117 @@
{% extends "management/base.html" %}
{% load i18n lucide ui %}
{% block heading %}{{ series.title }}{% endblock heading %}
{% block subheading %}{{ series.get_kind_display }} · {{ recurrence_summary }}{% endblock subheading %}
{% block actions %}
{% if can_manage %}
<a class="btn btn-outline gap-2" href="{% url 'management:event_series_update' series.pk %}">{% lucide "pencil" size=16 %} {% trans "Edit" %}</a>
<form method="post" action="{% url 'management:event_series_generate' series.pk %}">
{% csrf_token %}
<button class="btn btn-outline gap-2" type="submit">{% lucide "refresh-cw" size=16 %} {% trans "Regenerate occurrences" %}</button>
</form>
<button class="btn btn-outline btn-warning gap-2" type="button" onclick="document.getElementById('series_stop_modal').showModal()">{% lucide "octagon-pause" size=16 %} {% trans "Stop repeating" %}</button>
<button class="btn btn-outline btn-error gap-2" type="button" onclick="document.getElementById('series_delete_modal').showModal()">{% lucide "trash-2" size=16 %} {% trans "Delete series" %}</button>
{% endif %}
{% endblock actions %}
{% block panel %}
<div class="mb-6 grid gap-4 lg:grid-cols-2">
<div class="card bg-base-100 shadow">
<div class="card-body">
<h2 class="card-title text-base">{% lucide "repeat" size=18 %} {% trans "Recurrence" %}</h2>
<dl class="divide-y divide-base-200">
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "Pattern" %}</dt>
<dd>{{ recurrence_summary }}</dd>
</div>
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "First occurrence" %}</dt>
<dd>{{ series.dtstart|date:"j M Y H:i" }}</dd>
</div>
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "Repeats until" %}</dt>
<dd>{{ series.until|date:"j M Y H:i"|default:"—" }}</dd>
</div>
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "Generated up to" %}</dt>
<dd>{{ series.generated_until|date:"j M Y"|default:"—" }}</dd>
</div>
</dl>
</div>
</div>
<div class="card bg-base-100 shadow">
<div class="card-body">
<h2 class="card-title text-base">{% lucide "info" size=18 %} {% trans "Template" %}</h2>
<dl class="divide-y divide-base-200">
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "Location" %}</dt>
<dd>{{ series.location|default:"—" }}</dd>
</div>
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "Opponent" %}</dt>
<dd>{{ series.opponent|default:"—" }}</dd>
</div>
<div class="flex items-center justify-between py-2">
<dt class="text-sm opacity-70">{% trans "Teams" %}</dt>
<dd>{% for team in series.teams.all %}{{ team.name }}{% if not forloop.last %}, {% endif %}{% empty %}—{% endfor %}</dd>
</div>
</dl>
</div>
</div>
</div>
<div class="card bg-base-100 shadow">
<div class="card-body">
<h2 class="card-title text-base">{% lucide "calendar" size=18 %} {% trans "Occurrences" %}</h2>
<div class="overflow-x-auto">
<table class="table">
<thead>
<tr>
<th>{% trans "When" %}</th>
<th>{% trans "Status" %}</th>
</tr>
</thead>
<tbody>
{% for occurrence in occurrences %}
<tr>
<td><a class="link link-hover" href="{% url 'management:event_detail' occurrence.pk %}">{{ occurrence.start|date:"j M Y H:i" }}</a></td>
<td>
{% if occurrence.cancelled %}
<span class="badge badge-outline gap-1">{% lucide "ban" size=12 %} {% trans "Cancelled" %}</span>
{% elif occurrence.detached %}
<span class="badge badge-neutral gap-1">{% lucide "unlink" size=12 %} {% trans "Detached" %}</span>
{% elif occurrence.is_past %}
<span class="badge badge-outline">{% trans "Past" %}</span>
{% else %}
<span class="badge badge-success">{% trans "Upcoming" %}</span>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="2" class="text-center opacity-60">{% trans "No occurrences generated yet." %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock panel %}
{% block extra_body %}
{% if can_manage %}
{% trans "Stop repeating" as stop_title %}
{% blocktrans with name=series.title asvar stop_body %}Stop “{{ name }}” from generating new occurrences? Existing ones (past and future) are left exactly as they are — this only affects what happens from here on.{% endblocktrans %}
{% url 'management:event_series_stop' series.pk as stop_url %}
{% include "controlpanel/_confirm_modal.html" with modal_id="series_stop_modal" title=stop_title body=stop_body action_url=stop_url submit_label=stop_title submit_icon="octagon-pause" %}
{% trans "Delete series" as delete_title %}
{% blocktrans with name=series.title asvar delete_body %}Delete “{{ name }}”? This also deletes every occurrence it has ever generated, past and future, and their attendance records. This cannot be undone — use “Stop repeating” instead if you just want it to stop.{% endblocktrans %}
{% url 'management:event_series_delete' series.pk as delete_url %}
{% include "controlpanel/_confirm_modal.html" with modal_id="series_delete_modal" title=delete_title body=delete_body action_url=delete_url submit_label=delete_title %}
{% endif %}
{% endblock extra_body %}

View File

@@ -0,0 +1,75 @@
{% extends "management/base.html" %}
{% load i18n lucide static ui %}
{% block heading %}{% if update_view %}{% blocktrans %}Edit {{ object }}{% endblocktrans %}{% else %}{% trans "New series" %}{% endif %}{% endblock heading %}
{% block panel %}
<div class="card w-full bg-base-100 shadow">
<div class="card-body">
<form method="post">
{% csrf_token %}
{% for error in form.non_field_errors %}
<div class="alert alert-error my-2">
<span>{{ error }}</span>
</div>
{% endfor %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{% form_field form.title %}
{% form_field form.kind %}
</div>
<div class="divider"></div>
<h3 class="text-lg font-semibold mb-3">{% trans "Repeats" %}</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
{% form_field form.frequency %}
{% form_field form.interval %}
{% form_field form.dtstart %}
{% form_field form.until %}
</div>
{% trans "Only used for a weekly pattern -- a monthly one repeats on the same day of the month as the first occurrence above." as weekdays_help %}
{% form_field form.weekdays help_text=weekdays_help %}
<details class="collapse collapse-arrow bg-base-200 mt-2">
<summary class="collapse-title text-sm font-medium">{% trans "Advanced: raw recurrence rule" %}</summary>
<div class="collapse-content">
{% form_field form.advanced_rrule %}
</div>
</details>
<div class="divider"></div>
<h3 class="text-lg font-semibold mb-3">{% trans "Audience" %}</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
{% form_field form.teams %}
{% form_field form.invited_members %}
{% form_field form.excluded_members %}
</div>
<div class="divider"></div>
<h3 class="text-lg font-semibold mb-3">{% trans "Where" %}</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{% form_field form.location %}
{% form_field form.opponent %}
</div>
<div class="divider"></div>
<h3 class="text-lg font-semibold mb-3">{% trans "Timing" %}</h3>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
{% form_field form.duration_hours %}
{% form_field form.duration_minutes %}
{% form_field form.gathering_minutes_before %}
{% form_field form.deadline_minutes_before %}
</div>
<div class="card-actions justify-start pt-2 mt-2">
<a class="btn btn-outline gap-2" href="{% if update_view %}{% url "management:event_series_detail" object.pk %}{% else %}{% url "management:event_list" %}{% endif %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<button class="btn btn-primary gap-2" type="submit">{% lucide "save" size=16 %} {% trans "Save" %}</button>
</div>
</form>
</div>
</div>
{% endblock panel %}
{% block extra_body %}
<script src="{% static 'js/searchable-select.js' %}"></script>
{% endblock extra_body %}

View File

@@ -7,8 +7,8 @@
{% if is_club_admin %}
{% trans "Add parent" as add_parent_label %}
{% trans "Add child" as add_child_label %}
<button class="btn btn-outline btn-neutral gap-2" type="button" onclick="document.getElementById('add_parent_modal').showModal()">{% lucide "user-plus" size=16 %} {{ add_parent_label }}</button>
<button class="btn btn-outline btn-neutral gap-2" type="button" onclick="document.getElementById('add_child_modal').showModal()">{% lucide "baby" size=16 %} {{ add_child_label }}</button>
<button class="btn btn-outline gap-2" type="button" onclick="document.getElementById('add_parent_modal').showModal()">{% lucide "user-plus" size=16 %} {{ add_parent_label }}</button>
<button class="btn btn-outline gap-2" type="button" onclick="document.getElementById('add_child_modal').showModal()">{% lucide "baby" size=16 %} {{ add_child_label }}</button>
{% endif %}
{% endblock actions %}

View File

@@ -34,7 +34,7 @@
</div>
<div class="card-actions justify-start pt-2 mt-2">
<a class="btn btn-outline btn-neutral gap-2" href="{% url "management:member_list" %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<a class="btn btn-outline gap-2" href="{% url "management:member_list" %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<button class="btn btn-primary gap-2" type="submit">{% lucide "save" size=16 %} {% trans "Save" %}</button>
</div>
</form>

View File

@@ -14,6 +14,19 @@
</div>
{% endif %}
{% if is_club_admin and billing_ends_at %}
<div class="alert alert-warning mb-6">
{% lucide "calendar-clock" size=20 %}
<span>
{% if billing_auto_renews %}
{% blocktrans with date=billing_ends_at|date:"j M Y" %}Your current billing period ends {{ date }} and will renew automatically.{% endblocktrans %}
{% else %}
{% blocktrans with date=billing_ends_at|date:"j M Y" %}Your current billing period ends {{ date }} and is not set to renew automatically — billing is about to stop. Contact us to keep access.{% endblocktrans %}
{% endif %}
</span>
</div>
{% endif %}
{% comment %}
The club's own numbers that should be zero -- same attention/chart/stat-group
data controlpanel/club_detail.html shows a platform admin drilling into this
@@ -82,7 +95,7 @@
<div class="card-body">
<div class="flex items-center justify-between">
<h2 class="card-title text-base">{% lucide "calendar" size=18 %} {% trans "Upcoming events" %}</h2>
<a class="btn btn-outline btn-neutral btn-sm gap-2" href="{% url 'management:event_list' %}">{% lucide "arrow-right" size=14 %} {% trans "View all" %}</a>
<a class="btn btn-outline btn-sm gap-2" href="{% url 'management:event_list' %}">{% lucide "arrow-right" size=14 %} {% trans "View all" %}</a>
</div>
<div class="overflow-x-auto">
<table class="table">
@@ -91,7 +104,8 @@
<tr>
<td class="whitespace-nowrap">{{ event.start|date:"D j M, H:i" }}</td>
<td>{{ event.title }}</td>
<td>{{ event.get_kind_display|capfirst }}</td>
<td>{{ event.get_kind_display }}</td>
<td>{% for team in event.teams.all %}{{ team.short_name }}{% if not forloop.last %}, {% endif %}{% empty %}—{% endfor %}</td>
</tr>
{% empty %}
<tr>
@@ -108,7 +122,7 @@
<div class="card-body">
<div class="flex items-center justify-between">
<h2 class="card-title text-base">{% lucide "newspaper" size=18 %} {% trans "News" %}</h2>
<a class="btn btn-outline btn-neutral btn-sm gap-2" href="{% url 'management:news_list' %}">{% lucide "arrow-right" size=14 %} {% trans "View all" %}</a>
<a class="btn btn-outline btn-sm gap-2" href="{% url 'management:news_list' %}">{% lucide "arrow-right" size=14 %} {% trans "View all" %}</a>
</div>
<div class="overflow-x-auto">
<table class="table">

View File

@@ -22,7 +22,7 @@
</div>
<div class="card-actions justify-start pt-2 mt-2">
<a class="btn btn-outline btn-neutral gap-2" href="{% url "management:location_list" %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<a class="btn btn-outline gap-2" href="{% url "management:location_list" %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<button class="btn btn-primary gap-2" type="submit">{% lucide "save" size=16 %} {% trans "Save" %}</button>
</div>
</form>

View File

@@ -33,7 +33,7 @@
<td>{{ location.country }}</td>
<td class="text-right">
<div class="flex justify-end gap-1">
<a class="btn btn-sm btn-outline btn-neutral" href="{% url 'management:location_update' location.pk %}" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</a>
<a class="btn btn-outline btn-sm" href="{% url 'management:location_update' location.pk %}" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</a>
<button class="btn btn-sm btn-outline btn-error" type="button" onclick="document.getElementById('{{ location.pk|dom_id:"location_delete_modal" }}').showModal()" aria-label="{% trans 'Delete' %}">{% lucide "trash-2" size=14 %} {% trans "Delete" %}</button>
</div>
</td>

View File

@@ -6,7 +6,7 @@
{% block actions %}
{% if is_club_admin %}
<a class="btn btn-outline btn-neutral gap-2" href="{% url 'management:member_update' member.pk %}">{% lucide "pencil" size=16 %} {% trans "Edit" %}</a>
<a class="btn btn-outline gap-2" href="{% url 'management:member_update' member.pk %}">{% lucide "pencil" size=16 %} {% trans "Edit" %}</a>
{% endif %}
{% endblock actions %}
@@ -114,8 +114,8 @@
</h2>
{% if is_club_admin %}
<div class="flex gap-2">
<button class="btn btn-outline btn-neutral btn-sm gap-2" type="button" onclick="document.getElementById('{{ family_group.family.pk|dom_id:"add_parent_modal" }}').showModal()">{% lucide "user-plus" size=14 %} {{ add_parent_label }}</button>
<button class="btn btn-outline btn-neutral btn-sm gap-2" type="button" onclick="document.getElementById('{{ family_group.family.pk|dom_id:"add_child_modal" }}').showModal()">{% lucide "baby" size=14 %} {{ add_child_label }}</button>
<button class="btn btn-outline btn-sm gap-2" type="button" onclick="document.getElementById('{{ family_group.family.pk|dom_id:"add_parent_modal" }}').showModal()">{% lucide "user-plus" size=14 %} {{ add_parent_label }}</button>
<button class="btn btn-outline btn-sm gap-2" type="button" onclick="document.getElementById('{{ family_group.family.pk|dom_id:"add_child_modal" }}').showModal()">{% lucide "baby" size=14 %} {{ add_child_label }}</button>
<button class="btn btn-outline btn-error btn-sm gap-2" type="button" onclick="document.getElementById('{{ family_group.family.pk|dom_id:"detach_family_modal" }}').showModal()">{% lucide "user-x" size=14 %} {{ remove_from_family_label }}</button>
</div>
{% endif %}
@@ -140,7 +140,7 @@
<div class="card-body">
<div class="flex items-center justify-between">
<h2 class="card-title text-base">{% lucide "users" size=18 %} {% trans "Family" %}</h2>
<button class="btn btn-outline btn-neutral btn-sm gap-2" type="button" onclick="document.getElementById('attach_family_modal').showModal()">{% lucide "user-plus" size=14 %} {% trans "Add to family" %}</button>
<button class="btn btn-outline btn-sm gap-2" type="button" onclick="document.getElementById('attach_family_modal').showModal()">{% lucide "user-plus" size=14 %} {% trans "Add to family" %}</button>
</div>
{% if not family_groups %}
<p class="text-sm opacity-60">{% trans "Not part of a family." %}</p>

View File

@@ -45,7 +45,7 @@
{% endif %}
<div class="card-actions justify-start pt-2 mt-2">
<a class="btn btn-outline btn-neutral gap-2" href="{% if update_view %}{% url "management:member_detail" object.pk %}{% else %}{% url "management:member_list" %}{% endif %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<a class="btn btn-outline gap-2" href="{% if update_view %}{% url "management:member_detail" object.pk %}{% else %}{% url "management:member_list" %}{% endif %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<button class="btn btn-primary gap-2" type="submit">{% lucide "save" size=16 %} {% trans "Save" %}</button>
</div>
</form>

View File

@@ -4,7 +4,7 @@
{% block heading %}{% trans "Mass upload members" %}{% endblock heading %}
{% block actions %}
<a class="btn btn-outline btn-neutral gap-2" href="{% url 'management:member_import_template' %}">{% lucide "download" size=16 %} {% trans "Download template" %}</a>
<a class="btn btn-outline gap-2" href="{% url 'management:member_import_template' %}">{% lucide "download" size=16 %} {% trans "Download template" %}</a>
{% endblock actions %}
{% block panel %}
@@ -30,7 +30,7 @@
{% endfor %}
<div class="card-actions justify-start pt-2 mt-2">
<a class="btn btn-outline btn-neutral gap-2" href="{% url 'management:member_list' %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<a class="btn btn-outline gap-2" href="{% url 'management:member_list' %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<button class="btn btn-primary gap-2" type="submit">{% lucide "upload" size=16 %} {% trans "Upload and preview" %}</button>
</div>
</form>

View File

@@ -55,7 +55,7 @@
</div>
<div class="card-actions justify-start pt-2 mt-2">
<a class="btn btn-outline btn-neutral gap-2" href="{% url 'management:member_import' %}">{% lucide "arrow-left" size=16 %} {% trans "Back" %}</a>
<a class="btn btn-outline gap-2" href="{% url 'management:member_import' %}">{% lucide "arrow-left" size=16 %} {% trans "Back" %}</a>
{% if valid_count %}
<form method="post" action="{% url 'management:member_import_confirm' %}">
{% csrf_token %}

View File

@@ -7,8 +7,8 @@
<a class="btn btn-outline btn-info gap-2" href="{% url 'management:member_import_template' %}" xmlns:management="http://www.w3.org/1999/xhtml">{% lucide "download" size=16 %} {% trans "Download upload template" %}</a>
{% if is_club_admin %}
<a class="btn btn-outline btn-info gap-2" href="{% url 'management:member_import' %}">{% lucide "upload" size=16 %} {% trans "Mass upload" %}</a>
<a class="btn btn-outline btn-neutral gap-2" href="{% url 'management:family_create' %}">{% lucide "users" size=16 %} {% trans "Add family" %}</a>
<a class="btn btn-outline btn-neutral gap-2" href="{% url 'management:member_create' %}">{% lucide "user-plus" size=16 %} {% trans "Add member" %}</a>
<a class="btn btn-outline gap-2" href="{% url 'management:family_create' %}">{% lucide "users" size=16 %} {% trans "Add family" %}</a>
<a class="btn btn-outline gap-2" href="{% url 'management:member_create' %}">{% lucide "user-plus" size=16 %} {% trans "Add member" %}</a>
{% endif %}
{% endblock actions %}
@@ -18,9 +18,9 @@
<span class="opacity-50">{% lucide "search" size=16 %}</span>
<input type="search" name="q" value="{{ search }}" placeholder="{% trans 'Search members ...' %}" class="input input-bordered w-full max-w-xs">
</label>
<button class="btn btn-outline btn-neutral gap-2" type="submit">{% lucide "search" size=16 %} {% trans "Search" %}</button>
<button class="btn btn-outline gap-2" type="submit">{% lucide "search" size=16 %} {% trans "Search" %}</button>
{% if search %}
<a class="btn btn-neutral gap-2" href="{% url "management:member_list" %}">{% lucide "x" size=16 %} {% trans "Clear filter" %}</a>
<a class="btn gap-2" href="{% url "management:member_list" %}">{% lucide "x" size=16 %} {% trans "Clear filter" %}</a>
{% endif %}
</form>
@@ -49,7 +49,7 @@
<div class="flex flex-col gap-1">
{% for fm in member.family_memberships_display %}
<div>
<a class="btn btn-sm btn-outline btn-neutral" href="{% url 'management:family_detail' fm.family.pk %}">{% lucide "users" size=14 %} {{ fm.family }} {% lucide "arrow-right" size=14 %}</a>
<a class="btn btn-outline btn-sm" href="{% url 'management:family_detail' fm.family.pk %}">{% lucide "users" size=14 %} {{ fm.family }} {% lucide "arrow-right" size=14 %}</a>
</div>
{% endfor %}
</div>
@@ -73,7 +73,7 @@
<td class="text-right">
{% if is_club_admin %}
<div class="flex justify-end gap-1">
<a class="btn btn-sm btn-outline btn-neutral" href="{% url 'management:member_detail' member.pk %}" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</a>
<a class="btn btn-outline btn-sm" href="{% url 'management:member_detail' member.pk %}" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</a>
<button class="btn btn-sm btn-outline btn-error" type="button" onclick="document.getElementById('{{ member.pk|dom_id:"member_delete_modal" }}').showModal()" aria-label="{% trans 'Delete' %}">{% lucide "trash-2" size=14 %} {% trans "Delete" %}</button>
</div>
{% endif %}

View File

@@ -5,7 +5,7 @@
{% block subheading %}{% if current_season %}{% blocktrans %}Fee status for season{% endblocktrans %}{% endif %}{% endblock subheading %}
{% block actions %}
<a class="btn btn-outline btn-neutral gap-2" href="{% url 'management:membership_export_pdf' %}?{{ request.GET.urlencode }}">{% lucide "file-down" size=16 %} {% trans "Export to PDF" %}</a>
<a class="btn btn-outline gap-2" href="{% url 'management:membership_export_pdf' %}?{{ request.GET.urlencode }}">{% lucide "file-down" size=16 %} {% trans "Export to PDF" %}</a>
{% endblock actions %}
{% block panel %}
@@ -93,8 +93,8 @@
{% endfor %}
</select>
<button class="btn btn-outline btn-neutral gap-2" type="submit">{% lucide "filter" size=16 %} {% trans "Filter" %}</button>
<a class="btn btn-neutral gap-2" href="{% url 'management:membership_list' %}">{% lucide "x" size=16 %} {% trans "Reset" %}</a>
<button class="btn btn-outline gap-2" type="submit">{% lucide "filter" size=16 %} {% trans "Filter" %}</button>
<a class="btn gap-2" href="{% url 'management:membership_list' %}">{% lucide "x" size=16 %} {% trans "Reset" %}</a>
</div>
</form>
@@ -173,7 +173,7 @@
<td class="text-right">
{% if membership.record_payment_form %}
<div class="flex justify-end gap-1">
<button class="btn btn-sm btn-outline btn-neutral" type="button" onclick="document.getElementById('{{ membership.pk|dom_id:"record_payment_modal" }}').showModal()">
<button class="btn btn-outline btn-sm" type="button" onclick="document.getElementById('{{ membership.pk|dom_id:"record_payment_modal" }}').showModal()">
{% lucide "receipt" size=12 %} {% trans "Record payment" %}
</button>
<button class="btn btn-sm btn-outline btn-success" type="submit" form="{{ membership.pk|dom_id:"mark_fully_paid_form" }}">

View File

@@ -15,7 +15,7 @@
{% block actions %}
{% if can_edit %}
<a class="btn btn-outline btn-neutral gap-2" href="{% url 'management:news_update' news_item.pk %}">{% lucide "pencil" size=16 %} {% trans "Edit" %}</a>
<a class="btn btn-outline gap-2" href="{% url 'management:news_update' news_item.pk %}">{% lucide "pencil" size=16 %} {% trans "Edit" %}</a>
<button class="btn btn-outline btn-error gap-2" type="button" onclick="document.getElementById('delete_news_modal').showModal()">{% lucide "trash-2" size=16 %} {% trans "Delete" %}</button>
{% endif %}
{% if can_publish %}
@@ -46,7 +46,7 @@
<div class="flex items-center justify-between">
<h2 class="card-title text-base">{% trans "Photos" %}</h2>
{% if can_edit %}
<button class="btn btn-outline btn-neutral btn-sm gap-2" type="button" onclick="document.getElementById('add_photos_modal').showModal()">{% lucide "image-plus" size=14 %} {% trans "Add photos" %}</button>
<button class="btn btn-outline btn-sm gap-2" type="button" onclick="document.getElementById('add_photos_modal').showModal()">{% lucide "image-plus" size=14 %} {% trans "Add photos" %}</button>
{% endif %}
</div>
@@ -62,7 +62,7 @@
{% if not photo.is_main %}
<form method="post" action="{% url 'management:news_photo_set_main' news_item.pk photo.pk %}">
{% csrf_token %}
<button class="btn btn-outline btn-neutral btn-xs gap-1" type="submit">{% lucide "star" size=12 %} {% trans "Set main" %}</button>
<button class="btn btn-outline btn-xs gap-1" type="submit">{% lucide "star" size=12 %} {% trans "Set main" %}</button>
</form>
{% endif %}
<button class="btn btn-outline btn-error btn-xs gap-1" type="button" onclick="document.getElementById('{{ photo.pk|dom_id:"delete_photo_modal" }}').showModal()">{% lucide "trash-2" size=12 %} {% trans "Delete" %}</button>

View File

@@ -26,7 +26,7 @@
</div>
<div class="card-actions justify-start pt-2 mt-2">
<a class="btn btn-outline btn-neutral gap-2" href="{% if update_view %}{% url "management:news_detail" object.pk %}{% else %}{% url "management:news_list" %}{% endif %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<a class="btn btn-outline gap-2" href="{% if update_view %}{% url "management:news_detail" object.pk %}{% else %}{% url "management:news_list" %}{% endif %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<button class="btn btn-primary gap-2" type="submit">{% lucide "save" size=16 %} {% trans "Save" %}</button>
</div>
</form>

View File

@@ -5,7 +5,7 @@
{% block actions %}
{% if can_add_news %}
<a class="btn btn-primary gap-2" href="{% url 'management:news_create' %}">{% lucide "plus" size=16 %} {% trans "New news item" %}</a>
<a class="btn btn-outline gap-2" href="{% url 'management:news_create' %}">{% lucide "plus" size=16 %} {% trans "New news item" %}</a>
{% endif %}
{% endblock actions %}
@@ -47,7 +47,7 @@
<td class="text-right">
{% if news_item.can_edit %}
<div class="flex justify-end gap-1">
<a class="btn btn-sm btn-outline btn-neutral" href="{% url 'management:news_detail' news_item.pk %}" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</a>
<a class="btn btn-outline btn-sm" href="{% url 'management:news_detail' news_item.pk %}" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</a>
<button class="btn btn-sm btn-outline btn-error" type="button" onclick="document.getElementById('{{ news_item.pk|dom_id:"news_delete_modal" }}').showModal()" aria-label="{% trans 'Delete' %}">{% lucide "trash-2" size=14 %} {% trans "Delete" %}</button>
</div>
{% endif %}

View File

@@ -22,7 +22,7 @@
</div>
<div class="card-actions justify-start pt-2 mt-2">
<a class="btn btn-outline btn-neutral gap-2" href="{% url "management:opponent_list" %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<a class="btn btn-outline gap-2" href="{% url "management:opponent_list" %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<button class="btn btn-primary gap-2" type="submit">{% lucide "save" size=16 %} {% trans "Save" %}</button>
</div>
</form>

View File

@@ -30,7 +30,7 @@
<td>{{ opponent.name }}</td>
<td class="text-right">
<div class="flex justify-end gap-1">
<a class="btn btn-sm btn-outline btn-neutral" href="{% url 'management:opponent_update' opponent.pk %}" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</a>
<a class="btn btn-outline btn-sm" href="{% url 'management:opponent_update' opponent.pk %}" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</a>
<button class="btn btn-sm btn-outline btn-error" type="button" onclick="document.getElementById('{{ opponent.pk|dom_id:"opponent_delete_modal" }}').showModal()" aria-label="{% trans 'Delete' %}">{% lucide "trash-2" size=14 %} {% trans "Delete" %}</button>
</div>
</td>

View File

@@ -29,7 +29,7 @@
</div>
<div class="card-actions justify-start pt-2 mt-2">
<a class="btn btn-outline btn-neutral gap-2" href="{% url "management:position_list" %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<a class="btn btn-outline gap-2" href="{% url "management:position_list" %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<button class="btn btn-primary gap-2" type="submit">{% lucide "save" size=16 %} {% trans "Save" %}</button>
</div>
</form>

View File

@@ -5,7 +5,7 @@
{% block actions %}
{% if is_club_admin %}
<a class="btn btn-primary gap-2" href="{% url 'management:position_create' %}">{% lucide "plus" size=16 %} {% trans "New position" %}</a>
<a class="btn btn-outline gap-2" href="{% url 'management:position_create' %}">{% lucide "plus" size=16 %} {% trans "New position" %}</a>
{% endif %}
{% endblock actions %}
@@ -42,7 +42,7 @@
</td>
<td class="text-right">
{% if is_club_admin %}
<a class="btn btn-outline btn-neutral btn-sm gap-2" href="{% url 'management:position_update' position.pk %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</a>
<a class="btn btn-outline btn-sm gap-2" href="{% url 'management:position_update' position.pk %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</a>
{% endif %}
</td>
</tr>

View File

@@ -0,0 +1,35 @@
{% extends "management/base.html" %}
{% load lucide ui i18n %}
{% block heading %}{% trans "Import from RBIHF" %}{% endblock heading %}
{% block panel %}
<div class="card w-full bg-base-100 shadow">
<div class="card-body">
<p class="text-sm opacity-70">
{% blocktrans %}Paste the URL of a team's page on the RBIHF website and pick which of
your teams it's for. Nothing is created yet: you'll see exactly what will
be added, changed, or removed before anything is saved.{% endblocktrans %}
</p>
<form method="post">
{% csrf_token %}
{% for error in form.non_field_errors %}
<div class="alert alert-error my-2">
<span>{{ error }}</span>
</div>
{% endfor %}
{% for field in form %}
{% form_field field %}
{% endfor %}
<div class="card-actions justify-start pt-2 mt-2">
<a class="btn btn-outline gap-2" href="{% url 'management:event_list' %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<button class="btn btn-primary gap-2" type="submit">{% lucide "download" size=16 %} {% trans "Fetch and preview" %}</button>
</div>
</form>
</div>
</div>
{% endblock panel %}

View File

@@ -0,0 +1,152 @@
{% extends "management/base.html" %}
{% load lucide i18n %}
{% block heading %}{% trans "Review import" %}{% endblock heading %}
{% block panel %}
<div class="alert alert-info mb-4">
{% lucide "info" size=16 %}
<span>{% blocktrans with scraped=plan.scraped_team_name team=plan.team %}Importing fixtures for “{{ scraped }}” onto your team “{{ team }}”. Double check that's the right match before confirming.{% endblocktrans %}</span>
</div>
<form method="post" action="{% url 'management:rbihf_import_confirm' %}">
{% csrf_token %}
<div class="card bg-base-100 shadow mb-4">
<div class="card-body">
<h2 class="card-title text-base">{% lucide "plus" size=18 %} {% blocktrans count counter=plan.to_create|length %}{{ counter }} game to create{% plural %}{{ counter }} games to create{% endblocktrans %}</h2>
{% if plan.to_create %}
<div class="overflow-x-auto">
<table class="table">
<thead>
<tr>
<th>{% trans "#" %}</th>
<th>{% trans "Start" %}</th>
<th>{% trans "Opponent" %}</th>
<th>{% trans "Location" %}</th>
</tr>
</thead>
<tbody>
{% for planned in plan.to_create %}
<tr>
<td>{{ planned.fixture.external_game_id }}</td>
<td>{{ planned.fixture.start|date:"Y-m-d H:i" }}</td>
<td>
<div class="flex items-center gap-2">
<select name="opponent_{{ planned.fixture.external_game_id }}" class="select select-sm w-full">
<option value="">{% trans "Use scraped name" %}</option>
{% for opponent in plan.opponent_choices %}
<option value="{{ opponent.pk }}" {% if planned.suggested_opponent.pk == opponent.pk %}selected{% endif %}>{{ opponent.name }}</option>
{% endfor %}
</select>
<span class="text-xs opacity-60 whitespace-nowrap">{% blocktrans with opponent=planned.fixture.opponent_name %}RBIHF: {{ opponent }}{% endblocktrans %}</span>
</div>
</td>
<td>
<div class="flex items-center gap-2">
<select name="location_{{ planned.fixture.external_game_id }}" class="select select-sm w-full">
<option value="">&mdash;</option>
{% for location in plan.location_choices %}
<option value="{{ location.pk }}" {% if planned.suggested_location.pk == location.pk %}selected{% endif %}>{{ location.name }}</option>
{% endfor %}
</select>
<span class="text-xs opacity-60 whitespace-nowrap">{% blocktrans with venue=planned.fixture.venue_text %}RBIHF: {{ venue }}{% endblocktrans %}</span>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
</div>
<div class="card bg-base-100 shadow mb-4">
<div class="card-body">
<h2 class="card-title text-base">{% lucide "refresh-cw" size=18 %} {% blocktrans count counter=plan.to_update|length %}{{ counter }} game to update{% plural %}{{ counter }} games to update{% endblocktrans %}</h2>
{% if plan.to_update %}
<div class="overflow-x-auto">
<table class="table">
<thead>
<tr>
<th>{% trans "#" %}</th>
<th>{% trans "Changes" %}</th>
<th>{% trans "Opponent" %}</th>
<th>{% trans "Location" %}</th>
</tr>
</thead>
<tbody>
{% for planned in plan.to_update %}
<tr>
<td>{{ planned.fixture.external_game_id }}</td>
<td>
{% for field, change in planned.changes.items %}
<div class="text-xs">
<span class="font-semibold">{{ field }}:</span>
<span class="opacity-60 line-through">{{ change.0|default:"—" }}</span>
&rarr;
<span>{{ change.1 }}</span>
</div>
{% endfor %}
</td>
<td>
<div class="flex items-center gap-2">
<select name="opponent_{{ planned.fixture.external_game_id }}" class="select select-sm w-full">
<option value="">{% trans "Use scraped name" %}</option>
{% for opponent in plan.opponent_choices %}
<option value="{{ opponent.pk }}" {% if planned.suggested_opponent.pk == opponent.pk %}selected{% endif %}>{{ opponent.name }}</option>
{% endfor %}
</select>
<span class="text-xs opacity-60 whitespace-nowrap">{% blocktrans with opponent=planned.fixture.opponent_name %}RBIHF: {{ opponent }}{% endblocktrans %}</span>
</div>
</td>
<td>
<div class="flex items-center gap-2">
<select name="location_{{ planned.fixture.external_game_id }}" class="select select-sm w-full">
<option value="">&mdash;</option>
{% for location in plan.location_choices %}
<option value="{{ location.pk }}" {% if planned.suggested_location.pk == location.pk %}selected{% endif %}>{{ location.name }}</option>
{% endfor %}
</select>
<span class="text-xs opacity-60 whitespace-nowrap">{% blocktrans with venue=planned.fixture.venue_text %}RBIHF: {{ venue }}{% endblocktrans %}</span>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
</div>
<div class="card bg-base-100 shadow mb-4">
<div class="card-body">
<h2 class="card-title text-base">{% lucide "trash-2" size=18 %} {% blocktrans count counter=plan.to_delete|length %}{{ counter }} game to delete{% plural %}{{ counter }} games to delete{% endblocktrans %}</h2>
<p class="text-sm opacity-70">{% trans "No longer listed on the RBIHF page — only upcoming games are ever removed this way, past results are left alone." %}</p>
{% if plan.to_delete %}
<ul class="list-disc list-inside text-sm">
{% for event in plan.to_delete %}
<li>{{ event.start|date:"Y-m-d H:i" }} &mdash; {{ event }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% if plan.unchanged_count %}
<p class="text-sm opacity-60 mb-4">{% blocktrans count counter=plan.unchanged_count %}{{ counter }} game is already up to date.{% plural %}{{ counter }} games are already up to date.{% endblocktrans %}</p>
{% endif %}
<div class="card-actions justify-start pt-2 mt-2">
<a class="btn btn-outline gap-2" href="{% url 'management:rbihf_import' %}">{% lucide "arrow-left" size=16 %} {% trans "Back" %}</a>
{% if plan.to_create or plan.to_update or plan.to_delete %}
<button class="btn btn-primary gap-2" type="submit">{% lucide "check" size=16 %} {% trans "Confirm import" %}</button>
{% endif %}
</div>
</form>
{% endblock panel %}

View File

@@ -12,7 +12,7 @@
{% block heading %}{% trans "Roles" %}{% endblock heading %}
{% block actions %}
<button class="btn btn-primary gap-2" type="button" onclick="document.getElementById('grant_role_modal').showModal()">{% lucide "plus" size=16 %} {% trans "Grant role" %}</button>
<button class="btn btn-outline gap-2" type="button" onclick="document.getElementById('grant_role_modal').showModal()">{% lucide "plus" size=16 %} {% trans "Grant role" %}</button>
{% endblock actions %}
{% block panel %}

View File

@@ -0,0 +1,31 @@
{% extends "management/base.html" %}
{% load i18n lucide ui %}
{% block heading %}{% if update_view %}{% blocktrans %}Edit {{ object }}{% endblocktrans %}{% else %}{% trans "New sponsor" %}{% endif %}{% endblock heading %}
{% block panel %}
<div class="card w-full bg-base-100 shadow">
<div class="card-body">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{% for error in form.non_field_errors %}
<div class="alert alert-error my-2">
<span>{{ error }}</span>
</div>
{% endfor %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{% for field in form %}
{% form_field field %}
{% endfor %}
</div>
<div class="card-actions justify-start pt-2 mt-2">
<a class="btn btn-outline gap-2" href="{% url "management:sponsor_list" %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<button class="btn btn-primary gap-2" type="submit">{% lucide "save" size=16 %} {% trans "Save" %}</button>
</div>
</form>
</div>
</div>
{% endblock panel %}

View File

@@ -0,0 +1,68 @@
{% extends "management/base.html" %}
{% load i18n lucide ui %}
{% block heading %}{% trans "Sponsors" %}{% endblock heading %}
{% block actions %}
<a class="btn btn-primary gap-2" href="{% url 'management:sponsor_create' %}">{% lucide "plus" size=16 %} {% trans "New sponsor" %}</a>
{% endblock actions %}
{% block panel %}
<div class="card bg-base-100 shadow">
<div class="card-body">
<div class="overflow-x-auto">
<table class="table">
<thead>
<tr>
<th></th>
<th>{% trans "Name" %}</th>
<th>{% trans "URL" %}</th>
<th>{% trans "Start date" %}</th>
<th>{% trans "End date" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for sponsor in sponsors %}
<tr>
<td>
{% if sponsor.logo %}
<img src="{{ sponsor.logo.url }}" alt="" class="size-8 rounded object-contain">
{% endif %}
</td>
<td>{{ sponsor.name }}</td>
<td>
{% if sponsor.url %}
<a class="link link-hover" href="{{ sponsor.url }}" target="_blank" rel="noopener noreferrer">{{ sponsor.url }}</a>
{% else %}
<span class="opacity-50">&mdash;</span>
{% endif %}
</td>
<td>{{ sponsor.start_date|date:"Y-m-d" }}</td>
<td>{{ sponsor.end_date|date:"Y-m-d"|default:"—" }}</td>
<td class="text-right">
<div class="flex justify-end gap-1">
<a class="btn btn-outline btn-sm" href="{% url 'management:sponsor_update' sponsor.pk %}" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</a>
<button class="btn btn-sm btn-outline btn-error" type="button" onclick="document.getElementById('{{ sponsor.pk|dom_id:"sponsor_delete_modal" }}').showModal()" aria-label="{% trans 'Delete' %}">{% lucide "trash-2" size=14 %} {% trans "Delete" %}</button>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center opacity-60">{% trans "No sponsors yet." %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% trans "Delete sponsor" as delete_sponsor_title %}
{% trans "Delete" as delete_label %}
{% for sponsor in sponsors %}
{% url 'management:sponsor_delete' sponsor.pk as sponsor_delete_url %}
{% blocktrans with name=sponsor.name asvar delete_sponsor_body %}Delete “{{ name }}”? This cannot be undone.{% endblocktrans %}
{% include "controlpanel/_confirm_modal.html" with modal_id=sponsor.pk|dom_id:"sponsor_delete_modal" title=delete_sponsor_title body=delete_sponsor_body action_url=sponsor_delete_url submit_label=delete_label %}
{% endfor %}
{% endblock panel %}

View File

@@ -2,11 +2,17 @@
{% load i18n lucide static ui %}
{% block heading %}{{ team.name }}{% endblock heading %}
{% block subheading %}{{ team.short_name }}{% endblock subheading %}
{% block subheading %}
<span class="flex flex-row gap-2 items-center">
<span>{{ team.short_name }}</span>
<span>&middot;</span>
<span class="badge-sm badge badge-outline">{% lucide "bookmark" size=14 %} {{ team.pk }}</span>
</span>
{% endblock subheading %}
{% block actions %}
{% if is_club_admin %}
<a class="btn btn-outline btn-neutral gap-2" href="{% url 'management:team_update' team.pk %}">{% lucide "pencil" size=16 %} {% trans "Edit" %}</a>
<a class="btn btn-outline gap-2" href="{% url 'management:team_update' team.pk %}">{% lucide "pencil" size=16 %} {% trans "Edit" %}</a>
{% endif %}
{% endblock actions %}
@@ -18,7 +24,7 @@
<option value="{{ season.pk }}" {% if season.pk == selected_season.pk %}selected{% endif %}>{% trans "Season" %} {{ season.start_date|date:"Y" }} - {{ season.end_date|date:"Y" }}</option>
{% endfor %}
</select>
<button class="btn btn-outline btn-neutral gap-2" type="submit">{% lucide "filter" size=16 %} {% trans "View" %}</button>
<button class="btn btn-outline gap-2" type="submit">{% lucide "filter" size=16 %} {% trans "View" %}</button>
</div>
</form>
@@ -28,6 +34,23 @@
<span>{% trans "This club has no seasons yet, so there's no roster or staff to show." %}</span>
</div>
{% else %}
{% if can_manage %}
{% if team_photo %}
{% trans "Replace photo" as photo_modal_title %}
{% else %}
{% trans "Upload photo" as photo_modal_title %}
{% endif %}
{% url 'management:team_photo_set' team.pk selected_season.pk as team_photo_set_url %}
{% include "controlpanel/_modal_form.html" with modal_id="team_photo_modal" title=photo_modal_title form=team_photo_form action_url=team_photo_set_url submit_label="Save" submit_icon="save" %}
{% if team_photo %}
{% trans "Remove team photo?" as remove_photo_body %}
{% trans "Remove photo" as remove_photo_title %}
{% url 'management:team_photo_delete' team.pk selected_season.pk as team_photo_delete_url %}
{% include "controlpanel/_confirm_modal.html" with modal_id="team_photo_delete_modal" title=remove_photo_title body=remove_photo_body action_url=team_photo_delete_url submit_label="Remove" submit_icon="trash-2" %}
{% endif %}
{% endif %}
<div class="mb-4 grid gap-4 lg:grid-cols-3">
<div class="card bg-base-100 shadow border-l-4 {% if attendance_rate is None %}border-info{% elif attendance_rate < 30 %}border-error{% elif attendance_rate < 65 %}border-warning{% else %}border-success{% endif %}">
<div class="card-body p-4">
@@ -126,7 +149,7 @@
<div class="flex items-center justify-between">
<h2 class="card-title text-base">{% trans "Roster" %}</h2>
{% if can_manage %}
<button class="btn btn-outline btn-neutral btn-sm gap-2" type="button" onclick="document.getElementById('add_player_modal').showModal()">{% lucide "user-plus" size=14 %} {% trans "Add player" %}</button>
<button class="btn btn-outline btn-sm gap-2" type="button" onclick="document.getElementById('add_player_modal').showModal()">{% lucide "user-plus" size=14 %} {% trans "Add player" %}</button>
{% endif %}
</div>
<div class="overflow-x-auto">
@@ -153,7 +176,7 @@
<td class="text-right">
{% if can_manage %}
<div class="flex justify-end gap-1">
<button class="btn btn-sm btn-outline btn-neutral" type="button" onclick="document.getElementById('{{ membership.pk|dom_id:"edit_player_modal" }}').showModal()" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</button>
<button class="btn btn-outline btn-sm" type="button" onclick="document.getElementById('{{ membership.pk|dom_id:"edit_player_modal" }}').showModal()" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</button>
<button class="btn btn-sm btn-outline btn-error" type="button" onclick="document.getElementById('{{ membership.pk|dom_id:"remove_player_modal" }}').showModal()" aria-label="{% trans 'Remove' %}">{% lucide "user-minus" size=14 %} {% trans "Remove" %}</button>
</div>
{% endif %}
@@ -175,7 +198,7 @@
<div class="flex items-center justify-between">
<h2 class="card-title text-base">{% trans "Staff" %}</h2>
{% if can_manage %}
<button class="btn btn-outline btn-neutral btn-sm gap-2" type="button" onclick="document.getElementById('add_staff_modal').showModal()">{% lucide "user-plus" size=14 %} {% trans "Assign staff" %}</button>
<button class="btn btn-outline btn-sm gap-2" type="button" onclick="document.getElementById('add_staff_modal').showModal()">{% lucide "user-plus" size=14 %} {% trans "Assign staff" %}</button>
{% endif %}
</div>
<div class="overflow-x-auto">
@@ -195,7 +218,7 @@
<td class="text-right">
{% if can_manage %}
<div class="flex justify-end gap-1">
<button class="btn btn-sm btn-outline btn-neutral" type="button" onclick="document.getElementById('{{ assignment.pk|dom_id:"edit_staff_modal" }}').showModal()" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</button>
<button class="btn btn-outline btn-sm" type="button" onclick="document.getElementById('{{ assignment.pk|dom_id:"edit_staff_modal" }}').showModal()" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</button>
<button class="btn btn-sm btn-outline btn-error" type="button" onclick="document.getElementById('{{ assignment.pk|dom_id:"remove_staff_modal" }}').showModal()" aria-label="{% trans 'Remove' %}">{% lucide "user-minus" size=14 %} {% trans "Remove" %}</button>
</div>
{% endif %}
@@ -212,6 +235,36 @@
</div>
</div>
<div class="card bg-base-100 shadow mt-4">
<div class="card-body">
<div class="flex items-center justify-between">
<h2 class="card-title text-base">{% trans "Team photo" %}</h2>
{% if can_manage %}
<div class="flex gap-2">
{% trans "Upload photo" as upload_photo_label %}
{% trans "Replace photo" as replace_photo_label %}
<button class="btn btn-outline btn-sm gap-2" type="button" onclick="document.getElementById('team_photo_modal').showModal()">
{% if team_photo %}
{% lucide "pencil" size=14 %} {{ replace_photo_label }}
{% else %}
{% lucide "upload" size=14 %} {{ upload_photo_label }}
{% endif %}
</button>
{% if team_photo %}
<button class="btn btn-outline btn-error btn-sm gap-2" type="button" onclick="document.getElementById('team_photo_delete_modal').showModal()">{% lucide "trash-2" size=14 %} {% trans "Remove" %}</button>
{% endif %}
</div>
{% endif %}
</div>
{% if team_photo %}
<img class="mt-2 w-full max-w-md rounded-lg object-cover" src="{{ team_photo.image.url }}" alt="{{ team }}">
{% else %}
<p class="text-sm opacity-60">{% trans "No photo uploaded for this season yet." %}</p>
{% endif %}
</div>
</div>
{% if can_manage %}
{% trans "Add player" as add_player_label %}
{% url 'management:team_roster_add' team.pk selected_season.pk as add_player_url %}

View File

@@ -22,7 +22,7 @@
</div>
<div class="card-actions justify-start pt-2 mt-2">
<a class="btn btn-outline btn-neutral gap-2" href="{% if update_view %}{% url "management:team_detail" object.pk %}{% else %}{% url "management:team_list" %}{% endif %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<a class="btn btn-outline gap-2" href="{% if update_view %}{% url "management:team_detail" object.pk %}{% else %}{% url "management:team_list" %}{% endif %}">{% lucide "arrow-left" size=16 %} {% trans "Cancel" %}</a>
<button class="btn btn-primary gap-2" type="submit">{% lucide "save" size=16 %} {% trans "Save" %}</button>
</div>
</form>

View File

@@ -5,7 +5,7 @@
{% block actions %}
{% if is_club_admin %}
<a class="btn btn-primary gap-2" href="{% url 'management:team_create' %}">{% lucide "plus" size=16 %} {% trans "New team" %}</a>
<a class="btn btn-outline gap-2" href="{% url 'management:team_create' %}">{% lucide "plus" size=16 %} {% trans "New team" %}</a>
{% endif %}
{% endblock actions %}
@@ -33,7 +33,7 @@
<td class="text-right">
{% if is_club_admin %}
<div class="flex justify-end gap-1">
<a class="btn btn-sm btn-outline btn-neutral" href="{% url 'management:team_detail' team.pk %}" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</a>
<a class="btn btn-outline btn-sm" href="{% url 'management:team_detail' team.pk %}" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</a>
<button class="btn btn-sm btn-outline btn-error" type="button" onclick="document.getElementById('{{ team.pk|dom_id:"team_delete_modal" }}').showModal()" aria-label="{% trans 'Delete' %}">{% lucide "trash-2" size=14 %} {% trans "Delete" %}</button>
</div>
{% endif %}