generated from JustScreaMy/ProjectTemplate
Compare commits
No commits in common. "master" and "galery-to-article" have entirely different histories.
master
...
galery-to-
23 changed files with 399 additions and 466 deletions
13
TODO
Normal file
13
TODO
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
Nastylovat:
|
||||||
|
- lazyload načítat o něco dříve (neli zrušit alespoň mimo galerii)
|
||||||
|
- mobilní verze
|
||||||
|
|
||||||
|
Frontend:
|
||||||
|
- přidat v galerii zvětsovač na obrázky
|
||||||
|
- notyfi
|
||||||
|
|
||||||
|
Naplánovat:
|
||||||
|
- kurzy + cena: vyskakovací okno na rezervaci s jménem lekce? jak to vyřešit s kalendářem? (stránka /rezervace)
|
||||||
|
|
||||||
|
Backend:
|
||||||
|
- přidat posílání mailu panu vrchnímu
|
|
@ -20,9 +20,6 @@ env = environ.Env()
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
MEDIA_URL = '/media/'
|
|
||||||
|
|
||||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
|
||||||
|
@ -165,6 +162,9 @@ STATIC_URL = '/static/'
|
||||||
# os.path.join(BASE_DIR, 'static')
|
# os.path.join(BASE_DIR, 'static')
|
||||||
# ]
|
# ]
|
||||||
|
|
||||||
|
MEDIA_URL = '/media/'
|
||||||
|
|
||||||
|
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
Binary file not shown.
After Width: | Height: | Size: 234 KiB |
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 126 KiB |
|
@ -19,7 +19,7 @@ class ArticleImageSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_title(obj):
|
def get_title(obj):
|
||||||
return obj.article.title if obj.article else ''
|
return obj.article.title
|
||||||
|
|
||||||
|
|
||||||
class ArticleListSerializer(serializers.ModelSerializer):
|
class ArticleListSerializer(serializers.ModelSerializer):
|
||||||
|
@ -35,15 +35,10 @@ class ArticleListSerializer(serializers.ModelSerializer):
|
||||||
def get_date(obj):
|
def get_date(obj):
|
||||||
return obj.date.strftime("%-d. %-m. %Y")
|
return obj.date.strftime("%-d. %-m. %Y")
|
||||||
|
|
||||||
def get_image(self, obj):
|
@staticmethod
|
||||||
main_image = next(iter(obj.images.all()), None)
|
def get_image(obj):
|
||||||
|
main_image = obj.images.order_by("main").first()
|
||||||
if main_image:
|
return main_image.image.url if main_image else None
|
||||||
return ArticleImageSerializer(main_image, context=self.context).data
|
|
||||||
return {
|
|
||||||
"image": "http://localhost:8000/media/images/image.jpg",
|
|
||||||
"title": "Výchozí obrázek",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class EventListSerializer(serializers.ModelSerializer):
|
class EventListSerializer(serializers.ModelSerializer):
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.db.models import Q, Prefetch
|
from django.db.models import Q
|
||||||
|
|
||||||
from post_office import mail
|
from post_office import mail
|
||||||
|
|
||||||
from rest_framework.generics import ListAPIView, CreateAPIView
|
from rest_framework.generics import ListAPIView, CreateAPIView
|
||||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from tko.models import Article, Event, ArticleImage
|
from tko.models import Article, Event, ArticleImage
|
||||||
|
@ -46,13 +45,10 @@ class AllArticleListView(ListAPIView):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Article.objects.filter(
|
return Article.objects.filter(
|
||||||
Q(active_to__gte=timezone.now()) | Q(active_to__isnull=True)
|
Q(active_to__gte=timezone.now()) | Q(active_to__isnull=True)
|
||||||
).order_by('-date').prefetch_related(
|
).order_by('-date')
|
||||||
Prefetch('images', queryset=ArticleImage.objects.order_by('-main'))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class GalleryView(ListAPIView):
|
class GalleryView(ListAPIView):
|
||||||
permission=[AllowAny]
|
|
||||||
queryset = ArticleImage.objects.all().order_by("-article_id", "main")
|
queryset = ArticleImage.objects.all().order_by("-article_id", "main")
|
||||||
serializer_class = ArticleImageSerializer
|
serializer_class = ArticleImageSerializer
|
||||||
|
|
||||||
|
|
|
@ -6,50 +6,49 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
color: #333;
|
color: #333;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
|
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
color: #CF3476;
|
color: #CF3476;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
word-break: break-word;
|
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
color: #666;
|
color: #666;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #666;
|
color: #666;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h5 {
|
h5 {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 1.8rem; /* Smaller size for mobile */
|
font-size: 2rem; /* Smaller size for mobile */
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +61,6 @@ h5 {
|
||||||
min-width: 25rem;
|
min-width: 25rem;
|
||||||
margin-left: calc(20% - 2.5rem);
|
margin-left: calc(20% - 2.5rem);
|
||||||
margin-right: calc(20% - 2.5rem);
|
margin-right: calc(20% - 2.5rem);
|
||||||
margin-bottom: 3rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.app__logo {
|
.app__logo {
|
||||||
|
@ -71,26 +69,30 @@ h5 {
|
||||||
}
|
}
|
||||||
|
|
||||||
.app__title {
|
.app__title {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
color: #333;
|
color: #333;
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.app__tab {
|
.app__tab {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
color: #CF3476;
|
color: #CF3476;
|
||||||
}
|
}
|
||||||
|
|
||||||
.to_left {
|
.to_left {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.to_right {
|
.to_right {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact {
|
.contact {
|
||||||
margin: 20px calc(20% - 40px) 50px;
|
margin: 20px calc(20% - 40px) 50px;
|
||||||
|
min-width: 25rem;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
@ -100,17 +102,16 @@ h5 {
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact__dialog__title {
|
.contact__dialog__title {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
word-break: break-word !important;
|
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact__button {
|
.contact__button {
|
||||||
|
@ -120,15 +121,6 @@ h5 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.contact__title {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact {
|
|
||||||
margin: 10px;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.v-dialog > .v-card {
|
.v-dialog > .v-card {
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
@ -138,62 +130,65 @@ h5 {
|
||||||
.contact__dialog__title {
|
.contact__dialog__title {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
word-break: break-word;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.articles {
|
.articles {
|
||||||
display: flex;
|
margin-left: calc(20% - 2.5rem);
|
||||||
justify-content: center;
|
margin-right: calc(20% - 2.5rem);
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 1200px;
|
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.article {
|
.article {
|
||||||
padding: 1rem;
|
padding: 10px;
|
||||||
min-width: 40%;
|
min-width: 40%;
|
||||||
border-radius: 0;
|
border-radius: 15px;
|
||||||
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
|
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3);
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
cursor: default;
|
}
|
||||||
|
|
||||||
|
.article__image {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel__image img {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
object-fit: contain !important;
|
||||||
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.article__title {
|
.article__title {
|
||||||
font-size: 1.4rem;
|
font-family: 'Futura', sans-serif;
|
||||||
|
font-size: 1.5rem;
|
||||||
color: #CF3476;
|
color: #CF3476;
|
||||||
|
text-align: left;
|
||||||
|
margin-top: 0.1rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.article__date {
|
.article__date {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.article__text {
|
.article__text {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #333;
|
text-align: left;
|
||||||
line-height: 1.5;
|
min-height: 6rem;
|
||||||
padding: 0.5rem;
|
color: #666;
|
||||||
margin-bottom: 1rem;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article__image:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.article__sign {
|
.article__sign {
|
||||||
text-align: right;
|
font-family: 'Futura', sans-serif;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-style: italic;
|
float: right;
|
||||||
color: #555;
|
padding-right: 1rem;
|
||||||
cursor: default;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.show_more {
|
.show_more {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #CF3476;
|
color: #CF3476;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
@ -241,7 +236,6 @@ h5 {
|
||||||
|
|
||||||
.pricing-title {
|
.pricing-title {
|
||||||
min-height: 3rem;
|
min-height: 3rem;
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.pricing-desc {
|
.pricing-desc {
|
||||||
|
@ -249,6 +243,7 @@ h5 {
|
||||||
}
|
}
|
||||||
|
|
||||||
.pricing__price {
|
.pricing__price {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #CF3476;
|
color: #CF3476;
|
||||||
|
@ -256,9 +251,9 @@ h5 {
|
||||||
}
|
}
|
||||||
|
|
||||||
.pricing__subtitle {
|
.pricing__subtitle {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #555;
|
color: #555;
|
||||||
word-break: break-word;
|
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,12 +262,9 @@ h5 {
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pricing-title {
|
.pricing-title,
|
||||||
font-size: 1.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pricing-desc {
|
.pricing-desc {
|
||||||
min-height: unset;
|
min-height: unset; /* Removes forced height to prevent breakage */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,7 +286,6 @@ h5 {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.trainer-avatar {
|
.trainer-avatar {
|
||||||
|
@ -307,6 +298,7 @@ h5 {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile Styles */
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.trainers__parallax {
|
.trainers__parallax {
|
||||||
max-height: 30rem;
|
max-height: 30rem;
|
||||||
|
@ -331,23 +323,22 @@ h5 {
|
||||||
margin-top: 1.2rem;
|
margin-top: 1.2rem;
|
||||||
margin-left: calc(20% - 2.5rem);
|
margin-left: calc(20% - 2.5rem);
|
||||||
margin-right: calc(20% - 2.5rem);
|
margin-right: calc(20% - 2.5rem);
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.advantage__title {
|
.advantage__title {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
padding: 0.5rem 0;
|
padding: 0.5rem 0;
|
||||||
color: #CF3476;
|
color: #CF3476;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-top: 0.1rem;
|
margin-top: 0.1rem;
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.advantage__text {
|
.advantage__text {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
padding-bottom: 1rem;
|
padding-bottom: 1rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #333;
|
color: #333;
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
|
@ -371,16 +362,15 @@ h5 {
|
||||||
.about {
|
.about {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
height: auto;
|
height: 100%;
|
||||||
max-width: 80%;
|
max-width: 80%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.about__parallax {
|
.about__parallax {
|
||||||
max-height: 28rem;
|
max-height: 30rem;
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -391,80 +381,60 @@ h5 {
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.history__parallax {
|
.history__parallax {
|
||||||
max-height: 58rem;
|
max-height: 45rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.about__title {
|
.about__title {
|
||||||
font-size: 2.5rem;
|
font-family: 'Futura', sans-serif;
|
||||||
|
font-size: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
word-break: break-word;
|
|
||||||
white-space: normal;
|
|
||||||
display: block;
|
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.about__subtitle {
|
.about__subtitle {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
color: #FF3D8C;
|
color: #FF3D8C;
|
||||||
font-size: 1.25rem;
|
font-size: 1.3rem;
|
||||||
margin-bottom: 1rem;
|
text-align: center;
|
||||||
word-break: break-word;
|
width: 100%;
|
||||||
font-weight: bold;
|
|
||||||
display: block;
|
display: block;
|
||||||
cursor: default;
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.about__text {
|
.about__text {
|
||||||
|
font-family: 'Futura', sans-serif;
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
font-size: 1.125rem;
|
font-size: 1rem;
|
||||||
|
text-align: center;
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
line-height: 1.6;
|
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.about {
|
.about {
|
||||||
max-width: 95%;
|
max-width: 100%;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.about__parallax {
|
|
||||||
min-height: 32rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.about__title {
|
.about__title {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.about__subtitle {
|
.about__subtitle {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
word-break: break-word;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.about__text {
|
.about__text {
|
||||||
font-size: 0.95rem;
|
font-size: 1rem;
|
||||||
|
padding: 0 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.masonry-gallery {
|
|
||||||
columns: 3;
|
|
||||||
column-gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.masonry-gallery {
|
|
||||||
columns: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.masonry-item {
|
|
||||||
break-inside: avoid;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
cursor: default;
|
|
||||||
}
|
}
|
|
@ -4,12 +4,12 @@
|
||||||
src="public/dark-dance.jpg"
|
src="public/dark-dance.jpg"
|
||||||
scale="0.8"
|
scale="0.8"
|
||||||
>
|
>
|
||||||
<v-container>
|
<v-container id="about">
|
||||||
<v-card class="about" id="about">
|
<v-card class="about">
|
||||||
<v-card-title class="about__title">Vítejte v tanečním klubu!</v-card-title>
|
<v-card-title class="about__title">Vítejte v tanečním klubu!</v-card-title>
|
||||||
<v-card-subtitle class="about__subtitle">Objevte kouzlo tance s námi!</v-card-subtitle>
|
<v-card-subtitle class="about__subtitle">Vítejte na webu Tanečního klubu Ostrava!</v-card-subtitle>
|
||||||
<v-card-text class="about__text">
|
<v-card-text class="about__text">
|
||||||
Ať už jste začátečník nebo zkušený tanečník, v našem klubu najdete místo, kde se můžete rozvíjet, bavit a sdílet svou vášeň pro pohyb. Nabízíme kurzy pro všechny věkové kategorie, od společenských tanců po moderní styly.
|
Objevte kouzlo tance s námi! Ať už jste začátečník nebo zkušený tanečník, v našem klubu najdete místo, kde se můžete rozvíjet, bavit a sdílet svou vášeň pro pohyb. Nabízíme kurzy pro všechny věkové kategorie, od společenských tanců po moderní styly.
|
||||||
<br><br>
|
<br><br>
|
||||||
Přidejte se k nám a nechte tanec proměnit váš život! 💃🕺
|
Přidejte se k nám a nechte tanec proměnit váš život! 💃🕺
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<h1>Přínosy tance</h1>
|
<h1>Přínosy tance: Více než jen pohyb</h1>
|
||||||
<h2>Více než jen pohyb</h2>
|
<h2>Tancem k lepšímu životu</h2>
|
||||||
<v-card
|
<v-card
|
||||||
v-for="advantage in advantages"
|
v-for="advantage in advantages"
|
||||||
:key="advantage.id"
|
:key="advantage.id"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<v-card class="contact pa-4">
|
<v-card class="contact">
|
||||||
<v-card-title class="contact__title">Kontaktujte nás!</v-card-title>
|
<v-card-title class="contact__title">Kontaktujte nás!</v-card-title>
|
||||||
<lazy-dialog-contact-form />
|
<dialog-contact-form />
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
|
@ -14,10 +14,10 @@
|
||||||
<v-card class="pricing-box" @click="openDialog = true; chosenCourse = course">
|
<v-card class="pricing-box" @click="openDialog = true; chosenCourse = course">
|
||||||
<div class="pricing-content">
|
<div class="pricing-content">
|
||||||
<h3 class="pricing-title">{{ course.name }}</h3>
|
<h3 class="pricing-title">{{ course.name }}</h3>
|
||||||
<v-card-subtitle v-if="course.time">
|
<v-card-subtitle>
|
||||||
<div class="pricing__subtitle">{{ course.time }}</div>
|
<div class="pricing__subtitle">{{ course.time }}</div>
|
||||||
</v-card-subtitle>
|
</v-card-subtitle>
|
||||||
<v-card-title v-if="course.price">
|
<v-card-title>
|
||||||
<div class="pricing__price">{{ course.price }}</div>
|
<div class="pricing__price">{{ course.price }}</div>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-card-text v-if="course.desc">
|
<v-card-text v-if="course.desc">
|
||||||
|
@ -38,10 +38,11 @@
|
||||||
transition="dialog-bottom-transition"
|
transition="dialog-bottom-transition"
|
||||||
>
|
>
|
||||||
<v-card class="contact pa-4">
|
<v-card class="contact pa-4">
|
||||||
<h1 class="contact__dialog__title d-flex align-center">
|
<v-card-title class="contact__dialog__title d-flex align-center">
|
||||||
<span class="flex-grow-1 text-center" style="margin-left: 40px;">{{ chosenCourse.name }}</span>
|
<span class="flex-grow-1 text-center" style="margin-left: 40px;">{{ chosenCourse.name }}</span>
|
||||||
<v-btn icon="mdi-close" variant="text" @click="openDialog = false"></v-btn>
|
<v-btn icon="mdi-close" variant="text" @click="openDialog = false"></v-btn>
|
||||||
</h1>
|
</v-card-title>
|
||||||
|
|
||||||
<v-card-text v-if="chosenCourse.desc" class="text-center">
|
<v-card-text v-if="chosenCourse.desc" class="text-center">
|
||||||
<div class="pricing__subtitle text-body-1">{{ chosenCourse.desc }}</div>
|
<div class="pricing__subtitle text-body-1">{{ chosenCourse.desc }}</div>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
|
@ -9,66 +9,62 @@
|
||||||
md="6"
|
md="6"
|
||||||
>
|
>
|
||||||
<v-card class="article">
|
<v-card class="article">
|
||||||
<v-row class="article__title">{{ article.title }}</v-row>
|
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>
|
<v-col>
|
||||||
<v-img
|
<v-img
|
||||||
:src="article.image.image"
|
:lazy-src="article.image"
|
||||||
:lazy-src="article.image.image"
|
:src="article.image"
|
||||||
class="article__image rounded-lg"
|
aspect-ratio="1"
|
||||||
@click="openCarousel(article.image, article.images)"
|
cover
|
||||||
|
class="article__image"
|
||||||
|
@click="showCarousel = true"
|
||||||
>
|
>
|
||||||
<template v-slot:placeholder>
|
<template v-slot:placeholder>
|
||||||
<v-row justify="center" align="center" class="fill-height">
|
<v-row justify="center" align="center" class="fill-height">
|
||||||
<v-progress-circular color="grey-lighten-5" indeterminate />
|
<v-progress-circular color="grey-lighten-5" indeterminate></v-progress-circular>
|
||||||
</v-row>
|
</v-row>
|
||||||
</template>
|
</template>
|
||||||
</v-img>
|
<v-dialog v-model="showCarousel">
|
||||||
</v-col>
|
|
||||||
<v-col cols="12" md="8">
|
|
||||||
<v-row class="article__text">{{ article.content }}</v-row>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
<v-row class="article__sign">Dne {{ article.date }}, {{ article.author }}</v-row>
|
|
||||||
</v-card>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-dialog v-model="showCarousel" max-width="90vw" v-if="selectedImages.length > 0">
|
|
||||||
<dialog-carousel
|
<dialog-carousel
|
||||||
v-if="selectedImage"
|
:image="article.image"
|
||||||
:image="selectedImage"
|
:images="article.images"
|
||||||
:images="selectedImages"
|
:title="article.title"
|
||||||
@isActive="showCarousel = false"
|
@isActive="showCarousel = false"
|
||||||
/>
|
/>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
</v-img>
|
||||||
<v-btn to="/aktuality" class="show_more" v-if="!props.forAll">
|
</v-col>
|
||||||
<v-icon icon="mdi-chevron-down" />Další aktuality<v-icon icon="mdi-chevron-down" />
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
md="8"
|
||||||
|
>
|
||||||
|
<v-row class="article__title">{{ article.title }}</v-row>
|
||||||
|
<v-row class="article__date">{{ article.date}}</v-row>
|
||||||
|
<v-row class="article__text">{{ article.content }}</v-row>
|
||||||
|
<v-row class="article__sign">{{ article.author }}</v-row>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-btn to="/aktuality" class="show_more">
|
||||||
|
<v-icon icon="mdi-chevron-down"/>Dalsí aktuality<v-icon icon="mdi-chevron-down"/>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import './assets/css/main.css'
|
import './assets/css/main.css'
|
||||||
import { useAPI } from "~/composables/useAPI";
|
import { useAPI } from "~/composables/useAPI";
|
||||||
|
|
||||||
const showCarousel = ref(false);
|
const showCarousel = ref(false);
|
||||||
const selectedImage = ref<ArticleImage | null>(null);
|
|
||||||
const selectedImages = ref<ArticleImage[]>([]);
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
forAll: boolean
|
|
||||||
}>();
|
|
||||||
|
|
||||||
interface ArticleImage {
|
interface ArticleImage {
|
||||||
image: string;
|
image: string;
|
||||||
title: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Article {
|
interface Article {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
image: ArticleImage;
|
image: string;
|
||||||
images: ArticleImage[];
|
images: ArticleImage[];
|
||||||
date: string;
|
date: string;
|
||||||
content: string;
|
content: string;
|
||||||
|
@ -77,18 +73,13 @@ interface Article {
|
||||||
|
|
||||||
const articles = ref<Article[]>([]);
|
const articles = ref<Article[]>([]);
|
||||||
|
|
||||||
function openCarousel(image: ArticleImage, images: ArticleImage[]) {
|
const { error, data } = await useAPI<Article[]>('load-articles/', {method: "GET"});
|
||||||
selectedImage.value = image;
|
|
||||||
selectedImages.value = images;
|
|
||||||
showCarousel.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const endpoint = props.forAll ? 'load-all-articles/' : 'load-articles/';
|
|
||||||
const { error, data } = await useAPI<Article[]>(endpoint, { method: "GET" });
|
|
||||||
|
|
||||||
if ( data.value ){
|
if ( data.value ){
|
||||||
articles.value = data.value;
|
articles.value = data.value;
|
||||||
} else if (error.value) {
|
}
|
||||||
|
else if (error.value) {
|
||||||
console.error("Error loading articles:", error.value);
|
console.error("Error loading articles:", error.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
|
@ -1,11 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<v-parallax class="trainers__parallax" src="public/img/black-pink.jpg">
|
<v-parallax class="trainers__parallax" src="public/img/black-pink.jpg">
|
||||||
<v-container>
|
<v-container id="trainers">
|
||||||
<v-card class="about" id="trainers">
|
<v-card class="trainers pa-4">
|
||||||
<v-card-title class="about__title ">Naši trenéři a lektoři</v-card-title>
|
<v-card-title class="about__title ">Naši trenéři a lektoři</v-card-title>
|
||||||
<v-card-subtitle class="about__subtitle ">Seznamte se s námi!</v-card-subtitle>
|
<v-card-subtitle class="about__subtitle ">Seznamte se s námi!</v-card-subtitle>
|
||||||
|
|
||||||
<v-carousel style="height: auto" cycle interval="4000" hide-delimiters :show-arrows="trainerGroups.length > 1 ? 'hover' : false">
|
<v-carousel cycle interval="4000" hide-delimiters show-arrows="hover">
|
||||||
<v-carousel-item v-for="(group, index) in trainerGroups" :key="index">
|
<v-carousel-item v-for="(group, index) in trainerGroups" :key="index">
|
||||||
<v-row justify="center">
|
<v-row justify="center">
|
||||||
<v-col
|
<v-col
|
||||||
|
@ -25,8 +25,8 @@
|
||||||
>
|
>
|
||||||
<v-img :src="lector.img" cover></v-img>
|
<v-img :src="lector.img" cover></v-img>
|
||||||
</v-avatar>
|
</v-avatar>
|
||||||
<div class="about__subtitle">{{ lector.name }}</div>
|
<v-card-subtitle class="about__subtitle">{{ lector.name }}</v-card-subtitle>
|
||||||
<div class="about__text">{{ lector.desc }}</div>
|
<v-card-text class="about__text">{{ lector.desc }}</v-card-text>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-carousel-item>
|
</v-carousel-item>
|
||||||
|
@ -64,11 +64,6 @@ const lectors = [
|
||||||
img: "/trainers/img.png",
|
img: "/trainers/img.png",
|
||||||
desc: "Lektorka - tance pro děti",
|
desc: "Lektorka - tance pro děti",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Ondřej Gilar",
|
|
||||||
img: "/trainers/img.png",
|
|
||||||
desc: "Trenér - latinskoamerické tance a Pro-AM",
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const trainerGroups = computed(() => {
|
const trainerGroups = computed(() => {
|
||||||
|
|
|
@ -1,46 +1,35 @@
|
||||||
<template>
|
<template>
|
||||||
<v-card class="article__image">
|
<v-card class="article__image">
|
||||||
<v-card-title>
|
<v-card-title>{{ title }}
|
||||||
{{ images[model] && images[model].title }}
|
|
||||||
<v-icon class="to_right" @click="$emit('isActive', false)">mdi-close</v-icon>
|
<v-icon class="to_right" @click="$emit('isActive', false)">mdi-close</v-icon>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
|
|
||||||
<v-carousel
|
<v-carousel
|
||||||
v-model="model"
|
|
||||||
:hide-delimiters="true"
|
:hide-delimiters="true"
|
||||||
height="90vh"
|
height="90vh"
|
||||||
>
|
>
|
||||||
<v-carousel-item
|
<v-carousel-item
|
||||||
v-for="(item, i) in props.images"
|
v-for="(item, i) in images"
|
||||||
:key="i"
|
:key="i"
|
||||||
:src="item.image"
|
:src="item.image"
|
||||||
cover
|
cover
|
||||||
class="carousel__image"
|
class="carousel__image"
|
||||||
/>
|
></v-carousel-item>
|
||||||
</v-carousel>
|
</v-carousel>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue';
|
|
||||||
import './assets/css/main.css';
|
import './assets/css/main.css';
|
||||||
|
|
||||||
interface ArticleImage {
|
interface ArticleImage {
|
||||||
image: string;
|
image: string;
|
||||||
title: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
defineProps<{
|
||||||
image: ArticleImage;
|
image: string;
|
||||||
images: ArticleImage[];
|
images: ArticleImage[];
|
||||||
|
title: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
defineEmits(["isActive"]);
|
defineEmits(["isActive"])
|
||||||
|
|
||||||
const model = ref(0);
|
|
||||||
|
|
||||||
watch(() => props.image, (newImage) => {
|
|
||||||
const index = props.images.findIndex((img) => img.image === newImage.image);
|
|
||||||
if (index !== -1) model.value = index;
|
|
||||||
}, { immediate: true });
|
|
||||||
</script>
|
</script>
|
|
@ -1,12 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<v-form v-model="valid">
|
<v-form v-model="valid">
|
||||||
<v-snackbar
|
|
||||||
v-model="showSnackbar"
|
|
||||||
:color="snackbarColor"
|
|
||||||
:timeout="5000"
|
|
||||||
>
|
|
||||||
{{ snackbarMessage }}
|
|
||||||
</v-snackbar>
|
|
||||||
<v-container>
|
<v-container>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col
|
<v-col
|
||||||
|
@ -14,11 +7,7 @@
|
||||||
md="4"
|
md="4"
|
||||||
>
|
>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
dense
|
|
||||||
hide-details="auto"
|
|
||||||
v-model="fullName"
|
v-model="fullName"
|
||||||
:error-messages="get(errorMessages, 'name')"
|
|
||||||
@change="hideError('name')"
|
|
||||||
label="Jméno"
|
label="Jméno"
|
||||||
variant="underlined"
|
variant="underlined"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
|
@ -29,13 +18,7 @@
|
||||||
md="4"
|
md="4"
|
||||||
>
|
>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
dense
|
|
||||||
hide-details="auto"
|
|
||||||
type="email"
|
|
||||||
autocomplete="email"
|
|
||||||
v-model="email"
|
v-model="email"
|
||||||
:error-messages="get(errorMessages, 'email')"
|
|
||||||
@change="hideError('email')"
|
|
||||||
label="Emailová adresa"
|
label="Emailová adresa"
|
||||||
variant="underlined"
|
variant="underlined"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
|
@ -46,12 +29,7 @@
|
||||||
md="4"
|
md="4"
|
||||||
>
|
>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
dense
|
|
||||||
hide-details="auto"
|
|
||||||
type="tel"
|
|
||||||
v-model="phone"
|
v-model="phone"
|
||||||
:error-messages="get(errorMessages, 'phone_number')"
|
|
||||||
@change="hideError('phone_number')"
|
|
||||||
label="Telefonní číslo"
|
label="Telefonní číslo"
|
||||||
variant="underlined"
|
variant="underlined"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
|
@ -61,8 +39,6 @@
|
||||||
<v-col>
|
<v-col>
|
||||||
<v-textarea
|
<v-textarea
|
||||||
v-model="textField"
|
v-model="textField"
|
||||||
:error-messages="get(errorMessages, 'content')"
|
|
||||||
@change="hideError('content')"
|
|
||||||
label="Váš dotaz"
|
label="Váš dotaz"
|
||||||
variant="underlined"
|
variant="underlined"
|
||||||
/>
|
/>
|
||||||
|
@ -71,8 +47,6 @@
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>
|
<v-col>
|
||||||
<v-btn
|
<v-btn
|
||||||
auto-grow
|
|
||||||
rows="3"
|
|
||||||
class="contact__button"
|
class="contact__button"
|
||||||
type="submit"
|
type="submit"
|
||||||
@click.prevent="sendContact"
|
@click.prevent="sendContact"
|
||||||
|
@ -89,34 +63,14 @@ import './assets/css/main.css'
|
||||||
import {useAPI} from "~/composables/useAPI";
|
import {useAPI} from "~/composables/useAPI";
|
||||||
|
|
||||||
const valid = ref<boolean>(false);
|
const valid = ref<boolean>(false);
|
||||||
const errorMessages = ref<Record<string, string[]>>({});
|
|
||||||
|
|
||||||
const fullName = ref<string>("");
|
const fullName = ref<string>("");
|
||||||
const email = ref<string>("");
|
const email = ref<string>("");
|
||||||
const phone = ref<string>("");
|
const phone = ref<string>("");
|
||||||
const textField = ref<string>("");
|
const textField = ref<string>("");
|
||||||
|
|
||||||
const showSnackbar = ref(false);
|
|
||||||
const snackbarMessage = ref("");
|
|
||||||
const snackbarColor = ref("red-accent-4")
|
|
||||||
|
|
||||||
function get(obj: any, path: string, defaultValue = undefined) {
|
|
||||||
return path.split('.').reduce((acc, part) => acc && acc[part], obj) ?? defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetContractData() {
|
|
||||||
fullName.value = "";
|
|
||||||
email.value = "";
|
|
||||||
phone.value = "";
|
|
||||||
textField.value = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideError (key: string) {
|
|
||||||
delete errorMessages.value[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendContact() {
|
async function sendContact() {
|
||||||
const { error } = await useAPI('create-contact/', {
|
const { data, error } = await useAPI('create-contact/', {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: {
|
body: {
|
||||||
name: fullName.value,
|
name: fullName.value,
|
||||||
|
@ -125,18 +79,6 @@ async function sendContact() {
|
||||||
content: textField.value,
|
content: textField.value,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error.value) {
|
|
||||||
snackbarMessage.value = "Něco se pokazilo. Zkuste to znovu.";
|
|
||||||
snackbarColor.value = "red-accent-4";
|
|
||||||
errorMessages.value = error.value.data;
|
|
||||||
} else {
|
|
||||||
snackbarMessage.value = "Děkujeme! Vaše zpráva byla odeslána.";
|
|
||||||
snackbarColor.value = "success";
|
|
||||||
resetContractData();
|
|
||||||
}
|
|
||||||
|
|
||||||
showSnackbar.value = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
|
@ -1,142 +1,103 @@
|
||||||
<template>
|
<template>
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<v-layout style="margin-bottom: 3rem">
|
<v-layout>
|
||||||
<v-app-bar style="position: fixed">
|
<v-app-bar style="position: fixed">
|
||||||
|
<v-app-bar-nav-icon
|
||||||
|
@click="drawer = !drawer"
|
||||||
|
class="d-flex d-sm-none"
|
||||||
|
/>
|
||||||
<div>
|
<div>
|
||||||
<a href="/"><v-img class="app__logo" src="/logo.png" /></a>
|
<a href="/"><v-img class="app__logo" src="/logo.png" /></a>
|
||||||
</div>
|
</div>
|
||||||
<v-app-bar-title class="app__title" v-if="$vuetify.display.smAndUp">
|
<v-app-bar-title class="app__title">Taneční klub Ostrava</v-app-bar-title>
|
||||||
Taneční klub Ostrava
|
|
||||||
</v-app-bar-title>
|
|
||||||
|
|
||||||
<v-tabs
|
<v-tabs v-model="currentTab" class="d-none d-sm-flex app__tab">
|
||||||
v-if="$vuetify.display.smAndUp"
|
<div
|
||||||
v-model="currentTabName"
|
|
||||||
class="app__tab"
|
|
||||||
>
|
|
||||||
<v-tab
|
|
||||||
v-for="(tab, index) in tabs"
|
v-for="(tab, index) in tabs"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
>
|
||||||
|
<v-tab
|
||||||
|
v-if="tab.ref"
|
||||||
|
:text="tab.name"
|
||||||
:value="tab.name"
|
:value="tab.name"
|
||||||
@click="handleNavigation(tab)"
|
@click="useGoTo(tab.ref)"
|
||||||
>
|
>
|
||||||
{{ tab.name }}
|
{{ tab.name }}
|
||||||
</v-tab>
|
</v-tab>
|
||||||
|
<v-tab
|
||||||
|
v-if="tab.href"
|
||||||
|
:text="tab.name"
|
||||||
|
:value="tab.name"
|
||||||
|
:href="tab.href"
|
||||||
|
>
|
||||||
|
{{ tab.name }}
|
||||||
|
</v-tab>
|
||||||
|
</div>
|
||||||
</v-tabs>
|
</v-tabs>
|
||||||
|
|
||||||
<v-menu transition="slide-y-transition">
|
|
||||||
<template v-slot:activator="{ props }">
|
|
||||||
<v-app-bar-nav-icon
|
|
||||||
class="d-flex d-sm-none app__tab ml-auto"
|
|
||||||
style="padding-right: 1rem"
|
|
||||||
v-bind="props"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<v-list>
|
<!-- <v-col-->
|
||||||
<v-list-item
|
<!-- class="text-right"-->
|
||||||
v-for="(item, i) in homeTabs"
|
<!-- @click="toggleTheme"-->
|
||||||
:key="i"
|
<!-- >-->
|
||||||
:value="i"
|
<!-- <big-icon icon="mdi-lightbulb-cfl"/>-->
|
||||||
@click="handleNavigation(item)"
|
<!-- </v-col>-->
|
||||||
|
</v-app-bar>
|
||||||
|
|
||||||
|
<v-fab
|
||||||
|
class="ms-4 mb-4"
|
||||||
|
></v-fab>
|
||||||
|
|
||||||
|
<v-navigation-drawer
|
||||||
|
v-model="drawer"
|
||||||
|
absolute
|
||||||
|
fixed
|
||||||
|
left
|
||||||
>
|
>
|
||||||
<v-list-item-title>{{ item.name }}</v-list-item-title>
|
<v-list nav dense>
|
||||||
|
<v-list-item v-for="(tab, index) in tabs" :key="index">
|
||||||
|
<v-list-item-title @click="useGoTo(tab.ref)">{{ tab.name }}</v-list-item-title>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-menu>
|
</v-navigation-drawer>
|
||||||
</v-app-bar>
|
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, watch, nextTick } from 'vue';
|
import { useGoTo } from "~/composables/useGoTo";
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import {useRoute} from "#vue-router";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
|
||||||
|
const currentTab = ref({ name: 'O nás', ref: "#about", href: "o-nas" });
|
||||||
|
const drawer = ref(null)
|
||||||
|
|
||||||
const homeTabs = [
|
const homeTabs = [
|
||||||
{ name: "O nás", href: "#about" },
|
{ name: "O nás", ref: "#about", href: "" },
|
||||||
{ name: "Trenéři", href: "#trainers" },
|
{ name: "Trenéři", ref: "#trainers", href: "" },
|
||||||
{ name: "Kurzy", href: "#courses" },
|
{ name: "Kurzy", ref: "#courses", href: "" },
|
||||||
{ name: "Galerie", href: "/galerie" },
|
{ name: "Galerie", ref: "", href: "/galerie" },
|
||||||
{ name: "Aktuality", href: "#article" },
|
{ name: "Aktuality", ref: "#article", href: "" },
|
||||||
{ name: "Kontakty", href: "/kontakty" },
|
{ name: "Kontakty", ref: "", href: "/kontakty" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const otherTabs = [
|
const otherTabs = [
|
||||||
{ name: "O nás", href: "/#about" },
|
{ name: "O nás", ref: "", href: "/" },
|
||||||
{ name: "Trenéři", href: "/#trainers" },
|
{ name: "Trenéři", ref: "", href: "/" },
|
||||||
{ name: "Kurzy", href: "/#courses" },
|
{ name: "Kurzy", ref: "", href: "/" },
|
||||||
{ name: "Galerie", href: "/galerie" },
|
{ name: "Galerie", ref: "", href: "/galerie" },
|
||||||
{ name: "Aktuality", href: "/aktuality" },
|
{ name: "Aktuality", ref: "", href: "/" },
|
||||||
{ name: "Kontakty", href: "/kontakty" },
|
{ name: "Kontakty", ref: "", href: "/kontakty" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const tabs = computed(() => (route.path === "/" ? homeTabs : otherTabs));
|
const tabs = computed(() => (route.path === "/" ? homeTabs : otherTabs));
|
||||||
|
// import { useTheme } from 'vuetify'
|
||||||
|
//
|
||||||
|
// const theme = useTheme()
|
||||||
|
//
|
||||||
|
// function toggleTheme () {
|
||||||
|
// theme.global.name.value = theme.global.current.value.dark ? 'light' : 'dark'
|
||||||
|
// }
|
||||||
|
|
||||||
type Tab = {
|
|
||||||
name: string;
|
|
||||||
href: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const currentTab = ref<Tab>({ name: "O nás", href: "#about" });
|
|
||||||
|
|
||||||
const currentTabName = computed<string>({
|
|
||||||
get: () => currentTab.value.name,
|
|
||||||
set: (newName: string) => {
|
|
||||||
const tab = tabs.value.find(t => t.name === newName);
|
|
||||||
if (tab) handleNavigation(tab);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleNavigation = async (tab: { name: string, href: string }) => {
|
|
||||||
currentTab.value = tab;
|
|
||||||
|
|
||||||
if (tab.href.startsWith("#")) {
|
|
||||||
await scrollToHash(tab.href);
|
|
||||||
} else if (tab.href.startsWith("/#")) {
|
|
||||||
await router.push(tab.href);
|
|
||||||
} else {
|
|
||||||
await router.push(tab.href);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
async function scrollToHash(hash: string) {
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
const el = document.querySelector(hash);
|
|
||||||
if (el) {
|
|
||||||
const yOffset = -80;
|
|
||||||
const y = el.getBoundingClientRect().top + window.pageYOffset + yOffset;
|
|
||||||
window.scrollTo({ top: y, behavior: 'smooth' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (route.path === "/" && route.hash) {
|
|
||||||
const match = homeTabs.find(tab => tab.href === route.hash);
|
|
||||||
if (match) {
|
|
||||||
currentTab.value = match;
|
|
||||||
}
|
|
||||||
scrollToHash(route.hash);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(() => route.hash, (newHash) => {
|
|
||||||
if (route.path === "/" && newHash) {
|
|
||||||
const match = homeTabs.find(tab => tab.href === newHash);
|
|
||||||
if (match) currentTab.value = match;
|
|
||||||
scrollToHash(newHash);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(() => route.path, (newPath) => {
|
|
||||||
if (newPath !== "/") {
|
|
||||||
const match = otherTabs.find(tab => tab.href === newPath || tab.href === `${newPath}${route.hash}`);
|
|
||||||
if (match) currentTab.value = match;
|
|
||||||
}
|
|
||||||
}, { immediate: true });
|
|
||||||
|
|
||||||
</script>
|
</script>
|
|
@ -7,35 +7,30 @@ export async function useGoTo(
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const yOffset = props?.offset ?? -150;
|
const yOffset = props?.offset ?? -80;
|
||||||
|
|
||||||
const hash = selector.startsWith("#") ? selector : "";
|
const hash = selector.startsWith("#") ? selector : "";
|
||||||
const basePath = route.path.split("#")[0];
|
const path = selector.startsWith("/") ? selector : route.path + hash;
|
||||||
const targetPath = selector.startsWith("/") ? selector : basePath + hash;
|
|
||||||
const [pathOnly, hashOnly] = targetPath.split("#");
|
|
||||||
|
|
||||||
if (route.path !== pathOnly) {
|
// If navigating to another page
|
||||||
// Navigate to target page
|
if (route.path !== path.split("#")[0]) {
|
||||||
await router.push(pathOnly + (hashOnly ? `#${hashOnly}` : ""));
|
await router.push(path);
|
||||||
await nextTick();
|
await nextTick(); // Ensure DOM updates
|
||||||
|
|
||||||
if (hashOnly) {
|
// Wait for the element to exist before scrolling
|
||||||
try {
|
try {
|
||||||
const element = await waitForElement(`#${hashOnly}`);
|
const element = await waitForElement(hash);
|
||||||
scrollToElement(element, yOffset);
|
scrollToElement(element, yOffset);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.warn(err);
|
console.warn(error);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Same page — scroll directly
|
// If already on the correct page, scroll immediately
|
||||||
if (hashOnly || selector.startsWith("#")) {
|
|
||||||
try {
|
try {
|
||||||
const element = await waitForElement(`#${hashOnly ?? selector}`);
|
const element = await waitForElement(hash);
|
||||||
scrollToElement(element, yOffset);
|
scrollToElement(element, yOffset);
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.warn(err);
|
console.warn(error);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,70 @@
|
||||||
<template>
|
<template v-if="articles">
|
||||||
<lazy-news :for-all="true"/>
|
<h1 id="article">Aktuality</h1>
|
||||||
|
<h2>Přečtěte si aktuality z našeho klubu</h2>
|
||||||
|
<v-row>
|
||||||
|
<v-col
|
||||||
|
v-for="article in articles"
|
||||||
|
:key="article.id"
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<v-card class="article">
|
||||||
|
<v-row>
|
||||||
|
<v-col>
|
||||||
|
<v-img
|
||||||
|
:lazy-src="article.image"
|
||||||
|
:src="article.image"
|
||||||
|
aspect-ratio="1"
|
||||||
|
cover
|
||||||
|
class="article__image"
|
||||||
|
>
|
||||||
|
<template v-slot:placeholder>
|
||||||
|
<v-row>
|
||||||
|
<v-progress-circular
|
||||||
|
color="grey-lighten-5"
|
||||||
|
indeterminate
|
||||||
|
></v-progress-circular>
|
||||||
|
</v-row>
|
||||||
</template>
|
</template>
|
||||||
|
</v-img>
|
||||||
|
</v-col>
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
md="8"
|
||||||
|
>
|
||||||
|
<v-row class="article__title">{{ article.title }}</v-row>
|
||||||
|
<v-row class="article__date">{{ article.date}}</v-row>
|
||||||
|
<v-row class="article__text">{{ article.content }}</v-row>
|
||||||
|
<v-row class="article__sign">{{ article.author }}</v-row>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import './assets/css/main.css'
|
||||||
|
|
||||||
|
import {useAPI} from "~/composables/useAPI";
|
||||||
|
|
||||||
|
interface Article {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
image: string;
|
||||||
|
date: string;
|
||||||
|
content: string;
|
||||||
|
author: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const articles = ref<Article[]>([]);
|
||||||
|
|
||||||
|
const { error, data } = await useAPI('load-all-articles/', {method: "GET"});
|
||||||
|
|
||||||
|
if ( data.value ){
|
||||||
|
articles.value = data.value as Article[];
|
||||||
|
}
|
||||||
|
else if (error.value) {
|
||||||
|
console.error("Error loading articles:", error.value);
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,47 +1,44 @@
|
||||||
<template>
|
<template>
|
||||||
<h1>Galerie</h1>
|
<h1>Galerie</h1>
|
||||||
<div class="masonry-gallery">
|
<v-row>
|
||||||
<div
|
<v-col
|
||||||
class="masonry-item"
|
|
||||||
v-for="(photo, index) in gallery"
|
v-for="(photo, index) in gallery"
|
||||||
:key="index"
|
:key="index"
|
||||||
@click="openCarousel(photo)"
|
cols="4"
|
||||||
|
style="padding: 0;"
|
||||||
>
|
>
|
||||||
<v-img
|
<v-img
|
||||||
:src="photo.image"
|
|
||||||
:lazy-src="photo.image"
|
:lazy-src="photo.image"
|
||||||
aspect-ratio=""
|
:src="photo.image"
|
||||||
class="article__image rounded-lg"
|
aspect-ratio="1"
|
||||||
cover
|
cover
|
||||||
|
@click="showCarousel = true"
|
||||||
>
|
>
|
||||||
<template #placeholder>
|
<template v-slot:placeholder>
|
||||||
<v-row justify="center" align="center" style="height: 100px;">
|
<v-row>
|
||||||
<v-progress-circular indeterminate color="grey-lighten-1" />
|
<v-progress-circular
|
||||||
|
color="grey-lighten-5"
|
||||||
|
indeterminate
|
||||||
|
></v-progress-circular>
|
||||||
</v-row>
|
</v-row>
|
||||||
</template>
|
</template>
|
||||||
</v-img>
|
|
||||||
</div>
|
|
||||||
<v-dialog v-model="showCarousel">
|
<v-dialog v-model="showCarousel">
|
||||||
<dialog-carousel
|
<dialog-carousel
|
||||||
v-if="selectedImage"
|
:image="photo.image"
|
||||||
:image="selectedImage"
|
|
||||||
:images="gallery"
|
:images="gallery"
|
||||||
|
:title="photo.title"
|
||||||
@isActive="showCarousel = false"
|
@isActive="showCarousel = false"
|
||||||
/>
|
/>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
</div>
|
</v-img>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import './assets/css/main.css'
|
import './assets/css/main.css'
|
||||||
import { useAPI } from "~/composables/useAPI";
|
import { useAPI } from "~/composables/useAPI";
|
||||||
|
|
||||||
const showCarousel = ref(false);
|
const showCarousel = ref(false);
|
||||||
const selectedImage = ref<ArticleImage | null>(null);
|
|
||||||
|
|
||||||
function openCarousel(photo: ArticleImage) {
|
|
||||||
selectedImage.value = photo;
|
|
||||||
showCarousel.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ArticleImage {
|
interface ArticleImage {
|
||||||
image: string;
|
image: string;
|
||||||
|
@ -50,9 +47,6 @@ interface ArticleImage {
|
||||||
|
|
||||||
const gallery = ref<ArticleImage[]>([]);
|
const gallery = ref<ArticleImage[]>([]);
|
||||||
|
|
||||||
loadImages();
|
|
||||||
|
|
||||||
async function loadImages(){
|
|
||||||
const { error, data } = await useAPI<ArticleImage[]>('load-gallery/', {method: "GET"});
|
const { error, data } = await useAPI<ArticleImage[]>('load-gallery/', {method: "GET"});
|
||||||
|
|
||||||
if ( data.value ){
|
if ( data.value ){
|
||||||
|
@ -61,9 +55,4 @@ async function loadImages(){
|
||||||
else if (error.value) {
|
else if (error.value) {
|
||||||
console.error("Error loading gallery:", error.value);
|
console.error("Error loading gallery:", error.value);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
|
@ -11,12 +11,12 @@
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</v-parallax>
|
</v-parallax>
|
||||||
<lazy-news :for-all="false"/>
|
<news/>
|
||||||
<lazy-about/>
|
<about/>
|
||||||
<lazy-courses/>
|
<courses/>
|
||||||
<lazy-trainers/>
|
<trainers/>
|
||||||
<lazy-advantages/>
|
<advantages/>
|
||||||
<lazy-history/>
|
<history/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
|
@ -1,4 +1,37 @@
|
||||||
<template>
|
<template>
|
||||||
<lazy-courses />
|
<h1 id="courses">Kurzy</h1>
|
||||||
<lazy-calendar/>
|
<h2>Vyberte, co Vám nejlépe vyhovuje</h2>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
md="4"
|
||||||
|
v-for="(course, index) in courses"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<v-card class="pricing-box">
|
||||||
|
<h3>{{ course.name }}</h3>
|
||||||
|
<v-card-subtitle><div class="pricing__subtitle">{{ course.time }}</div></v-card-subtitle>
|
||||||
|
<v-card-title><div class="pricing__price">{{ course.price }}</div></v-card-title>
|
||||||
|
<v-card-text v-if="course.desc"><div class="pricing__subtitle">{{ course.desc }}</div></v-card-text>
|
||||||
|
<v-btn class="show_more"><v-icon icon="mdi-chevron-down"/>Kontaktujte nás<v-icon icon="mdi-chevron-down"/></v-btn>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<calendar/>
|
||||||
</template>
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import './assets/css/main.css'
|
||||||
|
|
||||||
|
// TODO: vyskakovací okno s kontaktním formulářem po rozkliku :)
|
||||||
|
|
||||||
|
const courses = [
|
||||||
|
{name: "Sportovní taneční klub", time: "", price: "", desc: "Připojte se k našemu tanečnímu klubu a rozvíjejte své taneční dovednosti v přátelském prostředí. Nabízíme různé styly tance pro všechny úrovně."},
|
||||||
|
{name: "Svatební tance", time: "", price: "", desc: "Udělejte svůj první tanec nezapomenutelným. Pomůžeme Vám vytvořit choreografii na míru, která bude odrážet Váš jedinečný styl."},
|
||||||
|
{name: "Příprava na plesovou sezónu", time: "", price: "", desc: "Chcete zazářit na plese? Připravíme Vás na plesovou sezónu a naučíme Vás elegantní taneční kroky a etiketu."},
|
||||||
|
{name: "Individuální taneční kurzy", time: "", price: "", desc: "Učte se tančit vlastním tempem s individuálním přístupem. Naši zkušení lektoři se zaměří na Vaše potřeby a pomohou Vám dosáhnout Vašich tanečních cílů."},
|
||||||
|
{name: "Individuální lekce", time: "", price: "", desc: "Zlepšete své taneční dovednosti s intenzivními individuálními lekcemi. Zaměřte se na konkrétní taneční techniky nebo styly, které Vás zajímají."},
|
||||||
|
{name: "Pronájem sálu", time: "", price: "", desc: "Hledáte ideální prostor pro tanec nebo jinou aktivitu? Náš taneční sál je k dispozici k pronájmu pro Vaše akce."},
|
||||||
|
]
|
||||||
|
|
||||||
|
</script>
|
|
@ -62,10 +62,6 @@ export default defineNuxtPlugin((app) => {
|
||||||
cs: {
|
cs: {
|
||||||
calendar: {
|
calendar: {
|
||||||
today: "dnes",
|
today: "dnes",
|
||||||
},
|
|
||||||
carousel: {
|
|
||||||
next: "další",
|
|
||||||
prev: "předchozí",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue