sidebar rewrite

This commit is contained in:
Corban-Lee 2023-05-18 14:44:42 +01:00
parent 426f381da2
commit 724e6ed544
12 changed files with 732 additions and 122 deletions

View File

@ -1,4 +1,4 @@
# Generated by Django 4.1.5 on 2023-05-12 09:13
# Generated by Django 4.1.5 on 2023-05-15 11:20
from django.db import migrations, models
import django.db.models.deletion
@ -23,7 +23,7 @@ class Migration(migrations.Migration):
name='Team',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('number', models.PositiveIntegerField()),
('number', models.PositiveIntegerField(unique=True)),
],
),
migrations.CreateModel(

View File

@ -10,8 +10,52 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" />
{% endblock style %}
{% block header_buttons %}
<a href="/" class="btn btn-company px-4">Back</a>
{% endblock header_buttons %}
{% block content %}
<div class="p-4 row">
<div class="col-md-3">
<div class="input-group">
<button type="button" data-bs-toggle="dropdown" class="btn btn-outline-company border-secondary-subtle">
<i class="bi bi-sort-up"></i>
</button>
<input type="search" name="search" id="search" class="form-control border-secondary-subtle shadow-none" placeholder="Search Anglers">
<button type="button" class="btn btn-outline-company border-secondary-subtle rounded-end" id="searchButton"><i class="bi bi-search"></i></button>
</div>
</div>
<div class="col-md">
<div class="input-group">
<button class="btn border-secondary-subtle btn-outline-company me-4 rounded-end" id="addAngler" data-bs-toggle="tooltip" data-bs-title="Add Angler" data-bs-custom-class="light-tooltip">
<i class="bi bi-plus-lg"></i>
</button>
<button class="btn border-secondary-subtle btn-outline-company rounded-start" id="ContractView" data-bs-toggle="tooltip" data-bs-title="Compact View" data-bs-custom-class="light-tooltip">
<i class="bi bi-arrows-angle-contract"></i>
</button>
<button class="btn border-secondary-subtle btn-outline-company" id="expandView" data-bs-toggle="tooltip" data-bs-title="Enlarged View" data-bs-custom-class="light-tooltip">
<i class="bi bi-arrows-angle-expand"></i>
</button>
</div>
</div>
</div>
<div class="px-4 mt-2 mb-4 row">
{% for angler in anglers %}
<div class="col-md-2 mb-4">
<div class="card w-100 h-100 fluid-hover-zoom shadow-sm md-shadow-on-hover">
<div class="card-body">
<h5 class="card-title">{{ angler.first_name }}</h5>
<h6 class="card-subtitle mb-2 text-body-secondary">{{ angler.last_name }}</h6>
</div>
</div>
</div>
{% endfor %}
</div>
{% comment %}
<!-- Header -->
<div class="container"></div>
<div class="bg-body p-4 d-flex">
@ -19,7 +63,6 @@
<img class="ms-auto" width="40" src="{% static 'img/logo.webp' %}">
</div>
<!-- End Header -->
<div class="container my-4 p-4 pb-0 bg-body rounded">
<!-- Controls Header -->
@ -127,6 +170,7 @@
<!-- Angler Modal -->
{% include "modals/angler.html" %}
<!-- End Angler Modal -->
{% endcomment %}
{% endblock content %}

View File

@ -2,6 +2,16 @@
{% block content %}
<div class="flex-grow-1" id="waterContainer">
<div id="drop"></div>
<div id="wave"></div>
</div>
<!-- <h2 class="h1 p-4">
This is the home page, there is no content here right now
</h2> -->
<!--
<div class="py-5 bg-body mt-5">
<div class="container">
<div class="text-center mb-4">
@ -12,16 +22,9 @@
</div>
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-4 justify-contentpaper">
<!-- Teams & Members -->
<div class="col">
<a href="{% url 'anglers' %}" class="text-decoration-none text-body">
<!-- <div class="fluid-hover-zoom shadow-sm md-shadow-on-hover bg-body-tertiary rounded p-5 text-body d-flex flex-wrap justify-content-center align-items-center">
<i class="bi bi-people w-100 text-center" style="font-size: 5rem;"></i>
<h3 class=" fs-3">
Member Management
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
</h3>
</div> -->
<div class="fluid-hover-zoom d-flex flex-wrap justify-content-center shadow-sm md-shadow on-hover bg-body-tertiary rounded text-center p-5 h-100">
<h3 class="h2 w-100">
Angler Management
@ -37,16 +40,9 @@
</a>
</div>
<!-- Scoreboard -->
<div class="col">
<a href="{% url 'scoreboard' %}" class="text-decoration-none text-body">
<!-- <div class="fluid-hover-zoom shadow-sm md-shadow-on-hover bg-body-tertiary rounded p-5 text-body d-flex flex-wrap justify-content-center align-items-center">
<i class="bi bi-list-stars w-100 text-center" style="font-size: 5rem;"></i>
<h3 class=" fs-3">
&ThickSpace; Scoreboard &ThickSpace;
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
</h3>
</div> -->
<div class="fluid-hover-zoom d-flex flex-wrap justify-content-center shadow-sm md-shadow on-hover bg-body-tertiary rounded text-center p-5 h-100">
<h3 class="h2 w-100">
Live Scoreboard
@ -62,16 +58,9 @@
</a>
</div>
<!-- Results -->
<div class="col">
<a href="{% url 'results' %}" class="text-decoration-none text-body">
<!-- <div class="fluid-hover-zoom shadow-sm md-shadow-on-hover bg-body-tertiary rounded p-5 text-body d-flex flex-wrap justify-content-center align-items-center">
<i class="bi bi-journal-text w-100 text-center" style="font-size: 5rem;"></i>
<h3 class=" fs-3">
&ThickSpace; Results &ThickSpace;
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
</h3>
</div> -->
<div class="fluid-hover-zoom d-flex flex-wrap justify-content-center shadow-sm md-shadow on-hover bg-body-tertiary rounded text-center p-5 h-100">
<h3 class="h2 w-100">
Competition Results
@ -89,5 +78,5 @@
</div>
</div>
</div>
</div> -->
{% endblock content %}

View File

@ -2,34 +2,36 @@
<div class="modal fade" id="anglerModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content border-0">
<form id="anglerForm">
<!-- Modal Header -->
<div class="modal-header border-0">
<h3 class="modal-title fs-5 color" id="anglerTitle">
Angler Modal Title
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
</div>
<!-- End Modal Header -->
<!-- Modal Header -->
<div class="modal-header border-0">
<h3 class="modal-title fs-5 color">
<span class="pe-3" id="anglerTitle">Angler Modal Title</span>
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
</div>
<!-- End Modal Header -->
<!-- Modal Body -->
<div class="modal-body">
<form id="anglerForm">
<!-- Modal Body -->
<div class="modal-body">
<!-- Angler Name -->
<div class="row g-3 mb-3">
<div class="col-md">
<div class="col-md-6">
<div class="form-floating">
<input class="form-control" type="text" name="anglerForename" id="anglerForename" placeholder="Forename">
<input class="form-control" type="text" name="anglerForename" id="anglerForename" placeholder="Forename" requred maxlength="30">
<label for="anglerForename">Forename</label>
</div>
<div id="anglerForenameError" class="text-danger my-2 error" style="display: none;"></div>
</div>
<div class="col-md">
<div class="col-md-6">
<div class="form-floating">
<input class="form-control" type="text" name="anglerSurname" id="anglerSurname" placeholder="Surname">
<input class="form-control" type="text" name="anglerSurname" id="anglerSurname" placeholder="Surname" requred maxlength="30">
<label for="anglerSurname">Surname</label>
</div>
<div id="anglerSurnameError" class="text-danger my-2 error" style="display: none;"></div>
</div>
</div>
<!-- End Angler Name -->
@ -39,16 +41,20 @@
<span class="input-group-text" data-bs-toggle="tooltip" data-bs-placement="left" data-bs-title="Team">
<i class="bi bi-people"></i>
</span>
<select class="form-select" name="anglerTeam" id="anglerTeam"></select>
<select class="form-select" name="anglerTeam" id="anglerTeam" required></select>
</div>
<div id="anglerTeamError" class="text-danger my-2 error" style="display: none;"></div>
<!-- End Angler Team -->
<!-- Angler Section -->
<div class="input-group mb-3">
<span class="input-group-text" data-bs-toggle="tooltip" data-bs-placement="left" data-bs-title="Section">
<i class="bi bi-layers"></i>
</span>
<select class="form-select" name="anglerSection" id="anglerSection"></select>
<div class="col-12 mb-3">
<div class="input-group">
<span class="input-group-text" data-bs-toggle="tooltip" data-bs-placement="left" data-bs-title="Section">
<i class="bi bi-layers"></i>
</span>
<select class="form-select" name="anglerSection" id="anglerSection" required></select>
</div>
<div id="anglerSectionError" class="text-danger my-2 error" style="display: none;"></div>
</div>
<!-- End Angler Section -->
@ -59,25 +65,27 @@
<span class="input-group-text" data-bs-toggle="tooltip" data-bs-placement="left" data-bs-title="Peg Number">
<i class="bi bi-tag"></i>
</span>
<input type="number" name="anglerPeg" id="anglerPeg" class="form-control" min="1" max="9999" value="1">
<input type="number" name="anglerPeg" id="anglerPeg" class="form-control" min="1" max="9999" required>
</div>
<button type="button" class="btn btn-outline-company border-secondary-subtle ms-3" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-title="Find next available peg">
<button type="button" id="anglerPegNext" class="btn btn-outline-company border-secondary-subtle ms-3" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-title="Find next available peg">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
<div class="col-md-7 mt-2 pe-md-3 text-danger error" id="anglerPegError" style="display:none;"></div>
</div>
<!-- End Angler Peg Number -->
</form>
</div>
<!-- End Modal Body -->
</div>
<!-- End Modal Body -->
<!-- Modal Footer -->
<div class="modal-footer border-0">
<button type="button" class="btn btn-company px-4" id="saveAngler">Save</button>
</div>
<!-- End Modal Footer -->
<!-- Modal Footer -->
<div class="modal-footer border-0 d-flex">
<button type="button" class="btn btn-outline-danger me-auto" style="display: hidden;" id="anglerDelete">Delete Angler</button>
<button type="submit" class="btn btn-company px-4" id="saveAngler">Save</button>
</div>
<!-- End Modal Footer -->
</form>
</div>
</div>
</div>

View File

@ -2,46 +2,47 @@
<div class="modal fade" id="teamModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content border-0">
<form id="teamForm">
<!-- Modal Header -->
<div class="modal-header border-0">
<h3 class="modal-title fs-5" id="teamTitle">
Team Modal Title
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
</div>
<!-- End Modal Header -->
<!-- Modal Header -->
<div class="modal-header border-0">
<h3 class="modal-title fs-5">
<span id="teamTitle" class="pe-3">Team Modal Title</span>
<div class="border-company border-bottom border-2 w-100 pt-1"></div>
</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
</div>
<!-- End Modal Header -->
<!-- Modal Body -->
<div class="modal-body">
<form id="teamForm">
<!-- Team Number -->
<div class="col-md-6 d-flex">
<div class="input-group">
<span class="input-group-text" data-bs-toggle="tooltip" data-bs-placement="left" data-bs-title="Team number">
<i class="bi bi-tag"></i>
</span>
<input type="number" name="teamNumber" id="teamNumber" class="form-control" min="1" max="9999" value="1">
<!-- Modal Body -->
<div class="modal-body">
<!-- Team Number -->
<div class="col-md-6 d-flex">
<div class="input-group">
<span class="input-group-text" data-bs-toggle="tooltip" data-bs-placement="left" data-bs-title="Team number">
<i class="bi bi-tag"></i>
</span>
<input type="number" name="teamNumber" id="teamNumber" class="form-control" min="1" max="9999" value="1">
</div>
<button type="button" id="teamNumberNext" class="btn btn-outline-company border-secondary-subtle ms-3" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-title="Find next available number">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
<button type="button" id="teamNumberNext" class="btn btn-outline-company border-secondary-subtle ms-3" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-title="Find next available number">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
<div class="col-md-6 mt-2 text-danger" id="teamNumberError" style="display: hidden"></div>
<!-- End Team Number -->
<div class="col-md-6 mt-2 text-danger" id="teamNumberError" style="display: hidden"></div>
<!-- End Team Number -->
</form>
</div>
<!-- End Modal Body -->
</div>
<!-- End Modal Body -->
<!-- Modal Footer -->
<div class="modal-footer border-0">
<button type="button" class="btn btn-company px-4" id="saveTeam">Save</button>
</div>
<!-- End Modal Footer -->
<!-- Modal Footer -->
<div class="modal-footer border-0 d-flex">
<button class="btn btn-outline-danger me-auto" id="teamDelete" style="display: none;">Delete Team</button>
<button type="submit" class="btn btn-company px-4" id="saveTeam">Save</button>
</div>
<!-- End Modal Footer -->
</form>
</div>
</div>
</div>

View File

@ -4,7 +4,7 @@ from functools import reduce
from django.shortcuts import render, redirect
from django.http import JsonResponse
from django.db.models import Q, Case, When, Value, IntegerField
from django.db.models import Q, Case, When, Value, IntegerField, Min
from django.db.utils import IntegrityError
from django.views import View
from django.views.decorators.http import require_GET, require_POST
@ -57,7 +57,10 @@ class ManageAnglersView(View):
HttpRequest: A render of the Manage Anglers page.
"""
return render(request, self.template_name)
anglers = Member.objects.order_by("first_name", "last_name")
context = {"anglers": anglers}
return render(request, self.template_name, context)
def post(self, request: HttpRequest, *args, **kwargs) -> JsonResponse:
"""Handle POST requests to the Manage Anglers page.
@ -99,6 +102,11 @@ class ManageAnglersView(View):
"get-teams": self.get_teams,
"get-sections": self.get_sections,
"get-anglers": self.get_anglers,
"delete-team": self.delete_team,
"delete-section": self.delete_section,
"delete-angler": self.delete_angler,
"get-nextTeamNumber": self.get_next_team_number,
"get-nextPegNumber": self.get_next_peg_number,
}
handler = task_handlers.get(task)
@ -127,7 +135,7 @@ class ManageAnglersView(View):
team.save()
result["team"] = {"id": team.id, "number": team.number}
except IntegrityError:
result["form_errors"]["#editTeamNumberError"] = "A Team with this number already exists"
result["form_errors"]["#teamNumber"] = "A Team with this number already exists"
return result
@ -188,9 +196,13 @@ class ManageAnglersView(View):
angler.team = team
angler.section = section
angler.save()
try:
angler.save()
except IntegrityError:
result["form_errors"]["#anglerPeg"] = "An Angler with this peg number already exists"
result["angler"] = {
"id": id,
"id": angler.id,
"forename": forename,
"surname": surname,
"peg_number": peg_number,
@ -205,7 +217,7 @@ class ManageAnglersView(View):
def get_teams(self, request) -> dict[str]:
"""Returns a dictionary of all teams."""
search = request.GET.get("search")
search = request.POST.get("search")
teams = Team.objects.order_by("number").all()
# Search works by exluding teams that do not contain members with the search term in their names.
@ -222,7 +234,7 @@ class ManageAnglersView(View):
def get_sections(self, request) -> dict[str]:
"""Returns a dictionary of all sections."""
search = request.GET.get("search")
search = request.POST.get("search")
sections = Section.objects.order_by("character").all()
if search:
@ -238,8 +250,9 @@ class ManageAnglersView(View):
def get_anglers(self, request) -> dict[str]:
"""Returns a dictionary of all anglers."""
search = request.GET.get("search")
search = request.POST.get("search")
anglers = Member.objects.order_by("first_name").all()
order_by = "peg_number" if request.POST.get("sortAnglers") == "pegs" else "first_name"
if search:
search_terms = search.split()
@ -260,10 +273,65 @@ class ManageAnglersView(View):
"team_number": angler.team.number,
"section_character": angler.section.character
}
for angler in anglers
for angler in anglers.order_by(order_by).all()
]
}
def delete_team(self, request) -> dict:
"""Deletes a team."""
team_id = request.POST.get("team_id")
if not team_id:
raise ValueError("Invalid team ID")
teams = Team.objects.get(id=team_id)
teams.delete()
return {}
def delete_section(self, request) -> dict:
"""Deletes a section."""
section_id = request.POST.get("section_id")
if not section_id:
raise ValueError("Invalid section ID")
sections = Section.objects.get(id=section_id)
sections.delete()
return {}
def delete_angler(self, request) -> dict:
"""Delete an angler."""
angler_id = request.POST.get("angler_id")
if not angler_id:
raise ValueError("Invalid angler ID")
angler = Member.objects.get(id=angler_id)
angler.delete()
return {}
def get_next_team_number(self, request) -> dict[str, int]:
"""Returns the next available team number."""
next_team_number = 1
while Team.objects.filter(number=next_team_number).exists():
next_team_number += 1
return {"nextTeamNumber": next_team_number}
def get_next_peg_number(self, request) -> dict[str, int]:
"""Returns the next available peg number."""
next_peg_number = 1
while Member.objects.filter(peg_number=next_peg_number).exists():
next_peg_number += 1
return {"nextPegNumber": next_peg_number}
@ -412,8 +480,8 @@ def update_team(request):
if not request.POST:
return
team_id = request.POST.get("teamId")
team_number = request.POST.get("teamNumber")
team_id = request.POST.get("id")
team_number = request.POST.get("number")
try:
if team_id == "-1":

