Merge branch 'jones-dev'

This commit is contained in:
Corban-Lee 2023-05-09 13:49:05 +01:00
commit 1406c2de24
7 changed files with 278 additions and 107 deletions

112
specs.md Normal file
View File

@ -0,0 +1,112 @@
# Project Name: AT Results
## Overview
This project involves the creation of a website for the Angling Trust to manage members, teams, and other aspects of their fishing events. The website will serve as a central hub for Angling Trust staff to access and manage data related to events, participants, and teams. The website will be designed to streamline the event management process, allowing staff to easily add and edit event details, manage participants and teams, and generate reports in the form of scoreboards.
## Client Requirements
### Functional Requirements
- The website should allow staff to manage participant and team registrations, including adding and editing participant and team details.
- The website should allow staff to display a scoreboard on event participation, team rosters, and other relevant data.
### Content Requirements
- The website should contain event details, including participants and their assigned teams.
- The website should contain participant and team details, including names..
### Design Requirements
- The website should follow the Angling Trust branding and colour scheme.
- The website must be easy to navigate and use. Displayed instructions must be clear and interfaces must be intuitive.
- The website must be responsive and mobile-friendly.
- Staff should be able to add and edit members.
- Staff should be able to add and edit teams.
- Staff should be able to add and edit sections.
- Staff should be able to assign members to teams.
- Staff should be able to assign members to sections.
- An error should be displayed when a staff member tries to assign a member to a section that contains another member in the same team.
- An error should appear if a staff member tries to change a peg number to that of another member's peg number, or, should recieve the option to swap the peg numbers.
- Members that aren't yet assigned to a team should be shown as such, and scoreboards should not include these members.
- Members that aren't yet assigned to a section should be shown as such, and scoreboards should not include these members.
- Staff should be able to prohibit certain alphabetic characters from being used as section identifiers.
### Technical Requirements
- The website should be built using a modern web development framework.
- The website should be hosted on a reliable and secure server.
- The website should incorporate appropriate security measures, such as encryption and authentication.
#### Teams
- Represents a team of members
- Must have a name represented by a whole number.
- No members on a team can have be on the same section.
- Teams may be limited to a set amount of members, add a setting to allow for this.
#### Members
- Members must represent a participant in the event
- Members should have a first and last name
- Members should have a unique whole number as their peg.
- A member must be assigned to a team.
- Members must be assigned to a section
- A member cannot be assigned to a section that already contains one of their teammates.
#### Section
- Sections represent a fishing area
- Sections must be uniquely identified by an alphabetical character.
- Unique identifiers should use an additional character should all other options be used.
- A section cannot have 2 members assigned to it that share the same team.
## Deliverables
- A fully functional website that meets the client's functional, content, design, and technical requirements.
- A user manual or other documentation that explains how to use and maintain the website.
## Timeline
- Project kickoff: September 2022.
- No specific end date set.
## Budget
- Total project budget is a Costco hotdog with no onions.
## Stakeholders
- Angling Trust staff members responsible for managing events and overseeing participant and team registrations.
## Approval
- A project demo presentation was carried out for Angling Trust staff

View File

