Skip to content

Commit

Permalink
Feat/booking calendar
Browse files Browse the repository at this point in the history
* feat: add API for av slots for month

* fix: btn z-index

* feat: add getAvailableSlotsMonth

* feat: add color

* feat: block hover if av_slots <= 0

* ref: add async

* feat: max 2 month advance

* feat: hidden btn
  • Loading branch information
RezenkovD authored Aug 15, 2023
1 parent a61efa1 commit 5333df3
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 14 deletions.
31 changes: 29 additions & 2 deletions vr_club_site/templates/site/calendar.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<div id="prevMonthBtn" class="name-month"></div>
</div>
<div class="month" id="month"></div>
<div class="button-m item3">
<div id="nextMonth" class="button-m item3">
<div id="nextMonthBtn" class="name-month"></div>
<svg class="caret-right" width="32" height="32" viewBox="0 0 32 32" fill="none"
xmlns="http://www.w3.org/2000/svg">
Expand All @@ -42,7 +42,7 @@
<script src="{% static 'site/js/calendar.js' %}"></script>
</section>
<script>
function getAvailableSlots(date) {
async function getAvailableSlots(date) {
fetch(`{% url 'site:get_available_slots' %}?date=${date}`, {
method: "GET",
headers: {
Expand All @@ -64,4 +64,31 @@
updateSlotAvailability();
});
}
async function getAvailableSlotsMonth(currentYear, currentMonth) {
currentMonth += 1
fetch(`{% url 'site:get_available_slots_for_month' %}?year=${currentYear}&month=${currentMonth}`, {
method: "GET",
headers: {
"X-Requested-With": "XMLHttpRequest",
},
})
.then(response => response.json())
.then(data => {
console.log(data);
data.available_slots.forEach(item => {
const date = new Date(item.date);
const dayNumber = date.getDate();
const countPlaceElement = document.querySelector(`[data-day="${dayNumber}"] .count_place`);
if (countPlaceElement) {
if (item.available_slots <= 0) {
countPlaceElement.closest('.wrap').classList.add('inactive');
countPlaceElement.closest('.wrap').removeAttribute('onclick');
countPlaceElement.closest('.wrap').style.cursor = 'default';
}
countPlaceElement.textContent = `${item.available_slots} місць`;
}
});
});

}
</script>
35 changes: 30 additions & 5 deletions vr_club_site/templates/site/js/calendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ document.addEventListener("DOMContentLoaded", function () {
const nextMonthBtn = document.getElementById("nextMonthBtn");
let currentYear, currentMonth;

function generateCalendar(year, month) {
async function generateCalendar(year, month) {
const now = new Date();
const daysInMonth = new Date(year, month + 1, 0).getDate();
const firstDayIndex = new Date(year, month, 0).getDay();
Expand Down Expand Up @@ -38,23 +38,26 @@ document.addEventListener("DOMContentLoaded", function () {

let today = now.getDate();
let number;
let day_;
for (let i = 1; i <= daysInMonth; i++) {
day_ = i
number = i;
if (number < 10) {
number = "0" + number;
}
if (i === now.getDate() && month === now.getMonth() && year === now.getFullYear()) {
today = i;
days += `<div class="wrap" onclick="openPopup('${number}', '${month + 1}', '${year}')" style="cursor: pointer;"><div class="blue-background"></div><div class="today">${number}</div><div class="count_place">12 місць</div></div>`;
days += `<div class="wrap" onclick="openPopup('${number}', '${month + 1}', '${year}')" data-day="${day_}" style="cursor: pointer;"><div class="blue-background"></div><div class="today" style="color: #001729;">${number}</div><div class="count_place" style="color: #001729;"></div></div>`;
} else {
if (i < today && month <= now.getMonth() && year <= now.getFullYear() || month < now.getMonth() && year <= now.getFullYear() || month > now.getMonth() && year < now.getFullYear() || i >= today && month === now.getMonth() && year < now.getFullYear()) {
days += `<div class="wrap"><div class="no-change"></div><div class="old-day">${number}</div></div>`;
}
else {
days += `<div class="wrap" onclick="openPopup('${number}', '${month + 1}', '${year}')" style="cursor: pointer;"><div class="us-background"></div><div class="base-day">${number}</div></div>`;
days += `<div class="wrap" onclick="openPopup('${number}', '${month + 1}', '${year}')" data-day="${day_}" style="cursor: pointer;"><div class="us-background"></div><div class="base-day">${number}</div><div class="count_place"></div></div>`;
}
}
}
await getAvailableSlotsMonth(year, month)

for (let i = 1; i < 7 - lastDayIndex; i++) {
days += `<div class="transparent-wrap"></div>`;
Expand Down Expand Up @@ -85,6 +88,19 @@ document.addEventListener("DOMContentLoaded", function () {
prevMonth_.style.cursor = "pointer"
}

const nextMonth_ = document.getElementById("nextMonth");
const currentDate = new Date(currentYear, currentMonth);
const maxAllowedDate = new Date();
maxAllowedDate.setMonth(maxAllowedDate.getMonth() + maxMonthsAhead - 1);

if (currentDate > maxAllowedDate) {
nextMonth_.style.opacity = 0;
nextMonth_.style.cursor = "default"
} else {
nextMonth_.style.opacity = 1;
nextMonth_.style.cursor = "pointer"
}

}

function updateCalendar() {
Expand All @@ -102,7 +118,17 @@ document.addEventListener("DOMContentLoaded", function () {
}
}

const maxMonthsAhead = 2;

function nextMonth() {
const currentDate = new Date(currentYear, currentMonth);

const maxAllowedDate = new Date();
maxAllowedDate.setMonth(maxAllowedDate.getMonth() + maxMonthsAhead - 1);

if (currentDate > maxAllowedDate) {
return;
}
currentMonth++;
if (currentMonth > 11) {
currentMonth = 0;
Expand Down Expand Up @@ -142,8 +168,7 @@ async function openPopup(day, month, year) {

dateDiv.textContent = `${parseInt(day, 10)} ${monthsNames[parseInt(month) - 1]} ${year}`;

getAvailableSlots(`${year}-${parseInt(month, 10)}-${parseInt(day, 10)}`)
await new Promise(resolve => setTimeout(resolve, 150));
await getAvailableSlots(`${year}-${parseInt(month, 10)}-${parseInt(day, 10)}`)
const dateInput = document.getElementById('id_date');
dateInput.value = `${day}-${month}-${year}`;

Expand Down
23 changes: 22 additions & 1 deletion vr_club_site/templates/site/style/calendar.css
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@
}

.count_place {
color: #001729;
color: #4FB4FE;
text-align: left;
font: 500 18px/150% "Montserrat", sans-serif;
position: relative;
Expand All @@ -230,6 +230,23 @@
margin-top: 44px;
}

.wrap:hover .count_place {
color: #010103;
}

.inactive .base-day {
color: #768A9A;
}

.inactive .count_place {
color: #768A9A;
}

.wrap.inactive {
pointer-events: none;
}


@media screen and (max-width: 768px) {
.booking {
font: 700 28px "Montserrat", sans-serif;
Expand Down Expand Up @@ -362,6 +379,10 @@
height: 80px;
}

.wrap:hover .count_place {
color: #010103;
}

.count_place {
font: 500 12px/110% "Montserrat", sans-serif;
height: 28px;
Expand Down
2 changes: 2 additions & 0 deletions vr_club_site/templates/site/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@
cursor: pointer;
margin-left: auto;
margin-right: auto;
z-index: 9999;
}

.section-rules-club {
Expand Down Expand Up @@ -687,6 +688,7 @@
cursor: pointer;
margin-left: auto;
margin-right: auto;
z-index: 9999;
}
}

Expand Down
11 changes: 10 additions & 1 deletion vr_club_site/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from django.urls import path

from .views import index_page, get_available_slots_view
from .views import (
index_page,
get_available_slots_view,
get_available_slots_for_month_view,
)
from games.views import game_page

app_name = "site"
Expand All @@ -13,4 +17,9 @@
get_available_slots_view,
name="get_available_slots",
),
path(
"api/get_available_slots_for_month/",
get_available_slots_for_month_view,
name="get_available_slots_for_month",
),
]
76 changes: 71 additions & 5 deletions vr_club_site/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import calendar
import decimal
from datetime import datetime, date

from django.db import IntegrityError, transaction
from django.db.models import Sum
from django.db.models import F, Sum, Case, When, Value, IntegerField
from django.db.models.functions import Greatest, Least
from django.contrib import messages

from .models import Booking, BookingTime, ACTUAL, Settings
Expand All @@ -11,9 +14,7 @@
MAX_SESSION = 2


def get_available_slots(selected_date=None):
_available_slots = []

def get_session_seats():
try:
setting = Settings.objects.get(name="seats")
setting_type = setting.variable_type
Expand All @@ -28,12 +29,77 @@ def get_available_slots(selected_date=None):
except Settings.DoesNotExist:
session_seats = 1

return session_seats


def get_available_slots_for_month(currentYear=None, currentMonth=None):
_available_slots = []
session_seats = get_session_seats()
days_in_month = calendar.monthrange(int(currentYear), int(currentMonth))[1]

time_choice_to_index = {
choice[0]: index for index, choice in enumerate(BookingTime.TIME_CHOICES)
}

all_time_choices = [choice[0] for choice in BookingTime.TIME_CHOICES]

daily_available_slots = {
day: [session_seats] * len(all_time_choices)
for day in range(1, days_in_month + 1)
}

bookings = (
Booking.objects.filter(
time__status=ACTUAL,
time__date__year=currentYear,
time__date__month=currentMonth,
)
.annotate(
time_index=Case(
*[
When(time__time=choice, then=Value(index))
for choice, index in time_choice_to_index.items()
],
output_field=IntegerField(),
)
)
.values("time__date", "time_index")
.annotate(total_people=Sum("people_count"))
)

for booking in bookings:
day = booking["time__date"].day
time_index = booking["time_index"]
total_people = booking["total_people"]
daily_available_slots[day][time_index] -= total_people

for day, available_slots in daily_available_slots.items():
selected_date = date(int(currentYear), int(currentMonth), day)
total_available_slots = sum(max(slot, 0) for slot in available_slots)
_available_slots.append(
{
"date": selected_date.strftime("%Y-%m-%d"),
"available_slots": total_available_slots,
}
)

return _available_slots


def get_available_slots(selected_date=None):
_available_slots = []
session_seats = get_session_seats()

for x in BookingTime.TIME_CHOICES:
try:
slot = (
Booking.objects.filter(
time__status=ACTUAL, time__time=x[0], time__date=selected_date
).aggregate(total_people=Sum("people_count"))["total_people"]
).aggregate(
total_people=Least(Greatest(Sum("people_count"), 0), session_seats)
)[
"total_people"
]
or 0
)
except BookingTime.DoesNotExist:
Expand Down
10 changes: 10 additions & 0 deletions vr_club_site/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from .models import BookingTime
from .utils import (
get_available_slots,
get_available_slots_for_month,
get_session_seats,
is_all_neighbors,
has_invalid_slots,
book_session,
Expand All @@ -21,6 +23,14 @@ def get_available_slots_view(request):
return JsonResponse({"available_slots": available_slots})


def get_available_slots_for_month_view(request):
if request.method == "GET":
year = request.GET.get("year")
month = request.GET.get("month")
available_slots = get_available_slots_for_month(year, month)
return JsonResponse({"available_slots": available_slots})


def index_page(request):
if request.method == "POST":
form = BookingForm(request.POST)
Expand Down

0 comments on commit 5333df3

Please sign in to comment.