Working through the modal changes on the rewrite.

This commit is contained in:
Corban-Lee 2023-11-20 23:56:51 +00:00
parent ac5a66b054
commit 0284db2568
7 changed files with 1027 additions and 846 deletions

View File

@ -13,470 +13,392 @@
{% endblock header_buttons %}
{% block content %}
<div class="bg-body-tertiary pb-5">
<div class="row px-2 gx-3 bg-body shadow-sm border-bottom">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center p-2 flex-wrap">
<h1 class="fw-bold h4 mb-0 ms-2">Venues &amp; Waters</h1>
<div class="input-group w-auto">
<div class="input-group-text bg-white pe-0">
<i class="bi bi-search"></i>
</div>
<input type="search" class="form-control border-start-0" placeholder="Search Venues">
</div>
<div class="d-flex flex-row align-items-center">
<button class="btn btn-primary rounded-2 me-3" onclick="openVenueModal(-1);">
<i class="bi bi-plus-lg me-1"></i>
New
</button>
<button class="btn btn-outline-secondary border-secondary-subtle rounded-2 me-3">
<i class="bi bi-upload me-1"></i>
Import
</button>
<button class="btn btn-outline-secondary border-secondary-subtle rounded-2 me-3">
<i class="bi bi-sort-alpha-up me-sm-1"></i>
<i class="bi bi-chevron-down"></i>
</button>
<button class="btn btn-outline-secondary border-secondary-subtle rounded-2">
<i class="bi bi-filter-right me-sm-1"></i>
<i class="bi bi-chevron-down"></i>
</button>
</div>
<div class="px-4 bg-body shadow mb-5">
<div class="d-flex justify-content-between align-items-center p-2 flex-wrap">
<h1 class="fw-bold h4 mb-0 ms-2">Venues &amp; Waters</h1>
<div class="input-group w-auto">
<div class="input-group-text bg-body pe-0">
<i class="bi bi-search"></i>
</div>
<input type="search" class="form-control border-start-0" placeholder="Search Venues">
</div>
<div class="d-flex flex-row align-items-center">
<button class="btn btn-primary rounded-2 me-3" onclick="openVenueModal(-1);">
<i class="bi bi-plus-lg me-1"></i>
New
</button>
<button class="btn btn-outline-secondary border-secondary-subtle rounded-2 me-3">
<i class="bi bi-upload me-1"></i>
Import
</button>
<button class="btn btn-outline-secondary border-secondary-subtle rounded-2 me-3">
<i class="bi bi-sort-alpha-up me-sm-1"></i>
<i class="bi bi-chevron-down"></i>
</button>
<button class="btn btn-outline-secondary border-secondary-subtle rounded-2">
<i class="bi bi-filter-right me-sm-1"></i>
<i class="bi bi-chevron-down"></i>
</button>
</div>
</div>
</div>
<div class="row flex-row-reverse my-4 mx-lg-4">
<div class="col-lg-3 d-flex flex-column">
<div id="locationMapContainer" class="w-100 position-relative shadow mb-4 bg-body rounded-3 overflow-hidden" style="height: 300px;">
<div id="allLocationsMap" class="w-100 h-100"></div>
</div>
<div class="bg-body rounded-3 p-4 flex-grow-1 shadow mb-3">
<div class="text-body small">
placeholder<br>
maybe a graph here?
</div>
</div>
</div>
<div class="row flex-row-reverse mt-2 gy-2 gx-3 mx-2">
<div class="col-lg-3 d-flex flex-column">
<!-- <div class="d-flex justify-content-between align-items-center bg-light rounded-2 p-2 shadow-sm border mb-3">
<button class="btn btn-light text-dark border">
test
</button>
</div> -->
<div id="locationMapContainer" class="w-100 position-relative mb-3 border shadow-sm rounded-2" style="height: 300px;">
<div id="allLocationsMap" class="rounded-2 w-100 h-100"></div>
</div>
</div>
<div class="col-lg-9">
<div class="row g-3">
{% for venue in venues %}
<div class="col-xxl-3 col-xl-6 col-md-6 col-sm-12">
<div class="border rounded-2 p-4 h-100 bg-body shadow-sm d-flex flex-column">
<header class="d-flex justify-content-between align-items-start mb-2 dropend">
<div class="text-truncate">
<h4 class="h6 mb-0 text-body-secondary small">
{% if venue.venue_type == "FISHERY" %}
Fishery
{% elif venue.venue_type == "PRIVATE" %}
Private
{% elif venue.venue_type == "CLUB" %}
Club
{% endif %}
</h4>
<h3 class="h5 mb-0 me-2 text-truncate">{{ venue.name }}</h3>
</div>
<button class="border-0 bg-transparent" type="button" data-bs-toggle="dropdown">
<i class="bi bi-three-dots fs-4 d-flex"></i>
</button>
<ul class="dropdown-menu rounded-4 overflow-hidden py-0 shadow-sm" style="width: fit-content; min-width: fit-content;">
<li>
<button class="dropdown-item py-2">
<i class="bi bi-eye"></i>
</button>
</li>
<li>
<hr class="dropdown-divider my-0">
</li>
<li>
<button class="dropdown-item py-2" onclick="openVenueModal({{ venue.id }});">
<i class="bi bi-pencil"></i>
</button>
</li>
<li>
<hr class="dropdown-divider my-0">
</li>
<li>
<button class="dropdown-item py-2 hover-fill-danger">
<i class="bi bi-trash"></i>
</button>
</li>
</ul>
</header>
<p class="text-secondary venue-description">{{ venue.description }}</p>
<div class="d-flex align-items-center fs-6 mt-auto">
{% if venue.email_address %}
<a href="mailto:{{ venue.email_address }}" class="text-reset text-hover-primary me-2" data-bs-toggle="tooltip" data-bs-title="{{ venue.email_address }}">
<i class="bi bi-envelope-at"></i>
</a>
{% endif %}
{% if venue.phone_number %}
<a href="tel:{{ venue.phone_number }}" class="text-reset text-hover-primary me-2" data-bs-toggle="tooltip" data-bs-title="{{ venue.phone_number }}">
<i class="bi bi-telephone"></i>
</a>
{% endif %}
{% if venue.latitude and venue.longitude %}
<a href="https://www.openstreetmap.org/?mlat={{ venue.latitude }}&mlon={{ venue.longitude }}" target="_blank" class="text-reset text-hover-primary me-2" data-bs-toggle="tooltip" data-bs-html="true" data-bs-title="{{ venue.street_address }}<br>{{ venue.city }}, {{ venue.provence }}<br>{{ venue.postal_code }}">
<i class="bi bi-geo-alt"></i>
</a>
{% endif %}
{% if venue.website_url %}
<a href="{{ venue.website_url }}" class="text-reset text-hover-primary me-2" target="_blank" data-bs-toggle="tooltip" data-bs-title="{{ venue.website_url }}">
<i class="bi bi-globe2"></i>
</a>
{% endif %}
<div class="d-inline mx-auto"></div>
{% if venue.waters %}
<div class="badge bg-info-subtle text-info-emphasis ms-2" data-bs-toggle="tooltip" data-bs-title="There are {{ venue.waters|length }} waters in this venue">
{{ venue.waters|length }}
<i class="bi bi-droplet-half "></i>
</div>
{% endif %}
{% if not venue.latitude or not venue.longitude or not venue.phone_number or not venue.website_url or not venue.email_address %}
<div class="badge bg-warning-subtle text-warning-emphasis ms-2" data-bs-toggle="tooltip" data-bs-title="There are missing details for this venue">
<i class="bi bi-exclamation-triangle"></i>
</div>
{% endif %}
{% if not venue.active %}
<div class="badge bg-danger-subtle text-danger-emphasis ms-2" data-bs-toggle="tooltip" data-bs-title="This venue is inactive">
<i class="bi bi-x-lg"></i>
</div>
{% endif %}
<div class="col-lg-9">
<div class="row gx-3 px-2 mt-0">
{% for venue in venues %}
<div class="col-xxl-4 col-xl-6 col-md-6 col-sm-12 mb-3">
<div class="rounded-2 h-100 p-4 bg-body shadow d-flex flex-column">
<header class="d-flex justify-content-between align-items-start mb-3 dropend">
<div class="text-truncate">
<h4 class="h6 mb-1 text-body-secondary small">
{% if venue.venue_type == "FISHERY" %}
Fishery
{% elif venue.venue_type == "PRIVATE" %}
Private
{% elif venue.venue_type == "CLUB" %}
Club
{% endif %}
</h4>
<h4 class="h6 mb-0 me-2 text-truncate fw-bold">{{ venue.name }}</h4>
</div>
</div>
<button class="border-0 bg-transparent" type="button" data-bs-toggle="dropdown">
<i class="bi bi-three-dots fs-4 d-flex"></i>
</button>
<ul class="dropdown-menu rounded-4 overflow-hidden py-0 shadow-sm" style="width: fit-content; min-width: fit-content;">
<li>
<button class="dropdown-item hover-fill-primary py-2">
<i class="bi bi-eye"></i>
</button>
</li>
<li>
<hr class="dropdown-divider my-0">
</li>
<li>
<button class="dropdown-item py-2" onclick="openVenueModal({{ venue.id }});">
<i class="bi bi-pencil"></i>
</button>
</li>
<li>
<hr class="dropdown-divider my-0">
</li>
<li>
<button class="dropdown-item py-2 hover-fill-danger">
<i class="bi bi-trash"></i>
</button>
</li>
</ul>
</header>
<p class="text-body-secondary venue-description mb-4">{{ venue.description }}</p>
<div class="d-flex align-items-center fs-6 mt-auto ">
{% if venue.email_address %}
<a href="mailto:{{ venue.email_address }}" class="text-reset text-hover-primary me-3" data-bs-toggle="tooltip" data-bs-title="{{ venue.email_address }}">
<i class="bi bi-envelope-at"></i>
</a>
{% endif %}
{% if venue.phone_number %}
<a href="tel:{{ venue.phone_number }}" class="text-reset text-hover-primary me-3" data-bs-toggle="tooltip" data-bs-title="{{ venue.phone_number }}">
<i class="bi bi-telephone"></i>
</a>
{% endif %}
{% if venue.latitude and venue.longitude %}
<a href="https://www.openstreetmap.org/?mlat={{ venue.latitude }}&mlon={{ venue.longitude }}" target="_blank" class="text-reset text-hover-primary me-3" data-bs-toggle="tooltip" data-bs-html="true" data-bs-title="{{ venue.street_address }}<br>{{ venue.city }}, {{ venue.provence }}<br>{{ venue.postal_code }}">
<i class="bi bi-geo-alt"></i>
</a>
{% endif %}
{% if venue.website_url %}
<a href="{{ venue.website_url }}" class="text-reset text-hover-primary me-3" target="_blank" data-bs-toggle="tooltip" data-bs-title="{{ venue.website_url }}">
<i class="bi bi-globe2"></i>
</a>
{% endif %}
<div class="d-inline mx-auto"></div>
{% if venue.waters %}
<div class="badge bg-info-subtle text-info-emphasis ms-2" data-bs-toggle="tooltip" data-bs-title="There are {{ venue.waters|length }} waters in this venue">
{{ venue.waters|length }}
<i class="bi bi-droplet-half "></i>
</div>
{% endif %}
{% if not venue.latitude or not venue.longitude or not venue.phone_number or not venue.website_url or not venue.email_address %}
<div class="badge bg-warning-subtle text-warning-emphasis ms-2" data-bs-toggle="tooltip" data-bs-title="There are missing details for this venue">
<i class="bi bi-exclamation-triangle"></i>
</div>
{% endif %}
{% if not venue.active %}
<div class="badge bg-danger-subtle text-danger-emphasis ms-2" data-bs-toggle="tooltip" data-bs-title="This venue is inactive">
<i class="bi bi-x-lg"></i>
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endfor %}
<div class="col-xxl-4 col-xl-6 col-md-6 col-sm-12">
<div class="fluid-hover-zoom h-100 d-flex flex-column justify-content-center align-items-center" role="button" onclick="openVenueModal(-1);">
<i class="bi bi-plus-lg fs-1"></i>
<span class="fw-bold">
Create new
</span>
</div>
</div>
</div>
</div>
</div>
<div id="venueModal" class="modal fade" data-venue-id="-1">
<div class="modal-dialog modal-dialog-centered modal-fullscreen-sm-down modal-lg">
<div class="modal-content overflow-hidden rounded-4" style="min-height: 760px; max-height: 760px;">
<div class="modal-header justify-content-center">
<h4 class="card-title fw-bold mb-0">
<span class="create" style="display: none">New Venue</span>
<span class="edit" style="display: none">Edit Venue</span>
</h4>
<div id="venueModal" class="modal fade" data-venue-id="-1" data-bs-backdrop="static">
<div class="modal-dialog modal-dialog-centered modal-fullscreen-lg-down modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title text-truncate me-5">New Venue</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body border-bottom-0 p-0 overflow-x-hidden overflow-y-auto">
<ul id="newVenueTabBtns" class="nav nav-pills mb-4 d-flex w-100 justify-content-center py-2 bg-light" role="tablist">
<li class="nav-item" role="presentation">
<button id="newVenueDetailsTabBtn" class="nav-link rounded-4 active" data-bs-toggle="pill" data-bs-target="#newVenueDetailsTab" type="button" role="tab" aria-controls="newVenueDetailsTab" aria-selected="true">Details</button>
</li>
<li class="nav-item" role="presentation">
<button id="newVenueAddressTabBtn" class="nav-link rounded-4" data-bs-toggle="pill" data-bs-target="#newVenueAddressTab" type="button" role="tab" aria-controls="newVenueAddressTab" aria-selected="false">Address</button>
</li>
<li class="nav-item" role="presentation">
<button id="newVenueContactTabBtn" class="nav-link rounded-4" data-bs-toggle="pill" data-bs-target="#newVenueContactTab" type="button" role="tab" aria-controls="newVenueContactTab" aria-selected="false">Contact</button>
</li>
<li class="nav-item" role="presentation">
<button id="newVenueWatersTabBtn" class="nav-link rounded-4" data-bs-toggle="pill" data-bs-target="#newVenueWatersTab" type="button" role="tab" aria-controls="newVenueWatersTab" aria-selected="false">Waters</button>
</li>
</ul>
<div class="px-2 px-sm-4 pb-0">
<form id="venueForm" n class="needs-validation" novalidate>
<div id="newVenueTabs" class="tab-content">
<div id="newVenueDetailsTab" class="tab-pane fade show active" role="tabpanel" aria-labelledby="newVenueDetailsTabBtn" tabindex="0">
<div class="row g-4 align-items-start mb-3">
<div class="col-12">
<div class="form-floating">
<input name="venueName" id="venueName" type="text" class="form-control" placeholder="" minlength="3" maxlength="100" required>
<label for="venueName" class="form-label">
Name <strong class="text-danger">&ast;</strong>
</label>
<div class="invalid-feedback">Please enter the Venue's name</div>
</div>
</div>
<div class="col-sm-7">
<div class="form-floating">
<select name="venueType" id="venueType" class="form-select" placeholder="" required>
<option disabled value="">Choose one ...</option>
{% for type in venue_types %}
<option value="{{ type.0 }}">{{ type.1 }}</option>
{% endfor %}
</select>
<label for="venueType" class="form-label">
Type of Venue <strong class="text-danger">&ast;</strong>
</label>
<div class="invalid-feedback">Please select a Venue type</div>
</div>
</div>
<div class="col-12">
<div class="form-floating">
<textarea name="venueDescription" id="venueDescription" type="text" class="form-control venue-textarea" placeholder="" maxlength="500"></textarea>
<label for="venueDescription" class="form-label">
Description
</label>
<div class="invalid-feedback">Please enter a brief description of the Venue</div>
</div>
</div>
<div class="col-12">
<div class="form-floating">
<textarea name="venueExtraNotes" id="venueExtraNotes" type="text" class="form-control venue-textarea" placeholder="" maxlength="500"></textarea>
<label for="venueExtraNotes" class="form-label">Extra Notes</label>
<div class="invalid-feedback">Please enter any additional notes regarding the Venue</div>
</div>
</div>
<div class="row g-0 flex-md-grow-1">
<div class="col-md-1">
<div id="sidebarNavigation" class="bg-body-tertiary">
<div class="modal-sidebar flex-row flex-md-column align-items-start text-center" role="tablist">
<button id="detailsTab" class="modal-sidebar-btn active" type="button" data-bs-toggle="tab" data-bs-target="#detailsContent" role="tab" aria-controls="detailsContent" aria-selected="true">
<i class="bi bi-file-earmark-text"></i>
</button>
<button id="addressTab" class="modal-sidebar-btn" type="button" data-bs-toggle="tab" data-bs-target="#addressContent" role="tab" aria-controls="addressContent" aria-selected="false">
<i class="bi bi-geo-alt"></i>
</button>
<button id="contactTab" class="modal-sidebar-btn" type="button" data-bs-toggle="tab" data-bs-target="#contactContent" role="tab" aria-controls="contactContent" aria-selected="false">
<i class="bi bi-telephone"></i>
</button>
<button id="watersTab" class="modal-sidebar-btn" type="button" data-bs-toggle="tab" data-bs-target="#watersContent" role="tab" aria-controls="watersContent" aria-selected="false">
<i class="bi bi-droplet-half"></i>
</button>
<button id="confirmTab" class="modal-sidebar-btn mt-md-auto" type="button" data-bs-toggle="tab" data-bs-target="#confirmContent" role="tab" aria-controls="confirmContent" aria-selected="false">
<i class="bi bi-check-lg"></i>
</button>
</div>
</div>
</div>
<div class="col-md-11 overflow-auto">
<div class="tab-content d-inline-block w-100 h-100 py-3 px-4">
<div id="detailsContent" class="tab-pane fade flex-column h-100 show active">
<div class="form-floating mb-4">
<input type="text" name="venueName" id="venueName" class="form-control" placeholder="">
<label for="venueName" class="form-label">Venue Name</label>
</div>
<div class="form-floating mb-4">
<textarea class="form-control" style="height: 150px; resize: none;" placeholder=""></textarea>
<label for="" class="form-label">Description</label>
</div>
<div class="mb-4">
<div class="form-check form-check-inline">
<input type="radio" name="venueType" id="venueFishery" class="form-check-input">
<label for="venueFishery" class="form-check-label">Fishery</label>
</div>
<div class="form-check form-check-inline">
<input type="radio" name="venueType" id="venueClub" class="form-check-input">
<label for="venueClub" class="form-check-label">Club</label>
</div>
<div class="form-check form-check-inline">
<input type="radio" name="venueType" id="venuePrivate" class="form-check-input">
<label for="venuePrivate" class="form-check-label">Private</label>
</div>
</div>
<div id="newVenueAddressTab" class="tab-pane fade" role="tabpanel" aria-labelledby="newVenueAddressTabBtn" tabindex="0">
<div class="row flex-row-reverse">
<div class="col-lg-6">
<div class="form-floating mb-4">
<input name="venueStreetAddress" id="venueStreetAddress" type="text" class="form-control" placeholder="" required disabled>
<label for="venueStreetAddress" class="form-label">
Street Address <span class="text-danger">&ast;</span>
</label>
<div class="invalid-feedback"></div>
</div>
<div class="form-floating mb-4">
<input name="venueCity" id="venueCity" type="text" class="form-control" placeholder="" required disabled>
<label for="venueCity" class="form-label">
Town or City <strong class="text-danger">&ast;</strong>
</label>
<div class="invalid-feedback">Please enter a Town or City</div>
</div>
<div class="form-floating mb-4">
<input name="venueProvence" id="venueProvence" type="text" class="form-control" placeholder="" required disabled>
<label for="venueProvence" class="form-label">
Provence <strong class="text-danger">&ast;</strong>
</label>
<div class="invalid-feedback">Please enter a Provence</div>
</div>
<div class="form-floating mb-4">
<input name="venuePostCode" id="venuePostCode" type="text" class="form-control" placeholder="" required disabled>
<label for="venuePostCode" class="form-label">
Postal Code <strong class="text-danger">&ast;</strong>
</label>
<div class="invalid-feedback">Please enter a Postal Code</div>
</div>
<div class="form-floating mb-4">
<input name="venueLatitude" id="venueLatitude" type="text" class="form-control" placeholder="" required disabled>
<label for="venueLatitude" class="form-label">
Latitude <strong class="text-danger">&ast;</strong>
</label>
<div class="invalid-feedback">Please enter a Latitude</div>
</div>
<div class="form-floating">
<input name="venueLongitude" id="venueLongitude" type="text" class="form-control" placeholder="" required disabled>
<label for="venueLongitude" class="form-label">
Longitude <strong class="text-danger">&ast;</strong>
</label>
<div class="invalid-feedback">Please enter a Longitude</div>
</div>
</div>
<div class="col-lg-6 d-flex flex-column">
<div class="col-lg-8 input-group">
<span class="input-group-text bg-white pe-0">
<i class="bi bi-search"></i>
</span>
<input name="searchPostCode" id="searchPostCode" type="text" class="form-control border-start-0" list="postcodeSearchDataList" placeholder="Search by Post Code ...">
</div>
<datalist id="postcodeSearchDataList">
<option value="london street">london street</option>
</datalist>
<div class="separator my-3 text-secondary">
<span>or</span>
</div>
<h5 class="h6 text-body-secondary">Search by selecting a location on the map ...</h5>
<div id="locationMapContainer" class="w-100 position-relative flex-grow-1" >
<div id="locationMap" class="location-map rounded-2 w-100 h-100"></div>
<div id="locationMapOverlay" style="display: none;">
<div class="spinner"></div>
</div>
</div>
</div>
</div>
<!-- <div class="form-floating mb-3">
<input name="venuePostCode" id="venuePostCode" type="text" class="form-control" placeholder="" required>
<label for="venuePostCode" class="form-label">
Post Code <strong class="text-danger">&ast;</strong>
</label>
<div class="invalid-feedback">Please enter a Post Code</div>
</div>
<button class="btn btn-primary">
<i class="bi bi-search me-2"></i>
Find Address
</button> -->
<!-- <div class="mb-3">
<div class="form-floating">
<input name="venueStreetAddress" id="venueStreetAddress" type="text" class="form-control" placeholder="" required>
<label for="venueStreetAddress" class="form-label">
Street Address <strong class="text-danger">&ast;</strong>
</label>
<div class="invalid-feedback">Please enter a valid Street Address</div>
</div>
</div>
<div class="row g-4 align-items-start mb-3">
<div class="col-6">
<div class="form-floating">
<input name="venueCity" id="venueCity" type="text" class="form-control" placeholder="" required>
<label for="venueCity" class="form-label">
Town or City <strong class="text-danger">&ast;</strong>
</label>
<div class="invalid-feedback">Please enter a Town or City</div>
</div>
</div>
<div class="col-6">
<div class="form-floating">
<input name="venueProvence" id="venueProvence" type="text" class="form-control" placeholder="" required>
<label for="venueProvence" class="form-label">
Provence <strong class="text-danger">&ast;</strong>
</label>
<div class="invalid-feedback">Please enter a Provence</div>
</div>
</div>
<div class="col-6">
<div class="form-floating">
<input name="venuePostCode" id="venuePostCode" type="text" class="form-control" placeholder="" required>
<label for="venuePostCode" class="form-label">
Postal Code <strong class="text-danger">&ast;</strong>
</label>
<div class="invalid-feedback">Please enter a Postal Code</div>
</div>
</div>
<div class="col-6">
<div class="form-floating">
<select name="venueCountry" id="venueCountry" class="form-select" placeholder="" disabled required>
<option value="UK">United Kingdom</option>
</select>
<label for="venueCountry" class="form-label">
Country <i class="bi bi-lock-fill text-warning-emphasis"></i>
</label>
<div class="invalid-feedback">Please enter a Country</div>
</div>
</div>
<div class="col-12">
<div id="locationMapContainer" class="w-100 position-relative" style="height: 225px;">
<div id="locationMap" class="rounded-2 w-100 h-100"></div>
<div id="locationMapOverlay" style="display: none;">
<div class="spinner"></div>
</div>
</div>
</div>
<div class="col-6">
<div class="form-floating">
<input name="venueLatitude" id="venueLatitude" type="text" class="form-control" placeholder="" required>
<label for="venueLatitude" class="form-label">
Latitude <strong class="text-danger">&ast;</strong>
</label>
<div class="invalid-feedback">Please enter a Latitude</div>
</div>
</div>
<div class="col-6">
<div class="form-floating">
<input name="venueLongitude" id="venueLongitude" type="text" class="form-control" placeholder="" required>
<label for="venueLongitude" class="form-label">
Longitude <strong class="text-danger">&ast;</strong>
</label>
<div class="invalid-feedback">Please enter a Longitude</div>
</div>
</div>
</div> -->
<div class="form-check form-switch">
<input type="checkbox" name="venueActive" id="venueActive" class="form-check-input">
<label for="venueActive" class="form-check-label">Venue is active?</label>
</div>
<div id="newVenueContactTab" class="tab-pane fade" role="tabpanel" aria-labelledby="newVenueContactTabBtn" tabindex="0">
<div class="row g-4 align-items-start">
<div class="col-6">
<div class="form-floating">
<input name="venuePhone" id="venuePhone" type="tel" class="form-control" placeholder="">
<label for="venuePhone" class="form-label">Phone Number</label>
<div class="invalid-feedback">Bad phone number</div>
</div>
</div>
<div class="col-6">
<div class="form-floating">
<input name="venueEmail" id="venueEmail" type="email" class="form-control" placeholder="">
<label for="venueEmail" class="form-label">Email Address</label>
<div class="invalid-feedback"></div>
</div>
</div>
<div class="col-12">
<div class="form-floating">
<input name="venueWebsite" id="venueWebsite" type="url" class="form-control" placeholder="">
<label for="venueWebsite" class="form-label">Website Address</label>
<div class="invalid-feedback"></div>
</div>
</div>
<div class="col-12">
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-twitter" style="color: #1DA1F2"></i>
</span>
<div class="form-floating">
<input name="venueTwitter" id="venueTwitter" type="url" class="form-control" placeholder="">
<label for="venueTwitter" class="form-label d-flex align-items-center">
Twitter Profile Address
</label>
<div class="invalid-feedback"></div>
</div>
</div>
</div>
<div class="col-12">
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-facebook" style="color: #4267B2"></i>
</span>
<div class="form-floating">
<input name="venueFacebook" id="venueFacebook" type="url" class="form-control" placeholder="">
<label for="venueFacebook" class="form-label d-flex align-items-center">
Facebook Profile Address
</label>
<div class="invalid-feedback"></div>
</div>
</div>
</div>
<div class="col-12">
<div class="input-group">
<span class="input-group-text">
<i class="bi bi-instagram" style="color: #D62976;"></i>
</span>
<div class="form-floating">
<input name="venueInstagram" id="venueInstagram" type="url" class="form-control" placeholder="">
<label for="venueInstagram" class="form-label d-flex align-items-center">
Instagram Profile Address
</label>
<div class="invalid-feedback"></div>
</div>
</div>
</div>
</div>
</div>
<div id="newVenueWatersTab" class="tab-pane fade" role="tabpanel" aria-labelledby="newVenueWatersTabBtn" tabindex="0">
<div class="mt-5 mt-md-auto d-flex justify-content-end">
<button class="btn btn-primary px-3" type="button" onclick="$('#addressTab').click();">Next</button>
</div>
</div>
</form>
<div id="addressContent" class="tab-pane fade flex-column h-100">
<div class="form-floating mb-4">
<input type="text" name="venueName" id="venueName" class="form-control" placeholder="">
<label for="venueName" class="form-label">
<i class="bi bi-search me-1"></i>
Post Code or Street Address
</label>
</div>
<div class="mt-5 mt-md-auto d-flex justify-content-end">
<button class="btn btn-outline-secondary me-3" type="button" onclick="$('#detailsTab').click();">Back</button>
<button class="btn btn-primary px-3" type="button" onclick="$('#contactTab').click();">Next</button>
</div>
</div>
<div id="contactContent" class="tab-pane fade flex-column h-100">
<div class="row">
<div class="col-6">
<div class="form-floating mb-4">
<input type="tel" name="venuePhone" id="venuePhone" class="form-control" placeholder="">
<label for="venuePhone" class="form-label">Phone Number</label>
</div>
</div>
<div class="col-6">
<div class="form-floating mb-4">
<input type="email" name="venueEmail" id="venueEmail" class="form-control" placeholder="">
<label for="venueEmail" class="form-label">Email Address</label>
</div>
</div>
</div>
<div class="form-floating">
<input type="url" name="venueWebsite" id="venueWebsite" class="form-control" placeholder="">
<label for="venueWebsite" class="form-label">Website URL</label>
</div>
<hr class="my-4">
<div class="form-floating mb-4">
<input type="url" name="venueFacebook" id="venueFacebook" class="form-control" placeholder="">
<label for="" class="form-label">Facebook URL</label>
</div>
<div class="form-floating mb-4">
<input type="url" name="venueInstagram" id="venueInstagram" class="form-control" placeholder="">
<label for="venueInstagram" class="form-label">Instagram URL</label>
</div>
<div class="form-floating mb-4">
<input type="url" name="venueTwitter" id="venueTwitter" class="form-control" placeholder="">
<label for="venueTwitter" class="form-label">Twitter URL</label>
</div>
<div class="mt-5 mt-md-auto d-flex justify-content-end">
<button class="btn btn-outline-secondary me-3" type="button" onclick="$('#addressTab').click();">Back</button>
<button class="btn btn-primary px-3" type="button" onclick="$('#watersTab').click();">Next</button>
</div>
</div>
<div id="watersContent" class="tab-pane fade flex-column h-100">
waters
<div class="mt-5 mt-md-auto d-flex justify-content-end">
<button class="btn btn-outline-secondary me-3" type="button" onclick="$('#contactTab').click();">Back</button>
<button class="btn btn-primary px-3" type="button" onclick="$('#confirmTab').click();">Next</button>
</div>
</div>
<div id="confirmContent" class="tab-pane fade flex-column h-100">
confirm
<div class="mt-5 mt-md-auto d-flex justify-content-end">
<button class="btn btn-outline-secondary me-3" type="button" onclick="$('#watersTab').click();">Back</button>
<button class="btn btn-primary" type="button" onclick="alert('not implemented')">Save Changes</button>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer bg-light border-top-0 px-4 py-3">
<button type="button" class="btn btn-outline-danger rounded-4 me-auto edit">
<i class="bi bi-trash2"></i>
</button>
<button type="submit" id="saveVenue" class="btn btn-primary rounded-4 px-4" form="venueForm">
<span class="edit" style="display: none;">Save Edit</span>
<span class="create" style="display: none;">Save New</span>
</button>
<button type="button" class="btn btn-outline-secondary rounded-4 ms-auto" data-bs-dismiss="modal">
<i class="bi bi-x-lg"></i>
</button>
</div>
</div>
</div>
</div>
<!-- <div id="venueModal" class="modal fade" data-venue-id="-1" data-bs-backdrop="static">
<div class="modal-dialog modal-dialog-centered modal-fullscreen-sm-down ">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">New Venue</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div id="newVenuePages" class="modal-body">
<div id="detailsPage" class="page active">
<div class="form-floating mb-3">
<input type="text" class="form-control" placeholder="">
<label for="" class="form-label">Name</label>
</div>
<div class="form-floating mb-3">
<textarea class="form-control venue-textarea" placeholder=""></textarea>
<label for="" class="form-label">Description</label>
</div>
<label class="form-label">Type</label>
<div class="mb-3 group-radio-btns">
<input type="radio" name="vType" id="vtFishery" class="btn-check">
<label for="vtFishery" class="btn btn-outline-primary">Fishery</label>
<input type="radio" name="vType" id="vtClub" class="btn-check">
<label for="vtClub" class="btn btn-outline-primary">Club</label>
<input type="radio" name="vType" id="vtPrivate" class="btn-check">
<label for="vtPrivate" class="btn btn-outline-primary">Private</label>
</div>
</div>
<div id="addressPage" class="page">
<label class="form-label">Type part of the address or postcode to begin</label>
<input type="text" class="form-control" placeholder="E.g. 'CR0 3RL' or '36 Factory Lane'">
</div>
<div id="contactPage" class="page">
<div class="row mb-3">
<div class="col-lg-6">
<div class="form-floating">
<input type="tel" class="form-control" placeholder="">
<label for="">Phone Number</label>
</div>
</div>
<div class="col-lg-6">
<div class="form-floating">
<input type="email" class="form-control" placeholder="">
<label for="">Email Address</label>
</div>
</div>
</div>
<div class="form-floating mb-3">
<input type="url" class="form-control" placeholder="">
<label for="">
<i class="bi bi-link-45deg"></i>
Website
</label>
</div>
<div class="form-floating mb-3">
<input type="url" class="form-control" placeholder="">
<label for="">
<i class="bi bi-link-45deg"></i>
Facebook
</label>
</div>
<div class="form-floating mb-3">
<input type="url" class="form-control" placeholder="">
<label for="">
<i class="bi bi-link-45deg"></i>
Instagram
</label>
</div>
<div class="form-floating mb-3">
<input type="url" class="form-control" placeholder="">
<label for="">
<i class="bi bi-link-45deg"></i>
Twitter
</label>
</div>
</div>
<div id="watersPage" class="page">
Waters
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary me-auto" data-bs-dismiss="modal">Cancel</button>
<button type="button" id="newVenueBack" class="btn btn-secondary" style="display: none">
<i class="bi bi-arrow-left me-1"></i>
Back
</button>
<button type="button" id="newVenueNext" class="btn btn-primary">
Next
<i class="bi bi-arrow-right ms-1"></i>
</button>
<button type="button" id="newVenueFinished" class="btn btn-primary" style="display: none;">
Save Changes
</button>
</div>
</div>
</div>
</div> -->
{% endblock content %}
{% block scripts %}
<script>
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl, {trigger : 'hover'}))
$('[data-toggle="tooltip"]').tooltip()
</script>
<script>
(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
key: "AIzaSyBCs8WSphiPhNl2MJtJnPDFD8KLjjhAIYY",
v: "weekly",
});
</script>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<script src="https://unpkg.com/@turf/turf@6.5.0/turf.min.js"></script>
<script src="{% static 'js/mainapp/venues.js' %}" data-csrfmiddlewaretoken="{{ csrf_token }}"></script>
{% endblock scripts %}
{% endblock scripts %}+

View File

@ -1,17 +1,77 @@
$(".sidebar-collapse-button").on("click", function () {
// $(".sidebar-collapse-button").on("click", function () {
const sidebar = $("#sidebar")
const content = $("#webContent")
const collapsed = !sidebar.hasClass("sidebar-enlarged");
// const sidebar = $("#sidebar")
// const content = $("#webContent")
// const collapsed = !sidebar.hasClass("sidebar-enlarged");
if (collapsed) {
sidebar.addClass("sidebar-enlarged");
sidebar.find(".nav-item").tooltip("disable");
content.addClass("webcontent-collapsed")
}
else {
sidebar.removeClass("sidebar-enlarged");
sidebar.find(".nav-item").tooltip("enable");
content.removeClass("webcontent-collapsed")
}
});
// if (collapsed) {
// sidebar.addClass("sidebar-enlarged");
// sidebar.find(".nav-item").tooltip("disable");
// content.addClass("webcontent-collapsed")
// }
// else {
// sidebar.removeClass("sidebar-enlarged");
// sidebar.find(".nav-item").tooltip("enable");
// content.removeClass("webcontent-collapsed")
// }
// });
$(document).ready(function() {
const getStoredTheme = () => localStorage.getItem("theme");
const setStoredTheme = theme => localStorage.setItem("theme", theme);
const getPreferredTheme = () => {
const storedTheme = getStoredTheme();
if (storedTheme) return storedTheme;
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
};
const setTheme = theme => {
if (theme === "auto" && window.matchMedia("(prefers-color-scheme: dark)").matches) {
theme = "dark";
}
$("body").attr("data-bs-theme", theme);
};
const getThemeIcon = theme => {
return {
"auto": "diamond-half",
"light": "sun",
"dark": "moon-stars",
}[theme];
};
const showActiveTheme = (theme, focus = false) => {
const themeSwitcherButton = $("#themeSwitcher");
if (!themeSwitcherButton) {
warn("theme switcher button not found");
return;
}
// Swap the shown icon
const themeSwitcherIcon = $(themeSwitcherButton).find("i.bi");
const newIcon = getThemeIcon(theme);
themeSwitcherIcon.removeClass().addClass(`bi bi-${newIcon}`);
if (focus) themeSwitcherButton.focus();
};
$("#themeSwitcher").on("click", function() {
// 1. determine the current theme
const theme = getStoredTheme();
// 2. rotate to the next theme
const themes = ["light", "dark", "auto"];
const nextIndex = themes.indexOf(theme) + 1;
const nextTheme = nextIndex >= themes.length ? themes[0] : themes[nextIndex];
// 3. apply the new theme
setStoredTheme(nextTheme);
setTheme(nextTheme);
showActiveTheme(nextTheme);
});
var theme = getPreferredTheme();
setTheme(theme);
showActiveTheme(theme);
});

View File

@ -0,0 +1,386 @@
var marker = null;
map = null;
originalMapCoords = [51.509865, -0.118092];
isGeocodingInProgress = false;
scriptData = document.currentScript.dataset;
const formControls = [
// Details Tab
{
id: "venueName",
validation: function (element) {
const value = element.val(); // FIELD IS VALID IF:
return (!element.attr("required") || value.trim() !== ""); // - element is not required OR not empty
},
errorMessage: function (element) {
const minlength = element.attr("minlength");
return `Enter a venue name longer than ${minlength} characters`;
}
},
{
id: "venueType",
validation: function (element) {
const value = element.val(); // FIELD IS VALID IF:
return (!element.attr("required") || value !== null); // - element is not required OR not empty
},
errorMessage: function (element) {
return "Please selected a type of venue";
}
},
// Address Tab
{
id: "venueStreetAddress",
validation: function (element) {
const value = element.val();
return (!element.attr("required") || value.trim() !== "");
},
errorMessage: function (element) {
return "Enter the street address of the venue";
}
},
{
id: "venueCity",
validation: function (element) {
const value = element.val();
return (!element.attr("required") || value.trim() !== "");
},
errorMessage: function (element) {
return "Enter the town or city of the venue";
}
},
{
id: "venueProvence",
validation: function (element) {
const value = element.val();
return (!element.attr("required") || value.trim() !== "");
},
errorMessage: function (element) {
return "Enter the provence of the venue";
}
},
{
id: "venuePostCode",
validation: function (element) {
const value = element.val();
return (!element.attr("required") || value.trim() !== "");
},
errorMessage: function (element) {
return "Enter the postal code of the venue";
}
},
{
id: "venueLatitude",
validation: function (element) {
const value = element.val();
return (!element.attr("required") || value.trim() !== "");
},
errorMessage: function (element) {
return "Enter the latitude of the venue";
}
},
{
id: "venueLongitude",
validation: function (element) {
const value = element.val();
return (!element.attr("required") || value.trim() !== "");
},
errorMessage: function (element) {
return "Enter the longitude of the venue";
}
},
];
$("#newVenueAddressTabBtn").on("shown.bs.tab", function() {
if (!map) {
var tileLayer = L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: false,
})
map = L.map("locationMap", {
center: originalMapCoords,
zoom:8,
layers: [tileLayer]
});
const UKSouthWest = L.latLng(49.823809, -8.649357);
const UKNorthEast = L.latLng(60.905124, 2.637773);
const UKBounds = L.latLngBounds(UKSouthWest, UKNorthEast);
map.setMaxBounds(UKBounds);
}
venueCoords = {
lat: $("#venueLatitude").val(),
lng: $("#venueLongitude").val()
};
if (venueCoords.lat && venueCoords.lng) {
map.setView(new L.LatLng(venueCoords.lat, venueCoords.lng), 15);
marker = L.marker(venueCoords).addTo(map);
}
map.on('click', function (e) {
if (isGeocodingInProgress) {
return;
}
toggleLoadingMap(true);
var coordinates = e.latlng;
// Create a point feature from the clicked coordinates
var clickedPoint = turf.point([coordinates.lng, coordinates.lat]);
// Define the UK boundary polygon coordinates (simplified for illustration)
var ukBoundary = turf.polygon([
[
[-8.647, 59.688], // Northwest corner
[-8.647, 49.784], // Southwest corner
[1.768, 49.784], // Southeast corner
[1.768, 59.688], // Northeast corner
[-8.647, 59.688] // Close the polygon
]
]);
if (marker) {
map.removeLayer(marker);
}
marker = L.marker(coordinates).addTo(map);
var isInsideUK = turf.booleanPointInPolygon(clickedPoint, ukBoundary);
if (!isInsideUK) {
alert("Please select a point within the UK");
map.removeLayer(marker);
toggleLoadingMap(false);
return false;
}
fetch(`https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${coordinates.lat}&lon=${coordinates.lng}`)
.then(response => response.json())
.then(data => {
data.address; // check promise first
toggleLoadingMap(false);
$("#venueStreetAddress").val(data.address.road || '');
$("#venueCity").val(data.address.village || data.address.town || data.address.city);
$("#venueProvence").val(data.address.county || data.address.state_district);
$("#venuePostCode").val(data.address.postcode || '');
// $("#venueCountry").val(data.address.country || '');
$("#venueLatitude").val(coordinates.lat);
$("#venueLongitude").val(coordinates.lng);
});
});
setTimeout(function() {map.invalidateSize(false)}, 200)
});
function toggleLoadingMap(isLoading) {
isGeocodingInProgress = isLoading;
$("#saveVenue").prop("disabled", isLoading);
$("#venueStreetAddress").prop("disabled", isLoading)
$("#venueCity").prop("disabled", isLoading)
$("#venueProvence").prop("disabled", isLoading)
$("#venuePostCode").prop("disabled", isLoading)
// $("#venueCountry").prop("disabled", isLoading)
if (isLoading) {
$("#locationMapOverlay").show();
}
else {
$("#locationMapOverlay").hide();
}
}
$("#venueModal").on("hidden.bs.modal", function() {
// Reset the map if we are done
if (map) {
map.setView(originalMapCoords, 8);
if (marker) {
map.removeLayer(marker);
}
}
});
function openVenueModal(venue_id) {
$("#venueModal").data("venue-id", venue_id);
const detailsTab = new bootstrap.Tab("#newVenueDetailsTabBtn");
detailsTab.show(); // back to the first tab
$("#venueForm .form-control, #venueForm .form-select").removeClass("is-valid is-invalid");
$("#venueForm .invalid-feedback").text("");
if (venue_id == -1) {
$("#venueModal .edit").hide();
$("#venueModal .create").show();
$("#venueName").val("");
$("#venueType").val("").change();
$("#venueDescription").val("");
$("#venueExtraNotes").val("");
$("#venueStreetAddress").val("");
$("#venueCity").val("");
$("#venueProvence").val("");
$("#venuePostCode").val("");
// $("#venueCountry").val("");
$("#venueLatitude").val("");
$("#venueLongitude").val("");
$("#venuePhone").val("");
$("#venueEmail").val("");
$("#venueWebsite").val("");
$("#venueTwitter").val("");
$("#venueFacebook").val("");
$("#venueInstagram").val("");
}
else {
$("#venueModal .edit").show();
$("#venueModal .create").hide();
$.ajax({
url: `/venues/api/${venue_id}`,
method: 'get',
dataType: 'json',
success: function(response) {
const venue = response.data;
$("#venueName").val(venue.name);
$("#venueType").val(venue.venue_type).change();
$("#venueDescription").val(venue.description);
$("#venueExtraNotes").val(venue.extra_notes);
$("#venueStreetAddress").val(venue.street_address);
$("#venueCity").val(venue.city);
$("#venueProvence").val(venue.provence);
$("#venuePostCode").val(venue.postal_code);
// $("#venueCountry").val(venue.country);
$("#venueLatitude").val(venue.latitude);
$("#venueLongitude").val(venue.longitude);
$("#venuePhone").val(venue.phone_number);
$("#venueEmail").val(venue.email_address);
$("#venueWebsite").val(venue.website_url);
$("#venueTwitter").val(venue.twitter_url);
$("#venueFacebook").val(venue.facebook_url);
$("#venueInstagram").val(venue.instagram_url);
},
error: function(error) {
alert("error: " + JSON.stringify(error));
}
});
}
new bootstrap.Modal("#venueModal").show();
}
$("#venueForm").on("submit", function(event) {
event.preventDefault();
const valid = validateVenue();
if (valid) saveVenue();
});
function validateVenue() {
// Reset the validation indicators and messages
$("#venueForm .form-control, #venueForm .form-select").removeClass("is-valid is-invalid");
$("#venueForm .invalid-feedback").text("");
var valid = true;
formControls.forEach(function(control) {
var element = $("#" + control.id);
if (!control.validation(element)) {
element.addClass("is-invalid");
element.siblings(".invalid-feedback").text(control.errorMessage(element));
valid = false;
}
else {
element.addClass("is-valid");
}
});
return valid;
}
function saveVenue() {
var data = {
name: $("#venueName").val(),
description: $("#venueDescription").val(),
extra_notes: "* placeholder *",
venue_type: $("#venueType").val(),
street_address: $("#venueStreetAddress").val(),
city: $("#venueCity").val(),
provence: $("#venueProvence").val(),
postal_code: $("#venuePostCode").val(),
country: $("#venueCountry").val(),
longitude: $("#venueLongitude").val(),
latitude: $("#venueLatitude").val(),
phone_number: $("#venuePhone").val(),
email_address: $("#venueEmail").val(),
website_url: $("#venueWebsite").val(),
twitter_url: $("#venueTwitter").val(),
facebook_url: $("#venueFacebook").val(),
instagram_url: $("#venueInstagram").val(),
csrfmiddlewaretoken: scriptData.csrfmiddlewaretoken,
active: 1
}
const venue_id = $("#venueModal").data("venue-id");
data.id = venue_id > -1 ? venue_id : null;
$.ajax({
url: `/venues/api/create`,
method: 'post',
dataType: 'json',
data: data,
success: function(response) {
window.location.reload();
},
error: function(error) {
alert("error: " + JSON.stringify(error));
}
});
}
$("#newVenueTabLeft").on("click", function() {
const currentTab = $("#newVenueTabBtns .nav-link.active").parent();
const previousTabParent = currentTab.prev();
const previousTab = previousTabParent.find(".nav-link");
previousTab.click();
if (previousTabParent.is(":first-child")) {
$("#newVenueTabLeft").prop("disabled", true);
$("#newVenueTabRight").prop("disabled", false);
}
else {
$("#newVenueTabRight").prop("disabled", false);
$("#saveVenue").hide();
}
});
$("#newVenueTabRight").on("click", function() {
const currentTab = $("#newVenueTabBtns .nav-link.active").parent();
const nextTabParent = currentTab.next();
const nextTab = nextTabParent.find(".nav-link");
nextTab.click();
if (nextTabParent.is(":last-child")) {
$("#newVenueTabRight").prop("disabled", true);
$("#saveVenue").show();
$("#newVenueTabLeft").prop("disabled", false);
}
else {
$("#newVenueTabLeft").prop("disabled", false);
}
});

