some progress?

This commit is contained in:
KUB0570 2025-03-05 16:21:16 +01:00
parent 0aec187a0a
commit 10beabd148
34 changed files with 501 additions and 211 deletions

4
TODO
View file

@ -9,3 +9,7 @@ Naprogramovat:
Naplánovat: Naplánovat:
- kurzy + cena: vyskakovací okno na rezervaci s jménem lekce? jak to vyřešit s kalendářem? (stránka /rezervace) - kurzy + cena: vyskakovací okno na rezervaci s jménem lekce? jak to vyřešit s kalendářem? (stránka /rezervace)
DAVID:
- přidat posílání mailu panu vrchnímu

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

View file

@ -9,6 +9,8 @@ https://docs.djangoproject.com/en/5.1/topics/settings/
For the full list of settings and their values, see For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.1/ref/settings/ https://docs.djangoproject.com/en/5.1/ref/settings/
""" """
import os.path
import environ import environ
from pathlib import Path from pathlib import Path
@ -109,12 +111,6 @@ WSGI_APPLICATION = 'backend.wsgi.application'
# Database # Database
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases # https://docs.djangoproject.com/en/5.1/ref/settings/#databases
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': BASE_DIR / 'db.sqlite3',
# }
# }
DATABASES = { DATABASES = {
'default': env.db('DATABASE_URL') 'default': env.db('DATABASE_URL')
} }
@ -142,9 +138,9 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/5.1/topics/i18n/ # https://docs.djangoproject.com/en/5.1/topics/i18n/
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'cs-cz'
TIME_ZONE = 'UTC' TIME_ZONE = 'Europe/Prague'
USE_I18N = True USE_I18N = True
@ -156,8 +152,14 @@ USE_TZ = True
STATIC_URL = '/static/' STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static')
]
MEDIA_URL = '/media/' 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

View file

@ -18,11 +18,17 @@ from django.contrib import admin
from django.urls import path from django.urls import path
from debug_toolbar.toolbar import debug_toolbar_urls from debug_toolbar.toolbar import debug_toolbar_urls
from tko.views import ContactView, ArticleListView, EventListView from django.conf.urls.static import static
from django.conf import settings
from tko.views import ContactView, NewArticleListView, AllArticleListView, EventListView
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('create-contact/', ContactView.as_view(), name='create-contact'), path('create-contact/', ContactView.as_view(), name='create-contact'),
path('load-articles/', ArticleListView.as_view(), name='load-articles'), path('load-articles/', NewArticleListView.as_view(), name='load-articles'),
path('load-all-articles/', AllArticleListView.as_view(), name='load-all-articles'),
path('load-events/', EventListView.as_view(), name='load-events'), path('load-events/', EventListView.as_view(), name='load-events'),
] + debug_toolbar_urls() ] + debug_toolbar_urls()
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View file

@ -0,0 +1,18 @@
# Generated by Django 5.1.5 on 2025-02-21 16:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tko', '0003_rename_first_name_contact_name_and_more'),
]
operations = [
migrations.AlterField(
model_name='article',
name='image',
field=models.FileField(blank=True, default='default.png', upload_to=''),
),
]

View file

@ -14,8 +14,8 @@ class Contact(models.Model):
class Article(models.Model): class Article(models.Model):
title = models.CharField(max_length=100) title = models.CharField(max_length=100)
content = models.TextField() content = models.TextField()
image = models.FileField(upload_to='articles/%Y/%m/%d', null=True) image = models.FileField(default="default.png", blank=True)
date = models.DateField() date = models.DateField(auto_now_add=True)
author = models.CharField(max_length=100) author = models.CharField(max_length=100)
active_to = models.DateField(null=True, blank=True) # do not show some invitation after this date active_to = models.DateField(null=True, blank=True) # do not show some invitation after this date

View file

