working with UI library now
This commit is contained in:
@@ -30,7 +30,8 @@ edit-booking = Edit Booking
|
||||
date = Date
|
||||
hour = Hour
|
||||
booking-color = Colour
|
||||
booking-name = Name
|
||||
booking-name = Name (private)
|
||||
booking-title = Title (public)
|
||||
booking-contact = Contact
|
||||
booking-note = Note
|
||||
save = Save
|
||||
@@ -48,3 +49,6 @@ court-delete-error = Court name did not match — nothing was removed.
|
||||
theme-system = System
|
||||
theme-light = Light
|
||||
theme-dark = Dark
|
||||
settings = Settings
|
||||
settings-language = Language
|
||||
settings-theme = Theme
|
||||
|
||||
@@ -30,7 +30,8 @@ edit-booking = Upraviť rezerváciu
|
||||
date = Dátum
|
||||
hour = Hodina
|
||||
booking-color = Farba
|
||||
booking-name = Meno
|
||||
booking-name = Meno (súkromné)
|
||||
booking-title = Názov (verejný)
|
||||
booking-contact = Kontakt
|
||||
booking-note = Poznámka
|
||||
save = Uložiť
|
||||
@@ -48,3 +49,6 @@ court-delete-error = Názov kurtu sa nezhodoval — nič sa neodstránilo.
|
||||
theme-system = Systém
|
||||
theme-light = Svetlý
|
||||
theme-dark = Tmavý
|
||||
settings = Nastavenia
|
||||
settings-language = Jazyk
|
||||
settings-theme = Téma
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
<label class="label"><span class="label-text">{{ t(key="booking-name", lang=lang) }}</span></label>
|
||||
<input name="name" value="{{ name }}" required class="input input-bordered w-full">
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">{{ t(key="booking-title", lang=lang) }}</span></label>
|
||||
<input name="title" value="{{ title }}" class="input input-bordered w-full">
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">{{ t(key="booking-contact", lang=lang) }}</span></label>
|
||||
<input name="contact" value="{{ contact }}" class="input input-bordered w-full">
|
||||
|
||||
@@ -12,8 +12,11 @@
|
||||
document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light');
|
||||
}
|
||||
function highlightTheme(t) {
|
||||
document.querySelectorAll('#theme-switch [data-theme-opt]').forEach(function (b) {
|
||||
b.classList.toggle('btn-neutral', b.getAttribute('data-theme-opt') === t);
|
||||
document.querySelectorAll('[data-theme-opt]').forEach(function (b) {
|
||||
var on = b.getAttribute('data-theme-opt') === t;
|
||||
b.classList.toggle('active', on);
|
||||
var chk = b.querySelector('.opt-check');
|
||||
if (chk) chk.classList.toggle('hidden', !on);
|
||||
});
|
||||
}
|
||||
function setTheme(t) {
|
||||
@@ -54,19 +57,96 @@
|
||||
{% else %}
|
||||
<a href="/admin/login" class="btn btn-ghost btn-sm">{{ t(key="nav-admin", lang=lang) }}</a>
|
||||
{% endif %}
|
||||
<div class="join ml-1">
|
||||
<button onclick="setLang('en')"
|
||||
class="btn btn-xs join-item {% if lang == 'en' %}btn-neutral{% endif %}">EN</button>
|
||||
<button onclick="setLang('sk')"
|
||||
class="btn btn-xs join-item {% if lang == 'sk' %}btn-neutral{% endif %}">SK</button>
|
||||
|
||||
<div class="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-ghost btn-sm btn-circle"
|
||||
aria-label="{{ t(key='settings', lang=lang) }}" title="{{ t(key='settings', lang=lang) }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="h-5 w-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="join" id="theme-switch">
|
||||
<button type="button" data-theme-opt="system" onclick="setTheme('system')"
|
||||
class="btn btn-xs join-item">{{ t(key="theme-system", lang=lang) }}</button>
|
||||
<button type="button" data-theme-opt="light" onclick="setTheme('light')"
|
||||
class="btn btn-xs join-item">{{ t(key="theme-light", lang=lang) }}</button>
|
||||
<button type="button" data-theme-opt="dark" onclick="setTheme('dark')"
|
||||
class="btn btn-xs join-item">{{ t(key="theme-dark", lang=lang) }}</button>
|
||||
<ul tabindex="0"
|
||||
class="menu dropdown-content z-50 mt-3 w-56 rounded-box border border-base-300 bg-base-100 p-2 shadow-lg">
|
||||
<li class="menu-title">{{ t(key="settings-language", lang=lang) }}</li>
|
||||
<li>
|
||||
<button type="button" onclick="setLang('en')" class="{% if lang == 'en' %}active{% endif %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="h-4 w-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247" />
|
||||
</svg>
|
||||
<span>English</span>
|
||||
{% if lang == 'en' %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="3"
|
||||
stroke="currentColor" class="ml-auto h-4 w-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
|
||||
</svg>
|
||||
{% endif %}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" onclick="setLang('sk')" class="{% if lang == 'sk' %}active{% endif %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="h-4 w-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247" />
|
||||
</svg>
|
||||
<span>Slovenčina</span>
|
||||
{% if lang == 'sk' %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="3"
|
||||
stroke="currentColor" class="ml-auto h-4 w-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
|
||||
</svg>
|
||||
{% endif %}
|
||||
</button>
|
||||
</li>
|
||||
<li class="menu-title">{{ t(key="settings-theme", lang=lang) }}</li>
|
||||
<li>
|
||||
<button type="button" data-theme-opt="system" onclick="setTheme('system')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="h-4 w-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M9 17.25v1.007a3 3 0 0 1-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0 1 15 18.257V17.25m6-12V15a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 15V5.25m18 0A2.25 2.25 0 0 0 18.75 3H5.25A2.25 2.25 0 0 0 3 5.25m18 0V12a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 12V5.25" />
|
||||
</svg>
|
||||
<span>{{ t(key="theme-system", lang=lang) }}</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="3"
|
||||
stroke="currentColor" class="opt-check ml-auto hidden h-4 w-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" data-theme-opt="light" onclick="setTheme('light')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="h-4 w-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" />
|
||||
</svg>
|
||||
<span>{{ t(key="theme-light", lang=lang) }}</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="3"
|
||||
stroke="currentColor" class="opt-check ml-auto hidden h-4 w-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" data-theme-opt="dark" onclick="setTheme('dark')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="h-4 w-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
|
||||
</svg>
|
||||
<span>{{ t(key="theme-dark", lang=lang) }}</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="3"
|
||||
stroke="currentColor" class="opt-check ml-auto hidden h-4 w-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -51,22 +51,28 @@
|
||||
<tr>
|
||||
<td class="border border-base-300 bg-base-200 p-2 text-center font-medium opacity-70">{{ row.hour_label }}</td>
|
||||
{% for cell in row.cells %}
|
||||
<td class="h-14 border border-base-300 p-1 align-top">
|
||||
<td class="relative h-14 border border-base-300">
|
||||
{% if cell.booked %}
|
||||
{% if is_admin %}
|
||||
<a href="/admin/booking/{{ cell.booking_id }}"
|
||||
class="block h-full rounded px-1 py-1 text-xs font-medium text-white shadow-sm transition hover:opacity-90"
|
||||
style="background-color: {{ cell.color }}">{{ cell.name }}</a>
|
||||
class="absolute inset-1 flex items-center overflow-hidden rounded px-1.5 text-xs font-medium text-white shadow-sm transition hover:opacity-90"
|
||||
style="background-color: {{ cell.color }}">
|
||||
<span class="min-w-0 truncate">{{ cell.name }}</span>
|
||||
</a>
|
||||
{% else %}
|
||||
<div class="h-full rounded px-1 py-1 text-xs font-medium text-white shadow-sm"
|
||||
style="background-color: {{ cell.color }}" title="{{ cell.name }}">{{ t(key="booked", lang=lang) }}</div>
|
||||
<div class="absolute inset-1 flex items-center overflow-hidden rounded px-1.5 text-xs font-medium text-white shadow-sm"
|
||||
style="background-color: {{ cell.color }}"{% if cell.title %} title="{{ cell.title }}"{% endif %}>
|
||||
<span class="min-w-0 truncate">
|
||||
{%- if cell.title %}{{ cell.title }}{% else %}{{ t(key="booked", lang=lang) }}{% endif -%}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if is_admin %}
|
||||
<a href="/admin/booking?court={{ court_id }}&date={{ cell.date }}&hour={{ cell.hour }}"
|
||||
class="flex h-full items-center justify-center rounded text-lg opacity-30 transition hover:bg-base-200 hover:opacity-100">+</a>
|
||||
class="absolute inset-0 flex items-center justify-center text-lg opacity-30 transition hover:bg-base-200 hover:opacity-100">+</a>
|
||||
{% else %}
|
||||
<div class="px-1 py-1 text-xs opacity-30">{{ t(key="free", lang=lang) }}</div>
|
||||
<div class="absolute inset-0 flex items-center justify-center text-xs opacity-30">{{ t(key="free", lang=lang) }}</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
@@ -5,6 +5,7 @@ mod m20220101_000001_users;
|
||||
|
||||
mod m20260515_162423_courts;
|
||||
mod m20260515_170417_bookings;
|
||||
mod m20260516_111747_add_title_to_bookings;
|
||||
pub struct Migrator;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -14,6 +15,7 @@ impl MigratorTrait for Migrator {
|
||||
Box::new(m20220101_000001_users::Migration),
|
||||
Box::new(m20260515_162423_courts::Migration),
|
||||
Box::new(m20260515_170417_bookings::Migration),
|
||||
Box::new(m20260516_111747_add_title_to_bookings::Migration),
|
||||
// inject-above (do not remove this comment)
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
use loco_rs::schema::*;
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||
add_column(m, "bookings", "title", ColType::StringNull).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, m: &SchemaManager) -> Result<(), DbErr> {
|
||||
remove_column(m, "bookings", "title").await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -301,6 +301,7 @@ pub async fn booking_new(
|
||||
"hour": q.hour.unwrap_or(FIRST_HOUR),
|
||||
"color": "#3b82f6",
|
||||
"name": "",
|
||||
"title": "",
|
||||
"contact": "",
|
||||
"note": "",
|
||||
"hours": hour_options(),
|
||||
@@ -316,6 +317,7 @@ pub struct BookingForm {
|
||||
pub hour: i32,
|
||||
pub color: String,
|
||||
pub name: String,
|
||||
pub title: Option<String>,
|
||||
pub contact: Option<String>,
|
||||
pub note: Option<String>,
|
||||
}
|
||||
@@ -338,6 +340,7 @@ pub async fn booking_create(
|
||||
hour: Set(form.hour),
|
||||
color: Set(form.color),
|
||||
name: Set(form.name),
|
||||
title: Set(form.title.filter(|s| !s.is_empty())),
|
||||
contact: Set(form.contact.filter(|s| !s.is_empty())),
|
||||
note: Set(form.note.filter(|s| !s.is_empty())),
|
||||
..Default::default()
|
||||
@@ -381,6 +384,7 @@ pub async fn booking_edit(
|
||||
"hour": booking.hour,
|
||||
"color": booking.color,
|
||||
"name": booking.name,
|
||||
"title": booking.title.unwrap_or_default(),
|
||||
"contact": booking.contact.unwrap_or_default(),
|
||||
"note": booking.note.unwrap_or_default(),
|
||||
"hours": hour_options(),
|
||||
@@ -407,6 +411,7 @@ pub async fn booking_update(
|
||||
active.hour = Set(form.hour);
|
||||
active.color = Set(form.color);
|
||||
active.name = Set(form.name);
|
||||
active.title = Set(form.title.filter(|s| !s.is_empty()));
|
||||
active.contact = Set(form.contact.filter(|s| !s.is_empty()));
|
||||
active.note = Set(form.note.filter(|s| !s.is_empty()));
|
||||
active.update(&ctx.db).await?;
|
||||
|
||||
@@ -50,7 +50,10 @@ pub struct Cell {
|
||||
pub booked: bool,
|
||||
pub color: String,
|
||||
pub booking_id: i32,
|
||||
/// Private — admin only. Never rendered on the public calendar.
|
||||
pub name: String,
|
||||
/// Public-facing label shown on the calendar when set.
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@@ -173,6 +176,7 @@ pub async fn build_calendar(
|
||||
color: b.color.clone(),
|
||||
booking_id: b.id,
|
||||
name: b.name.clone(),
|
||||
title: b.title.clone().unwrap_or_default(),
|
||||
},
|
||||
None => Cell {
|
||||
date: iso,
|
||||
@@ -181,6 +185,7 @@ pub async fn build_calendar(
|
||||
color: String::new(),
|
||||
booking_id: 0,
|
||||
name: String::new(),
|
||||
title: String::new(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@@ -18,6 +18,7 @@ pub struct Model {
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
pub note: Option<String>,
|
||||
pub court_id: i32,
|
||||
pub title: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
||||
Reference in New Issue
Block a user