@ -3,8 +3,8 @@
"model": "mainapp.member",
"pk": 1,
"fields": {
"first_name": "Shaquita",
"last_name": "Nichols",
"first_name": "Robert",
"last_name": "Reid",
"team": 1,
"peg_number": null
}
@ -13,8 +13,8 @@
"model": "mainapp.member",
"pk": 2,
"fields": {
"first_name": "Tawanna",
"last_name": "Kingsbury",
"first_name": "Ronald",
"last_name": "Jones",
"team": 1,
"peg_number": null
}
@ -23,8 +23,8 @@
"model": "mainapp.member",
"pk": 3,
"fields": {
"first_name": "Ricardo",
"last_name": "Benton",
"first_name": "Casey",
"last_name": "Cohen",
"team": 1,
"peg_number": null
}
@ -33,8 +33,8 @@
"model": "mainapp.member",
"pk": 4,
"fields": {
"first_name": "David",
"last_name": "Moore",
"first_name": "James",
"last_name": "Scudder",
"team": 2,
"peg_number": null
}
@ -43,8 +43,8 @@
"model": "mainapp.member",
"pk": 5,
"fields": {
"first_name": "Patricia",
"last_name": "Mader",
"first_name": "Randall",
"last_name": "Young",
"team": 2,
"peg_number": null
}
@ -53,8 +53,8 @@
"model": "mainapp.member",
"pk": 6,
"fields": {
"first_name": "Sue",
"last_name": "Coleman",
"first_name": "Helen",
"last_name": "Doak",
"team": 2,
"peg_number": null
}
@ -63,8 +63,8 @@
"model": "mainapp.member",
"pk": 7,
"fields": {
"first_name": "Brandon",
"last_name": "Goodwin",
"first_name": "Brenda",
"last_name": "Powell",
"team": 3,
"peg_number": null
}
@ -73,8 +73,8 @@
"model": "mainapp.member",
"pk": 8,
"fields": {
"first_name": "Travis",
"last_name": "Walls",
"first_name": "Constance",
"last_name": "Abild",
"team": 3,
"peg_number": null
}
@ -83,8 +83,8 @@
"model": "mainapp.member",
"pk": 9,
"fields": {
"first_name": "Donna",
"last_name": "Sanders",
"first_name": "Patsy",
"last_name": "Branham",
"team": 3,
"peg_number": null
}
@ -93,8 +93,8 @@
"model": "mainapp.member",
"pk": 10,
"fields": {
"first_name": "Debra",
"last_name": "Lu",
"first_name": "Cheryl",
"last_name": "Sears",
"team": 4,
"peg_number": null
}
@ -103,8 +103,8 @@
"model": "mainapp.member",
"pk": 11,
"fields": {
"first_name": "Edwin",
"last_name": "Goldberger",
"first_name": "Justin",
"last_name": "Cramer",
"team": 4,
"peg_number": null
}
@ -113,8 +113,8 @@
"model": "mainapp.member",
"pk": 12,
"fields": {
"first_name": "Peggy",
"last_name": "Lane",
"first_name": "Theodore",
"last_name": "Wilson",
"team": 4,
"peg_number": null
}
@ -123,8 +123,8 @@
"model": "mainapp.member",
"pk": 13,
"fields": {
"first_name": "Dora",
"last_name": "Eye",
"first_name": "Geneva",
"last_name": "Low",
"team": 5,
"peg_number": null
}
@ -133,8 +133,8 @@
"model": "mainapp.member",
"pk": 14,
"fields": {
"first_name": "Christopher",
"last_name": "Delap",
"first_name": "John",
"last_name": "Burtt",
"team": 5,
"peg_number": null
}
@ -143,8 +143,8 @@
"model": "mainapp.member",
"pk": 15,
"fields": {
"first_name": "John",
"last_name": "Reyes",
"first_name": "Alfred",
"last_name": "Diaz",
"team": 5,
"peg_number": null
}
@ -153,8 +153,8 @@
"model": "mainapp.member",
"pk": 16,
"fields": {
"first_name": "Robert",
"last_name": "Reveles",
"first_name": "Arthur",
"last_name": "Alton",
"team": 6,
"peg_number": null
}
@ -163,8 +163,8 @@
"model": "mainapp.member",
"pk": 17,
"fields": {
"first_name": "Cheryl",
"last_name": "Jones",
"first_name": "Vicki",
"last_name": "Greer",
"team": 6,
"peg_number": null
}
@ -173,8 +173,8 @@
"model": "mainapp.member",
"pk": 18,
"fields": {
"first_name": "Anne",
"last_name": "Thomas",
"first_name": "Lewis",
"last_name": "Segovia",
"team": 6,
"peg_number": null
}
@ -183,8 +183,8 @@
"model": "mainapp.member",
"pk": 19,
"fields": {
"first_name": "Erika",
"last_name": "Dolfi",
"first_name": "Vince",
"last_name": "Robinson",
"team": 7,
"peg_number": null
}
@ -193,8 +193,8 @@
"model": "mainapp.member",
"pk": 20,
"fields": {
"first_name": "Scott",
"last_name": "Hines",
"first_name": "Blake",
"last_name": "Mueller",
"team": 7,
"peg_number": null
}
@ -203,8 +203,8 @@
"model": "mainapp.member",
"pk": 21,
"fields": {
"first_name": "Min",
"last_name": "Baker",
"first_name": "Luis",
"last_name": "Hazel",
"team": 7,
"peg_number": null
}
@ -213,8 +213,8 @@
"model": "mainapp.member",
"pk": 22,
"fields": {
"first_name": "Laura",
"last_name": "Cleaves",
"first_name": "Diane",
"last_name": "Lloyd",
"team": 8,
"peg_number": null
}
@ -223,8 +223,8 @@
"model": "mainapp.member",
"pk": 23,
"fields": {
"first_name": "Deborah",
"last_name": "Pence",
"first_name": "Jamey",
"last_name": "Mendes",
"team": 8,
"peg_number": null
}
@ -233,8 +233,8 @@
"model": "mainapp.member",
"pk": 24,
"fields": {
"first_name": "Harvey",
"last_name": "Cabello",
"first_name": "Virgilio",
"last_name": "Nixon",
"team": 8,
"peg_number": null
}
@ -243,8 +243,8 @@
"model": "mainapp.member",
"pk": 25,
"fields": {
"first_name": "Shawn",
"last_name": "Mabe",
"first_name": "Rodney",
"last_name": "White",
"team": 9,
"peg_number": null
}
@ -253,8 +253,8 @@
"model": "mainapp.member",
"pk": 26,
"fields": {
"first_name": "Donald",
"last_name": "Duryea",
"first_name": "Kathleen",
"last_name": "Ashe",
"team": 9,
"peg_number": null
}
@ -263,8 +263,8 @@
"model": "mainapp.member",
"pk": 27,
"fields": {
"first_name": "John",
"last_name": "Mcinturff",
"first_name": "Stephanie",
"last_name": "Taylor",
"team": 9,
"peg_number": null
}
@ -273,8 +273,8 @@
"model": "mainapp.member",
"pk": 28,
"fields": {
"first_name": "Ivette",
"last_name": "Paterson",
"first_name": "John",
"last_name": "Brennan",
"team": 10,
"peg_number": null
}
@ -283,8 +283,8 @@
"model": "mainapp.member",
"pk": 29,
"fields": {
"first_name": "Mary",
"last_name": "Sussman",
"first_name": "Kenneth",
"last_name": "Duff",
"team": 10,
"peg_number": null
}
@ -293,8 +293,8 @@
"model": "mainapp.member",
"pk": 30,
"fields": {
"first_name": "Peter",
"last_name": "Faison",
"first_name": "Matthew",
"last_name": "Whitesell",
"team": 10,
"peg_number": null
}

View File

@ -3,70 +3,70 @@
"model": "mainapp.team",
"pk": 1,
"fields": {
"section_letter": "P"
"section_letter": "D"
}
},
{
"model": "mainapp.team",
"pk": 2,
"fields": {
"section_letter": "C"
"section_letter": "P"
}
},
{
"model": "mainapp.team",
"pk": 3,
"fields": {
"section_letter": "Y"
"section_letter": "L"
}
},
{
"model": "mainapp.team",
"pk": 4,
"fields": {
"section_letter": "B"
"section_letter": "M"
}
},
{
"model": "mainapp.team",
"pk": 5,
"fields": {
"section_letter": "T"
"section_letter": "Z"
}
},
{
"model": "mainapp.team",
"pk": 6,
"fields": {
"section_letter": "K"
"section_letter": "Y"
}
},
{
"model": "mainapp.team",
"pk": 7,
"fields": {
"section_letter": "N"
"section_letter": "J"
}
},
{
"model": "mainapp.team",
"pk": 8,
"fields": {
"section_letter": "S"
"section_letter": "W"
}
},
{
"model": "mainapp.team",
"pk": 9,
"fields": {
"section_letter": "Z"
"section_letter": "T"
}
},
{
"model": "mainapp.team",
"pk": 10,
"fields": {
"section_letter": "U"
"section_letter": "F"
}
}
]

View File

