mirror of
https://github.com/creating2morrow/neveko.git
synced 2024-12-23 03:59:24 +00:00
add product management to vendor dashboard
This commit is contained in:
parent
6abb0551cd
commit
0d3539b8c4
3 changed files with 262 additions and 40 deletions
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,18 +329,39 @@ 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");
|
||||||
|
if ui.button("View Orders").clicked() {
|
||||||
|
|
||||||
|
}
|
||||||
|
if self.is_vendor_enabled {
|
||||||
ui.label("\n");
|
ui.label("\n");
|
||||||
ui.heading("Add Product");
|
ui.heading("Add Product");
|
||||||
ui.label(
|
ui.label(
|
||||||
"____________________________________________________________________________\n",
|
"____________________________________________________________________________\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| {
|
ui.horizontal(|ui| {
|
||||||
let product_name = ui.label("name: \t\t\t");
|
let product_name = ui.label("name: \t\t\t");
|
||||||
ui.text_edit_singleline(&mut self.new_product_name)
|
ui.text_edit_singleline(&mut self.new_product_name)
|
||||||
|
@ -204,12 +376,46 @@ impl eframe::App for MarketApp {
|
||||||
let product_price = ui.label("price: \t\t\t");
|
let product_price = ui.label("price: \t\t\t");
|
||||||
ui.text_edit_singleline(&mut self.new_product_price)
|
ui.text_edit_singleline(&mut self.new_product_price)
|
||||||
.labelled_by(product_price.id);
|
.labelled_by(product_price.id);
|
||||||
|
ui.label("\t (piconeros)")
|
||||||
});
|
});
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
let product_qty = ui.label("quantity: \t\t");
|
let product_qty = ui.label("quantity: \t\t");
|
||||||
ui.text_edit_singleline(&mut self.new_product_qty)
|
ui.text_edit_singleline(&mut self.new_product_qty)
|
||||||
.labelled_by(product_qty.id);
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue