BreweryControlSystem/code/src/recipe_tab.rs

567 lines
23 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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<RwLock<Vec<Recipe>>>,
materials: Arc<RwLock<Vec<Material>>>,
equipment: Arc<RwLock<Vec<Equipment>>>,
products: Arc<RwLock<Vec<Product>>>,
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,
}
}
}