@ -16,11 +16,11 @@
</div>
<div class="col-sm-3 col-md-6 col-xl-4 mb-3 mb-sm-0">
<div class="input-group justify-content-xl-end">
<button class="btn btn-outline-secondary border-secondary-subtle">
<i class="bi bi-person" data-bs-toggle="tooltip" data-bs-title="Add Member"></i>
<button class="btn btn-outline-secondary border-secondary-subtle" id="addMember" data-bs-toggle="tooltip" data-bs-title="Add Member">
<i class="bi bi-person"></i>
</button>
<button class="btn btn-outline-secondary border-secondary-subtle">
<i class="bi bi-people" data-bs-toggle="tooltip" data-bs-title="Add Team"></i>
<button class="btn btn-outline-secondary border-secondary-subtle" id="addTeam" data-bs-toggle="tooltip" data-bs-title="Add Team">
<i class="bi bi-people"></i>
</button>
</div>
</div>
@ -32,7 +32,7 @@
<input type="search" class="form-control border-secondary-subtle shadow-none" placeholder="Search Members" id="search">
<button type="button" class="btn btn-outline-secondary border-secondary-subtle rounded-end" id="searchButton"><i class="bi bi-search"></i></button>
<form id="sortForm">
<ul class="dropdown-menu w-100 py-3 text-body-secondary" onclick="event.stopPropagation()">
<ul class="dropdown-menu w-100 py-3 text-body-secondary border-secondary-subtle shadow-sm" onclick="event.stopPropagation()">
<li class="px-4">
<h3 class="h6 mb-3">Sort teams by</h3>
<div>

22
src/static/js/custom.js Normal file
View File

@ -0,0 +1,22 @@
/**
* Custom javascript for the entire website.
* Add to this file for javascript that will be ran globally.
*/
$(document).ready(() => {
// Enable all tooltips on the page
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
});
/**
* Returns boolean if the string is empty or only contains whitespace.
*
* @param {string} string - The string to check against.
* @returns {boolean} - The result as boolean
*/
function isEmptyOrSpaces(string) {
return string === null || string.match(/^ *$/) !== null;
}

View File

