diff --git a/frontend/src/pages/utilities.rs b/frontend/src/pages/utilities.rs index 37e5e76..57c6583 100644 --- a/frontend/src/pages/utilities.rs +++ b/frontend/src/pages/utilities.rs @@ -1,15 +1,85 @@ +use chrono::{DateTime, Datelike, Utc}; use gloo_net::http::Request; +use serde::Deserialize; use wasm_bindgen_futures::spawn_local; use yew::prelude::*; -use yew_router::prelude::*; use crate::pages::ticket::{ActiveUser, Ticket}; +#[derive(Debug, Deserialize, Clone)] +struct Date { + date: DateTime, // matches JSON "date": "2026-05-01T14:23:00Z" +} + +fn weekday_index(dt: &DateTime) -> 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)] pub fn diagnostics_component() -> Html { html! {
+
} } @@ -108,5 +178,83 @@ pub fn ticket_count_component() -> Html { } } -// #[component(SubmitStats)] -// pub fn submit_stats_component() -> Html {} +#[component(SubmitStats)] +pub fn submit_stats_component() -> Html { + let tickets = use_state(|| Vec::::new()); + let error = use_state(|| None::); + 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::>().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! { +
+ if *loading { +

{ "Loading..." }

+ } + if let Some(e) = &*error { +

{ e.clone() }

+ } +

{ "Tickets per weekday" }

+
    + { for (0..7).map(|i| { + let is_max = i == max_idx; + html! { +
  • + { format!("{}: {:3}", weekdays[i], avg[i]) } + { if is_max { html!{ { " ← most" } } } else { html!{} } } +
  • + } + })} +
+
+ } +}