Add a calendar to schedule a recipe to a chosen date (WIP)
authorGreg Burri <greg.burri@gmail.com>
Thu, 23 Jan 2025 02:01:15 +0000 (03:01 +0100)
committerGreg Burri <greg.burri@gmail.com>
Thu, 23 Jan 2025 02:01:15 +0000 (03:01 +0100)
15 files changed:
Cargo.lock
backend/Cargo.toml
backend/scss/calendar.scss [new file with mode: 0644]
backend/scss/modal-dialog.scss
backend/scss/style.scss
backend/src/translation.rs
backend/templates/calendar.html [new file with mode: 0644]
backend/templates/recipe_view.html
backend/translation.ron
frontend/Cargo.toml
frontend/src/calendar.rs [new file with mode: 0644]
frontend/src/lib.rs
frontend/src/modal_dialog.rs
frontend/src/recipe_edit.rs
frontend/src/recipe_view.rs [new file with mode: 0644]

index 5b745be..a53d261 100644 (file)
@@ -149,9 +149,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
 
 [[package]]
 name = "axum"
-version = "0.8.2"
+version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efea76243612a2436fb4074ba0cf3ba9ea29efdeb72645d8fc63f116462be1de"
+checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8"
 dependencies = [
  "axum-core",
  "axum-macros",
@@ -184,12 +184,12 @@ dependencies = [
 
 [[package]]
 name = "axum-core"
-version = "0.5.1"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eab1b0df7cded837c40dacaa2e1c33aa17c84fc3356ae67b5645f1e83190753e"
+checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733"
 dependencies = [
  "bytes",
- "futures-core",
+ "futures-util",
  "http 1.2.0",
  "http-body",
  "http-body-util",
@@ -204,9 +204,9 @@ dependencies = [
 
 [[package]]
 name = "axum-extra"
-version = "0.11.0"
+version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "543f0799d22486525744f06a3580b64f3e51d97aba73ea0e09040969c0034722"
+checksum = "460fc6f625a1f7705c6cf62d0d070794e94668988b1c38111baeec177c715f7b"
 dependencies = [
  "axum",
  "axum-core",
@@ -700,6 +700,7 @@ dependencies = [
 name = "frontend"
 version = "0.1.0"
 dependencies = [
+ "chrono",
  "common",
  "console_error_panic_hook",
  "futures",
@@ -2067,9 +2068,9 @@ checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
 
 [[package]]
 name = "rustix"
-version = "0.38.43"
+version = "0.38.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
+checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
 dependencies = [
  "bitflags",
  "errno",
index a55c0e1..cc6a499 100644 (file)
@@ -8,7 +8,7 @@ edition = "2021"
 common = { path = "../common" }
 
 axum = { version = "0.8", features = ["macros"] }
-axum-extra = { version = "0.11", features = ["cookie"] }
+axum-extra = { version = "0.10", features = ["cookie"] }
 tokio = { version = "1", features = ["full"] }
 tower = { version = "0.5", features = ["util"] }
 tower-http = { version = "0.6", features = ["fs", "trace"] }
diff --git a/backend/scss/calendar.scss b/backend/scss/calendar.scss
new file mode 100644 (file)
index 0000000..6500a85
--- /dev/null
@@ -0,0 +1,46 @@
+.calendar {
+    .month-selector {
+        width: 100%;
+        text-align: center;
+
+        .prev {
+            float: left;
+        }
+
+        .next {
+            float: right;
+        }
+
+        .month {
+            display: none;
+        }
+
+        .month.current {
+            display: inline;
+        }
+    }
+
+    ul.weekdays {
+        margin: 0;
+        padding: 20px 0;
+
+        li {
+            display: inline-block;
+            width: 14%;
+            text-align: center;
+            margin: 0;
+        }
+    }
+
+    ul.days {
+        margin: 0;
+        padding: 20px 0;
+
+        li {
+            display: inline-block;
+            width: 14%;
+            text-align: center;
+            margin: 0;
+        }
+    }
+}
\ No newline at end of file
index 7ea2fb1..597d842 100644 (file)
@@ -1,8 +1,8 @@
 #modal-dialog {
     // visibility: hidden;
     color: white;
-    max-width: 300px;
-    margin-left: -125px;
+    width: 500px;
+    margin-left: -250px;
     background-color: black;
     text-align: center;
     border-radius: 2px;
index 8c9f9b0..a1efb64 100644 (file)
@@ -1,5 +1,6 @@
 @use 'toast.scss';
 @use 'modal-dialog.scss';
+@use 'calendar.scss';
 
 $color-1: #B29B89;
 $color-2: #89B29B;
@@ -123,6 +124,10 @@ body {
             h1 {
                 text-align: center;
             }
+
+            #hidden-templates {
+                display: none;
+            }
         }
 
         #recipe-edit {
@@ -163,10 +168,6 @@ body {
                     background-color: red;
                 }
             }
-
-            #hidden-templates {
-                display: none;
-            }
         }
 
         form {
index 7fa8053..0e4383f 100644 (file)
@@ -1,4 +1,4 @@
-use std::{fs::File, sync::LazyLock};
+use std::{borrow::Borrow, fs::File, sync::LazyLock};
 
 use ron::de::from_reader;
 use serde::Deserialize;
@@ -114,6 +114,27 @@ pub enum Sentence {
     RecipeOneServing,
     RecipeSomeServings,
     RecipeEstimatedTimeMinAbbreviation,
+
+    // Calendar.
+    CalendarMondayAbbreviation,
+    CalendarTuesdayAbbreviation,
+    CalendarWednesdayAbbreviation,
+    CalendarThursdayAbbreviation,
+    CalendarFridayAbbreviation,
+    CalendarSaturdayAbbreviation,
+    CalendarSundayAbbreviation,
+    CalendarJanuary,
+    CalendarFebruary,
+    CalendarMarch,
+    CalendarApril,
+    CalendarMay,
+    CalendarJune,
+    CalendarJuly,
+    CalendarAugust,
+    CalendarSeptember,
+    CalendarOctober,
+    CalendarNovember,
+    CalendarDecember,
 }
 
 pub const DEFAULT_LANGUAGE_CODE: &str = "en";
@@ -131,7 +152,10 @@ impl Tr {
         }
     }
 
-    pub fn t(&self, sentence: Sentence) -> &'static str {
+    pub fn t<T>(&self, sentence: T) -> &'static str
+    where
+        T: Borrow<Sentence>,
+    {
         self.lang.get(sentence)
     }
 
@@ -196,10 +220,15 @@ impl Language {
         }
     }
 
-    pub fn get(&'static self, sentence: Sentence) -> &'static str {
+    pub fn get<T>(&'static self, sentence: T) -> &'static str
+    where
+        T: Borrow<Sentence>,
+    {
+        let sentence_cloned: Sentence = sentence.borrow().clone();
+
         let text: &str = self
             .translation
-            .get(sentence.clone() as usize)
+            .get(sentence_cloned as usize)
             .unwrap()
             .as_ref();
         if text.is_empty() && self.code != DEFAULT_LANGUAGE_CODE {
diff --git a/backend/templates/calendar.html b/backend/templates/calendar.html
new file mode 100644 (file)
index 0000000..5183ea5
--- /dev/null
@@ -0,0 +1,45 @@
+<div class="calendar">
+    <div class="month-selector">
+        <span class="prev">PREV</span>
+        <span class="year" ></span>
+
+        {% for month in [
+            Sentence::CalendarJanuary,
+            Sentence::CalendarFebruary,
+            Sentence::CalendarMarch,
+            Sentence::CalendarApril,
+            Sentence::CalendarMay,
+            Sentence::CalendarJune,
+            Sentence::CalendarJuly,
+            Sentence::CalendarAugust,
+            Sentence::CalendarSeptember,
+            Sentence::CalendarOctober,
+            Sentence::CalendarNovember,
+            Sentence::CalendarDecember,
+        ] %}
+            <span class="month">{{ tr.t(*month) }}</span>
+        {% endfor %}
+
+        <span class="next">NEXT</span>
+    </div>
+    <ul class="weekdays">
+        {% for day in [
+            Sentence::CalendarMondayAbbreviation,
+            Sentence::CalendarTuesdayAbbreviation,
+            Sentence::CalendarWednesdayAbbreviation,
+            Sentence::CalendarThursdayAbbreviation,
+            Sentence::CalendarFridayAbbreviation,
+            Sentence::CalendarSaturdayAbbreviation,
+            Sentence::CalendarSundayAbbreviation,
+        ] %}
+            <li class="weekday">{{ tr.t(*day) }}</li>
+        {% endfor %}
+
+    <ul class="days">
+        {% for i in 0..7 %}
+            {% for j in 0..5 %}
+                <li id="day-{{i}}{{j}}"></li>
+            {% endfor %}
+        {% endfor %}
+    </ul>
+</div>
\ No newline at end of file
index 989735e..3a2d487 100644 (file)
@@ -5,8 +5,11 @@
 <div class="content" id="recipe-view">
     <h2 class="recipe-title" >{{ recipe.title }}</h2>
 
-    {% if user.is_some() && crate::data::model::can_user_edit_recipe(&user.as_ref().unwrap(), &recipe) %}
-        <a class="edit-recipe" href="/recipe/edit/{{ recipe.id }}" >Edit</a>
+    {% if let Some(user) = user %}
+        {% if crate::data::model::can_user_edit_recipe(user, recipe) %}
+            <a class="edit-recipe" href="/recipe/edit/{{ recipe.id }}" >Edit</a>
+        {% endif %}
+        <span class="add-to-planner">Add to planner</span>
     {% endif %}
 
     <div class="tags">
@@ -27,7 +30,6 @@
     {% else %}
     {% endmatch %}
 
-
     {% match recipe.estimated_time %}
     {% when Some(time) %}
         {{ time +}} {{+ tr.t(Sentence::RecipeEstimatedTimeMinAbbreviation) }}
         </div>
     </div>
     {% endfor %}
+
+    <div id="hidden-templates">
+        {% include "calendar.html" %}
+    </div>
 </div>
 
 {% endblock %}
\ No newline at end of file
index 835f018..168d8de 100644 (file)
             (RecipeOneServing, "1 serving"),
             (RecipeSomeServings, "{} servings"),
             (RecipeEstimatedTimeMinAbbreviation, "min"),
+
+            (CalendarMondayAbbreviation, "Mon"),
+            (CalendarTuesdayAbbreviation, "Tue"),
+            (CalendarWednesdayAbbreviation, "Wed"),
+            (CalendarThursdayAbbreviation, "Thu"),
+            (CalendarFridayAbbreviation, "Fri"),
+            (CalendarSaturdayAbbreviation, "Sat"),
+            (CalendarSundayAbbreviation, "Sun"),
+            (CalendarJanuary, "January"),
+            (CalendarFebruary, "February"),
+            (CalendarMarch, "March"),
+            (CalendarApril, "April"),
+            (CalendarMay, "May"),
+            (CalendarJune, "June"),
+            (CalendarJuly, "July"),
+            (CalendarAugust, "August"),
+            (CalendarSeptember, "September"),
+            (CalendarOctober, "October"),
+            (CalendarNovember, "November"),
+            (CalendarDecember, "December"),
         ]
     ),
     (
             (RecipeOneServing, "pour 1 personne"),
             (RecipeSomeServings, "pour {} personnes"),
             (RecipeEstimatedTimeMinAbbreviation, "min"),
+
+            (CalendarMondayAbbreviation, "Lun"),
+            (CalendarTuesdayAbbreviation, "Mar"),
+            (CalendarWednesdayAbbreviation, "Mer"),
+            (CalendarThursdayAbbreviation, "Jeu"),
+            (CalendarFridayAbbreviation, "Ven"),
+            (CalendarSaturdayAbbreviation, "Sam"),
+            (CalendarSundayAbbreviation, "Dim"),
+            (CalendarJanuary, "Janvier"),
+            (CalendarFebruary, "Février"),
+            (CalendarMarch, "Mars"),
+            (CalendarApril, "Avril"),
+            (CalendarMay, "Mai"),
+            (CalendarJune, "Juin"),
+            (CalendarJuly, "Juillet"),
+            (CalendarAugust, "Août"),
+            (CalendarSeptember, "Septembre"),
+            (CalendarOctober, "Octobre"),
+            (CalendarNovember, "Novembre"),
+            (CalendarDecember, "Décembre"),
         ]
     )
 ]
\ No newline at end of file
index 7c9f8b8..4214b2f 100644 (file)
@@ -13,6 +13,8 @@ default = ["console_error_panic_hook"]
 [dependencies]
 common = { path = "../common" }
 
+chrono = "0.4"
+
 ron = "0.8"
 serde = { version = "1.0", features = ["derive"] }
 thiserror = "2"
diff --git a/frontend/src/calendar.rs b/frontend/src/calendar.rs
new file mode 100644 (file)
index 0000000..7ace6d3
--- /dev/null
@@ -0,0 +1,121 @@
+use std::{
+    ops::{AddAssign, SubAssign},
+    sync::{
+        atomic::{AtomicI32, AtomicU32, Ordering},
+        Arc,
+    },
+};
+
+use chrono::{offset::Local, Datelike, Days, NaiveDate, Weekday};
+use gloo::{console::log, events::EventListener};
+use wasm_bindgen::prelude::*;
+use wasm_bindgen_futures::spawn_local;
+use web_sys::Element;
+
+use crate::utils::{by_id, SelectorExt};
+
+pub fn setup(calendar: &Element) {
+    let prev: Element = calendar.selector(".prev");
+    let next: Element = calendar.selector(".next");
+
+    let current_month = Arc::new(AtomicU32::new(Local::now().month()));
+    let current_year = Arc::new(AtomicI32::new(Local::now().year()));
+
+    display_month(calendar, Local::now().year(), Local::now().month());
+
+    let calendar_clone = calendar.clone();
+    let current_month_clone = current_month.clone();
+    let current_year_clone = current_year.clone();
+    EventListener::new(&prev, "click", move |_event| {
+        let mut m = current_month_clone.load(Ordering::Relaxed) - 1;
+        if m == 0 {
+            current_year_clone.fetch_sub(1, Ordering::Relaxed);
+            m = 12
+        }
+        current_month_clone.store(m, Ordering::Relaxed);
+        display_month(
+            &calendar_clone,
+            current_year_clone.load(Ordering::Relaxed),
+            m,
+        );
+    })
+    .forget();
+
+    let calendar_clone = calendar.clone();
+    let current_month_clone = current_month.clone();
+    let current_year_clone = current_year.clone();
+    EventListener::new(&next, "click", move |_event| {
+        let mut m = current_month_clone.load(Ordering::Relaxed) + 1;
+        if m == 13 {
+            current_year_clone.fetch_add(1, Ordering::Relaxed);
+            m = 1
+        }
+        current_month_clone.store(m, Ordering::Relaxed);
+        display_month(
+            &calendar_clone,
+            current_year_clone.load(Ordering::Relaxed),
+            m,
+        );
+    })
+    .forget();
+
+    // now.weekday()
+
+    // console!(now.to_string());
+}
+
+// fn translate_month(month: u32) -> &'static str {
+//     match
+// }
+
+fn display_month(calendar: &Element, year: i32, month: u32) {
+    log!(year, month);
+
+    calendar
+        .selector::<Element>(".year")
+        .set_inner_html(&year.to_string());
+
+    for (i, m) in calendar
+        .selector_all::<Element>(".month")
+        .into_iter()
+        .enumerate()
+    {
+        if i as u32 + 1 == month {
+            m.set_class_name("month current");
+        } else {
+            m.set_class_name("month");
+        }
+    }
+
+    // calendar
+    //     .selector::<Element>(".month")
+    //     .set_inner_html(&month.to_string());
+
+    let mut current = NaiveDate::from_ymd_opt(year, month, 1).unwrap();
+
+    // let mut day = Local:: ;
+    while (current - Days::new(1)).month() == month {
+        current = current - Days::new(1);
+    }
+
+    while current.weekday() != Weekday::Mon {
+        current = current - Days::new(1);
+    }
+
+    for i in 0..7 {
+        for j in 0..5 {
+            let li: Element = by_id(&format!("day-{}{}", i, j));
+            li.set_inner_html(&current.day().to_string());
+
+            if current == Local::now().date_naive() {
+                li.set_class_name("current-month today");
+            } else if current.month() == month {
+                li.set_class_name("current-month");
+            } else {
+                li.set_class_name("");
+            }
+
+            current = current + Days::new(1);
+        }
+    }
+}
index d114c56..588be31 100644 (file)
@@ -1,6 +1,8 @@
+mod calendar;
 mod modal_dialog;
 mod on_click;
 mod recipe_edit;
+mod recipe_view;
 mod request;
 mod toast;
 mod utils;
@@ -20,16 +22,24 @@ pub fn main() -> Result<(), JsValue> {
     let location = window().location().pathname()?;
     let path: Vec<&str> = location.split('/').skip(1).collect();
 
-    if let ["recipe", "edit", id] = path[..] {
-        let id = id.parse::<i64>().unwrap(); // TODO: remove unwrap.
-        if let Err(error) = recipe_edit::setup_page(id) {
-            log!(error);
+    // if let ["recipe", "edit", id] = path[..] {
+    match path[..] {
+        ["recipe", "edit", id] => {
+            let id = id.parse::<i64>().unwrap(); // TODO: remove unwrap.
+            if let Err(error) = recipe_edit::setup_page(id) {
+                log!(error);
+            }
         }
-
-        // Disable: user editing data are now submitted as classic form data.
-        // ["user", "edit"] => {
-        //     handles::user_edit(document)?;
-        // }
+        ["recipe", "view", id] => {
+            let id = id.parse::<i64>().unwrap(); // TODO: remove unwrap.
+            if let Err(error) = recipe_view::setup_page(id) {
+                log!(error);
+            }
+        }
+        _ => (), // Disable: user editing data are now submitted as classic form data.
+                 // ["user", "edit"] => {
+                 //     handles::user_edit(document)?;
+                 // }
     }
 
     let select_language: HtmlSelectElement = by_id("select-website-language");
index a42a33a..ac83ab9 100644 (file)
@@ -1,16 +1,39 @@
 use futures::{future::FutureExt, pin_mut, select};
 use web_sys::{Element, HtmlDialogElement};
 
-use crate::utils::{by_id, SelectorExt};
-
-use crate::on_click;
+use crate::{
+    on_click,
+    utils::{by_id, selector_and_clone, SelectorExt},
+};
+
+pub enum DialogContent<'a, T>
+where
+    T: Fn(&Element),
+{
+    Text(&'a str),
+    CloneFromElement(&'a str, T),
+}
 
-pub async fn show(message: &str) -> bool {
+pub async fn show<T>(content: DialogContent<'_, T>) -> bool
+where
+    T: Fn(&Element),
+{
     let dialog: HtmlDialogElement = by_id("modal-dialog");
+
     let input_ok: Element = dialog.selector(".ok");
     let input_cancel: Element = dialog.selector(".cancel");
 
-    dialog.selector::<Element>(".content").set_inner_html(message);
+    let content_element = dialog.selector::<Element>(".content");
+
+    match content {
+        DialogContent::Text(message) => content_element.set_inner_html(message),
+        DialogContent::CloneFromElement(element_selector, initilizer) => {
+            let element: Element = selector_and_clone(element_selector);
+            content_element.set_inner_html("");
+            content_element.append_child(&element).unwrap();
+            initilizer(&element);
+        }
+    }
 
     dialog.show_modal().unwrap();
 
index 5fe5d13..d1a4e4c 100644 (file)
@@ -20,22 +20,6 @@ use crate::{
     utils::{by_id, selector, selector_and_clone, SelectorExt},
 };
 
-async fn reload_recipes_list(current_recipe_id: i64) {
-    match Request::get("/fragments/recipes_list")
-        .query([("current_recipe_id", current_recipe_id.to_string())])
-        .send()
-        .await
-    {
-        Err(error) => {
-            toast::show(Level::Info, &format!("Internal server error: {}", error));
-        }
-        Ok(response) => {
-            let list = document().get_element_by_id("recipes-list").unwrap();
-            list.set_outer_html(&response.text().await.unwrap());
-        }
-    }
-}
-
 pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> {
     // Title.
     {
@@ -266,10 +250,10 @@ pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> {
     EventListener::new(&delete_button, "click", move |_event| {
         let title: HtmlInputElement = by_id("input-title");
         spawn_local(async move {
-            if modal_dialog::show(&format!(
+            if modal_dialog::show(modal_dialog::DialogContent::<fn(&Element)>::Text(&format!(
                 "Are you sure to delete the recipe '{}'",
                 title.value()
-            ))
+            )))
             .await
             {
                 let body = ron_api::Id { id: recipe_id };
@@ -314,7 +298,7 @@ pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> {
     // Add a new group.
     {
         let button_add_group: HtmlInputElement = by_id("input-add-group");
-        let on_click_add_group = EventListener::new(&button_add_group, "click", move |_event| {
+        EventListener::new(&button_add_group, "click", move |_event| {
             let body = ron_api::Id { id: recipe_id };
             spawn_local(async move {
                 let response: ron_api::Id = request::post("recipe/add_group", body).await.unwrap();
@@ -325,8 +309,8 @@ pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> {
                     steps: vec![],
                 });
             });
-        });
-        on_click_add_group.forget();
+        })
+        .forget();
     }
 
     Ok(())
@@ -397,7 +381,12 @@ fn create_group_element(group: &ron_api::Group) -> Element {
             .selector::<HtmlInputElement>(".input-group-name")
             .value();
         spawn_local(async move {
-            if modal_dialog::show(&format!("Are you sure to delete the group '{}'", name)).await {
+            if modal_dialog::show(modal_dialog::DialogContent::<fn(&Element)>::Text(&format!(
+                "Are you sure to delete the group '{}'",
+                name
+            )))
+            .await
+            {
                 let body = ron_api::Id { id: group_id };
                 let _ = request::delete::<(), _>("recipe/remove_group", body).await;
                 let group_element = by_id::<Element>(&format!("group-{}", group_id));
@@ -530,7 +519,12 @@ fn create_step_element(group_element: &Element, step: &ron_api::Step) -> Element
             .selector::<HtmlTextAreaElement>(".text-area-step-action")
             .value();
         spawn_local(async move {
-            if modal_dialog::show(&format!("Are you sure to delete the step '{}'", action)).await {
+            if modal_dialog::show(modal_dialog::DialogContent::<fn(&Element)>::Text(&format!(
+                "Are you sure to delete the step '{}'",
+                action
+            )))
+            .await
+            {
                 let body = ron_api::Id { id: step_id };
                 let _ = request::delete::<(), _>("recipe/remove_step", body).await;
                 let step_element = by_id::<Element>(&format!("step-{}", step_id));
@@ -675,12 +669,17 @@ fn create_ingredient_element(step_element: &Element, ingredient: &ron_api::Ingre
             .selector::<HtmlInputElement>(".input-ingredient-name")
             .value();
         spawn_local(async move {
-            if modal_dialog::show(&format!("Are you sure to delete the ingredient '{}'", name))
-                .await
+            if modal_dialog::show(modal_dialog::DialogContent::<fn(&Element)>::Text(&format!(
+                "Are you sure to delete the ingredient '{}'",
+                name
+            )))
+            .await
             {
                 let body = ron_api::Id { id: ingredient_id };
                 let _ = request::delete::<(), _>("recipe/remove_ingredient", body).await;
-                by_id::<Element>(&format!("ingredient-{}", ingredient_id)).remove();
+                let ingredient_element = by_id::<Element>(&format!("ingredient-{}", ingredient_id));
+                ingredient_element.next_element_sibling().unwrap().remove();
+                ingredient_element.remove();
             }
         });
     })
@@ -689,6 +688,22 @@ fn create_ingredient_element(step_element: &Element, ingredient: &ron_api::Ingre
     ingredient_element
 }
 
+async fn reload_recipes_list(current_recipe_id: i64) {
+    match Request::get("/fragments/recipes_list")
+        .query([("current_recipe_id", current_recipe_id.to_string())])
+        .send()
+        .await
+    {
+        Err(error) => {
+            toast::show(Level::Info, &format!("Internal server error: {}", error));
+        }
+        Ok(response) => {
+            let list = document().get_element_by_id("recipes-list").unwrap();
+            list.set_outer_html(&response.text().await.unwrap());
+        }
+    }
+}
+
 enum CursorPosition {
     UpperPart,
     LowerPart,
diff --git a/frontend/src/recipe_view.rs b/frontend/src/recipe_view.rs
new file mode 100644 (file)
index 0000000..488e459
--- /dev/null
@@ -0,0 +1,40 @@
+use gloo::{
+    console::console,
+    events::EventListener,
+    net::http::Request,
+    utils::{document, window},
+};
+use wasm_bindgen::prelude::*;
+use wasm_bindgen_futures::spawn_local;
+use web_sys::{
+    DragEvent, Element, HtmlDivElement, HtmlInputElement, HtmlSelectElement, HtmlTextAreaElement,
+    KeyboardEvent,
+};
+
+use common::ron_api;
+
+use crate::{
+    calendar, modal_dialog, request,
+    toast::{self, Level},
+    utils::{by_id, selector, selector_and_clone, SelectorExt},
+};
+
+pub fn setup_page(recipe_id: i64) -> Result<(), JsValue> {
+    let add_to_planner: Element = selector("#recipe-view .add-to-planner");
+    EventListener::new(&add_to_planner, "click", move |_event| {
+        // console!("CLICK".to_string());
+        spawn_local(async move {
+            modal_dialog::show(modal_dialog::DialogContent::CloneFromElement(
+                "#hidden-templates .calendar",
+                |element| {
+                    // console!("SETUP...".to_string());
+                    calendar::setup(element);
+                },
+            ))
+            .await;
+        });
+    })
+    .forget();
+
+    Ok(())
+}