diff --git a/frontend/main.ts b/frontend/main.ts index ab42d06..304d6f6 100644 --- a/frontend/main.ts +++ b/frontend/main.ts @@ -8,6 +8,8 @@ export interface choiceMap { let choiceObjects: choiceMap = {}; document.addEventListener('DOMContentLoaded', function() { + changeTheme(); + initcolorTheme(); initSearch(); initSidebar(); initToggle(); @@ -20,6 +22,73 @@ document.addEventListener('DOMContentLoaded', function() { setCurrentdate(document.querySelector('#departure')); }); +function changeTheme() { + let toggleBtn = document.querySelector('#theme-toggle-js'); + + if(toggleBtn) { + toggleBtn.addEventListener('click', function() { + if(toggleBtn.dataset.theme === 'light') { + setTheme('dark', true); + } else { + setTheme('light', true); + } + }); + } +} + +/*** +* init javascript +* 1) detect native color scheme or use set theme in local storage +* 2) detect view (desktop or responsive) if on mobile device with touch screen +* 3) set base font size to 112.5% -> 18px +*/ +function initcolorTheme() { + colorThemeWatcher(); + let theme = localStorage.getItem('theme'); + if (theme == null || theme === 'auto') { + if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { + setTheme('dark', false); + } else { + setTheme('light', false); + } + } else { + setTheme(theme) + } +} + +/*** +* Listener operating system native color configuration +*/ +function colorThemeWatcher() { + try { + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { + setTheme(e.matches ? 'dark' : 'light'); + }); + } catch { + console.warn('color theme watcher not supported'); + } +} + +/** +* Define color scheme, colors without losing the base font size configuration +* and add data-theme to html tag +* @param theme +*/ +function setTheme(theme: string, setLocalStorage = true) { + let toggleBtn = document.querySelector('#theme-toggle-js'); + + if (setLocalStorage) { + localStorage.setItem('theme', theme); + } + if (toggleBtn) toggleBtn.setAttribute('data-theme', theme); + + if (document.documentElement.classList.contains('dark') && theme === 'light') { + document.documentElement.classList.remove('dark'); + } else if(theme === 'dark'){ + document.documentElement.classList.add('dark'); + } +} + function setCurrentdate(input: HTMLInputElement) { if(input) { const now = new Date(); diff --git a/frontend/scss/app.scss b/frontend/scss/app.scss index 46606c2..b4bb35b 100644 --- a/frontend/scss/app.scss +++ b/frontend/scss/app.scss @@ -10,3 +10,4 @@ @import 'components/alert'; @import 'components/status'; @import 'components/chart'; +@import 'components/search'; diff --git a/frontend/scss/components/_btns.scss b/frontend/scss/components/_btns.scss index 6856169..4fca153 100644 --- a/frontend/scss/components/_btns.scss +++ b/frontend/scss/components/_btns.scss @@ -6,11 +6,11 @@ } &-dark { - @apply bg-primary-900 hover:bg-primary-950 focus-visible:outline-primary-950; + @apply bg-primary-900 hover:bg-primary-950 dark:bg-primary-950 dark:hover:bg-primary-700 focus-visible:outline-primary-950; } &-gray { - @apply bg-gray-400 hover:bg-gray-500 focus-visible:outline-primary-500; + @apply bg-gray-400 hover:bg-gray-500 dark:bg-primary-600 focus-visible:outline-primary-500; } &-attention { diff --git a/frontend/scss/components/_headlines.scss b/frontend/scss/components/_headlines.scss index 723d436..cd4fa44 100644 --- a/frontend/scss/components/_headlines.scss +++ b/frontend/scss/components/_headlines.scss @@ -1,7 +1,7 @@ .h1 { - @apply text-center text-3xl uppercase tracking-wide font-bold text-primary-900; + @apply text-center text-3xl uppercase tracking-wide font-bold text-primary-900 dark:text-white; } .h2 { - @apply font-bold uppercase tracking-wide text-center rounded-t-md text-primary-950 bg-gray-200 bg-opacity-80 text-lg px-3 py-3; + @apply font-bold uppercase tracking-wide text-center rounded-t-md text-primary-950 dark:text-white bg-gray-200 dark:bg-primary-950 bg-opacity-80 text-lg px-3 py-3; } \ No newline at end of file diff --git a/frontend/scss/components/_input.scss b/frontend/scss/components/_input.scss index 33489c2..8665c7c 100644 --- a/frontend/scss/components/_input.scss +++ b/frontend/scss/components/_input.scss @@ -1,5 +1,5 @@ .input { - @apply relative block w-full border-0 py-1.5 px-2 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset focus:ring-primary-600 sm:text-sm sm:leading-6; + @apply relative block w-full bg-white dark:bg-black border-0 py-1.5 px-2 text-gray-900 dark:text-white ring-1 ring-inset ring-gray-300 dark:ring-black placeholder:text-gray-400 focus:z-10 focus:ring-2 focus:ring-inset focus:ring-primary-600 sm:text-sm sm:leading-6; } select { @@ -7,7 +7,7 @@ select { background-repeat: no-repeat; background-position: right .75rem center; background-size: 16px 12px; - background-color: white; + @apply bg-white dark:bg-black; -webkit-appearance: none; appearance: none; } @@ -32,7 +32,7 @@ select { } .choices.is-disabled .choices__inner, .choices.is-disabled .choices__input { - background-color: #eaeaea; + @apply bg-white dark:bg-black; cursor: not-allowed; -webkit-user-select: none; user-select: none; @@ -52,8 +52,7 @@ select { display: block; width: 100%; padding: 10px; - @apply border-0 ring-1 ring-inset ring-gray-300; - background-color: #fff; + @apply bg-white dark:bg-black border-0 ring-1 ring-inset ring-gray-300 dark:ring-primary-600; margin: 0; } .choices[data-type*=select-one] .choices__button { @@ -93,7 +92,7 @@ select { pointer-events: none; } .choices[data-type*=select-one].is-open::after { - @apply border-0 ring-1 ring-inset ring-gray-300; + @apply border-0 ring-1 ring-inset ring-gray-300 dark:ring-primary-600; margin-top: -7.5px; } .choices[data-type*=select-one][dir=rtl]::after { @@ -135,10 +134,10 @@ select { } .choices__inner { - @apply input rounded-md bg-white; + @apply input rounded-md bg-white dark:bg-black; } .is-focused .choices__inner, .is-open .choices__inner { - @apply border-0 ring-1 ring-inset ring-gray-300; + @apply border-0 ring-1 ring-inset ring-gray-300 dark:ring-primary-600; } .is-open .choices__inner { border-radius: 0; @@ -190,7 +189,7 @@ select { @apply bg-primary-900; } .is-disabled .choices__list--multiple .choices__item { - @apply bg-gray-600; + @apply bg-gray-600 dark:bg-primary-900; } .choices__list--dropdown, .choices__list[aria-expanded] { @@ -198,8 +197,7 @@ select { z-index: 1; position: absolute; width: 100%; - background-color: #fff; - @apply border; + @apply border bg-white dark:bg-black dark:text-white; top: 100%; margin-top: -1px; border-bottom-left-radius: 2.5px; @@ -212,7 +210,7 @@ select { visibility: visible; } .is-open .choices__list--dropdown, .is-open .choices__list[aria-expanded] { - @apply border-0 ring-1 ring-inset ring-gray-300; + @apply border-0 ring-1 ring-inset ring-gray-300 dark:ring-primary-600; } .is-flipped .choices__list--dropdown, .is-flipped .choices__list[aria-expanded] { top: auto; @@ -260,7 +258,7 @@ select { } } .choices__list--dropdown .choices__item--selectable.is-highlighted, .choices__list[aria-expanded] .choices__item--selectable.is-highlighted { - @apply bg-gray-100; + @apply bg-gray-100 dark:bg-primary-950; } .choices__list--dropdown .choices__item--selectable.is-highlighted::after, .choices__list[aria-expanded] .choices__item--selectable.is-highlighted::after { opacity: 0.5; @@ -309,6 +307,8 @@ select { border: 0; border-radius: 0; max-width: 100%; + + @apply bg-transparent; } .choices__input:focus { outline: 0; diff --git a/frontend/scss/components/_links.scss b/frontend/scss/components/_links.scss index fcb67b1..21d9ab7 100644 --- a/frontend/scss/components/_links.scss +++ b/frontend/scss/components/_links.scss @@ -1,6 +1,6 @@ .link { &-primary { - @apply text-primary-600 hover:text-primary-900 underline; + @apply text-primary-600 dark:text-primary-200 hover:text-primary-900 hover:dark:text-primary-300 underline; } &-dark { diff --git a/frontend/scss/components/_search.scss b/frontend/scss/components/_search.scss new file mode 100644 index 0000000..a621e04 --- /dev/null +++ b/frontend/scss/components/_search.scss @@ -0,0 +1,13 @@ +.search { + &-bar { + @apply w-full relative block rounded-md border-0 py-1.5 px-2 bg-white dark:bg-black text-gray-900 dark:text-white ring-1 ring-inset ring-gray-300 dark:ring-black placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-primary-600 sm:text-sm sm:leading-6 mb-2 md:mb-0; + } + + &-wrapper { + @apply bg-gray-200 dark:bg-primary-600 p-3 mt-4 rounded-t-md; + } + + &-result { + @apply bg-gray-200 dark:bg-primary-600 text-primary-950 dark:text-white pb-3 px-3 text-right; + } +} diff --git a/frontend/scss/components/_sidebar.scss b/frontend/scss/components/_sidebar.scss index a9c12f3..619753f 100644 --- a/frontend/scss/components/_sidebar.scss +++ b/frontend/scss/components/_sidebar.scss @@ -3,14 +3,12 @@ position: fixed; overflow-y: scroll; top: 0; - background: white; z-index: 2000; width: 0; max-width: 0; box-shadow: 0 1rem 3rem rgba(0,0,0,.175); &.open { - background-color: rgba(255, 255, 255, 100%); display: block; height: 100vh; right: 0; diff --git a/frontend/tailwind.config.cjs b/frontend/tailwind.config.cjs index 6f9bcd6..c17b852 100644 --- a/frontend/tailwind.config.cjs +++ b/frontend/tailwind.config.cjs @@ -40,5 +40,7 @@ export default { }, }, plugins: [], + important: true, + darkMode: 'class', } diff --git a/src/tera/ergo.rs b/src/tera/ergo.rs index b2decf6..e0fb3b1 100644 --- a/src/tera/ergo.rs +++ b/src/tera/ergo.rs @@ -17,11 +17,12 @@ use tera::Context; use crate::model::{ log::Log, - user::{AdminUser, NonGuestUser, User}, + user::{AdminUser, User}, }; #[derive(Serialize)] struct ErgoStat { + id: i64, name: String, dob: Option, weight: Option, @@ -33,7 +34,7 @@ struct ErgoStat { async fn send(db: &State, _user: AdminUser) -> Template { let thirty = sqlx::query_as!( ErgoStat, - "SELECT name, dirty_thirty as result, dob, weight, sex FROM user WHERE deleted = 0 AND dirty_thirty is not null ORDER BY result DESC" + "SELECT id, name, dirty_thirty as result, dob, weight, sex FROM user WHERE deleted = 0 AND dirty_thirty is not null ORDER BY result DESC" ) .fetch_all(db.inner()) .await @@ -41,7 +42,7 @@ async fn send(db: &State, _user: AdminUser) -> Template { let dozen= sqlx::query_as!( ErgoStat, - "SELECT name, dirty_dozen as result, dob, weight, sex FROM user WHERE deleted = 0 AND dirty_dozen is not null ORDER BY result DESC" + "SELECT id, name, dirty_dozen as result, dob, weight, sex FROM user WHERE deleted = 0 AND dirty_dozen is not null ORDER BY result DESC" ) .fetch_all(db.inner()) .await @@ -66,13 +67,41 @@ async fn reset(db: &State, _user: AdminUser) -> Flash { ) } +#[get("//user//new?")] +async fn update( + db: &State, + _admin: AdminUser, + challenge: &str, + user_id: i64, + new: &str, +) -> Flash { + if challenge == "thirty" { + sqlx::query!("UPDATE user SET dirty_thirty = ? WHERE id=?", new, user_id) + .execute(db.inner()) + .await + .unwrap(); + Flash::success(Redirect::to("/ergo"), "Succ") + } else if challenge == "dozen" { + sqlx::query!("UPDATE user SET dirty_dozen = ? WHERE id=?", new, user_id) + .execute(db.inner()) + .await + .unwrap(); + Flash::success(Redirect::to("/ergo"), "Succ") + } else { + Flash::error( + Redirect::to("/ergo"), + "Challenge not found (should be thirty or dozen)", + ) + } +} + #[get("/")] async fn index(db: &State, user: User, flash: Option>) -> Template { let users = User::ergo(db).await; let thirty = sqlx::query_as!( ErgoStat, - "SELECT name, dirty_thirty as result, dob, weight, sex FROM user WHERE deleted = 0 AND dirty_thirty is not null ORDER BY result DESC" + "SELECT id, name, dirty_thirty as result, dob, weight, sex FROM user WHERE deleted = 0 AND dirty_thirty is not null ORDER BY result DESC" ) .fetch_all(db.inner()) .await @@ -80,7 +109,7 @@ async fn index(db: &State, user: User, flash: Option Vec { - routes![index, new_thirty, new_dozen, send, reset] + routes![index, new_thirty, new_dozen, send, reset, update] } #[cfg(test)] diff --git a/templates/admin/user/index.html.tera b/templates/admin/user/index.html.tera index 5fbb47b..5e748ac 100644 --- a/templates/admin/user/index.html.tera +++ b/templates/admin/user/index.html.tera @@ -30,26 +30,29 @@ -
+
- +
-
-
+
+
{% for user in users %}
-
-
+ +
-
{{ user.name }} +
{{ user.name }} {% if user.last_access %} (last access: {{ user.last_access | date }}) {% endif %} + {% if user.pw %} + Passwort zurücksetzen + {% endif %}
-
+
{{ macros::checkbox(label='Scheckbuch', name='is_guest', id=loop.index , checked=user.is_guest) }} {{ macros::checkbox(label='Steuerberechtigter', name='is_cox', id=loop.index , checked=user.is_cox) }} {{ macros::checkbox(label='Technical', name='is_tech', id=loop.index , checked=user.is_tech) }} @@ -58,16 +61,13 @@ {{ macros::input(label='Weight (kg)', name='weight', id=loop.index, type="text", value=user.weight) }} {{ macros::input(label='Sex', name='sex', id=loop.index, type="text", value=user.sex) }}
- {% if user.pw %} - Passwort zurücksetzen - {% endif %}
- diff --git a/templates/base.html.tera b/templates/base.html.tera index e7ae735..c33fefc 100644 --- a/templates/base.html.tera +++ b/templates/base.html.tera @@ -9,7 +9,7 @@ Ruderassistent - ASKÖ Ruderverein Donau Linz - + {% if loggedin_user %} {{ macros::header(loggedin_user=loggedin_user) }} {% endif %} @@ -30,7 +30,7 @@ {% endif %} -
{% block content %}{% endblock content %} +
{% block content %}{% endblock content %}
{% if loggedin_user %} diff --git a/templates/boatdamages.html.tera b/templates/boatdamages.html.tera index 0bfadbd..49e2d2f 100644 --- a/templates/boatdamages.html.tera +++ b/templates/boatdamages.html.tera @@ -38,24 +38,24 @@
-
+
- +
-
+
{% for boatdamage in boatdamages | sort(attribute="verified") %} -
+
- {{ boatdamage.created_at | date(format='%d.%m.%Y') }} ({{ boatdamage.boat.name }}){% if boatdamage.boat.damage %}(Boot gesperrt){% endif %} + {{ boatdamage.created_at | date(format='%d.%m.%Y') }} ({{ boatdamage.boat.name }}){% if boatdamage.boat.damage %}(Boot gesperrt){% endif %}
{{ boatdamage.desc }}
- + Schaden eingetragen von {{ boatdamage.user_created.name }} am/um {{ boatdamage.created_at | date(format='%d.%m.%Y (%H:%M)') }} {% if boatdamage.fixed_at %} - Repariert von {{ boatdamage.user_fixed.name }} am/um {{ boatdamage.fixed_at | date(format='%d.%m.%Y (%H:%M)') }} + Repariert von {{ boatdamage.user_fixed.name }} am/um {{ boatdamage.fixed_at | date(format='%d.%m.%Y (%H:%M)') }} {% else %} {% if loggedin_user.is_cox %}
@@ -70,7 +70,7 @@ {% endif %} {% if boatdamage.verified_at %} - Verifziert von {{ boatdamage.user_verified.name }} am/um {{ boatdamage.verified_at | date(format='%d.%m.%Y (%H:%M)') }} + Verifziert von {{ boatdamage.user_verified.name }} am/um {{ boatdamage.verified_at | date(format='%d.%m.%Y (%H:%M)') }} {% else %} {% if loggedin_user.is_tech and boatdamage.fixed_at %} diff --git a/templates/dynamics/sidebar.html.tera b/templates/dynamics/sidebar.html.tera index 59c7041..2f9e7f3 100644 --- a/templates/dynamics/sidebar.html.tera +++ b/templates/dynamics/sidebar.html.tera @@ -1,4 +1,4 @@ -