add product management to vendor dashboard

This commit is contained in:
creating2morrow 2023-06-07 12:30:47 -04:00
parent 6abb0551cd
commit 0d3539b8c4
3 changed files with 262 additions and 40 deletions

View file

@ -401,7 +401,7 @@ pub async fn create_wallet(filename: &String, password: &String) -> bool {
fn update_wallet_lock(filename: &String, closing: bool) -> bool { fn update_wallet_lock(filename: &String, closing: bool) -> bool {
let is_busy: bool = match IS_WALLET_BUSY.lock() { let is_busy: bool = match IS_WALLET_BUSY.lock() {
Ok(m) => *m, Ok(m) => *m,
Err(_) => false, Err(_) => true,
}; };
if is_busy && !closing { if is_busy && !closing {
debug!("wallet {} is busy", filename); debug!("wallet {} is busy", filename);

View file

@ -229,6 +229,20 @@ pub fn message_to_json(m: &models::Message) -> Json<models::Message> {
Json(r_message) Json(r_message)
} }
/// convert product to json so only core module does the work
pub fn product_to_json(m: &models::Product) -> Json<models::Product> {
let r_message: models::Product = models::Product {
pid: String::from(&m.pid),
description: String::from(&m.description),
image: m.image.iter().cloned().collect(),
in_stock: m.in_stock,
name: String::from(&m.name),
price: m.price,
qty: m.qty,
};
Json(r_message)
}
/// Instead of putting `String::from("")` /// Instead of putting `String::from("")`
pub fn empty_string() -> String { pub fn empty_string() -> String {
String::from("") String::from("")
@ -248,7 +262,7 @@ pub const fn message_limit() -> usize {
} }
pub const fn image_limit() -> usize { pub const fn image_limit() -> usize {
2048 9999
} }
/// Generate application gpg keys at startup if none exist /// Generate application gpg keys at startup if none exist
@ -660,16 +674,18 @@ pub async fn can_transfer(invoice: u128) -> bool {
} }
/// Gui toggle for vendor mode /// Gui toggle for vendor mode
pub fn toggle_vendor_enabled() { pub fn toggle_vendor_enabled() -> bool {
let off: &str = "0"; let off: &str = "0";
let on: &str = "1"; let on: &str = "1";
let vendor_env = std::env::var(contact::NEVEKO_VENDOR_ENABLED).unwrap_or(String::from(off)); let vendor_env = std::env::var(contact::NEVEKO_VENDOR_ENABLED).unwrap_or(String::from(off));
if vendor_env == off { if vendor_env == off {
info!("neveko vendor mode enabled"); info!("neveko vendor mode enabled");
std::env::set_var(contact::NEVEKO_VENDOR_ENABLED, on); std::env::set_var(contact::NEVEKO_VENDOR_ENABLED, on);
return true;
} else { } else {
info!("neveko vendor mode disabled"); info!("neveko vendor mode disabled");
std::env::set_var(contact::NEVEKO_VENDOR_ENABLED, off); std::env::set_var(contact::NEVEKO_VENDOR_ENABLED, off);
return false;
} }
} }

View file

@ -5,10 +5,17 @@ use std::sync::mpsc::{
}; };
pub struct MarketApp { pub struct MarketApp {
is_product_image_set: bool,
is_showing_products: bool, is_showing_products: bool,
is_showing_product_image: bool,
is_showing_product_update: bool,
is_showing_orders: bool, is_showing_orders: bool,
is_vendor_enabled: bool,
orders: Vec<models::Order>, orders: Vec<models::Order>,
product_image: egui_extras::RetainedImage,
products: Vec<models::Product>, products: Vec<models::Product>,
product_update_pid: String,
new_product_image: String,
new_product_name: String, new_product_name: String,
new_product_desc: String, new_product_desc: String,
new_product_price: String, new_product_price: String,
@ -21,11 +28,19 @@ impl Default for MarketApp {
fn default() -> Self { fn default() -> Self {
let (_refresh_on_delete_product_tx, _refresh_on_delete_product_rx) = let (_refresh_on_delete_product_tx, _refresh_on_delete_product_rx) =
std::sync::mpsc::channel(); std::sync::mpsc::channel();
let read_product_image = std::fs::read("./assets/qr.png").unwrap_or(Vec::new());
MarketApp { MarketApp {
is_product_image_set: false,
is_showing_orders: false, is_showing_orders: false,
is_showing_products: false, is_showing_products: false,
is_showing_product_image: false,
is_showing_product_update: false,
is_vendor_enabled: false,
orders: Vec::new(), orders: Vec::new(),
product_image: egui_extras::RetainedImage::from_image_bytes("qr.png", &read_product_image).unwrap(),
products: Vec::new(), products: Vec::new(),
product_update_pid: utils::empty_string(),
new_product_image: utils::empty_string(),
new_product_name: utils::empty_string(), new_product_name: utils::empty_string(),
new_product_desc: utils::empty_string(), new_product_desc: utils::empty_string(),
new_product_price: utils::empty_string(), new_product_price: utils::empty_string(),
@ -41,6 +56,100 @@ impl eframe::App for MarketApp {
// Hook into async channel threads // Hook into async channel threads
//----------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------
// Product image window
//-----------------------------------------------------------------------------------
let mut is_showing_product_image = self.is_showing_product_image;
egui::Window::new("")
.open(&mut is_showing_product_image)
.vscroll(true)
.show(ctx, |ui| {
self.product_image.show(ui);
if ui.button("Exit").clicked() {
self.is_showing_product_image = false;
self.is_product_image_set = false;
let read_product_image = std::fs::read("./assets/qr.png").unwrap_or(Vec::new());
self.product_image = egui_extras::RetainedImage::from_image_bytes("qr.png", &read_product_image).unwrap();
}
});
// Update Product window
//-----------------------------------------------------------------------------------
let mut is_showing_product_update = self.is_showing_product_update;
egui::Window::new(format!("Update Product - {}", self.new_product_name))
.open(&mut is_showing_product_update)
.vscroll(true)
.show(ctx, |ui| {
ui.label(
"____________________________________________________________________________\n",
);
// TODO(c2m): file picker for images
ui.horizontal(|ui| {
let product_name = ui.label("image: \t\t\t");
ui.text_edit_singleline(&mut self.new_product_image)
.labelled_by(product_name.id);
ui.label("\t/path/to/image.png");
});
ui.horizontal(|ui| {
let product_name = ui.label("name: \t\t\t");
ui.text_edit_singleline(&mut self.new_product_name)
.labelled_by(product_name.id);
});
ui.horizontal(|ui| {
let product_desc = ui.label("description: \t");
ui.text_edit_singleline(&mut self.new_product_desc)
.labelled_by(product_desc.id);
});
ui.horizontal(|ui| {
let product_price = ui.label("price: \t\t\t");
ui.text_edit_singleline(&mut self.new_product_price)
.labelled_by(product_price.id);
ui.label("\t (piconeros)")
});
ui.horizontal(|ui| {
let product_qty = ui.label("quantity: \t\t");
ui.text_edit_singleline(&mut self.new_product_qty)
.labelled_by(product_qty.id);
});
ui.label("\n");
if ui.button("Update Product").clicked() {
let image: Vec<u8> = std::fs::read(self.new_product_image.clone()).unwrap_or_default();
let price = match self.new_product_price.parse::<u128>() {
Ok(p) => p,
Err(_) => 0,
};
let qty = match self.new_product_qty.parse::<u128>() {
Ok(q) => q,
Err(_) => 0,
};
let product: models::Product = models::Product {
pid: self.product_update_pid.clone(),
description: self.new_product_desc.clone(),
image,
in_stock: qty > 0,
name: self.new_product_name.clone(),
price,
qty,
};
let j_product = utils::product_to_json(&product);
product::modify(j_product);
self.new_product_desc = utils::empty_string();
self.new_product_name = utils::empty_string();
self.new_product_price = utils::empty_string();
self.new_product_qty = utils::empty_string();
self.new_product_image = utils::empty_string();
self.is_showing_product_update = false;
self.products = product::find_all();
}
if ui.button("Exit").clicked() {
self.new_product_desc = utils::empty_string();
self.new_product_name = utils::empty_string();
self.new_product_price = utils::empty_string();
self.new_product_qty = utils::empty_string();
self.new_product_image = utils::empty_string();
self.is_showing_product_update = false;
}
});
// Products window // Products window
//----------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------
let mut is_showing_products = self.is_showing_products; let mut is_showing_products = self.is_showing_products;
@ -59,9 +168,10 @@ impl eframe::App for MarketApp {
.resizable(true) .resizable(true)
.cell_layout(egui::Layout::left_to_right(egui::Align::Center)) .cell_layout(egui::Layout::left_to_right(egui::Align::Center))
.column(Column::auto()) .column(Column::auto())
.column(Column::initial(100.0).at_least(40.0).clip(true)) .column(Column::auto())
.column(Column::initial(100.0).at_least(40.0).clip(true)) .column(Column::auto())
.column(Column::initial(100.0).at_least(40.0).clip(true)) .column(Column::auto())
.column(Column::auto())
.column(Column::remainder()) .column(Column::remainder())
.min_scrolled_height(0.0); .min_scrolled_height(0.0);
@ -79,13 +189,16 @@ impl eframe::App for MarketApp {
header.col(|ui| { header.col(|ui| {
ui.strong("Quantity"); ui.strong("Quantity");
}); });
header.col(|ui| {
ui.strong("Image");
});
header.col(|ui| { header.col(|ui| {
ui.strong(""); ui.strong("");
}); });
}) })
.body(|mut body| { .body(|mut body| {
for p in &self.products { for p in &self.products {
let row_height = 200.0; let row_height = 20.0;
body.row(row_height, |mut row| { body.row(row_height, |mut row| {
row.col(|ui| { row.col(|ui| {
ui.label(format!("{}", p.name)); ui.label(format!("{}", p.name));
@ -99,17 +212,55 @@ impl eframe::App for MarketApp {
row.col(|ui| { row.col(|ui| {
ui.label(format!("{}", p.qty)); ui.label(format!("{}", p.qty));
}); });
row.col(|ui| {
if ui.button("View").clicked() {
if !self.is_product_image_set {
self.is_showing_product_image = true;
let file_path = format!(
"/home/{}/.neveko/{}.jpeg",
std::env::var("USER").unwrap_or(String::from("user")),
p.pid
);
// For the sake of brevity product list doesn't have image bytes, get them
let i_product = product::find(&p.pid);
match std::fs::write(&file_path, &i_product.image) {
Ok(w) => w,
Err(_) => log::error!("failed to write product image")
};
self.is_product_image_set = true;
let contents = std::fs::read(&file_path).unwrap_or(Vec::new());
if !i_product.image.is_empty() {
self.product_image =
egui_extras::RetainedImage::from_image_bytes(file_path, &contents).unwrap();
}
self.is_product_image_set = true;
ctx.request_repaint();
}
}
});
row.col(|ui| { row.col(|ui| {
ui.style_mut().wrap = Some(false); ui.style_mut().wrap = Some(false);
ui.horizontal(|_ui| { ui.horizontal(|ui| {
// update button if ui.button("Update").clicked() {
self.product_update_pid = p.pid.clone();
self.new_product_desc = p.description.clone();
self.new_product_name = p.name.clone();
self.new_product_price = format!("{}", p.price);
self.new_product_qty = format!("{}", p.qty);
self.is_showing_product_update = true;
}
}); });
}); });
}); });
} }
}); });
if ui.button("Exit").clicked() {
self.is_showing_products = false;
}
}); });
// TODO(c2m): Orders window
//-----------------------------------------------------------------------------------
egui::Window::new("Orders") egui::Window::new("Orders")
.open(&mut is_showing_orders) .open(&mut is_showing_orders)
.vscroll(true) .vscroll(true)
@ -133,16 +284,16 @@ impl eframe::App for MarketApp {
table table
.header(20.0, |mut header| { .header(20.0, |mut header| {
header.col(|ui| { header.col(|ui| {
ui.strong("Name"); ui.strong("");
}); });
header.col(|ui| { header.col(|ui| {
ui.strong("Description"); ui.strong("");
}); });
header.col(|ui| { header.col(|ui| {
ui.strong("Price"); ui.strong("");
}); });
header.col(|ui| { header.col(|ui| {
ui.strong("Quantity"); ui.strong("");
}); });
header.col(|ui| { header.col(|ui| {
ui.strong(""); ui.strong("");
@ -150,7 +301,7 @@ impl eframe::App for MarketApp {
}) })
.body(|mut body| { .body(|mut body| {
for o in &self.orders { for o in &self.orders {
let row_height = 200.0; let row_height = 20.0;
body.row(row_height, |mut row| { body.row(row_height, |mut row| {
row.col(|ui| { row.col(|ui| {
ui.label(format!("{}", o.cid)); ui.label(format!("{}", o.cid));
@ -178,38 +329,93 @@ impl eframe::App for MarketApp {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
if ui.button("Refresh").clicked() { if ui.button("Refresh").clicked() {
self.products = product::find_all(); self.products = product::find_all();
self.orders = order::find_all();
} }
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label("vendor mode: \t"); let vendor_mode: &str = if self.is_vendor_enabled {
"enabled"
} else {
"disabled"
};
ui.label(format!("vendor mode: {} \t", vendor_mode));
if ui.button("toggle").clicked() { if ui.button("toggle").clicked() {
utils::toggle_vendor_enabled(); self.is_vendor_enabled = utils::toggle_vendor_enabled();
} }
}); });
if ui.button("View Vendors").clicked() {
}
ui.label("\n"); ui.label("\n");
ui.heading("Add Product"); if ui.button("View Orders").clicked() {
ui.label(
"____________________________________________________________________________\n", }
); if self.is_vendor_enabled {
ui.horizontal(|ui| { ui.label("\n");
let product_name = ui.label("name: \t\t\t"); ui.heading("Add Product");
ui.text_edit_singleline(&mut self.new_product_name) ui.label(
.labelled_by(product_name.id); "____________________________________________________________________________\n",
}); );
ui.horizontal(|ui| { // TODO(c2m): file picker for images
let product_desc = ui.label("description: \t"); ui.horizontal(|ui| {
ui.text_edit_singleline(&mut self.new_product_desc) let product_name = ui.label("image: \t\t\t");
.labelled_by(product_desc.id); ui.text_edit_singleline(&mut self.new_product_image)
}); .labelled_by(product_name.id);
ui.horizontal(|ui| { ui.label("\t/path/to/image.png");
let product_price = ui.label("price: \t\t\t"); });
ui.text_edit_singleline(&mut self.new_product_price) ui.horizontal(|ui| {
.labelled_by(product_price.id); let product_name = ui.label("name: \t\t\t");
}); ui.text_edit_singleline(&mut self.new_product_name)
ui.horizontal(|ui| { .labelled_by(product_name.id);
let product_qty = ui.label("quantity: \t\t"); });
ui.text_edit_singleline(&mut self.new_product_qty) ui.horizontal(|ui| {
.labelled_by(product_qty.id); let product_desc = ui.label("description: \t");
}); ui.text_edit_singleline(&mut self.new_product_desc)
.labelled_by(product_desc.id);
});
ui.horizontal(|ui| {
let product_price = ui.label("price: \t\t\t");
ui.text_edit_singleline(&mut self.new_product_price)
.labelled_by(product_price.id);
ui.label("\t (piconeros)")
});
ui.horizontal(|ui| {
let product_qty = ui.label("quantity: \t\t");
ui.text_edit_singleline(&mut self.new_product_qty)
.labelled_by(product_qty.id);
});
if ui.button("Add Product").clicked() {
let image: Vec<u8> = std::fs::read(self.new_product_image.clone()).unwrap_or_default();
let price = match self.new_product_price.parse::<u128>() {
Ok(p) => p,
Err(_) => 0,
};
let qty = match self.new_product_qty.parse::<u128>() {
Ok(q) => q,
Err(_) => 0,
};
let product: models::Product = models::Product {
pid: utils::empty_string(),
description: self.new_product_desc.clone(),
image,
in_stock: qty > 0,
name: self.new_product_name.clone(),
price,
qty,
};
let j_product = utils::product_to_json(&product);
product::create(j_product);
self.new_product_desc = utils::empty_string();
self.new_product_name = utils::empty_string();
self.new_product_price = utils::empty_string();
self.new_product_qty = utils::empty_string();
self.new_product_image = utils::empty_string();
}
ui.label("\n");
if ui.button("View Products").clicked() {
self.products = product::find_all();
self.is_showing_products = true;
}
}
}); });
} }
} }