New event: space out the stacked cards, rework the kind picker to 4 tiles
- The kind-tile grid, title/date/location card, Who card, and Answers-close card were direct children of the form with no gap between them at all -- the wrapping flex/gap-4 on coach-sheet's own content div only ever applied between the header bar and the form, not what's inside it. The form itself is now flex flex-col gap-4. - The picker offered Practice/Game/Other, none of which actually changed which fields render. Reworked to Practice/Game/Tournament/Meeting (2x2) -- the four kinds worth adding from the app; social/other stay desktop-only. Also narrowed the form's own kind choices to match server-side, not just cosmetically in the template. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,11 @@ IN_STATUSES = [Attendance.AttendanceStatus.PRESENT, Attendance.AttendanceStatus.
|
||||
#: Distinct from NO_RESPONSE ("silent"), which is a non-answer rather than a no.
|
||||
OUT_STATUSES = [Attendance.AttendanceStatus.ABSENT, Attendance.AttendanceStatus.EXCUSED, Attendance.AttendanceStatus.NOT_SELECTED]
|
||||
|
||||
#: The event kinds CoachCreateEventView's tile picker offers -- social/other
|
||||
#: stay desktop-only (management.forms.EventForm keeps the full list), since
|
||||
#: neither has a tile here.
|
||||
COACH_EVENT_KINDS = [Event.EventKind.TRAINING, Event.EventKind.GAME, Event.EventKind.TOURNAMENT, Event.EventKind.MEETING]
|
||||
|
||||
#: How long an event stays "current" (CoachTodayView's session card, and the
|
||||
#: missing-line-up nudge) past the moment it starts -- events.start__gte=now
|
||||
#: alone would flip to the next session the instant this one begins, while
|
||||
@@ -321,6 +326,10 @@ class CoachCreateEventView(CoachScopeMixin, LoginRequiredMixin, TemplateView):
|
||||
# referee-count control this screen has no use for; construct_instance
|
||||
# skips deleted fields entirely, leaving the instance's own default.
|
||||
del form.fields["max_referees"]
|
||||
# Narrowed to the four kinds the tile picker actually offers -- social/
|
||||
# other don't get their own tile, and this keeps a tampered request from
|
||||
# setting one anyway (the desktop form still offers the full list).
|
||||
form.fields["kind"].choices = [choice for choice in form.fields["kind"].choices if choice[0] in COACH_EVENT_KINDS]
|
||||
# The desktop searchable multi-select relies on management's own JS
|
||||
# widget, not loaded here -- plain checkboxes work without it and
|
||||
# read better on a phone regardless.
|
||||
|
||||
@@ -24,10 +24,10 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form id="coach-event-form" method="post" action="{% url "mobile:coach_create_event" %}" hx-boost="false">
|
||||
<form id="coach-event-form" class="flex flex-col gap-4" method="post" action="{% url "mobile:coach_create_event" %}" hx-boost="false">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<label class="flex h-12 items-center justify-center rounded-lg border border-line bg-white font-display text-xs font-extrabold tracking-wide text-muted uppercase has-checked:border-ink has-checked:bg-ink has-checked:text-white">
|
||||
<input class="sr-only" type="radio" name="kind" value="training" checked>
|
||||
{% trans "Practice" %}
|
||||
@@ -37,10 +37,15 @@
|
||||
{% trans "Game" %}
|
||||
</label>
|
||||
<label class="flex h-12 items-center justify-center rounded-lg border border-line bg-white font-display text-xs font-extrabold tracking-wide text-muted uppercase has-checked:border-ink has-checked:bg-ink has-checked:text-white">
|
||||
<input class="sr-only" type="radio" name="kind" value="other">
|
||||
{% trans "Other" %}
|
||||
<input class="sr-only" type="radio" name="kind" value="tournament">
|
||||
{% trans "Tournament" %}
|
||||
</label>
|
||||
<label class="flex h-12 items-center justify-center rounded-lg border border-line bg-white font-display text-xs font-extrabold tracking-wide text-muted uppercase has-checked:border-ink has-checked:bg-ink has-checked:text-white">
|
||||
<input class="sr-only" type="radio" name="kind" value="meeting">
|
||||
{% trans "Meeting" %}
|
||||
</label>
|
||||
</div>
|
||||
{% for error in form.kind.errors %}<p class="text-xs text-club-dark">{{ error }}</p>{% endfor %}
|
||||
|
||||
<div class="m-card flex flex-col p-4">
|
||||
<div>
|
||||
|
||||
@@ -3464,6 +3464,40 @@ class CoachCreateEventViewTests(TestCase):
|
||||
self.assertEqual(response.status_code, 403)
|
||||
self.assertFalse(Event.objects.filter(title="Blocked practice").exists())
|
||||
|
||||
def test_kind_choices_are_limited_to_the_tile_picker(self):
|
||||
self.client.force_login(self.user)
|
||||
|
||||
response = self.client.get(reverse("mobile:coach_create_event"), HTTP_HOST="ajax-united.rosterchief.app")
|
||||
|
||||
kind_choices = {value for value, _label in response.context["form"].fields["kind"].choices}
|
||||
self.assertEqual(kind_choices, {"training", "game", "tournament", "meeting"})
|
||||
|
||||
def test_can_create_a_tournament(self):
|
||||
self.client.force_login(self.user)
|
||||
|
||||
self._post(kind="tournament", title="Regional tournament")
|
||||
|
||||
event = Event.objects.get(title="Regional tournament")
|
||||
self.assertEqual(event.kind, Event.EventKind.TOURNAMENT)
|
||||
|
||||
def test_can_create_a_meeting(self):
|
||||
self.client.force_login(self.user)
|
||||
|
||||
self._post(kind="meeting", title="Team meeting")
|
||||
|
||||
event = Event.objects.get(title="Team meeting")
|
||||
self.assertEqual(event.kind, Event.EventKind.MEETING)
|
||||
|
||||
def test_other_and_social_are_rejected(self):
|
||||
self.client.force_login(self.user)
|
||||
|
||||
for kind in ("other", "social"):
|
||||
with self.subTest(kind=kind):
|
||||
response = self._post(kind=kind, title=f"Not a {kind} event")
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertFalse(Event.objects.filter(title=f"Not a {kind} event").exists())
|
||||
|
||||
def test_missing_title_reshows_the_form_with_errors(self):
|
||||
self.client.force_login(self.user)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user