View File

@ -58,6 +58,10 @@
font-family: "Source Sans Pro";
}
.font-helvetica {
font-family: Arial, Helvetica, sans-serif;
}
.pencil-btn {
width: 32px;
height: 32px;
@ -118,4 +122,154 @@ input[type='radio']:checked {
background-color: #04385c;
border: 1px solid #04385c;
box-shadow: none;
}
}
#sidebar {
width: 80px;
}
#webContent {
margin-left: 80px;
}
.light-tooltip {
--bs-tooltip-bg: #04385c;
}
#sidebar .nav-item > a:hover {
background-color: rgba(0, 0, 0, 0.15);
}
html, body {
height: 100%;
}
#waterContainer {
background-color: rgba(2, 40, 110, 0.70);
max-height: calc(100vh - 70px);
overflow: hidden;
}
:root {
--drip-time: 4s;
--drip-length: 600px;
}
#drop {
position: relative;
width: 20px;
height: 20px;
top: -30px;
margin: 0 auto;
background: #FFF;
-moz-border-radius: 20px;
-webkit-border-radius: 20px;
border-radius: 20px;
-moz-animation-name: drip;
-webkit-animation-name: drip;
animation-name: drip;
-moz-animation-timing-function: cubic-bezier(1,0,.91,.19);
-webkit-animation-timing-function: cubic-bezier(1,0,.91,.19);
animation-timing-function: cubic-bezier(1,0,.91,.19);
-moz-animation-duration: var(--drip-time);
-webkit-animation-duration: var(--drip-time);
animation-duration: var(--drip-time);
-moz-animation-iteration-count: infinite;
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
margin: 0 auto;
}
#drop:before {
content: "";
position: absolute;
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-bottom: 30px solid rgba(255,255,255,1);
top: -22px;
}
#wave {
position: relative;
opacity: 0;
top: 0;
width: 2px;
height: 1px;
border: #FFF 7px solid;
-moz-border-radius: 300px / 150px;
-webkit-border-radius: 300px / 150px;
border-radius: 300px / 150px;
-moz-animation-name: ripple;
-webkit-animation-name: ripple;
animation-name: ripple;
-moz-animation-delay: var(--drip-time);
-webkit-animation-delay: var(--drip-time);
animation-delay: var(--drip-time);
-moz-animation-duration: var(--drip-time);
-webkit-animation-duration: var(--drip-time);
animation-duration: var(--drip-time);
-moz-animation-iteration-count: infinite;
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
margin: var(--drip-length) auto 0 auto;
}
#wave:after {
content: "";
position: absolute;
opacity: 0;
top: -5px;
left: -5px;
width: 2px;
height: 1px;
border: #FFF 5px solid;
-moz-border-radius: 300px / 150px;
-webkit-border-radius: 300px / 150px;
border-radius: 300px / 150px;
-moz-animation-name: ripple-2;
-webkit-animation-name: ripple-2;
animation-name: ripple-2;
-moz-animation-duration: var(--drip-time);
-webkit-animation-duration: var(--drip-time);
animation-duration: var(--drip-time);
-moz-animation-iteration-count: infinite;
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
}
@keyframes ripple {
from {
opacity: 1;
}
to {
width: 600px;
height: 300px;
border-width: 1px;
top: -100px;
opacity: 0;
}
}
@keyframes ripple-2 {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
width: 200px;
height: 100px;
border-width: 1px;
top: 100px;
left: 200px;
}
}
@keyframes drip {
to {
top: var(--drip-length);
}
}

View File

@ -53,7 +53,7 @@
-webkit-overflow-scrolling: touch;
}
.card{
/* .card{
border-radius: 4px;
background: #fff;
box-shadow: 0 6px 10px rgba(0,0,0,.08), 0 0 6px rgba(0,0,0,.05);
@ -64,4 +64,4 @@
.card:hover{
transform: scale(1.05);
box-shadow: 0 10px 20px rgba(0,0,0,.12), 0 4px 8px rgba(0,0,0,.06);
}
} */

BIN
src/static/img/at-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -8,7 +8,9 @@ $(document).ready(() => {
activateTooltips();
pageLoadMotionEffects();
$("select").select2({theme: "bootstrap-5", minimumResultsForSearch: -1})
// $("select").select2({theme: "bootstrap-5", minimumResultsForSearch: -1})
$("#headerTitle").text(document.title);
});
@ -58,3 +60,8 @@ jQuery.expr[':'].icontains = function(a, i, m) {
const findByKey = (array, key, value) => {
return array.find(item => item[key] === value);
}
function isEmpty(obj) {
return Object.keys(obj).length === 0;
}

View File

@ -27,6 +27,16 @@ $(document).ready(() => {
fetchAndLoadGroups();
});
const showGroupsValue = localStorage.getItem("showGroups");
if (showGroupsValue !== null) {
$("#sortForm input[name='showGroups']").val([showGroupsValue]);
}
const sortAnglersValue = localStorage.getItem("sortAnglers");
if (sortAnglersValue !== null) {
$("#sortForm input[name='sortAnglers']").val([sortAnglersValue]);
}
// Modals
$("#addAngler").on("click", () => {
@ -51,7 +61,7 @@ function fetchAndLoadGroups() {
const showGroups = localStorage.getItem("showGroups");
const sortAnglers = localStorage.getItem("sortAnglers");
const task = showGroups === "teams" ? "get-teams" : "get-sections";
const search = $("#search").val()
$.ajax({
type: "POST",
url: pageUrl,
@ -59,7 +69,7 @@ function fetchAndLoadGroups() {
csrfmiddlewaretoken: csrfMiddlewareToken,
tasks: [task, "get-anglers"],
sortAnglers: sortAnglers,
search: $("#search").val()
search: isEmptyOrSpaces(search) ? "" : search,
},
error: (error) => {
console.error(error);
@ -74,18 +84,20 @@ function fetchAndLoadGroups() {
function groupTemplate(group, groupType, oppositeGroupType) {
groupValue = groupType === "teams" ? group.number : group.character
const nonPluralGroupType = groupType.slice(0, -1);
const editFunction = groupType === "teams"? "addTeam" : "addSection";
return `
<div class='col-12 col-md-6 col-xl-4 mb-4'>
<div
class='${groupType.toLowerCase()} px-4 py-3 bg-body-tertiary bg-gradient rounded h-100 fluid-hover-zoom shadow-sm md-shadow-on-hover'
data-value='${groupValue}'>
class='${nonPluralGroupType.toLowerCase()} px-4 py-3 bg-body-tertiary bg-gradient rounded h-100 fluid-hover-zoom shadow-sm md-shadow-on-hover'
data-id='${group.id}' data-value='${groupValue}'>
<h3>
<span class='fs-4'>
<span class='fs-6'>${groupType.toUpperCase()}</span>
<span class='fs-6'>${nonPluralGroupType.toUpperCase()}</span>
${groupValue}
</span>
<button class='btn btn-sm btn-light float-end border-0 me-1'>
<button onclick='${editFunction}(${group.id})' class='btn btn-sm btn-light float-end border-0 me-1'>
<i class='bi bi-gear fs-6'></i>
</button>
</h3>
@ -104,7 +116,7 @@ function anglerTemplate(angler, oppositeGroupType) {
oppositeGroup = oppositeGroup.charAt(0).toUpperCase() + oppositeGroup.slice(1);
return `
<li class='mb-3 rounded w-100 fluid-hover-zoom'>
<li class='mb-2 rounded w-100 fluid-hover-zoom'>
<div
class='angler d-flex'
data-first='${angler.first_name}'
@ -124,7 +136,7 @@ function anglerTemplate(angler, oppositeGroupType) {
${fullname}
</div>
</div>
<button type='button' class='ms-auto btn btn-light border-0 fs-6 pencil-btn align-self-center me-1 force-contents-center'>
<button type='button' onclick="addAngler(${angler.id})" class='ms-auto btn btn-light border-0 fs-6 pencil-btn align-self-center me-1 force-contents-center'>
<i class='bi bi-pencil'></i>
</button>
</div>
@ -140,10 +152,15 @@ function loadGroups(data) {
$("#groups").html("");
if (!groups.length) {
$("#notFound").show();
return;
}
groups.forEach((group) => {
$("#groups").append(groupTemplate(group, groupType, oppositeGroupType));
data.anglers.forEach((angler) => {
if (angler.team_id === group.id || angler.section_id === group.id) {
if ((groupType == "teams" && angler.team_id === group.id) || (groupType == "sections" && angler.section_id === group.id)) {
$("#groups").find(".anglers").last().append(
anglerTemplate(angler, oppositeGroupType)
);
@ -152,6 +169,13 @@ function loadGroups(data) {
});
activateTooltips();
if (!isEmptyOrSpaces($("#search").val())) {
$("#groups").find('.angler-fullname').each(function() {
var regex = new RegExp($("#search").val(), 'gi');
$(this).html($(this).text().replace(regex, '<span class="text-company fw-bolder border-2 border-bottom border-company">$&</span>'));
});
}
}
/**
@ -177,7 +201,182 @@ function toggleLoading(loading) {
* @param {Number} anglerId - ID of the angler, if -1 will create a new angler
*/
function addAngler(anglerId) {
// Reset the form values
$("#anglerTitle").text("Add New Angler");
$("#anglerForename").val("");
$("#anglerSurname").val("");
$("#anglerTeam").empty().trigger("change");
$("#anglerSection").empty().trigger("change");
$("#anglerPeg").val(1);
$("#anglerForm .error").text("").hide();
$("#anglerDelete").hide();
function validateSection() {
const sectionId = $("#anglerSection").val();
var valid = true;
// Ensure that no teammates are in this section
const teamId = $("#anglerTeam").val();
$(`.team[data-id='${teamId}'] .angler`).each(function() {
if ($(this).data("section-id") == sectionId && $(this).data("id") != anglerId) {
valid = false;
return false;
}
else {
$("#anglerSectionError").text("").hide();
}
});
return valid;
}
$("#anglerSection").change(() => {validateSection()});
$.ajax({
type: "POST",
url: pageUrl,
data: {
csrfmiddlewaretoken: csrfMiddlewareToken,
tasks: ["get-teams", "get-sections"]
},
error: (error) => {
console.error(error);
},
success: (data) => {
data.teams.forEach((team) => {
$("#anglerTeam").append(`<option value='${team.id}'>Team ${team.number}</option>`);
});
data.sections.forEach((section) => {
$("#anglerSection").append(`<option value='${section.id}'>Section ${section.character}</option>`);
});
if (anglerId !== -1) {
const angler = $(`.angler[data-id='${anglerId}']`)
const forename = angler.data("first");
const surname = angler.data("last");
const peg = angler.data("peg-number");
const teamId = angler.data("team-id");
const sectionId = angler.data("section-id");
$("#anglerTitle").text(`Edit ${forename} ${surname}`);
$("#anglerForename").val(forename);
$("#anglerSurname").val(surname);
$("#anglerTeam").val(teamId).trigger("change");
$("#anglerSection").val(sectionId).trigger("change");
$("#anglerPeg").val(peg);
$("#anglerDelete").show();
}
}
});
$("#anglerModal").modal("show");
$("#anglerForm").off("submit").on("submit", (e) => {
e.preventDefault();
$("#anglerForm .error").text("").hide();
const forename = $("#anglerForename").val();
const surname = $("#anglerSurname").val();
const teamId = $("#anglerTeam").val();
const sectionId = $("#anglerSection").val();
const peg = $("#anglerPeg").val();
const validSection = validateSection();
1
var formErrors = !(forename && surname && teamId && sectionId && peg && validSection);
// This form validation is not very scalable
if (!forename) {
$("#anglerForenameError").text("Please enter a forename").show();
}
if (!surname) {
$("#anglerSurnameError").text("Please enter a surname").show();
}
if (!teamId) {
$("#anglerTeamError").text("Please select a team").show();
}
if (!sectionId) {
$("#anglerSectionError").text("Please select a section").show();
}
if (!peg) {
$("#anglerPegError").text("Please enter a peg number").show();
}
if (!validSection) {
$("#anglerSectionError").text("Anglers cannot share a section with a teammate").show();
}
if (formErrors) {
return;
}
$.ajax({
type: "POST",
url: pageUrl,
data: {
csrfmiddlewaretoken: csrfMiddlewareToken,
tasks: ["update-angler"],
angler_id: anglerId,
forename: forename,
surname: surname,
team_id: teamId,
section_id: sectionId,
peg_number: peg
},
error: (error) => {
console.error(error);
},
success: (data) => {
if (isEmpty(data.form_errors)) {
fetchAndLoadGroups();
$("#anglerModal").modal("hide");
return;
}
for(var inputId in data.form_errors) {
var errorMessage = data.form_errors[inputId];
$(`${inputId}Error`).text(errorMessage).show();
}
}
});
});
$("#anglerDelete").off("click").on("click", () => {
$.ajax({
type: "POST",
url: pageUrl,
data: {
csrfmiddlewaretoken: csrfMiddlewareToken,
tasks: ["delete-angler"],
angler_id: anglerId
},
error: (error) => {
console.error(error);
},
success: (data) => {
fetchAndLoadGroups();
$("#anglerModal").modal("hide");
alert("Angler Deleted");
}
});
});
$("#anglerPegNext").off("click").on("click", () => {
$.ajax({
type: "POST",
url: pageUrl,
data: {
csrfmiddlewaretoken: csrfMiddlewareToken,
tasks: ["get-nextPegNumber"]
},
error: (error) => {
console.error(error);
},
success: (data) => {
$("#anglerPeg").val(data.nextPegNumber);
}
});
});
}
/**
@ -186,7 +385,96 @@ function addAngler(anglerId) {
* @param {Number} teamId - ID of the team, if -1 will create a new team
*/
function addTeam(teamId) {
// Reset the form values
$("#teamNumberError").text("").hide();
if (teamId !== -1) {
const team = $(`.team[data-id='${teamId}']`)
const number = team.data("value");
$("#teamTitle").text(`Edit Team #${number}`);
$("#teamNumber").val(number);
$("#teamDelete").show();
}
else {
$("#teamTitle").text("Add New Team");
$("#teamNumber").val(1);
$("#teamDelete").hide();
}
$("#teamModal").modal("show");
$("#teamForm").off("submit").on("submit", (e) => {
e.preventDefault();
const number = $("#teamNumber").val();
if (!number) {
$("#teamNumberError").text("Please enter a team number").show();
return;
}
$.ajax({
type: "POST",
url: pageUrl,
data: {
csrfmiddlewaretoken: csrfMiddlewareToken,
tasks: ["update-team"],
id: teamId,
number: number
},
error: (error) => {
console.error(error);
},
success: (data) => {
if (isEmpty(data.form_errors)) {
fetchAndLoadGroups();
$("#teamModal").modal("hide");
return;
}
for(var inputId in data.form_errors) {
var errorMessage = data.form_errors[inputId];
$(`${inputId}Error`).text(errorMessage).show();
}
}
});
});
$("#teamDelete").off("click").on("click", () => {
$.ajax({
type: "POST",
url: pageUrl,
data: {
csrfmiddlewaretoken: csrfMiddlewareToken,
tasks: ["delete-team"],
team_id: teamId
},
error: (error) => {
console.error(error);
},
success: (data) => {
fetchAndLoadGroups();
$("#teamModal").modal("hide");
}
});
});
$("#teamNumberNext").off("click").on("click", () => {
$.ajax({
type: "POST",
url: pageUrl,
data: {
csrfmiddlewaretoken: csrfMiddlewareToken,
tasks: ["get-nextTeamNumber"]
},
error: (error) => {
console.error(error);
},
success: (data) => {
$("#teamNumber").val(data.nextTeamNumber);
}
});
});
}
/**

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}{% endblock title %}AT Results</title>
<title>{% block title %}{% endblock title %}Angling Trust Results</title>
{% load static %}
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
@ -14,9 +14,60 @@
{% block style %}
{% endblock style %}
</head>
<body class="font-ssp company-bg">
{% block content %}
{% endblock content %}
<body class="font-helvetica mw-100">
<div class="d-flex align-content-stretch h-100 align-self-stretch">
<aside class="company-bg text-white d-flex flex-column flex-shrink-0 py-3 position-fixed h-100" id="sidebar">
<a href="/" class="d-flex align-items-center mb-3 mb-md-0 text-reset text-decoration-none px-2">
<img src="{% static 'img/at-logo.png' %}" alt="" class="w-100">
</a>
<hr class="mx-2">
<ul class="nav nav-pills flex-column mb-auto">
<li class="nav-item" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-custom-class="light-tooltip" data-bs-title="Home">
<a class="nav-link text-reset d-flex align-items-center py-3" href="/">
<i class="bi bi-house-fill fs-4 mx-auto"></i>
</a>
</li>
<li class="nav-item" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-custom-class="light-tooltip" data-bs-title="Anglers">
<a class="nav-link text-reset d-flex align-items-center py-3" href="/anglers">
<i class="bi bi-people-fill fs-4 mx-auto"></i>
</a>
</li>
<li class="nav-item" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-custom-class="light-tooltip" data-bs-title="Matches">
<a class="nav-link text-reset d-flex align-items-center py-3" href="#">
<i class="bi bi-award-fill fs-4 mx-auto"></i>
</a>
</li>
<li class="nav-item" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-custom-class="light-tooltip" data-bs-title="Scoreboard">
<a class="nav-link text-reset d-flex align-items-center py-3" href="#">
<i class="bi bi-clipboard-data-fill fs-4 mx-auto"></i>
</a>
</li>
</ul>
<ul class="nav nav-pills flex-column">
<li class="nav-item" data-bs-toggle="tooltip" data-bs-placement="right" data-bs-custom-class="light-tooltip" data-bs-title="Admin">
<a class="nav-link text-reset d-flex align-items-center py-3" href="/admin">
<i class="bi bi-person-fill-gear fs-4 mx-auto"></i>
</a>
</li>
</ul>
</aside>
<div class="flex-grow-1 d-flex flex-column h-100" id="webContent">
<div class="bg-body-tertiary shadow-sm p-3 d-flex align-items-center">
<h1 class="h2 mb-0" id="headerTitle"></h1>
<div class="ms-auto">
{% block header_buttons %}
{% endblock header_buttons %}
</div>
</div>
<div class="flex-grow-1 d-flex">
{% block content %}
{% endblock content %}
</div>
</div>
</div>
<script src="{% static 'js/jquery-3.6.3.min.js' %}"></script>
<script src="{% static 'js/jquery.validate.min.js' %}"></script>