use bigdecimal::{BigDecimal, FromPrimitive, ToPrimitive}; use egui_extras::{Column, TableBuilder}; use crate::{ database::DBOperator, misc::{self, ELEMENTS_PER_PAGE}, models::{ Equipment, Material, ModalStatus, Product, ProductModalState, Recipe, RecipeElement, RecipeElementModalState, RecipeModalState, }, tab::{self, Tab, TabTypes}, }; use egui::{Sense, mutex::RwLock}; use std::{ collections::{HashMap, HashSet}, default::Default, ops::Deref, sync::Arc, }; pub struct RecipeTabViewer { is_admin: bool, rt: tokio::runtime::Runtime, db_oper: DBOperator, recipes: Arc>>, materials: Arc>>, equipment: Arc>>, products: Arc>>, process_recipe_status: RecipeModalState, process_element_status: RecipeElementModalState, process_product_status: ProductModalState, old_price: BigDecimal, product_page: i32, recipe_page: i32, mat_page: i32, } impl RecipeTabViewer { pub fn new(is_admin: bool) -> Self { Self { is_admin, ..Default::default() } } fn update_recipes(&mut self) { self.recipes = Arc::new(RwLock::new(self.rt.block_on(async { self.db_oper .get_recipes(self.recipe_page, misc::ELEMENTS_PER_PAGE) .await .unwrap_or(Vec::new()) }))); } pub fn update_products(&mut self) { self.products = Arc::new(RwLock::new(self.rt.block_on(async { self.db_oper .get_products(self.product_page, misc::ELEMENTS_PER_PAGE) .await }))); } fn new_element_modal(&mut self, ui: &mut egui::Ui) { egui::Modal::new(egui::Id::new("new_element_modal")).show(ui.ctx(), |ui| { if ui.button("Закрыть").clicked() { self.process_element_status.is_open = false; } ui.horizontal(|ui| { ui.label("Использовать:"); egui::ComboBox::new("material_select_combobox", "") .selected_text(&self.process_element_status.element.material.name) .show_ui(ui, |ui| { if ui.button("<-").clicked() { if self.mat_page > 0 { self.mat_page -= 1; self.update_materials(); } } ui.monospace(self.mat_page.to_string()); if ui.button("->").clicked() { if self.materials.read().len().to_i32().unwrap() == misc::ELEMENTS_PER_PAGE { self.mat_page += 1; self.update_materials(); } } for mat in self.materials.read().clone().iter() { ui.selectable_value( &mut self.process_element_status.element.material, mat.clone(), &mat.name, ); } }); }); ui.horizontal(|ui| { ui.label("В количестве:"); ui.add( egui::DragValue::new(&mut self.process_element_status.element.quantity) .range(0..=100), ); ui.label("ед."); }); ui.horizontal(|ui| { ui.label("С использованием:"); egui::ComboBox::new("equipment_select_combobox", "") .selected_text(&self.process_element_status.element.equipment.name) .show_ui(ui, |ui| { for eq in self.equipment.read().clone().iter() { ui.selectable_value( &mut self.process_element_status.element.equipment, eq.clone(), &eq.name, ); } }); }); if ui.button("Добавить").clicked() { self.process_recipe_status .recipe .elements .insert(self.process_element_status.element.clone()); self.process_element_status.is_open = false; } }); } fn process_recipe_modal(&mut self, ui: &mut egui::Ui) { egui::Modal::new(egui::Id::new("process_recipe_modal")).show(ui.ctx(), |ui| { if ui.button("Закрыть").clicked() { self.process_recipe_status.is_open = false; } egui::Grid::new("process_recipe_grid").show(ui, |ui| { let size = ui.available_size(); ui.label("Название:"); ui.add_sized( size, egui::TextEdit::singleline(&mut self.process_recipe_status.recipe.name) .hint_text("Пиво"), ); ui.end_row(); ui.label("Составляющие:"); let text = match self.process_recipe_status.recipe.elements.len() { 0 => "Пусто", 1 => "1 элемент", 2..=4 => &format!( "{} элемента", self.process_recipe_status.recipe.elements.len() ), _ => &format!( "{} элементов", self.process_recipe_status.recipe.elements.len() ), }; egui::ComboBox::new("recipe_element_combobox", "") .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside) .selected_text(text) .show_ui(ui, |ui| { if ui.button("Новый элемент").clicked() { self.process_element_status.is_open = true; self.process_element_status.status = ModalStatus::Add; self.process_element_status.element = RecipeElement::default(); } for el in self.process_recipe_status.recipe.elements.clone().iter() { ui.horizontal(|ui| { if ui.button("x").clicked() { self.process_recipe_status.recipe.elements.remove(el); } ui.label(format!( "{} ({})\n{}", el.material.name, el.material.category.name, el.equipment.name )); }); } }); }); let btext = match self.process_recipe_status.status { ModalStatus::Add => "Добавить", ModalStatus::Edit => "Сохранить", ModalStatus::Remove => "Удалить", }; if ui.button(btext).clicked() { match self.process_recipe_status.status { ModalStatus::Add => { self.rt.block_on(async { let a = self .db_oper .add_recipe(self.process_recipe_status.recipe.clone()) .await .ok(); self.db_oper .add_recipe_elems( self.process_recipe_status.recipe.elements.clone(), a.unwrap().to_i32().unwrap(), ) .await .ok(); }); } ModalStatus::Edit => { self.rt.block_on(async { self.db_oper .update_recipe(self.process_recipe_status.recipe.clone()) .await .ok(); self.db_oper .remove_recipe_elements(self.process_recipe_status.recipe.id) .await .ok(); self.db_oper .add_recipe_elems( self.process_recipe_status.recipe.elements.clone(), self.process_recipe_status.recipe.id, ) .await .ok(); }); } ModalStatus::Remove => { self.rt.block_on(async { self.db_oper .remove_recipe_elements(self.process_recipe_status.recipe.id) .await .ok(); self.db_oper .remove_recipe(self.process_recipe_status.recipe.id) .await .ok(); }); } } self.update_recipes(); self.process_recipe_status.is_open = false; } }); if self.process_element_status.is_open { self.new_element_modal(ui); } } pub fn show_recipe_list(&mut self, ui: &mut egui::Ui) { ui.horizontal(|ui| { if self.is_admin { if ui.button("Добавить").clicked() { self.process_recipe_status.is_open = true; self.process_recipe_status.recipe = Recipe::default(); self.process_recipe_status.status = ModalStatus::Add; } } if ui.button("<-").clicked() { if self.recipe_page > 0 { self.recipe_page -= 1; self.update_recipes(); } } ui.monospace(self.recipe_page.to_string()); if ui.button("->").clicked() { if self.recipes.read().len().to_i32().unwrap() > misc::ELEMENTS_PER_PAGE { self.recipe_page += 1; self.update_recipes(); } } }); TableBuilder::new(ui) .striped(true) .columns(Column::auto(), 4) .vscroll(false) .sense(Sense::click()) .header(30.0, |mut head| { head.col(|mut ui| { ui.heading("Название"); }); head.col(|mut ui| { ui.heading("Элементы"); }); }) .body(|mut body| { for rec in self.recipes.read().iter() { body.row(30.0, |mut row| { row.col(|ui| { ui.label(rec.name.clone()); }); row.col(|ui| { let text = match rec.elements.len() { 0 => "Пусто", 1 => "1 элемент", 2..=4 => &format!("{} элемента", rec.elements.len()), _ => &format!("{} элементов", rec.elements.len()), }; ui.label(text); }); if self.is_admin { row.col(|ui| { if ui.button("Удалить").clicked() { self.process_recipe_status.is_open = true; self.process_recipe_status.recipe = rec.clone(); self.process_recipe_status.status = crate::models::ModalStatus::Remove; } }); row.col(|ui| { if ui.button("Произвести").clicked() { self.process_product_status.is_open = true; self.process_product_status.status = ModalStatus::Add; self.process_product_status.product = Product::default(); self.process_product_status.product.recipe = rec.clone(); } }); } if row.response().double_clicked() { self.process_recipe_status.is_open = true; self.process_recipe_status.recipe = rec.clone(); self.process_recipe_status.status = crate::models::ModalStatus::Edit; } }); } }); if self.process_recipe_status.is_open { self.process_recipe_modal(ui); } } fn show_product_list(&mut self, ui: &mut egui::Ui) { ui.horizontal(|ui| { if ui.button("<-").clicked() { if self.product_page > 0 { self.product_page -= 1; self.update_products(); } } ui.monospace(self.product_page.to_string()); if ui.button("->").clicked() { if self.products.read().len() == 5 { self.product_page += 1; self.update_products(); } } }); egui_extras::TableBuilder::new(ui) .striped(false) .vscroll(false) .sense(Sense::click()) .columns(Column::auto(), 4) .column(Column::exact(100.0).at_least(100.0)) .header(25.0, |mut head| { head.col(|ui| { ui.heading("Рецепт"); }); head.col(|ui| { ui.heading("Количество"); }); head.col(|ui| { ui.heading("Цена за ед."); }); head.col(|ui| { ui.heading(" "); }); }) .body(|mut body| { for pr in self.products.read().iter() { body.row(25.0, |mut row| { row.col(|ui| { ui.label(&pr.recipe.name); }); row.col(|ui| { ui.label(pr.volume.to_string()); }); row.col(|ui| { ui.label(pr.price_per_unit.to_string()); }); if self.is_admin { row.col(|ui| { if ui.add(egui::Button::new("Удалить")).clicked() { self.process_product_status.is_open = true; self.process_product_status.status = ModalStatus::Remove; self.process_product_status.product = pr.clone(); } }); if row.response().double_clicked() { self.process_product_status.is_open = true; self.process_product_status.status = ModalStatus::Edit; self.process_product_status.product = pr.clone(); } } }); } }); } fn update_materials(&mut self) { self.materials = Arc::new(RwLock::new(self.rt.block_on(async { self.db_oper .get_materials(self.mat_page, ELEMENTS_PER_PAGE) .await .unwrap_or(Vec::new()) }))) } } impl egui_dock::TabViewer for RecipeTabViewer { type Tab = Tab; fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText { tab.title.clone().into() } fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) { match &tab.tab_type { TabTypes::Recipe => { self.show_recipe_list(ui); } TabTypes::Product => { self.show_product_list(ui); } _ => { ui.label("Помогите!!"); } } if self.process_product_status.is_open { egui::Modal::new(egui::Id::new("process_product_modal")).show(ui.ctx(), |ui| { if ui.button("Закрыть").clicked() { self.process_product_status.is_open = false; } egui::Grid::new("process_product_grid").show(ui, |ui| { let size = ui.available_size(); ui.heading(&self.process_product_status.product.recipe.name); ui.end_row(); if self.process_product_status.status != ModalStatus::Remove { ui.label("Объём/кол-во:"); ui.add_sized( size, egui::Slider::new( &mut self.process_product_status.product.volume.into_inner(), 0.0..=100.0, ) .clamping(egui::SliderClamping::Never), ); ui.end_row(); ui.label("Цена за штуку:"); let mut price = self .process_product_status .product .price_per_unit .to_f64() .unwrap(); ui.add_sized(size, egui::DragValue::new(&mut price)); if BigDecimal::from_f64(price).unwrap() != self.process_product_status.product.price_per_unit { self.process_product_status.product.price_per_unit = BigDecimal::from_f64(price).unwrap(); } ui.end_row(); } }); self.process_product_status.can_finish = match self.process_product_status.status { ModalStatus::Remove => true, _ => { self.process_product_status.product.price_per_unit > 0 && self.process_product_status.product.volume.into_inner() > 0.0 } }; let btext = match self.process_product_status.status { ModalStatus::Add => "Добавить", ModalStatus::Edit => "Сохранить", ModalStatus::Remove => "Удалить", }; if ui .add_enabled( self.process_product_status.can_finish, egui::Button::new(btext), ) .clicked() { match self.process_product_status.status { ModalStatus::Add => { self.rt.block_on(async { println!("yaya"); self.db_oper .add_product(self.process_product_status.product.clone()) .await .unwrap(); }); } ModalStatus::Edit => { self.rt.block_on(async { self.db_oper .update_product(self.process_product_status.product.clone()) .await .ok(); }); } ModalStatus::Remove => self.rt.block_on(async { self.db_oper .remove_product(self.process_product_status.product.id) .await .ok(); }), } self.process_product_status.is_open = false; self.update_products(); } }); } } fn allowed_in_windows(&self, _tab: &mut Self::Tab) -> bool { tab::TABS_CAN_BE_WINDOWS } fn is_closeable(&self, _tab: &Self::Tab) -> bool { false } } impl Default for RecipeTabViewer { fn default() -> Self { let rt = tokio::runtime::Runtime::new().unwrap(); let db_oper = rt.block_on(async { DBOperator::new().await }); Self { is_admin: false, recipes: Arc::new(RwLock::new(rt.block_on(async { db_oper .get_recipes(0, misc::ELEMENTS_PER_PAGE) .await .unwrap_or(Vec::new()) }))), materials: Arc::new(RwLock::new(rt.block_on(async { db_oper .get_materials(0, ELEMENTS_PER_PAGE) .await .unwrap_or(Vec::new()) }))), equipment: Arc::new(RwLock::new(rt.block_on(async { db_oper .get_equipment(0, 5, false) .await .unwrap_or(Vec::new()) }))), products: Arc::new(RwLock::new(rt.block_on(async { db_oper.get_products(0, misc::ELEMENTS_PER_PAGE).await }))), process_recipe_status: RecipeModalState { is_open: false, can_finish: false, recipe: Recipe::default(), status: crate::models::ModalStatus::Add, elements: HashSet::new(), }, process_element_status: RecipeElementModalState { is_open: false, can_finish: false, element: RecipeElement::default(), status: crate::models::ModalStatus::Add, }, process_product_status: ProductModalState::default(), old_price: BigDecimal::default(), product_page: 0, recipe_page: 0, mat_page: 0, rt, db_oper, } } }