Make the management app responsive on mobile
Replace the mobile nav's horizontal scroll strip (which wrapped into an unreadable jumble because daisyUI's .menu sets flex-wrap: wrap and .menu-horizontal never resets it) with a hamburger button that opens a proper left-side drawer, mirrored in controlpanel for the same bug. Move the theme toggle, "Management", and "Django admin" controls into that drawer below `lg`, leaving only the account icon in the navbar, so the club name has room to render in full instead of truncating to initials. Add a reusable `.table-cards` CSS pattern (app.css) that turns a list table into a stack of labelled cards below `md`, and apply it to every list/detail table in the management app -- members, families, teams, memberships, events, groups, locations, opponents, positions, referee levels/list, roles, sponsors, news, parent claims, team roster/staff, and the shared family-members table. Tables revert to normal desktop layout at `md` and up. Member list gets extra passes: search submits icon-only below `sm` so it fits next to the input, the Members/Guardians/Both tabs go full-width, and last/first name merge into one column. Action-button rows across every page stack full-width on mobile instead of wrapping mid-button (shared fix in base.html). Home dashboard's upcoming-events and news mini-tables become simple lists instead of overflowing tables. Also: brand truncates instead of overflowing on a long club name, a stray invalid xmlns attribute removed, a wrapping UUID badge hidden below `sm` on team detail. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,11 +10,10 @@ passes its own URL so admins land back on the member they were viewing; family_d
|
||||
leaves it unset, since staying on the family page is already the right place there.
|
||||
{% endcomment %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Last name" %}</th>
|
||||
<th>{% trans "First name" %}</th>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Email" %}</th>
|
||||
<th>{% trans "Type" %}</th>
|
||||
<th></th>
|
||||
@@ -23,10 +22,9 @@ leaves it unset, since staying on the family page is already the right place the
|
||||
<tbody>
|
||||
{% for person in group.all %}
|
||||
<tr>
|
||||
<td><a class="link link-hover" href="{% url 'management:member_detail' person.pk %}">{{ person.last_name }}</a></td>
|
||||
<td>{{ person.first_name }}</td>
|
||||
<td>{{ person.contact_email|default:"-" }}</td>
|
||||
<td>
|
||||
<td><a class="link link-hover font-semibold" href="{% url 'management:member_detail' person.pk %}">{{ person.last_name }}, {{ person.first_name }}</a></td>
|
||||
<td data-label="{% trans 'Email' %}">{{ person.contact_email|default:"-" }}</td>
|
||||
<td data-label="{% trans 'Type' %}">
|
||||
{% if is_club_admin %}
|
||||
<form method="post" action="{% url 'management:family_membership_role_update' group.family.pk person.pk %}">
|
||||
{% csrf_token %}
|
||||
@@ -43,7 +41,7 @@ leaves it unset, since staying on the family page is already the right place the
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{% if is_club_admin %}
|
||||
<div class="flex justify-end gap-1">
|
||||
<div class="flex flex-wrap justify-end gap-1">
|
||||
{% if person.grant_login_form %}
|
||||
<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" %}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
can_manage_referees, and an optional next_url to return to after a POST
|
||||
(defaults to the event detail page when blank).
|
||||
{% endcomment %}
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<h2 class="card-title text-base">
|
||||
{% lucide "flag" size=18 %} {% trans "Referees" %}
|
||||
<span class="badge badge-sm {% if referees_full %}badge-neutral{% else %}badge-outline{% endif %}">
|
||||
|
||||
@@ -11,6 +11,15 @@
|
||||
{% block section_title %}{% trans "Management" %}{% endblock section_title %}
|
||||
{% endblock head_title %}
|
||||
|
||||
{% block nav_toggle %}
|
||||
<button class="btn btn-ghost btn-square lg:hidden" type="button" onclick="document.getElementById('mobile_nav_modal').showModal()" aria-label="{% trans 'Menu' %}">
|
||||
{% lucide "menu" size=20 %}
|
||||
</button>
|
||||
{% endblock nav_toggle %}
|
||||
|
||||
{% comment %} Kept in the navbar itself only at `lg`+, where there's no hamburger drawer to hold them instead -- see the comment on `nav_icons_class` in _base.html. {% endcomment %}
|
||||
{% block nav_icons_class %}hidden items-center lg:flex{% endblock nav_icons_class %}
|
||||
|
||||
{% block menu %}
|
||||
<aside class="hidden w-64 shrink-0 overflow-y-auto border-r border-base-300 bg-base-100 lg:block">
|
||||
<ul class="menu w-full gap-1 p-3 mt-4">
|
||||
@@ -20,7 +29,40 @@
|
||||
{% endblock menu %}
|
||||
|
||||
{% block main %}
|
||||
<div class="mb-6 flex flex-wrap flex-row items-center justify-between gap-3">
|
||||
{# Below `lg` the sidebar is hidden and {% block nav_toggle %} above opens this instead. #}
|
||||
<dialog id="mobile_nav_modal" class="modal modal-start lg:hidden">
|
||||
<div class="modal-box h-full max-h-none w-72 max-w-[85vw] rounded-none p-0">
|
||||
<div class="flex items-center justify-between border-b border-base-300 p-3">
|
||||
<span class="font-roboto text-lg font-bold tracking-wider">{% trans "Menu" %}</span>
|
||||
<form method="dialog">
|
||||
<button class="btn btn-ghost btn-square btn-sm" aria-label="{% trans 'Close' %}">{% lucide "x" size=18 %}</button>
|
||||
</form>
|
||||
</div>
|
||||
<ul class="menu w-full gap-1 p-3">
|
||||
<li>
|
||||
<button type="button" data-theme-toggle>
|
||||
<span data-theme-icon="light" class="hidden items-center gap-3">{% lucide "sun" size=16 %} {% trans "Light theme" %}</span>
|
||||
<span data-theme-icon="dark" class="hidden items-center gap-3">{% lucide "moon" size=16 %} {% trans "Dark theme" %}</span>
|
||||
<span data-theme-icon="auto" class="hidden items-center gap-3">{% lucide "sun-moon" size=16 %} {% trans "Auto theme" %}</span>
|
||||
</button>
|
||||
</li>
|
||||
{% if has_management_access %}
|
||||
<li><a href="{% url "management:home" %}">{% lucide "layout-dashboard" size=16 %} {% trans "Management" %}</a></li>
|
||||
{% endif %}
|
||||
{% if user.is_superuser %}
|
||||
<li><a href="{% url "admin:index" %}">{% lucide "shield-cog" size=16 %} {% trans "Django admin" %}</a></li>
|
||||
{% endif %}
|
||||
<li class="menu-title">{% trans "Navigation" %}</li>
|
||||
{% include "management/_nav_items.html" %}
|
||||
</ul>
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>{% trans "close" %}</button>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
{# flex-col below `sm`: title over a full-width, stacked column of action buttons reads far better on a phone than the same row wrapping mid-button. #}
|
||||
<div class="mb-6 flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between">
|
||||
<div class="flex flex-col gap-2 grow">
|
||||
<h1 class="text-3xl font-bold">
|
||||
{% block heading %}{% trans "Management" %}{% endblock heading %}
|
||||
@@ -28,16 +70,11 @@
|
||||
<span class="text-sm text-base-content/50">{% block subheading %}{% endblock subheading %}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<div class="flex w-full flex-col gap-2 sm:w-auto sm:flex-row sm:flex-wrap">
|
||||
{% block actions %}{% endblock actions %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Below `lg` the sidebar is hidden, so the same links appear here rather than nowhere. #}
|
||||
<ul class="menu menu-horizontal mb-6 w-full gap-1 overflow-x-auto rounded-box bg-base-100 lg:hidden">
|
||||
{% include "management/_nav_items.html" %}
|
||||
</ul>
|
||||
|
||||
{% comment %}
|
||||
A final billing notice follows the admin onto every management page -- but only at
|
||||
error level (inside 7 days of archiving, or already past it). Shown from the moment
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<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()">
|
||||
@@ -107,7 +107,7 @@
|
||||
{% if event.kind == "game" %}
|
||||
<div class="mb-6 card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<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 %}
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Title" %}</th>
|
||||
@@ -52,14 +52,14 @@
|
||||
<tbody>
|
||||
{% for event in events %}
|
||||
<tr>
|
||||
<td class="flex flex-row items-center gap-2">
|
||||
<td class="flex flex-row items-center gap-2 font-semibold">
|
||||
<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 data-label="{% trans 'Kind' %}">{{ event.get_kind_display }}</td>
|
||||
<td data-label="{% trans 'When' %}">{{ event.start|date:"j M Y H:i" }}</td>
|
||||
<td data-label="{% trans 'Teams' %}">{% for team in event.teams.all %}{{ team.short_name }}{% if not forloop.last %}, {% endif %}{% empty %}—{% endfor %}</td>
|
||||
<td data-label="{% trans 'Location' %}">{{ event.location.name|default:"—" }}</td>
|
||||
<td class="text-right">
|
||||
{% if event.can_manage %}
|
||||
<div class="flex justify-end gap-1">
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
<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">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "When" %}</th>
|
||||
@@ -77,8 +77,8 @@
|
||||
<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>
|
||||
<td class="font-semibold"><a class="link link-hover" href="{% url 'management:event_detail' occurrence.pk %}">{{ occurrence.start|date:"j M Y H:i" }}</a></td>
|
||||
<td data-label="{% trans 'Status' %}">
|
||||
{% if occurrence.cancelled %}
|
||||
<span class="badge badge-outline gap-1">{% lucide "ban" size=12 %} {% trans "Cancelled" %}</span>
|
||||
{% elif occurrence.detached %}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Family" %}</th>
|
||||
@@ -38,14 +38,14 @@
|
||||
{% for family in families %}
|
||||
<tr>
|
||||
<td><a class="link link-hover font-semibold" href="{% url 'management:family_detail' family.pk %}">{{ family }}</a></td>
|
||||
<td>
|
||||
<td data-label="{% trans 'Parents / guardians' %}">
|
||||
{% for guardian in family.guardians_display %}
|
||||
<a class="link link-hover" href="{% url 'management:member_detail' guardian.pk %}">{{ guardian }}</a>{% if not forloop.last %}, {% endif %}
|
||||
{% empty %}
|
||||
<span class="opacity-40">-</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>
|
||||
<td data-label="{% trans 'Children' %}">
|
||||
{% for child in family.children_display %}
|
||||
<a class="link link-hover" href="{% url 'management:member_detail' child.pk %}">{{ child }}</a>{% if not forloop.last %}, {% endif %}
|
||||
{% empty %}
|
||||
|
||||
@@ -18,23 +18,26 @@
|
||||
{% endfor %}
|
||||
|
||||
{% comment %}
|
||||
Deliberately not wrapped in the usual overflow-x-auto: that makes a
|
||||
scroll container, which clips the member picker's absolutely
|
||||
positioned dropdown for every row but the first couple.
|
||||
overflow-x-auto is safe here: the member picker's dropdown
|
||||
(searchable-select.js) is position: fixed, computed from the input's
|
||||
own screen position rather than document flow, so a scrolling
|
||||
ancestor around the table no longer clips it.
|
||||
{% endcomment %}
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Member" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="bulk-add-rows" data-prefix="{{ formset.prefix }}">
|
||||
{% for form in formset %}
|
||||
{% include "management/_group_bulk_add_row.html" with form=form %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Member" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="bulk-add-rows" data-prefix="{{ formset.prefix }}">
|
||||
{% for form in formset %}
|
||||
{% include "management/_group_bulk_add_row.html" with form=form %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<template id="bulk-add-row-template">
|
||||
{% include "management/_group_bulk_add_row.html" with form=formset.empty_form %}
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
{% block panel %}
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<h2 class="card-title text-base">{% trans "Members" %}</h2>
|
||||
<a class="btn btn-outline btn-sm gap-2" href="{% url 'management:group_bulk_add' group.pk %}">{% lucide "users" size=14 %} {% trans "Add multiple" %}</a>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Member" %}</th>
|
||||
@@ -25,7 +25,7 @@
|
||||
<tbody>
|
||||
{% for membership in memberships %}
|
||||
<tr>
|
||||
<td><a class="link link-hover" href="{% url 'management:member_detail' membership.member.pk %}">{{ membership.member }}</a></td>
|
||||
<td class="font-semibold"><a class="link link-hover" href="{% url 'management:member_detail' membership.member.pk %}">{{ membership.member }}</a></td>
|
||||
<td class="text-right">
|
||||
<button class="btn btn-sm btn-outline btn-error" type="button" onclick="document.getElementById('{{ membership.pk|dom_id:"remove_member_modal" }}').showModal()">{% lucide "user-minus" size=14 %} {% trans "Remove" %}</button>
|
||||
</td>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Name" %}</th>
|
||||
@@ -23,8 +23,8 @@
|
||||
<tbody>
|
||||
{% for group in groups %}
|
||||
<tr>
|
||||
<td><a class="link link-hover" href="{% url 'management:group_detail' group.pk %}">{{ group.name }}</a></td>
|
||||
<td>{{ group.member_count }}</td>
|
||||
<td><a class="link link-hover font-semibold" href="{% url 'management:group_detail' group.pk %}">{{ group.name }}</a></td>
|
||||
<td data-label="{% trans 'Members' %}">{{ group.member_count }}</td>
|
||||
<td class="text-right">
|
||||
<div class="flex justify-end gap-1">
|
||||
<a class="btn btn-outline btn-sm" href="{% url 'management:group_detail' group.pk %}" aria-label="{% trans 'Edit' %}">{% lucide "pencil" size=14 %} {% trans "Edit" %}</a>
|
||||
|
||||
@@ -96,53 +96,45 @@
|
||||
<div class="mb-6 grid gap-4 lg:grid-cols-2">
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<h2 class="card-title text-base">{% lucide "calendar" size=18 %} {% trans "Upcoming events" %}</h2>
|
||||
<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">
|
||||
<tbody>
|
||||
{% for event in upcoming_events %}
|
||||
<tr>
|
||||
<td class="whitespace-nowrap">{{ event.start|date:"D j M, H:i" }}</td>
|
||||
<td>{{ event.title }}</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>
|
||||
<td class="text-center opacity-60">{% trans "Nothing scheduled." %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<ul class="divide-y divide-base-200">
|
||||
{% for event in upcoming_events %}
|
||||
<li class="flex flex-col gap-1 py-2 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
|
||||
<div class="min-w-0">
|
||||
<div class="truncate font-semibold">{{ event.title }}</div>
|
||||
<div class="truncate text-xs opacity-60">
|
||||
{{ event.get_kind_display }}
|
||||
{% for team in event.teams.all %}· {{ team.short_name }}{% if not forloop.last %}, {% endif %}{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="shrink-0 whitespace-nowrap text-sm opacity-70">{{ event.start|date:"D j M, H:i" }}</div>
|
||||
</li>
|
||||
{% empty %}
|
||||
<li class="py-2 text-center text-sm opacity-60">{% trans "Nothing scheduled." %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<h2 class="card-title text-base">{% lucide "newspaper" size=18 %} {% trans "News" %}</h2>
|
||||
<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">
|
||||
<tbody>
|
||||
{% for news_item in published_news %}
|
||||
<tr>
|
||||
<td class="whitespace-nowrap">{{ news_item.published_at|date:"D j M" }}</td>
|
||||
<td><a class="link link-hover" href="{% url 'management:news_detail' news_item.pk %}">{{ news_item.title }}</a></td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td class="text-center opacity-60">{% trans "Nothing published yet." %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<ul class="divide-y divide-base-200">
|
||||
{% for news_item in published_news %}
|
||||
<li class="flex flex-col gap-1 py-2 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
|
||||
<a class="link link-hover truncate font-semibold" href="{% url 'management:news_detail' news_item.pk %}">{{ news_item.title }}</a>
|
||||
<span class="shrink-0 whitespace-nowrap text-sm opacity-70">{{ news_item.published_at|date:"D j M" }}</span>
|
||||
</li>
|
||||
{% empty %}
|
||||
<li class="py-2 text-center text-sm opacity-60">{% trans "Nothing published yet." %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Name" %}</th>
|
||||
@@ -24,13 +24,13 @@
|
||||
<tbody>
|
||||
{% for location in locations %}
|
||||
<tr>
|
||||
<td>
|
||||
<td class="font-semibold">
|
||||
{{ location.name }}
|
||||
{% if location.is_home %}<span class="badge badge-sm badge-primary ml-1">{% trans "Home" %}</span>{% endif %}
|
||||
</td>
|
||||
<td>{{ location.address }}</td>
|
||||
<td>{{ location.city }}</td>
|
||||
<td>{{ location.country }}</td>
|
||||
<td data-label="{% trans 'Address' %}">{{ location.address }}</td>
|
||||
<td data-label="{% trans 'City' %}">{{ location.city }}</td>
|
||||
<td data-label="{% trans 'Country' %}">{{ location.country }}</td>
|
||||
<td class="text-right">
|
||||
<div class="flex justify-end gap-1">
|
||||
<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>
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
<div class="divider"></div>
|
||||
<h3 class="text-sm font-semibold opacity-70">{% trans "Season history" %}</h3>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Season" %}</th>
|
||||
@@ -131,10 +131,10 @@
|
||||
<tbody>
|
||||
{% for membership in membership_history %}
|
||||
<tr>
|
||||
<td>{{ membership.season.start_date|date:"Y" }} - {{ membership.season.end_date|date:"Y" }}</td>
|
||||
<td>{{ membership.get_status_display }}</td>
|
||||
<td>{{ membership.get_fee_status_display }}</td>
|
||||
<td>{{ membership.license|default:"-" }}</td>
|
||||
<td class="font-semibold">{{ membership.season.start_date|date:"Y" }} - {{ membership.season.end_date|date:"Y" }}</td>
|
||||
<td data-label="{% trans 'Status' %}">{{ membership.get_status_display }}</td>
|
||||
<td data-label="{% trans 'Fee status' %}">{{ membership.get_fee_status_display }}</td>
|
||||
<td data-label="{% trans 'License' %}">{{ membership.license|default:"-" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@@ -148,7 +148,7 @@
|
||||
{% if referee_profile or is_club_admin %}
|
||||
<div class="card bg-base-100 shadow mt-4">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<h2 class="card-title text-base">{% lucide "flag" size=18 %} {% trans "Referee eligibility" %}</h2>
|
||||
{% if is_club_admin %}
|
||||
<button class="btn btn-outline btn-sm gap-2" type="button" onclick="document.getElementById('referee_eligibility_modal').showModal()">{% lucide "pencil" size=14 %} {% trans "Edit" %}</button>
|
||||
@@ -214,13 +214,13 @@
|
||||
{% for family_group in family_groups %}
|
||||
<div class="card bg-base-100 shadow mt-4">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<h2 class="card-title text-base">
|
||||
{% lucide "users" size=18 %}
|
||||
<a class="link link-hover" href="{% url 'management:family_detail' family_group.family.pk %}">{{ family_group.family }}</a>
|
||||
</h2>
|
||||
{% if is_club_admin %}
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<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>
|
||||
@@ -245,7 +245,7 @@
|
||||
{% if is_club_admin %}
|
||||
<div class="card bg-base-100 shadow mt-4">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<h2 class="card-title text-base">{% lucide "users" size=18 %} {% trans "Family" %}</h2>
|
||||
<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>
|
||||
|
||||
@@ -4,24 +4,25 @@
|
||||
{% block heading %}{% trans "Members" %}{% endblock heading %}
|
||||
|
||||
{% block actions %}
|
||||
<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>
|
||||
<a class="btn btn-sm btn-outline btn-info gap-2" href="{% url 'management:member_import_template' %}">{% lucide "download" size=14 %} {% 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 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>
|
||||
<a class="btn btn-sm btn-outline btn-info gap-2" href="{% url 'management:member_import' %}">{% lucide "upload" size=14 %} {% trans "Mass upload" %}</a>
|
||||
<a class="btn btn-sm btn-outline gap-2" href="{% url 'management:family_create' %}">{% lucide "users" size=14 %} {% trans "Add family" %}</a>
|
||||
<a class="btn btn-sm btn-outline gap-2" href="{% url 'management:member_create' %}">{% lucide "user-plus" size=14 %} {% trans "Add member" %}</a>
|
||||
{% endif %}
|
||||
{% endblock actions %}
|
||||
|
||||
{% block panel %}
|
||||
<form method="get" class="mb-2 flex flex-wrap items-center gap-2">
|
||||
<label class="input">
|
||||
<form method="get" class="mb-2 flex items-center gap-2">
|
||||
<label class="input grow sm:grow-0">
|
||||
<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">
|
||||
<input type="search" name="q" value="{{ search }}" placeholder="{% trans 'Search members ...' %}" class="input input-bordered w-full sm:max-w-xs">
|
||||
</label>
|
||||
{% if selected_kind != "member" %}<input type="hidden" name="kind" value="{{ selected_kind }}">{% endif %}
|
||||
<button class="btn btn-outline gap-2" type="submit">{% lucide "search" size=16 %} {% trans "Search" %}</button>
|
||||
{# Icon-only below `sm`: label text next to a growing input is what forced this row to wrap on a phone. #}
|
||||
<button class="btn btn-outline gap-2" type="submit" aria-label="{% trans 'Search' %}">{% lucide "search" size=16 %}<span class="hidden sm:inline">{% trans "Search" %}</span></button>
|
||||
{% if search %}
|
||||
<a class="btn 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" %}" aria-label="{% trans 'Clear filter' %}">{% lucide "x" size=16 %}<span class="hidden sm:inline">{% trans "Clear filter" %}</span></a>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
@@ -33,23 +34,23 @@
|
||||
with nothing on the page explaining why. ?kind= makes the exclusion an
|
||||
explicit, reversible choice instead.
|
||||
{% endcomment %}
|
||||
<div class="mb-4 flex flex-wrap items-center gap-2">
|
||||
<div class="join">
|
||||
<a href="{% querystring kind=None page=None %}" class="btn btn-sm join-item {% if selected_kind == "member" %}btn-primary{% endif %}">{% trans "Members" %}</a>
|
||||
<a href="{% querystring kind="guardian" page=None %}" class="btn btn-sm join-item {% if selected_kind == "guardian" %}btn-primary{% endif %}">{% trans "Guardians" %}</a>
|
||||
<a href="{% querystring kind="both" page=None %}" class="btn btn-sm join-item {% if selected_kind == "both" %}btn-primary{% endif %}">{% trans "Both" %}</a>
|
||||
<div class="mb-4 flex flex-col gap-2">
|
||||
<div class="join w-full">
|
||||
<a href="{% querystring kind=None page=None %}" class="btn btn-sm join-item flex-1 {% if selected_kind == "member" %}btn-primary{% endif %}">{% trans "Members" %}</a>
|
||||
<a href="{% querystring kind="guardian" page=None %}" class="btn btn-sm join-item flex-1 {% if selected_kind == "guardian" %}btn-primary{% endif %}">{% trans "Guardians" %}</a>
|
||||
<a href="{% querystring kind="both" page=None %}" class="btn btn-sm join-item flex-1 {% if selected_kind == "both" %}btn-primary{% endif %}">{% trans "Both" %}</a>
|
||||
</div>
|
||||
<span class="text-xs opacity-60">{% trans "Guardians are parents linked only through a child -- no fee, not counted as a member." %}</span>
|
||||
</div>
|
||||
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
{# table-cards: below `md` each row reads as a card instead of forcing a horizontal scroll -- see the .table-cards rule in app.css. #}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Last name" %}</th>
|
||||
<th>{% trans "First name" %}</th>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Email" %}</th>
|
||||
<th>{% trans "Family" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
@@ -59,10 +60,9 @@
|
||||
<tbody>
|
||||
{% for member in members %}
|
||||
<tr>
|
||||
<td><a class="link link-hover" href="{% url 'management:member_detail' member.pk %}">{{ member.last_name }}</a></td>
|
||||
<td>{{ member.first_name }}</td>
|
||||
<td>{{ member.contact_email|default:"-" }}</td>
|
||||
<td>
|
||||
<td><a class="link link-hover font-semibold" href="{% url 'management:member_detail' member.pk %}">{{ member.last_name }}, {{ member.first_name }}</a></td>
|
||||
<td data-label="{% trans 'Email' %}">{{ member.contact_email|default:"-" }}</td>
|
||||
<td data-label="{% trans 'Family' %}">
|
||||
{% if member.family_memberships_display %}
|
||||
<div class="flex flex-col gap-1">
|
||||
{% for fm in member.family_memberships_display %}
|
||||
@@ -75,7 +75,7 @@
|
||||
<span class="opacity-40">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<td data-label="{% trans 'Status' %}">
|
||||
{% if member.current_membership %}
|
||||
<span class="badge badge-sm
|
||||
{% if member.current_membership.status == "active" %}badge-success
|
||||
@@ -90,7 +90,7 @@
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{% if is_club_admin %}
|
||||
<div class="flex justify-end gap-1">
|
||||
<div class="flex flex-wrap justify-end gap-1">
|
||||
<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>
|
||||
@@ -99,7 +99,7 @@
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center opacity-60">{% trans "No members yet." %}</td>
|
||||
<td colspan="5" class="text-center opacity-60">{% trans "No members yet." %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@@ -59,19 +59,19 @@
|
||||
{% endif %}
|
||||
|
||||
<form method="get" class="mb-4">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<label class="input">
|
||||
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:flex lg:flex-row lg:flex-wrap lg:items-center">
|
||||
<label class="input w-full lg:w-auto">
|
||||
<span class="opacity-50">{% lucide "search" size=16 %}</span>
|
||||
<input type="search" name="q" value="{{ search }}" placeholder="{% trans 'Search members ...' %}" class="input input-bordered">
|
||||
<input type="search" name="q" value="{{ search }}" placeholder="{% trans 'Search members ...' %}" class="input input-bordered w-full">
|
||||
</label>
|
||||
|
||||
<select name="season" class="select select-bordered">
|
||||
<select name="season" class="select select-bordered w-full lg:w-auto">
|
||||
{% 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="fee_status" class="select select-bordered">
|
||||
<select name="fee_status" class="select select-bordered w-full lg:w-auto">
|
||||
<option value="not_paid" {% if selected_fee_status == "not_paid" %}selected{% endif %}>{% trans "Not paid (unpaid or partially)" %}</option>
|
||||
{% for value, label in fee_status_choices %}
|
||||
<option value="{{ value }}" {% if value == selected_fee_status %}selected{% endif %}>{{ label|capfirst }}</option>
|
||||
@@ -79,22 +79,24 @@
|
||||
<option value="all" {% if selected_fee_status == "all" %}selected{% endif %}>{% trans "All" %}</option>
|
||||
</select>
|
||||
|
||||
<select name="status" class="select select-bordered">
|
||||
<select name="status" class="select select-bordered w-full lg:w-auto">
|
||||
<option value="all" {% if selected_status == "all" %}selected{% endif %}>{% trans "All statuses" %}</option>
|
||||
{% for value, label in status_choices %}
|
||||
<option value="{{ value }}" {% if value == selected_status %}selected{% endif %}>{{ label|capfirst }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<select name="team" class="select select-bordered">
|
||||
<select name="team" class="select select-bordered w-full lg:w-auto">
|
||||
<option value="" {% if not selected_team %}selected{% endif %}>{% trans "All teams" %}</option>
|
||||
{% for team in teams %}
|
||||
<option value="{{ team.pk }}" {% if team.pk|stringformat:"s" == selected_team %}selected{% endif %}>{{ team }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<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 class="col-span-full flex gap-2 lg:col-span-1 lg:contents">
|
||||
<button class="btn btn-outline grow gap-2 lg:grow-0" type="submit">{% lucide "filter" size=16 %} {% trans "Filter" %}</button>
|
||||
<a class="btn grow gap-2 lg:grow-0" href="{% url 'management:membership_list' %}">{% lucide "x" size=16 %} {% trans "Reset" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -104,20 +106,20 @@
|
||||
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2 mb-2">
|
||||
<span class="text-sm opacity-70 font-semibold">
|
||||
{% blocktrans count counter=memberships|length %}{{ counter }} membership{% plural %}{{ counter }} memberships{% endblocktrans %}
|
||||
</span>
|
||||
<button class="btn btn-success btn-sm gap-2" type="submit">{% lucide "circle-check" size=14 %} {% trans "Mark selected as paid" %}</button>
|
||||
</div>
|
||||
|
||||
{# table-cards: this table is dense enough (10+ columns) that horizontal scroll alone stops being usable below `md` -- see the .table-cards rule in app.css. #}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" class="checkbox" id="select-all-memberships"></th>
|
||||
<th>{% trans "Last name" %}</th>
|
||||
<th>{% trans "First name" %}</th>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Email" %}</th>
|
||||
<th>{% trans "Family" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
@@ -132,10 +134,9 @@
|
||||
{% for membership in memberships %}
|
||||
<tr>
|
||||
<td><input type="checkbox" class="checkbox membership-row-checkbox" name="membership_ids" value="{{ membership.pk }}"></td>
|
||||
<td><a class="link link-hover" href="{% url 'management:member_detail' membership.member.pk %}">{{ membership.member.last_name }}</a></td>
|
||||
<td>{{ membership.member.first_name }}</td>
|
||||
<td>{{ membership.member.contact_email|default:"-" }}</td>
|
||||
<td>
|
||||
<td><a class="link link-hover font-semibold" href="{% url 'management:member_detail' membership.member.pk %}">{{ membership.member.last_name }}, {{ membership.member.first_name }}</a></td>
|
||||
<td data-label="{% trans 'Email' %}">{{ membership.member.contact_email|default:"-" }}</td>
|
||||
<td data-label="{% trans 'Family' %}">
|
||||
{% if membership.member.family_memberships_display %}
|
||||
{% for fm in membership.member.family_memberships_display %}
|
||||
{{ fm.family }} {% if not forloop.last %},{% endif %}
|
||||
@@ -144,7 +145,7 @@
|
||||
<span class="opacity-40">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<td data-label="{% trans 'Status' %}">
|
||||
<span class="badge badge-sm
|
||||
{% if membership.status == "active" %}badge-success
|
||||
{% elif membership.status == "pending" %}badge-warning
|
||||
@@ -153,7 +154,7 @@
|
||||
{{ membership.get_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<td data-label="{% trans 'Fee status' %}">
|
||||
<span class="badge badge-sm
|
||||
{% if membership.fee_status == "paid" %}badge-success
|
||||
{% elif membership.fee_status == "partially_paid" %}badge-warning
|
||||
@@ -162,17 +163,17 @@
|
||||
{{ membership.get_fee_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="tabular-nums">€ {{ membership.fee_amount }}</td>
|
||||
<td class="tabular-nums">
|
||||
<td data-label="{% trans 'Owed' %}" class="tabular-nums">€ {{ membership.fee_amount }}</td>
|
||||
<td data-label="{% trans 'Paid' %}" class="tabular-nums">
|
||||
€ {{ membership.amount_paid }}
|
||||
{% if membership.record_payment_form and membership.fee_amount %}
|
||||
<div class="text-xs opacity-60">{% blocktrans with remaining=membership.remaining_balance_display %}{{ remaining }} left{% endblocktrans %}</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ membership.license|default:"-" }}</td>
|
||||
<td data-label="{% trans 'License' %}">{{ membership.license|default:"-" }}</td>
|
||||
<td class="text-right">
|
||||
{% if membership.record_payment_form %}
|
||||
<div class="flex justify-end gap-1">
|
||||
<div class="flex flex-wrap justify-end gap-1">
|
||||
<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>
|
||||
@@ -185,7 +186,7 @@
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="11" class="text-center opacity-60">{% trans "Nobody matches these filters." %}</td>
|
||||
<td colspan="10" class="text-center opacity-60">{% trans "Nobody matches these filters." %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
<div class="card bg-base-100 shadow mt-4">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<h2 class="card-title text-base">{% trans "Photos" %}</h2>
|
||||
{% if can_edit %}
|
||||
<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>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Title" %}</th>
|
||||
@@ -26,16 +26,16 @@
|
||||
<tbody>
|
||||
{% for news_item in news_items %}
|
||||
<tr>
|
||||
<td><a class="link link-hover" href="{% url 'management:news_detail' news_item.pk %}">{{ news_item.title }}</a></td>
|
||||
<td>
|
||||
<td><a class="link link-hover font-semibold" href="{% url 'management:news_detail' news_item.pk %}">{{ news_item.title }}</a></td>
|
||||
<td data-label="{% trans 'Teams' %}">
|
||||
{% for team in news_item.teams.all %}
|
||||
<span class="badge badge-neutral badge-sm">{{ team.short_name }}</span>
|
||||
{% empty %}
|
||||
<span class="opacity-60">{% trans "Club-wide" %}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ news_item.get_visibility_display }}</td>
|
||||
<td>
|
||||
<td data-label="{% trans 'Visibility' %}">{{ news_item.get_visibility_display }}</td>
|
||||
<td data-label="{% trans 'Status' %}">
|
||||
{% if news_item.status == "draft" %}
|
||||
<span class="badge badge-neutral badge-sm">{% trans "Draft" %}</span>
|
||||
{% elif news_item.is_scheduled %}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
@@ -27,7 +27,7 @@
|
||||
<img src="{{ opponent.logo.url }}" alt="" class="size-8 rounded object-contain">
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ opponent.name }}</td>
|
||||
<td class="font-semibold">{{ opponent.name }}</td>
|
||||
<td class="text-right">
|
||||
<div class="flex justify-end gap-1">
|
||||
<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>
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-base">{% lucide "history" size=18 %} {% trans "Already dealt with" %}</h2>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Parent" %}</th>
|
||||
@@ -104,9 +104,9 @@
|
||||
<tbody>
|
||||
{% for claim in reviewed %}
|
||||
<tr>
|
||||
<td>{{ claim.parent_name }}<div class="text-xs opacity-60">{{ claim.parent_email }}</div></td>
|
||||
<td>{{ claim.claimed_child_name }}</td>
|
||||
<td>
|
||||
<td class="font-semibold">{{ claim.parent_name }}<div class="text-xs font-normal opacity-60">{{ claim.parent_email }}</div></td>
|
||||
<td data-label="{% trans 'Claimed' %}">{{ claim.claimed_child_name }}</td>
|
||||
<td data-label="{% trans 'Outcome' %}">
|
||||
{% if claim.status == "approved" %}
|
||||
<span class="badge badge-success badge-sm">{% trans "Approved" %}</span>
|
||||
{% if claim.child %}<div class="text-xs opacity-60">{{ claim.child }}</div>{% endif %}
|
||||
@@ -115,7 +115,7 @@
|
||||
{% if claim.note %}<div class="text-xs opacity-60">{{ claim.note }}</div>{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ claim.reviewed_by|default:"—" }}</td>
|
||||
<td data-label="{% trans 'Reviewed by' %}">{{ claim.reviewed_by|default:"—" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Ordering" %}</th>
|
||||
@@ -27,15 +27,15 @@
|
||||
<tbody>
|
||||
{% for position in positions %}
|
||||
<tr>
|
||||
<td>{{ position.ordering }}</td>
|
||||
<td>{{ position.name }}</td>
|
||||
<td>{{ position.short_name }}</td>
|
||||
<td>
|
||||
<td data-label="{% trans 'Ordering' %}">{{ position.ordering }}</td>
|
||||
<td class="font-semibold">{{ position.name }}</td>
|
||||
<td data-label="{% trans 'Short name' %}">{{ position.short_name }}</td>
|
||||
<td data-label="{% trans 'Staff position' %}">
|
||||
<span class="badge badge-sm {% if position.staff_position %}badge-success{% else %}badge-neutral{% endif %}">
|
||||
{% if position.staff_position %}{% trans "Yes" %}{% else %}{% trans "No" %}{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<td data-label="{% trans 'Management position' %}">
|
||||
<span class="badge badge-sm {% if position.management_position %}badge-success{% else %}badge-neutral{% endif %}">
|
||||
{% if position.management_position %}{% trans "Yes" %}{% else %}{% trans "No" %}{% endif %}
|
||||
</span>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Ordering" %}</th>
|
||||
@@ -26,9 +26,9 @@
|
||||
<tbody>
|
||||
{% for level in levels %}
|
||||
<tr>
|
||||
<td>{{ level.ordering }}</td>
|
||||
<td>{{ level.name }}</td>
|
||||
<td>
|
||||
<td data-label="{% trans 'Ordering' %}">{{ level.ordering }}</td>
|
||||
<td class="font-semibold">{{ level.name }}</td>
|
||||
<td data-label="{% trans 'Qualifies for' %}">
|
||||
{% for team in level.teams.all %}
|
||||
<span class="badge badge-outline badge-sm">{{ team.name }}</span>
|
||||
{% empty %}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Referee" %}</th>
|
||||
@@ -21,9 +21,9 @@
|
||||
<tbody>
|
||||
{% for referee in referees %}
|
||||
<tr>
|
||||
<td><a class="link link-hover" href="{% url 'management:member_detail' referee.pk %}">{{ referee }}</a></td>
|
||||
<td>{{ referee.referee_profile.level|default:"—" }}</td>
|
||||
<td>
|
||||
<td><a class="link link-hover font-semibold" href="{% url 'management:member_detail' referee.pk %}">{{ referee }}</a></td>
|
||||
<td data-label="{% trans 'Level' %}">{{ referee.referee_profile.level|default:"—" }}</td>
|
||||
<td data-label="{% trans 'Can referee for' %}">
|
||||
{% if referee.referee_profile.is_eligible %}
|
||||
{% for team in referee.referee_profile.eligible_teams %}
|
||||
<span class="badge badge-outline badge-sm">{{ team.name }}</span>
|
||||
@@ -34,8 +34,8 @@
|
||||
<span class="opacity-40">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ referee.referee_profile.valid_until|default:"—" }}</td>
|
||||
<td>
|
||||
<td data-label="{% trans 'Valid until' %}">{{ referee.referee_profile.valid_until|default:"—" }}</td>
|
||||
<td data-label="{% trans 'Status' %}">
|
||||
{% if referee.referee_profile.is_eligible %}
|
||||
<span class="badge badge-success badge-sm">{% trans "Valid" %}</span>
|
||||
{% elif not referee.referee_profile.level %}
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<span class="opacity-60">{% trans "No referees assigned yet." %}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<span class="badge badge-sm {% if not game.referee_rows %}badge-error{% elif not game.referees_full %}badge-warning{% else %}badge-success{% endif %}">
|
||||
{{ game.referee_rows|length }} / {{ game.max_referees }}
|
||||
</span>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<h2 class="card-title text-base">{{ role_label }}</h2>
|
||||
{% if description %}<p class="text-sm opacity-70">{{ description }}</p>{% endif %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Member" %}</th>
|
||||
@@ -34,7 +34,7 @@
|
||||
<tbody>
|
||||
{% for role in roles_for_section %}
|
||||
<tr>
|
||||
<td>{{ role.member }}</td>
|
||||
<td class="font-semibold">{{ role.member }}</td>
|
||||
<td class="text-right">
|
||||
<form method="post" action="{% url 'management:role_revoke' role.pk %}">
|
||||
{% csrf_token %}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
@@ -30,16 +30,16 @@
|
||||
<img src="{{ sponsor.logo.url }}" alt="" class="size-8 rounded object-contain">
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ sponsor.name }}</td>
|
||||
<td>
|
||||
<td class="font-semibold">{{ sponsor.name }}</td>
|
||||
<td data-label="{% trans 'URL' %}">
|
||||
{% if sponsor.url %}
|
||||
<a class="link link-hover" href="{{ sponsor.url }}" target="_blank" rel="noopener noreferrer">{{ sponsor.url }}</a>
|
||||
{% else %}
|
||||
<span class="opacity-50">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ sponsor.start_date|date:"Y-m-d" }}</td>
|
||||
<td>{{ sponsor.end_date|date:"Y-m-d"|default:"—" }}</td>
|
||||
<td data-label="{% trans 'Start date' %}">{{ sponsor.start_date|date:"Y-m-d" }}</td>
|
||||
<td data-label="{% trans 'End date' %}">{{ 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>
|
||||
|
||||
@@ -22,27 +22,30 @@
|
||||
{% endfor %}
|
||||
|
||||
{% comment %}
|
||||
Deliberately not wrapped in the usual overflow-x-auto: that makes a
|
||||
scroll container, which clips the member picker's absolutely
|
||||
positioned dropdown for every row but the first couple.
|
||||
overflow-x-auto is safe here: the member picker's dropdown
|
||||
(searchable-select.js) is position: fixed, computed from the input's
|
||||
own screen position rather than document flow, so a scrolling
|
||||
ancestor around the table no longer clips it.
|
||||
{% endcomment %}
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Member" %}</th>
|
||||
<th>{% trans "Position" %}</th>
|
||||
<th>{% trans "Jersey #" %}</th>
|
||||
<th class="text-center">{% trans "Captain" %}</th>
|
||||
<th class="text-center">{% trans "Alternate" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="bulk-add-rows" data-prefix="{{ formset.prefix }}">
|
||||
{% for form in formset %}
|
||||
{% include "management/_team_bulk_add_row.html" with form=form %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Member" %}</th>
|
||||
<th>{% trans "Position" %}</th>
|
||||
<th>{% trans "Jersey #" %}</th>
|
||||
<th class="text-center">{% trans "Captain" %}</th>
|
||||
<th class="text-center">{% trans "Alternate" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="bulk-add-rows" data-prefix="{{ formset.prefix }}">
|
||||
{% for form in formset %}
|
||||
{% include "management/_team_bulk_add_row.html" with form=form %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<template id="bulk-add-row-template">
|
||||
{% include "management/_team_bulk_add_row.html" with form=formset.empty_form %}
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
|
||||
{% block heading %}{{ team.name }}{% endblock heading %}
|
||||
{% block subheading %}
|
||||
<span class="flex flex-row gap-2 items-center">
|
||||
{# The raw id is a reference for support/debugging, not something a coach glancing at their phone needs -- hidden below `sm` rather than left to wrap awkwardly inside the pill. #}
|
||||
<span class="flex flex-row items-center gap-2">
|
||||
<span>{{ team.short_name }}</span>
|
||||
<span>·</span>
|
||||
<span class="badge-sm badge badge-outline">{% lucide "bookmark" size=14 %} {{ team.pk }}</span>
|
||||
<span class="hidden sm:inline">·</span>
|
||||
<span class="badge badge-outline badge-sm hidden sm:inline-flex">{% lucide "bookmark" size=14 %} {{ team.pk }}</span>
|
||||
</span>
|
||||
{% endblock subheading %}
|
||||
|
||||
@@ -18,7 +19,7 @@
|
||||
|
||||
{% block panel %}
|
||||
<form method="get" class="mb-4">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<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>
|
||||
@@ -123,40 +124,36 @@
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-base">{% lucide "user-x" size=18 %} {% trans "No-shows" %}</h2>
|
||||
<!-- <p class="text-sm opacity-70">{% trans "Said they'd attend, but were checked in as absent." %}</p> -->
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
{% for entry in no_shows %}
|
||||
<tr>
|
||||
<td>{{ entry.member }}</td>
|
||||
<td class="opacity-70">{{ entry.event.title }}</td>
|
||||
<td class="whitespace-nowrap text-right opacity-60">{{ entry.event.start|date:"j M" }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="3" class="text-center opacity-60">{% trans "None recorded." %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<ul class="divide-y divide-base-200">
|
||||
{% for entry in no_shows %}
|
||||
<li class="flex flex-col gap-1 py-2 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
|
||||
<div class="min-w-0">
|
||||
<span class="font-semibold">{{ entry.member }}</span>
|
||||
<span class="text-sm opacity-70">· {{ entry.event.title }}</span>
|
||||
</div>
|
||||
<div class="shrink-0 whitespace-nowrap text-sm opacity-60">{{ entry.event.start|date:"j M" }}</div>
|
||||
</li>
|
||||
{% empty %}
|
||||
<li class="py-2 text-center text-sm opacity-60">{% trans "None recorded." %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<h2 class="card-title text-base">{% trans "Roster" %}</h2>
|
||||
{% if can_manage %}
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a class="btn btn-outline btn-sm gap-2" href="{% url 'management:team_bulk_add' team.pk selected_season.pk %}">{% lucide "users" size=14 %} {% trans "Add multiple" %}</a>
|
||||
<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>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Member" %}</th>
|
||||
@@ -169,16 +166,16 @@
|
||||
<tbody>
|
||||
{% for membership in roster %}
|
||||
<tr>
|
||||
<td>{{ membership.member }}</td>
|
||||
<td>{{ membership.position }}</td>
|
||||
<td>{{ membership.jersey_number|default:"—" }}</td>
|
||||
<td class="font-semibold">{{ membership.member }}</td>
|
||||
<td data-label="{% trans 'Position' %}">{{ membership.position }}</td>
|
||||
<td data-label="{% trans 'Jersey #' %}">{{ membership.jersey_number|default:"—" }}</td>
|
||||
<td>
|
||||
{% if membership.is_captain %}<span class="badge badge-neutral badge-sm">{% trans "Captain" %}</span>{% endif %}
|
||||
{% if membership.is_alternate_captain %}<span class="badge badge-neutral badge-sm">{% trans "Alternate captain" %}</span>{% endif %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{% if can_manage %}
|
||||
<div class="flex justify-end gap-1">
|
||||
<div class="flex flex-wrap justify-end gap-1">
|
||||
<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>
|
||||
@@ -198,10 +195,10 @@
|
||||
|
||||
<div class="card bg-base-100 shadow mt-4">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<h2 class="card-title text-base">{% trans "Staff" %}</h2>
|
||||
{% if can_manage %}
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a class="btn btn-outline btn-sm gap-2" href="{% url 'management:team_bulk_add' team.pk selected_season.pk %}">{% lucide "users" size=14 %} {% trans "Add multiple" %}</a>
|
||||
<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>
|
||||
</div>
|
||||
@@ -263,10 +260,10 @@
|
||||
|
||||
<div class="card bg-base-100 shadow mt-4">
|
||||
<div class="card-body">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-wrap items-center justify-between gap-2">
|
||||
<h2 class="card-title text-base">{% trans "Team photo" %}</h2>
|
||||
{% if can_manage %}
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-wrap 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()">
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="card bg-base-100 shadow">
|
||||
<div class="card-body">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<table class="table table-cards">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Name" %}</th>
|
||||
@@ -26,10 +26,10 @@
|
||||
<tbody>
|
||||
{% for team in teams %}
|
||||
<tr>
|
||||
<td><a class="link link-hover" href="{% url 'management:team_detail' team.pk %}">{{ team.name }}</a></td>
|
||||
<td>{{ team.short_name }}</td>
|
||||
<td>{{ team.player_count }}</td>
|
||||
<td>{{ team.staff_count }}</td>
|
||||
<td><a class="link link-hover font-semibold" href="{% url 'management:team_detail' team.pk %}">{{ team.name }}</a></td>
|
||||
<td data-label="{% trans 'Short name' %}">{{ team.short_name }}</td>
|
||||
<td data-label="{% trans 'Players' %}">{{ team.player_count }}</td>
|
||||
<td data-label="{% trans 'Staff' %}">{{ team.staff_count }}</td>
|
||||
<td class="text-right">
|
||||
{% if is_club_admin %}
|
||||
<div class="flex justify-end gap-1">
|
||||
|
||||
Reference in New Issue
Block a user