@ -1,4 +1,5 @@
from rest_framework import serializers from rest_framework import serializers
from django.utils.timezone import localtime
from tko.models import Contact, Article, Event from tko.models import Contact, Article, Event
@ -10,12 +11,29 @@ class ContactSerializer(serializers.ModelSerializer):
class ArticleListSerializer(serializers.ModelSerializer): class ArticleListSerializer(serializers.ModelSerializer):
date = serializers.SerializerMethodField()
class Meta: class Meta:
model = Article model = Article
fields = '__all__' fields = ["id", "author", "content", "date", "image", "title"]
@staticmethod
def get_date(obj):
return obj.date.strftime("%-d. %-m. %Y")
class EventListSerializer(serializers.ModelSerializer): class EventListSerializer(serializers.ModelSerializer):
start = serializers.SerializerMethodField()
end = serializers.SerializerMethodField()
class Meta: class Meta:
model = Event model = Event
fields = '__all__' fields = ["title", "end", "start", "color"]
@staticmethod
def get_start(obj):
return localtime(obj.start_date).isoformat() if obj.start_date else None
@staticmethod
def get_end(obj):
return localtime(obj.end_date).isoformat() if obj.end_date else None

View file

@ -1,3 +1,6 @@
from django.utils import timezone
from django.db.models import Q
from rest_framework.generics import ListAPIView, CreateAPIView from rest_framework.generics import ListAPIView, CreateAPIView
from rest_framework import permissions from rest_framework import permissions
@ -10,11 +13,25 @@ class ContactView(CreateAPIView):
permission_classes = [permissions.AllowAny] permission_classes = [permissions.AllowAny]
class ArticleListView(ListAPIView): class NewArticleListView(ListAPIView):
queryset = Article.objects.all()
serializer_class = ArticleListSerializer serializer_class = ArticleListSerializer
permission_classes = [permissions.AllowAny] permission_classes = [permissions.AllowAny]
def get_queryset(self):
return Article.objects.filter(
Q(active_to__gte=timezone.now()) | Q(active_to__isnull=True)
).order_by('-date')[:2]
class AllArticleListView(ListAPIView):
serializer_class = ArticleListSerializer
permission_classes = [permissions.AllowAny]
def get_queryset(self):
return Article.objects.filter(
Q(active_to__gte=timezone.now()) | Q(active_to__isnull=True)
).order_by('-date')
class EventListView(ListAPIView): class EventListView(ListAPIView):
queryset = Event.objects.all() queryset = Event.objects.all()

View file

