show notification badge in menu
This commit is contained in:
		| @@ -47,16 +47,18 @@ pub struct User { | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Serialize, Deserialize)] | ||||
| pub struct UserWithRoles { | ||||
| pub struct UserWithRolesAndNotificationCount { | ||||
|     #[serde(flatten)] | ||||
|     pub user: User, | ||||
|     pub amount_unread_notifications: i32, | ||||
|     pub roles: Vec<String>, | ||||
| } | ||||
|  | ||||
| impl UserWithRoles { | ||||
| impl UserWithRolesAndNotificationCount { | ||||
|     pub async fn from_user(user: User, db: &SqlitePool) -> Self { | ||||
|         Self { | ||||
|             roles: user.roles(db).await, | ||||
|             amount_unread_notifications: user.amount_unread_notifications(db).await, | ||||
|             user, | ||||
|         } | ||||
|     } | ||||
| @@ -237,6 +239,17 @@ impl User { | ||||
|         .count | ||||
|     } | ||||
|  | ||||
|     pub async fn amount_unread_notifications(&self, db: &SqlitePool) -> i32 { | ||||
|         sqlx::query!( | ||||
|             "SELECT COUNT(*) as count FROM notification WHERE user_id = ? AND read_at IS NULL", | ||||
|             self.id | ||||
|         ) | ||||
|         .fetch_one(db) | ||||
|         .await | ||||
|         .unwrap() | ||||
|         .count | ||||
|     } | ||||
|  | ||||
|     pub async fn has_role(&self, db: &SqlitePool, role: &str) -> bool { | ||||
|         if sqlx::query!( | ||||
|             "SELECT * FROM user_role WHERE user_id=? AND role_id = (SELECT id FROM role WHERE name = ?)", | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| use crate::model::{ | ||||
|     boat::{Boat, BoatToAdd, BoatToUpdate}, | ||||
|     location::Location, | ||||
|     user::{AdminUser, User, UserWithRoles}, | ||||
|     user::{AdminUser, User, UserWithRolesAndNotificationCount}, | ||||
| }; | ||||
| use rocket::{ | ||||
|     form::Form, | ||||
| @@ -32,7 +32,7 @@ async fn index( | ||||
|     context.insert("users", &users); | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRoles::from_user(admin.user, db).await, | ||||
|         &UserWithRolesAndNotificationCount::from_user(admin.user, db).await, | ||||
|     ); | ||||
|  | ||||
|     Template::render("admin/boat/index", context.into_json()) | ||||
|   | ||||
| @@ -10,7 +10,7 @@ use crate::model::log::Log; | ||||
| use crate::model::mail::Mail; | ||||
| use crate::model::role::Role; | ||||
| use crate::model::user::AdminUser; | ||||
| use crate::model::user::UserWithRoles; | ||||
| use crate::model::user::UserWithRolesAndNotificationCount; | ||||
| use crate::tera::Config; | ||||
|  | ||||
| #[get("/mail")] | ||||
| @@ -27,7 +27,7 @@ async fn index( | ||||
|  | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRoles::from_user(admin.user, db).await, | ||||
|         &UserWithRolesAndNotificationCount::from_user(admin.user, db).await, | ||||
|     ); | ||||
|     context.insert("roles", &roles); | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,7 @@ use crate::model::{ | ||||
|     log::Log, | ||||
|     notification::Notification, | ||||
|     role::Role, | ||||
|     user::{AdminUser, User, UserWithRoles}, | ||||
|     user::{AdminUser, User, UserWithRolesAndNotificationCount}, | ||||
| }; | ||||
| use rocket::{ | ||||
|     form::Form, | ||||
| @@ -26,7 +26,7 @@ async fn index( | ||||
|     } | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRoles::from_user(user.user, db).await, | ||||
|         &UserWithRolesAndNotificationCount::from_user(user.user, db).await, | ||||
|     ); | ||||
|     context.insert("roles", &Role::all(db).await); | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| use crate::model::{ | ||||
|     role::Role, | ||||
|     user::{SchnupperBetreuerUser, User, UserWithRoles}, | ||||
|     user::{SchnupperBetreuerUser, User, UserWithRolesAndNotificationCount}, | ||||
| }; | ||||
| use futures::future::join_all; | ||||
| use rocket::{ | ||||
| @@ -38,9 +38,9 @@ async fn index( | ||||
|     let user_futures: Vec<_> = User::all_with_role(db, &schnupperant) | ||||
|         .await | ||||
|         .into_iter() | ||||
|         .map(|u| async move { UserWithRoles::from_user(u, db).await }) | ||||
|         .map(|u| async move { UserWithRolesAndNotificationCount::from_user(u, db).await }) | ||||
|         .collect(); | ||||
|     let users: Vec<UserWithRoles> = join_all(user_futures).await; | ||||
|     let users: Vec<UserWithRolesAndNotificationCount> = join_all(user_futures).await; | ||||
|  | ||||
|     let mut context = Context::new(); | ||||
|     if let Some(msg) = flash { | ||||
| @@ -49,7 +49,7 @@ async fn index( | ||||
|     context.insert("schnupperanten", &users); | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRoles::from_user(user.into(), db).await, | ||||
|         &UserWithRolesAndNotificationCount::from_user(user.into(), db).await, | ||||
|     ); | ||||
|  | ||||
|     Template::render("admin/schnupper/index", context.into_json()) | ||||
|   | ||||
| @@ -6,8 +6,8 @@ use crate::model::{ | ||||
|     logbook::Logbook, | ||||
|     role::Role, | ||||
|     user::{ | ||||
|         AdminUser, User, UserWithMembershipPdf, UserWithRoles, UserWithRolesAndMembershipPdf, | ||||
|         VorstandUser, | ||||
|         AdminUser, User, UserWithMembershipPdf, UserWithRolesAndMembershipPdf, | ||||
|         UserWithRolesAndNotificationCount, VorstandUser, | ||||
|     }, | ||||
| }; | ||||
| use futures::future::join_all; | ||||
| @@ -67,7 +67,10 @@ async fn index( | ||||
|     context.insert("users", &users); | ||||
|     context.insert("roles", &roles); | ||||
|     context.insert("families", &families); | ||||
|     context.insert("loggedin_user", &UserWithRoles::from_user(user, db).await); | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRolesAndNotificationCount::from_user(user, db).await, | ||||
|     ); | ||||
|  | ||||
|     Template::render("admin/user/index", context.into_json()) | ||||
| } | ||||
| @@ -99,7 +102,10 @@ async fn index_admin( | ||||
|     context.insert("users", &users); | ||||
|     context.insert("roles", &roles); | ||||
|     context.insert("families", &families); | ||||
|     context.insert("loggedin_user", &UserWithRoles::from_user(user, db).await); | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRolesAndNotificationCount::from_user(user, db).await, | ||||
|     ); | ||||
|  | ||||
|     Template::render("admin/user/index", context.into_json()) | ||||
| } | ||||
| @@ -127,7 +133,7 @@ async fn fees( | ||||
|     } | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRoles::from_user(admin.into(), db).await, | ||||
|         &UserWithRolesAndNotificationCount::from_user(admin.into(), db).await, | ||||
|     ); | ||||
|  | ||||
|     Template::render("admin/user/fees", context.into_json()) | ||||
| @@ -147,7 +153,7 @@ async fn scheckbuch( | ||||
|     for s in scheckbooks { | ||||
|         scheckbooks_with_roles.push(( | ||||
|             Logbook::completed_with_user(db, &s).await, | ||||
|             UserWithRoles::from_user(s, db).await, | ||||
|             UserWithRolesAndNotificationCount::from_user(s, db).await, | ||||
|         )) | ||||
|     } | ||||
|  | ||||
| @@ -158,7 +164,7 @@ async fn scheckbuch( | ||||
|     } | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRoles::from_user(user.into(), db).await, | ||||
|         &UserWithRolesAndNotificationCount::from_user(user.into(), db).await, | ||||
|     ); | ||||
|  | ||||
|     Template::render("admin/user/scheckbuch", context.into_json()) | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| use crate::model::{ | ||||
|     boat::Boat, | ||||
|     boathouse::Boathouse, | ||||
|     user::{AdminUser, UserWithRoles, VorstandUser}, | ||||
|     user::{AdminUser, UserWithRolesAndNotificationCount, VorstandUser}, | ||||
| }; | ||||
| use rocket::{ | ||||
|     form::Form, | ||||
| @@ -39,7 +39,7 @@ async fn index( | ||||
|  | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRoles::from_user(admin.into(), db).await, | ||||
|         &UserWithRolesAndNotificationCount::from_user(admin.into(), db).await, | ||||
|     ); | ||||
|  | ||||
|     Template::render("board/boathouse", context.into_json()) | ||||
|   | ||||
| @@ -13,7 +13,7 @@ use crate::{ | ||||
|     model::{ | ||||
|         boat::Boat, | ||||
|         boatdamage::{BoatDamage, BoatDamageFixed, BoatDamageToAdd, BoatDamageVerified}, | ||||
|         user::{CoxUser, DonauLinzUser, TechUser, User, UserWithRoles}, | ||||
|         user::{CoxUser, DonauLinzUser, TechUser, User, UserWithRolesAndNotificationCount}, | ||||
|     }, | ||||
|     tera::log::KioskCookie, | ||||
| }; | ||||
| @@ -59,7 +59,7 @@ async fn index( | ||||
|     context.insert("boats", &boats); | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRoles::from_user(user.into(), db).await, | ||||
|         &UserWithRolesAndNotificationCount::from_user(user.into(), db).await, | ||||
|     ); | ||||
|  | ||||
|     Template::render("boatdamages", context.into_json()) | ||||
|   | ||||
| @@ -14,7 +14,7 @@ use crate::{ | ||||
|     model::{ | ||||
|         boat::Boat, | ||||
|         boatreservation::{BoatReservation, BoatReservationToAdd}, | ||||
|         user::{DonauLinzUser, User, UserWithRoles}, | ||||
|         user::{DonauLinzUser, User, UserWithRolesAndNotificationCount}, | ||||
|     }, | ||||
|     tera::log::KioskCookie, | ||||
| }; | ||||
| @@ -74,7 +74,7 @@ async fn index( | ||||
|     context.insert("user", &User::all(db).await); | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRoles::from_user(user.into(), db).await, | ||||
|         &UserWithRolesAndNotificationCount::from_user(user.into(), db).await, | ||||
|     ); | ||||
|  | ||||
|     Template::render("boatreservations", context.into_json()) | ||||
|   | ||||
| @@ -18,7 +18,7 @@ use tera::Context; | ||||
|  | ||||
| use crate::model::{ | ||||
|     log::Log, | ||||
|     user::{AdminUser, User, UserWithRoles}, | ||||
|     user::{AdminUser, User, UserWithRolesAndNotificationCount}, | ||||
| }; | ||||
|  | ||||
| #[derive(Serialize)] | ||||
| @@ -51,7 +51,7 @@ async fn send(db: &State<SqlitePool>, _user: AdminUser) -> Template { | ||||
|  | ||||
|     Template::render( | ||||
|         "ergo.final", | ||||
|         context!(loggedin_user: &UserWithRoles::from_user(_user.user, db).await, thirty, dozen), | ||||
|         context!(loggedin_user: &UserWithRolesAndNotificationCount::from_user(_user.user, db).await, thirty, dozen), | ||||
|     ) | ||||
| } | ||||
|  | ||||
| @@ -120,7 +120,10 @@ async fn index(db: &State<SqlitePool>, user: User, flash: Option<FlashMessage<'_ | ||||
|     if let Some(msg) = flash { | ||||
|         context.insert("flash", &msg.into_inner()); | ||||
|     } | ||||
|     context.insert("loggedin_user", &UserWithRoles::from_user(user, db).await); | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRolesAndNotificationCount::from_user(user, db).await, | ||||
|     ); | ||||
|     context.insert("users", &users); | ||||
|     context.insert("thirty", &thirty); | ||||
|     context.insert("dozen", &dozen); | ||||
|   | ||||
| @@ -24,7 +24,9 @@ use crate::model::{ | ||||
|         LogbookUpdateError, | ||||
|     }, | ||||
|     logtype::LogType, | ||||
|     user::{AdminUser, DonauLinzUser, User, UserWithRoles, UserWithWaterStatus}, | ||||
|     user::{ | ||||
|         AdminUser, DonauLinzUser, User, UserWithRolesAndNotificationCount, UserWithWaterStatus, | ||||
|     }, | ||||
| }; | ||||
|  | ||||
| pub struct KioskCookie(String); | ||||
| @@ -84,7 +86,7 @@ async fn index( | ||||
|     context.insert("logtypes", &logtypes); | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRoles::from_user(user.into(), db).await, | ||||
|         &UserWithRolesAndNotificationCount::from_user(user.into(), db).await, | ||||
|     ); | ||||
|     context.insert("on_water", &on_water); | ||||
|     context.insert("distances", &distances); | ||||
| @@ -98,7 +100,7 @@ async fn show(db: &State<SqlitePool>, user: DonauLinzUser) -> Template { | ||||
|  | ||||
|     Template::render( | ||||
|         "log.completed", | ||||
|         context!(logs, loggedin_user: &UserWithRoles::from_user(user.into(), db).await), | ||||
|         context!(logs, loggedin_user: &UserWithRolesAndNotificationCount::from_user(user.into(), db).await), | ||||
|     ) | ||||
| } | ||||
|  | ||||
| @@ -108,7 +110,7 @@ async fn show_for_year(db: &State<SqlitePool>, user: AdminUser, year: i32) -> Te | ||||
|  | ||||
|     Template::render( | ||||
|         "log.completed", | ||||
|         context!(logs, loggedin_user: &UserWithRoles::from_user(user.user, db).await), | ||||
|         context!(logs, loggedin_user: &UserWithRolesAndNotificationCount::from_user(user.user, db).await), | ||||
|     ) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -23,7 +23,7 @@ use tera::Context; | ||||
| use crate::model::{ | ||||
|     notification::Notification, | ||||
|     role::Role, | ||||
|     user::{User, UserWithRoles}, | ||||
|     user::{User, UserWithRolesAndNotificationCount}, | ||||
| }; | ||||
|  | ||||
| pub(crate) mod admin; | ||||
| @@ -53,7 +53,10 @@ async fn index(db: &State<SqlitePool>, user: User, flash: Option<FlashMessage<'_ | ||||
|     } | ||||
|  | ||||
|     context.insert("notifications", &Notification::for_user(db, &user).await); | ||||
|     context.insert("loggedin_user", &UserWithRoles::from_user(user, db).await); | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRolesAndNotificationCount::from_user(user, db).await, | ||||
|     ); | ||||
|     Template::render("index", context.into_json()) | ||||
| } | ||||
|  | ||||
| @@ -75,7 +78,10 @@ async fn steering(db: &State<SqlitePool>, user: User, flash: Option<FlashMessage | ||||
|     context.insert("coxes", &coxes); | ||||
|     context.insert("bootskundige", &bootskundige); | ||||
|  | ||||
|     context.insert("loggedin_user", &UserWithRoles::from_user(user, db).await); | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRolesAndNotificationCount::from_user(user, db).await, | ||||
|     ); | ||||
|     Template::render("steering", context.into_json()) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -13,7 +13,7 @@ use crate::model::{ | ||||
|     logbook::Logbook, | ||||
|     tripdetails::TripDetails, | ||||
|     triptype::TripType, | ||||
|     user::{AllowedForPlannedTripsUser, User, UserWithRoles}, | ||||
|     user::{AllowedForPlannedTripsUser, User, UserWithRolesAndNotificationCount}, | ||||
|     usertrip::{UserTrip, UserTripDeleteError, UserTripError}, | ||||
| }; | ||||
|  | ||||
| @@ -44,7 +44,10 @@ async fn index( | ||||
|     } | ||||
|  | ||||
|     context.insert("fee", &user.fee(db).await); | ||||
|     context.insert("loggedin_user", &UserWithRoles::from_user(user, db).await); | ||||
|     context.insert( | ||||
|         "loggedin_user", | ||||
|         &UserWithRolesAndNotificationCount::from_user(user, db).await, | ||||
|     ); | ||||
|     context.insert("days", &days); | ||||
|     Template::render("planned", context.into_json()) | ||||
| } | ||||
|   | ||||
| @@ -4,7 +4,7 @@ use sqlx::SqlitePool; | ||||
|  | ||||
| use crate::model::{ | ||||
|     stat::{self, BoatStat, Stat}, | ||||
|     user::{DonauLinzUser, UserWithRoles}, | ||||
|     user::{DonauLinzUser, UserWithRolesAndNotificationCount}, | ||||
| }; | ||||
|  | ||||
| use super::log::KioskCookie; | ||||
| @@ -16,7 +16,7 @@ async fn index_boat(db: &State<SqlitePool>, user: DonauLinzUser) -> Template { | ||||
|  | ||||
|     Template::render( | ||||
|         "stat.boats", | ||||
|         context!(loggedin_user: &UserWithRoles::from_user(user.into(), db).await, stat, kiosk), | ||||
|         context!(loggedin_user: &UserWithRolesAndNotificationCount::from_user(user.into(), db).await, stat, kiosk), | ||||
|     ) | ||||
| } | ||||
|  | ||||
| @@ -38,7 +38,7 @@ async fn index(db: &State<SqlitePool>, user: DonauLinzUser, year: Option<i32>) - | ||||
|  | ||||
|     Template::render( | ||||
|         "stat.people", | ||||
|         context!(loggedin_user: &UserWithRoles::from_user(user.into(), db).await, stat, personal, kiosk, guest_km, club_km), | ||||
|         context!(loggedin_user: &UserWithRolesAndNotificationCount::from_user(user.into(), db).await, stat, personal, kiosk, guest_km, club_km), | ||||
|     ) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -8,16 +8,16 @@ | ||||
|             <div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5" | ||||
|                  role="alert"> | ||||
|                 <h2 class="h2">Mail versenden</h2> | ||||
|                 <form action="/admin/mail" method="post" enctype="multipart/form-data" class="grid gap-3 p-3"> | ||||
|                 <form action="/admin/mail" | ||||
|                       method="post" | ||||
|                       enctype="multipart/form-data" | ||||
|                       class="grid gap-3 p-3"> | ||||
|                     {{ macros::select(label="Gruppe", data=roles, name="role_id") }} | ||||
|                     {{ macros::input(label="Betreff", name="subject", type="text", required=true) }} | ||||
|                     <div class=""> | ||||
|                         <label for="content" class=" text-sm text-gray-600 dark:text-white "> | ||||
|                             Inhalt | ||||
|                         </label> | ||||
|                         <label for="content" class=" text-sm text-gray-600 dark:text-white ">Inhalt</label> | ||||
|                         <textarea id="content" name="body" rows="4" cols="50" class="input rounded-md"></textarea> | ||||
|                     </div> | ||||
|                      | ||||
|                     <input type="file" name="files" multiple /> | ||||
|                     <input type="submit" class="btn btn-primary" value="Abschicken" /> | ||||
|                 </form> | ||||
|   | ||||
| @@ -6,9 +6,17 @@ | ||||
|               content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||||
|         <link rel="manifest" href="public/manifest.json" /> | ||||
|         <link rel="stylesheet" href="/public/main.css" /> | ||||
|         <link rel="apple-touch-icon" sizes="180x180" href="/public/images/apple-touch-icon.png"> | ||||
|         <link rel="icon" type="image/png" sizes="32x32" href="/public/images/favicon-32x32.png"> | ||||
|         <link rel="icon" type="image/png" sizes="16x16" href="/public/images/favicon-16x16.png"> | ||||
|         <link rel="apple-touch-icon" | ||||
|               sizes="180x180" | ||||
|               href="/public/images/apple-touch-icon.png"> | ||||
|         <link rel="icon" | ||||
|               type="image/png" | ||||
|               sizes="32x32" | ||||
|               href="/public/images/favicon-32x32.png"> | ||||
|         <link rel="icon" | ||||
|               type="image/png" | ||||
|               sizes="16x16" | ||||
|               href="/public/images/favicon-16x16.png"> | ||||
|         <link rel="manifest" href="/public/images/site.webmanifest"> | ||||
|         <meta name="msapplication-TileColor" content="#da532c"> | ||||
|         <meta name="theme-color" content="#ffffff"> | ||||
|   | ||||
| @@ -31,6 +31,18 @@ | ||||
|                 <a href="/"> | ||||
|                     Hü | ||||
|                     {{ loggedin_user.name }} | ||||
|                     {% if loggedin_user.amount_unread_notifications > 0 %} | ||||
|                         <span class="bg-red-100 text-red-800 text-sm font-medium me-2 px-2.5 py-0.5 rounded-full dark:bg-red-900 dark:text-red-300 inline-flex items-center"> | ||||
|                             <svg class="w-3 h-3 me-1.5" | ||||
|                                  aria-hidden="true" | ||||
|                                  xmlns="http://www.w3.org/2000/svg" | ||||
|                                  fill="currentColor" | ||||
|                                  viewBox="0 0 20 20"> | ||||
|                                 <path d="M1.5 8.67v8.58a3 3 0 0 0 3 3h15a3 3 0 0 0 3-3V8.67l-8.928 5.493a3 3 0 0 1-3.144 0L1.5 8.67Z" /> | ||||
|                                 <path d="M22.5 6.908V6.75a3 3 0 0 0-3-3h-15a3 3 0 0 0-3 3v.158l9.714 5.978a1.5 1.5 0 0 0 1.572 0L22.5 6.908Z" /> | ||||
|                             </svg> | ||||
|                         {{ loggedin_user.amount_unread_notifications }}</span> | ||||
|                     {% endif %} | ||||
|                 </a> | ||||
|             </div> | ||||
|             <div> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user