@ -5,17 +5,12 @@ jQuery.expr[':'].icontains = function(a, i, m) {
$(document).ready(() => {
// Enable all tooltips on the page
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
teamsLoading(true); // show the loading icon
fetchAndLoadTeams(); // load the teams
// Search functionality
var searchTimeout = null;
// Bind searching to when a key is lifted on the searchbar
$("#search").keyup(() => {
clearTimeout(searchTimeout);
teamsLoading(true);
@ -25,18 +20,32 @@ $(document).ready(() => {
}, 500)
});
// Bind searching to the search button
$("#searchButton").on("click", () => {
fetchAndLoadTeams(...getFilters());
});
$("#sortForm input").on("click", () => {
// Bind searching to the sort buttons
$("#sortForm input").on("click", function() {
const name = $(this).attr("name");
localStorage.setItem(name, $(`input[name='${name}']:checked`, "#sortForm").val());
fetchAndLoadTeams(...getFilters());
});
// Edit member form validation options
$("#editMemberForm").validate({
errorClass: "text-danger mb-2"
})
// Load the last saved sort settings TODO: use local storage so it only saves on one reload
const sortTeamsValue = localStorage.getItem("sortTeams");
if (sortTeamsValue !== null) {
$("#sortForm input[name='sortTeams']").val([sortTeamsValue]);
}
const sortMembersValue = localStorage.getItem("sortMembers");
if (sortMembersValue !== null) {
$("#sortForm input[name='sortMembers']").val([sortMembersValue]);
}
// Customize form validation for the edit members form
$("#editMemberForm").validate({ errorClass: "text-danger mb-2" });
// Prevent dropdowns from closing when clicking inside
$('.dropdown-menu').on('hide.bs.dropdown', function (e) {
@ -47,8 +56,17 @@ $(document).ready(() => {
return true;
}
});
// load the teams with default filters
fetchAndLoadTeams(...getFilters());
});
/**
* Returns an array of search filters in this order [search, sortTeams, sortMembers]
*
* @returns {Array} The array of filters, each filter is a string.
*/
function getFilters() {
return [
$("#search").val(),
@ -57,14 +75,6 @@ function getFilters() {
]
}
/**
* Returns bool if the string is empty or only contains whitespace
*
* **/
function isEmptyOrSpaces(string) {
return string === null || string.match(/^ *$/) !== null;
}
function loadTeams(teams, highlightText="") {
$("#teamsContainer").html(""); // Clear the previous listed teams
@ -99,7 +109,7 @@ function loadTeams(teams, highlightText="") {
</div>`
);
// Whilimage.pnge we have the team, iterate over and add it's members
// While we have the team, iterate over and add it's members
team.members.forEach((member) => {
const fullname = member.first + " " + member.last;
@ -125,7 +135,7 @@ function loadTeams(teams, highlightText="") {
</button>
</div>
</li>`
);
)
});
});
@ -139,6 +149,10 @@ function loadTeams(teams, highlightText="") {
// Bind edit team modal to the edit team button
$(".team > h3 > button").on("click", function() {
$(this).addClass("active");
$("#editTeamModal").off("hidden.bs.modal").on("hidden.bs.modal", function() {
$("#teamsContainer").find(".team").find("button.active").removeClass("active");
});
openEditTeamModal($(this).parent().parent().data("number"));
});
@ -150,6 +164,15 @@ function loadTeams(teams, highlightText="") {
});
openEditMemberModal($(this).parent().data("member-id"));
});
// Bind new member/team buttons
$("#addTeam").on("click", () => {
openEditTeamModal(-1);
});
$("#addMember").on("click", () => {
openEditMemberModal(-1);
});
}
function fetchAndLoadTeams(search="", sortTeams="", sortMembers="") {
@ -158,9 +181,9 @@ function fetchAndLoadTeams(search="", sortTeams="", sortMembers="") {
type: "post",
data: {
"csrfmiddlewaretoken": csrfMiddlewareToken,
"search": !isEmptyOrSpaces(search) ? search : null, // might not be necessary? TODO: re-evaluate
"sortTeams": sortTeams ? sortTeams : null,
"sortMembers": sortMembers ? sortMembers : null
"search": search,
"sortTeams": sortTeams,
"sortMembers": sortMembers
},
error: (xhr, textStatus, errorThrown) => { alert(errorThrown); },
success: (result) => {
@ -200,12 +223,25 @@ function openEditTeamModal(teamNumber) {
function openEditMemberModal(memberId) {
var modalTitle = "Add Member";
var first = "";
var last = "";
var teamNumber = $(".team").map(function() { return $(this).data("number"); }).get(); // TODO: shorten this
var pegNumber = 0;
var firstPlaceholder = "Forename";
var lastPlaceholder = "Surname";
// Member data
const member = $(`.team-member[data-member-id='${memberId}']`);
const first = member.data("first");
const last = member.data("last");
const teamNumber = member.data("team-number");
const pegNumber = member.data("peg-number");
if (memberId !== -1) {
const member = $(`.team-member[data-member-id='${memberId}']`);
first = member.data("first");
last = member.data("last");
teamNumber = member.data("team-number");
pegNumber = member.data("peg-number");
firstPlaceholder = first;
lastPlaceholder = last;
modalTitle = "Edit: " + first + " " + last
}
// Load teams as options
$("#editMemberTeam").html("");
@ -216,14 +252,14 @@ function openEditMemberModal(memberId) {
});
// Load data to form
$("#editMemberName").text(first + " " + last);
$("#editMemberName").text(modalTitle);
$("#editMemberFirstName").val(first);
$("#editMemberLastName").val(last);
$("#editMemberTeam").val(teamNumber);
$("#editMemberPeg").val(pegNumber);
$("#editMemberFirstName").attr("placeholder", first);
$("#editMemberLastName").attr("placeholder", last);
$("#editMemberFirstName").attr("placeholder", firstPlaceholder);
$("#editMemberLastName").attr("placeholder", lastPlaceholder);
$("#editMemberModal").modal("show");

View File

@ -20,6 +20,7 @@
<script src="{% static 'js/jquery-3.6.3.min.js' %}"></script>
<script src="{% static 'js/jquery.validate.min.js' %}"></script>
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
<script src="{% static 'js/custom.js' %}"></script>
{% block scripts %}
{% endblock scripts %}
</body>