Compare commits

...

26 Commits

Author SHA1 Message Date
Corban-Lee
75a97d762b Update specs.md 2023-05-09 22:11:04 +01:00
Corban-Lee
b8ed427cf9 UI redesign for teams page 2023-05-09 22:11:01 +01:00
Corban-Lee
bb721f2894 Added new fonts for UI redesign 2023-05-09 22:10:22 +01:00
Corban-Lee
1406c2de24 Merge branch 'jones-dev' 2023-05-09 13:49:05 +01:00
Corban-Lee
2b7645e57c Update specs.md
Added new requirements
2023-05-09 13:48:51 +01:00
Corban-Lee
8b16cc76e6 created specs markdown 2023-05-09 12:06:13 +01:00
Corban-Lee
af081df0cf working on modal for new members 2023-05-09 09:59:05 +01:00
Corban-Lee
79d391d171 JS cleanup [read comment]
Moved global js to new file custom.js and documented teams.js
2023-05-09 09:42:21 +01:00
Corban-Lee
78e4361452 first load of teams now uses default filtesr 2023-05-09 01:56:30 +01:00
Corban-Lee
57615ffd4d Moved tooltips from icons to buttons
The tooltips for the placeholder buttons 'New Member' and 'New Team' were incorrectly placed on the icons instead of the entire button, causing confusion triggering the tooltips.

I have moved these and also changed the border colour and added some shadow to the search sort dropdown menu to match the style of the search bar and buttons.
2023-05-09 01:52:15 +01:00
Corban-Lee
0a594ef666 Sort controls persistence between page refreshing 2023-05-09 01:39:21 +01:00
Corban-Lee
f9c0c0f279
Merge pull request #1 from jamesparkin675/jones-dev
Jones dev
2023-05-09 01:38:35 +01:00
Corban-Lee
078af05850 Altered responsiveness of teams page 2023-05-09 01:00:52 +01:00
Corban-Lee
96e0f7960f Refactored 'create_teams_fixture' 2023-05-09 00:31:10 +01:00
Corban-Lee
73c55fc301 Working on the edit teams modal 2023-05-08 17:21:07 +01:00
Corban-Lee
8fdb4a624c Added sorting for teams and members 2023-05-08 12:35:31 +01:00
Corban-Lee
2483bbbf5c Form validation 2023-05-08 00:42:30 +01:00
Corban-Lee
89825676e8 Made dependencies local instead of relying on a CDN
I failed to pay any attention to this until my internet went down last night while I was working on this - it didn't come back on for hours.

Having dependencies like this as local should have been a number one priority.
2023-05-07 23:59:22 +01:00
Corban-Lee
77b8c2fc2d Finalisation of teams/member model. Added testing script. 2023-05-07 23:20:34 +01:00
Corban-Lee
8afd3fa905 Integrated models for teams & members page 2023-05-06 18:57:45 +01:00
Corban-Lee
8394de0dcb improved search css 2023-05-06 15:47:34 +01:00
Corban-Lee
439ae32a79 improved search and UI on teams page 2023-05-06 04:09:18 +01:00
Corban-Lee
0f1c839daa Added live search to teams demo page
Live search using ajax
2023-05-05 00:24:10 +01:00
Corban-Lee
05efd3109e Rewrite for the templates + teams page demo
Wrote a demo page as concept for the teams page.
2023-05-04 23:20:29 +01:00
Corban-Lee
4d6bf1ff59 added launch file for debugging 2023-05-04 21:12:14 +01:00
Corban-Lee
c9aaf9511a removed ignore files from git cache 2023-05-04 21:10:56 +01:00
2041 changed files with 14454 additions and 199 deletions

166
.gitignore vendored
View File

@ -1,8 +1,160 @@
/venv
venv
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
pyvenv.cfg
*.pyc
venv/pyvenv.cfg
src/db.sqlite3
*.sqlite3
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

19
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,19 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Django",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}\\src\\manage.py",
"args": [
"runserver"
],
"django": true,
"justMyCode": true
}
]
}

23
create_dummy_data.bat Normal file
View File

@ -0,0 +1,23 @@
@echo off
echo The purpose of this file is to populate the database with dummy data for testing. Running this WILL DELETE your current sqlite3 database.
set /p confirm=Do you want to continue? [y/n]
if /i "%confirm%"=="y" (
cd /d %~dp0
if exist src\db.sqlite3 (
del src\db.sqlite3
)
call venv\Scripts\activate.bat
python src/manage.py migrate
python src/manage.py create_teams_fixture 10
python src/manage.py loaddata teams_fixture
python src/manage.py create_members_fixture 3
python src/manage.py loaddata members_fixture
deactivate
) else (
echo Exiting script...
)

106
specs.md Normal file
View File

@ -0,0 +1,106 @@
# 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, teams and sections.
- Staff should be able to assign: members to teams and 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

