Ingredients can now be added to shopping list when a recipe is scheduled
This commit is contained in:
parent
15173c4842
commit
ce3821b94e
12 changed files with 98 additions and 39 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
|
@ -2604,15 +2604,15 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum"
|
name = "strum"
|
||||||
version = "0.26.3"
|
version = "0.27.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
checksum = "ce1475c515a4f03a8a7129bb5228b81a781a86cb0b3fbbc19e1c556d491a401f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum_macros"
|
name = "strum_macros"
|
||||||
version = "0.26.4"
|
version = "0.27.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
checksum = "9688894b43459159c82bfa5a5fa0435c19cbe3c9b427fa1dd7b1ce0c279b18a7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ rinja = { version = "0.3" }
|
||||||
argon2 = { version = "0.5", features = ["default", "std"] }
|
argon2 = { version = "0.5", features = ["default", "std"] }
|
||||||
rand_core = { version = "0.9", features = ["std"] }
|
rand_core = { version = "0.9", features = ["std"] }
|
||||||
rand = "0.9"
|
rand = "0.9"
|
||||||
strum = "0.26"
|
strum = "0.27"
|
||||||
strum_macros = "0.26"
|
strum_macros = "0.27"
|
||||||
|
|
||||||
lettre = { version = "0.11", default-features = false, features = [
|
lettre = { version = "0.11", default-features = false, features = [
|
||||||
"smtp-transport",
|
"smtp-transport",
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,8 @@ CREATE TABLE [ShoppingEntry] (
|
||||||
-- In both cases [name], [quantity_value] and [quantity_unit] are used to display
|
-- In both cases [name], [quantity_value] and [quantity_unit] are used to display
|
||||||
-- the entry instead of [Ingredient] data.
|
-- the entry instead of [Ingredient] data.
|
||||||
[ingredient_id] INTEGER,
|
[ingredient_id] INTEGER,
|
||||||
|
[recipe_scheduled_id] INTEGER, -- Can be null when manually added.
|
||||||
|
|
||||||
[is_checked] INTEGER NOT NULL DEFAULT FALSE,
|
[is_checked] INTEGER NOT NULL DEFAULT FALSE,
|
||||||
|
|
||||||
[name] TEXT NOT NULL DEFAULT '',
|
[name] TEXT NOT NULL DEFAULT '',
|
||||||
|
|
@ -194,7 +196,8 @@ CREATE TABLE [ShoppingEntry] (
|
||||||
[servings] INTEGER,
|
[servings] INTEGER,
|
||||||
|
|
||||||
FOREIGN KEY([user_id]) REFERENCES [User]([id]) ON DELETE CASCADE,
|
FOREIGN KEY([user_id]) REFERENCES [User]([id]) ON DELETE CASCADE,
|
||||||
FOREIGN KEY([ingredient_id]) REFERENCES [Ingredient]([id]) ON DELETE SET NULL
|
FOREIGN KEY([ingredient_id]) REFERENCES [Ingredient]([id]) ON DELETE SET NULL,
|
||||||
|
FOREIGN KEY([recipe_scheduled_id]) REFERENCES [RecipeScheduled]([id]) ON DELETE SET NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
-- When an ingredient is deleted, its values are copied to any shopping entry
|
-- When an ingredient is deleted, its values are copied to any shopping entry
|
||||||
|
|
|
||||||
|
|
@ -795,7 +795,10 @@ VALUES ($1, $2)
|
||||||
recipe_id: i64,
|
recipe_id: i64,
|
||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
servings: u32,
|
servings: u32,
|
||||||
|
add_ingredients_element: bool,
|
||||||
) -> Result<AddScheduledRecipeResult> {
|
) -> Result<AddScheduledRecipeResult> {
|
||||||
|
let mut tx = self.tx().await?;
|
||||||
|
|
||||||
match sqlx::query(
|
match sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO [RecipeScheduled] (user_id, recipe_id, date, servings)
|
INSERT INTO [RecipeScheduled] (user_id, recipe_id, date, servings)
|
||||||
|
|
@ -806,7 +809,7 @@ VALUES ($1, $2, $3, $4)
|
||||||
.bind(recipe_id)
|
.bind(recipe_id)
|
||||||
.bind(date)
|
.bind(date)
|
||||||
.bind(servings)
|
.bind(servings)
|
||||||
.execute(&self.pool)
|
.execute(&mut *tx)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Err(Error::Database(error))
|
Err(Error::Database(error))
|
||||||
|
|
@ -815,7 +818,33 @@ VALUES ($1, $2, $3, $4)
|
||||||
{
|
{
|
||||||
Ok(AddScheduledRecipeResult::RecipeAlreadyScheduledAtThisDate)
|
Ok(AddScheduledRecipeResult::RecipeAlreadyScheduledAtThisDate)
|
||||||
}
|
}
|
||||||
_ => Ok(AddScheduledRecipeResult::Ok),
|
Err(error) => {
|
||||||
|
Err(DBError::from(error))
|
||||||
|
}
|
||||||
|
Ok(insert_result) => {
|
||||||
|
|
||||||
|
if add_ingredients_element {
|
||||||
|
sqlx::query(
|
||||||
|
r#"
|
||||||
|
INSERT INTO [ShoppingEntry] ([ingredient_id], [user_id], [recipe_scheduled_id], [servings])
|
||||||
|
SELECT [Ingredient].[id], $2, $3, $4 FROM [Ingredient]
|
||||||
|
INNER JOIN [Step] ON [Step].[id] = [Ingredient].[step_id]
|
||||||
|
INNER JOIN [Group] ON [Group].[id] = [Step].[group_id]
|
||||||
|
INNER JOIN [Recipe] ON [Recipe].[id] = [Group].[recipe_id]
|
||||||
|
WHERE [Recipe].[id] = $1
|
||||||
|
"#)
|
||||||
|
.bind(recipe_id)
|
||||||
|
.bind(user_id)
|
||||||
|
.bind(insert_result.last_insert_rowid())
|
||||||
|
.bind(servings)
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
|
Ok(AddScheduledRecipeResult::Ok)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1009,13 +1038,13 @@ VALUES
|
||||||
let tomorrow = today + Days::new(1);
|
let tomorrow = today + Days::new(1);
|
||||||
|
|
||||||
connection
|
connection
|
||||||
.add_scheduled_recipe(user_id, recipe_id_1, today, 4)
|
.add_scheduled_recipe(user_id, recipe_id_1, today, 4, false)
|
||||||
.await?;
|
.await?;
|
||||||
connection
|
connection
|
||||||
.add_scheduled_recipe(user_id, recipe_id_2, yesterday, 4)
|
.add_scheduled_recipe(user_id, recipe_id_2, yesterday, 4, false)
|
||||||
.await?;
|
.await?;
|
||||||
connection
|
connection
|
||||||
.add_scheduled_recipe(user_id, recipe_id_1, tomorrow, 4)
|
.add_scheduled_recipe(user_id, recipe_id_1, tomorrow, 4, false)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -1054,7 +1083,7 @@ VALUES
|
||||||
|
|
||||||
// Recipe scheduled at the same date is forbidden.
|
// Recipe scheduled at the same date is forbidden.
|
||||||
let Ok(AddScheduledRecipeResult::RecipeAlreadyScheduledAtThisDate) = connection
|
let Ok(AddScheduledRecipeResult::RecipeAlreadyScheduledAtThisDate) = connection
|
||||||
.add_scheduled_recipe(user_id, recipe_id_1, today, 4)
|
.add_scheduled_recipe(user_id, recipe_id_1, today, 4, false)
|
||||||
.await
|
.await
|
||||||
else {
|
else {
|
||||||
panic!("DBError::RecipeAlreadyScheduledAtThisDate must be returned");
|
panic!("DBError::RecipeAlreadyScheduledAtThisDate must be returned");
|
||||||
|
|
|
||||||
|
|
@ -660,7 +660,13 @@ pub async fn schedule_recipe(
|
||||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||||
if let Some(user) = user {
|
if let Some(user) = user {
|
||||||
connection
|
connection
|
||||||
.add_scheduled_recipe(user.id, ron.recipe_id, ron.date, ron.servings)
|
.add_scheduled_recipe(
|
||||||
|
user.id,
|
||||||
|
ron.recipe_id,
|
||||||
|
ron.date,
|
||||||
|
ron.servings,
|
||||||
|
ron.add_ingredients_to_shopping_list,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map(|res| {
|
.map(|res| {
|
||||||
ron_response_ok(common::ron_api::ScheduleRecipeResult::from(res)).into_response()
|
ron_response_ok(common::ron_api::ScheduleRecipeResult::from(res)).into_response()
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,7 @@ pub enum Sentence {
|
||||||
CalendarAddToPlannerSuccess,
|
CalendarAddToPlannerSuccess,
|
||||||
CalendarAddToPlannerAlreadyExists,
|
CalendarAddToPlannerAlreadyExists,
|
||||||
CalendarDateFormat, // See https://docs.rs/chrono/latest/chrono/format/strftime/index.html.
|
CalendarDateFormat, // See https://docs.rs/chrono/latest/chrono/format/strftime/index.html.
|
||||||
|
CalendarAddIngredientsToShoppingList,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const DEFAULT_LANGUAGE_CODE: &str = "en";
|
pub const DEFAULT_LANGUAGE_CODE: &str = "en";
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@
|
||||||
{# To create a modal dialog to choose a date and and servings #}
|
{# To create a modal dialog to choose a date and and servings #}
|
||||||
<div class="date-and-servings" >
|
<div class="date-and-servings" >
|
||||||
{% include "calendar.html" %}
|
{% include "calendar.html" %}
|
||||||
|
|
||||||
<label for="input-servings">{{ tr.t(Sentence::RecipeServings) }}</label>
|
<label for="input-servings">{{ tr.t(Sentence::RecipeServings) }}</label>
|
||||||
<input
|
<input
|
||||||
id="input-servings"
|
id="input-servings"
|
||||||
|
|
@ -96,6 +97,15 @@
|
||||||
4
|
4
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"/>
|
"/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
id="input-add-ingredients-to-shopping-list"
|
||||||
|
type="checkbox"
|
||||||
|
checked
|
||||||
|
>
|
||||||
|
<label for="input-add-ingredients-to-shopping-list">
|
||||||
|
{{ tr.t(Sentence::CalendarAddIngredientsToShoppingList) }}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="calendar-add-to-planner-success">{{ tr.t(Sentence::CalendarAddToPlannerSuccess) }}</span>
|
<span class="calendar-add-to-planner-success">{{ tr.t(Sentence::CalendarAddToPlannerSuccess) }}</span>
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,7 @@
|
||||||
(CalendarAddToPlannerSuccess, "Recipe {title} has been scheduled for {date}"),
|
(CalendarAddToPlannerSuccess, "Recipe {title} has been scheduled for {date}"),
|
||||||
(CalendarAddToPlannerAlreadyExists, "Recipe {title} has already been scheduled for {date}"),
|
(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.
|
(CalendarDateFormat, "%A, %-d %B, %C%y"), // See https://docs.rs/chrono/latest/chrono/format/strftime/index.html.
|
||||||
|
(CalendarAddIngredientsToShoppingList, "Add ingredients to shopping list"),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
|
@ -261,6 +262,7 @@
|
||||||
(CalendarAddToPlannerSuccess, "La recette {title} a été agendée pour le {date}"),
|
(CalendarAddToPlannerSuccess, "La recette {title} a été agendée pour le {date}"),
|
||||||
(CalendarAddToPlannerAlreadyExists, "La recette {title} a été déjà été agendée pour le {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.
|
(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"),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
@ -194,6 +194,7 @@ pub struct ScheduleRecipe {
|
||||||
pub recipe_id: i64,
|
pub recipe_id: i64,
|
||||||
pub date: NaiveDate,
|
pub date: NaiveDate,
|
||||||
pub servings: u32,
|
pub servings: u32,
|
||||||
|
pub add_ingredients_to_shopping_list: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ web-sys = { version = "0.3", features = [
|
||||||
"NodeList",
|
"NodeList",
|
||||||
"Window",
|
"Window",
|
||||||
"Location",
|
"Location",
|
||||||
"Storage",
|
|
||||||
"EventTarget",
|
"EventTarget",
|
||||||
"DragEvent",
|
"DragEvent",
|
||||||
"DataTransfer",
|
"DataTransfer",
|
||||||
|
|
|
||||||
|
|
@ -115,10 +115,10 @@ impl RecipeScheduler {
|
||||||
recipe_id: i64,
|
recipe_id: i64,
|
||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
servings: u32,
|
servings: u32,
|
||||||
|
add_ingredients_to_shopping_list: bool,
|
||||||
) -> Result<ScheduleRecipeResult> {
|
) -> Result<ScheduleRecipeResult> {
|
||||||
if self.is_local {
|
if self.is_local {
|
||||||
// storage.get(format("scheduled_recipes-{}-{}", )
|
// TODO: use 'add_ingredients_to_shopping_list'.
|
||||||
// storage.set("asd", "hello").unwrap();
|
|
||||||
let mut recipe_ids_and_dates = load_scheduled_recipes(date.year(), date.month0());
|
let mut recipe_ids_and_dates = load_scheduled_recipes(date.year(), date.month0());
|
||||||
for recipe in recipe_ids_and_dates.iter() {
|
for recipe in recipe_ids_and_dates.iter() {
|
||||||
if recipe.recipe_id == recipe_id && recipe.date == date {
|
if recipe.recipe_id == recipe_id && recipe.date == date {
|
||||||
|
|
@ -135,6 +135,7 @@ impl RecipeScheduler {
|
||||||
recipe_id,
|
recipe_id,
|
||||||
date,
|
date,
|
||||||
servings,
|
servings,
|
||||||
|
add_ingredients_to_shopping_list,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -21,30 +21,37 @@ pub fn setup_page(recipe_id: i64, is_user_logged: bool) -> Result<(), JsValue> {
|
||||||
|
|
||||||
EventListener::new(&add_to_planner, "click", move |_event| {
|
EventListener::new(&add_to_planner, "click", move |_event| {
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
if let Some((date, servings)) = modal_dialog::show_and_initialize_with_ok(
|
if let Some((date, servings, add_ingredients_to_shopping_list)) =
|
||||||
"#hidden-templates .date-and-servings",
|
modal_dialog::show_and_initialize_with_ok(
|
||||||
async |element| {
|
"#hidden-templates .date-and-servings",
|
||||||
calendar::setup(
|
async |element| {
|
||||||
element.selector(".calendar"),
|
calendar::setup(
|
||||||
calendar::CalendarOptions {
|
element.selector(".calendar"),
|
||||||
can_select_date: true,
|
calendar::CalendarOptions {
|
||||||
with_link_and_remove: false,
|
can_select_date: true,
|
||||||
},
|
with_link_and_remove: false,
|
||||||
recipe_scheduler,
|
},
|
||||||
)
|
recipe_scheduler,
|
||||||
},
|
)
|
||||||
|element, calendar_state| {
|
},
|
||||||
let servings_element: HtmlInputElement = element.selector("#input-servings");
|
|element, calendar_state| {
|
||||||
(
|
let servings_element: HtmlInputElement =
|
||||||
calendar_state.get_selected_date(),
|
element.selector("#input-servings");
|
||||||
servings_element.value_as_number() as u32,
|
|
||||||
)
|
let add_ingredients_element: HtmlInputElement =
|
||||||
},
|
element.selector("#input-add-ingredients-to-shopping-list");
|
||||||
)
|
|
||||||
.await
|
(
|
||||||
|
calendar_state.get_selected_date(),
|
||||||
|
servings_element.value_as_number() as u32,
|
||||||
|
add_ingredients_element.checked(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
if let Ok(result) = recipe_scheduler
|
if let Ok(result) = recipe_scheduler
|
||||||
.shedule_recipe(recipe_id, date, servings)
|
.shedule_recipe(recipe_id, date, servings, add_ingredients_to_shopping_list)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
toast::show_element_and_initialize(
|
toast::show_element_and_initialize(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue