567 lines
23 KiB
Rust
567 lines
23 KiB
Rust
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,
|
||
}
|
||
}
|
||
}
|