@ -1,22 +1,22 @@
@font-face { @font-face {
font-family: 'Playfair Display', serif; font-family: 'Futura', sans-serif;
font-weight: 700; font-weight: 700;
font-style: normal; font-style: normal;
font-display: block; font-display: block;
} }
h1 { h1 {
font-family: 'Playfair Display', serif; /* Or a similar elegant font */ font-family: 'Futura', sans-serif;
font-size: 3rem; font-size: 3rem;
color: #333; /* Dark gray */ 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); /* Subtle shadow */ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
} }
h2 { h2 {
font-family: 'Playfair Display', serif; font-family: 'Futura', sans-serif;
font-size: 1.5rem; font-size: 1.5rem;
color: #CF3476; color: #CF3476;
text-align: center; text-align: center;
@ -24,25 +24,25 @@ h2 {
} }
h3 { h3 {
font-family: 'Playfair Display', serif; font-family: 'Futura', sans-serif;
font-size: 1.5rem; font-size: 1.5rem;
color: #666; /* Light gray */ color: #666;
text-align: center; text-align: center;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
h4 { h4 {
font-family: 'Playfair Display', serif; font-family: 'Futura', sans-serif;
font-size: 1rem; font-size: 1rem;
color: #666; /* Light gray */ color: #666;
text-align: center; text-align: center;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
h5 { h5 {
font-family: 'Playfair Display', serif; font-family: 'Futura', sans-serif;
font-size: 1rem; font-size: 1rem;
color: #aaa; /* Light gray */ color: #aaa;
margin-top: 1rem; margin-top: 1rem;
} }
@ -52,25 +52,30 @@ h5 {
margin-right: calc(20% - 2.5rem); margin-right: calc(20% - 2.5rem);
} }
.app__logo {
height: 3.5rem;
width: 8rem;
}
.app__title { .app__title {
font-family: 'Playfair Display', serif; font-family: 'Futura', sans-serif;
font-size: 2rem; font-size: 2rem;
color: #333; /* Light gray */ color: #333;
} }
.app__tab { .app__tab {
font-family: 'Playfair Display', serif; font-family: 'Futura', sans-serif;
font-size: 2rem; font-size: 2rem;
color: #CF3476; /* Light gray */ color: #CF3476;
} }
.to_left { .to_left {
font-family: 'Playfair Display', serif; font-family: 'Futura', sans-serif;
font-size: 1rem; font-size: 1rem;
} }
.to_right { .to_right {
font-family: 'Playfair Display', serif; font-family: 'Futura', sans-serif;
float: right; float: right;
} }
@ -84,9 +89,9 @@ h5 {
.contact__title { .contact__title {
text-align: center; text-align: center;
font-size: 3rem; font-size: 3rem;
color: #333; /* Dark gray */ color: #333;
margin-bottom: 1rem; margin-bottom: 1rem;
font-family: 'Playfair Display', serif; font-family: 'Futura', sans-serif;
font-weight: bold; font-weight: bold;
} }
@ -96,71 +101,114 @@ h5 {
background-color: #CF3476; background-color: #CF3476;
} }
.articles {
margin-left: calc(20% - 2.5rem);
margin-right: calc(20% - 2.5rem);
}
.article { .article {
padding: 20px; padding: 10px;
min-width: 40%; min-width: 40%;
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);
margin: 50px margin-bottom: 1rem;
}
.article__image {
border-radius: 0.5rem;
} }
.article__title { .article__title {
font-family: 'Playfair Display', serif; font-family: 'Futura', sans-serif;
font-size: 1.5rem; font-size: 1.5rem;
color: #CF3476; /* Dark gray */ color: #CF3476;
text-align: left; text-align: left;
margin-top: 0.1rem; margin-top: 0.1rem;
font-weight: bold; font-weight: bold;
} }
.article__date { .article__date {
font-family: 'Playfair Display', serif; font-family: 'Futura', sans-serif;
font-size: 0.8rem; font-size: 0.8rem;
text-align: left; text-align: left;
} }
.article__text { .article__text {
font-family: 'Playfair Display', serif; /* Or a similar elegant font */ font-family: 'Futura', sans-serif;
font-size: 1rem; font-size: 1rem;
text-align: left; text-align: left;
color: #666; /* Dark gray */ min-height: 6rem;
color: #666;
} }
.article__sign { .article__sign {
font-family: 'Playfair Display', serif; /* Or a similar elegant font */ font-family: 'Futura', sans-serif;
font-size: 1rem; font-size: 1rem;
float: right; float: right;
color: #333; /* Dark gray */ padding-right: 1rem;
color: #333;
}
.show_more {
font-family: 'Futura', sans-serif;
text-align: center;
color: #CF3476;
background: transparent;
box-shadow: none;
width: 300px;
margin: 0 auto;
}
.show_more:hover {
box-shadow: none;
}
.pricing {
margin-left: calc(20% - 2.5rem);
margin-right: calc(20% - 2.5rem);
} }
.pricing-box { .pricing-box {
padding: 20px; padding: 1rem;
text-align: center; text-align: center;
min-width: 25rem; min-width: 25rem;
border-radius: 1rem; border-radius: 1rem;
height: 230px; height: 230px;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.3);
margin: 50px
} }
.pricing-box:not(:last-child) { .pricing-box:hover {
margin: 20px; box-shadow: 0 5px 10px rgba(0, 0, 0, 0.8);
} }
.pricing__price { .pricing__price {
font-family: 'Playfair Display', serif; /* Or a similar elegant font */ font-family: 'Futura', sans-serif;
font-size: 3.5rem; font-size: 3.5rem;
color: #CF3476; /* Dark gray */ color: #CF3476;
text-align: center; text-align: center;
} }
.pricing__subtitle { .pricing__subtitle {
font-family: 'Playfair Display', serif; /* Or a similar elegant font */ font-family: 'Futura', sans-serif;
font-size: 1rem; font-size: 1rem;
color: #333; /* Dark gray */ color: #333;
text-align: center; text-align: center;
} }
.trainers__parallax {
max-height: 34rem;
margin-top: 2rem;
}
.trainers {
min-width: 25rem;
background: transparent;
box-shadow: none;
height: 100%;
margin-left: calc(20% - 2.5rem);
margin-right: calc(20% - 2.5rem);
}
.advantage { .advantage {
min-width: 25rem; min-width: 25rem;
border-radius: 1rem; border-radius: 1rem;
@ -172,7 +220,7 @@ h5 {
} }
.advantage__title { .advantage__title {
font-family: 'Playfair Display', serif; font-family: 'Futura', sans-serif;
color: #CF3476; color: #CF3476;
font-size: 1.5rem; font-size: 1.5rem;
font-weight: bold; font-weight: bold;
@ -180,34 +228,37 @@ h5 {
} }
.advantage__text { .advantage__text {
font-family: 'Playfair Display', serif; font-family: 'Futura', sans-serif;
font-size: 1rem; font-size: 1rem;
color: #333; color: #333;
} }
.about { .about {
min-width: 25rem; min-width: 25rem;
border-radius: 0;
background: transparent; background: transparent;
box-shadow: none;
height: 100%;
margin-left: calc(20% - 2.5rem);
margin-right: calc(20% - 2.5rem);
} }
.about__parallax { .about__parallax {
max-height: 36rem; max-height: 30rem;
margin-top: 2rem;
} }
.about__title { .about__title {
font-family: 'Playfair Display', serif; font-family: 'Futura', sans-serif;
font-size: 3rem; font-size: 3rem;
text-align: center; text-align: center;
color: #ddd; /* Dark gray */ color: #fff;
margin-bottom: 1rem;
margin-top: 1rem; margin-top: 1rem;
font-weight: bold; font-weight: bold;
} }
.about__subtitle { .about__subtitle {
font-family: 'Playfair Display', serif; font-family: 'Futura', sans-serif;
color: #CF3476; color: #FF3D8C;
font-size: 1.5rem; font-size: 1.5rem;
text-align: center; text-align: center;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
@ -215,8 +266,12 @@ h5 {
} }
.about__text { .about__text {
font-family: 'Playfair Display', serif; font-family: 'Futura', sans-serif;
color: #aaa; /* Dark gray */ color: #ddd;
font-size: 1.2rem; font-size: 1.2rem;
text-align: center; text-align: center;
} }
.footer {
background-color: black;
}

View file

@ -1,35 +1,21 @@
<template> <template>
<v-parallax <v-parallax
src="https://www.danceus.org/parse/files/Bjy5anNVI0Q81M8bmrwIiuU20x4kepQTxzDBfqpR/70d831b8f51edc1f6e1a4320d52f164b_latin-dance.jpg"
class="about__parallax" class="about__parallax"
src="public/dark-dance.jpg"
scale="0.8"
> >
<v-container fluid fill-height id="about"> <v-container id="about">
<v-row align="center" justify="center">
<v-col
cols="12"
md="4"
/>
<v-col
cols="12"
md="8"
style="color: white; padding: 50px"
>
<v-card class="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">Tanec je vášeň!</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">
Sportovní taneční klub jsme založili v roce 2016 s absolventy středoškolských tanečních kurzů taneční školy Bolero Ostrava. Objevte kouzlo tance s námi! 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.
Společně jsme se zaměřili na profesionální stránku tance, našli jsme pár aktivních tanečníků se kterými jsme začali pravidelně trénovat pohyb, krokové variace a techniku jednotlivých tanců.
Po roce tréninků se dostavily i první výsledky a dnes již tito mladí tanečníci skvěle reprezentují klub na celé řadě tanečních soutěží.
<br><br> <br><br>
O rok později v roce 2017 jsme založili také společenský taneční klub pro dospělé, kteří by se rádi také tanci věnovali. Přidejte se k nám a nechte tanec proměnit váš život! 💃🕺
Příjemná atmosféra, zábava, ale i chuť se učit novým věcem, to jsou tři slova, které by mohly charakterizovat atmosféru v tanečních lekcích. <br><br>
Taneční klub, sportovní tak i společenský vítá všechny milovníky tance, kteří chtějí poodhalit tajemství ladných tanečních pohybů, sdílet nezaměnitelnou atmosféru a dovědět se více o taneční technice. 👉 Podívejte se na naše kurzy a přihlaste se ještě dnes!
Našim přáním je, abyste na tanečním parketu jednoduše zářili.
</v-card-text> </v-card-text>
</v-card> </v-card>
</v-col>
</v-row>
</v-container> </v-container>
</v-parallax> </v-parallax>
</template> </template>

View file

@ -1,6 +1,6 @@
<template> <template>
<h1>Co dostanete s tanečním klubem</h1> <h1>Přínosy tance: Více než jen pohyb</h1>
<h2>Výhody tanečníka</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"

View file

@ -4,13 +4,14 @@
<v-sheet class="sheet__box"> <v-sheet class="sheet__box">
<v-calendar <v-calendar
v-model="value" :firstDayOfWeek="0"
first-day-of-week="1"
locale="cs-CZ" locale="cs-CZ"
ref="calendar" ref="calendar"
:showAdjacentMonths="true"
view-mode="week" view-mode="week"
:events="events" :events="events"
:weekdays="[1, 2, 3, 4, 5, 6, 0]" :interval-start="7"
:interval-height="25"
/> />
</v-sheet> </v-sheet>
</template> </template>
@ -19,16 +20,28 @@
import './assets/css/main.css' import './assets/css/main.css'
import {useAPI} from "~/composables/useAPI"; import {useAPI} from "~/composables/useAPI";
interface Event {
title: string;
end: Date;
start: Date;
color: string;
}
const { error, data } = await useAPI('load-events/', {method: "GET"}); const events = ref<Event[]>([]);
const value = [new Date()]; await loadEvents();
const events = [
{ async function loadEvents() {
title: "ahoj", const { error, data } = await useAPI<Event[]>('load-events/', { method: "GET" });
start: new Date(),
end: new Date(), if (data.value && Array.isArray(data.value)) {
color: 'red', events.value = data.value as Event[];
for(const i in data.value){
data.value[i].end = new Date((data.value[i].end));
data.value[i].start = new Date((data.value[i].start));
} }
]; } else if (error.value) {
console.error("Error loading events:", error.value);
}
}
</script> </script>

View file

@ -72,8 +72,8 @@ const email = ref<string>("");
const phone = ref<string>(""); const phone = ref<string>("");
const textField = ref<string>(""); const textField = ref<string>("");
function sendContact() { async function sendContact() {
useAPI('create-contact/', { const { data, error } = await useAPI('create-contact/', {
method: "POST", method: "POST",
body: { body: {
name: fullName.value, name: fullName.value,

View file

@ -1,30 +1,41 @@
<template> <template>
<h1 id="courses">Kurzy - cena</h1> <h1 id="courses">Kurzy</h1>
<h2>Vyberte, co Vám nejlépe vyhovuje</h2> <h2>Vyberte, co Vám nejlépe vyhovuje</h2>
<v-row> <v-row class="pricing">
<v-col <v-col
cols="12" cols="12"
md="4" md="4"
v-for="course in courses" v-for="(course, index) in courses"
:key="course.id" :key="index"
> >
<v-card class="pricing-box"> <v-card class="pricing-box">
<h3>{{ course.name }}</h3> <h3>{{ course.name }}</h3>
<v-card-subtitle><div class="pricing__subtitle">{{ course.time }}</div></v-card-subtitle> <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-title><div class="pricing__price">{{ course.price }}</div></v-card-title>
<v-card-subtitle v-if="course.desc"><div class="pricing__subtitle">{{ course.desc }}</div></v-card-subtitle> <v-card-text v-if="course.desc"><div class="pricing__subtitle">{{ course.desc }}</div></v-card-text>
<v-btn class="show_more">
Kontaktujte nás
</v-btn>
</v-card> </v-card>
</v-col> </v-col>
</v-row> </v-row>
<!-- <v-btn to="/kurzy" class="show_more">-->
<!-- <v-icon icon="mdi-chevron-down"/>Více informací<v-icon icon="mdi-chevron-down"/>-->
<!-- </v-btn>-->
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import './assets/css/main.css' import './assets/css/main.css'
// TODO: vyskakovací okno s kontaktním formulářem po rozkliku :)
const courses = [ const courses = [
{id: 1, name: "Sportovní taneční klub", time: "24 hodin/měsíc", price: "600 Kč", desc: false}, {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ě."},
{id: 2, name: "Individuální lekce", time: "60min / taneční pár", price: "500 Kč", desc: "Pro veřejnost"}, {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."},
{id: 3, name: "Volný trénink s lektorem", time: "60min / taneční pár", price: "100 Kč", desc: "Pro členy tanečního klubu"}, {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> </script>

View file

@ -0,0 +1,27 @@
<template>
<v-parallax
class="about__parallax"
src="public/img/black-pink.jpg"
>
<v-container id="about">
<v-card class="about">
<v-card-title class="about__title">Vítejte v tanečním klubu!</v-card-title>
<v-card-subtitle class="about__subtitle">Tanec je vášeň!</v-card-subtitle>
<v-card-text class="about__text">
Sportovní taneční klub jsme založili v roce 2016 s absolventy středoškolských tanečních kurzů taneční školy Bolero Ostrava.
Společně jsme se zaměřili na profesionální stránku tance, našli jsme pár aktivních tanečníků se kterými jsme začali pravidelně trénovat pohyb, krokové variace a techniku jednotlivých tanců.
Po roce tréninků se dostavily i první výsledky a dnes již tito mladí tanečníci skvěle reprezentují klub na celé řadě tanečních soutěží.
<br><br>
O rok později v roce 2017 jsme založili také společenský taneční klub pro dospělé, kteří by se rádi také tanci věnovali.
Příjemná atmosféra, zábava, ale i chuť se učit novým věcem, to jsou tři slova, které by mohly charakterizovat atmosféru v tanečních lekcích.
Taneční klub, sportovní tak i společenský vítá všechny milovníky tance, kteří chtějí poodhalit tajemství ladných tanečních pohybů, sdílet nezaměnitelnou atmosféru a dovědět se více o taneční technice.
Našim přáním je, abyste na tanečním parketu jednoduše zářili.
</v-card-text>
</v-card>
</v-container>
</v-parallax>
</template>
<script setup lang="ts">
import './assets/css/main.css'
</script>

View file

@ -1,10 +1,10 @@
<template> <template v-if="articles">
<h1 id="article">Aktuality</h1> <h1 id="article">Aktuality</h1>
<h2>Přečtěte si aktuality z našeho klubu</h2> <h2>Přečtěte si aktuality z našeho klubu</h2>
<v-row> <v-row class="articles">
<v-col <v-col
v-for="(article, index) in articles" v-for="article in articles"
:key="index" :key="article.id"
cols="12" cols="12"
md="6" md="6"
> >
@ -12,10 +12,11 @@
<v-row> <v-row>
<v-col> <v-col>
<v-img <v-img
:lazy-src="article.src" :lazy-src="article.image"
:src="article.src" :src="article.image"
aspect-ratio="1" aspect-ratio="1"
cover cover
class="article__image"
> >
<template v-slot:placeholder> <template v-slot:placeholder>
<v-row> <v-row>
@ -33,29 +34,41 @@
> >
<v-row class="article__title">{{ article.title }}</v-row> <v-row class="article__title">{{ article.title }}</v-row>
<v-row class="article__date">{{ article.date}}</v-row> <v-row class="article__date">{{ article.date}}</v-row>
<v-row class="article__text">{{ article.desc }}</v-row> <v-row class="article__text">{{ article.content }}</v-row>
<v-row class="article__sign">{{ article.autor }}</v-row> <v-row class="article__sign">{{ article.author }}</v-row>
</v-col> </v-col>
</v-row> </v-row>
</v-card> </v-card>
</v-col> </v-col>
</v-row> </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>
</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";
interface Article {
id: number;
title: string;
image: string;
date: string;
content: string;
author: string;
}
const articles = ref<Article[]>([]);
const { error, data } = await useAPI<Article[]>('load-articles/', {method: "GET"});
if ( data.value ){
articles.value = data.value;
}
else if (error.value) {
console.error("Error loading articles:", error.value);
}
const { error, data } = await useAPI('load-articles/', {method: "GET"});
console.log(error.value);
const articles = [
{
autor: "Bebloid Obecný",
date: "2. 2. 2025",
title: "David potrolil soutěž",
src: "/terka&david.jpg",
desc: "V neděli 2. 2. David potrolil sambu, kde místo tančení lítal jako zmatená včelka. Terka, ačkoliv před soutěží velice zmatkovala a sebepoškozovala se, tak na parketě předvedla výborné představení. Na další trolení se můžeme všichni těšit do Brna na konci tohoto měsíce. "
},
]
</script> </script>

View file

@ -1,7 +1,13 @@
<template> <template>
<h1 id="trainers">Naši trenéři a lektoři</h1> <v-parallax
<h2>Seznamte se s námi!</h2> class="trainers__parallax"
<v-row style="margin-bottom: 2rem;"> src="public/img/black-pink.jpg"
>
<v-container id="trainers">
<v-card class="trainers">
<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-row>
<v-col class="text-center" cols="12" md="3" v-for="lector in lectors" :key="lector.id"> <v-col class="text-center" cols="12" md="3" v-for="lector in lectors" :key="lector.id">
<v-avatar <v-avatar
color="none" color="none"
@ -11,10 +17,13 @@
> >
<v-img :src="lector.img" cover></v-img> <v-img :src="lector.img" cover></v-img>
</v-avatar> </v-avatar>
<h2>{{lector.name}}</h2> <v-card-subtitle class="about__subtitle">{{lector.name}}</v-card-subtitle>
<h4>{{ lector.desc }}</h4> <v-card-text class="about__text">{{ lector.desc }}</v-card-text>
</v-col> </v-col>
</v-row> </v-row>
</v-card>
</v-container>
</v-parallax>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import './assets/css/main.css' import './assets/css/main.css'
@ -23,25 +32,25 @@ const lectors = [
{ {
id: 1, id: 1,
name: "Ondřej Gilar", name: "Ondřej Gilar",
img: "/Beblik.jpg", img: "/trainers/img.png",
desc: "Trenér - latinskoamerické tance a Pro-AM", desc: "Trenér - latinskoamerické tance a Pro-AM",
}, },
{ {
id: 2, id: 2,
name: "Leona Hruštincová", name: "Leona Hruštincová",
img: "https://cdn.vuetifyjs.com/images/profiles/marcus.jpg", img: "/trainers/img.png",
desc: "Lektorka - tance pro děti", desc: "Lektorka - tance pro děti",
}, },
{ {
id: 1, id: 1,
name: "Ondřej Gilar", name: "Ondřej Gilar",
img: "https://cdn.vuetifyjs.com/images/profiles/marcus.jpg", img: "/trainers/img.png",
desc: "Trenér - latinskoamerické tance a Pro-AM", desc: "Trenér - latinskoamerické tance a Pro-AM",
}, },
{ {
id: 2, id: 2,
name: "Leona Hruštincová", name: "Leona Hruštincová",
img: "https://cdn.vuetifyjs.com/images/profiles/marcus.jpg", img: "/trainers/img.png",
desc: "Lektorka - tance pro děti", desc: "Lektorka - tance pro děti",
}, },
] ]

View file

@ -1,7 +1,5 @@
<template> <template>
<v-footer <v-footer class="footer">
style="background-color: black"
>
<v-container class="text-white"> <v-container class="text-white">
<v-row> <v-row>
<v-col <v-col
@ -47,7 +45,7 @@
<v-col <v-col
style="text-align: left" style="text-align: left"
> >
<h5>Taneční klub Ostrava © 2025</h5> <h5>Taneční klub Ostrava s.r.o. © {{date}}</h5>
</v-col> </v-col>
<v-col <v-col
style="text-align: right" style="text-align: right"
@ -61,4 +59,5 @@
<script setup lang="ts"> <script setup lang="ts">
import './assets/css/main.css' import './assets/css/main.css'
const date = new Date().getFullYear()
</script> </script>

View file

@ -6,7 +6,9 @@
@click="drawer = !drawer" @click="drawer = !drawer"
class="d-flex d-sm-none" class="d-flex d-sm-none"
/> />
<a href="/"><big-icon icon="mdi-dance-ballroom" /></a> <div>
<a href="/"><v-img class="app__logo" src="/logo.png" /></a>
</div>
<v-app-bar-title class="app__title">Taneční klub Ostrava</v-app-bar-title> <v-app-bar-title class="app__title">Taneční klub Ostrava</v-app-bar-title>
<v-tabs <v-tabs
@ -18,8 +20,6 @@
<v-tab v-if="tab.ref" :text="tab.name" :value="tab.name" @click="useGoTo(tab.ref)"></v-tab> <v-tab v-if="tab.ref" :text="tab.name" :value="tab.name" @click="useGoTo(tab.ref)"></v-tab>
<v-tab v-if="tab.href" :text="tab.name" :value="tab.name" :href="tab.href"></v-tab> <v-tab v-if="tab.href" :text="tab.name" :value="tab.name" :href="tab.href"></v-tab>
</v-tabs> </v-tabs>
<!-- <v-col--> <!-- <v-col-->
<!-- class="text-right"--> <!-- class="text-right"-->
<!-- @click="toggleTheme"--> <!-- @click="toggleTheme"-->
@ -43,11 +43,9 @@
nav nav
dense dense
> >
<v-list-item>
<v-list-item v-for="(tab, index) in tabs" :key="index"> <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-title @click="useGoTo(tab.ref)">{{ tab.name }}</v-list-item-title>
</v-list-item> </v-list-item>
</v-list-item>
</v-list> </v-list>
</v-navigation-drawer> </v-navigation-drawer>
</v-layout> </v-layout>
@ -55,46 +53,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
function waitForElement (selector: string, timeout = 2000) : Promise<Element> { import { useGoTo } from "~/composables/useGoTo";
return new Promise((resolve, reject) => {
const startTime = Date.now();
// eslint-disable-next-line prefer-const
let observer: MutationObserver;
function checkElement () {
const element = document.querySelector(selector);
if (element) {
resolve(element);
observer.disconnect(); // Stop observing DOM changes
} else if (Date.now() - startTime >= timeout) {
reject(new Error(`Timeout exceeded while waiting for element with selector '${selector}'`));
observer.disconnect(); // Stop observing DOM changes
}
}
observer = new MutationObserver(checkElement);
observer.observe(document.body, { childList: true, subtree: true });
checkElement(); // Check initially in case the element is already present
});
}
async function useGoTo (selector: string, props: {offset? :number} = {}): Promise<void> {
console.log(selector);
let element: Element;
try {
element = await waitForElement(selector);
} catch {
// element not found
return;
}
const yOffset = props?.offset ?? -80;
const y = element.getBoundingClientRect().top + window.pageYOffset + yOffset;
window.scrollTo({ top: y, behavior: "smooth" });
}
const currentTab = ref({name: 'O nás', ref: "#about", href: "o-nas"}); const currentTab = ref({name: 'O nás', ref: "#about", href: "o-nas"});
const drawer = ref(null) const drawer = ref(null)

View file

@ -0,0 +1,40 @@
function waitForElement (selector: string, timeout = 2000) : Promise<Element> {
return new Promise((resolve, reject) => {
const startTime = Date.now();
// eslint-disable-next-line prefer-const
let observer: MutationObserver;
function checkElement () {
const element = document.querySelector(selector);
if (element) {
resolve(element);
observer.disconnect(); // Stop observing DOM changes
} else if (Date.now() - startTime >= timeout) {
reject(new Error(`Timeout exceeded while waiting for element with selector '${selector}'`));
observer.disconnect(); // Stop observing DOM changes
}
}
observer = new MutationObserver(checkElement);
observer.observe(document.body, { childList: true, subtree: true });
checkElement(); // Check initially in case the element is already present
});
}
export async function useGoTo (selector: string, props: {offset? :number} = {}): Promise<void> {
console.log(selector);
let element: Element;
try {
element = await waitForElement(selector);
} catch {
// element not found
return;
}
const yOffset = props?.offset ?? -80;
const y = element.getBoundingClientRect().top + window.pageYOffset + yOffset;
window.scrollTo({ top: y, behavior: "smooth" });
}

View file

@ -0,0 +1,70 @@
<template v-if="articles">
<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>
</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>

View file

@ -1,6 +1,6 @@
<template> <template>
<v-parallax <v-parallax
src="https://danceostrava.cz/wp-content/uploads/2021/10/IMG_5203-scaled.jpg" src="public/dark-dance.jpg"
> >
<div class="d-flex flex-column fill-height justify-center"> <div class="d-flex flex-column fill-height justify-center">
<h1 class="text-white"> <h1 class="text-white">
@ -11,13 +11,12 @@
</h2> </h2>
</div> </div>
</v-parallax> </v-parallax>
<calendar/>
<news/> <news/>
<trainers/>
<about/> <about/>
<advantages/>
<courses/> <courses/>
<contact-us/> <trainers/>
<advantages/>
<history/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View file

@ -0,0 +1,37 @@
<template>
<h1 id="courses">Kurzy</h1>
<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>
<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>

View file

@ -58,6 +58,13 @@ export default defineNuxtPlugin((app) => {
}, },
locale: { locale: {
locale: 'cs', locale: 'cs',
messages: {
cs: {
calendar: {
today: "dnes",
}
}
}
}, },
date: { date: {
locale: { locale: {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB