Improve club detail layout: enhance logo display, adjust badge styles, refine card layouts, and optimize spacing
This commit is contained in:
@@ -122,7 +122,7 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn-sm gap-2" href="{% url "controlpanel:club_detail" club.pk %}">{% lucide "pencil" size=14 %} Edit</a>
|
<a class="btn btn-sm btn-outline gap-2" href="{% url "controlpanel:club_detail" club.pk %}">{% lucide "pencil" size=14 %} Edit</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
|
|||||||
@@ -28,8 +28,10 @@
|
|||||||
<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 items-center justify-between gap-3">
|
<div class="mb-6 flex flex-wrap flex-row items-center justify-between gap-3">
|
||||||
<div class="flex flex-col gap-2">
|
{% block logo %}{% endblock logo %}
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|||||||
@@ -1,19 +1,29 @@
|
|||||||
{% extends "controlpanel/base.html" %}
|
{% extends "controlpanel/base.html" %}
|
||||||
{% load static lucide %}
|
{% load static lucide %}
|
||||||
|
|
||||||
|
{% block logo %}
|
||||||
|
{% if club.logo %}
|
||||||
|
<img class="h-16 w-16 object-contain" src="{{ club.logo.url }}" alt="{{ club.name }}">
|
||||||
|
{% else %}
|
||||||
|
<div class="avatar avatar-placeholder">
|
||||||
|
<div class="w-16 text-xl rounded-full bg-neutral text-neutral-content">
|
||||||
|
<span>{{ club.initials }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock logo %}
|
||||||
|
|
||||||
{% block heading %}{{ club.name }}{% endblock heading %}
|
{% block heading %}{{ club.name }}{% endblock heading %}
|
||||||
|
|
||||||
{% block subheading %}
|
{% block subheading %}
|
||||||
<p class="text-sm opacity-70">
|
{{ club.slug }}.rosterchief.app
|
||||||
{{ club.slug }}
|
{% if club.is_archived %}
|
||||||
{% if club.is_archived %}
|
<span class="badge badge-warning badge-sm ml-2">Archived</span>
|
||||||
<span class="badge badge-warning badge-sm ml-2">Archived</span>
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
{% endblock subheading %}
|
{% endblock subheading %}
|
||||||
|
|
||||||
{% block actions %}
|
{% block actions %}
|
||||||
<a class="btn btn-ghost gap-2" href="{% url 'controlpanel:club_update' club.pk %}">{% lucide "pencil" size=16 %} Edit</a>
|
<a class="btn btn-outline gap-2" href="{% url 'controlpanel:club_update' club.pk %}">{% lucide "pencil" size=16 %} Edit</a>
|
||||||
{% if club.is_archived %}
|
{% if club.is_archived %}
|
||||||
<form method="post" action="{% url 'controlpanel:club_restore' club.pk %}">
|
<form method="post" action="{% url 'controlpanel:club_restore' club.pk %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@@ -30,6 +40,7 @@
|
|||||||
{% block panel %}
|
{% block panel %}
|
||||||
{% if club.is_archived %}
|
{% if club.is_archived %}
|
||||||
<div class="alert alert-warning mb-6">
|
<div class="alert alert-warning mb-6">
|
||||||
|
{% lucide "alert-triangle" size=20 %}
|
||||||
<span>This club is archived: its subdomain no longer resolves. Nothing has been deleted — restore it to bring it back.</span>
|
<span>This club is archived: its subdomain no longer resolves. Nothing has been deleted — restore it to bring it back.</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -47,96 +58,91 @@
|
|||||||
the club's setup, not a statistic: with nobody in a management position the access
|
the club's setup, not a statistic: with nobody in a management position the access
|
||||||
service grants no authority over that team, so nobody can pick the squad.
|
service grants no authority over that team, so nobody can pick the squad.
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
<div class="mb-6 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
<div class="mb-6 grid gap-4 sm:grid-cols-2 lg:grid-cols-6">
|
||||||
<div class="card bg-base-100 shadow {% if attention.outstanding %}border-l-4 border-error{% endif %}">
|
{% comment %}<div class="card bg-base-100 shadow {% if attention.outstanding %}border-l-4 border-error{% endif %}">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<div class="flex items-center gap-2 text-sm opacity-70">{% lucide "banknote" size=16 %} Outstanding</div>
|
<div class="flex items-center gap-2 text-sm opacity-70">{% lucide "banknote" size=16 %} Outstanding</div>
|
||||||
<div class="text-3xl font-bold tabular-nums">€{{ attention.outstanding|floatformat:2 }}</div>
|
<div class="text-3xl font-bold tabular-nums">€{{ attention.outstanding|floatformat:2 }}</div>
|
||||||
<div class="text-xs opacity-60">{{ attention.unpaid_members }} member{{ attention.unpaid_members|pluralize }} unpaid this season</div>
|
<div class="text-xs opacity-60">{{ attention.unpaid_members }} member{{ attention.unpaid_members|pluralize }} unpaid this season</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>{% endcomment %}
|
||||||
<div class="card bg-base-100 shadow {% if attention.teams_without_manager %}border-l-4 border-error{% endif %}">
|
<div class="card bg-base-100 shadow border-l-4 {% if attention.teams_without_manager %}border-error{% else %}border-success{% endif %}">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<div class="flex items-center gap-2 text-sm opacity-70">{% lucide "user-x" size=16 %} No coach</div>
|
<div class="flex items-center gap-2 text-sm opacity-70 mb-3">{% lucide "user-x" size=16 %} Teams without coach</div>
|
||||||
<div class="text-3xl font-bold tabular-nums">{{ attention.teams_without_manager }}</div>
|
<div class="text-4xl font-bold tabular-nums font-mono">{{ attention.teams_without_manager }}</div>
|
||||||
<div class="text-xs opacity-60">Teams nobody can pick a squad for</div>
|
<div class="text-xs opacity-60">Teams nobody can pick a squad for</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card bg-base-100 shadow {% if attention.unrostered %}border-l-4 border-warning{% endif %}">
|
|
||||||
|
<div class="card bg-base-100 shadow border-l-4 {% if attention.unrostered %}border-warning{% else %}border-success{% endif %}">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<div class="flex items-center gap-2 text-sm opacity-70">{% lucide "user-minus" size=16 %} Unrostered</div>
|
<div class="flex items-center gap-2 text-sm opacity-70 mb-3">{% lucide "user-minus" size=16 %} Unrostered members</div>
|
||||||
<div class="text-3xl font-bold tabular-nums">{{ attention.unrostered }}</div>
|
<div class="text-4xl font-bold tabular-nums font-mono">{{ attention.unrostered }}</div>
|
||||||
<div class="text-xs opacity-60">Active members on no team</div>
|
<div class="text-xs opacity-60">Active members on no team</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card bg-base-100 shadow {% if attention.pending_approvals %}border-l-4 border-warning{% endif %}">
|
|
||||||
|
<div class="card bg-base-100 shadow border-l-4 {% if attention.pending_approvals %}border-warning{% else %}border-success{% endif %}">
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
<div class="flex items-center gap-2 text-sm opacity-70">{% lucide "clock" size=16 %} Pending</div>
|
<div class="flex items-center gap-2 text-sm opacity-70 mb-3">{% lucide "clock" size=16 %} Pending</div>
|
||||||
<div class="text-3xl font-bold tabular-nums">{{ attention.pending_approvals }}</div>
|
<div class="text-4xl font-bold tabular-nums font-mono">{{ attention.pending_approvals }}</div>
|
||||||
<div class="text-xs opacity-60">Memberships awaiting approval</div>
|
<div class="text-xs opacity-60">Memberships awaiting approval</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-6 grid gap-4 lg:grid-cols-4">
|
<div class="card bg-base-100 shadow border-l-4 border-info">
|
||||||
<div class="card bg-base-100 shadow">
|
<div class="card-body p-4">
|
||||||
<div class="card-body">
|
<div class="flex items-center gap-2 text-sm opacity-70 mb-3">{% lucide "sparkles" size=16 %} New members</div>
|
||||||
<h2 class="card-title text-base">{% lucide "sparkles" size=18 %} New members</h2>
|
<div class="text-4xl font-bold tabular-nums font-mono">{{ attention.new_members }}</div>
|
||||||
<div class="text-4xl font-bold tabular-nums">{{ attention.new_members }}</div>
|
|
||||||
{# First season at this club — someone returning after a year away is a renewal. #}
|
{# First season at this club — someone returning after a year away is a renewal. #}
|
||||||
<p class="text-sm opacity-70">first season at this club</p>
|
<div class="text-xs opacity-60">First season at this club</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card bg-base-100 shadow">
|
<div class="card bg-base-100 shadow border-l-4 {% if attention.renewal_rate is None %}border-info{% elif attention.renewal_rate < 30 %}border-error{% elif attention.renewal_rate < 65 %}border-warning{% else %}border-success{% endif %}">
|
||||||
<div class="card-body">
|
<div class="card-body p-4">
|
||||||
<h2 class="card-title text-base">{% lucide "repeat" size=18 %} Renewal</h2>
|
<div class="flex items-center gap-2 text-sm opacity-70 mb-3">{% lucide "repeat" size=16 %} Renewal rate</div>
|
||||||
{% if attention.renewal_rate is None %}
|
<div class="text-4xl font-bold tabular-nums font-mono">
|
||||||
{# No prior season to compare against: a first-season club has not failed to renew anyone. #}
|
{% if attention.renewal_rate is None %}
|
||||||
<p class="text-sm opacity-60">No previous season to compare against yet.</p>
|
N/A
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-4xl font-bold tabular-nums">{{ attention.renewal_rate }}%</div>
|
{{ attention.renewal_rate }}%
|
||||||
<p class="text-sm opacity-70">of last season's active members signed up again</p>
|
{% endif %}
|
||||||
<progress class="progress progress-primary w-full" value="{{ attention.renewal_rate }}" max="100"></progress>
|
</div>
|
||||||
{% endif %}
|
<div class="text-xs opacity-60">
|
||||||
|
{% if attention.renewal_rate is None %}
|
||||||
|
No previous season
|
||||||
|
{% else %}
|
||||||
|
<progress class="progress w-full {% if attention.renewal_rate < 30 %}progress-error{% elif attention.renewal_rate < 65 %}progress-warning{% else %}progress-success{% endif %}" value="{{ attention.renewal_rate }}"
|
||||||
|
max="100"></progress>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card bg-base-100 shadow">
|
<div class="card bg-base-100 shadow border-l-4 {% if attention.attendance.turnout is None %}border-info{% elif attention.attendance.turnout < 30 %}border-error{% elif attention.attendance.turnout < 65 %}border-warning{% else %}border-success{% endif %}">
|
||||||
<div class="card-body">
|
<div class="card-body p-4">
|
||||||
<h2 class="card-title text-base">{% lucide "user-check" size=18 %} Attendance</h2>
|
<div class="flex items-center gap-2 text-sm opacity-70 mb-3">{% lucide "user-check" size=16 %} Attendance rate</div>
|
||||||
{% if attention.attendance.turnout is None %}
|
<div class="text-4xl font-bold tabular-nums font-mono">
|
||||||
<p class="text-sm opacity-60">No past events with responses this season.</p>
|
{% if attention.attendance.turnout is None %}
|
||||||
{% else %}
|
N/A
|
||||||
<div class="text-4xl font-bold tabular-nums">{{ attention.attendance.turnout }}%</div>
|
{% else %}
|
||||||
<p class="text-sm opacity-70">turnout of those who answered</p>
|
{{ attention.attendance.turnout }}%
|
||||||
<p class="mt-2 text-sm">
|
{% endif %}
|
||||||
{# The leading indicator: it measures whether members use the app at all. #}
|
</div>
|
||||||
<span class="font-semibold {% if attention.attendance.no_response > 30 %}text-warning{% endif %}">{{ attention.attendance.no_response }}%</span>
|
<div class="text-xs opacity-60">
|
||||||
<span class="opacity-70">never responded</span>
|
{% if attention.attendance.turnout is None %}
|
||||||
</p>
|
No events this season
|
||||||
{% endif %}
|
{% else %}
|
||||||
</div>
|
<progress class="progress w-full {% if attention.attendance.turnout < 30 %}progress-error{% elif attention.attendance.turnout < 65 %}progress-warning{% else %}progress-success{% endif %}"
|
||||||
</div>
|
value="{{ attention.attendance.turnout }}" max="100"></progress>
|
||||||
|
{% endif %}
|
||||||
<div class="card bg-base-100 shadow">
|
</div>
|
||||||
<div class="card-body">
|
|
||||||
<h2 class="card-title text-base">{% lucide "hourglass" size=18 %} Unpaid, by age</h2>
|
|
||||||
<table class="table table-sm">
|
|
||||||
<tbody>
|
|
||||||
{% for bucket in attention.aging %}
|
|
||||||
<tr>
|
|
||||||
<td class="{% if bucket.overdue and bucket.total %}font-semibold text-error{% endif %}">{{ bucket.label }}</td>
|
|
||||||
<td class="text-right tabular-nums">€{{ bucket.total|floatformat:2 }}</td>
|
|
||||||
<td class="text-right opacity-60">{{ bucket.count }} order{{ bucket.count|pluralize }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<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">
|
||||||
@@ -149,7 +155,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card bg-base-100 shadow">
|
<div class="card bg-base-100 shadow">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title text-base">{% lucide "wallet" size=18 %} Fee status this season</h2>
|
<h2 class="card-title text-base">{% lucide "wallet" size=18 %} Club fee status this season</h2>
|
||||||
<div class="h-56">
|
<div class="h-56">
|
||||||
<canvas id="fees-chart"></canvas>
|
<canvas id="fees-chart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
@@ -157,7 +163,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-6 grid gap-4 md:grid-cols-2">
|
<div class="mb-6 grid gap-4 md:grid-cols-4">
|
||||||
{% for group in groups %}
|
{% for group in groups %}
|
||||||
<div class="card bg-base-100 shadow">
|
<div class="card bg-base-100 shadow">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@@ -166,7 +172,7 @@
|
|||||||
{% for label, value in group.stats %}
|
{% for label, value in group.stats %}
|
||||||
<div class="flex items-center justify-between py-2">
|
<div class="flex items-center justify-between py-2">
|
||||||
<dt class="text-sm opacity-70">{{ label }}</dt>
|
<dt class="text-sm opacity-70">{{ label }}</dt>
|
||||||
<dd class="font-semibold tabular-nums">{{ value }}</dd>
|
<dd class="font-semibold tabular-nums font-mono">{% if group.title == "Shop" and label == "Outstanding" or label == "Revenue" %}€{% endif %}{{ value }}</dd>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</dl>
|
</dl>
|
||||||
@@ -178,7 +184,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h2 class="card-title text-base">{% lucide "toggle-right" size=18 %} Features</h2>
|
<h2 class="card-title text-base">{% lucide "toggle-right" size=18 %} Features</h2>
|
||||||
<a class="btn btn-ghost btn-xs" href="{% url 'controlpanel:features' %}">Manage features</a>
|
<a class="btn btn-outline btn-sm gap-2" href="{% url 'controlpanel:features' %}">{% lucide "wrench" size=14 %} Manage features</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
@@ -233,7 +239,7 @@
|
|||||||
<p class="text-sm opacity-70">This club is not billed for anything. Put it on a tier to start.</p>
|
<p class="text-sm opacity-70">This club is not billed for anything. Put it on a tier to start.</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="text-sm opacity-70">
|
<p class="text-sm opacity-70">
|
||||||
On <strong>{{ subscription.tier.name }}</strong>.
|
On plan <strong>{{ subscription.tier.name }}</strong>.
|
||||||
{% if subscription.auto_renew %}
|
{% if subscription.auto_renew %}
|
||||||
Renews automatically 30 days before the period ends.
|
Renews automatically 30 days before the period ends.
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -253,7 +259,7 @@
|
|||||||
<th>Period</th>
|
<th>Period</th>
|
||||||
<th class="text-right">Billed</th>
|
<th class="text-right">Billed</th>
|
||||||
<th class="text-right">Paid</th>
|
<th class="text-right">Paid</th>
|
||||||
<th>Status</th>
|
<th class="text-right">Status</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -266,17 +272,17 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="text-right tabular-nums">€{{ due.amount|floatformat:2 }}</td>
|
<td class="text-right tabular-nums">€{{ due.amount|floatformat:2 }}</td>
|
||||||
<td class="text-right tabular-nums">€{{ due.amount_paid|floatformat:2 }}</td>
|
<td class="text-right tabular-nums">€{{ due.amount_paid|floatformat:2 }}</td>
|
||||||
<td>
|
<td class="text-right">
|
||||||
{% if due.status == "paid" %}
|
{% if due.status == "paid" %}
|
||||||
<span class="badge badge-success gap-1">{% lucide "check" size=12 %} Paid</span>
|
<span class="badge badge-success gap-1">{% lucide "check" size=12 %} Paid</span>
|
||||||
{% elif due.status == "waived" %}
|
{% elif due.status == "waived" %}
|
||||||
<span class="badge badge-ghost">Waived</span>
|
<span class="badge badge-outline gap-1">{% lucide "check" size=12 %} Waived</span>
|
||||||
{% elif due.grace_until < today %}
|
{% elif due.grace_until < today %}
|
||||||
<span class="badge badge-error gap-1">{% lucide "triangle-alert" size=12 %} Overdue</span>
|
<span class="badge badge-error gap-1">{% lucide "triangle-alert" size=12 %} Overdue</span>
|
||||||
{% elif due.period_end < today %}
|
{% elif due.period_end < today %}
|
||||||
<span class="badge badge-warning gap-1">{% lucide "hourglass" size=12 %} In grace</span>
|
<span class="badge badge-warning gap-1">{% lucide "hourglass" size=12 %} In grace</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge badge-ghost">{{ due.get_status_display }}</span>
|
<span class="badge badge-outline">{{ due.get_status_display }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
@@ -289,7 +295,7 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a class="btn btn-ghost btn-xs gap-1" href="{% url 'controlpanel:due_invoice' due.pk %}">{% lucide "file-text" size=14 %} Invoice</a>
|
<a class="btn btn-accent btn-outline btn-sm gap-1" href="{% url 'controlpanel:due_invoice' due.pk %}">{% lucide "file-down" size=14 %} Download invoice</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% for payment in due.payments.all %}
|
{% for payment in due.payments.all %}
|
||||||
@@ -317,7 +323,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 items-center justify-between">
|
||||||
<h2 class="card-title text-base">Club admins</h2>
|
<h2 class="card-title text-base">{% lucide "shield-user" size=18 %} Club admins</h2>
|
||||||
<a class="btn btn-primary btn-sm gap-2" href="{% url 'controlpanel:club_admin_add' club.pk %}">{% lucide "user-plus" size=16 %} Add admin</a>
|
<a class="btn btn-primary btn-sm gap-2" href="{% url 'controlpanel:club_admin_add' club.pk %}">{% lucide "user-plus" size=16 %} Add admin</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
@@ -337,7 +343,7 @@
|
|||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<form method="post" action="{% url 'controlpanel:club_admin_remove' club.pk role.pk %}">
|
<form method="post" action="{% url 'controlpanel:club_admin_remove' club.pk role.pk %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button class="btn btn-ghost btn-xs gap-1 text-error" type="submit">{% lucide "trash-2" size=14 %} Remove</button>
|
<button class="btn btn-error btn-outline btn-sm gap-1" type="submit">{% lucide "trash-2" size=14 %} Remove</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -372,17 +378,17 @@
|
|||||||
data: {
|
data: {
|
||||||
labels: data.signups.map((point) => point.month),
|
labels: data.signups.map((point) => point.month),
|
||||||
datasets: [
|
datasets: [
|
||||||
{ label: "New", data: data.signups.map((point) => point.new), backgroundColor: css("--color-primary", "#4f46e5") },
|
{label: "New", data: data.signups.map((point) => point.new), backgroundColor: css("--color-primary", "#4f46e5")},
|
||||||
{ label: "Returning", data: data.signups.map((point) => point.returning), backgroundColor: css("--color-accent", "#0ea5e9") },
|
{label: "Returning", data: data.signups.map((point) => point.returning), backgroundColor: css("--color-accent", "#0ea5e9")},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
plugins: { legend: { position: "bottom", labels: { color: ink } } },
|
plugins: {legend: {position: "bottom", labels: {color: ink}}},
|
||||||
scales: {
|
scales: {
|
||||||
x: { stacked: true, ticks: { color: ink }, grid: { color: grid } },
|
x: {stacked: true, ticks: {color: ink}, grid: {color: grid}},
|
||||||
y: { stacked: true, beginAtZero: true, ticks: { color: ink, precision: 0 }, grid: { color: grid } },
|
y: {stacked: true, beginAtZero: true, ticks: {color: ink, precision: 0}, grid: {color: grid}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -403,7 +409,7 @@
|
|||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
plugins: { legend: { position: "right", labels: { color: ink } } },
|
plugins: {legend: {position: "right", labels: {color: ink}}},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -416,7 +422,7 @@
|
|||||||
new MutationObserver(() => {
|
new MutationObserver(() => {
|
||||||
charts.forEach((chart) => chart.destroy());
|
charts.forEach((chart) => chart.destroy());
|
||||||
charts = render();
|
charts = render();
|
||||||
}).observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] });
|
}).observe(document.documentElement, {attributes: true, attributeFilter: ["data-theme"]});
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
{% endblock extra_body %}
|
{% endblock extra_body %}
|
||||||
|
|||||||
@@ -4,28 +4,33 @@
|
|||||||
{% block heading %}{% if object %}Edit {{ object }}{% else %}New club{% endif %}{% endblock heading %}
|
{% block heading %}{% if object %}Edit {{ object }}{% else %}New club{% endif %}{% endblock heading %}
|
||||||
|
|
||||||
{% block panel %}
|
{% block panel %}
|
||||||
<div class="card max-w-xl bg-base-100 shadow">
|
<div class="card w-full bg-base-100 shadow">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="post" enctype="multipart/form-data">
|
<form method="post" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
{% for error in form.non_field_errors %}
|
{% for error in form.non_field_errors %}
|
||||||
<div class="alert alert-error my-2">
|
<div class="alert alert-error my-2">
|
||||||
<span>{{ error }}</span>
|
<span>{{ error }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for field in form %}
|
|
||||||
<div class="form-control my-3 w-full">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<label class="label" for="{{ field.id_for_label }}">
|
{% for field in form %}
|
||||||
<span class="label-text">{{ field.label }}</span>
|
<div class="my-3 w-full">
|
||||||
</label>
|
<label class="label" for="{{ field.id_for_label }}">
|
||||||
{{ field|daisy }}
|
<span class="label-text">{{ field.label }}</span>
|
||||||
{% if field.help_text %}<span class="label-text-alt mt-1 text-base-content/70">{{ field.help_text }}</span>{% endif %}
|
</label>
|
||||||
{% for error in field.errors %}<span class="label-text-alt mt-1 text-error">{{ error }}</span>{% endfor %}
|
{{ field|daisy }}
|
||||||
</div>
|
{% if field.help_text and not field.errors %}<span class="label-text-alt mt-1 text-base-content/70">{{ field.help_text }}</span>{% endif %}
|
||||||
{% endfor %}
|
{% for error in field.errors %}<span class="label-text-alt mt-1 text-error">{{ error }}</span>{% endfor %}
|
||||||
<div class="card-actions justify-end pt-2">
|
</div>
|
||||||
<a class="btn btn-outline gap-2" href="{% url 'controlpanel:club_list' %}">{% lucide "arrow-left" size=16 %} Cancel</a>
|
{% endfor %}
|
||||||
<button class="btn btn-primary" type="submit">Save</button>
|
</div>
|
||||||
|
|
||||||
|
<div class="card-actions justify-start pt-2 mt-2">
|
||||||
|
<a class="btn btn-outline gap-2" href="{% if update_view %}{% url "controlpanel:club_detail" object.pk %}{% else %}{% url "controlpanel:club_list" %}{% endif %}">{% lucide "arrow-left" size=16 %} Cancel</a>
|
||||||
|
<button class="btn btn-primary gap-2" type="submit">{% lucide "save" size=16 %} Save</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
|
|
||||||
{% block actions %}
|
{% block actions %}
|
||||||
{% if show_archived %}
|
{% if show_archived %}
|
||||||
<a class="btn btn-ghost" href="{% url 'controlpanel:club_list' %}">Active clubs</a>
|
<a class="btn btn-outline" href="{% url 'controlpanel:club_list' %}">{% lucide "archive-x" size=16 %} Hide archived clubs</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a class="btn btn-ghost" href="{% url 'controlpanel:club_list' %}?archived=1">Archived</a>
|
<a class="btn btn-outline" href="{% url 'controlpanel:club_list' %}?archived=1">{% lucide "archive" size=16 %} Show archived clubs</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a class="btn btn-primary gap-2" href="{% url 'controlpanel:club_create' %}">{% lucide "plus" size=16 %} New club</a>
|
<a class="btn btn-primary gap-2" href="{% url 'controlpanel:club_create' %}">{% lucide "plus" size=16 %} New club</a>
|
||||||
{% endblock actions %}
|
{% endblock actions %}
|
||||||
@@ -15,12 +15,19 @@
|
|||||||
{% block panel %}
|
{% block panel %}
|
||||||
<form method="get" class="mb-4 flex gap-2">
|
<form method="get" class="mb-4 flex gap-2">
|
||||||
{% if show_archived %}<input type="hidden" name="archived" value="1">{% endif %}
|
{% if show_archived %}<input type="hidden" name="archived" value="1">{% endif %}
|
||||||
<input type="search"
|
<label class="input">
|
||||||
name="q"
|
<span class="opacity-50">{% lucide "search" size=16 %}</span>
|
||||||
value="{{ search }}"
|
<input type="search"
|
||||||
placeholder="Search clubs…"
|
name="q"
|
||||||
class="input input-bordered w-full max-w-xs">
|
value="{{ search }}"
|
||||||
<button class="btn gap-2" type="submit">{% lucide "search" size=16 %} Search</button>
|
placeholder="Search clubs…"
|
||||||
|
class="input input-bordered w-full max-w-xs">
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button class="btn btn-outline gap-2" type="submit">{% lucide "search" size=16 %} Search</button>
|
||||||
|
{% if search %}
|
||||||
|
<a class="btn btn-primary gap-2" href="{% url "controlpanel:club_list" %}">{% lucide "x" size=16 %} Clear filter</a>
|
||||||
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
<div class="card bg-base-100 shadow">
|
<div class="card bg-base-100 shadow">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|||||||
@@ -91,7 +91,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="mb-1 flex items-center justify-between text-sm">
|
<div class="mb-1 flex items-center justify-between text-sm">
|
||||||
<span class="flex items-center gap-2">{% lucide step.icon size=14 %} {{ step.label }}</span>
|
<span class="flex items-center gap-2">{% lucide step.icon size=14 %} {{ step.label }}</span>
|
||||||
<span class="font-semibold tabular-nums">{{ step.count }}</span>
|
<span class="font-semibold tabular-nums font-mono">{{ step.count }}</span>
|
||||||
</div>
|
</div>
|
||||||
<progress class="progress {% if step.count == funnel.0.count %}progress-success{% elif step.count == 0 %}progress-error{% else %}progress-warning{% endif %} w-full" value="{{ step.count }}"
|
<progress class="progress {% if step.count == funnel.0.count %}progress-success{% elif step.count == 0 %}progress-error{% else %}progress-warning{% endif %} w-full" value="{{ step.count }}"
|
||||||
max="{{ funnel.0.count }}"></progress>
|
max="{{ funnel.0.count }}"></progress>
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ DEFAULT_WIDGET_CLASS = "input input-bordered w-full"
|
|||||||
|
|
||||||
#: Icon, default heading and daisyUI colour per message level.
|
#: Icon, default heading and daisyUI colour per message level.
|
||||||
MESSAGE_ALERTS = {
|
MESSAGE_ALERTS = {
|
||||||
"debug": ("bug", "Debug", "alert-info"),
|
"debug": ("bug", "Debug", "alert-info border-info"),
|
||||||
"info": ("info", "Heads up", "alert-info"),
|
"info": ("info", "Heads up", "alert-info border-info"),
|
||||||
"success": ("circle-check", "Done", "alert-success"),
|
"success": ("circle-check", "Done", "alert-success border-success"),
|
||||||
"warning": ("triangle-alert", "Careful", "alert-warning"),
|
"warning": ("triangle-alert", "Careful", "alert-warning border-warning"),
|
||||||
"error": ("circle-x", "Something went wrong", "alert-error"),
|
"error": ("circle-x", "Something went wrong", "alert-error border-error"),
|
||||||
}
|
}
|
||||||
DEFAULT_MESSAGE_ALERT = MESSAGE_ALERTS["info"]
|
DEFAULT_MESSAGE_ALERT = MESSAGE_ALERTS["info"]
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ class ClubCreateView(PlatformStaffRequiredMixin, CreateView):
|
|||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse("controlpanel:club_detail", args=[self.object.pk])
|
return reverse("controlpanel:club_detail", args=[self.object.pk])
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
return super().get_context_data(nav="clubs", **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ClubUpdateView(PlatformStaffRequiredMixin, UpdateView):
|
class ClubUpdateView(PlatformStaffRequiredMixin, UpdateView):
|
||||||
model = Club
|
model = Club
|
||||||
@@ -94,6 +97,9 @@ class ClubUpdateView(PlatformStaffRequiredMixin, UpdateView):
|
|||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse("controlpanel:club_detail", args=[self.object.pk])
|
return reverse("controlpanel:club_detail", args=[self.object.pk])
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
return super().get_context_data(nav="clubs", update_view=True, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ClubDetailView(PlatformStaffRequiredMixin, DetailView):
|
class ClubDetailView(PlatformStaffRequiredMixin, DetailView):
|
||||||
model = Club
|
model = Club
|
||||||
|
|||||||
5312
static/css/app.css
5312
static/css/app.css
File diff suppressed because one or more lines are too long
@@ -85,7 +85,7 @@
|
|||||||
<div class="mb-6 w-full space-y-2">
|
<div class="mb-6 w-full space-y-2">
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
{% with alert=message|as_alert %}
|
{% with alert=message|as_alert %}
|
||||||
<div class="alert alert-soft {{ alert.css }}" role="alert">
|
<div class="alert alert-soft border-2 {{ alert.css }}" role="alert">
|
||||||
{% lucide alert.icon size=20 %}
|
{% lucide alert.icon size=20 %}
|
||||||
<div>
|
<div>
|
||||||
<div class="font-bold">{{ alert.title }}</div>
|
<div class="font-bold">{{ alert.title }}</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user