diff --git a/crates/frontend/src/components/root.rs b/crates/frontend/src/components/root.rs
index 92480e1..13709f8 100644
--- a/crates/frontend/src/components/root.rs
+++ b/crates/frontend/src/components/root.rs
@@ -1,5 +1,7 @@
use crate::components::achievement::AchievementComponent;
use crate::components::milestone::MilestoneComponent;
+use chrono::NaiveTime;
+use common::Achievement;
use yew::functional::*;
use yew::prelude::*;
use yew_router::prelude::*;
@@ -20,19 +22,41 @@ pub fn Root() -> Html {
nav.push(&crate::Route::CreateMilestone);
});
- let achievements = app_state
- .state
- .achievements
- .iter()
- .cloned()
- .enumerate()
- .map(|(idx, a)| (idx + 1, a))
- .map(|(n, a)| {
- html! {
-
+ let current_time: chrono::NaiveTime = chrono::Local::now().time();
+
+ let achievements = app_state.state.achievements.clone();
+ let grouped_achievements = group_achievements(achievements);
+
+ let mut achievements_html: Vec = vec![];
+ let mut n = 0;
+ for (time_of_reveal, group) in grouped_achievements {
+ if let Some(time_of_reveal) = time_of_reveal {
+ let s = if time_of_reveal > current_time {
+ format!(
+ "{} more revealed at {}!",
+ group.len(),
+ time_of_reveal.format("%H:%M")
+ )
+ } else {
+ format!("{}", time_of_reveal.format("%H:%M"))
+ };
+ let html = html! {
+
{s}
+ };
+ achievements_html.push(html);
+ if time_of_reveal > current_time {
+ break;
}
- })
- .collect::();
+ }
+ for a in group {
+ n += 1;
+ let html = html! {
+
+ };
+ achievements_html.push(html);
+ }
+ }
+ let achievements = achievements_html.into_iter().collect::();
let completed_achievements = app_state
.state
@@ -77,3 +101,139 @@ pub fn Root() -> Html {
>
}
}
+
+fn group_achievements(mut achievements: Vec) -> Vec<(Option, Vec)> {
+ // Sort the achievements by time_of_reveal.
+ achievements.sort_by_key(|a| a.time_of_reveal);
+
+ let mut grouped_achievements: Vec<(Option, Vec)> = Vec::new();
+ let mut current_time_of_reveal: Option = None;
+ let mut current_group: Vec = Vec::new();
+
+ for achievement in achievements {
+ if current_time_of_reveal == achievement.time_of_reveal {
+ current_group.push(achievement);
+ continue;
+ }
+ if !current_group.is_empty() {
+ grouped_achievements.push((current_time_of_reveal, current_group));
+ }
+ current_time_of_reveal = achievement.time_of_reveal;
+ current_group = vec![achievement];
+ }
+
+ if !current_group.is_empty() {
+ grouped_achievements.push((current_time_of_reveal, current_group));
+ }
+
+ grouped_achievements
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use uuid::Uuid;
+
+ #[test]
+ fn test_group_achievements() {
+ let time1 = NaiveTime::from_hms_opt(12, 0, 0).unwrap();
+ let time2 = NaiveTime::from_hms_opt(13, 0, 0).unwrap();
+
+ let mut achievements = vec![
+ Achievement {
+ goal: "Goal 1".to_string(),
+ completed: true,
+ uuid: Uuid::new_v4(),
+ time_of_reveal: Some(time1),
+ },
+ Achievement {
+ goal: "Goal 2".to_string(),
+ completed: false,
+ uuid: Uuid::new_v4(),
+ time_of_reveal: Some(time1),
+ },
+ Achievement {
+ goal: "Goal 3".to_string(),
+ completed: true,
+ uuid: Uuid::new_v4(),
+ time_of_reveal: Some(time2),
+ },
+ ];
+
+ let grouped = group_achievements(achievements);
+ assert_eq!(grouped.len(), 2);
+ assert_eq!(grouped[0].0, Some(time1));
+ assert_eq!(grouped[0].1.len(), 2);
+ assert_eq!(grouped[1].0, Some(time2));
+ assert_eq!(grouped[1].1.len(), 1);
+ }
+
+ #[test]
+ fn test_group_achievements_empty_vec() {
+ let achievements = vec![];
+ let grouped = group_achievements(achievements);
+ assert_eq!(grouped.len(), 0);
+ }
+
+ #[test]
+ fn test_group_achievements_group_none_values() {
+ let time1 = NaiveTime::from_hms_opt(12, 0, 0).unwrap();
+
+ let achievements = vec![
+ Achievement {
+ goal: "Goal 1".to_string(),
+ completed: true,
+ uuid: Uuid::new_v4(),
+ time_of_reveal: Some(time1),
+ },
+ Achievement {
+ goal: "Goal 2".to_string(),
+ completed: false,
+ uuid: Uuid::new_v4(),
+ time_of_reveal: None,
+ },
+ Achievement {
+ goal: "Goal 3".to_string(),
+ completed: true,
+ uuid: Uuid::new_v4(),
+ time_of_reveal: None,
+ },
+ ];
+
+ let grouped = group_achievements(achievements);
+ assert_eq!(grouped.len(), 2);
+ assert_eq!(grouped[0].0, None);
+ assert_eq!(grouped[0].1.len(), 2);
+ assert_eq!(grouped[1].0, Some(time1));
+ assert_eq!(grouped[1].1.len(), 1);
+ }
+
+ #[test]
+ fn test_group_achievements_all_none() {
+ let achievements = vec![
+ Achievement {
+ goal: "Goal 1".to_string(),
+ completed: true,
+ uuid: Uuid::new_v4(),
+ time_of_reveal: None,
+ },
+ Achievement {
+ goal: "Goal 2".to_string(),
+ completed: false,
+ uuid: Uuid::new_v4(),
+ time_of_reveal: None,
+ },
+ Achievement {
+ goal: "Goal 3".to_string(),
+ completed: true,
+ uuid: Uuid::new_v4(),
+ time_of_reveal: None,
+ },
+ ];
+
+ let grouped = group_achievements(achievements);
+ assert_eq!(grouped.len(), 1);
+ assert_eq!(grouped[0].0, None);
+ assert_eq!(grouped[0].1.len(), 3);
+ }
+}
diff --git a/todo.md b/todo.md
index e9de734..2382e8a 100644
--- a/todo.md
+++ b/todo.md
@@ -1,3 +1,5 @@
- UI errors from failed requests
- websocket reconnect
- disable timed reveal
+- trigger some refresh when time exceeds next reveal
+- make it an admin interface, and move the new + remove buttons there, as well as milestone management