@ -59,7 +59,7 @@ ROOT_URLCONF = 'Results.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'mainapp/templates'],
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [

Binary file not shown.

View File

@ -2,7 +2,7 @@
from django.contrib import admin
from .models import Peg, Member, Team, Section
from .models import Peg, Member, Team
@admin.register(Peg)
@ -19,9 +19,9 @@ class PegAdmin(admin.ModelAdmin):
class MemberAdmin(admin.ModelAdmin):
"""Admin model for the Member model."""
list_display = ("first_name", "last_name", "user", "team", "peg")
search_fields = ("first_name", "last_name", "user", "team", "peg")
list_filter = ("team", "peg")
list_display = ("first_name", "last_name", "team")
search_fields = ("first_name", "last_name", "team")
list_filter = ("team",)
@admin.register(Team)
@ -33,9 +33,9 @@ class TeamAdmin(admin.ModelAdmin):
search_fields = ("team_number",)
@admin.register(Section)
class SectionAdmin(admin.ModelAdmin):
"""Admin model for the Section model."""
# @admin.register(Section)
# class SectionAdmin(admin.ModelAdmin):
# """Admin model for the Section model."""
list_display = ("character",)
search_fields = ("character",)
# list_display = ("character",)
# search_fields = ("character",)

View File

@ -0,0 +1,302 @@
[
{
"model": "mainapp.member",
"pk": 1,
"fields": {
"first_name": "Robert",
"last_name": "Reid",
"team": 1,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 2,
"fields": {
"first_name": "Ronald",
"last_name": "Jones",
"team": 1,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 3,
"fields": {
"first_name": "Casey",
"last_name": "Cohen",
"team": 1,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 4,
"fields": {
"first_name": "James",
"last_name": "Scudder",
"team": 2,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 5,
"fields": {
"first_name": "Randall",
"last_name": "Young",
"team": 2,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 6,
"fields": {
"first_name": "Helen",
"last_name": "Doak",
"team": 2,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 7,
"fields": {
"first_name": "Brenda",
"last_name": "Powell",
"team": 3,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 8,
"fields": {
"first_name": "Constance",
"last_name": "Abild",
"team": 3,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 9,
"fields": {
"first_name": "Patsy",
"last_name": "Branham",
"team": 3,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 10,
"fields": {
"first_name": "Cheryl",
"last_name": "Sears",
"team": 4,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 11,
"fields": {
"first_name": "Justin",
"last_name": "Cramer",
"team": 4,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 12,
"fields": {
"first_name": "Theodore",
"last_name": "Wilson",
"team": 4,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 13,
"fields": {
"first_name": "Geneva",
"last_name": "Low",
"team": 5,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 14,
"fields": {
"first_name": "John",
"last_name": "Burtt",
"team": 5,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 15,
"fields": {
"first_name": "Alfred",
"last_name": "Diaz",
"team": 5,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 16,
"fields": {
"first_name": "Arthur",
"last_name": "Alton",
"team": 6,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 17,
"fields": {
"first_name": "Vicki",
"last_name": "Greer",
"team": 6,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 18,
"fields": {
"first_name": "Lewis",
"last_name": "Segovia",
"team": 6,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 19,
"fields": {
"first_name": "Vince",
"last_name": "Robinson",
"team": 7,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 20,
"fields": {
"first_name": "Blake",
"last_name": "Mueller",
"team": 7,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 21,
"fields": {
"first_name": "Luis",
"last_name": "Hazel",
"team": 7,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 22,
"fields": {
"first_name": "Diane",
"last_name": "Lloyd",
"team": 8,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 23,
"fields": {
"first_name": "Jamey",
"last_name": "Mendes",
"team": 8,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 24,
"fields": {
"first_name": "Virgilio",
"last_name": "Nixon",
"team": 8,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 25,
"fields": {
"first_name": "Rodney",
"last_name": "White",
"team": 9,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 26,
"fields": {
"first_name": "Kathleen",
"last_name": "Ashe",
"team": 9,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 27,
"fields": {
"first_name": "Stephanie",
"last_name": "Taylor",
"team": 9,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 28,
"fields": {
"first_name": "John",
"last_name": "Brennan",
"team": 10,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 29,
"fields": {
"first_name": "Kenneth",
"last_name": "Duff",
"team": 10,
"peg_number": null
}
},
{
"model": "mainapp.member",
"pk": 30,
"fields": {
"first_name": "Matthew",
"last_name": "Whitesell",
"team": 10,
"peg_number": null
}
}
]

View File

@ -0,0 +1,72 @@
[
{
"model": "mainapp.team",
"pk": 1,
"fields": {
"section_letter": "D"
}
},
{
"model": "mainapp.team",
"pk": 2,
"fields": {
"section_letter": "P"
}
},
{
"model": "mainapp.team",
"pk": 3,
"fields": {
"section_letter": "L"
}
},
{
"model": "mainapp.team",
"pk": 4,
"fields": {
"section_letter": "M"
}
},
{
"model": "mainapp.team",
"pk": 5,
"fields": {
"section_letter": "Z"
}
},
{
"model": "mainapp.team",
"pk": 6,
"fields": {
"section_letter": "Y"
}
},
{
"model": "mainapp.team",
"pk": 7,
"fields": {
"section_letter": "J"
}
},
{
"model": "mainapp.team",
"pk": 8,
"fields": {
"section_letter": "W"
}
},
{
"model": "mainapp.team",
"pk": 9,
"fields": {
"section_letter": "T"
}
},
{
"model": "mainapp.team",
"pk": 10,
"fields": {
"section_letter": "F"
}
}
]

View File

View File

@ -0,0 +1,52 @@
import random
import names
import json
from django.core.management.base import BaseCommand
from mainapp.models import Member, Team
# TODO: refactor this file like create_teams_fixtures.py
class Command(BaseCommand):
help = "Creates a fixture with randomly generated Member objects"
def add_arguments(self, parser):
parser.add_argument("num_members", type=int)
def handle(self, *args, **options):
num_members = options["num_members"]
teams = Team.objects.all()
if not teams:
print("No teams found. Please create some teams first.")
return
members = []
for team in teams:
for _ in range(num_members):
first_name = names.get_first_name()
last_name = names.get_last_name()
member = Member(first_name=first_name, last_name=last_name, team=team)
members.append(member)
Member.objects.bulk_create(members)
self.stdout.write(self.style.SUCCESS(f"Created {num_members} members."))
# create a members fixture file
members_fixture = []
for member in members:
member_fixture = {
"model": "mainapp.member",
"pk": member.pk,
"fields": {
"first_name": member.first_name,
"last_name": member.last_name,
"team": member.team_id,
"peg_number": member.peg_number
}
}
members_fixture.append(member_fixture)
with open("src/mainapp/fixtures/members_fixture.json", "w") as f:
f.write(json.dumps(members_fixture, indent=2))
self.stdout.write(self.style.SUCCESS("Created members_fixture.json."))

View File

@ -0,0 +1,64 @@
"""Command to create test data fixture for teams."""
import random
import json
from string import ascii_uppercase
from django.core.management.base import BaseCommand
from django.db.utils import IntegrityError
from mainapp.models import Team, BLOCKED_SECTION_LETTERS
class Command(BaseCommand):
help = "Creates a fixture file for Team objects"
def add_arguments(self, parser):
parser.add_argument("amount_of_teams", type=int, help="Number of teams to create")
def handle(self, *args, **options):
existing_teams = Team.objects.all()
# Available sections
available_sections = [
char for char in [*ascii_uppercase]
if char.lower() not in BLOCKED_SECTION_LETTERS
and char not in [team.section_letter for team in existing_teams]
]
if not available_sections:
self.stdout.write(self.style.ERROR(
f"There are no available sections for new teams."
))
return
teams_amount = options["amount_of_teams"]
max_teams = len(available_sections)
if teams_amount > max_teams:
self.stdout.write(self.style.ERROR(
f"Number of teams is too large [{teams_amount}/{max_teams}]."
))
return
# Create the new teams (this will create them in the database)#
new_teams = []
for i in range(teams_amount):
section_letter = random.choice(available_sections)
available_sections.remove(section_letter)
team = Team.objects.create(section_letter=section_letter)
new_teams.append(team)
teams_fixture = [{
"model": "mainapp.team",
"pk": team.pk,
"fields": {"section_letter": team.section_letter}
} for team in new_teams]
# Remove the teams from the database
for team in new_teams:
team.delete()
with open("src/mainapp/fixtures/teams_fixture.json", "w") as file:
file.write(json.dumps(teams_fixture, indent=4))
self.stdout.write(self.style.SUCCESS("Created teams_fixture.json."))

View File

@ -1,6 +1,5 @@
# Generated by Django 4.1.5 on 2023-01-26 14:24
# Generated by Django 4.1.5 on 2023-05-07 21:57
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import mainapp.models
@ -11,7 +10,6 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
@ -21,17 +19,11 @@ class Migration(migrations.Migration):
('peg_number', mainapp.models.ReusableAutoField(default=mainapp.models.ReusableAutoField.get_default, editable=False, primary_key=True, serialize=False)),
],
),
migrations.CreateModel(
name='Section',
fields=[
('character', models.CharField(max_length=1, primary_key=True, serialize=False, validators=[mainapp.models.validate_section_character])),
],
),
migrations.CreateModel(
name='Team',
fields=[
('team_number', mainapp.models.ReusableAutoField(default=mainapp.models.ReusableAutoField.get_default, editable=False, primary_key=True, serialize=False)),
('section', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='mainapp.section')),
('section_letter', models.CharField(choices=[('A', 'A'), ('B', 'B'), ('C', 'C'), ('D', 'D'), ('E', 'E'), ('F', 'F'), ('G', 'G'), ('H', 'H'), ('I', 'I'), ('J', 'J'), ('K', 'K'), ('L', 'L'), ('M', 'M'), ('N', 'N'), ('O', 'O'), ('P', 'P'), ('Q', 'Q'), ('R', 'R'), ('S', 'S'), ('T', 'T'), ('U', 'U'), ('V', 'V'), ('W', 'W'), ('X', 'X'), ('Y', 'Y'), ('Z', 'Z')], max_length=1, unique=True)),
],
),
migrations.CreateModel(
@ -40,8 +32,8 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=255)),
('last_name', models.CharField(max_length=255)),
('team', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='mainapp.team')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('peg_number', models.PositiveIntegerField(null=True, unique=True)),
('team', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='members', to='mainapp.team', validators=[mainapp.models.validate_team_size])),
],
),
]

View File

@ -1,25 +0,0 @@
# Generated by Django 4.1.5 on 2023-01-26 14:31
from django.db import migrations, models
import django.db.models.deletion
import mainapp.models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='member',
name='peg',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='mainapp.peg'),
),
migrations.AlterField(
model_name='member',
name='team',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='mainapp.team', validators=[mainapp.models.validate_team_size]),
),
]

View File

@ -1,13 +1,14 @@
from string import ascii_uppercase
from django.db import models
from django.core.exceptions import ValidationError
# products/models.py
from django.db import models
class ReusableAutoField(models.PositiveIntegerField):
"""A django auto field that can reuse deleted primary keys"""
def get_next_available_id(self, model_cls, using=None):
"""
Returns the next available id for the given model class.
@ -49,23 +50,47 @@ class Member(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
user = models.OneToOneField("auth.User", on_delete=models.CASCADE)
team = models.ForeignKey("Team", on_delete=models.SET_NULL, null=True, blank=True, validators=(validate_team_size,))
peg = models.OneToOneField("Peg", on_delete=models.SET_NULL, null=True, blank=True)
team = models.ForeignKey("Team", on_delete=models.SET_NULL, null=True, blank=True, validators=(validate_team_size,), related_name='members')
peg_number = models.PositiveIntegerField(null=True, editable=True, unique=True)
# peg = models.OneToOneField("Peg", on_delete=models.SET_NULL, null=True, blank=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# If the peg_number field is not set, we assign it the smallest
# available positive integer, excluding any used values.
if not self.peg_number:
used_peg_numbers = Member.objects.exclude(id=self.id).exclude(peg_number=None).values_list("peg_number", flat=True)
peg_numbers = set(range(1, Member.objects.count() + 1)) - set(used_peg_numbers)
if peg_numbers:
self.peg_number = min (peg_numbers)
def __str__(self):
return f"{self.first_name} {self.last_name} (team {self.team.team_number}))"
@property
def fullname(self) -> str:
return f"{self.first_name} {self.last_name}"
BLOCKED_SECTION_LETTERS = ["i"] # lowercase only, sections cannot be any of these letters#
class Team(models.Model):
"""Represents a team"""
team_number = ReusableAutoField(primary_key=True, default=ReusableAutoField.get_default, editable=False)
section = models.OneToOneField("Section", on_delete=models.SET_NULL, null=True, blank=True)
section_letter = models.CharField(max_length=1, unique=True, choices=[
(char, char) for char in ascii_uppercase if char.lower() not in BLOCKED_SECTION_LETTERS
])
def __str__(self):
return f"Team {self.team_number}"
@property
def members(self) -> list[Member]:
Member.objects.filter(team=self)
def validate_section_character(value):
"""Validates the section character"""
@ -77,17 +102,17 @@ def validate_section_character(value):
raise ValidationError(f"The character <{value}> is a prohibited character.")
class Section(models.Model):
"""Represents a section of the scoreboard"""
# class Section(models.Model):
# """Represents a section of the scoreboard"""
# character field stores a single character but doesnt allow for 'I' or 'i'
character = models.CharField(primary_key=True, max_length=1, validators=(validate_section_character, ))
# # character field stores a single character but doesnt allow for 'I' or 'i'
# character = models.CharField(primary_key=True, max_length=1, validators=(validate_section_character, ))
def clean(self):
self.character = self.character.upper()
# def clean(self):
# self.character = self.character.upper()
def __str__(self):
return f"Section {self.character}"
# def __str__(self):
# return f"Section {self.character}"
#class Scoreboard(models.Model):

View File

@ -0,0 +1,11 @@
import names
from string import ascii_lowercase
created_teams = []
for char in ascii_lowercase:
created_teams.append({
"identifier": char.upper(),
"members": [{
"name": names.get_full_name()
} for i in range(9)]
})

View File

@ -1,63 +1,56 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN" crossorigin="anonymous"></script>
<title>Home</title>
{% extends "base.html" %}
<!-- Styles -->
{% load static %}
<link rel="stylesheet" href="{% static 'css/index.css' %}">
</head>
<body>
<main>
<section class="py-5 text-center container">
<div class="row py-lg-5">
<div class="col-lg-6 col-md-8 mx-auto">
<h1 class="fw-light text-white">Results System</h1>
<p class="lead text-white">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi faucibus elementum diam nec semper. Nullam vestibulum enim eu nisi condimentum, vitae suscipit risus imperdiet.</p>
</div>
</div>
</section>
<div class="album py-5 bg-light">
<div class="container">
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3">
<div class="col">
<div class="card shadow-sm">
<a href="teams.html">
<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c"/><text x="50%" y="50%" fill="#eceeef" dy=".3em">Thumbnail</text></svg>
<div class="card-body">
<p class="card-text">Teams / Members</p>
</div>
</a>
</div>
{% block content %}
<section class="py-5 text-center container">
<div class="row py-lg-5">
<div class="col-lg-6 col-md-8 mx-auto">
<h1 class="fw-light text-white">Results System</h1>
<p class="lead text-white">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi faucibus elementum diam nec semper. Nullam vestibulum enim eu nisi condimentum, vitae suscipit risus imperdiet.</p>
</div>
</div>
</section>
<div class="col">
<div class="card shadow-sm">
<a href="scoreboard.html">
<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c"/><text x="50%" y="50%" fill="#eceeef" dy=".3em">Thumbnail</text></svg>
<div class="card-body">
<p class="card-text">Scoreboard</p>
<div class="album py-5 bg-light">
<div class="container">
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3">
<!-- Teams & Members -->
<div class="col">
<div class="card shadow-sm">
<a href="{% url 'teams' %}">
<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c"/><text x="50%" y="50%" fill="#eceeef" dy=".3em">Thumbnail</text></svg>
<div class="card-body">
<p class="card-text">Teams / Members</p>
</div>
</a>
</div>
</a>
</div>
</div>
</div>
<div class="col">
<div class="card shadow-sm">
<a href="results.html">
<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c"/><text x="50%" y="50%" fill="#eceeef" dy=".3em">Thumbnail</text></svg>
<div class="card-body">
<p class="card-text">Results</p>
<!-- Scoreboard -->
<div class="col">
<div class="card shadow-sm">
<a href="{% url 'scoreboard' %}">
<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c"/><text x="50%" y="50%" fill="#eceeef" dy=".3em">Thumbnail</text></svg>
<div class="card-body">
<p class="card-text">Scoreboard</p>
</div>
</a>
</div>
</a>
</div>
<!-- Results -->
<div class="col">
<div class="card shadow-sm">
<a href="{% url 'results' %}">
<svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Placeholder: Thumbnail" preserveAspectRatio="xMidYMid slice" focusable="false"><title>Placeholder</title><rect width="100%" height="100%" fill="#55595c"/><text x="50%" y="50%" fill="#eceeef" dy=".3em">Thumbnail</text></svg>
<div class="card-body">
<p class="card-text">Results</p>
</div>
</a>
</div>
</div>
</div>
</div>
</body>
</html>
</div>
</div>
{% endblock content %}

View File

@ -1,9 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN" crossorigin="anonymous"></script>
<title>Results</title>
{% extends "base.html" %}
{% block title %}
Results |
{% endblock title %}
{% block content %}
<div class="bg-dark-subtle p-4">
<a href="{% url 'index' %}" class="btn btn-primary">Back</a>
</div>
{% endblock content %}

View File

@ -1,49 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN" crossorigin="anonymous"></script>
<title>Scoreboard</title>
<style>
table {
border-spacing: 0px;
}
</style>
{% extends "base.html" %}
<body>
<div class="row justify-content-center">
<div class="col">
<table class="table table-responsive table-striped table-hover table-bordered border-primary" contenteditable>
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">First</th>
<th scope="col">Last</th>
<th scope="col">Handle</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">1</th>
<td>Mark</td>
<td>Otto</td>
<td>@mdo</td>
</tr>
<tr>
<th scope="row">2</th>
<td>Jacob</td>
<td>Thornton</td>
<td>@fat</td>
</tr>
<tr>
<th scope="row">3</th>
<td colspan="2">Larry the Bird</td>
<td>@twitter</td>
</tr>
</tbody>
</table>
{% block title %}
Scoreboard |
{% endblock title %}
{% block content %}
<div class="bg-dark-subtle p-4">
<a href="{% url 'index' %}" class="btn btn-primary">Back</a>
</div>
</div>
</body>
{% endblock content %}

View File

@ -1,8 +1,160 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN" crossorigin="anonymous"></script>
<title>Teams</title>
{% extends "base.html" %}
{% block title %}
Teams & Members |
{% endblock title %}
{% block content %}
<div class="bg-body p-4">
<a href="{% url 'index' %}" class="btn btn-company px-4">Back</a>
</div>
<div class="container my-4 p-4 pb-0 bg-body rounded">
<div class="row mb-4">
<div class="col-12 col-xl-4">
<h3 class="mb-3 mb-xl-0">Teams &amp; Members</h3>
</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 border-secondary-subtle btn-outline-company" id="addMember" data-bs-toggle="tooltip" data-bs-title="Add Member">
<i class="bi bi-person"></i>
</button>
<button class="btn border-secondary-subtle btn-outline-company" id="addTeam" data-bs-toggle="tooltip" data-bs-title="Add Team">
<i class="bi bi-people"></i>
</button>
</div>
</div>
<div class="col-sm-9 col-md-6 col-xl-4">
<div class="input-group dropdown">
<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" class="form-control border-secondary-subtle shadow-none" placeholder="Search Members" id="search">
<button type="button" class="btn btn-outline-company border-secondary-subtle rounded-end" id="searchButton"><i class="bi bi-search"></i></button>
<form id="sortForm">
<ul class="dropdown-menu py-3 text-body-secondary bg-body-tertiary border border-light-subtle shadow-sm justify-self-center mt-2" onclick="event.stopPropagation()">
<li class="px-4">
<h3 class="h6 mb-3">Sort groups by</h3>
<div>
<div class="d-inline-block form-check">
<input type="radio" class="form-check-input" checked value="team_number" name="sortTeams" id="sortTeamsName">
<label for="sortTeamsName" class="form-check-label">Teams</label>
</div>
<div class="d-inline-block form-check ms-4">
<input type="radio" class="form-check-input" value="section_letter" name="sortTeams" id="sortTeamsSection">
<label for="sortTeamsSection" class="form-check-label">Sections</label>
</div>
</div>
</li>
<li class="dropdown-divider my-3 mx-4 bg-light-subtle"></li>
<li class="px-4">
<h3 class="h6 mb-3">Sort members by</h3>
<div>
<div class="d-inline-block form-check">
<input type="radio" class="form-check-input" value="first_name" name="sortMembers" id="sortMembersName">
<label for="sortMembersName" class="form-check-label">Name</label>
</div>
<div class="d-inline-block form-check ms-4">
<input type="radio" class="form-check-input" checked value="peg_number" name="sortMembers" id="sortMembersPeg">
<label for="sortMembersPeg" class="form-check-label">Peg Number</label>
</div>
</div>
</li>
</ul>
</form>
</div>
</div>
</div>
<div class="row" id="teamsContainer"></div>
<div class="col-12 text-center pb-4" id="teamsLoadingSpinner">
<div class="spinner-border spinner-border-lg" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<div class="pb-4" id="teamsNotFound">
<div class="alert alert-danger m-0" role="alert">
No teams found under that search
</div>
</div>
</div>
<!-- Edit Team Modal -->
<div class="modal fade" id="editTeamModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content border-0">
<div class="modal-header">
<h3 class="modal-title fs-5" id="editTeamTitle">Team Number Here</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
</div>
<div class="modal-body">
<form id="editTeamForm">
<div class="row">
<div class="col-md-6 mb-3 mb-md-0">
<label for="editTeamNumber" class="form-label">Team Number</label>
<input type="number" name="editTeamNumber" class="form-control" id="editTeamNumber">
</div>
<div class="col-md-6">
<label for="editTeamSection" class="form-label">Section Letter</label>
<input type="text" name="editTeamSection" class="form-control" id="editTeamSection">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="saveEditTeamModal">Save Changes</button>
</div>
</div>
</div>
</div>
<!-- Edit Member Modal -->
<div class="modal fade" id="editMemberModal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content border-0">
<div class="modal-header">
<h3 class="modal-title fs-5" id="editMemberName">Member Name Here</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="close"></button>
</div>
<div class="modal-body">
<form id="editMemberForm">
<div class="row">
<div class="col-md-6 mb-3">
<label for="editMemberFirstName" class="form-label">Forename</label>
<input type="text" name="editMemberFirstName" id="editMemberFirstName" class="form-control" required minlength="1" maxlength="30">
</div>
<div class="col-md-6 mb-3">
<label for="editMemberLastName" class="form-label">Surname</label>
<input type="text" name="editMemberLastName" id="editMemberLastName" class="form-control" required minlength="1" maxlength="30">
</div>
<div class="col-md-6">
<label for="editMemberTeam" class="form-label">Team</label>
<select name="editMemberTeam" id="editMemberTeam" class="form-select" required></select>
</div>
<div class="col-md-6">
<label for="editMemberPeg" class="form-label">Peg Number</label>
<input type="number" name="editMemberPeg" id="editMemberPeg" class="form-control" min="1" max="9999">
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="saveEditModal">Save Changes</button>
</div>
</div>
</div>
</div>
{% endblock content %}
{% load static %}
{% block scripts %}
<script type="text/javascript">
const getTeamsUrl = "{% url 'get-teams' %}";
const updateMemberUrl = "{% url 'update-member' %}";
const csrfMiddlewareToken = "{{ csrf_token }}";
</script>
<script src="{% static 'js/teams.js' %}"></script>
{% endblock scripts %}

View File

@ -3,8 +3,11 @@ from . import views
urlpatterns = [
path('', views.index, name='index'),
path('results.html', views.results, name='results'),
path('scoreboard.html', views.scoreboard, name='scoreboard'),
path('teams.html', views.teams, name='teams'),
path('bulk-peg/', views.bulk_create_pegs, name='bulk-peg')
path('results/', views.results, name='results'),
path('scoreboard/', views.scoreboard, name='scoreboard'),
path('teams/', views.teams, name='teams'),
path('bulk-peg/', views.bulk_create_pegs, name='bulk-peg'),
path('get-teams/', views.get_teams, name='get-teams'),
path('update-member/', views.update_member, name='update-member')
]

View File

@ -1,6 +1,13 @@
from django.shortcuts import render, redirect
"""Views for the main app."""
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 .models import Peg, Team, Member
from .models import Peg
def index(request):
return render(request, 'index.html')
@ -23,3 +30,87 @@ def bulk_create_pegs(request):
# return to previous page
return redirect(request.META.get('HTTP_REFERER'))
def get_teams(request):
"""Returns a JsonResponse containing a dictionary with a k/v pair for a list of teams.
Args:
request: the web request object.
Returns:
JsonResponse: dictionary of teams like so {'teams': [{}, {}, {}]}.
"""
if not request.POST:
return
search = request.POST.get("search")
sort_teams = request.POST.get("sortTeams") or "team_number"
sort_members = request.POST.get("sortMembers") or "peg_number"
teams = Team.objects.order_by(sort_teams).all()
# Filter out teams that don't contain members being searched for
if search:
search_terms = search.split()
members = Member.objects.filter(
reduce(
lambda x, y: x | y,
[
Q(first_name__icontains=term) | Q(last_name__icontains=term)
for term in search_terms
]
)
)
teams = teams.filter(members__in=members).distinct()
# Create a dictionary for the data that is JSON safe
response_data = {"teams": []}
for team in teams:
team_data = {
"team_number": team.team_number,
"section_letter": team.section_letter if team.section_letter else None,
"members": [
{
"first": member.first_name,
"last": member.last_name,
"id": member.id,
"team": team.team_number,
"peg": member.peg_number,
"section": team.section_letter if team.section_letter else None
}
for member in team.members.order_by(sort_members).all()
]
}
response_data["teams"].append(team_data)
response_data["sortTeams"] = sort_teams
response_data["sortMembers"] = sort_members
return JsonResponse(response_data)
def update_member(request):
"""Update a member. Returns a JsonResponse with the updated teams."""
if not request.POST:
return
# Get the updated values
member_id = request.POST.get("memberId")
first = request.POST.get("first")
last = request.POST.get("last")
team_number = request.POST.get("teamNumber")
peg_number = request.POST.get("pegNumber")
# Get the member and team
member = Member.objects.get(id=member_id)
team = Team.objects.get(team_number=team_number)
# Update the member
member.first_name = first
member.last_name = last
member.team = team
member.peg_number = peg_number
member.save()
return get_teams(request)

6
src/static/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

121
src/static/css/custom.css Normal file
View File

@ -0,0 +1,121 @@
.ul-cols-2 {
column-count: 2;
column-gap: 20px;
}
.ul-cols-2 li {
display: inline-block;
width: 100%;
box-sizing: border-box;
padding: 5px;
}
.spinner-border-lg {
width: 3rem;
height: 3rem;
}
.fluid-hover-zoom {
transition:
.3s transform cubic-bezier(.155,1.105,.295,1.12),
.3s box-shadow,
.3s -webkit-transform cubic-bezier(.155,1.105,.295,1.12);
backface-visibility: hidden;
}
.fluid-hover-zoom:hover {
transform: scale(1.035);
}
.no-transform {
transform: none !important;
}
.shadow-on-hover:not(:hover) {
box-shadow: none !important;
}
.team-column {
opacity: 0;
transition: 5s opacity ease-in;
}
.fixed-badge {
min-width: 22px;
height: 22px;
}
.font-montserrat {
font-family: Montserrat;
}
.font-raleway {
font-family: Raleway;
}
.font-ssp {
font-family: "Source Sans Pro";
}
.pencil-btn {
width: 32px;
height: 32px;
}
.force-contents-center {
display: flex;
justify-content: center;
align-items: center;
}
.md-shadow-on-hover:hover {
box-shadow: rgba(0, 0, 0, 0.15) 0px 8px 16px 0px !important;
}
.text-company {
color: #04385c;
}
.company-bg {
background-color: #04385c !important;
}
.underline-company {
text-decoration: underline solid #04385c;
}
.border-company {
border-color: #04385c !important;
}
.btn-company {
color: white;
background-color: #04385c;
}
.btn-company:hover {
color: white;
background-color: #002d51;
}
.btn-outline-company {
border: 1px solid #04385c;
}
.btn-outline-company:hover {
color: white;
background-color: #04385c;
border: 1px solid #04385c;
}
.hover-only-bg:not(:hover) {
background-color: transparent !important;
}
input[type='radio']:checked {
background-color: #04385c;
border: 1px solid #04385c;
box-shadow: none;
}

View File

@ -1,6 +1,6 @@
body {
/* body {
background-color: #0d5979;
}
} */
.bd-placeholder-img {
font-size: 1.125rem;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-0-circle-fill" viewBox="0 0 16 16">
<path d="M8 4.951c-1.008 0-1.629 1.09-1.629 2.895v.31c0 1.81.627 2.895 1.629 2.895s1.623-1.09 1.623-2.895v-.31c0-1.8-.621-2.895-1.623-2.895Z"/>
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0Zm-8.012 4.158c1.858 0 2.96-1.582 2.96-3.99V7.84c0-2.426-1.079-3.996-2.936-3.996-1.864 0-2.965 1.588-2.965 3.996v.328c0 2.42 1.09 3.99 2.941 3.99Z"/>
</svg>

After

Width:  |  Height:  |  Size: 479 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-0-circle" viewBox="0 0 16 16">
<path d="M7.988 12.158c-1.851 0-2.941-1.57-2.941-3.99V7.84c0-2.408 1.101-3.996 2.965-3.996 1.857 0 2.935 1.57 2.935 3.996v.328c0 2.408-1.101 3.99-2.959 3.99ZM8 4.951c-1.008 0-1.629 1.09-1.629 2.895v.31c0 1.81.627 2.895 1.629 2.895s1.623-1.09 1.623-2.895v-.31c0-1.8-.621-2.895-1.623-2.895Z"/>
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0ZM1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8Z"/>
</svg>

After

Width:  |  Height:  |  Size: 511 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-0-square-fill" viewBox="0 0 16 16">
<path d="M8 4.951c-1.008 0-1.629 1.09-1.629 2.895v.31c0 1.81.627 2.895 1.629 2.895s1.623-1.09 1.623-2.895v-.31c0-1.8-.621-2.895-1.623-2.895Z"/>
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2Zm5.988 12.158c-1.851 0-2.941-1.57-2.941-3.99V7.84c0-2.408 1.101-3.996 2.965-3.996 1.857 0 2.935 1.57 2.935 3.996v.328c0 2.408-1.101 3.99-2.959 3.99Z"/>
</svg>

After

Width:  |  Height:  |  Size: 518 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-0-square" viewBox="0 0 16 16">
<path d="M7.988 12.158c-1.851 0-2.941-1.57-2.941-3.99V7.84c0-2.408 1.101-3.996 2.965-3.996 1.857 0 2.935 1.57 2.935 3.996v.328c0 2.408-1.101 3.99-2.959 3.99ZM8 4.951c-1.008 0-1.629 1.09-1.629 2.895v.31c0 1.81.627 2.895 1.629 2.895s1.623-1.09 1.623-2.895v-.31c0-1.8-.621-2.895-1.623-2.895Z"/>
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2Zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2Z"/>
</svg>

After

Width:  |  Height:  |  Size: 585 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-1-circle-fill" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0ZM9.283 4.002H7.971L6.072 5.385v1.271l1.834-1.318h.065V12h1.312V4.002Z"/>
</svg>

After

Width:  |  Height:  |  Size: 257 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-1-circle" viewBox="0 0 16 16">
<path d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8Zm15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0ZM9.283 4.002V12H7.971V5.338h-.065L6.072 6.656V5.385l1.899-1.383h1.312Z"/>
</svg>

After

Width:  |  Height:  |  Size: 287 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-1-square-fill" viewBox="0 0 16 16">
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2Zm7.283 4.002V12H7.971V5.338h-.065L6.072 6.656V5.385l1.899-1.383h1.312Z"/>
</svg>

After

Width:  |  Height:  |  Size: 294 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-1-square" viewBox="0 0 16 16">
<path d="M9.283 4.002V12H7.971V5.338h-.065L6.072 6.656V5.385l1.899-1.383h1.312Z"/>
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2Zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2Z"/>
</svg>

After

Width:  |  Height:  |  Size: 376 B

3
src/static/icons/123.svg Normal file
View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-123" viewBox="0 0 16 16">
<path d="M2.873 11.297V4.142H1.699L0 5.379v1.137l1.64-1.18h.06v5.961h1.174Zm3.213-5.09v-.063c0-.618.44-1.169 1.196-1.169.676 0 1.174.44 1.174 1.106 0 .624-.42 1.101-.807 1.526L4.99 10.553v.744h4.78v-.99H6.643v-.069L8.41 8.252c.65-.724 1.237-1.332 1.237-2.27C9.646 4.849 8.723 4 7.308 4c-1.573 0-2.36 1.064-2.36 2.15v.057h1.138Zm6.559 1.883h.786c.823 0 1.374.481 1.379 1.179.01.707-.55 1.216-1.421 1.21-.77-.005-1.326-.419-1.379-.953h-1.095c.042 1.053.938 1.918 2.464 1.918 1.478 0 2.642-.839 2.62-2.144-.02-1.143-.922-1.651-1.551-1.714v-.063c.535-.09 1.347-.66 1.326-1.678-.026-1.053-.933-1.855-2.359-1.845-1.5.005-2.317.88-2.348 1.898h1.116c.032-.498.498-.944 1.206-.944.703 0 1.206.435 1.206 1.07.005.64-.504 1.106-1.2 1.106h-.75v.96Z"/>
</svg>

After

Width:  |  Height:  |  Size: 870 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-2-circle-fill" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0ZM6.646 6.24c0-.691.493-1.306 1.336-1.306.756 0 1.313.492 1.313 1.236 0 .697-.469 1.23-.902 1.705l-2.971 3.293V12h5.344v-1.107H7.268v-.077l1.974-2.22.096-.107c.688-.763 1.287-1.428 1.287-2.43 0-1.266-1.031-2.215-2.613-2.215-1.758 0-2.637 1.19-2.637 2.402v.065h1.271v-.07Z"/>
</svg>

After

Width:  |  Height:  |  Size: 458 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-2-circle" viewBox="0 0 16 16">
<path d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8Zm15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0ZM6.646 6.24v.07H5.375v-.064c0-1.213.879-2.402 2.637-2.402 1.582 0 2.613.949 2.613 2.215 0 1.002-.6 1.667-1.287 2.43l-.096.107-1.974 2.22v.077h3.498V12H5.422v-.832l2.97-3.293c.434-.475.903-1.008.903-1.705 0-.744-.557-1.236-1.313-1.236-.843 0-1.336.615-1.336 1.306Z"/>
</svg>

After

Width:  |  Height:  |  Size: 480 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-2-square-fill" viewBox="0 0 16 16">
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2Zm4.646 6.24v.07H5.375v-.064c0-1.213.879-2.402 2.637-2.402 1.582 0 2.613.949 2.613 2.215 0 1.002-.6 1.667-1.287 2.43l-.096.107-1.974 2.22v.077h3.498V12H5.422v-.832l2.97-3.293c.434-.475.903-1.008.903-1.705 0-.744-.557-1.236-1.313-1.236-.843 0-1.336.615-1.336 1.306Z"/>
</svg>

After

Width:  |  Height:  |  Size: 487 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-2-square" viewBox="0 0 16 16">
<path d="M6.646 6.24v.07H5.375v-.064c0-1.213.879-2.402 2.637-2.402 1.582 0 2.613.949 2.613 2.215 0 1.002-.6 1.667-1.287 2.43l-.096.107-1.974 2.22v.077h3.498V12H5.422v-.832l2.97-3.293c.434-.475.903-1.008.903-1.705 0-.744-.557-1.236-1.313-1.236-.843 0-1.336.615-1.336 1.306Z"/>
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2Zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2Z"/>
</svg>

After

Width:  |  Height:  |  Size: 569 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-3-circle-fill" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0Zm-8.082.414c.92 0 1.535.54 1.541 1.318.012.791-.615 1.36-1.588 1.354-.861-.006-1.482-.469-1.54-1.066H5.104c.047 1.177 1.05 2.144 2.754 2.144 1.653 0 2.954-.937 2.93-2.396-.023-1.278-1.031-1.846-1.734-1.916v-.07c.597-.1 1.505-.739 1.482-1.876-.03-1.177-1.043-2.074-2.637-2.062-1.675.006-2.59.984-2.625 2.12h1.248c.036-.556.557-1.054 1.348-1.054.785 0 1.348.486 1.348 1.195.006.715-.563 1.237-1.342 1.237h-.838v1.072h.879Z"/>
</svg>

After

Width:  |  Height:  |  Size: 608 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-3-circle" viewBox="0 0 16 16">
<path d="M7.918 8.414h-.879V7.342h.838c.78 0 1.348-.522 1.342-1.237 0-.709-.563-1.195-1.348-1.195-.79 0-1.312.498-1.348 1.055H5.275c.036-1.137.95-2.115 2.625-2.121 1.594-.012 2.608.885 2.637 2.062.023 1.137-.885 1.776-1.482 1.875v.07c.703.07 1.71.64 1.734 1.917.024 1.459-1.277 2.396-2.93 2.396-1.705 0-2.707-.967-2.754-2.144H6.33c.059.597.68 1.06 1.541 1.066.973.006 1.6-.563 1.588-1.354-.006-.779-.621-1.318-1.541-1.318Z"/>
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0ZM1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8Z"/>
</svg>

After

Width:  |  Height:  |  Size: 645 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-3-square-fill" viewBox="0 0 16 16">
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2Zm5.918 8.414h-.879V7.342h.838c.78 0 1.348-.522 1.342-1.237 0-.709-.563-1.195-1.348-1.195-.79 0-1.312.498-1.348 1.055H5.275c.036-1.137.95-2.115 2.625-2.121 1.594-.012 2.608.885 2.637 2.062.023 1.137-.885 1.776-1.482 1.875v.07c.703.07 1.71.64 1.734 1.917.024 1.459-1.277 2.396-2.93 2.396-1.705 0-2.707-.967-2.754-2.144H6.33c.059.597.68 1.06 1.541 1.066.973.006 1.6-.563 1.588-1.354-.006-.779-.621-1.318-1.541-1.318Z"/>
</svg>

After

Width:  |  Height:  |  Size: 637 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-3-square" viewBox="0 0 16 16">
<path d="M7.918 8.414h-.879V7.342h.838c.78 0 1.348-.522 1.342-1.237 0-.709-.563-1.195-1.348-1.195-.79 0-1.312.498-1.348 1.055H5.275c.036-1.137.95-2.115 2.625-2.121 1.594-.012 2.608.885 2.637 2.062.023 1.137-.885 1.776-1.482 1.875v.07c.703.07 1.71.64 1.734 1.917.024 1.459-1.277 2.396-2.93 2.396-1.705 0-2.707-.967-2.754-2.144H6.33c.059.597.68 1.06 1.541 1.066.973.006 1.6-.563 1.588-1.354-.006-.779-.621-1.318-1.541-1.318Z"/>
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2Zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2Z"/>
</svg>

After

Width:  |  Height:  |  Size: 719 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-4-circle-fill" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0ZM7.519 5.057c-.886 1.418-1.772 2.838-2.542 4.265v1.12H8.85V12h1.26v-1.559h1.007V9.334H10.11V4.002H8.176c-.218.352-.438.703-.657 1.055ZM6.225 9.281v.053H8.85V5.063h-.065c-.867 1.33-1.787 2.806-2.56 4.218Z"/>
</svg>

After

Width:  |  Height:  |  Size: 391 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-4-circle" viewBox="0 0 16 16">
<path d="M7.519 5.057c.22-.352.439-.703.657-1.055h1.933v5.332h1.008v1.107H10.11V12H8.85v-1.559H4.978V9.322c.77-1.427 1.656-2.847 2.542-4.265ZM6.225 9.281v.053H8.85V5.063h-.065c-.867 1.33-1.787 2.806-2.56 4.218Z"/>
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0ZM1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8Z"/>
</svg>

After

Width:  |  Height:  |  Size: 433 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-4-square-fill" viewBox="0 0 16 16">
<path d="M6.225 9.281v.053H8.85V5.063h-.065c-.867 1.33-1.787 2.806-2.56 4.218Z"/>
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2Zm5.519 5.057c.22-.352.439-.703.657-1.055h1.933v5.332h1.008v1.107H10.11V12H8.85v-1.559H4.978V9.322c.77-1.427 1.656-2.847 2.542-4.265Z"/>
</svg>

After

Width:  |  Height:  |  Size: 440 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-4-square" viewBox="0 0 16 16">
<path d="M7.519 5.057c.22-.352.439-.703.657-1.055h1.933v5.332h1.008v1.107H10.11V12H8.85v-1.559H4.978V9.322c.77-1.427 1.656-2.847 2.542-4.265ZM6.225 9.281v.053H8.85V5.063h-.065c-.867 1.33-1.787 2.806-2.56 4.218Z"/>
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2Zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2Z"/>
</svg>

After

Width:  |  Height:  |  Size: 507 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-5-circle-fill" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0Zm-8.006 4.158c1.74 0 2.924-1.119 2.924-2.806 0-1.641-1.178-2.584-2.56-2.584-.897 0-1.442.421-1.612.68h-.064l.193-2.344h3.621V4.002H5.791L5.445 8.63h1.149c.193-.358.668-.809 1.435-.809.85 0 1.582.604 1.582 1.57 0 1.085-.779 1.682-1.57 1.682-.697 0-1.389-.31-1.53-1.031H5.276c.065 1.213 1.149 2.115 2.72 2.115Z"/>
</svg>

After

Width:  |  Height:  |  Size: 496 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-5-circle" viewBox="0 0 16 16">
<path d="M1 8a7 7 0 1 1 14 0A7 7 0 0 1 1 8Zm15 0A8 8 0 1 0 0 8a8 8 0 0 0 16 0Zm-8.006 4.158c-1.57 0-2.654-.902-2.719-2.115h1.237c.14.72.832 1.031 1.529 1.031.791 0 1.57-.597 1.57-1.681 0-.967-.732-1.57-1.582-1.57-.767 0-1.242.45-1.435.808H5.445L5.791 4h4.705v1.103H6.875l-.193 2.343h.064c.17-.258.715-.68 1.611-.68 1.383 0 2.561.944 2.561 2.585 0 1.687-1.184 2.806-2.924 2.806Z"/>
</svg>

After

Width:  |  Height:  |  Size: 516 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-5-square-fill" viewBox="0 0 16 16">
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2Zm5.994 12.158c-1.57 0-2.654-.902-2.719-2.115h1.237c.14.72.832 1.031 1.529 1.031.791 0 1.57-.597 1.57-1.681 0-.967-.732-1.57-1.582-1.57-.767 0-1.242.45-1.435.808H5.445L5.791 4h4.705v1.103H6.875l-.193 2.343h.064c.17-.258.715-.68 1.611-.68 1.383 0 2.561.944 2.561 2.585 0 1.687-1.184 2.806-2.924 2.806Z"/>
</svg>

After

Width:  |  Height:  |  Size: 523 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-5-square" viewBox="0 0 16 16">
<path d="M7.994 12.158c-1.57 0-2.654-.902-2.719-2.115h1.237c.14.72.832 1.031 1.529 1.031.791 0 1.57-.597 1.57-1.681 0-.967-.732-1.57-1.582-1.57-.767 0-1.242.45-1.435.808H5.445L5.791 4h4.705v1.103H6.875l-.193 2.343h.064c.17-.258.715-.68 1.611-.68 1.383 0 2.561.944 2.561 2.585 0 1.687-1.184 2.806-2.924 2.806Z"/>
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2Zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2Z"/>
</svg>

After

Width:  |  Height:  |  Size: 605 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-6-circle-fill" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0ZM8.21 3.855c-1.868 0-3.116 1.395-3.116 4.407 0 1.183.228 2.039.597 2.642.569.926 1.477 1.254 2.409 1.254 1.629 0 2.847-1.013 2.847-2.783 0-1.676-1.254-2.555-2.508-2.555-1.125 0-1.752.61-1.98 1.155h-.082c-.012-1.946.727-3.036 1.805-3.036.802 0 1.213.457 1.312.815h1.29c-.06-.908-.962-1.899-2.573-1.899Zm-.099 4.008c-.92 0-1.564.65-1.564 1.576 0 1.032.703 1.635 1.558 1.635.868 0 1.553-.533 1.553-1.629 0-1.06-.744-1.582-1.547-1.582Z"/>
</svg>

After

Width:  |  Height:  |  Size: 619 B

Some files were not shown because too many files have changed in this diff Show More