Ingredients can now be remove from shopping list when a recipe is unscheduled.
authorGreg Burri <greg.burri@gmail.com>
Wed, 12 Feb 2025 01:05:38 +0000 (02:05 +0100)
committerGreg Burri <greg.burri@gmail.com>
Wed, 12 Feb 2025 01:05:38 +0000 (02:05 +0100)
backend/src/data/db/recipe.rs
backend/src/services/ron.rs
backend/src/translation.rs
backend/templates/calendar.html
backend/translation.ron
common/src/ron_api.rs
frontend/src/calendar.rs

index 5000dc3..0e07673 100644 (file)
@@ -795,7 +795,7 @@ VALUES ($1, $2)
         recipe_id: i64,
         date: NaiveDate,
         servings: u32,
-        add_ingredients_element: bool,
+        add_ingredients_to_shopping_list: bool,
     ) -> Result<AddScheduledRecipeResult> {
         let mut tx = self.tx().await?;
 
@@ -823,7 +823,7 @@ VALUES ($1, $2, $3, $4)
             }
             Ok(insert_result) => {
 
-                if add_ingredients_element {
+                if add_ingredients_to_shopping_list {
                     sqlx::query(
                         r#"
 INSERT INTO [ShoppingEntry] ([ingredient_id], [user_id], [recipe_scheduled_id], [servings])
@@ -853,20 +853,42 @@ INSERT INTO [ShoppingEntry] ([ingredient_id], [user_id], [recipe_scheduled_id],
         user_id: i64,
         recipe_id: i64,
         date: NaiveDate,
+        remove_ingredients_from_shopping_list: bool,
     ) -> Result<()> {
+        let mut tx = self.tx().await?;
+
+        if remove_ingredients_from_shopping_list {
+            sqlx::query(
+                r#"
+DELETE FROM [ShoppingEntry]
+WHERE [recipe_scheduled_id] IN (
+    SELECT [id] FROM [RecipeScheduled]
+    WHERE [user_id] = $1 AND [recipe_id] = $2 AND [date] = $3
+)
+                "#,
+            )
+            .bind(user_id)
+            .bind(recipe_id)
+            .bind(date)
+            .execute(&mut *tx)
+            .await?;
+        }
+
         sqlx::query(
             r#"
-    DELETE FROM [RecipeScheduled]
-    WHERE [user_id] = $1 AND [recipe_id] = $2 AND [date] = $3
+DELETE FROM [RecipeScheduled]
+WHERE [user_id] = $1 AND [recipe_id] = $2 AND [date] = $3
                 "#,
         )
         .bind(user_id)
         .bind(recipe_id)
         .bind(date)
-        .execute(&self.pool)
-        .await
-        .map(|_| ())
-        .map_err(DBError::from)
+        .execute(&mut *tx)
+        .await?;
+
+        tx.commit().await?;
+
+        Ok(())
     }
 
     pub async fn get_scheduled_recipes(
index 55f2266..7090ddb 100644 (file)
@@ -681,12 +681,17 @@ pub async fn schedule_recipe(
 pub async fn rm_scheduled_recipe(
     State(connection): State<db::Connection>,
     Extension(user): Extension<Option<model::User>>,
-    ExtractRon(ron): ExtractRon<common::ron_api::ScheduledRecipe>,
+    ExtractRon(ron): ExtractRon<common::ron_api::RemoveScheduledRecipe>,
 ) -> Result<impl IntoResponse> {
     check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
     if let Some(user) = user {
         connection
-            .rm_scheduled_recipe(user.id, ron.recipe_id, ron.date)
+            .rm_scheduled_recipe(
+                user.id,
+                ron.recipe_id,
+                ron.date,
+                ron.remove_ingredients_from_shopping_list,
+            )
             .await?;
     }
     Ok(StatusCode::OK)
index 8e89ca2..884b79b 100644 (file)
@@ -146,6 +146,7 @@ pub enum Sentence {
     CalendarAddToPlannerAlreadyExists,
     CalendarDateFormat, // See https://docs.rs/chrono/latest/chrono/format/strftime/index.html.
     CalendarAddIngredientsToShoppingList,
+    CalendarRemoveIngredientsFromShoppingList,
     CalendarUnscheduleConfirmation,
 }
 
index ac60471..d6a10b3 100644 (file)
         <div class="scheduled-recipe-with-link-and-remove"><a></a><span class="remove-scheduled-recipe">X</span></div>
         <div class="scheduled-recipe"></div>
 
-        <span class="unschedule-confirmation">{{ tr.t(Sentence::CalendarUnscheduleConfirmation) }}</span>
+        <div class="unschedule-confirmation">
+            <div>{{ tr.t(Sentence::CalendarUnscheduleConfirmation) }}</div>
+            <input
+                id="input-remove-ingredients-from-shopping-list"
+                type="checkbox"
+                checked
+            />
+            <label for="input-remove-ingredients-from-shopping-list">
+                {{ tr.t(Sentence::CalendarRemoveIngredientsFromShoppingList) }}
+            </label>
+        </div>
+
         <span class="calendar-date-format">{{ tr.t(Sentence::CalendarDateFormat) }}</span>
     </div>
 </div>
\ No newline at end of file
index fa8a54e..53e68e0 100644 (file)
             (CalendarAddToPlannerAlreadyExists, "Recipe {title} has already been scheduled for {date}"),
             (CalendarDateFormat, "%A, %-d %B, %C%y"), // See https://docs.rs/chrono/latest/chrono/format/strftime/index.html.
             (CalendarAddIngredientsToShoppingList, "Add ingredients to shopping list"),
+            (CalendarRemoveIngredientsFromShoppingList, "Remove ingredients from shopping list"),
             (CalendarUnscheduleConfirmation, "Are you sure to remove {title} on {date}"),
         ]
     ),
             (CalendarAddToPlannerAlreadyExists, "La recette {title} a été déjà été agendée pour le {date}"),
             (CalendarDateFormat, "%A %-d %B %C%y"), // See https://docs.rs/chrono/latest/chrono/format/strftime/index.html.
             (CalendarAddIngredientsToShoppingList, "Ajouter les ingrédients à la liste de course"),
+            (CalendarRemoveIngredientsFromShoppingList, "Enlever les ingrédients de la liste de course"),
             (CalendarUnscheduleConfirmation, "Êtes-vous sûr de vouloir enlever {title} du {date}"),
         ]
     )
index 379073c..0e308ce 100644 (file)
@@ -204,9 +204,10 @@ pub enum ScheduleRecipeResult {
 }
 
 #[derive(Serialize, Deserialize, Clone, Debug)]
-pub struct ScheduledRecipe {
+pub struct RemoveScheduledRecipe {
     pub recipe_id: i64,
     pub date: NaiveDate,
+    pub remove_ingredients_from_shopping_list: bool,
 }
 
 /*** Shopping list ***/
index ef942be..97a00dc 100644 (file)
@@ -2,11 +2,15 @@ use std::{cell::RefCell, rc::Rc};
 
 use chrono::{offset::Local, Datelike, Days, Months, NaiveDate, Weekday};
 use common::{ron_api, utils::substitute_with_names};
-use gloo::{console::log, events::EventListener, utils::document};
+use gloo::{
+    console::log,
+    events::EventListener,
+    utils::{document, window},
+};
 use scanf::sscanf;
 use wasm_bindgen::prelude::*;
 use wasm_bindgen_futures::spawn_local;
-use web_sys::Element;
+use web_sys::{Element, HtmlInputElement};
 
 use crate::{
     modal_dialog,
@@ -140,31 +144,41 @@ pub fn setup(
 
                 let title = target.previous_element_sibling().unwrap().inner_html();
 
-                if modal_dialog::show_and_initialize(
-                    "#hidden-templates-calendar .unschedule-confirmation",
-                    async |element| {
-                        let date_format =
-                            selector::<Element>("#hidden-templates-calendar .calendar-date-format")
-                                .inner_html();
-                        element.set_inner_html(&substitute_with_names(
-                            &element.inner_html(),
-                            &["{title}", "{date}"],
-                            &[
-                                &title,
-                                &date
-                                    .format_localized(&date_format, get_locale())
-                                    .to_string(),
-                            ],
-                        ));
-                    },
-                )
-                .await
-                .is_some()
+                if let Some(remove_ingredients_from_shopping_list) =
+                    modal_dialog::show_and_initialize_with_ok(
+                        "#hidden-templates-calendar .unschedule-confirmation",
+                        async |element| {
+                            let date_format = selector::<Element>(
+                                "#hidden-templates-calendar .calendar-date-format",
+                            )
+                            .inner_html();
+                            element.set_inner_html(&substitute_with_names(
+                                &element.inner_html(),
+                                &["{title}", "{date}"],
+                                &[
+                                    &title,
+                                    &date
+                                        .format_localized(&date_format, get_locale())
+                                        .to_string(),
+                                ],
+                            ));
+                        },
+                        |element, _| {
+                            let remove_ingredients_element: HtmlInputElement =
+                                element.selector("#input-remove-ingredients-from-shopping-list");
+                            remove_ingredients_element.checked()
+                        },
+                    )
+                    .await
                 {
-                    let body = ron_api::ScheduledRecipe { recipe_id, date };
+                    let body = ron_api::RemoveScheduledRecipe {
+                        recipe_id,
+                        date,
+                        remove_ingredients_from_shopping_list,
+                    };
                     let _ =
                         request::delete::<(), _>("calendar/remove_scheduled_recipe", body).await;
-                    target.parent_element().unwrap().remove();
+                    window().location().reload().unwrap();
                 }
             });
         }