@@ -617,41 +57,8 @@
- Забронюй час гри прямо зараз!
-
-
-
-
Пн
-
Вт
-
Ср
-
Чт
-
Пт
-
Сб
-
Нд
-
-
-
-
+ {% include 'site/calendar.html' %}
+
{% endblock %}
{% block script %}
-
{% endblock %}
\ No newline at end of file
diff --git a/games/templates/games/svg-objects/games-group1.html b/games/templates/games/svg-objects/games-group1.html
new file mode 100644
index 0000000..9841332
--- /dev/null
+++ b/games/templates/games/svg-objects/games-group1.html
@@ -0,0 +1,141 @@
+
diff --git a/games/templates/games/svg-objects/games-group2.html b/games/templates/games/svg-objects/games-group2.html
new file mode 100644
index 0000000..c6b1cd4
--- /dev/null
+++ b/games/templates/games/svg-objects/games-group2.html
@@ -0,0 +1,141 @@
+
diff --git a/games/templates/games/svg-objects/games-group3.html b/games/templates/games/svg-objects/games-group3.html
new file mode 100644
index 0000000..52a3ba4
--- /dev/null
+++ b/games/templates/games/svg-objects/games-group3.html
@@ -0,0 +1,141 @@
+
diff --git a/games/templates/games/svg-objects/games-group4.html b/games/templates/games/svg-objects/games-group4.html
new file mode 100644
index 0000000..6eb769c
--- /dev/null
+++ b/games/templates/games/svg-objects/games-group4.html
@@ -0,0 +1,141 @@
+
diff --git a/games/templates/style/games.css b/games/templates/style/games.css
index ea0923f..a098753 100644
--- a/games/templates/style/games.css
+++ b/games/templates/style/games.css
@@ -1,4 +1,5 @@
.game-panel {
+ margin-top: 44px;
display: flex;
width: 987px;
flex-direction: row;
@@ -102,7 +103,7 @@
text-align: center;
font: 500 14px "Montserrat", sans-serif;
line-height: 24px;
- padding: 3px 8px;
+ padding: 0 13.5px;
}
.rectangle-12,
@@ -126,13 +127,6 @@
.game-card:hover .rectangle-12 {
opacity: 1;
}
-/*.game-title,*/
-/*.player-badge,*/
-/*.descriptions,*/
-/*.btn-blue,*/
-/*.btn-play {*/
-/* transition: transform 0.3s ease-in-out;*/
-/*}*/
.game-card:hover .game-title {
transform: translateY(-262px);
@@ -238,11 +232,14 @@
}
.games {
+ margin-top: 90px;
text-align: center;
font: 700 48px "Montserrat", sans-serif;
position: relative;
- width: 1187px;
- height: 65px;
+ max-width: 1187px;
+ width: auto;
+ max-height: 65px;
+ height: auto;
margin-left: auto;
margin-right: auto;
}
@@ -260,217 +257,6 @@
font: 700 48px "Montserrat", sans-serif;
}
-.calendar {
- width: 738px;
-}
-
-.month {
- color: #ffffff;
- text-align: center;
- font: 600 24px "Montserrat", sans-serif;
- position: relative;
- width: auto;
- height: 33px;
-}
-
-.weekdays {
- margin-top: 46px;
- opacity: 0.5;
- display: flex;
- flex-direction: row;
- gap: 72px;
- align-items: flex-start;
- justify-content: flex-start;
- position: relative;
-}
-
-.weekdays div {
- color: #ffffff;
- text-align: center;
- font: 500 18px/150% "Montserrat", sans-serif;
- position: relative;
- width: 33px;
- height: 31px;
-}
-
-.days {
- margin-top: 24px;
- display: grid;
- grid-template-columns: repeat(7, 1fr);
- gap: 4px;
-}
-
-.month-nav {
- display: flex;
- justify-content: space-between;
- width: 738px;
-}
-
-.item1 {
- cursor: pointer;
-}
-
-.item3 {
- cursor: pointer;
-}
-
-.button-m,
-.button-m * {
- box-sizing: border-box;
-}
-
-.button-m {
- display: flex;
- flex-direction: row;
- gap: 12px;
- align-items: flex-start;
- justify-content: flex-start;
- position: relative;
-}
-
-.name-month {
- color: #4fb4fe;
- text-align: left;
- font: 500 24px "Montserrat", sans-serif;
- position: relative;
- width: auto;
- height: 33px;
-}
-
-.caret-right {
- flex-shrink: 0;
- position: relative;
- overflow: visible;
- bottom: 1px;
-}
-
-.caret-left {
- flex-shrink: 0;
- position: relative;
- overflow: visible;
- bottom: 1px;
-}
-
-.wrap,
-.wrap * {
- box-sizing: border-box;
-}
-
-.wrap {
- width: 102px;
- height: 102px;
- position: static;
-}
-
-.transparent-wrap,
-.transparent-wrap * {
- box-sizing: border-box;
-}
-
-.transparent-wrap {
- width: 102px;
- height: 102px;
- position: static;
- opacity: 0;
-}
-
-.no-change {
- background: rgba(79, 180, 254, 0.2);
- border-radius: 12px;
- width: 102px;
- height: 102px;
- position: absolute;
-}
-
-.us-background {
- background: rgba(79, 180, 254, 0.2);
- border-radius: 12px;
- width: 102px;
- height: 102px;
- position: absolute;
-}
-
-.blue-background {
- background: #4fb4fe;
- border-radius: 12px;
- width: 102px;
- height: 102px;
- position: absolute;
-}
-
-.old-day {
- color: rgba(255, 255, 255, 0.5);
- text-align: left;
- font: 500 24px/150% "Montserrat", sans-serif;
- position: relative;
- padding-top: 8px;
- padding-left: 12px;
- cursor: default;
-}
-
-.base-day {
- color: #4FB4FE;
- font: 500 24px/150% "Montserrat", sans-serif;
- position: absolute;
- padding-top: 8px;
- padding-left: 12px;
- cursor: default;
-}
-
-.today {
- color: #001729;
- font: 500 24px/150% "Montserrat", sans-serif;
- position: absolute;
- padding-top: 8px;
- padding-left: 12px;
- cursor: default;
-}
-
-.wrap:hover .blue-background {
- background: #ffffff;
- border-radius: 12px;
- width: 102px;
- height: 102px;
- position: absolute;
-}
-
-.wrap:hover .us-background {
- background: #ffffff;
- border-radius: 12px;
- width: 102px;
- height: 102px;
- position: absolute;
-}
-
-.wrap:hover .today {
- color: #001729;
-}
-
-.wrap:hover .base-day {
- color: #001729;
-}
-
-.count_place,
-.count_place * {
- box-sizing: border-box;
-}
-
-.count_place {
- color: #001729;
- text-align: left;
- font: 500 18px/150% "Montserrat", sans-serif;
- position: relative;
- height: 25px;
- padding-left: 12px;
- padding-top: 54px;
-}
-
-.flex-calendar {
- display: flex;
- flex-direction: column;
- align-items: center;
-}
-
.all-games,
.all-games * {
box-sizing: border-box;
@@ -498,6 +284,7 @@
}
.filter {
+ margin-top: 44px;
display: flex;
flex-direction: row;
justify-content: center;
@@ -522,7 +309,7 @@
padding: 10px 10px;
text-align: center;
font-size: 16px;
- font-family: "Gilroy", sans-serif;
+ font-family: "Montserrat", sans-serif;
font-weight: 700;
line-height: 150.521%;
text-transform: uppercase;
@@ -643,3 +430,268 @@
opacity: 0.11999999731779099;
overflow: visible;
}
+
+@media screen and (max-width: 1000px) {
+ .game-panel {
+ margin-top: 32px;
+ display: flex;
+ width: auto;
+ margin-left: auto;
+ margin-right: auto;
+ row-gap: 24px;
+ column-gap: 0;
+ align-items: center;
+ flex-wrap: wrap;
+ justify-content: space-evenly;
+ flex-direction: row;
+ }
+}
+
+@media screen and (max-width: 768px) {
+ .games,
+ .games * {
+ box-sizing: border-box;
+ }
+
+ .games {
+ margin-top: 56px;
+ font: 700 28px "Montserrat", sans-serif;
+ position: relative;
+ max-width: 327px;
+ width: auto;
+ max-height: 70px;
+ height: auto;
+ }
+
+ .span {
+ font: 700 28px "Montserrat", sans-serif;
+ }
+ .span2 {
+ font: 700 28px "Montserrat", sans-serif;
+ }
+ .span3 {
+ font: 700 28px "Montserrat", sans-serif;
+ }
+
+ .filter {
+ margin-top: 44px;
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ max-width: 343px;
+ width: auto;
+ max-height: 44px;
+ height: auto;
+ gap: 12px;
+ }
+
+ .fil-games {
+ width: auto;
+ height: 44px;
+ border-radius: 100px;
+ border: 1px solid #FFF;
+ color: #FFF;
+ padding: 12px 10px;
+ text-align: center;
+ font-size: 12px;
+ font-family: "Montserrat", sans-serif;
+ font-weight: 700;
+ }
+
+ .all-games {
+ width: auto;
+ height: 44px;
+ border-radius: 100px;
+ background: #4FB4FE;
+ padding: 12px 13px;
+
+ color: #FFF;
+ text-align: center;
+ font-size: 12px;
+ font-family: "Montserrat", sans-serif;
+ font-weight: 700;
+ line-height: 150.521%;
+ text-transform: uppercase;
+ }
+
+ .game-panel {
+ margin-top: 32px;
+ display: flex;
+ width: auto;
+ margin-left: auto;
+ margin-right: auto;
+ row-gap: 24px;
+ column-gap: 0;
+ flex-direction: column;
+ flex-wrap: nowrap;
+ align-items: center;
+ }
+
+ .game-card {
+ width: 263px;
+ height: 336px;
+ }
+
+ .group-1 {
+ width: 100%;
+ height: 100%;
+ }
+
+ .game-image {
+ width: 100%;
+ height: 100%;
+ }
+
+ .overlay {
+ width: 100%;
+ height: 100%;
+ }
+
+ .rectangle-12 {
+ width: 100%;
+ height: 100%;
+ }
+
+ .game-title {
+ font: 600 18px "Montserrat", sans-serif;
+ left: 16px;
+ top: 290px;
+ width: auto;
+ }
+
+ .age-group {
+ width: 44px;
+ height: 44px;
+ position: static;
+ }
+
+ .age-badge {
+ left: 203px;
+ }
+
+ .player-count {
+ width: auto;
+ height: 24px;
+ position: static;
+ }
+
+ .player-badge {
+ top: 254px;
+ font: 500 12px "Montserrat", sans-serif;
+ line-height: 24px;
+ }
+
+ .game-card:hover .rectangle-12 {
+ opacity: 1;
+ outline: none !important;
+ }
+
+ .game-title,
+ .player-badge,
+ .descriptions,
+ .btn-blue,
+ .btn-play {
+ transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55), opacity 0.3s;
+ }
+
+ .game-card:hover .game-title {
+ transform: translateY(-238px);
+ }
+
+ .game-card:hover .player-badge {
+ transform: translateY(-238px);
+ }
+
+ .descriptions {
+ color: var(--white, #f1f1f1);
+ text-align: left;
+ font: 500 14px "Montserrat", sans-serif;
+ width: auto;
+ padding-right: 15px;
+ max-height: 158px;
+ }
+
+ .game-card:hover .descriptions {
+ opacity: 1;
+ transform: translateY(-250px);
+ }
+
+ .btn-blue {
+ width: 178px;
+ height: 44px;
+ }
+
+ .btn-play {
+ margin-left: 202px;
+ }
+
+ .ellipse-1 {
+ background: #ef7d1e;
+ border-radius: 50%;
+ width: 165px;
+ height: 165px;
+ position: absolute;
+ filter: blur(125px);
+ top: 1300px;
+ left: 0px;
+ }
+
+
+ .ellipse-2 {
+ background: #ef7d1e;
+ border-radius: 50%;
+ width: 165px;
+ height: 165px;
+ position: absolute;
+ filter: blur(125px);
+ top: 600px;
+ left: 300px;
+ }
+
+
+ .ellipse-3 {
+ background: #00a3ff;
+ border-radius: 50%;
+ width: 178px;
+ height: 178px;
+ position: absolute;
+ filter: blur(125px);
+ top: -65px;
+ right: 243px;
+ }
+
+
+ .ellipse-4 {
+ background: #00a3ff;
+ border-radius: 50%;
+ width: 178px;
+ height: 178px;
+ position: absolute;
+ filter: blur(125px);
+ left: 240px;
+ top: 2000px;
+ }
+
+
+ .group1 {
+ display: none;
+ }
+
+ .group2 {
+ display: none;
+ }
+
+ .group3 {
+ position: absolute;
+ overflow: visible;
+ transform: rotate(303deg);
+ top: -905px;
+ left: -793px;
+ opacity: 0.11999999731779099;
+ }
+
+ .group4 {
+ display: none;
+ }
+
+}
\ No newline at end of file
diff --git a/games/urls.py b/games/urls.py
index c2b0887..f347153 100644
--- a/games/urls.py
+++ b/games/urls.py
@@ -1,4 +1,3 @@
-from django.contrib.auth import views as auth_views
from django.urls import path
from .views import filter_games
diff --git a/games/views.py b/games/views.py
index 8bcced3..dc7dd8a 100644
--- a/games/views.py
+++ b/games/views.py
@@ -1,13 +1,64 @@
-from django.shortcuts import render, reverse
+from django.shortcuts import render, reverse, redirect
from django.http import HttpResponseBadRequest, JsonResponse
from django.db.models import Q
+from django.contrib import messages
from .models import Games
-# Create your views here.
+from vr_club_site.forms import BookingForm
+from vr_club_site.models import BookingTime
+from vr_club_site.utils import (
+ get_available_slots,
+ has_invalid_slots,
+ is_all_neighbors,
+ book_session,
+ MAX_SESSION,
+)
-def games(request):
+def game_page(request):
+ if request.method == "POST":
+ form = BookingForm(request.POST)
+ if not form.is_valid():
+ messages.error(request, "Будь ласка, оберіть слот, перед відправкою!")
+ return render_game_page(request)
+ selected_date = form.cleaned_data["date"]
+ _available_slots = get_available_slots(selected_date)
+ slot_av_slots = {
+ y[0]: x for y, x in zip(BookingTime.TIME_CHOICES, _available_slots)
+ }
+ slot_compatibility = {
+ y[0]: index for index, y in enumerate(BookingTime.TIME_CHOICES)
+ }
+ slots = form.cleaned_data["slots"]
+
+ if len(slots) > MAX_SESSION:
+ messages.error(
+ request, f"Максимальна кількість обраних сеансів: {MAX_SESSION}"
+ )
+ return render_game_page(request)
+
+ if len(slots) > 1:
+ _is_all_neighbors = is_all_neighbors(slot_compatibility, slots)
+
+ if not _is_all_neighbors:
+ messages.error(request, "Сеанси мають бути суміжними")
+ return render_game_page(request)
+
+ people_count = form.cleaned_data["people_count"]
+ _has_invalid_slots = has_invalid_slots(
+ request, people_count, slot_av_slots, slots
+ )
+
+ if not _has_invalid_slots:
+ book_session(request, slots, form.cleaned_data)
+ return redirect("site:games")
+
+ return render_game_page(request)
+
+
+def render_game_page(request):
+ form = BookingForm()
signup_url = reverse("account_signup")
login_url = reverse("account_login")
game_mode = request.GET.get("game_mode")
@@ -19,7 +70,12 @@ def games(request):
return render(
request,
"games/games.html",
- {"signup_url": signup_url, "login_url": login_url, "games": games},
+ {
+ "form": form,
+ "signup_url": signup_url,
+ "login_url": login_url,
+ "games": games,
+ },
)
diff --git a/vr_club_site/templates/site/booking.html b/vr_club_site/templates/site/booking.html
index 6781ba3..20036b5 100644
--- a/vr_club_site/templates/site/booking.html
+++ b/vr_club_site/templates/site/booking.html
@@ -1,6 +1,5 @@
diff --git a/vr_club_site/templates/site/style/booking.css b/vr_club_site/templates/site/style/booking.css
index 5502f96..a26e510 100644
--- a/vr_club_site/templates/site/style/booking.css
+++ b/vr_club_site/templates/site/style/booking.css
@@ -92,7 +92,7 @@
}
.people-count-2 {
- gap: 125px;
+ gap: 124px;
display: flex;
flex-direction: row;
align-items: center;
diff --git a/vr_club_site/urls.py b/vr_club_site/urls.py
index b965eea..4897a1f 100644
--- a/vr_club_site/urls.py
+++ b/vr_club_site/urls.py
@@ -1,13 +1,13 @@
from django.urls import path
-from .views import home, booking_view, get_available_slots_view
-from games.views import games
+from .views import index_page, get_available_slots_view
+from games.views import game_page
app_name = "site"
urlpatterns = [
- path("", booking_view, name="home"),
- path("games/", games, name="games"),
+ path("", index_page, name="home"),
+ path("games/", game_page, name="games"),
path(
"api/get_available_slots/",
get_available_slots_view,
diff --git a/vr_club_site/utils.py b/vr_club_site/utils.py
index 78e047a..ab111ca 100644
--- a/vr_club_site/utils.py
+++ b/vr_club_site/utils.py
@@ -1,10 +1,16 @@
import decimal
+from django.db import IntegrityError, transaction
from django.db.models import Sum
+from django.contrib import messages
from .models import Booking, BookingTime, ACTUAL, Settings
+COST_SESSION = 200
+MAX_SESSION = 2
+
+
def get_available_slots(selected_date=None):
_available_slots = []
@@ -34,3 +40,47 @@ def get_available_slots(selected_date=None):
slot = 0
_available_slots.append(session_seats - slot)
return tuple(_available_slots)
+
+
+def is_all_neighbors(slot_compatibility, slots):
+ slots_values = [abs(slot_compatibility[value]) for value in slots]
+ sorted_slots = sorted(slots_values)
+ for i in range(1, len(sorted_slots)):
+ if sorted_slots[i] - sorted_slots[i - 1] != 1:
+ return False
+
+ return True
+
+
+def has_invalid_slots(request, people_count, slot_av_slots, slots):
+ _has_invalid_slots = False
+ for slot in slots:
+ if slot_av_slots[slot] <= 0 or slot_av_slots[slot] < people_count:
+ messages.error(request, f"Місць для {slot} вже немає!")
+ _has_invalid_slots = True
+
+ return _has_invalid_slots
+
+
+def book_session(request, slots, data_to_save):
+ booking = Booking(
+ name=data_to_save["name"],
+ email=data_to_save["email"],
+ phone_number=data_to_save["phone_number"],
+ people_count=data_to_save["people_count"],
+ comment=data_to_save["comment"],
+ price=COST_SESSION * data_to_save["people_count"] * len(slots),
+ )
+ booking.save()
+ booking_times_to_create = [
+ BookingTime(time=slot, status=ACTUAL, date=data_to_save["date"])
+ for slot in slots
+ ]
+ try:
+ with transaction.atomic():
+ BookingTime.objects.bulk_create(booking_times_to_create)
+
+ for booking_time in booking_times_to_create:
+ booking.time.add(booking_time)
+ except IntegrityError as e:
+ messages.error(request, f"Помилка бронювання: {e}")
diff --git a/vr_club_site/views.py b/vr_club_site/views.py
index dfcdb6c..2377f6d 100644
--- a/vr_club_site/views.py
+++ b/vr_club_site/views.py
@@ -1,16 +1,17 @@
-from django.db import IntegrityError, transaction
from django.shortcuts import redirect, render
from django.contrib import messages
from django.urls import reverse
from django.http import JsonResponse
from .forms import BookingForm
-from .models import Booking, BookingTime, ACTUAL, OUTDATED
-from .utils import get_available_slots
-
-
-COST_SESSION = 200
-MAX_SESSION = 2
+from .models import BookingTime
+from .utils import (
+ get_available_slots,
+ is_all_neighbors,
+ has_invalid_slots,
+ book_session,
+ MAX_SESSION,
+)
def get_available_slots_view(request):
@@ -20,20 +21,12 @@ def get_available_slots_view(request):
return JsonResponse({"available_slots": available_slots})
-def home(request):
- signup_url = reverse("account_signup")
- login_url = reverse("account_login")
- return render(
- request, "site/index.html", {"signup_url": signup_url, "login_url": login_url}
- )
-
-
-def booking_view(request):
+def index_page(request):
if request.method == "POST":
form = BookingForm(request.POST)
if not form.is_valid():
messages.error(request, "Будь ласка, оберіть слот, перед відправкою!")
- return render_main_page(request)
+ return render_index_page(request)
selected_date = form.cleaned_data["date"]
_available_slots = get_available_slots(selected_date)
slot_av_slots = {
@@ -43,18 +36,19 @@ def booking_view(request):
y[0]: index for index, y in enumerate(BookingTime.TIME_CHOICES)
}
slots = form.cleaned_data["slots"]
+
if len(slots) > MAX_SESSION:
messages.error(
request, f"Максимальна кількість обраних сеансів: {MAX_SESSION}"
)
- return render_main_page(request)
+ return render_index_page(request)
if len(slots) > 1:
_is_all_neighbors = is_all_neighbors(slot_compatibility, slots)
if not _is_all_neighbors:
messages.error(request, "Сеанси мають бути суміжними")
- return render_main_page(request)
+ return render_index_page(request)
people_count = form.cleaned_data["people_count"]
_has_invalid_slots = has_invalid_slots(
@@ -65,65 +59,19 @@ def booking_view(request):
book_session(request, slots, form.cleaned_data)
return redirect("site:home")
- return render_main_page(request)
-
+ return render_index_page(request)
-def is_all_neighbors(slot_compatibility, slots):
- slots_values = [abs(slot_compatibility[value]) for value in slots]
- sorted_slots = sorted(slots_values)
- for i in range(1, len(sorted_slots)):
- if sorted_slots[i] - sorted_slots[i - 1] != 1:
- return False
- return True
-
-
-def has_invalid_slots(request, people_count, slot_av_slots, slots):
- _has_invalid_slots = False
- for slot in slots:
- if slot_av_slots[slot] <= 0 or slot_av_slots[slot] < people_count:
- messages.error(request, f"Місць для {slot} вже немає!")
- _has_invalid_slots = True
-
- return _has_invalid_slots
-
-
-def render_main_page(request):
+def render_index_page(request):
signup_url = reverse("account_signup")
login_url = reverse("account_login")
form = BookingForm()
- _available_slots = get_available_slots()
return render(
request,
"site/index.html",
{
"form": form,
- "available_slots": _available_slots,
"signup_url": signup_url,
"login_url": login_url,
},
)
-
-
-def book_session(request, slots, data_to_save):
- booking = Booking(
- name=data_to_save["name"],
- email=data_to_save["email"],
- phone_number=data_to_save["phone_number"],
- people_count=data_to_save["people_count"],
- comment=data_to_save["comment"],
- price=COST_SESSION * data_to_save["people_count"] * len(slots),
- )
- booking.save()
- booking_times_to_create = [
- BookingTime(time=slot, status=ACTUAL, date=data_to_save["date"])
- for slot in slots
- ]
- try:
- with transaction.atomic():
- BookingTime.objects.bulk_create(booking_times_to_create)
-
- for booking_time in booking_times_to_create:
- booking.time.add(booking_time)
- except IntegrityError as e:
- messages.error(request, f"Помилка бронювання: {e}")