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:
2026-08-16 09:55:08 +02:00
parent 5d3dbebd77
commit 9f4a0ea687
35 changed files with 674 additions and 699 deletions

View File

@@ -210,3 +210,59 @@
backface-visibility: hidden; backface-visibility: hidden;
transform: translateZ(0); transform: translateZ(0);
} }
/* Below `md`, a `.table-cards` table reads as a stack of cards instead of a
horizontally-scrolling grid: each row becomes a bordered block, each cell
becomes a label/value line pulling its label from `data-label` (set on the
<td> in the template -- there's no way to read the matching <th> text from
pure CSS). A <td> with no `data-label` renders as plain full-width content
instead of a label/value row -- for an image cell, a heading-style link, or
an actions row that already lays itself out. At `md` and up this reverts to
an ordinary table with nothing left over from the mobile styling. */
@media (width < 48rem) {
.table-cards, .table-cards :is(thead, tbody, tr, th, td) {
display: block;
width: 100%;
}
.table-cards thead {
display: none;
}
.table-cards tbody tr {
border: 1px solid var(--color-base-300);
border-radius: var(--radius-box);
padding: 0 0.75rem;
}
.table-cards tbody tr + tr {
margin-top: 0.75rem;
}
.table-cards td {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
padding: 0.6rem 0;
border-bottom: 1px solid var(--color-base-200);
}
.table-cards td:last-child {
border-bottom: none;
}
.table-cards td[data-label]:before {
content: attr(data-label);
flex-shrink: 0;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.02em;
text-transform: uppercase;
opacity: 0.5;
}
.table-cards td:not([data-label]) {
justify-content: flex-start;
}
}

View File

@@ -5,6 +5,15 @@
{% block panel_title %}Control panel{% endblock panel_title %} · RosterChief {% block panel_title %}Control panel{% endblock panel_title %} · RosterChief
{% endblock title %} {% endblock title %}
{% block nav_toggle %}
<button class="btn btn-ghost btn-square lg:hidden" type="button" onclick="document.getElementById('mobile_nav_modal').showModal()" aria-label="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 %} {% block menu %}
{% comment %} {% comment %}
Outside <main>, so it never scrolls with the content. Its own overflow-y-auto is for Outside <main>, so it never scrolls with the content. Its own overflow-y-auto is for
@@ -18,6 +27,38 @@
{% endblock menu %} {% endblock menu %}
{% block main %} {% block main %}
{# 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">Menu</span>
<form method="dialog">
<button class="btn btn-ghost btn-square btn-sm" aria-label="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 %} Light theme</span>
<span data-theme-icon="dark" class="hidden items-center gap-3">{% lucide "moon" size=16 %} Dark theme</span>
<span data-theme-icon="auto" class="hidden items-center gap-3">{% lucide "sun-moon" size=16 %} Auto theme</span>
</button>
</li>
{% if has_management_access %}
<li><a href="{% url "management:home" %}">{% lucide "layout-dashboard" size=16 %} Management</a></li>
{% endif %}
{% if user.is_superuser %}
<li><a href="{% url "admin:index" %}">{% lucide "shield-cog" size=16 %} Django admin</a></li>
{% endif %}
<li class="menu-title">Navigation</li>
{% include "controlpanel/_nav_items.html" %}
</ul>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
{% if maintenance_on %} {% if maintenance_on %}
<div class="alert alert-error mb-6"> <div class="alert alert-error mb-6">
{% lucide "wrench" size=20 %} {% lucide "wrench" size=20 %}
@@ -28,25 +69,22 @@
<a class="btn btn-sm gap-2 btn-error btn-soft" href="{% url 'controlpanel:features' %}">{% lucide "unlock" size=16 %} Reopen platform</a> <a class="btn btn-sm gap-2 btn-error btn-soft" href="{% url 'controlpanel:features' %}">{% lucide "unlock" size=16 %} Reopen platform</a>
</div> </div>
{% endif %} {% endif %}
<div class="mb-6 flex flex-wrap flex-row items-center justify-between gap-3"> <div class="mb-6 flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between">
{% block logo %}{% endblock logo %} <div class="flex flex-row items-center gap-3">
{% block logo %}{% endblock logo %}
<div class="flex flex-col gap-2 grow"> <div class="flex flex-col gap-2 grow">
<h1 class="text-3xl font-bold"> <h1 class="text-3xl font-bold">
{% block heading %}Control panel{% endblock heading %} {% block heading %}Control panel{% endblock heading %}
</h1> </h1>
<span class="text-sm text-base-content/50">{% block subheading %}{% endblock subheading %}</span> <span class="text-sm text-base-content/50">{% block subheading %}{% endblock subheading %}</span>
</div>
</div> </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 %} {% block actions %}{% endblock actions %}
</div> </div>
</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 "controlpanel/_nav_items.html" %}
</ul>
{% block panel %}{% endblock panel %} {% block panel %}{% endblock panel %}
{% endblock main %} {% endblock main %}

View File

@@ -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. leaves it unset, since staying on the family page is already the right place there.
{% endcomment %} {% endcomment %}
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th>{% trans "Last name" %}</th> <th>{% trans "Name" %}</th>
<th>{% trans "First name" %}</th>
<th>{% trans "Email" %}</th> <th>{% trans "Email" %}</th>
<th>{% trans "Type" %}</th> <th>{% trans "Type" %}</th>
<th></th> <th></th>
@@ -23,10 +22,9 @@ leaves it unset, since staying on the family page is already the right place the
<tbody> <tbody>
{% for person in group.all %} {% for person in group.all %}
<tr> <tr>
<td><a class="link link-hover" href="{% url 'management:member_detail' person.pk %}">{{ person.last_name }}</a></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>{{ person.first_name }}</td> <td data-label="{% trans 'Email' %}">{{ person.contact_email|default:"-" }}</td>
<td>{{ person.contact_email|default:"-" }}</td> <td data-label="{% trans 'Type' %}">
<td>
{% if is_club_admin %} {% if is_club_admin %}
<form method="post" action="{% url 'management:family_membership_role_update' group.family.pk person.pk %}"> <form method="post" action="{% url 'management:family_membership_role_update' group.family.pk person.pk %}">
{% csrf_token %} {% csrf_token %}
@@ -43,7 +41,7 @@ leaves it unset, since staying on the family page is already the right place the
</td> </td>
<td class="text-right"> <td class="text-right">
{% if is_club_admin %} {% 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 %} {% 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' %}"> <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" %} {% lucide "key-round" size=14 %} {% trans "Grant login" %}

View File

@@ -6,7 +6,7 @@
can_manage_referees, and an optional next_url to return to after a POST can_manage_referees, and an optional next_url to return to after a POST
(defaults to the event detail page when blank). (defaults to the event detail page when blank).
{% endcomment %} {% 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"> <h2 class="card-title text-base">
{% lucide "flag" size=18 %} {% trans "Referees" %} {% lucide "flag" size=18 %} {% trans "Referees" %}
<span class="badge badge-sm {% if referees_full %}badge-neutral{% else %}badge-outline{% endif %}"> <span class="badge badge-sm {% if referees_full %}badge-neutral{% else %}badge-outline{% endif %}">

View File

@@ -11,6 +11,15 @@
{% block section_title %}{% trans "Management" %}{% endblock section_title %} {% block section_title %}{% trans "Management" %}{% endblock section_title %}
{% endblock head_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 %} {% block menu %}
<aside class="hidden w-64 shrink-0 overflow-y-auto border-r border-base-300 bg-base-100 lg:block"> <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"> <ul class="menu w-full gap-1 p-3 mt-4">
@@ -20,7 +29,40 @@
{% endblock menu %} {% endblock menu %}
{% block main %} {% 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"> <div class="flex flex-col gap-2 grow">
<h1 class="text-3xl font-bold"> <h1 class="text-3xl font-bold">
{% block heading %}{% trans "Management" %}{% endblock heading %} {% block heading %}{% trans "Management" %}{% endblock heading %}
@@ -28,16 +70,11 @@
<span class="text-sm text-base-content/50">{% block subheading %}{% endblock subheading %}</span> <span class="text-sm text-base-content/50">{% block subheading %}{% endblock subheading %}</span>
</div> </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 %} {% block actions %}{% endblock actions %}
</div> </div>
</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 %} {% comment %}
A final billing notice follows the admin onto every management page -- but only at 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 error level (inside 7 days of archiving, or already past it). Shown from the moment

View File

@@ -81,7 +81,7 @@
<div class="card bg-base-100 shadow"> <div class="card bg-base-100 shadow">
<div class="card-body"> <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> <h2 class="card-title text-base">{% lucide "user-check" size=18 %} {% trans "RSVPs" %}</h2>
{% if has_attendance_rows %} {% if has_attendance_rows %}
<button class="btn btn-outline btn-sm gap-2" type="button" onclick="document.getElementById('rsvp_modal').showModal()"> <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" %} {% if event.kind == "game" %}
<div class="mb-6 card bg-base-100 shadow"> <div class="mb-6 card bg-base-100 shadow">
<div class="card-body"> <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"> <h2 class="card-title text-base">
{% lucide "trophy" size=18 %} {% trans "Game" %} {% 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 %} {% if event.is_live %}<span class="badge badge-error gap-1 animate-pulse">{% lucide "circle" size=10 %} {% trans "Live" %}</span>{% endif %}

View File

@@ -38,7 +38,7 @@
<div class="card bg-base-100 shadow"> <div class="card bg-base-100 shadow">
<div class="card-body"> <div class="card-body">
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th>{% trans "Title" %}</th> <th>{% trans "Title" %}</th>
@@ -52,14 +52,14 @@
<tbody> <tbody>
{% for event in events %} {% for event in events %}
<tr> <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> <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 %} {% if event.series_id %}<span class="tooltip" data-tip="{% trans 'Part of a series' %}">{% lucide "repeat" size=12 %}</span>{% endif %}
</td> </td>
<td>{{ event.get_kind_display }}</td> <td data-label="{% trans 'Kind' %}">{{ event.get_kind_display }}</td>
<td>{{ event.start|date:"j M Y H:i" }}</td> <td data-label="{% trans 'When' %}">{{ 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 data-label="{% trans 'Teams' %}">{% 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 'Location' %}">{{ event.location.name|default:"—" }}</td>
<td class="text-right"> <td class="text-right">
{% if event.can_manage %} {% if event.can_manage %}
<div class="flex justify-end gap-1"> <div class="flex justify-end gap-1">

View File

@@ -67,7 +67,7 @@
<div class="card-body"> <div class="card-body">
<h2 class="card-title text-base">{% lucide "calendar" size=18 %} {% trans "Occurrences" %}</h2> <h2 class="card-title text-base">{% lucide "calendar" size=18 %} {% trans "Occurrences" %}</h2>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th>{% trans "When" %}</th> <th>{% trans "When" %}</th>
@@ -77,8 +77,8 @@
<tbody> <tbody>
{% for occurrence in occurrences %} {% for occurrence in occurrences %}
<tr> <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 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> <td data-label="{% trans 'Status' %}">
{% if occurrence.cancelled %} {% if occurrence.cancelled %}
<span class="badge badge-outline gap-1">{% lucide "ban" size=12 %} {% trans "Cancelled" %}</span> <span class="badge badge-outline gap-1">{% lucide "ban" size=12 %} {% trans "Cancelled" %}</span>
{% elif occurrence.detached %} {% elif occurrence.detached %}

View File

@@ -25,7 +25,7 @@
<div class="card bg-base-100 shadow"> <div class="card bg-base-100 shadow">
<div class="card-body"> <div class="card-body">
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th>{% trans "Family" %}</th> <th>{% trans "Family" %}</th>
@@ -38,14 +38,14 @@
{% for family in families %} {% for family in families %}
<tr> <tr>
<td><a class="link link-hover font-semibold" href="{% url 'management:family_detail' family.pk %}">{{ family }}</a></td> <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 %} {% 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 %} <a class="link link-hover" href="{% url 'management:member_detail' guardian.pk %}">{{ guardian }}</a>{% if not forloop.last %}, {% endif %}
{% empty %} {% empty %}
<span class="opacity-40">-</span> <span class="opacity-40">-</span>
{% endfor %} {% endfor %}
</td> </td>
<td> <td data-label="{% trans 'Children' %}">
{% for child in family.children_display %} {% 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 %} <a class="link link-hover" href="{% url 'management:member_detail' child.pk %}">{{ child }}</a>{% if not forloop.last %}, {% endif %}
{% empty %} {% empty %}

View File

@@ -18,23 +18,26 @@
{% endfor %} {% endfor %}
{% comment %} {% comment %}
Deliberately not wrapped in the usual overflow-x-auto: that makes a overflow-x-auto is safe here: the member picker's dropdown
scroll container, which clips the member picker's absolutely (searchable-select.js) is position: fixed, computed from the input's
positioned dropdown for every row but the first couple. own screen position rather than document flow, so a scrolling
ancestor around the table no longer clips it.
{% endcomment %} {% endcomment %}
<table class="table w-full"> <div class="overflow-x-auto">
<thead> <table class="table w-full">
<tr> <thead>
<th>{% trans "Member" %}</th> <tr>
<th></th> <th>{% trans "Member" %}</th>
</tr> <th></th>
</thead> </tr>
<tbody id="bulk-add-rows" data-prefix="{{ formset.prefix }}"> </thead>
{% for form in formset %} <tbody id="bulk-add-rows" data-prefix="{{ formset.prefix }}">
{% include "management/_group_bulk_add_row.html" with form=form %} {% for form in formset %}
{% endfor %} {% include "management/_group_bulk_add_row.html" with form=form %}
</tbody> {% endfor %}
</table> </tbody>
</table>
</div>
<template id="bulk-add-row-template"> <template id="bulk-add-row-template">
{% include "management/_group_bulk_add_row.html" with form=formset.empty_form %} {% include "management/_group_bulk_add_row.html" with form=formset.empty_form %}

View File

@@ -10,12 +10,12 @@
{% block panel %} {% block panel %}
<div class="card bg-base-100 shadow"> <div class="card bg-base-100 shadow">
<div class="card-body"> <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> <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> <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>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th>{% trans "Member" %}</th> <th>{% trans "Member" %}</th>
@@ -25,7 +25,7 @@
<tbody> <tbody>
{% for membership in memberships %} {% for membership in memberships %}
<tr> <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"> <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> <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> </td>

View File

@@ -12,7 +12,7 @@
<div class="card bg-base-100 shadow"> <div class="card bg-base-100 shadow">
<div class="card-body"> <div class="card-body">
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th>{% trans "Name" %}</th> <th>{% trans "Name" %}</th>
@@ -23,8 +23,8 @@
<tbody> <tbody>
{% for group in groups %} {% for group in groups %}
<tr> <tr>
<td><a class="link link-hover" href="{% url 'management:group_detail' group.pk %}">{{ group.name }}</a></td> <td><a class="link link-hover font-semibold" href="{% url 'management:group_detail' group.pk %}">{{ group.name }}</a></td>
<td>{{ group.member_count }}</td> <td data-label="{% trans 'Members' %}">{{ group.member_count }}</td>
<td class="text-right"> <td class="text-right">
<div class="flex justify-end gap-1"> <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> <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>

View File

@@ -96,53 +96,45 @@
<div class="mb-6 grid gap-4 lg:grid-cols-2"> <div class="mb-6 grid gap-4 lg:grid-cols-2">
<div class="card bg-base-100 shadow"> <div class="card bg-base-100 shadow">
<div class="card-body"> <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> <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> <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>
<div class="overflow-x-auto"> <ul class="divide-y divide-base-200">
<table class="table"> {% for event in upcoming_events %}
<tbody> <li class="flex flex-col gap-1 py-2 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
{% for event in upcoming_events %} <div class="min-w-0">
<tr> <div class="truncate font-semibold">{{ event.title }}</div>
<td class="whitespace-nowrap">{{ event.start|date:"D j M, H:i" }}</td> <div class="truncate text-xs opacity-60">
<td>{{ event.title }}</td> {{ event.get_kind_display }}
<td>{{ event.get_kind_display }}</td> {% for team in event.teams.all %}&middot; {{ team.short_name }}{% if not forloop.last %}, {% endif %}{% endfor %}
<td>{% for team in event.teams.all %}{{ team.short_name }}{% if not forloop.last %}, {% endif %}{% empty %}—{% endfor %}</td> </div>
</tr> </div>
{% empty %} <div class="shrink-0 whitespace-nowrap text-sm opacity-70">{{ event.start|date:"D j M, H:i" }}</div>
<tr> </li>
<td class="text-center opacity-60">{% trans "Nothing scheduled." %}</td> {% empty %}
</tr> <li class="py-2 text-center text-sm opacity-60">{% trans "Nothing scheduled." %}</li>
{% endfor %} {% endfor %}
</tbody> </ul>
</table>
</div>
</div> </div>
</div> </div>
<div class="card bg-base-100 shadow"> <div class="card bg-base-100 shadow">
<div class="card-body"> <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> <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> <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>
<div class="overflow-x-auto"> <ul class="divide-y divide-base-200">
<table class="table"> {% for news_item in published_news %}
<tbody> <li class="flex flex-col gap-1 py-2 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
{% for news_item in published_news %} <a class="link link-hover truncate font-semibold" href="{% url 'management:news_detail' news_item.pk %}">{{ news_item.title }}</a>
<tr> <span class="shrink-0 whitespace-nowrap text-sm opacity-70">{{ news_item.published_at|date:"D j M" }}</span>
<td class="whitespace-nowrap">{{ news_item.published_at|date:"D j M" }}</td> </li>
<td><a class="link link-hover" href="{% url 'management:news_detail' news_item.pk %}">{{ news_item.title }}</a></td> {% empty %}
</tr> <li class="py-2 text-center text-sm opacity-60">{% trans "Nothing published yet." %}</li>
{% empty %} {% endfor %}
<tr> </ul>
<td class="text-center opacity-60">{% trans "Nothing published yet." %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -11,7 +11,7 @@
<div class="card bg-base-100 shadow"> <div class="card bg-base-100 shadow">
<div class="card-body"> <div class="card-body">
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th>{% trans "Name" %}</th> <th>{% trans "Name" %}</th>
@@ -24,13 +24,13 @@
<tbody> <tbody>
{% for location in locations %} {% for location in locations %}
<tr> <tr>
<td> <td class="font-semibold">
{{ location.name }} {{ location.name }}
{% if location.is_home %}<span class="badge badge-sm badge-primary ml-1">{% trans "Home" %}</span>{% endif %} {% if location.is_home %}<span class="badge badge-sm badge-primary ml-1">{% trans "Home" %}</span>{% endif %}
</td> </td>
<td>{{ location.address }}</td> <td data-label="{% trans 'Address' %}">{{ location.address }}</td>
<td>{{ location.city }}</td> <td data-label="{% trans 'City' %}">{{ location.city }}</td>
<td>{{ location.country }}</td> <td data-label="{% trans 'Country' %}">{{ location.country }}</td>
<td class="text-right"> <td class="text-right">
<div class="flex justify-end gap-1"> <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> <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>

View File

@@ -119,7 +119,7 @@
<div class="divider"></div> <div class="divider"></div>
<h3 class="text-sm font-semibold opacity-70">{% trans "Season history" %}</h3> <h3 class="text-sm font-semibold opacity-70">{% trans "Season history" %}</h3>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th>{% trans "Season" %}</th> <th>{% trans "Season" %}</th>
@@ -131,10 +131,10 @@
<tbody> <tbody>
{% for membership in membership_history %} {% for membership in membership_history %}
<tr> <tr>
<td>{{ membership.season.start_date|date:"Y" }} - {{ membership.season.end_date|date:"Y" }}</td> <td class="font-semibold">{{ membership.season.start_date|date:"Y" }} - {{ membership.season.end_date|date:"Y" }}</td>
<td>{{ membership.get_status_display }}</td> <td data-label="{% trans 'Status' %}">{{ membership.get_status_display }}</td>
<td>{{ membership.get_fee_status_display }}</td> <td data-label="{% trans 'Fee status' %}">{{ membership.get_fee_status_display }}</td>
<td>{{ membership.license|default:"-" }}</td> <td data-label="{% trans 'License' %}">{{ membership.license|default:"-" }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@@ -148,7 +148,7 @@
{% if referee_profile or is_club_admin %} {% if referee_profile or is_club_admin %}
<div class="card bg-base-100 shadow mt-4"> <div class="card bg-base-100 shadow mt-4">
<div class="card-body"> <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> <h2 class="card-title text-base">{% lucide "flag" size=18 %} {% trans "Referee eligibility" %}</h2>
{% if is_club_admin %} {% 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> <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 %} {% for family_group in family_groups %}
<div class="card bg-base-100 shadow mt-4"> <div class="card bg-base-100 shadow mt-4">
<div class="card-body"> <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"> <h2 class="card-title text-base">
{% lucide "users" size=18 %} {% lucide "users" size=18 %}
<a class="link link-hover" href="{% url 'management:family_detail' family_group.family.pk %}">{{ family_group.family }}</a> <a class="link link-hover" href="{% url 'management:family_detail' family_group.family.pk %}">{{ family_group.family }}</a>
</h2> </h2>
{% if is_club_admin %} {% 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_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-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> <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 %} {% if is_club_admin %}
<div class="card bg-base-100 shadow mt-4"> <div class="card bg-base-100 shadow mt-4">
<div class="card-body"> <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> <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> <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> </div>

View File

@@ -4,24 +4,25 @@
{% block heading %}{% trans "Members" %}{% endblock heading %} {% block heading %}{% trans "Members" %}{% endblock heading %}
{% block actions %} {% 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 %} {% 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-sm btn-outline btn-info gap-2" href="{% url 'management:member_import' %}">{% lucide "upload" size=14 %} {% 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-sm btn-outline gap-2" href="{% url 'management:family_create' %}">{% lucide "users" size=14 %} {% 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 gap-2" href="{% url 'management:member_create' %}">{% lucide "user-plus" size=14 %} {% trans "Add member" %}</a>
{% endif %} {% endif %}
{% endblock actions %} {% endblock actions %}
{% block panel %} {% block panel %}
<form method="get" class="mb-2 flex flex-wrap items-center gap-2"> <form method="get" class="mb-2 flex items-center gap-2">
<label class="input"> <label class="input grow sm:grow-0">
<span class="opacity-50">{% lucide "search" size=16 %}</span> <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> </label>
{% if selected_kind != "member" %}<input type="hidden" name="kind" value="{{ selected_kind }}">{% endif %} {% 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 %} {% 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 %} {% endif %}
</form> </form>
@@ -33,23 +34,23 @@
with nothing on the page explaining why. ?kind= makes the exclusion an with nothing on the page explaining why. ?kind= makes the exclusion an
explicit, reversible choice instead. explicit, reversible choice instead.
{% endcomment %} {% endcomment %}
<div class="mb-4 flex flex-wrap items-center gap-2"> <div class="mb-4 flex flex-col gap-2">
<div class="join"> <div class="join w-full">
<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=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 {% if selected_kind == "guardian" %}btn-primary{% endif %}">{% trans "Guardians" %}</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 {% if selected_kind == "both" %}btn-primary{% endif %}">{% trans "Both" %}</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> </div>
<span class="text-xs opacity-60">{% trans "Guardians are parents linked only through a child -- no fee, not counted as a member." %}</span> <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>
<div class="card bg-base-100 shadow"> <div class="card bg-base-100 shadow">
<div class="card-body"> <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"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th>{% trans "Last name" %}</th> <th>{% trans "Name" %}</th>
<th>{% trans "First name" %}</th>
<th>{% trans "Email" %}</th> <th>{% trans "Email" %}</th>
<th>{% trans "Family" %}</th> <th>{% trans "Family" %}</th>
<th>{% trans "Status" %}</th> <th>{% trans "Status" %}</th>
@@ -59,10 +60,9 @@
<tbody> <tbody>
{% for member in members %} {% for member in members %}
<tr> <tr>
<td><a class="link link-hover" href="{% url 'management:member_detail' member.pk %}">{{ member.last_name }}</a></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>{{ member.first_name }}</td> <td data-label="{% trans 'Email' %}">{{ member.contact_email|default:"-" }}</td>
<td>{{ member.contact_email|default:"-" }}</td> <td data-label="{% trans 'Family' %}">
<td>
{% if member.family_memberships_display %} {% if member.family_memberships_display %}
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
{% for fm in member.family_memberships_display %} {% for fm in member.family_memberships_display %}
@@ -75,7 +75,7 @@
<span class="opacity-40">-</span> <span class="opacity-40">-</span>
{% endif %} {% endif %}
</td> </td>
<td> <td data-label="{% trans 'Status' %}">
{% if member.current_membership %} {% if member.current_membership %}
<span class="badge badge-sm <span class="badge badge-sm
{% if member.current_membership.status == "active" %}badge-success {% if member.current_membership.status == "active" %}badge-success
@@ -90,7 +90,7 @@
</td> </td>
<td class="text-right"> <td class="text-right">
{% if is_club_admin %} {% 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> <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> <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> </div>
@@ -99,7 +99,7 @@
</tr> </tr>
{% empty %} {% empty %}
<tr> <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> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@@ -59,19 +59,19 @@
{% endif %} {% endif %}
<form method="get" class="mb-4"> <form method="get" class="mb-4">
<div class="flex flex-row items-center gap-2"> <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"> <label class="input w-full lg:w-auto">
<span class="opacity-50">{% lucide "search" size=16 %}</span> <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> </label>
<select name="season" class="select select-bordered"> <select name="season" class="select select-bordered w-full lg:w-auto">
{% for season in seasons %} {% 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> <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 %} {% endfor %}
</select> </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> <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 %} {% for value, label in fee_status_choices %}
<option value="{{ value }}" {% if value == selected_fee_status %}selected{% endif %}>{{ label|capfirst }}</option> <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> <option value="all" {% if selected_fee_status == "all" %}selected{% endif %}>{% trans "All" %}</option>
</select> </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> <option value="all" {% if selected_status == "all" %}selected{% endif %}>{% trans "All statuses" %}</option>
{% for value, label in status_choices %} {% for value, label in status_choices %}
<option value="{{ value }}" {% if value == selected_status %}selected{% endif %}>{{ label|capfirst }}</option> <option value="{{ value }}" {% if value == selected_status %}selected{% endif %}>{{ label|capfirst }}</option>
{% endfor %} {% endfor %}
</select> </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> <option value="" {% if not selected_team %}selected{% endif %}>{% trans "All teams" %}</option>
{% for team in teams %} {% for team in teams %}
<option value="{{ team.pk }}" {% if team.pk|stringformat:"s" == selected_team %}selected{% endif %}>{{ team }}</option> <option value="{{ team.pk }}" {% if team.pk|stringformat:"s" == selected_team %}selected{% endif %}>{{ team }}</option>
{% endfor %} {% endfor %}
</select> </select>
<button class="btn btn-outline gap-2" type="submit">{% lucide "filter" size=16 %} {% trans "Filter" %}</button> <div class="col-span-full flex gap-2 lg:col-span-1 lg:contents">
<a class="btn gap-2" href="{% url 'management:membership_list' %}">{% lucide "x" size=16 %} {% trans "Reset" %}</a> <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> </div>
</form> </form>
@@ -104,20 +106,20 @@
<div class="card bg-base-100 shadow"> <div class="card bg-base-100 shadow">
<div class="card-body"> <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"> <span class="text-sm opacity-70 font-semibold">
{% blocktrans count counter=memberships|length %}{{ counter }} membership{% plural %}{{ counter }} memberships{% endblocktrans %} {% blocktrans count counter=memberships|length %}{{ counter }} membership{% plural %}{{ counter }} memberships{% endblocktrans %}
</span> </span>
<button class="btn btn-success btn-sm gap-2" type="submit">{% lucide "circle-check" size=14 %} {% trans "Mark selected as paid" %}</button> <button class="btn btn-success btn-sm gap-2" type="submit">{% lucide "circle-check" size=14 %} {% trans "Mark selected as paid" %}</button>
</div> </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"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th><input type="checkbox" class="checkbox" id="select-all-memberships"></th> <th><input type="checkbox" class="checkbox" id="select-all-memberships"></th>
<th>{% trans "Last name" %}</th> <th>{% trans "Name" %}</th>
<th>{% trans "First name" %}</th>
<th>{% trans "Email" %}</th> <th>{% trans "Email" %}</th>
<th>{% trans "Family" %}</th> <th>{% trans "Family" %}</th>
<th>{% trans "Status" %}</th> <th>{% trans "Status" %}</th>
@@ -132,10 +134,9 @@
{% for membership in memberships %} {% for membership in memberships %}
<tr> <tr>
<td><input type="checkbox" class="checkbox membership-row-checkbox" name="membership_ids" value="{{ membership.pk }}"></td> <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><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>{{ membership.member.first_name }}</td> <td data-label="{% trans 'Email' %}">{{ membership.member.contact_email|default:"-" }}</td>
<td>{{ membership.member.contact_email|default:"-" }}</td> <td data-label="{% trans 'Family' %}">
<td>
{% if membership.member.family_memberships_display %} {% if membership.member.family_memberships_display %}
{% for fm in membership.member.family_memberships_display %} {% for fm in membership.member.family_memberships_display %}
{{ fm.family }} {% if not forloop.last %},{% endif %} {{ fm.family }} {% if not forloop.last %},{% endif %}
@@ -144,7 +145,7 @@
<span class="opacity-40">-</span> <span class="opacity-40">-</span>
{% endif %} {% endif %}
</td> </td>
<td> <td data-label="{% trans 'Status' %}">
<span class="badge badge-sm <span class="badge badge-sm
{% if membership.status == "active" %}badge-success {% if membership.status == "active" %}badge-success
{% elif membership.status == "pending" %}badge-warning {% elif membership.status == "pending" %}badge-warning
@@ -153,7 +154,7 @@
{{ membership.get_status_display }} {{ membership.get_status_display }}
</span> </span>
</td> </td>
<td> <td data-label="{% trans 'Fee status' %}">
<span class="badge badge-sm <span class="badge badge-sm
{% if membership.fee_status == "paid" %}badge-success {% if membership.fee_status == "paid" %}badge-success
{% elif membership.fee_status == "partially_paid" %}badge-warning {% elif membership.fee_status == "partially_paid" %}badge-warning
@@ -162,17 +163,17 @@
{{ membership.get_fee_status_display }} {{ membership.get_fee_status_display }}
</span> </span>
</td> </td>
<td class="tabular-nums">&euro; {{ membership.fee_amount }}</td> <td data-label="{% trans 'Owed' %}" class="tabular-nums">&euro; {{ membership.fee_amount }}</td>
<td class="tabular-nums"> <td data-label="{% trans 'Paid' %}" class="tabular-nums">
&euro; {{ membership.amount_paid }} &euro; {{ membership.amount_paid }}
{% if membership.record_payment_form and membership.fee_amount %} {% 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> <div class="text-xs opacity-60">{% blocktrans with remaining=membership.remaining_balance_display %}{{ remaining }} left{% endblocktrans %}</div>
{% endif %} {% endif %}
</td> </td>
<td>{{ membership.license|default:"-" }}</td> <td data-label="{% trans 'License' %}">{{ membership.license|default:"-" }}</td>
<td class="text-right"> <td class="text-right">
{% if membership.record_payment_form %} {% 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()"> <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" %} {% lucide "receipt" size=12 %} {% trans "Record payment" %}
</button> </button>
@@ -185,7 +186,7 @@
</tr> </tr>
{% empty %} {% empty %}
<tr> <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> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@@ -53,7 +53,7 @@
<div class="card bg-base-100 shadow mt-4"> <div class="card bg-base-100 shadow mt-4">
<div class="card-body"> <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> <h2 class="card-title text-base">{% trans "Photos" %}</h2>
{% if can_edit %} {% 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> <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>

View File

@@ -13,7 +13,7 @@
<div class="card bg-base-100 shadow"> <div class="card bg-base-100 shadow">
<div class="card-body"> <div class="card-body">
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th>{% trans "Title" %}</th> <th>{% trans "Title" %}</th>
@@ -26,16 +26,16 @@
<tbody> <tbody>
{% for news_item in news_items %} {% for news_item in news_items %}
<tr> <tr>
<td><a class="link link-hover" href="{% url 'management:news_detail' news_item.pk %}">{{ news_item.title }}</a></td> <td><a class="link link-hover font-semibold" href="{% url 'management:news_detail' news_item.pk %}">{{ news_item.title }}</a></td>
<td> <td data-label="{% trans 'Teams' %}">
{% for team in news_item.teams.all %} {% for team in news_item.teams.all %}
<span class="badge badge-neutral badge-sm">{{ team.short_name }}</span> <span class="badge badge-neutral badge-sm">{{ team.short_name }}</span>
{% empty %} {% empty %}
<span class="opacity-60">{% trans "Club-wide" %}</span> <span class="opacity-60">{% trans "Club-wide" %}</span>
{% endfor %} {% endfor %}
</td> </td>
<td>{{ news_item.get_visibility_display }}</td> <td data-label="{% trans 'Visibility' %}">{{ news_item.get_visibility_display }}</td>
<td> <td data-label="{% trans 'Status' %}">
{% if news_item.status == "draft" %} {% if news_item.status == "draft" %}
<span class="badge badge-neutral badge-sm">{% trans "Draft" %}</span> <span class="badge badge-neutral badge-sm">{% trans "Draft" %}</span>
{% elif news_item.is_scheduled %} {% elif news_item.is_scheduled %}

View File

@@ -11,7 +11,7 @@
<div class="card bg-base-100 shadow"> <div class="card bg-base-100 shadow">
<div class="card-body"> <div class="card-body">
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
@@ -27,7 +27,7 @@
<img src="{{ opponent.logo.url }}" alt="" class="size-8 rounded object-contain"> <img src="{{ opponent.logo.url }}" alt="" class="size-8 rounded object-contain">
{% endif %} {% endif %}
</td> </td>
<td>{{ opponent.name }}</td> <td class="font-semibold">{{ opponent.name }}</td>
<td class="text-right"> <td class="text-right">
<div class="flex justify-end gap-1"> <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> <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>

View File

@@ -92,7 +92,7 @@
<div class="card-body"> <div class="card-body">
<h2 class="card-title text-base">{% lucide "history" size=18 %} {% trans "Already dealt with" %}</h2> <h2 class="card-title text-base">{% lucide "history" size=18 %} {% trans "Already dealt with" %}</h2>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th>{% trans "Parent" %}</th> <th>{% trans "Parent" %}</th>
@@ -104,9 +104,9 @@
<tbody> <tbody>
{% for claim in reviewed %} {% for claim in reviewed %}
<tr> <tr>
<td>{{ claim.parent_name }}<div class="text-xs opacity-60">{{ claim.parent_email }}</div></td> <td class="font-semibold">{{ claim.parent_name }}<div class="text-xs font-normal opacity-60">{{ claim.parent_email }}</div></td>
<td>{{ claim.claimed_child_name }}</td> <td data-label="{% trans 'Claimed' %}">{{ claim.claimed_child_name }}</td>
<td> <td data-label="{% trans 'Outcome' %}">
{% if claim.status == "approved" %} {% if claim.status == "approved" %}
<span class="badge badge-success badge-sm">{% trans "Approved" %}</span> <span class="badge badge-success badge-sm">{% trans "Approved" %}</span>
{% if claim.child %}<div class="text-xs opacity-60">{{ claim.child }}</div>{% endif %} {% 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 %} {% if claim.note %}<div class="text-xs opacity-60">{{ claim.note }}</div>{% endif %}
{% endif %} {% endif %}
</td> </td>
<td>{{ claim.reviewed_by|default:"—" }}</td> <td data-label="{% trans 'Reviewed by' %}">{{ claim.reviewed_by|default:"—" }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@@ -13,7 +13,7 @@
<div class="card bg-base-100 shadow"> <div class="card bg-base-100 shadow">
<div class="card-body"> <div class="card-body">
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th>{% trans "Ordering" %}</th> <th>{% trans "Ordering" %}</th>
@@ -27,15 +27,15 @@
<tbody> <tbody>
{% for position in positions %} {% for position in positions %}
<tr> <tr>
<td>{{ position.ordering }}</td> <td data-label="{% trans 'Ordering' %}">{{ position.ordering }}</td>
<td>{{ position.name }}</td> <td class="font-semibold">{{ position.name }}</td>
<td>{{ position.short_name }}</td> <td data-label="{% trans 'Short name' %}">{{ position.short_name }}</td>
<td> <td data-label="{% trans 'Staff position' %}">
<span class="badge badge-sm {% if position.staff_position %}badge-success{% else %}badge-neutral{% endif %}"> <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 %} {% if position.staff_position %}{% trans "Yes" %}{% else %}{% trans "No" %}{% endif %}
</span> </span>
</td> </td>
<td> <td data-label="{% trans 'Management position' %}">
<span class="badge badge-sm {% if position.management_position %}badge-success{% else %}badge-neutral{% endif %}"> <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 %} {% if position.management_position %}{% trans "Yes" %}{% else %}{% trans "No" %}{% endif %}
</span> </span>

View File

@@ -14,7 +14,7 @@
<div class="card bg-base-100 shadow"> <div class="card bg-base-100 shadow">
<div class="card-body"> <div class="card-body">
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th>{% trans "Ordering" %}</th> <th>{% trans "Ordering" %}</th>
@@ -26,9 +26,9 @@
<tbody> <tbody>
{% for level in levels %} {% for level in levels %}
<tr> <tr>
<td>{{ level.ordering }}</td> <td data-label="{% trans 'Ordering' %}">{{ level.ordering }}</td>
<td>{{ level.name }}</td> <td class="font-semibold">{{ level.name }}</td>
<td> <td data-label="{% trans 'Qualifies for' %}">
{% for team in level.teams.all %} {% for team in level.teams.all %}
<span class="badge badge-outline badge-sm">{{ team.name }}</span> <span class="badge badge-outline badge-sm">{{ team.name }}</span>
{% empty %} {% empty %}

View File

@@ -8,7 +8,7 @@
<div class="card bg-base-100 shadow"> <div class="card bg-base-100 shadow">
<div class="card-body"> <div class="card-body">
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th>{% trans "Referee" %}</th> <th>{% trans "Referee" %}</th>
@@ -21,9 +21,9 @@
<tbody> <tbody>
{% for referee in referees %} {% for referee in referees %}
<tr> <tr>
<td><a class="link link-hover" href="{% url 'management:member_detail' referee.pk %}">{{ referee }}</a></td> <td><a class="link link-hover font-semibold" href="{% url 'management:member_detail' referee.pk %}">{{ referee }}</a></td>
<td>{{ referee.referee_profile.level|default:"—" }}</td> <td data-label="{% trans 'Level' %}">{{ referee.referee_profile.level|default:"—" }}</td>
<td> <td data-label="{% trans 'Can referee for' %}">
{% if referee.referee_profile.is_eligible %} {% if referee.referee_profile.is_eligible %}
{% for team in referee.referee_profile.eligible_teams %} {% for team in referee.referee_profile.eligible_teams %}
<span class="badge badge-outline badge-sm">{{ team.name }}</span> <span class="badge badge-outline badge-sm">{{ team.name }}</span>
@@ -34,8 +34,8 @@
<span class="opacity-40"></span> <span class="opacity-40"></span>
{% endif %} {% endif %}
</td> </td>
<td>{{ referee.referee_profile.valid_until|default:"—" }}</td> <td data-label="{% trans 'Valid until' %}">{{ referee.referee_profile.valid_until|default:"—" }}</td>
<td> <td data-label="{% trans 'Status' %}">
{% if referee.referee_profile.is_eligible %} {% if referee.referee_profile.is_eligible %}
<span class="badge badge-success badge-sm">{% trans "Valid" %}</span> <span class="badge badge-success badge-sm">{% trans "Valid" %}</span>
{% elif not referee.referee_profile.level %} {% elif not referee.referee_profile.level %}

View File

@@ -59,7 +59,7 @@
<span class="opacity-60">{% trans "No referees assigned yet." %}</span> <span class="opacity-60">{% trans "No referees assigned yet." %}</span>
{% endfor %} {% endfor %}
</div> </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 %}"> <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 }} {{ game.referee_rows|length }} / {{ game.max_referees }}
</span> </span>

View File

@@ -24,7 +24,7 @@
<h2 class="card-title text-base">{{ role_label }}</h2> <h2 class="card-title text-base">{{ role_label }}</h2>
{% if description %}<p class="text-sm opacity-70">{{ description }}</p>{% endif %} {% if description %}<p class="text-sm opacity-70">{{ description }}</p>{% endif %}
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th>{% trans "Member" %}</th> <th>{% trans "Member" %}</th>
@@ -34,7 +34,7 @@
<tbody> <tbody>
{% for role in roles_for_section %} {% for role in roles_for_section %}
<tr> <tr>
<td>{{ role.member }}</td> <td class="font-semibold">{{ role.member }}</td>
<td class="text-right"> <td class="text-right">
<form method="post" action="{% url 'management:role_revoke' role.pk %}"> <form method="post" action="{% url 'management:role_revoke' role.pk %}">
{% csrf_token %} {% csrf_token %}

View File

@@ -11,7 +11,7 @@
<div class="card bg-base-100 shadow"> <div class="card bg-base-100 shadow">
<div class="card-body"> <div class="card-body">
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
@@ -30,16 +30,16 @@
<img src="{{ sponsor.logo.url }}" alt="" class="size-8 rounded object-contain"> <img src="{{ sponsor.logo.url }}" alt="" class="size-8 rounded object-contain">
{% endif %} {% endif %}
</td> </td>
<td>{{ sponsor.name }}</td> <td class="font-semibold">{{ sponsor.name }}</td>
<td> <td data-label="{% trans 'URL' %}">
{% if sponsor.url %} {% if sponsor.url %}
<a class="link link-hover" href="{{ sponsor.url }}" target="_blank" rel="noopener noreferrer">{{ sponsor.url }}</a> <a class="link link-hover" href="{{ sponsor.url }}" target="_blank" rel="noopener noreferrer">{{ sponsor.url }}</a>
{% else %} {% else %}
<span class="opacity-50">&mdash;</span> <span class="opacity-50">&mdash;</span>
{% endif %} {% endif %}
</td> </td>
<td>{{ sponsor.start_date|date:"Y-m-d" }}</td> <td data-label="{% trans 'Start date' %}">{{ sponsor.start_date|date:"Y-m-d" }}</td>
<td>{{ sponsor.end_date|date:"Y-m-d"|default:"—" }}</td> <td data-label="{% trans 'End date' %}">{{ sponsor.end_date|date:"Y-m-d"|default:"—" }}</td>
<td class="text-right"> <td class="text-right">
<div class="flex justify-end gap-1"> <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> <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>

View File

@@ -22,27 +22,30 @@
{% endfor %} {% endfor %}
{% comment %} {% comment %}
Deliberately not wrapped in the usual overflow-x-auto: that makes a overflow-x-auto is safe here: the member picker's dropdown
scroll container, which clips the member picker's absolutely (searchable-select.js) is position: fixed, computed from the input's
positioned dropdown for every row but the first couple. own screen position rather than document flow, so a scrolling
ancestor around the table no longer clips it.
{% endcomment %} {% endcomment %}
<table class="table w-full"> <div class="overflow-x-auto">
<thead> <table class="table w-full">
<tr> <thead>
<th>{% trans "Member" %}</th> <tr>
<th>{% trans "Position" %}</th> <th>{% trans "Member" %}</th>
<th>{% trans "Jersey #" %}</th> <th>{% trans "Position" %}</th>
<th class="text-center">{% trans "Captain" %}</th> <th>{% trans "Jersey #" %}</th>
<th class="text-center">{% trans "Alternate" %}</th> <th class="text-center">{% trans "Captain" %}</th>
<th></th> <th class="text-center">{% trans "Alternate" %}</th>
</tr> <th></th>
</thead> </tr>
<tbody id="bulk-add-rows" data-prefix="{{ formset.prefix }}"> </thead>
{% for form in formset %} <tbody id="bulk-add-rows" data-prefix="{{ formset.prefix }}">
{% include "management/_team_bulk_add_row.html" with form=form %} {% for form in formset %}
{% endfor %} {% include "management/_team_bulk_add_row.html" with form=form %}
</tbody> {% endfor %}
</table> </tbody>
</table>
</div>
<template id="bulk-add-row-template"> <template id="bulk-add-row-template">
{% include "management/_team_bulk_add_row.html" with form=formset.empty_form %} {% include "management/_team_bulk_add_row.html" with form=formset.empty_form %}

View File

@@ -3,10 +3,11 @@
{% block heading %}{{ team.name }}{% endblock heading %} {% block heading %}{{ team.name }}{% endblock heading %}
{% block subheading %} {% 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>{{ team.short_name }}</span>
<span>&middot;</span> <span class="hidden sm:inline">&middot;</span>
<span class="badge-sm badge badge-outline">{% lucide "bookmark" size=14 %} {{ team.pk }}</span> <span class="badge badge-outline badge-sm hidden sm:inline-flex">{% lucide "bookmark" size=14 %} {{ team.pk }}</span>
</span> </span>
{% endblock subheading %} {% endblock subheading %}
@@ -18,7 +19,7 @@
{% block panel %} {% block panel %}
<form method="get" class="mb-4"> <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"> <select name="season" class="select select-bordered">
{% for season in seasons %} {% 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> <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"> <div class="card-body">
<h2 class="card-title text-base">{% lucide "user-x" size=18 %} {% trans "No-shows" %}</h2> <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> --> <!-- <p class="text-sm opacity-70">{% trans "Said they'd attend, but were checked in as absent." %}</p> -->
<div class="overflow-x-auto"> <ul class="divide-y divide-base-200">
<table class="table"> {% for entry in no_shows %}
<tbody> <li class="flex flex-col gap-1 py-2 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
{% for entry in no_shows %} <div class="min-w-0">
<tr> <span class="font-semibold">{{ entry.member }}</span>
<td>{{ entry.member }}</td> <span class="text-sm opacity-70">&middot; {{ entry.event.title }}</span>
<td class="opacity-70">{{ entry.event.title }}</td> </div>
<td class="whitespace-nowrap text-right opacity-60">{{ entry.event.start|date:"j M" }}</td> <div class="shrink-0 whitespace-nowrap text-sm opacity-60">{{ entry.event.start|date:"j M" }}</div>
</tr> </li>
{% empty %} {% empty %}
<tr> <li class="py-2 text-center text-sm opacity-60">{% trans "None recorded." %}</li>
<td colspan="3" class="text-center opacity-60">{% trans "None recorded." %}</td> {% endfor %}
</tr> </ul>
{% endfor %}
</tbody>
</table>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="card bg-base-100 shadow"> <div class="card bg-base-100 shadow">
<div class="card-body"> <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> <h2 class="card-title text-base">{% trans "Roster" %}</h2>
{% if can_manage %} {% 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> <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> <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> </div>
{% endif %} {% endif %}
</div> </div>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th>{% trans "Member" %}</th> <th>{% trans "Member" %}</th>
@@ -169,16 +166,16 @@
<tbody> <tbody>
{% for membership in roster %} {% for membership in roster %}
<tr> <tr>
<td>{{ membership.member }}</td> <td class="font-semibold">{{ membership.member }}</td>
<td>{{ membership.position }}</td> <td data-label="{% trans 'Position' %}">{{ membership.position }}</td>
<td>{{ membership.jersey_number|default:"—" }}</td> <td data-label="{% trans 'Jersey #' %}">{{ membership.jersey_number|default:"—" }}</td>
<td> <td>
{% if membership.is_captain %}<span class="badge badge-neutral badge-sm">{% trans "Captain" %}</span>{% endif %} {% 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 %} {% if membership.is_alternate_captain %}<span class="badge badge-neutral badge-sm">{% trans "Alternate captain" %}</span>{% endif %}
</td> </td>
<td class="text-right"> <td class="text-right">
{% if can_manage %} {% 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-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> <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> </div>
@@ -198,10 +195,10 @@
<div class="card bg-base-100 shadow mt-4"> <div class="card bg-base-100 shadow mt-4">
<div class="card-body"> <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> <h2 class="card-title text-base">{% trans "Staff" %}</h2>
{% if can_manage %} {% 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> <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> <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> </div>
@@ -263,10 +260,10 @@
<div class="card bg-base-100 shadow mt-4"> <div class="card bg-base-100 shadow mt-4">
<div class="card-body"> <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> <h2 class="card-title text-base">{% trans "Team photo" %}</h2>
{% if can_manage %} {% if can_manage %}
<div class="flex gap-2"> <div class="flex flex-wrap gap-2">
{% trans "Upload photo" as upload_photo_label %} {% trans "Upload photo" as upload_photo_label %}
{% trans "Replace photo" as replace_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()"> <button class="btn btn-outline btn-sm gap-2" type="button" onclick="document.getElementById('team_photo_modal').showModal()">

View File

@@ -13,7 +13,7 @@
<div class="card bg-base-100 shadow"> <div class="card bg-base-100 shadow">
<div class="card-body"> <div class="card-body">
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table"> <table class="table table-cards">
<thead> <thead>
<tr> <tr>
<th>{% trans "Name" %}</th> <th>{% trans "Name" %}</th>
@@ -26,10 +26,10 @@
<tbody> <tbody>
{% for team in teams %} {% for team in teams %}
<tr> <tr>
<td><a class="link link-hover" href="{% url 'management:team_detail' team.pk %}">{{ team.name }}</a></td> <td><a class="link link-hover font-semibold" href="{% url 'management:team_detail' team.pk %}">{{ team.name }}</a></td>
<td>{{ team.short_name }}</td> <td data-label="{% trans 'Short name' %}">{{ team.short_name }}</td>
<td>{{ team.player_count }}</td> <td data-label="{% trans 'Players' %}">{{ team.player_count }}</td>
<td>{{ team.staff_count }}</td> <td data-label="{% trans 'Staff' %}">{{ team.staff_count }}</td>
<td class="text-right"> <td class="text-right">
{% if is_club_admin %} {% if is_club_admin %}
<div class="flex justify-end gap-1"> <div class="flex justify-end gap-1">

View File

@@ -31,14 +31,13 @@
--text-3xl--line-height: calc(2.25 / 1.875); --text-3xl--line-height: calc(2.25 / 1.875);
--text-4xl: 2.25rem; --text-4xl: 2.25rem;
--text-4xl--line-height: calc(2.5 / 2.25); --text-4xl--line-height: calc(2.5 / 2.25);
--font-weight-normal: 400;
--font-weight-medium: 500; --font-weight-medium: 500;
--font-weight-semibold: 600; --font-weight-semibold: 600;
--font-weight-bold: 700; --font-weight-bold: 700;
--tracking-wide: 0.025em; --tracking-wide: 0.025em;
--tracking-wider: 0.05em; --tracking-wider: 0.05em;
--radius-lg: 0.5rem; --radius-lg: 0.5rem;
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
--default-transition-duration: 150ms; --default-transition-duration: 150ms;
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
@@ -776,26 +775,6 @@
} }
} }
} }
.collapse-plus {
@layer daisyui.l1.l2 {
> .collapse-title:after {
position: absolute;
display: block;
height: 0.5rem;
width: 0.5rem;
@media (prefers-reduced-motion: no-preference) {
transition-property: all;
transition-duration: 300ms;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
top: 0.9rem;
inset-inline-end: 1.4rem;
--tw-content: "+";
content: var(--tw-content);
pointer-events: none;
}
}
}
.dropdown { .dropdown {
@layer daisyui.l1.l2.l3 { @layer daisyui.l1.l2.l3 {
position: relative; position: relative;
@@ -1321,21 +1300,6 @@
} }
} }
} }
.collapse-open {
@layer daisyui.l1.l2 {
grid-template-rows: max-content 1fr;
> .collapse-content {
--overflow-delay: 0.2s;
overflow: revert-layer;
content-visibility: visible;
min-height: fit-content;
padding-bottom: 1rem;
@supports not (content-visibility: visible) {
visibility: visible;
}
}
}
}
.collapse { .collapse {
visibility: collapse; visibility: collapse;
} }
@@ -1404,27 +1368,6 @@
} }
} }
} }
.toast {
@layer daisyui.l1.l2.l3 {
position: fixed;
inset-inline-start: auto;
inset-inline-end: calc(0.25rem * 4);
top: auto;
bottom: calc(0.25rem * 4);
display: flex;
flex-direction: column;
gap: calc(0.25rem * 2);
background-color: transparent;
translate: var(--toast-x, 0) var(--toast-y, 0);
width: max-content;
max-width: calc(100vw - 2rem);
& > * {
@media (prefers-reduced-motion: no-preference) {
animation: toast 0.25s ease-out;
}
}
}
}
.toggle { .toggle {
@layer daisyui.l1.l2.l3 { @layer daisyui.l1.l2.l3 {
border: var(--border) solid currentColor; border: var(--border) solid currentColor;
@@ -1877,51 +1820,6 @@
} }
} }
} }
.aura {
@layer daisyui.l1.l2.l3 {
position: relative;
display: inline-block;
--aura-padding: 0.125rem;
padding: var(--aura-padding);
border-radius: calc(var(--aura-padding) + var(--aura-radius, var(--radius-box)));
animation: aura var(--tw-duration, 6s) linear infinite;
background-image: conic-gradient(from var(--aura-angle), transparent 225deg, currentColor);
&:has( > .card, > .alert) {
--aura-radius: var(--radius-box);
}
&:has( > .btn, > .input, > .select) {
--aura-radius: var(--radius-field);
}
&:has( > .checkbox, > .toggle, > .badge) {
--aura-radius: var(--radius-selector);
}
&:before, &:after {
animation: inherit;
background-color: inherit;
background-image: inherit;
border-radius: inherit;
position: absolute;
top: calc(1 / 2 * 100%);
left: calc(1 / 2 * 100%);
z-index: 0;
display: block;
opacity: 70%;
filter: blur(0.25rem);
translate: -50% -50%;
width: 100%;
height: 100%;
content: "";
}
&:after {
opacity: 30%;
filter: blur(1rem);
}
& > * {
position: relative;
z-index: 1;
}
}
}
.steps { .steps {
@layer daisyui.l1.l2.l3 { @layer daisyui.l1.l2.l3 {
display: inline-grid; display: inline-grid;
@@ -2277,49 +2175,6 @@
white-space: nowrap; white-space: nowrap;
border-width: 0; border-width: 0;
} }
.menu-horizontal {
@layer daisyui.l1.l2 {
display: inline-flex;
flex-direction: row;
align-items: center;
& > li:not(.menu-title) > details {
& > :is(ul, menu) {
position: absolute;
margin-inline-start: 0;
margin-top: calc(0.25rem * 4);
transform-origin: top;
border-radius: var(--radius-box);
background-color: var(--color-base-100);
padding-block: calc(0.25rem * 2);
padding-inline-end: calc(0.25rem * 2);
opacity: 0%;
scale: 95%;
box-shadow: 0 1px 3px 0 oklch(0% 0 0/0.1), 0 1px 2px -1px oklch(0% 0 0/0.1);
@media (prefers-reduced-motion: no-preference) {
@starting-style {
scale: 95%;
opacity: 0;
}
animation: menu 0.2s;
transition-property: opacity, scale, display;
transition-behavior: allow-discrete;
transition-duration: 0.2s;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
}
&[open] > :is(ul, menu) {
opacity: 100%;
scale: 100%;
}
}
& > li > details > :is(ul, menu) {
&:before {
--tw-content: none;
content: var(--tw-content);
}
}
}
}
.avatar { .avatar {
@layer daisyui.l1.l2.l3 { @layer daisyui.l1.l2.l3 {
position: relative; position: relative;
@@ -2487,48 +2342,6 @@
} }
} }
} }
.rating {
@layer daisyui.l1.l2.l3 {
position: relative;
display: inline-flex;
vertical-align: middle;
--size: var(--size-selector, 0.25rem) * 6;
input {
cursor: pointer;
appearance: none;
}
* {
border-radius: 0;
background-color: var(--color-base-content);
opacity: 20%;
width: calc(var(--size) * 1);
height: calc(var(--size));
@media (prefers-reduced-motion: no-preference) {
animation: rating 0.25s ease-out;
}
}
.rating-hidden {
width: calc(0.25rem * 2);
background-color: transparent;
}
:checked, [aria-checked="true"], [aria-current="true"], :has( ~ :checked, ~ [aria-checked="true"], ~ [aria-current="true"]) {
opacity: 100%;
}
:focus-visible {
scale: 1.1;
@media (prefers-reduced-motion: no-preference) {
transition: scale 0.2s ease-out;
}
}
:active:focus {
animation: none;
scale: 1.1;
}
}
@layer daisyui.l1.l2 {
--size: var(--size-selector, 0.25rem) * 6;
}
}
.navbar { .navbar {
@layer daisyui.l1.l2.l3 { @layer daisyui.l1.l2.l3 {
display: flex; display: flex;
@@ -2543,6 +2356,14 @@
} }
} }
} }
.drawer {
@layer daisyui.l1.l2.l3 {
position: relative;
display: grid;
width: 100%;
grid-auto-columns: max-content auto;
}
}
.card { .card {
@layer daisyui.l1.l2.l3 { @layer daisyui.l1.l2.l3 {
position: relative; position: relative;
@@ -2668,30 +2489,6 @@
.sticky { .sticky {
position: sticky; position: sticky;
} }
.dropdown-right {
@layer daisyui.l1.l2 {
--anchor-h: right;
--anchor-v: span-bottom;
.dropdown-content {
inset-inline-start: 100%;
top: 0;
bottom: auto;
transform-origin: 0;
}
}
}
.dropdown-left {
@layer daisyui.l1.l2 {
--anchor-h: left;
--anchor-v: span-bottom;
.dropdown-content {
inset-inline-end: 100%;
top: 0;
bottom: auto;
transform-origin: 100%;
}
}
}
.dropdown-end { .dropdown-end {
@layer daisyui.l1.l2 { @layer daisyui.l1.l2 {
--anchor-h: span-left; --anchor-h: span-left;
@@ -2961,25 +2758,6 @@
} }
} }
} }
.btn-active {
@layer daisyui.l1.l2 {
--btn-bg: var(--btn-color, var(--color-base-200));
color: var(--btn-fg, var(--color-base-content));
--btn-border: var(--btn-bg);
@supports (color: color-mix(in lab, red, red)) {
--btn-border: color-mix(in oklab, var(--btn-bg), #000 calc(var(--depth) * 5%));
}
--btn-border-style: solid;
--btn-inset: 0 0.5px 0 0.5px oklch(100% 0 0 / calc(var(--depth) * 6%));
--btn-shadow: 0 3px 2px -2px var(--btn-bg),
0 4px 3px -2px var(--btn-bg);
@supports (color: color-mix(in lab, red, red)) {
--btn-shadow: 0 3px 2px -2px color-mix(in oklab, var(--btn-bg) calc(var(--depth) * 30%), #0000),
0 4px 3px -2px color-mix(in oklab, var(--btn-bg) calc(var(--depth) * 30%), #0000);
}
isolation: isolate;
}
}
.stack { .stack {
@layer daisyui.l1.l2.l3 { @layer daisyui.l1.l2.l3 {
display: inline-grid; display: inline-grid;
@@ -3159,25 +2937,8 @@
.z-10 { .z-10 {
z-index: 10; z-index: 10;
} }
.tab-content { .z-50 {
@layer daisyui.l1.l2.l3 { z-index: 50;
order: var(--tabcontent-order);
display: none;
border-color: transparent;
--tabcontent-radius-ss: var(--radius-box);
--tabcontent-radius-se: var(--radius-box);
--tabcontent-radius-es: var(--radius-box);
--tabcontent-radius-ee: var(--radius-box);
--tabcontent-order: 1;
width: 100%;
height: calc(100% - var(--tab-height) + var(--border));
margin: var(--tabcontent-margin);
border-width: var(--border);
border-start-start-radius: var(--tabcontent-radius-ss);
border-start-end-radius: var(--tabcontent-radius-se);
border-end-start-radius: var(--tabcontent-radius-es);
border-end-end-radius: var(--tabcontent-radius-ee);
}
} }
.col-span-full { .col-span-full {
grid-column: 1 / -1; grid-column: 1 / -1;
@@ -3452,17 +3213,6 @@
padding-inline: calc(var(--size) / 2 - var(--border)); padding-inline: calc(var(--size) / 2 - var(--border));
} }
} }
.tabs {
@layer daisyui.l1.l2.l3 {
display: flex;
flex-wrap: wrap;
--tabs-height: auto;
--tabs-direction: row;
--tab-height: calc(var(--size-field, 0.25rem) * 10);
height: var(--tabs-height);
flex-direction: var(--tabs-direction);
}
}
.footer { .footer {
@layer daisyui.l1.l2.l3 { @layer daisyui.l1.l2.l3 {
display: grid; display: grid;
@@ -3658,15 +3408,32 @@
.inline-flex { .inline-flex {
display: inline-flex; display: inline-flex;
} }
.inline-grid {
display: inline-grid;
}
.table { .table {
display: table; display: table;
} }
.aspect-square { .aspect-square {
aspect-ratio: 1 / 1; aspect-ratio: 1 / 1;
} }
.modal-start {
@layer daisyui.l1.l2 {
place-items: start;
.modal-box {
height: 100vh;
max-height: none;
width: auto;
max-width: none;
translate: -100% 0;
scale: 1;
--modal-tl: 0;
--modal-tr: var(--radius-box);
--modal-bl: 0;
--modal-br: var(--radius-box);
[dir="rtl"] & {
translate: 100% 0;
}
}
}
}
.btn-circle { .btn-circle {
@layer daisyui.l1.l2 { @layer daisyui.l1.l2 {
border-radius: calc(infinity * 1px); border-radius: calc(infinity * 1px);
@@ -3675,16 +3442,23 @@
height: var(--size); height: var(--size);
} }
} }
.btn-square {
@layer daisyui.l1.l2 {
padding-inline: 0;
width: var(--size);
height: var(--size);
}
}
.size-8 { .size-8 {
width: calc(var(--spacing) * 8); width: calc(var(--spacing) * 8);
height: calc(var(--spacing) * 8); height: calc(var(--spacing) * 8);
} }
.h-10 {
height: calc(var(--spacing) * 10);
}
.h-12 { .h-12 {
height: calc(var(--spacing) * 12); height: calc(var(--spacing) * 12);
} }
.h-16 {
height: calc(var(--spacing) * 16);
}
.h-56 { .h-56 {
height: calc(var(--spacing) * 56); height: calc(var(--spacing) * 56);
} }
@@ -3700,30 +3474,33 @@
.max-h-60 { .max-h-60 {
max-height: calc(var(--spacing) * 60); max-height: calc(var(--spacing) * 60);
} }
.max-h-none {
max-height: none;
}
.min-h-6 { .min-h-6 {
min-height: calc(var(--spacing) * 6); min-height: calc(var(--spacing) * 6);
} }
.w-10 {
width: calc(var(--spacing) * 10);
}
.w-12 { .w-12 {
width: calc(var(--spacing) * 12); width: calc(var(--spacing) * 12);
} }
.w-16 { .w-16 {
width: calc(var(--spacing) * 16); width: calc(var(--spacing) * 16);
} }
.w-20 {
width: calc(var(--spacing) * 20);
}
.w-24 { .w-24 {
width: calc(var(--spacing) * 24); width: calc(var(--spacing) * 24);
} }
.w-28 {
width: calc(var(--spacing) * 28);
}
.w-60 { .w-60 {
width: calc(var(--spacing) * 60); width: calc(var(--spacing) * 60);
} }
.w-64 { .w-64 {
width: calc(var(--spacing) * 64); width: calc(var(--spacing) * 64);
} }
.w-72 {
width: calc(var(--spacing) * 72);
}
.w-full { .w-full {
width: 100%; width: 100%;
} }
@@ -3736,6 +3513,9 @@
.max-w-40 { .max-w-40 {
max-width: calc(var(--spacing) * 40); max-width: calc(var(--spacing) * 40);
} }
.max-w-\[85vw\] {
max-width: 85vw;
}
.max-w-md { .max-w-md {
max-width: var(--container-md); max-width: var(--container-md);
} }
@@ -3754,61 +3534,21 @@
.min-w-40 { .min-w-40 {
min-width: calc(var(--spacing) * 40); min-width: calc(var(--spacing) * 40);
} }
.min-w-64 {
min-width: calc(var(--spacing) * 64);
}
.flex-1 { .flex-1 {
flex: 1; flex: 1;
} }
.flex-shrink {
flex-shrink: 1;
}
.shrink { .shrink {
flex-shrink: 1; flex-shrink: 1;
} }
.shrink-0 { .shrink-0 {
flex-shrink: 0; flex-shrink: 0;
} }
.flex-grow {
flex-grow: 1;
}
.grow { .grow {
flex-grow: 1; flex-grow: 1;
} }
.border-collapse { .border-collapse {
border-collapse: collapse; border-collapse: collapse;
} }
.transform {
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
}
.skeleton {
@layer daisyui.l1.l2.l3 {
border-radius: var(--radius-box);
background-color: var(--color-base-300);
@media (prefers-reduced-motion: reduce) {
transition-duration: 15s;
}
will-change: background-position;
background-image: linear-gradient( 105deg, #0000 0% 40%, var(--color-base-100) 50%, #0000 60% 100% );
background-size: 200% auto;
background-position-x: -50%;
@media (prefers-reduced-motion: no-preference) {
animation: skeleton 1.8s ease-in-out infinite;
}
}
}
.aura-glow {
@layer daisyui.l1.l2 {
animation: none;
background-image: radial-gradient(closest-corner at center, currentColor 0%, transparent 90%);
&:before {
animation: aura-glow var(--tw-duration, 6s) ease-out infinite;
}
&:after {
animation: aura-glow-after var(--tw-duration, 6s) ease-out infinite;
}
}
}
.animate-pulse { .animate-pulse {
animation: var(--animate-pulse); animation: var(--animate-pulse);
} }
@@ -3884,9 +3624,6 @@
.justify-start { .justify-start {
justify-content: flex-start; justify-content: flex-start;
} }
.justify-items-start {
justify-items: start;
}
.gap-1 { .gap-1 {
gap: var(--spacing); gap: var(--spacing);
} }
@@ -3916,9 +3653,6 @@
.gap-x-4 { .gap-x-4 {
column-gap: calc(var(--spacing) * 4); column-gap: calc(var(--spacing) * 4);
} }
.gap-x-6 {
column-gap: calc(var(--spacing) * 6);
}
.gap-y-2 { .gap-y-2 {
row-gap: calc(var(--spacing) * 2); row-gap: calc(var(--spacing) * 2);
} }
@@ -3968,6 +3702,9 @@
.rounded-lg { .rounded-lg {
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
} }
.rounded-none {
border-radius: 0;
}
.border { .border {
border-style: var(--tw-border-style); border-style: var(--tw-border-style);
border-width: 1px; border-width: 1px;
@@ -4141,15 +3878,15 @@
--btn-shadow: 0 0 0 0 oklch(0% 0 0/0); --btn-shadow: 0 0 0 0 oklch(0% 0 0/0);
} }
} }
.mask-repeat {
mask-repeat: repeat;
}
.object-contain { .object-contain {
object-fit: contain; object-fit: contain;
} }
.object-cover { .object-cover {
object-fit: cover; object-fit: cover;
} }
.p-0 {
padding: 0;
}
.p-2 { .p-2 {
padding: calc(var(--spacing) * 2); padding: calc(var(--spacing) * 2);
} }
@@ -4282,6 +4019,10 @@
--tw-font-weight: var(--font-weight-medium); --tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium); font-weight: var(--font-weight-medium);
} }
.font-normal {
--tw-font-weight: var(--font-weight-normal);
font-weight: var(--font-weight-normal);
}
.font-semibold { .font-semibold {
--tw-font-weight: var(--font-weight-semibold); --tw-font-weight: var(--font-weight-semibold);
font-weight: var(--font-weight-semibold); font-weight: var(--font-weight-semibold);
@@ -4294,9 +4035,6 @@
--tw-tracking: var(--tracking-wider); --tw-tracking: var(--tracking-wider);
letter-spacing: var(--tracking-wider); letter-spacing: var(--tracking-wider);
} }
.text-wrap {
text-wrap: wrap;
}
.whitespace-nowrap { .whitespace-nowrap {
white-space: nowrap; white-space: nowrap;
} }
@@ -4413,9 +4151,6 @@
text-decoration-line: none; text-decoration-line: none;
} }
} }
.underline {
text-decoration-line: underline;
}
.opacity-40 { .opacity-40 {
opacity: 40%; opacity: 40%;
} }
@@ -4472,19 +4207,6 @@
.filter { .filter {
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
} }
.transition {
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.ease-in-out {
--tw-ease: var(--ease-in-out);
transition-timing-function: var(--ease-in-out);
}
.ease-out {
--tw-ease: var(--ease-out);
transition-timing-function: var(--ease-out);
}
.input-lg { .input-lg {
@layer daisyui.l1.l2 { @layer daisyui.l1.l2 {
--in-size-mul: 12; --in-size-mul: 12;
@@ -4664,6 +4386,11 @@
grid-column: span 2 / span 2; grid-column: span 2 / span 2;
} }
} }
.sm\:block {
@media (width >= 40rem) {
display: block;
}
}
.sm\:hidden { .sm\:hidden {
@media (width >= 40rem) { @media (width >= 40rem) {
display: none; display: none;
@@ -4674,6 +4401,36 @@
display: inline; display: inline;
} }
} }
.sm\:inline-flex {
@media (width >= 40rem) {
display: inline-flex;
}
}
.sm\:h-16 {
@media (width >= 40rem) {
height: calc(var(--spacing) * 16);
}
}
.sm\:w-16 {
@media (width >= 40rem) {
width: calc(var(--spacing) * 16);
}
}
.sm\:w-auto {
@media (width >= 40rem) {
width: auto;
}
}
.sm\:max-w-xs {
@media (width >= 40rem) {
max-width: var(--container-xs);
}
}
.sm\:grow-0 {
@media (width >= 40rem) {
flex-grow: 0;
}
}
.sm\:grid-cols-2 { .sm\:grid-cols-2 {
@media (width >= 40rem) { @media (width >= 40rem) {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -4684,34 +4441,51 @@
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
} }
} }
.sm\:flex-row {
@media (width >= 40rem) {
flex-direction: row;
}
}
.sm\:flex-wrap {
@media (width >= 40rem) {
flex-wrap: wrap;
}
}
.sm\:items-center {
@media (width >= 40rem) {
align-items: center;
}
}
.sm\:justify-between {
@media (width >= 40rem) {
justify-content: space-between;
}
}
.sm\:gap-3 {
@media (width >= 40rem) {
gap: calc(var(--spacing) * 3);
}
}
.sm\:gap-4 {
@media (width >= 40rem) {
gap: calc(var(--spacing) * 4);
}
}
.sm\:px-6 { .sm\:px-6 {
@media (width >= 40rem) { @media (width >= 40rem) {
padding-inline: calc(var(--spacing) * 6); padding-inline: calc(var(--spacing) * 6);
} }
} }
.md\:col-span-2 { .sm\:text-2xl {
@media (width >= 48rem) { @media (width >= 40rem) {
grid-column: span 2 / span 2; font-size: var(--text-2xl);
line-height: var(--tw-leading, var(--text-2xl--line-height));
} }
} }
.md\:col-span-4 { .sm\:text-xl {
@media (width >= 48rem) { @media (width >= 40rem) {
grid-column: span 4 / span 4; font-size: var(--text-xl);
} line-height: var(--tw-leading, var(--text-xl--line-height));
}
.md\:mb-2 {
@media (width >= 48rem) {
margin-bottom: calc(var(--spacing) * 2);
}
}
.md\:grid {
@media (width >= 48rem) {
display: grid;
}
}
.md\:hidden {
@media (width >= 48rem) {
display: none;
} }
} }
.md\:grid-cols-2 { .md\:grid-cols-2 {
@@ -4734,24 +4508,9 @@
grid-template-columns: repeat(5, minmax(0, 1fr)); grid-template-columns: repeat(5, minmax(0, 1fr));
} }
} }
.md\:grid-cols-\[minmax\(0\,1fr\)_2\.5rem\] { .lg\:col-span-1 {
@media (width >= 48rem) { @media (width >= 64rem) {
grid-template-columns: minmax(0,1fr) 2.5rem; grid-column: span 1 / span 1;
}
}
.md\:grid-cols-\[minmax\(0\,2fr\)_minmax\(0\,2fr\)_7rem_2\.5rem\] {
@media (width >= 48rem) {
grid-template-columns: minmax(0,2fr) minmax(0,2fr) 7rem 2.5rem;
}
}
.md\:items-start {
@media (width >= 48rem) {
align-items: flex-start;
}
}
.md\:justify-center {
@media (width >= 48rem) {
justify-content: center;
} }
} }
.lg\:block { .lg\:block {
@@ -4759,16 +4518,36 @@
display: block; display: block;
} }
} }
.lg\:contents {
@media (width >= 64rem) {
display: contents;
}
}
.lg\:flex {
@media (width >= 64rem) {
display: flex;
}
}
.lg\:hidden { .lg\:hidden {
@media (width >= 64rem) { @media (width >= 64rem) {
display: none; display: none;
} }
} }
.lg\:w-auto {
@media (width >= 64rem) {
width: auto;
}
}
.lg\:w-fit { .lg\:w-fit {
@media (width >= 64rem) { @media (width >= 64rem) {
width: fit-content; width: fit-content;
} }
} }
.lg\:grow-0 {
@media (width >= 64rem) {
flex-grow: 0;
}
}
.lg\:grid-cols-2 { .lg\:grid-cols-2 {
@media (width >= 64rem) { @media (width >= 64rem) {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -4789,6 +4568,21 @@
grid-template-columns: repeat(6, minmax(0, 1fr)); grid-template-columns: repeat(6, minmax(0, 1fr));
} }
} }
.lg\:flex-row {
@media (width >= 64rem) {
flex-direction: row;
}
}
.lg\:flex-wrap {
@media (width >= 64rem) {
flex-wrap: wrap;
}
}
.lg\:items-center {
@media (width >= 64rem) {
align-items: center;
}
}
.xl\:grid-cols-3 { .xl\:grid-cols-3 {
@media (width >= 80rem) { @media (width >= 80rem) {
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -4933,6 +4727,46 @@
backface-visibility: hidden; backface-visibility: hidden;
transform: translateZ(0); transform: translateZ(0);
} }
@media (width < 48rem) {
.table-cards, .table-cards :is(thead, tbody, tr, th, td) {
display: block;
width: 100%;
}
.table-cards thead {
display: none;
}
.table-cards tbody tr {
border: 1px solid var(--color-base-300);
border-radius: var(--radius-box);
padding: 0 0.75rem;
}
.table-cards tbody tr + tr {
margin-top: 0.75rem;
}
.table-cards td {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
padding: 0.6rem 0;
border-bottom: 1px solid var(--color-base-200);
}
.table-cards td:last-child {
border-bottom: none;
}
.table-cards td[data-label]:before {
content: attr(data-label);
flex-shrink: 0;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.02em;
text-transform: uppercase;
opacity: 0.5;
}
.table-cards td:not([data-label]) {
justify-content: flex-start;
}
}
@layer base { @layer base {
:where(:root),:root:has(input.theme-controller[value=light]:checked),[data-theme=light] { :where(:root),:root:has(input.theme-controller[value=light]:checked),[data-theme=light] {
color-scheme: light; color-scheme: light;
@@ -5219,26 +5053,6 @@
opacity: 0; opacity: 0;
} }
} }
@property --tw-rotate-x {
syntax: "*";
inherits: false;
}
@property --tw-rotate-y {
syntax: "*";
inherits: false;
}
@property --tw-rotate-z {
syntax: "*";
inherits: false;
}
@property --tw-skew-x {
syntax: "*";
inherits: false;
}
@property --tw-skew-y {
syntax: "*";
inherits: false;
}
@property --tw-space-y-reverse { @property --tw-space-y-reverse {
syntax: "*"; syntax: "*";
inherits: false; inherits: false;
@@ -5405,10 +5219,6 @@
syntax: "*"; syntax: "*";
inherits: false; inherits: false;
} }
@property --tw-ease {
syntax: "*";
inherits: false;
}
@keyframes pulse { @keyframes pulse {
50% { 50% {
opacity: 0.5; opacity: 0.5;
@@ -5417,11 +5227,6 @@
@layer properties { @layer properties {
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
*, ::before, ::after, ::backdrop { *, ::before, ::after, ::backdrop {
--tw-rotate-x: initial;
--tw-rotate-y: initial;
--tw-rotate-z: initial;
--tw-skew-x: initial;
--tw-skew-y: initial;
--tw-space-y-reverse: 0; --tw-space-y-reverse: 0;
--tw-divide-y-reverse: 0; --tw-divide-y-reverse: 0;
--tw-border-style: solid; --tw-border-style: solid;
@@ -5460,7 +5265,6 @@
--tw-drop-shadow-color: initial; --tw-drop-shadow-color: initial;
--tw-drop-shadow-alpha: 100%; --tw-drop-shadow-alpha: 100%;
--tw-drop-shadow-size: initial; --tw-drop-shadow-size: initial;
--tw-ease: initial;
} }
} }
} }

View File

@@ -32,8 +32,28 @@
input.placeholder = select.dataset.searchPlaceholder || ""; input.placeholder = select.dataset.searchPlaceholder || "";
input.autocomplete = "off"; input.autocomplete = "off";
// `fixed`, not `absolute`: an absolutely positioned dropdown is clipped by
// the nearest scrolling ancestor (e.g. the overflow-x-auto wrapper the
// bulk-add tables would otherwise need on mobile), which is exactly what
// stopped those tables from getting one. Fixed positioning is relative to
// the viewport regardless of ancestors, so it's never clipped that way --
// position and size are computed in JS instead of via CSS, see positionList().
const list = document.createElement("ul"); const list = document.createElement("ul");
list.className = "menu absolute z-10 mt-1 w-full rounded-box bg-base-100 shadow max-h-60 overflow-y-auto flex-nowrap hidden"; list.className = "menu fixed z-50 rounded-box bg-base-100 shadow max-h-60 overflow-y-auto flex-nowrap hidden";
const positionList = () => {
const rect = input.getBoundingClientRect();
list.style.left = `${rect.left}px`;
list.style.top = `${rect.bottom + 4}px`;
list.style.width = `${rect.width}px`;
};
// Capture phase: a "scroll" event on an inner scroll container (the
// overflow-x-auto table wrapper) doesn't bubble up to window in the
// normal (bubbling) phase, but every scroll is seen on the way down in
// the capture phase, so this still fires no matter which ancestor moved.
window.addEventListener("scroll", () => { if (!list.classList.contains("hidden")) positionList(); }, true);
window.addEventListener("resize", () => { if (!list.classList.contains("hidden")) positionList(); });
select.parentNode.insertBefore(wrapper, select); select.parentNode.insertBefore(wrapper, select);
if (isMultiple) { if (isMultiple) {
@@ -115,7 +135,12 @@
}); });
highlighted = -1; highlighted = -1;
list.classList.toggle("hidden", matches.length === 0); if (matches.length === 0) {
list.classList.add("hidden");
} else {
positionList();
list.classList.remove("hidden");
}
return matches; return matches;
}; };

View File

@@ -53,26 +53,46 @@
intrinsic width. intrinsic width.
{% endcomment %} {% endcomment %}
<div class="navbar shrink-0 border-b border-base-300 bg-base-100 px-3 shadow-sm sm:px-6"> <div class="navbar shrink-0 border-b border-base-300 bg-base-100 px-3 shadow-sm sm:px-6">
<div class="my-4 min-w-0 flex-1"> <div class="my-4 flex min-w-0 flex-1 items-center gap-1">
{% block brand %}{% endblock brand %} {% comment %}
Empty for pages with no menu (the auth screens) -- management/base.html
and controlpanel/base.html are the only overriders, each pairing this
with its own `{% block mobile_menu %}` dialog below in `main`.
{% endcomment %}
{% block nav_toggle %}{% endblock nav_toggle %}
<div class="min-w-0">
{% block brand %}{% endblock brand %}
</div>
</div> </div>
<button class="btn btn-ghost" type="button" data-theme-toggle aria-label="Theme"> {% comment %}
<span data-theme-icon="light" class="hidden items-center gap-4">{% lucide "sun" size=20 %} <span class="hidden sm:inline">light</span></span> Hidden below `lg` wherever a hamburger drawer exists to hold them instead
<span data-theme-icon="dark" class="hidden items-center gap-4">{% lucide "moon" size=20 %} <span class="hidden sm:inline">dark</span></span> (management/base.html, controlpanel/base.html override `nav_icons_class` to
<span data-theme-icon="auto" class="hidden items-center gap-4">{% lucide "sun-moon" size=20 %} <span class="hidden sm:inline">auto</span></span> `hidden lg:flex` and duplicate these same three controls inside their
</button> {% block nav_toggle %} dialog) -- on a page with no such drawer (the auth
screens, the public club site) the default keeps them in the navbar itself,
since there'd be nowhere else for them to go.
{% endcomment %}
<div class="{% block nav_icons_class %}flex items-center{% endblock nav_icons_class %}">
<button class="btn btn-ghost" type="button" data-theme-toggle aria-label="Theme">
<span data-theme-icon="light" class="hidden items-center gap-4">{% lucide "sun" size=20 %} <span class="hidden sm:inline">light</span></span>
<span data-theme-icon="dark" class="hidden items-center gap-4">{% lucide "moon" size=20 %} <span class="hidden sm:inline">dark</span></span>
<span data-theme-icon="auto" class="hidden items-center gap-4">{% lucide "sun-moon" size=20 %} <span class="hidden sm:inline">auto</span></span>
</button>
{% if user.is_authenticated %}
{% if has_management_access %}
{# Only ever true on a club subdomain -- see management.context_processors.management_link. #}
<a class="btn btn-ghost gap-4" href="{% url "management:home" %}" aria-label="Management">{% lucide "layout-dashboard" %}<span class="hidden sm:inline">Management</span></a>
{% endif %}
{% if user.is_superuser %}
<a class="btn btn-ghost gap-4" href="{% url "admin:index" %}" aria-label="Django admin">{% lucide "shield-cog" %}<span class="hidden sm:inline">Django admin</span></a>
{% endif %}
{% endif %}
</div>
{% if user.is_authenticated %} {% if user.is_authenticated %}
{% if has_management_access %}
{# Only ever true on a club subdomain -- see management.context_processors.management_link. #}
<a class="btn btn-ghost gap-4" href="{% url "management:home" %}" aria-label="Management">{% lucide "layout-dashboard" %}<span class="hidden sm:inline">Management</span></a>
{% endif %}
{% if user.is_superuser %}
<a class="btn btn-ghost gap-4" href="{% url "admin:index" %}" aria-label="Django admin">{% lucide "shield-cog" %}<span class="hidden sm:inline">Django admin</span></a>
{% endif %}
<div class="dropdown dropdown-end"> <div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn btn-ghost gap-4" aria-label="Account">{% lucide "circle-user" %}<span class="hidden max-w-40 truncate sm:inline">{{ user.get_full_name }}</span></div> <div tabindex="0" role="button" class="btn btn-ghost gap-4" aria-label="Account">{% lucide "circle-user" %}<span class="hidden max-w-40 truncate sm:inline">{{ user.get_full_name }}</span></div>
<ul tabindex="0" class="menu dropdown-content z-10 mt-2 w-60 rounded-box bg-base-100 p-2 shadow border border-base-content/20"> <ul tabindex="0" class="menu dropdown-content z-10 mt-2 w-60 rounded-box bg-base-100 p-2 shadow border border-base-content/20">

View File

@@ -34,25 +34,26 @@
{% endblock extra %} {% endblock extra %}
{% block brand %} {% block brand %}
<a class="flex flex-row items-center gap-3" href="/"> {# min-w-0 on both the link and the text column: without it a flex child's default min-width:auto refuses to shrink past the club name's intrinsic width, so `truncate` never gets the chance to kick in and the name pushes the header wider than the viewport instead of ellipsizing. The avatar and tagline shrink/hide below `sm` for the same reason -- a long club name at full size has nowhere left to give. #}
<a class="flex min-w-0 flex-row items-center gap-2 sm:gap-3" href="/">
{% if club.logo %} {% if club.logo %}
{# The ring is the club's own primary colour -- a plain frame around whatever crest they uploaded. #} {# The ring is the club's own primary colour -- a plain frame around whatever crest they uploaded. #}
<div class="avatar"> <div class="avatar shrink-0">
<div class="w-16 rounded-full bg-base-100 ring-2 ring-primary ring-offset-2 ring-offset-base-100"> <div class="w-10 rounded-full bg-base-100 ring-2 ring-primary ring-offset-2 ring-offset-base-100 sm:w-16">
<img class="club-logo object-contain" src="{{ club.logo.url }}" alt="{{ club.name }}"> <img class="club-logo object-contain" src="{{ club.logo.url }}" alt="{{ club.name }}">
</div> </div>
</div> </div>
{% else %} {% else %}
{# Never the RosterChief mark: that would pass our branding off as the club's own. #} {# Never the RosterChief mark: that would pass our branding off as the club's own. #}
<div class="avatar avatar-placeholder"> <div class="avatar avatar-placeholder shrink-0">
<div class="w-16 rounded-full bg-secondary text-secondary-content"> <div class="w-10 rounded-full bg-secondary text-secondary-content sm:w-16">
<span class="font-roboto text-xl font-bold">{{ club.initials }}</span> <span class="font-roboto text-sm font-bold sm:text-xl">{{ club.initials }}</span>
</div> </div>
</div> </div>
{% endif %} {% endif %}
<div class="flex flex-col gap-1"> <div class="flex min-w-0 flex-col gap-1">
<div class="font-roboto text-2xl font-bold tracking-wider">{{ club.name }}</div> <div class="truncate font-roboto text-lg font-bold tracking-wider sm:text-2xl">{{ club.name }}</div>
<div class="font-mono text-xs font-semibold text-base-content/50">Powered by RosterChief</div> <div class="hidden truncate font-mono text-xs font-semibold text-base-content/50 sm:block">Powered by RosterChief</div>
</div> </div>
</a> </a>
{% endblock brand %} {% endblock brand %}

View File

@@ -25,11 +25,11 @@
{% endblock extra %} {% endblock extra %}
{% block brand %} {% block brand %}
<a class="flex flex-row items-center gap-2" href="/"> <a class="flex min-w-0 flex-row items-center gap-2" href="/">
<span class="logo inline-block h-16 w-16" role="img" aria-label="RosterChief"></span> <span class="logo inline-block h-10 w-10 shrink-0 sm:h-16 sm:w-16" role="img" aria-label="RosterChief"></span>
<div class="flex flex-col gap-1"> <div class="flex min-w-0 flex-col gap-1">
<div class="font-roboto text-2xl font-bold tracking-wider">Roster<span class="text-sky-500">Chief</span></div> <div class="truncate font-roboto text-lg font-bold tracking-wider sm:text-2xl">Roster<span class="text-sky-500">Chief</span></div>
<div class="font-mono text-xs font-semibold text-base-content/50">Club &amp; Team Management</div> <div class="hidden truncate font-mono text-xs font-semibold text-base-content/50 sm:block">Club &amp; Team Management</div>
</div> </div>
</a> </a>
{% endblock brand %} {% endblock brand %}