Average of tickets submitted per weekday
Avvailable in the diagnostics
This commit is contained in:
@@ -1,15 +1,85 @@
|
|||||||
|
use chrono::{DateTime, Datelike, Utc};
|
||||||
use gloo_net::http::Request;
|
use gloo_net::http::Request;
|
||||||
|
use serde::Deserialize;
|
||||||
use wasm_bindgen_futures::spawn_local;
|
use wasm_bindgen_futures::spawn_local;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_router::prelude::*;
|
|
||||||
|
|
||||||
use crate::pages::ticket::{ActiveUser, Ticket};
|
use crate::pages::ticket::{ActiveUser, Ticket};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
struct Date {
|
||||||
|
date: DateTime<Utc>, // matches JSON "date": "2026-05-01T14:23:00Z"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn weekday_index(dt: &DateTime<Utc>) -> usize {
|
||||||
|
// chrono::Weekday: Mon = 1 ... Sun = 7
|
||||||
|
match dt.weekday() {
|
||||||
|
chrono::Weekday::Mon => 0,
|
||||||
|
chrono::Weekday::Tue => 1,
|
||||||
|
chrono::Weekday::Wed => 2,
|
||||||
|
chrono::Weekday::Thu => 3,
|
||||||
|
chrono::Weekday::Fri => 4,
|
||||||
|
chrono::Weekday::Sat => 5,
|
||||||
|
chrono::Weekday::Sun => 6,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_by_weekday(tickets: &[Date]) -> [usize; 7] {
|
||||||
|
let mut counts = [0usize; 7];
|
||||||
|
for t in tickets {
|
||||||
|
counts[weekday_index(&t.date)] += 1;
|
||||||
|
}
|
||||||
|
counts
|
||||||
|
}
|
||||||
|
|
||||||
|
fn day_counts(dates: &[Date]) -> [usize; 7] {
|
||||||
|
if dates.is_empty() {
|
||||||
|
return [0usize; 7];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut min = dates[0].date.date_naive();
|
||||||
|
let mut max = min;
|
||||||
|
for d in dates.iter().skip(1) {
|
||||||
|
let dt = d.date.date_naive();
|
||||||
|
if dt < min {
|
||||||
|
min = dt
|
||||||
|
}
|
||||||
|
if dt > max {
|
||||||
|
max = dt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let total_days = (max - min).num_days() + 1;
|
||||||
|
if total_days <= 0 {
|
||||||
|
return [0usize; 7];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut occ = [0usize; 7];
|
||||||
|
let mut current = min;
|
||||||
|
for _ in 0..total_days {
|
||||||
|
let wd = current.weekday();
|
||||||
|
let idx = match wd {
|
||||||
|
chrono::Weekday::Mon => 0,
|
||||||
|
chrono::Weekday::Tue => 1,
|
||||||
|
chrono::Weekday::Wed => 2,
|
||||||
|
chrono::Weekday::Thu => 3,
|
||||||
|
chrono::Weekday::Fri => 4,
|
||||||
|
chrono::Weekday::Sat => 5,
|
||||||
|
chrono::Weekday::Sun => 6,
|
||||||
|
};
|
||||||
|
occ[idx] += 1;
|
||||||
|
current = current.succ();
|
||||||
|
}
|
||||||
|
|
||||||
|
occ
|
||||||
|
}
|
||||||
|
|
||||||
#[component(Diagnostics)]
|
#[component(Diagnostics)]
|
||||||
pub fn diagnostics_component() -> Html {
|
pub fn diagnostics_component() -> Html {
|
||||||
html! {
|
html! {
|
||||||
<div>
|
<div>
|
||||||
<TicketCount/>
|
<TicketCount/>
|
||||||
|
<SubmitStats/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,5 +178,83 @@ pub fn ticket_count_component() -> Html {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[component(SubmitStats)]
|
#[component(SubmitStats)]
|
||||||
// pub fn submit_stats_component() -> Html {}
|
pub fn submit_stats_component() -> Html {
|
||||||
|
let tickets = use_state(|| Vec::<Date>::new());
|
||||||
|
let error = use_state(|| None::<String>);
|
||||||
|
let loading = use_state(|| false);
|
||||||
|
|
||||||
|
{
|
||||||
|
let tickets = tickets.clone();
|
||||||
|
let error = error.clone();
|
||||||
|
let loading = loading.clone();
|
||||||
|
|
||||||
|
use_effect_with((), move |_| {
|
||||||
|
loading.set(true);
|
||||||
|
spawn_local(async move {
|
||||||
|
let url = "/api/tickets".to_string();
|
||||||
|
match Request::get(&url).send().await {
|
||||||
|
Ok(response) if response.status() == 200 => {
|
||||||
|
match response.json::<Vec<Date>>().await {
|
||||||
|
Ok(t) => tickets.set(t),
|
||||||
|
Err(e) => error.set(Some(format!("parse error: {}", e))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(response) => {
|
||||||
|
if let Ok(text) = response.text().await {
|
||||||
|
error.set(Some(text));
|
||||||
|
} else {
|
||||||
|
error.set(Some(format!("status {}", response.status())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => error.set(Some(format!("Network error: {}", err))),
|
||||||
|
}
|
||||||
|
loading.set(false);
|
||||||
|
});
|
||||||
|
|| ()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let counts = count_by_weekday(&tickets);
|
||||||
|
let occ = day_counts(&tickets);
|
||||||
|
|
||||||
|
let mut avg = [0.0f64; 7];
|
||||||
|
for i in 0..7 {
|
||||||
|
if occ[i] > 0 {
|
||||||
|
avg[i] = counts[i] as f64 / occ[i] as f64;
|
||||||
|
} else {
|
||||||
|
avg[i] = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let weekdays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
||||||
|
let (max_idx, _max_val) = counts
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
|
||||||
|
.map(|(i, _)| (i, ()))
|
||||||
|
.unwrap_or((0, ()));
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<div>
|
||||||
|
if *loading {
|
||||||
|
<p>{ "Loading..." }</p>
|
||||||
|
}
|
||||||
|
if let Some(e) = &*error {
|
||||||
|
<p style="color: red;">{ e.clone() }</p>
|
||||||
|
}
|
||||||
|
<h3>{ "Tickets per weekday" }</h3>
|
||||||
|
<ul>
|
||||||
|
{ for (0..7).map(|i| {
|
||||||
|
let is_max = i == max_idx;
|
||||||
|
html! {
|
||||||
|
<li style={ if is_max { "font-weight: bold; color: green;" } else { "" } }>
|
||||||
|
{ format!("{}: {:3}", weekdays[i], avg[i]) }
|
||||||
|
{ if is_max { html!{ <span>{ " ← most" }</span> } } else { html!{} } }
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user