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-card class="about">
<v-col <v-card-title class="about__title">Vítejte v tanečním klubu!</v-card-title>
cols="12" <v-card-subtitle class="about__subtitle">Vítejte na webu Tanečního klubu Ostrava!</v-card-subtitle>
md="4" <v-card-text class="about__text">
/> 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.
<v-col <br><br>
cols="12" Přidejte se k nám a nechte tanec proměnit váš život! 💃🕺
md="8" <br><br>
style="color: white; padding: 50px" 👉 Podívejte se na naše kurzy a přihlaste se ještě dnes!
> </v-card-text>
<v-card class="about"> </v-card>
<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-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,20 +1,29 @@
<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-col class="text-center" cols="12" md="3" v-for="lector in lectors" :key="lector.id"> >
<v-avatar <v-container id="trainers">
color="none" <v-card class="trainers">
rounded="1" <v-card-title class="about__title">Naši trenéři a lektoři</v-card-title>
size="150" <v-card-subtitle class="about__subtitle">Seznamte se s námi!</v-card-subtitle>
style="margin: 30px" <v-row>
> <v-col class="text-center" cols="12" md="3" v-for="lector in lectors" :key="lector.id">
<v-img :src="lector.img" cover></v-img> <v-avatar
</v-avatar> color="none"
<h2>{{lector.name}}</h2> rounded="1"
<h4>{{ lector.desc }}</h4> size="150"
</v-col> style="margin: 30px"
</v-row> >
<v-img :src="lector.img" cover></v-img>
</v-avatar>
<v-card-subtitle class="about__subtitle">{{lector.name}}</v-card-subtitle>
<v-card-text class="about__text">{{ lector.desc }}</v-card-text>
</v-col>
</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,10 +43,8 @@
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>
@ -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