Merge branch 'boat-stats' into 'main'
Boat stats See merge request PhilippHofer/rot!59
This commit is contained in:
commit
e428d01ea8
36
src/model/notification.rs
Normal file
36
src/model/notification.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
use chrono::{DateTime, Local, NaiveDateTime, TimeZone, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::{FromRow, SqlitePool};
|
||||||
|
|
||||||
|
#[derive(FromRow, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Notification {
|
||||||
|
pub id: i64,
|
||||||
|
pub user_id: i64,
|
||||||
|
pub message: String,
|
||||||
|
pub read_at: NaiveDateTime,
|
||||||
|
pub category: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Notification {
|
||||||
|
//pub async fn create(db: &SqlitePool, msg: String) -> bool {
|
||||||
|
// sqlx::query!("INSERT INTO log(msg) VALUES (?)", msg,)
|
||||||
|
// .execute(db)
|
||||||
|
// .await
|
||||||
|
// .is_ok()
|
||||||
|
//}
|
||||||
|
|
||||||
|
async fn for_user(db: &SqlitePool, user: &User) -> Vec<Self> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
Log,
|
||||||
|
"
|
||||||
|
SELECT id, user_id, message, read_at, category
|
||||||
|
FROM notification
|
||||||
|
WHERE user_id = {}
|
||||||
|
",
|
||||||
|
user.id
|
||||||
|
)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
@ -9,7 +9,28 @@ pub struct Stat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Stat {
|
impl Stat {
|
||||||
pub async fn get_rowed_km(db: &SqlitePool) -> Vec<Stat> {
|
pub async fn boats(db: &SqlitePool) -> Vec<Stat> {
|
||||||
|
//TODO: switch to query! macro again (once upgraded to sqlite 3.42 on server)
|
||||||
|
sqlx::query(
|
||||||
|
"
|
||||||
|
SELECT (SELECT name FROM boat WHERE id=logbook.boat_id) as name, CAST(SUM(distance_in_km) AS INTEGER) AS rowed_km
|
||||||
|
FROM logbook
|
||||||
|
GROUP BY boat_id
|
||||||
|
ORDER BY rowed_km DESC;
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.map(|row| Stat {
|
||||||
|
name: row.get("name"),
|
||||||
|
rowed_km: row.get("rowed_km"),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn people(db: &SqlitePool) -> Vec<Stat> {
|
||||||
//TODO: switch to query! macro again (once upgraded to sqlite 3.42 on server)
|
//TODO: switch to query! macro again (once upgraded to sqlite 3.42 on server)
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
"
|
"
|
||||||
|
@ -9,28 +9,50 @@ use crate::model::{
|
|||||||
|
|
||||||
use super::log::KioskCookie;
|
use super::log::KioskCookie;
|
||||||
|
|
||||||
|
#[get("/boats", rank = 2)]
|
||||||
|
async fn index_boat(db: &State<SqlitePool>, user: NonGuestUser) -> Template {
|
||||||
|
let stat = Stat::boats(db).await;
|
||||||
|
let kiosk = false;
|
||||||
|
|
||||||
|
Template::render(
|
||||||
|
"stat.boats",
|
||||||
|
context!(loggedin_user: &user.user, stat, kiosk),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/boats")]
|
||||||
|
async fn index_boat_kiosk(db: &State<SqlitePool>, _kiosk: KioskCookie) -> Template {
|
||||||
|
let stat = Stat::boats(db).await;
|
||||||
|
let kiosk = true;
|
||||||
|
|
||||||
|
Template::render("stat.boats", context!(stat, kiosk, show_kiosk_header: true))
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/", rank = 2)]
|
#[get("/", rank = 2)]
|
||||||
async fn index(db: &State<SqlitePool>, user: NonGuestUser) -> Template {
|
async fn index(db: &State<SqlitePool>, user: NonGuestUser) -> Template {
|
||||||
let stat = Stat::get_rowed_km(db).await;
|
let stat = Stat::people(db).await;
|
||||||
let personal = stat::get_personal(db, &user.user).await;
|
let personal = stat::get_personal(db, &user.user).await;
|
||||||
let kiosk = false;
|
let kiosk = false;
|
||||||
|
|
||||||
Template::render(
|
Template::render(
|
||||||
"stat",
|
"stat.people",
|
||||||
context!(loggedin_user: &user.user, stat, personal, kiosk),
|
context!(loggedin_user: &user.user, stat, personal, kiosk),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
async fn index_kiosk(db: &State<SqlitePool>, _kiosk: KioskCookie) -> Template {
|
async fn index_kiosk(db: &State<SqlitePool>, _kiosk: KioskCookie) -> Template {
|
||||||
let stat = Stat::get_rowed_km(db).await;
|
let stat = Stat::people(db).await;
|
||||||
let kiosk = true;
|
let kiosk = true;
|
||||||
|
|
||||||
Template::render("stat", context!(stat, kiosk, show_kiosk_header: true))
|
Template::render(
|
||||||
|
"stat.people",
|
||||||
|
context!(stat, kiosk, show_kiosk_header: true),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
routes![index, index_kiosk]
|
routes![index, index_kiosk, index_boat, index_boat_kiosk]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
<a href="/log" class="px-2">Ausfahrt eintragen</a>
|
<a href="/log" class="px-2">Ausfahrt eintragen</a>
|
||||||
<a href="/log/show" class="px-2">Logbuch</a>
|
<a href="/log/show" class="px-2">Logbuch</a>
|
||||||
<a href="/stat" class="px-2">Statistik</a>
|
<a href="/stat" class="px-2">Statistik</a>
|
||||||
|
<a href="/stat/boats" class="px-2">Bootsauswertung</a>
|
||||||
<a href="/boatdamage" class="px-2">Bootsschaden</a>
|
<a href="/boatdamage" class="px-2">Bootsschaden</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
@ -30,6 +30,9 @@
|
|||||||
<a href="/stat" class="block w-100 py-2 hover:text-primary-600 border-t">
|
<a href="/stat" class="block w-100 py-2 hover:text-primary-600 border-t">
|
||||||
Statistik
|
Statistik
|
||||||
</a>
|
</a>
|
||||||
|
<a href="/stat/boats" class="block w-100 py-2 hover:text-primary-600 border-t">
|
||||||
|
Bootsauswertung
|
||||||
|
</a>
|
||||||
{% if loggedin_user.is_admin %}
|
{% if loggedin_user.is_admin %}
|
||||||
<a href="/admin/boat" class="block w-100 py-2 hover:text-primary-600 border-t">
|
<a href="/admin/boat" class="block w-100 py-2 hover:text-primary-600 border-t">
|
||||||
Boote
|
Boote
|
||||||
|
40
templates/stat.boats.html.tera
Normal file
40
templates/stat.boats.html.tera
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{% import "includes/macros" as macros %}
|
||||||
|
|
||||||
|
{% extends "base" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="max-w-screen-lg w-full">
|
||||||
|
<h1 class="h1">Bootsauswertung</h1>
|
||||||
|
<div class="bg-gray-200 p-3 mt-4 rounded-t-md">
|
||||||
|
<label for="name" class="sr-only">Suche</label>
|
||||||
|
<input type="search" name="name" id="filter-js" class="w-full relative block rounded-md border-0 py-1.5 px-2 text-gray-900 ring-1 ring-inset ring-gray-300 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" placeholder="Suchen nach Bootsnamen...">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="filter-result-js" class="bg-gray-200 text-primary-950 pb-3 px-3 text-right"></div>
|
||||||
|
|
||||||
|
<div class="border-r border-l">
|
||||||
|
{% set_global km = 0 %}
|
||||||
|
{% set_global index = 1 %}
|
||||||
|
{% for s in stat %}
|
||||||
|
<div class="border-t {% if loop.last %} border-b {% endif %} bg-white flex justify-between items-center px-3 py-1" data-filterable="true" data-filter="{{ s.name }}">
|
||||||
|
<span class="text-sm text-gray-600 w-10">
|
||||||
|
{% if km != s.rowed_km %}
|
||||||
|
{{loop.index}}
|
||||||
|
{% set_global index = loop.index %}
|
||||||
|
{% else %}
|
||||||
|
{{ index }}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
<span class="grow">{{s.name}}</span>
|
||||||
|
<span>{{s.rowed_km}}
|
||||||
|
km</span>
|
||||||
|
|
||||||
|
{% set_global km = s.rowed_km %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div id="container" class="w-full"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/public/logbook.js"></script>
|
||||||
|
{% endblock content%}
|
Loading…
x
Reference in New Issue
Block a user