sidebar rewrite
This commit is contained in:
parent
426f381da2
commit
724e6ed544
@ -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(
|
||||
|
@ -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 %}
|
||||
|
||||
|
@ -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">
|
||||
   Scoreboard   
|
||||
<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">
|
||||
   Results   
|
||||
<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 %}
|
||||
|
@ -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>
|
@ -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>
|
@ -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":
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
BIN
src/static/img/at-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user