diff --git a/code/src/app.rs b/code/src/app.rs index 57a9d61..c13e8c2 100644 --- a/code/src/app.rs +++ b/code/src/app.rs @@ -1,9 +1,10 @@ -use std::sync::Arc; +use std::{collections::HashMap, ops::Deref, sync::Arc}; -use egui::mutex::RwLock; +use egui::{TextBuffer, mutex::RwLock}; use egui_dock::{DockArea, Style, TabViewer}; +use egui_extras::{Column, TableBuilder}; use sqlx::types::BigDecimal; -use crate::{database::DBOperator, models::{Equipment, Position, Worker}}; +use crate::{database::DBOperator, models::{Equipment, Material, MaterialCategory, ModalDataType, ModalWinState, Position, Worker}}; static TABS_CAN_BE_WINDOWS: bool = false; @@ -19,6 +20,8 @@ enum TabTypes{ Settings, WorkerList, WorkerPosition, + MaterialList, + MaterialTypeList } struct Tab{ tab_type: TabTypes, @@ -97,6 +100,8 @@ struct MainTabViewer { db_oper: DBOperator, worker_tabs: WorkerTabViewer, worker_tree: egui_dock::DockState, + material_tabs: MaterialTabViewer, + material_tree: egui_dock::DockState, rt: tokio::runtime::Runtime, is_dark_theme: bool, interface_scale_ratio: f32, @@ -125,6 +130,17 @@ impl Default for MainTabViewer{ tab_type:TabTypes::WorkerPosition, } ]), + material_tabs: MaterialTabViewer::default(), + material_tree: egui_dock::DockState::new(vec![ + Tab{ + title:"Сырьё".to_owned(), + tab_type: TabTypes::MaterialList, + }, + Tab{ + title:"Тип".to_owned(), + tab_type: TabTypes::MaterialTypeList, + } + ]), rt, is_dark_theme: true, interface_scale_ratio: 1.2, @@ -192,7 +208,10 @@ impl MainTabViewer{ .show_inside(ui, &mut self.worker_tabs); } fn show_material(&mut self, ui: &mut egui::Ui){ - ui.label("Сырьё короче да"); + let id = ui.make_persistent_id("MaterialMenu"); + DockArea::new(&mut self.material_tree) + .id(id) + .show_inside(ui, &mut self.material_tabs); } fn show_salary(&mut self, ui: &mut egui::Ui){ ui.label("Помогите"); @@ -621,4 +640,204 @@ impl Default for WorkerTabViewer{ rt, } } +} + +struct MaterialTabViewer{ + db_oper: DBOperator, + rt: tokio::runtime::Runtime, + process_mcat_state: ModalWinState, + + process_material_state: ModalWinState, + + mat_cats: Arc>>, + mats: Arc>>, + +} +impl MaterialTabViewer { + fn show_material(&mut self, ui: &mut egui::Ui) { + ui.horizontal(|ui|{ + if ui.button("Добавить").clicked(){ + self.process_material_state.is_open = true; + self.process_material_state.status = "add".to_owned(); + } + if ui.button("Редактировать").clicked(){ + self.process_material_state.is_open = true; + self.process_material_state.status = "edit".to_owned(); + } + if ui.button("Удалить").clicked(){ + self.process_material_state.is_open = true; + self.process_material_state.status = "remove".to_owned(); + } + if ui.button("Обновить").clicked(){ + + } + }); + if self.process_material_state.is_open{ + egui::Modal::new("add_material".into()).show(ui.ctx(), |ui|{ + if ui.button("Закрыть").clicked(){ + self.process_material_state.is_open = false; + } + }); + } + + TableBuilder::new(ui) + .striped(true) + .cell_layout(egui::Layout::centered_and_justified(egui::Direction::LeftToRight)) + .vscroll(true) + .column(Column::auto()) + .column(Column::auto()) + .column(Column::auto()) + .column(Column::auto()) + .header(20.0, |mut header|{ + header.col(|ui|{ui.heading("ID");}); + header.col(|ui|{ui.heading("Название");}); + header.col(|ui|{ui.heading("Количество");}); + header.col(|ui|{ui.heading("Тип");}); + }) + .body(|mut body|{ + self.rt.block_on(async{ + + + for mat in self.mats.read().clone().iter(){ + body.row(30.0,|mut row|{ + row.col(|ui|{ui.label(mat.id.to_string());}); + row.col(|ui|{ui.label(&mat.name);}); + row.col(|ui|{ui.label(mat.quantity.to_string());}); + row.col(|ui|{ui.label(&mat.category.name);}); + }); + } + + + }) + }); + + } + + fn show_material_type(&mut self, ui: &mut egui::Ui) { + ui.horizontal(|ui|{ + if ui.button("Добавить").clicked(){ + self.process_mcat_state.is_open = true; + self.process_mcat_state.status = "add".to_owned(); + } + if ui.button("Редактировать").clicked(){ + self.process_mcat_state.is_open = true; + self.process_mcat_state.status = "edit".to_owned(); + } + if ui.button("Удалить").clicked(){ + self.process_mcat_state.is_open = true; + self.process_mcat_state.status = "remove".to_owned(); + } + if ui.button("Обновить").clicked(){ + + } + }); + if self.process_mcat_state.is_open{ + egui::Modal::new("process_mcat".into()).show(ui.ctx(), |ui|{ + if ui.button("Закрыть").clicked(){ + self.process_mcat_state.is_open = false; + } + egui::Grid::new("process_mcat_grid") + .show(ui, |ui|{ + ui.label("Тип"); + // println!("{}",self.process_mcat_state.data.get("name").unwrap().as_str()); + let mut iterr = self.mat_cats.read().clone(); + let selected_id = self.process_mcat_state.data.get("id") + .map(|s| s.as_str()) // если там что-то, у чего есть as_str() + .unwrap_or(""); + let current_name = iterr.iter() + .find(|mc| mc.id.to_string() == *selected_id) + .map(|mc | mc.name.as_str()) + .unwrap_or("Выбрать"); + egui::ComboBox::new("process_mcat_grid_selector","Выбрать") + .selected_text(current_name) + .show_ui(ui, |ui|{ + + self.rt.block_on(async{ + for mcat in iterr{ + ui.selectable_value(self.process_mcat_state.data.get_mut("id").unwrap(), mcat.id.to_string(), &mcat.name); + } + }); + }); + ui.end_row(); + + ui.label("Название"); + ui.text_edit_singleline(self.process_mcat_state.data.get_mut("name").unwrap()); + }); + }); + } + TableBuilder::new(ui) + .striped(true) + .column(Column::auto()) + .column(Column::auto()) + .header(20.0, |mut header|{ + header.col(|ui|{ + ui.heading("ID"); + }); + header.col(|ui|{ + ui.heading("Название"); + }); + }) + .body(|mut body|{ + self.rt.block_on(async{ + for mcat in self.mat_cats.read().clone().iter(){ + body.row(20.0,|mut row|{ + row.col(|ui|{ui.label(mcat.id.to_string());}); + row.col(|ui|{ui.label(&mcat.name);}); + }); + } + }); + }); + + } +} +impl egui_dock::TabViewer for MaterialTabViewer{ + type Tab = Tab; + fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText { + (&*tab.title).into() + } + fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) { + match &tab.tab_type{ + TabTypes::MaterialList => self.show_material(ui), + TabTypes::MaterialTypeList => self.show_material_type(ui), + _ => {ui.label("Каким образом?");}, + } + } + fn allowed_in_windows(&self, _tab: &mut Self::Tab) -> bool { + TABS_CAN_BE_WINDOWS + } + fn is_closeable(&self, _tab: &Self::Tab) -> bool { + false + } +} +impl Default for MaterialTabViewer{ + fn default() -> Self { + let rt = tokio::runtime::Runtime::new().unwrap(); + let db_oper = rt.block_on(async{DBOperator::new().await}); + let mat_cats = Arc::new(RwLock::new(rt.block_on(async{db_oper.get_mcat().await.unwrap()}))); + Self { + + mats: Arc::new(RwLock::new(rt.block_on(async{db_oper.get_materials().await.unwrap()}))), + db_oper, + rt, + process_mcat_state: ModalWinState{ + data: HashMap::from([ + (String::from("id"), mat_cats.clone().read().clone()[0].id.to_string()), + (String::from("name"), mat_cats.clone().read().clone()[0].name.clone()), + ]), + status: "none".to_owned(), + ..Default::default() + }, + + process_material_state: ModalWinState{ + data: HashMap::from([ + ("id".to_owned(),String::new()), + ("name".to_owned(), String::new()), + ("category_id".to_owned(), "1".to_owned()) + ]), + ..Default::default() + }, + mat_cats, + + } + } } \ No newline at end of file diff --git a/code/src/database.rs b/code/src/database.rs index 7f9cff3..1885e39 100644 --- a/code/src/database.rs +++ b/code/src/database.rs @@ -3,10 +3,12 @@ use sqlx::mysql::MySqlPool; use dotenvy::dotenv_override; +use std::collections::HashMap; use std::env; // use crate::schema::equipment::dsl::*; // use crate::schema::worker::dsl::*; use crate::models::*; +use anyhow::anyhow; pub struct DBOperator{ pool: MySqlPool, @@ -54,6 +56,33 @@ impl DBOperator{ } Ok(rets) } + pub async fn get_mcat(&self) -> Result,sqlx::Error>{ + let rets = sqlx::query_as::<_, MaterialCategory>("SELECT * FROM `material_category`").fetch_all(&self.pool).await?; + Ok(rets) + } + pub async fn get_materials(&self) -> Result, sqlx::Error>{ + let mats = sqlx::query_as::<_,MaterialRow>("SELECT * FROM `material`").fetch_all(&self.pool).await?; + let cats = self.get_mcat().await?; + let cat_map : HashMap<_, MaterialCategory> = cats + .into_iter() + .map(|cat| (cat.id, cat)) + .collect(); + let mut rets = Vec::with_capacity(mats.len()); + for mat in mats{ + let cat = cat_map + .get(&mat.category_id) + .expect("Никто не знает как, но БД не смогла связать материал с его категорией. Заставьте разраба это починить.") + .clone(); + rets.push(Material{ + id: mat.id, + name: mat.name, + quantity: mat.quantity, + category: cat, + }); + } + Ok(rets) + + } pub async fn check_worker(&self, worker: Worker) -> Result{ let ret = sqlx::query(&format!("SELECT * FROM `worker` WHERE full_name = {}, position_id = {}, hire_date = {}", worker.full_name, worker.position.id, worker.hire_date.to_string())).fetch_all(&self.pool).await?; if ret.len() > 0{ @@ -136,6 +165,8 @@ impl DBOperator{ }); ret } + + } diff --git a/code/src/models.rs b/code/src/models.rs index 6ce5ee4..c9c3fd8 100644 --- a/code/src/models.rs +++ b/code/src/models.rs @@ -1,3 +1,6 @@ +use std::{any::TypeId, ops::Deref}; + +use egui::TextBuffer; use sqlx::types::{BigDecimal, chrono::DateTime}; @@ -51,11 +54,24 @@ pub struct EquipmentRow{ pub maintenance_date: chrono::DateTime, pub worker_id: i32, } +#[derive(Clone)] pub struct Material{ - id: i32, - name: String, - quantity: i32, - category: String + pub id: i32, + pub name: String, + pub quantity: i32, + pub category: MaterialCategory +} +#[derive(sqlx::FromRow)] +pub struct MaterialRow{ + pub id: i32, + pub name: String, + pub quantity: i32, + pub category_id: i32, +} +#[derive(sqlx::FromRow, Default, Clone, PartialEq)] +pub struct MaterialCategory{ + pub id: i32, + pub name: String, } pub struct RecipeElement{ id:i32, @@ -92,4 +108,51 @@ pub struct Product<'a>{ volume: f64, price_per_unit: rust_decimal::Decimal, +} +#[derive(Default)] +pub struct ModalWinState{ + pub is_open: bool, + pub can_finish: bool, + pub data: std::collections::HashMap, + pub status: String, +} +#[derive(PartialEq)] +pub enum ModalDataType{ + Pos(Position), + MatCat(MaterialCategory), + Text(String), +} +impl TextBuffer for ModalDataType{ + fn is_mutable(&self) -> bool { + true + } + + fn as_str(&self) -> &str { + match self{ + ModalDataType::Pos(pos) => &pos.name, + ModalDataType::Text(txt) => &txt, + ModalDataType::MatCat(mat_cat) => &mat_cat.name, + } + } + + fn insert_text(&mut self, text: &str, char_index: usize) -> usize { + match self{ + ModalDataType::Pos(pos) => {pos.name.insert_str(char_index, text);}, + ModalDataType::Text(txt) => {txt.insert_str(char_index, text);}, + ModalDataType::MatCat(mat_cat) => {mat_cat.name.insert_str(char_index, text);} + } + text.len() + } + + fn delete_char_range(&mut self, char_range: std::ops::Range) { + match self{ + ModalDataType::Pos(pos) => {pos.name.delete_char_range(char_range);}, + ModalDataType::Text(txt) => {txt.delete_char_range(char_range);}, + ModalDataType::MatCat(mat_cat) => {mat_cat.name.delete_char_range(char_range);} + } + } + + fn type_id(&self) -> std::any::TypeId { + TypeId::of::() + } } \ No newline at end of file