View File

@ -1,401 +1,73 @@
var marker = null;
map = null;
originalMapCoords = [51.509865, -0.118092];
isGeocodingInProgress = false;
scriptData = document.currentScript.dataset;
const formControls = [
// Details Tab
{
id: "venueName",
validation: function (element) {
const value = element.val(); // FIELD IS VALID IF:
return (!element.attr("required") || value.trim() !== ""); // - element is not required OR not empty
},
errorMessage: function (element) {
const minlength = element.attr("minlength");
return `Enter a venue name longer than ${minlength} characters`;
}
},
{
id: "venueType",
validation: function (element) {
const value = element.val(); // FIELD IS VALID IF:
return (!element.attr("required") || value !== null); // - element is not required OR not empty
},
errorMessage: function (element) {
return "Please selected a type of venue";
}
},
// Address Tab
{
id: "venueStreetAddress",
validation: function (element) {
const value = element.val();
return (!element.attr("required") || value.trim() !== "");
},
errorMessage: function (element) {
return "Enter the street address of the venue";
}
},
{
id: "venueCity",
validation: function (element) {
const value = element.val();
return (!element.attr("required") || value.trim() !== "");
},
errorMessage: function (element) {
return "Enter the town or city of the venue";
}
},
{
id: "venueProvence",
validation: function (element) {
const value = element.val();
return (!element.attr("required") || value.trim() !== "");
},
errorMessage: function (element) {
return "Enter the provence of the venue";
}
},
{
id: "venuePostCode",
validation: function (element) {
const value = element.val();
return (!element.attr("required") || value.trim() !== "");
},
errorMessage: function (element) {
return "Enter the postal code of the venue";
}
},
{
id: "venueLatitude",
validation: function (element) {
const value = element.val();
return (!element.attr("required") || value.trim() !== "");
},
errorMessage: function (element) {
return "Enter the latitude of the venue";
}
},
{
id: "venueLongitude",
validation: function (element) {
const value = element.val();
return (!element.attr("required") || value.trim() !== "");
},
errorMessage: function (element) {
return "Enter the longitude of the venue";
}
},
];
$(document).ready(function() {
// allLocationsMap
var allLocationstileLayer = L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: false,
})
var allLocationsmap = L.map("allLocationsMap", {
center: originalMapCoords,
zoom:8,
layers: [allLocationstileLayer]
});
setTimeout(function() {allLocationsmap.invalidateSize(false)}, 200)
});
$("#newVenueAddressTabBtn").on("shown.bs.tab", function() {
if (!map) {
var tileLayer = L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: false,
})
map = L.map("locationMap", {
center: originalMapCoords,
zoom:8,
layers: [tileLayer]
});
const UKSouthWest = L.latLng(49.823809, -8.649357);
const UKNorthEast = L.latLng(60.905124, 2.637773);
const UKBounds = L.latLngBounds(UKSouthWest, UKNorthEast);
map.setMaxBounds(UKBounds);
}
venueCoords = {
lat: $("#venueLatitude").val(),
lng: $("#venueLongitude").val()
};
if (venueCoords.lat && venueCoords.lng) {
map.setView(new L.LatLng(venueCoords.lat, venueCoords.lng), 15);
marker = L.marker(venueCoords).addTo(map);
}
map.on('click', function (e) {
if (isGeocodingInProgress) {
return;
}
toggleLoadingMap(true);
var coordinates = e.latlng;
// Create a point feature from the clicked coordinates
var clickedPoint = turf.point([coordinates.lng, coordinates.lat]);
// Define the UK boundary polygon coordinates (simplified for illustration)
var ukBoundary = turf.polygon([
[
[-8.647, 59.688], // Northwest corner
[-8.647, 49.784], // Southwest corner
[1.768, 49.784], // Southeast corner
[1.768, 59.688], // Northeast corner
[-8.647, 59.688] // Close the polygon
]
]);
if (marker) {
map.removeLayer(marker);
}
marker = L.marker(coordinates).addTo(map);
var isInsideUK = turf.booleanPointInPolygon(clickedPoint, ukBoundary);
if (!isInsideUK) {
alert("Please select a point within the UK");
map.removeLayer(marker);
toggleLoadingMap(false);
return false;
}
fetch(`https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${coordinates.lat}&lon=${coordinates.lng}`)
.then(response => response.json())
.then(data => {
data.address; // check promise first
toggleLoadingMap(false);
$("#venueStreetAddress").val(data.address.road || '');
$("#venueCity").val(data.address.village || data.address.town || data.address.city);
$("#venueProvence").val(data.address.county || data.address.state_district);
$("#venuePostCode").val(data.address.postcode || '');
// $("#venueCountry").val(data.address.country || '');
$("#venueLatitude").val(coordinates.lat);
$("#venueLongitude").val(coordinates.lng);
});
});
setTimeout(function() {map.invalidateSize(false)}, 200)
});
function toggleLoadingMap(isLoading) {
isGeocodingInProgress = isLoading;
$("#saveVenue").prop("disabled", isLoading);
$("#venueStreetAddress").prop("disabled", isLoading)
$("#venueCity").prop("disabled", isLoading)
$("#venueProvence").prop("disabled", isLoading)
$("#venuePostCode").prop("disabled", isLoading)
// $("#venueCountry").prop("disabled", isLoading)
if (isLoading) {
$("#locationMapOverlay").show();
}
else {
$("#locationMapOverlay").hide();
}
}
$("#venueModal").on("hidden.bs.modal", function() {
// Reset the map if we are done
if (map) {
map.setView(originalMapCoords, 8);
if (marker) {
map.removeLayer(marker);
}
}
});
function openVenueModal(venue_id) {
$("#venueModal").data("venue-id", venue_id);
const detailsTab = new bootstrap.Tab("#newVenueDetailsTabBtn");
detailsTab.show(); // back to the first tab
$("#venueForm .form-control, #venueForm .form-select").removeClass("is-valid is-invalid");
$("#venueForm .invalid-feedback").text("");
if (venue_id == -1) {
$("#venueModal .edit").hide();
$("#venueModal .create").show();
$("#venueName").val("");
$("#venueType").val("").change();
$("#venueDescription").val("");
$("#venueExtraNotes").val("");
$("#venueStreetAddress").val("");
$("#venueCity").val("");
$("#venueProvence").val("");
$("#venuePostCode").val("");
// $("#venueCountry").val("");
$("#venueLatitude").val("");
$("#venueLongitude").val("");
$("#venuePhone").val("");
$("#venueEmail").val("");
$("#venueWebsite").val("");
$("#venueTwitter").val("");
$("#venueFacebook").val("");
$("#venueInstagram").val("");
}
else {
$("#venueModal .edit").show();
$("#venueModal .create").hide();
$.ajax({
url: `/venues/api/${venue_id}`,
method: 'get',
dataType: 'json',
success: function(response) {
const venue = response.data;
$("#venueName").val(venue.name);
$("#venueType").val(venue.venue_type).change();
$("#venueDescription").val(venue.description);
$("#venueExtraNotes").val(venue.extra_notes);
$("#venueStreetAddress").val(venue.street_address);
$("#venueCity").val(venue.city);
$("#venueProvence").val(venue.provence);
$("#venuePostCode").val(venue.postal_code);
// $("#venueCountry").val(venue.country);
$("#venueLatitude").val(venue.latitude);
$("#venueLongitude").val(venue.longitude);
$("#venuePhone").val(venue.phone_number);
$("#venueEmail").val(venue.email_address);
$("#venueWebsite").val(venue.website_url);
$("#venueTwitter").val(venue.twitter_url);
$("#venueFacebook").val(venue.facebook_url);
$("#venueInstagram").val(venue.instagram_url);
},
error: function(error) {
alert("error: " + JSON.stringify(error));
}
});
}
// showPage($("#newVenuePages .page:first"));
$("#detailsTab").click();
setTimeout(() => { $("#venueName").focus() }, 300);
new bootstrap.Modal("#venueModal").show();
}
$("#venueForm").on("submit", function(event) {
event.preventDefault();
const valid = validateVenue();
if (valid) saveVenue();
});
function showPage(page) {
if (page.length === 0) return;
$("#newVenuePages .page").removeClass("active");
page.addClass("active");
function validateVenue() {
// Reset the validation indicators and messages
$("#venueForm .form-control, #venueForm .form-select").removeClass("is-valid is-invalid");
$("#venueForm .invalid-feedback").text("");
var valid = true;
formControls.forEach(function(control) {
var element = $("#" + control.id);
if (!control.validation(element)) {
element.addClass("is-invalid");
element.siblings(".invalid-feedback").text(control.errorMessage(element));
valid = false;
}
else {
element.addClass("is-valid");
}
});
return valid;
if (page.index() === 0) {
$("#newVenueBack").hide();
$("#newVenueNext").show();
$("#newVenueFinished").hide();
}
else if (page.index() + 1 === $("#newVenuePages .page").length) {
$("#newVenueBack").show();
$("#newVenueNext").hide();
$("#newVenueFinished").show();
}
else {
$("#newVenueBack").show();
$("#newVenueNext").show();
$("#newVenueFinished").hide();
}
}
function saveVenue() {
var data = {
name: $("#venueName").val(),
description: $("#venueDescription").val(),
extra_notes: "* placeholder *",
venue_type: $("#venueType").val(),
$("#newVenueNext").on("click", function() {
const currentPage = $("#newVenuePages .page.active");
showPage(currentPage.next(".page"));
});
street_address: $("#venueStreetAddress").val(),
city: $("#venueCity").val(),
provence: $("#venueProvence").val(),
postal_code: $("#venuePostCode").val(),
country: $("#venueCountry").val(),
longitude: $("#venueLongitude").val(),
latitude: $("#venueLatitude").val(),
$("#newVenueBack").on("click", function() {
const currentPage = $("#newVenuePages .page.active");
showPage(currentPage.prev(".page"));
});
phone_number: $("#venuePhone").val(),
email_address: $("#venueEmail").val(),
website_url: $("#venueWebsite").val(),
let map;
twitter_url: $("#venueTwitter").val(),
facebook_url: $("#venueFacebook").val(),
instagram_url: $("#venueInstagram").val(),
async function initMap() {
const position = { lat: 54.7023545, lng: -3.2765753 };
const { Map } = await google.maps.importLibrary("maps");
const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");
csrfmiddlewaretoken: scriptData.csrfmiddlewaretoken,
active: 1
}
const venue_id = $("#venueModal").data("venue-id");
data.id = venue_id > -1 ? venue_id : null;
$.ajax({
url: `/venues/api/create`,
method: 'post',
dataType: 'json',
data: data,
success: function(response) {
window.location.reload();
map = new Map($("#allLocationsMap")[0], {
zoom: 6,
restriction: {
latLngBounds: {
north: 58.804,
east: 2,
south: 49.864,
west: -8
},
strictBounds: true
},
error: function(error) {
alert("error: " + JSON.stringify(error));
}
center: position,
mapId: "ALL_LOCATIONS_MAP",
disableDefaultUI: true,
});
// const marker = new AdvancedMarkerElement({
// map: map,
// position: position,
// title: "Test"
// });
}
$("#newVenueTabLeft").on("click", function() {
const currentTab = $("#newVenueTabBtns .nav-link.active").parent();
const previousTabParent = currentTab.prev();
const previousTab = previousTabParent.find(".nav-link");
previousTab.click();
if (previousTabParent.is(":first-child")) {
$("#newVenueTabLeft").prop("disabled", true);
$("#newVenueTabRight").prop("disabled", false);
}
else {
$("#newVenueTabRight").prop("disabled", false);
$("#saveVenue").hide();
}
});
$("#newVenueTabRight").on("click", function() {
const currentTab = $("#newVenueTabBtns .nav-link.active").parent();
const nextTabParent = currentTab.next();
const nextTab = nextTabParent.find(".nav-link");
nextTab.click();
if (nextTabParent.is(":last-child")) {
$("#newVenueTabRight").prop("disabled", true);
$("#saveVenue").show();
$("#newVenueTabLeft").prop("disabled", false);
}
else {
$("#newVenueTabLeft").prop("disabled", false);
}
$(document).ready(function() {
initMap();
});

View File

@ -7,9 +7,10 @@ $danger-emphasis-colour: #58151c;
$secondary-emphasis-colour:red;
$secondary-subtle-colour:red;
$border-color: #dee2e6;
$body-bg :#000;
body {
// background-image: linear-gradient(0deg, #182848, #2980b9)
.bg-primary {
background-color: $primary-colour !important;
}
.btn {
@ -32,6 +33,10 @@ body {
border-color: $primary-hover-colour;
background-color: $primary-hover-colour
}
.btn-check:checked + & {
border-color: $primary-colour;
background-color: $primary-colour;
}
}
}
@ -86,6 +91,15 @@ body {
}
}
.hover-fill-primary {
background-color: transparent;
transition: color 0.3s, background-color 0.3s;
&:hover {
color: white;
background-color: $primary-hover-colour;
}
}
.separator {
display: flex;
align-items: center;
@ -104,6 +118,38 @@ body {
}
}
.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;
&:hover {
transform: scale(1.035);
}
}
.dropdown-menu {
.dropdown-item {
&:active {
color: black;
background-color: #f8f9fa;
}
}
}
.venue-textarea {
resize: none;
min-height: 152px !important;
}
#newVenuePages .page {
display: none;
&.active {
display: block !important;
}
}
@ -275,10 +321,70 @@ body {
border-bottom-right-radius: 1.2rem !important;
}
// bootstrap md breakpoint
@media (min-width: 768px) {
#sidebarNavigation {
height: 100%;
}
}
@media (max-width: 768px) {
#venueModal .modal-content > .row:first {
height: 100%;
flex: 1;
}
#sidebarNavigation {
border-radius: 0.375rem;
overflow-y: hidden;
width: fit-content;
margin: 0.75rem auto;
.modal-sidebar .modal-sidebar-btn {
padding: 0.5rem 1rem !important;
width: fit-content !important;
}
}
}
#venueModal .modal-content {
min-height: 550px;
overflow: hidden;
.modal-sidebar {
display: flex;
height: 100%;
padding: 0;
.modal-sidebar-btn {
background-color: inherit;
text-align: inherit;
border: 0;
text-decoration: none;
color: inherit;
padding: 0.75rem 1.5rem;
display: block;
width: 100%;
transition: background-color 0.15s, color 0.15s;
&:hover {
background-color: $primary-hover-colour;
color: white;
}
// &:active {
// background-color: $primary-colour;
// color: white;
// }
&.active {
background-color: $primary-colour !important;
color: white !important;
}
}
}
.tab-content > .active {
display: flex;
}
}
#venueModal
.location-map {
position: absolute;
top: 0;
@ -309,4 +415,8 @@ body {
.venue-textarea {
resize: none;
min-height: 152px !important;
}
[data-bs-theme="dark"] .group-radio-btns label {
color: white !important;
}

View File

@ -17,11 +17,11 @@
{% block style %}
{% endblock style %}
</head>
<body class="overflow-y-auto">
<body class="overflow-y-auto font-montserrat bg-body-tertiary">
<nav id="navbar" class="navbar navbar-expand-lg navbar-dark" style="background-color: #182848;">
<nav id="navbar" class="navbar navbar-expand-lg navbar-dark" style="background-color: #04385c;">
<div class="container-fluid">
<a href="#" class="navbar-brand mx-auto ms-sm-4 me-sm-5">
<a href="#" class="navbar-brand mx-auto ms-sm-4 me-sm-5 user-select-none">
<img src="{% static 'img/logo-icon-alt.webp' %}" class="me-1" height="45px" alt="Brand Logo">
<img src="{% static 'img/logo-text-alt.webp' %}" height="40px" alt="Brand Logo">
</a>
@ -43,23 +43,42 @@
<hr class="d-lg-none">
<ul class="navbar-nav mb-2 mb-lg-0 me-0 me-lg-4 flex-row flex-wrap">
<li class="nav-item mb-2 mb-lg-0">
<button class="btn btn-outline-light border-0 border-secondary-subtle d-flex align-items-center justify-content-center me-3">
<i class="bi bi-moon-stars"></i>
<button id="themeSwitcher" class="btn btn-outline-light border-0 border-secondary-subtle d-flex align-items-center justify-content-center me-3">
<i class="bi"></i>
<span class="d-lg-none ms-2">Theme</span>
</button>
</li>
<li class="nav-item mb-2 mb-lg-0">
<button class="btn btn-outline-light border-0 border-secondary-subtle d-flex align-items-center justify-content-center me-3">
<li class="nav-item mb-2 mb-lg-0 dropdown position-relative">
<button class="btn btn-outline-light border-0 border-secondary-subtle d-flex align-items-center justify-content-center me-3" data-bs-toggle="dropdown">
<i class="bi bi-bell"></i>
<span class="d-lg-none ms-2">Notifications</span>
</button>
<ul class="dropdown-menu dropdown-menu-end position-absolute me-3 p-3" style="min-width: 15rem;">
<li class="text-center text-body-secondary small">
There are no notifications right now
</li>
</ul>
</li>
<li class="nav-item mb-2 mb-lg-0 ms-auto ms-lg-0">
<button class="btn btn-outline-light rounded-pill border-secondary-subtle d-flex align-items-center justify-content-center me-3">
<li class="nav-item mb-2 mb-lg-0 ms-auto ms-lg-0 dropdown position-relative">
<button class="btn btn-outline-light rounded-pill border-secondary-subtle d-flex align-items-center justify-content-center me-3" data-bs-toggle="dropdown">
<i class="bi bi-person me-2"></i>
<span class="d-lg-none me-2">Administrator</span>
<i class="bi bi-chevron-down"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end position-absolute me-3">
<li>
<a href="" class="dropdown-item">
<i class="bi bi-gear me-2"></i>
Settings
</a>
</li>
<li>
<a href="" class="dropdown-item text-danger">
<i class="bi bi-box-arrow-right me-2"></i>
Logout
</a>
</li>
</ul>
</li>
</ul>
</div>

View File

@ -1,6 +1,18 @@
{% load static %}
<style type="text/css">
@font-face {
font-family: "Montserrat";
src: url("{% static 'fonts/Montserrat/ExtraBold.ttf' %}") format("truetype");
font-weight: bolder;
font-style: normal;
}
@font-face {
font-family: "Montserrat";
src: url("{% static 'fonts/Montserrat/Bold.ttf' %}") format("truetype");
font-weight: bold;
font-style: normal;
}
@font-face {
font-family: "Montserrat";
src: url("{% static 'fonts/Montserrat/Regular.ttf' %}") format("truetype");