Handle messages for selecting a plan_date

This commit is contained in:
Jeremy Wall 2023-01-17 13:44:13 -05:00
parent 3f0836dad2
commit f808ca8585
13 changed files with 505 additions and 4 deletions

View File

@ -116,6 +116,16 @@
}, },
"query": "insert into staples (user_id, content) values (?, ?)\n on conflict(user_id) do update set content = excluded.content" "query": "insert into staples (user_id, content) values (?, ?)\n on conflict(user_id) do update set content = excluded.content"
}, },
"1b6fd91460bef61cf02f210404a4ca57b520c969d1f9613e7101ee6aa7a9962a": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Right": 6
}
},
"query": "insert into modified_amts(user_id, name, form, measure_type, amt, plan_date)\n values (?, ?, ?, ?, ?, ?) on conflict (user_id, name, form, measure_type, plan_date) do update set amt=excluded.amt"
},
"2582522f8ca9f12eccc70a3b339d9030aee0f52e62d6674cfd3862de2a68a177": { "2582522f8ca9f12eccc70a3b339d9030aee0f52e62d6674cfd3862de2a68a177": {
"describe": { "describe": {
"columns": [], "columns": [],
@ -236,6 +246,40 @@
}, },
"query": "insert into recipes (user_id, recipe_id, recipe_text) values (?, ?, ?)\n on conflict(user_id, recipe_id) do update set recipe_text=excluded.recipe_text" "query": "insert into recipes (user_id, recipe_id, recipe_text) values (?, ?, ?)\n on conflict(user_id, recipe_id) do update set recipe_text=excluded.recipe_text"
}, },
"4237ff804f254c122a36a14135b90434c6576f48d3a83245503d702552ea9f30": {
"describe": {
"columns": [
{
"name": "name",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "amt",
"ordinal": 1,
"type_info": "Text"
}
],
"nullable": [
false,
false
],
"parameters": {
"Right": 2
}
},
"query": "select\n name,\n amt\nfrom extra_items\nwhere\n user_id = ?\n and plan_date = ?"
},
"5883c4a57def93cca45f8f9d81c8bba849547758217cd250e7ab28cc166ab42b": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Right": 5
}
},
"query": "insert into filtered_ingredients(user_id, name, form, measure_type, plan_date)\n values (?, ?, ?, ?, ?) on conflict(user_id, name, form, measure_type, plan_date) DO NOTHING"
},
"5d743897fb0d8fd54c3708f1b1c6e416346201faa9e28823c1ba5a421472b1fa": { "5d743897fb0d8fd54c3708f1b1c6e416346201faa9e28823c1ba5a421472b1fa": {
"describe": { "describe": {
"columns": [], "columns": [],
@ -264,6 +308,42 @@
}, },
"query": "select content from staples where user_id = ?" "query": "select content from staples where user_id = ?"
}, },
"699ff0f0d4d4c6e26a21c1922a5b5249d89ed1677680a2276899a7f8b26344ee": {
"describe": {
"columns": [
{
"name": "name",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "form",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "measure_type",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "amt",
"ordinal": 3,
"type_info": "Text"
}
],
"nullable": [
false,
false,
false,
false
],
"parameters": {
"Right": 2
}
},
"query": "select\n modified_amts.name,\n modified_amts.form,\n modified_amts.measure_type,\n modified_amts.amt\nfrom modified_amts\nwhere\n user_id = ?\n and plan_date = ?"
},
"6c43908d90f229b32ed8b1b076be9b452a995e1b42ba2554e947c515b031831a": { "6c43908d90f229b32ed8b1b076be9b452a995e1b42ba2554e947c515b031831a": {
"describe": { "describe": {
"columns": [], "columns": [],
@ -294,6 +374,36 @@
}, },
"query": "delete from sessions where id = ?" "query": "delete from sessions where id = ?"
}, },
"7695a0602395006f9b76ecd4d0cb5ecd5dee419b71b3b0b9ea4f47a83f3df41a": {
"describe": {
"columns": [
{
"name": "name",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "form",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "measure_type",
"ordinal": 2,
"type_info": "Text"
}
],
"nullable": [
false,
false,
false
],
"parameters": {
"Right": 2
}
},
"query": "select\n filtered_ingredients.name,\n filtered_ingredients.form,\n filtered_ingredients.measure_type\nfrom filtered_ingredients\nwhere\n user_id = ?\n and plan_date = ?"
},
"7f4abc448b16e8b6b2bb74f8e810e245e81b38e1407085a20d28bfddfc06891f": { "7f4abc448b16e8b6b2bb74f8e810e245e81b38e1407085a20d28bfddfc06891f": {
"describe": { "describe": {
"columns": [ "columns": [
@ -414,6 +524,16 @@
}, },
"query": "with max_date as (\n select user_id, max(date(plan_date)) as plan_date from plan_recipes group by user_id\n)\n\nselect plan_recipes.plan_date as \"plan_date: NaiveDate\", plan_recipes.recipe_id, plan_recipes.count\n from plan_recipes\n inner join max_date on plan_recipes.user_id = max_date.user_id\nwhere\n plan_recipes.user_id = ?\n and plan_recipes.plan_date = max_date.plan_date" "query": "with max_date as (\n select user_id, max(date(plan_date)) as plan_date from plan_recipes group by user_id\n)\n\nselect plan_recipes.plan_date as \"plan_date: NaiveDate\", plan_recipes.recipe_id, plan_recipes.count\n from plan_recipes\n inner join max_date on plan_recipes.user_id = max_date.user_id\nwhere\n plan_recipes.user_id = ?\n and plan_recipes.plan_date = max_date.plan_date"
}, },
"ba07658eb11f9d6cfdb5dbee4496b2573f1e51f4b4d9ae760eca3b977649b5c7": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Right": 4
}
},
"query": "insert into extra_items (user_id, name, amt, plan_date)\nvalues (?, ?, ?, ?)\non conflict (user_id, name, plan_date) do update set amt=excluded.amt"
},
"c988364f9f83f4fa8bd0e594bab432ee7c9ec47ca40f4d16e5e2a8763653f377": { "c988364f9f83f4fa8bd0e594bab432ee7c9ec47ca40f4d16e5e2a8763653f377": {
"describe": { "describe": {
"columns": [ "columns": [

View File

@ -293,6 +293,26 @@ async fn api_inventory_v2(
} }
} }
async fn api_inventory_for_date(
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
session: storage::UserIdFromSession,
Path(date): Path<chrono::NaiveDate>,
) -> api::InventoryResponse {
use storage::{UserId, UserIdFromSession::FoundUserId};
if let FoundUserId(UserId(id)) = session {
app_store
.fetch_inventory_for_date(id, date)
.await
.map(|d| {
let data: api::InventoryData = d.into();
data
})
.into()
} else {
api::Response::Unauthorized
}
}
async fn api_inventory( async fn api_inventory(
Extension(app_store): Extension<Arc<storage::SqliteStore>>, Extension(app_store): Extension<Arc<storage::SqliteStore>>,
session: storage::UserIdFromSession, session: storage::UserIdFromSession,
@ -309,6 +329,35 @@ async fn api_inventory(
} }
} }
async fn api_save_inventory_for_date(
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
session: storage::UserIdFromSession,
Path(date): Path<NaiveDate>,
Json((filtered_ingredients, modified_amts, extra_items)): Json<(
Vec<IngredientKey>,
Vec<(IngredientKey, String)>,
Vec<(String, String)>,
)>,
) -> api::EmptyResponse {
use storage::{UserId, UserIdFromSession::FoundUserId};
if let FoundUserId(UserId(id)) = session {
let filtered_ingredients = filtered_ingredients.into_iter().collect();
let modified_amts = modified_amts.into_iter().collect();
app_store
.save_inventory_data_for_date(
id,
&date,
filtered_ingredients,
modified_amts,
extra_items,
)
.await
.into()
} else {
api::EmptyResponse::Unauthorized
}
}
async fn save_inventory_data( async fn save_inventory_data(
app_store: Arc<storage::SqliteStore>, app_store: Arc<storage::SqliteStore>,
id: String, id: String,
@ -441,6 +490,10 @@ fn mk_v2_routes() -> Router {
"/inventory", "/inventory",
get(api_inventory_v2).post(api_save_inventory_v2), get(api_inventory_v2).post(api_save_inventory_v2),
) )
.route(
"/inventory/at/:date",
get(api_inventory_for_date).post(api_save_inventory_for_date),
)
// TODO(jwall): This is now deprecated but will still work // TODO(jwall): This is now deprecated but will still work
.route("/categories", get(api_categories).post(api_save_categories)) .route("/categories", get(api_categories).post(api_save_categories))
.route( .route(

View File

@ -0,0 +1,8 @@
select
filtered_ingredients.name,
filtered_ingredients.form,
filtered_ingredients.measure_type
from filtered_ingredients
where
user_id = ?
and plan_date = ?

View File

@ -0,0 +1,9 @@
select
modified_amts.name,
modified_amts.form,
modified_amts.measure_type,
modified_amts.amt
from modified_amts
where
user_id = ?
and plan_date = ?

View File

@ -145,6 +145,16 @@ pub trait APIStore {
date: NaiveDate, date: NaiveDate,
) -> Result<()>; ) -> Result<()>;
async fn fetch_inventory_for_date<S: AsRef<str> + Send>(
&self,
user_id: S,
date: NaiveDate,
) -> Result<(
Vec<IngredientKey>,
Vec<(IngredientKey, String)>,
Vec<(String, String)>,
)>;
async fn fetch_latest_inventory_data<S: AsRef<str> + Send>( async fn fetch_latest_inventory_data<S: AsRef<str> + Send>(
&self, &self,
user_id: S, user_id: S,
@ -154,6 +164,15 @@ pub trait APIStore {
Vec<(String, String)>, Vec<(String, String)>,
)>; )>;
async fn save_inventory_data_for_date<S: AsRef<str> + Send>(
&self,
user_id: S,
date: &NaiveDate,
filtered_ingredients: BTreeSet<IngredientKey>,
modified_amts: BTreeMap<IngredientKey, String>,
extra_items: Vec<(String, String)>,
) -> Result<()>;
async fn save_inventory_data<S: AsRef<str> + Send>( async fn save_inventory_data<S: AsRef<str> + Send>(
&self, &self,
user_id: S, user_id: S,
@ -652,7 +671,89 @@ impl APIStore for SqliteStore {
Ok(Some(result)) Ok(Some(result))
} }
// TODO(jwall): Do we need fetch for date variants of this. async fn fetch_inventory_for_date<S: AsRef<str> + Send>(
&self,
user_id: S,
date: NaiveDate,
) -> Result<(
Vec<IngredientKey>,
Vec<(IngredientKey, String)>,
Vec<(String, String)>,
)> {
let user_id = user_id.as_ref();
struct FilteredIngredientRow {
name: String,
form: String,
measure_type: String,
}
let filtered_ingredient_rows: Vec<FilteredIngredientRow> = sqlx::query_file_as!(
FilteredIngredientRow,
"src/web/storage/fetch_filtered_ingredients_for_date.sql",
user_id,
date,
)
.fetch_all(self.pool.as_ref())
.await?;
let mut filtered_ingredients = Vec::new();
for row in filtered_ingredient_rows {
filtered_ingredients.push(IngredientKey::new(
row.name,
if row.form.is_empty() {
None
} else {
Some(row.form)
},
row.measure_type,
));
}
struct ModifiedAmtRow {
name: String,
form: String,
measure_type: String,
amt: String,
}
let modified_amt_rows = sqlx::query_file_as!(
ModifiedAmtRow,
"src/web/storage/fetch_modified_amts_for_date.sql",
user_id,
date,
)
.fetch_all(self.pool.as_ref())
.await?;
let mut modified_amts = Vec::new();
for row in modified_amt_rows {
modified_amts.push((
IngredientKey::new(
row.name,
if row.form.is_empty() {
None
} else {
Some(row.form)
},
row.measure_type,
),
row.amt,
));
}
pub struct ExtraItemRow {
name: String,
amt: String,
}
let extra_items_rows = sqlx::query_file_as!(
ExtraItemRow,
"src/web/storage/fetch_extra_items_for_date.sql",
user_id,
date,
)
.fetch_all(self.pool.as_ref())
.await?;
let mut extra_items = Vec::new();
for row in extra_items_rows {
extra_items.push((row.name, row.amt));
}
Ok((filtered_ingredients, modified_amts, extra_items))
}
async fn fetch_latest_inventory_data<S: AsRef<str> + Send>( async fn fetch_latest_inventory_data<S: AsRef<str> + Send>(
&self, &self,
user_id: S, user_id: S,
@ -732,6 +833,66 @@ impl APIStore for SqliteStore {
Ok((filtered_ingredients, modified_amts, extra_items)) Ok((filtered_ingredients, modified_amts, extra_items))
} }
async fn save_inventory_data_for_date<S: AsRef<str> + Send>(
&self,
user_id: S,
date: &NaiveDate,
filtered_ingredients: BTreeSet<IngredientKey>,
modified_amts: BTreeMap<IngredientKey, String>,
extra_items: Vec<(String, String)>,
) -> Result<()> {
let user_id = user_id.as_ref();
let mut transaction = self.pool.as_ref().begin().await?;
// store the filtered_ingredients
for key in filtered_ingredients {
let name = key.name();
let form = key.form();
let measure_type = key.measure_type();
sqlx::query_file!(
"src/web/storage/save_filtered_ingredients_for_date.sql",
user_id,
name,
form,
measure_type,
date,
)
.execute(&mut transaction)
.await?;
}
// store the modified amts
for (key, amt) in modified_amts {
let name = key.name();
let form = key.form();
let measure_type = key.measure_type();
let amt = &amt;
sqlx::query_file!(
"src/web/storage/save_modified_amts_for_date.sql",
user_id,
name,
form,
measure_type,
amt,
date,
)
.execute(&mut transaction)
.await?;
}
// Store the extra items
for (name, amt) in extra_items {
sqlx::query_file!(
"src/web/storage/store_extra_items_for_date.sql",
user_id,
name,
amt,
date
)
.execute(&mut transaction)
.await?;
}
transaction.commit().await?;
Ok(())
}
async fn save_inventory_data<S: AsRef<str> + Send>( async fn save_inventory_data<S: AsRef<str> + Send>(
&self, &self,
user_id: S, user_id: S,

View File

@ -0,0 +1,2 @@
insert into filtered_ingredients(user_id, name, form, measure_type, plan_date)
values (?, ?, ?, ?, ?) on conflict(user_id, name, form, measure_type, plan_date) DO NOTHING

View File

@ -0,0 +1,2 @@
insert into modified_amts(user_id, name, form, measure_type, amt, plan_date)
values (?, ?, ?, ?, ?, ?) on conflict (user_id, name, form, measure_type, plan_date) do update set amt=excluded.amt

View File

@ -0,0 +1,3 @@
insert into extra_items (user_id, name, amt, plan_date)
values (?, ?, ?, ?)
on conflict (user_id, name, plan_date) do update set amt=excluded.amt

View File

@ -257,6 +257,27 @@ impl LocalStore {
} }
} }
pub fn set_plan_date(&self, date: &NaiveDate) {
self.store
.set(
"plan:date",
&to_string(&date).expect("Failed to serialize plan:date"),
)
.expect("Failed to store plan:date");
}
pub fn get_plan_date(&self) -> Option<NaiveDate> {
if let Some(date) = self
.store
.get("plan:date")
.expect("Failed to get plan date")
{
Some(from_str(&date).expect("Failed to deserialize plan_date"))
} else {
None
}
}
pub fn get_inventory_data( pub fn get_inventory_data(
&self, &self,
) -> Option<( ) -> Option<(
@ -606,7 +627,7 @@ impl HttpStore {
pub async fn fetch_plan_for_date( pub async fn fetch_plan_for_date(
&self, &self,
date: NaiveDate, date: &NaiveDate,
) -> Result<Option<Vec<(String, i32)>>, Error> { ) -> Result<Option<Vec<(String, i32)>>, Error> {
let mut path = self.v2_path(); let mut path = self.v2_path();
path.push_str("/plan"); path.push_str("/plan");
@ -643,6 +664,48 @@ impl HttpStore {
} }
} }
pub async fn fetch_inventory_for_date(
&self,
date: &NaiveDate,
) -> Result<
(
BTreeSet<IngredientKey>,
BTreeMap<IngredientKey, String>,
Vec<(String, String)>,
),
Error,
> {
let mut path = self.v2_path();
path.push_str("/inventory");
path.push_str("/at");
path.push_str(&format!("/{}", date));
let resp = reqwasm::http::Request::get(&path).send().await?;
if resp.status() != 200 {
let err = Err(format!("Status: {}", resp.status()).into());
Ok(match self.local_store.get_inventory_data() {
Some(val) => val,
None => return err,
})
} else {
debug!("We got a valid response back");
let InventoryData {
filtered_ingredients,
modified_amts,
extra_items,
} = resp
.json::<InventoryResponse>()
.await
.map_err(|e| format!("{}", e))?
.as_success()
.unwrap();
Ok((
filtered_ingredients.into_iter().collect(),
modified_amts.into_iter().collect(),
extra_items,
))
}
}
pub async fn fetch_inventory_data( pub async fn fetch_inventory_data(
&self, &self,
) -> Result< ) -> Result<

View File

@ -38,6 +38,7 @@ pub struct AppState {
pub modified_amts: BTreeMap<IngredientKey, String>, pub modified_amts: BTreeMap<IngredientKey, String>,
pub auth: Option<UserData>, pub auth: Option<UserData>,
pub plan_dates: BTreeSet<NaiveDate>, pub plan_dates: BTreeSet<NaiveDate>,
pub selected_plan_date: Option<NaiveDate>,
} }
impl AppState { impl AppState {
@ -52,6 +53,7 @@ impl AppState {
modified_amts: BTreeMap::new(), modified_amts: BTreeMap::new(),
auth: None, auth: None,
plan_dates: BTreeSet::new(), plan_dates: BTreeSet::new(),
selected_plan_date: None,
} }
} }
} }
@ -73,6 +75,7 @@ pub enum Message {
SaveState(Option<Box<dyn FnOnce()>>), SaveState(Option<Box<dyn FnOnce()>>),
LoadState(Option<Box<dyn FnOnce()>>), LoadState(Option<Box<dyn FnOnce()>>),
UpdateStaples(String, Option<Box<dyn FnOnce()>>), UpdateStaples(String, Option<Box<dyn FnOnce()>>),
SelectPlanDate(NaiveDate),
} }
impl Debug for Message { impl Debug for Message {
@ -113,6 +116,7 @@ impl Debug for Message {
Self::SaveState(_) => write!(f, "SaveState"), Self::SaveState(_) => write!(f, "SaveState"),
Self::LoadState(_) => write!(f, "LoadState"), Self::LoadState(_) => write!(f, "LoadState"),
Self::UpdateStaples(arg, _) => f.debug_tuple("UpdateStaples").field(arg).finish(), Self::UpdateStaples(arg, _) => f.debug_tuple("UpdateStaples").field(arg).finish(),
Self::SelectPlanDate(arg) => f.debug_tuple("SelectPlanDate").field(arg).finish(),
} }
} }
} }
@ -189,8 +193,15 @@ impl StateMachine {
debug!(?plan_dates, "meal plan list"); debug!(?plan_dates, "meal plan list");
state.plan_dates = BTreeSet::from_iter(plan_dates.drain(0..)); state.plan_dates = BTreeSet::from_iter(plan_dates.drain(0..));
} }
info!("Synchronizing meal plan"); info!("Synchronizing meal plan");
let plan = store.fetch_plan().await?; let plan = if let Some(cached_plan_date) = local_store.get_plan_date() {
let plan = store.fetch_plan_for_date(&cached_plan_date).await?;
state.selected_plan_date = Some(cached_plan_date);
plan
} else {
store.fetch_plan().await?
};
if let Some(plan) = plan { if let Some(plan) = plan {
// set the counts. // set the counts.
let mut plan_map = BTreeMap::new(); let mut plan_map = BTreeMap::new();
@ -242,8 +253,13 @@ impl StateMachine {
error!("{:?}", e); error!("{:?}", e);
} }
} }
let inventory_data = if let Some(cached_plan_date) = &state.selected_plan_date {
store.fetch_inventory_for_date(cached_plan_date).await
} else {
store.fetch_inventory_data().await
};
info!("Synchronizing inventory data"); info!("Synchronizing inventory data");
match store.fetch_inventory_data().await { match inventory_data {
Ok((filtered_ingredients, modified_amts, extra_items)) => { Ok((filtered_ingredients, modified_amts, extra_items)) => {
local_store.set_inventory_data(( local_store.set_inventory_data((
&filtered_ingredients, &filtered_ingredients,
@ -436,6 +452,36 @@ impl MessageMapper<Message, AppState> for StateMachine {
}); });
return; return;
} }
Message::SelectPlanDate(date) => {
let store = self.store.clone();
let local_store = self.local_store.clone();
spawn_local_scoped(cx, async move {
if let Some(mut plan) = store
.fetch_plan_for_date(&date)
.await
.expect("Failed to fetch plan for date")
{
// Note(jwall): This is a little unusual but because this
// is async code we can't rely on the set below.
original_copy.recipe_counts =
BTreeMap::from_iter(plan.drain(0..).map(|(k, v)| (k, v as usize)));
}
let (filtered, modified, extras) = store
.fetch_inventory_for_date(&date)
.await
.expect("Failed to fetch inventory_data for date");
original_copy.modified_amts = modified;
original_copy.filtered_ingredients = filtered;
original_copy.extras = extras;
local_store.set_plan_date(&date);
original.set(original_copy);
});
// NOTE(jwall): Because we do our signal set above in the async block
// we have to return here to avoid lifetime issues and double setting
// the original signal.
return;
}
} }
original.set(original_copy); original.set(original_copy);
} }

View File

@ -17,10 +17,12 @@ use sycamore::prelude::*;
pub mod cook; pub mod cook;
pub mod inventory; pub mod inventory;
pub mod plan; pub mod plan;
pub mod select;
pub use cook::*; pub use cook::*;
pub use inventory::*; pub use inventory::*;
pub use plan::*; pub use plan::*;
pub use select::*;
#[derive(Props)] #[derive(Props)]
pub struct PageState<'a, G: Html> { pub struct PageState<'a, G: Html> {
@ -33,6 +35,7 @@ pub fn PlanningPage<'a, G: Html>(cx: Scope<'a>, state: PageState<'a, G>) -> View
let PageState { children, selected } = state; let PageState { children, selected } = state;
let children = children.call(cx); let children = children.call(cx);
let planning_tabs: Vec<(String, &'static str)> = vec![ let planning_tabs: Vec<(String, &'static str)> = vec![
("/ui/planning/select".to_owned(), "Select"),
("/ui/planning/plan".to_owned(), "Plan"), ("/ui/planning/plan".to_owned(), "Plan"),
("/ui/planning/inventory".to_owned(), "Inventory"), ("/ui/planning/inventory".to_owned(), "Inventory"),
("/ui/planning/cook".to_owned(), "Cook"), ("/ui/planning/cook".to_owned(), "Cook"),

View File

@ -0,0 +1,26 @@
// Copyright 2023 Jeremy Wall (Jeremy@marzhilsltudios.com)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::PlanningPage;
use crate::app_state::StateHandler;
use sycamore::prelude::*;
#[component]
pub fn SelectPage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View<G> {
view! {cx,
PlanningPage(
selected=Some("Select".to_owned()),
) { "TODO(jwall)" }
}
}

View File

@ -62,6 +62,8 @@ pub enum ManageRoutes {
#[derive(Route, Debug)] #[derive(Route, Debug)]
pub enum PlanningRoutes { pub enum PlanningRoutes {
#[to("/select")]
Select,
#[to("/plan")] #[to("/plan")]
Plan, Plan,
#[to("/inventory")] #[to("/inventory")]
@ -83,6 +85,9 @@ fn route_switch<'ctx, G: Html>(route: &Routes, cx: Scope<'ctx>, sh: StateHandler
use ManageRoutes::*; use ManageRoutes::*;
use PlanningRoutes::*; use PlanningRoutes::*;
match route { match route {
Routes::Planning(Select) => view! {cx,
SelectPage(sh)
},
Routes::Planning(Plan) => view! {cx, Routes::Planning(Plan) => view! {cx,
PlanPage(sh) PlanPage(sh)
}, },