From 0d558c106381d39d3ed7cfa3103389cbe0369113 Mon Sep 17 00:00:00 2001 From: ultrageese Date: Sat, 20 Jun 2026 23:30:08 +1000 Subject: [PATCH] =?UTF-8?q?=D0=BC=D1=8F=D0=BC=D1=8F=D0=BC=D1=8F=20=D0=BC?= =?UTF-8?q?=D1=8F=D0=BC=D1=8F=D0=BC=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code/src/app.rs | 1 + code/src/database.rs | 19 +++- code/src/models.rs | 27 +++++- code/src/recipe_tab.rs | 202 +++++++++++++++++++++++++++++++++++++++-- 4 files changed, 236 insertions(+), 13 deletions(-) diff --git a/code/src/app.rs b/code/src/app.rs index 330a8f8..6c84ddc 100644 --- a/code/src/app.rs +++ b/code/src/app.rs @@ -306,6 +306,7 @@ impl MainTabViewer { is_admin, worker_tabs: WorkerTabViewer::new(is_admin), material_tabs: MaterialTabViewer::new(is_admin), + recipe_tab: RecipeTabViewer::new(is_admin), ..Default::default() } } diff --git a/code/src/database.rs b/code/src/database.rs index fc69b6a..e48bc2e 100644 --- a/code/src/database.rs +++ b/code/src/database.rs @@ -410,9 +410,21 @@ impl DBOperator { .await .unwrap_or(Vec::new()); - let ret: Vec = Vec::new(); + let mut ret: Vec = Vec::new(); let elements = self.get_recipe_elements().await.unwrap_or(Vec::new()); - for pr in pre_ret.iter() {} + for pr in pre_ret.iter() { + let mut els: Vec = Vec::new(); + for elem in elements.clone() { + if elem.recipe_id == pr.id { + els.push(elem.clone()); + } + } + ret.push(Recipe { + id: pr.id, + name: pr.name.clone(), + elements: els, + }); + } Ok(ret) } pub async fn get_recipe_elements(&self) -> Result, sqlx::Error> { @@ -420,7 +432,7 @@ impl DBOperator { let mats = self.get_materials().await.unwrap(); let eqs = self.get_equipment().await.unwrap(); - let ret: Vec = Vec::new(); + let mut ret: Vec = Vec::new(); for re in pre_ret.unwrap().iter() { let mat = mats.iter().find(|m| m.id == re.material_id); let eq = eqs.iter().find(|e| e.id == re.equipment_id); @@ -431,6 +443,7 @@ impl DBOperator { material: m.clone(), equipment: e.clone(), quantity: re.quantity, + recipe_id: re.recipe_id, }); } (_, _) => {} diff --git a/code/src/models.rs b/code/src/models.rs index d22fa74..3e85dd4 100644 --- a/code/src/models.rs +++ b/code/src/models.rs @@ -54,7 +54,7 @@ pub struct Salary { pub comment: String, } -#[derive(Default, Clone, Hash)] +#[derive(Default, Clone, Hash, PartialEq, Eq)] pub struct Equipment { pub id: i32, pub name: String, @@ -72,7 +72,7 @@ pub struct EquipmentRow { pub worker_id: i32, pub is_written_off: bool, } -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq, Hash, Default)] pub struct Material { pub id: i32, pub name: String, @@ -86,16 +86,18 @@ pub struct MaterialRow { pub quantity: i32, pub category_id: i32, } -#[derive(sqlx::FromRow, Default, Clone, PartialEq)] +#[derive(sqlx::FromRow, Default, Clone, PartialEq, Eq, Hash)] pub struct MaterialCategory { pub id: i32, pub name: String, } +#[derive(Clone, PartialEq, Eq, Hash, Default)] pub struct RecipeElement { pub id: i32, pub material: Material, pub equipment: Equipment, pub quantity: i32, + pub recipe_id: i32, } #[derive(FromRow)] pub struct RecipeElementRow { @@ -103,7 +105,9 @@ pub struct RecipeElementRow { pub material_id: i32, pub equipment_id: i32, pub quantity: i32, + pub recipe_id: i32, } +#[derive(Default, Clone)] pub struct Recipe { pub id: i32, pub name: String, @@ -161,6 +165,23 @@ pub struct SalaryModalState { pub status: ModalStatus, } +#[derive(Default)] +pub struct RecipeModalState { + pub is_open: bool, + pub can_finish: bool, + pub recipe: Recipe, + pub elements: HashSet, + pub status: ModalStatus, +} + +#[derive(Default)] +pub struct ElementModalState { + pub is_open: bool, + pub can_finish: bool, + pub element: RecipeElement, + pub status: ModalStatus, +} + #[derive(Default, PartialEq)] pub enum ModalStatus { #[default] diff --git a/code/src/recipe_tab.rs b/code/src/recipe_tab.rs index c777d0b..2a3832c 100644 --- a/code/src/recipe_tab.rs +++ b/code/src/recipe_tab.rs @@ -1,11 +1,20 @@ -use tokio::sync::RwLock; +use egui_extras::{Column, TableBuilder}; use crate::{ database::DBOperator, - models::{Equipment, Material, Recipe, RecipeElement}, + models::{ + ElementModalState, Equipment, Material, ModalStatus, Recipe, RecipeElement, + RecipeModalState, + }, tab::{Tab, TabTypes}, }; -use std::{default::Default, sync::Arc}; +use egui::{Sense, mutex::RwLock}; +use std::{ + collections::{HashMap, HashSet}, + default::Default, + ops::Deref, + sync::Arc, +}; pub struct RecipeTabViewer { is_admin: bool, @@ -16,6 +25,9 @@ pub struct RecipeTabViewer { recipe_elements: Arc>>, materials: Arc>>, equipment: Arc>>, + + process_recipe_status: RecipeModalState, + process_element_status: ElementModalState, } impl RecipeTabViewer { @@ -25,8 +37,156 @@ impl RecipeTabViewer { ..Default::default() } } + 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| { + 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(){ + + } + }); + } + + 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("Составляющие:"); + egui::ComboBox::new("recipe_element_combobox", "") + .close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside) + .show_ui(ui, |ui| { + if ui.button("Новый элемент").clicked() { + self.process_element_status.is_open = true; + self.process_element_status.status = ModalStatus::Add; + } + + for el in self.recipe_elements.read().clone().iter() { + let resp = ui.checkbox( + &mut self.process_recipe_status.elements.contains(el), + &el.material.name, + ); + if resp.clicked() { + if self.process_recipe_status.elements.contains(el) { + self.process_recipe_status.elements.remove(el); + } else { + self.process_recipe_status.elements.insert(el.clone()); + } + } + } + }); + }); + }); + if self.process_element_status.is_open { + self.new_element_modal(ui); + } + } pub fn show_recipe_list(&mut self, ui: &mut egui::Ui) { - ui.label("Goida"); + 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; + } + } + }); + TableBuilder::new(ui) + .striped(true) + .columns(Column::auto(), 3) + .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; + } + }); + } + if row.response().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); + } } } impl egui_dock::TabViewer for RecipeTabViewer { @@ -51,11 +211,39 @@ impl egui_dock::TabViewer for RecipeTabViewer { 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, + 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().await.unwrap_or(Vec::new()) }), + )), + recipe_elements: Arc::new(RwLock::new( + rt.block_on(async { db_oper.get_recipe_elements().await.unwrap_or(Vec::new()) }), + )), + materials: Arc::new(RwLock::new( + rt.block_on(async { db_oper.get_materials().await.unwrap_or(Vec::new()) }), + )), + equipment: Arc::new(RwLock::new( + rt.block_on(async { db_oper.get_equipment().await.unwrap_or(Vec::new()) }), + )), + + process_recipe_status: RecipeModalState { + is_open: false, + can_finish: false, + recipe: Recipe::default(), + status: crate::models::ModalStatus::Add, + elements: HashSet::new(), + }, + process_element_status: ElementModalState { + is_open: false, + can_finish: false, + element: RecipeElement::default(), + status: crate::models::ModalStatus::Add, + }, + rt, db_oper, - recipes: } } }