From bbced4bc3355b569d4ee8f8c1a4f3b551f3e7b99 Mon Sep 17 00:00:00 2001
From: marcantoinem <marc-antoine.m@outlook.com>
Date: Sat, 27 Jul 2024 22:55:50 -0400
Subject: [PATCH] (feat) rework reactive system

---
 aep-schedule-website/src/frontend/app.rs      | 10 +++---
 .../components/options/courses_selector.rs    | 16 ++++++---
 .../src/frontend/components/options/form.rs   |  5 +++
 .../frontend/components/options/personal.rs   |  6 ++--
 .../src/frontend/components/options/search.rs | 34 +++----------------
 .../src/frontend/components/options/state.rs  | 34 +++++++++++++++----
 .../src/frontend/components/options/todo.rs   | 17 +++-------
 .../src/frontend/components/schedules.rs      | 18 +++++-----
 aep-schedule-website/style/main.scss          |  3 +-
 aep-schedule-website/style/options.scss       |  8 -----
 10 files changed, 72 insertions(+), 79 deletions(-)

diff --git a/aep-schedule-website/src/frontend/app.rs b/aep-schedule-website/src/frontend/app.rs
index 9c5c705..eca8392 100644
--- a/aep-schedule-website/src/frontend/app.rs
+++ b/aep-schedule-website/src/frontend/app.rs
@@ -31,16 +31,16 @@ pub fn App() -> impl IntoView {
                     <span class="text-2xl font-semibold leading-none font-sans tracking-tight">"Générateur d'horaire de l'AEP"
                         <span class="text-amber-600">"v2"</span>
                     </span>
-                    <A class="rounded-md font-medium text-lg font-sans tracking-tight" href="/">"Générateur d'horaire"</A>
-                    <A class="rounded-md font-medium text-lg font-sans tracking-tight" href="/local">"Horaire d'un local"</A>
-                    <A class="rounded-md font-medium text-lg font-sans tracking-tight" href="/apropos">"À propos"</A>
+                    <A class="rounded-md font-medium text-gray-700 text-lg font-sans tracking-tight" href="/">"Générateur d'horaire"</A>
+                    <A class="rounded-md font-medium text-gray-700 text-lg font-sans tracking-tight" href="/local">"Horaire d'un local"</A>
+                    <A class="rounded-md font-medium text-gray-700 text-lg font-sans tracking-tight" href="/apropos">"À propos"</A>
 
 
                     <a href="https://git.step.polymtl.ca/Lemark/aep-schedule-generator-rusty/-/issues/new" class="sources pad-left"  target="_blank">
-                        <span class="rounded-md font-medium text-lg font-sans tracking-tight">"Signaler un bug"</span>
+                        <span class="rounded-md font-medium text-gray-700 text-lg font-sans tracking-tight">"Signaler un bug"</span>
                         <Bug weight=IconWeight::Regular size="3vh"/>
                     </a>
-                    <a href="https://git.step.polymtl.ca/Lemark/aep-schedule-generator-rusty" class="sources"  target="_blank" ><span class="rounded-md font-medium text-lg font-sans tracking-tight">"Sources "</span><GitlabLogo weight=IconWeight::Regular size="3vh"/></a>
+                    <a href="https://git.step.polymtl.ca/Lemark/aep-schedule-generator-rusty" class="sources"  target="_blank" ><span class="rounded-md font-medium text-gray-700 text-lg font-sans tracking-tight">"Sources "</span><GitlabLogo weight=IconWeight::Regular size="3vh"/></a>
                 </nav>
                 <div class=move || is_active.get() + " hamburger" on:click=move |_| {
                     set_active.update(|text| {
diff --git a/aep-schedule-website/src/frontend/components/options/courses_selector.rs b/aep-schedule-website/src/frontend/components/options/courses_selector.rs
index f1d46f5..ca0c25e 100644
--- a/aep-schedule-website/src/frontend/components/options/courses_selector.rs
+++ b/aep-schedule-website/src/frontend/components/options/courses_selector.rs
@@ -150,14 +150,16 @@ pub fn CoursesSelector<F>(state: OptionState, submit: F) -> impl IntoView
 where
     F: Fn() + Copy + 'static,
 {
-    let (selections, set_selections) = state.selections;
     let (active_tab, set_active_tab) = create_signal("".to_string());
+
+    let action_courses = state.action_courses;
+
     view! {
         <Await
             future=get_courses
             let:courses
         >
-            <SearchCourse courses=courses.clone() set_selections set_active_tab submit/>
+            <SearchCourse courses=courses.clone() action_courses set_active_tab/>
         </Await>
         <div class="row-container tab-width">
             <button class="tab-button chips" class=("tab-selected", move || active_tab.get() == "") id="personal" on:click={
@@ -167,7 +169,7 @@ where
                 {"Horaire personnel"}
             </button>
             <For
-                each=selections
+                each=move || {action_courses.value().get().unwrap_or_default()}
                 key=|c| c.sigle.clone()
                 children=move |course| {
                     let sigle = course.sigle.to_string();
@@ -182,7 +184,11 @@ where
                         <button class="close" on:click={
                             let sigle = sigle.clone();
                             move |_| {
-                                set_selections.update(|courses| courses.retain(|c| c.sigle.as_str() != sigle));
+                                action_courses.value().update(|courses| {
+                                    if let Some(courses) = courses {
+                                        courses.retain(|c| c.sigle.as_str() != sigle);
+                                    }}
+                                );
                                 submit();
                             }
                         }><X weight=IconWeight::Regular size="16px"/></button>
@@ -195,7 +201,7 @@ where
             <PersonalTimeSelector week=state.week submit></PersonalTimeSelector>
         </Tab>
         <For
-            each=selections
+            each=move || {action_courses.value().get().unwrap_or_default()}
             key=|c| c.sigle.clone()
             let:course
         >
diff --git a/aep-schedule-website/src/frontend/components/options/form.rs b/aep-schedule-website/src/frontend/components/options/form.rs
index 4eee55c..d7d6efa 100644
--- a/aep-schedule-website/src/frontend/components/options/form.rs
+++ b/aep-schedule-website/src/frontend/components/options/form.rs
@@ -31,6 +31,11 @@ where
         action.dispatch((&state).into());
     };
 
+    create_local_resource(state.action_courses.pending(), move |_| {
+        submit();
+        async move {}
+    });
+
     let submit_mobile = move |_| action.dispatch((&state).into());
 
     view! {
diff --git a/aep-schedule-website/src/frontend/components/options/personal.rs b/aep-schedule-website/src/frontend/components/options/personal.rs
index 308490e..be7f6e0 100644
--- a/aep-schedule-website/src/frontend/components/options/personal.rs
+++ b/aep-schedule-website/src/frontend/components/options/personal.rs
@@ -73,13 +73,13 @@ where
                         let day = week[i].get();
                         let hour = day & (1 << j);
                         if hour != 0 {
-                            "selected-hour"
+                            "touch-none selected-hour"
                         } else {
-                            ""
+                            "touch-none"
                         }
                     };
                     view! {
-                        <div class="pointer-events-none touch-none" style=style class=class
+                        <div style=style class=class
                         on:pointerdown=move |e| {
                             set_initial.set(Some((i, j)));
                             set_positive.set((week[i].get() & (1 << j)) == 0);
diff --git a/aep-schedule-website/src/frontend/components/options/search.rs b/aep-schedule-website/src/frontend/components/options/search.rs
index ea932b6..02773d9 100644
--- a/aep-schedule-website/src/frontend/components/options/search.rs
+++ b/aep-schedule-website/src/frontend/components/options/search.rs
@@ -1,21 +1,14 @@
 use super::state::ReactiveCourse;
-use crate::{
-    backend::routes::get_course,
-    frontend::components::common::autocomplete::{AutoComplete, AutoCompleteOption},
-};
+use crate::frontend::components::common::autocomplete::{AutoComplete, AutoCompleteOption};
 use aep_schedule_generator::data::course::CourseName;
 use leptos::*;
 
 #[component]
-pub fn SearchCourse<F>(
+pub fn SearchCourse(
     courses: Result<Vec<CourseName>, ServerFnError>,
-    set_selections: WriteSignal<Vec<ReactiveCourse>>,
     set_active_tab: WriteSignal<String>,
-    submit: F,
-) -> impl IntoView
-where
-    F: Fn() + Copy + 'static,
-{
+    action_courses: Action<String, Vec<ReactiveCourse>>,
+) -> impl IntoView {
     let Ok(courses) = courses else {
         return None;
     };
@@ -24,26 +17,9 @@ where
         .map(|c| AutoCompleteOption::new(c.sigle.clone(), c.sigle + " - " + &c.name))
         .collect();
 
-    let add_course = create_action(
-        move |(sigle, set): &(String, WriteSignal<Vec<ReactiveCourse>>)| {
-            let sigle = sigle.clone();
-            let set = *set;
-            async move {
-                if let Ok(c) = get_course(sigle).await {
-                    set.update(|s| {
-                        if !s.iter().any(|react_c| react_c.sigle == c.sigle) {
-                            s.push(c.into())
-                        }
-                    });
-                    submit();
-                }
-            }
-        },
-    );
-
     let on_submit = move |sigle: String| {
         set_active_tab(sigle.clone());
-        add_course.dispatch((sigle, set_selections));
+        action_courses.dispatch(sigle);
     };
 
     Some(view! {
diff --git a/aep-schedule-website/src/frontend/components/options/state.rs b/aep-schedule-website/src/frontend/components/options/state.rs
index 23537ae..8c9989b 100644
--- a/aep-schedule-website/src/frontend/components/options/state.rs
+++ b/aep-schedule-website/src/frontend/components/options/state.rs
@@ -5,12 +5,12 @@ use aep_schedule_generator::{
 use compact_str::CompactString;
 use leptos::*;
 
+use crate::backend::routes::get_course;
+
 #[derive(Copy, Clone)]
 pub struct OptionState {
-    pub selections: (
-        ReadSignal<Vec<ReactiveCourse>>,
-        WriteSignal<Vec<ReactiveCourse>>,
-    ),
+    pub stored_courses: StoredValue<Vec<ReactiveCourse>>,
+    pub action_courses: Action<String, Vec<ReactiveCourse>>,
     pub week: [RwSignal<u64>; 5],
     pub max_nb_conflicts: RwSignal<u8>,
     pub day_off: RwSignal<u8>,
@@ -51,8 +51,27 @@ pub struct ReactiveCourse {
 
 impl Default for OptionState {
     fn default() -> Self {
+        let stored_courses: StoredValue<Vec<ReactiveCourse>> = store_value(vec![]);
+
+        let action_courses = create_action(move |sigle: &String| {
+            let sigle = sigle.clone();
+            async move {
+                if let Ok(c) = get_course(sigle).await {
+                    if !stored_courses
+                        .get_value()
+                        .iter()
+                        .any(|react_c| react_c.sigle == c.sigle)
+                    {
+                        stored_courses.update_value(|courses| courses.push(c.into()));
+                    }
+                }
+                stored_courses.get_value()
+            }
+        });
+
         Self {
-            selections: create_signal(vec![]),
+            stored_courses,
+            action_courses,
             max_nb_conflicts: create_rw_signal(0),
             week: std::array::from_fn(|_i| create_rw_signal(0)),
             day_off: create_rw_signal(3),
@@ -179,9 +198,10 @@ impl From<Course> for ReactiveCourse {
 impl From<&OptionState> for SchedulesOptions {
     fn from(state: &OptionState) -> Self {
         let courses_to_take = state
-            .selections
-            .0
+            .action_courses
+            .value()
             .get()
+            .unwrap_or_default()
             .into_iter()
             .map(|c| c.into())
             .collect();
diff --git a/aep-schedule-website/src/frontend/components/options/todo.rs b/aep-schedule-website/src/frontend/components/options/todo.rs
index 88d9f10..526f973 100644
--- a/aep-schedule-website/src/frontend/components/options/todo.rs
+++ b/aep-schedule-website/src/frontend/components/options/todo.rs
@@ -16,9 +16,9 @@ pub fn Step(
 ) -> impl IntoView {
     let bg_color = move || {
         match n.cmp(&step.get()) {
-            Ordering::Less => "flex transition-colors items-center justify-center w-10 h-10 border rounded-full bg-green-400",
-            Ordering::Greater => "flex transition-colors items-center justify-center w-10 h-10 border rounded-full",
-            Ordering::Equal => "flex transition-colors items-center justify-center w-10 h-10 border rounded-full bg-amber-400",
+            Ordering::Less => "flex transition-all items-center justify-center w-10 h-10 border rounded-full bg-green-400",
+            Ordering::Greater => "flex transition-all items-center justify-center w-10 h-10 border rounded-full bg-gray-100",
+            Ordering::Equal => "flex transition-all items-center justify-center w-10 h-10 border rounded-full bg-amber-400",
         }
     };
 
@@ -66,13 +66,6 @@ pub fn Todo(
         }
     };
 
-    let bg_color = move || {
-        match step.get().cmp(&5) {
-            Ordering::Less => "flex transition-colors items-center justify-center w-10 h-10 border rounded-full",
-            Ordering::Greater | Ordering::Equal => "flex transition-colors items-center justify-center w-10 h-10 border rounded-full bg-green-400",
-        }
-    };
-
     view! {
         <div class="px-4 py-4 mx-auto">
             <div class="grid gap-6 row-gap-10">
@@ -84,14 +77,14 @@ pub fn Todo(
                     <div class="flex items-center">
                         <div class="flex flex-col items-center mr-4">
                             <div>
-                                <div class=bg_color>
+                                <div class="flex transition-colors items-center justify-center w-10 h-10 border rounded-full" class=("bg-gray-100", move || step.get() != 5) class=("bg-green-400", move || step.get() == 5)>
                                     <svg class="w-6 text-gray-600" stroke="currentColor" viewBox="0 0 24 24">
                                         <polyline fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="6,12 10,16 18,8"></polyline>
                                     </svg>
                                 </div>
                             </div>
                         </div>
-                        <button on:click=submit class="select-none rounded-lg bg-amber-500 py-1 text-lg font-sans font-semibold px-2 w-64 self-center text-center align-middle text-black shadow-md shadow-amber-500/20 transition-all hover:shadow-lg hover:shadow-amber-500/40 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none" prop:disabled=disab disabled>"Générer les horaires"</button>
+                        <button on:click=submit class="select-none rounded-lg bg-amber-500 py-1 text-lg font-sans font-semibold px-2 w-64 self-center text-center align-middle text-black shadow-md shadow-amber-500/20 transition-all hover:shadow-lg hover:shadow-amber-500/40 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none" prop:disabled=disab>"Générer les horaires"</button>
                     </div>
                 </div>
             </div>
diff --git a/aep-schedule-website/src/frontend/components/schedules.rs b/aep-schedule-website/src/frontend/components/schedules.rs
index caf96ee..2ec6422 100644
--- a/aep-schedule-website/src/frontend/components/schedules.rs
+++ b/aep-schedule-website/src/frontend/components/schedules.rs
@@ -14,15 +14,15 @@ pub fn SchedulesComponent(
         <Await
             future=get_calendar
             children=move |calendar| {
-                match read_signal.get() {
-                    Some(result) => result.into_iter().rev().map(|schedule| {
-                    let calendar = calendar.clone().unwrap();
-                    let schedule = schedule.clone();
-                    view! {
-                        <ScheduleComponent schedule calendar/>
-                    }
-                }).collect_view(),
-                    None => view ! {
+                match (read_signal.get(), step.get() == 5) {
+                   (Some(result), true) => result.into_iter().rev().map(|schedule| {
+                                        let calendar = calendar.clone().unwrap();
+                                        let schedule = schedule.clone();
+                                        view! {
+                                            <ScheduleComponent schedule calendar/>
+                                        }
+                                    }).collect_view(),
+                    _ => view ! {
                         <Todo action step/>
                     }
                 }
diff --git a/aep-schedule-website/style/main.scss b/aep-schedule-website/style/main.scss
index 93b18b3..8ff3c65 100644
--- a/aep-schedule-website/style/main.scss
+++ b/aep-schedule-website/style/main.scss
@@ -229,6 +229,7 @@ nav.active {
 }
 
 [aria-current]:not([aria-current="false"]) {
-	font-weight: bold;
+	color: black;
 	text-decoration: underline rgb(245 158 11);
+	text-decoration-thickness: 2px;
 }
\ No newline at end of file
diff --git a/aep-schedule-website/style/options.scss b/aep-schedule-website/style/options.scss
index d92cd29..5fea310 100644
--- a/aep-schedule-website/style/options.scss
+++ b/aep-schedule-website/style/options.scss
@@ -80,9 +80,6 @@
 
 .group-text-col {
   justify-content: space-between;
-  max-height: 22px;
-  gap: 8px;
-  flex-wrap: nowrap;
 }
 
 @keyframes translateOpen {
@@ -95,11 +92,6 @@
   }
 }
 
-.group-text-col:hover {
-  max-height: fit-content;
-  animation: 0.5s ease-out 1 translateOpen;
-}
-
 .group-text {
   gap: 3px;
   width: 100%;
-- 
GitLab