aboutsummaryrefslogtreecommitdiff
path: root/src/ui/ribbon.rs
diff options
context:
space:
mode:
authorUMTS at Teleco <crt@teleco.ch>2025-12-13 02:51:15 +0100
committerUMTS at Teleco <crt@teleco.ch>2025-12-13 02:51:15 +0100
commit8323fdd73272a2882781aba3c499ba0be3dff2a6 (patch)
treeffbf86473933e69cfaeef30d5c6ea7e5b494856c /src/ui/ribbon.rs
committing to insanityHEADmaster
Diffstat (limited to 'src/ui/ribbon.rs')
-rw-r--r--src/ui/ribbon.rs1056
1 files changed, 1056 insertions, 0 deletions
diff --git a/src/ui/ribbon.rs b/src/ui/ribbon.rs
new file mode 100644
index 0000000..0da355f
--- /dev/null
+++ b/src/ui/ribbon.rs
@@ -0,0 +1,1056 @@
+use crate::core::components::filter_builder::FilterBuilder;
+use eframe::egui;
+use egui_phosphor::variants::regular as icons;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+
+pub struct RibbonUI {
+ pub active_tab: String,
+ pub search_texts: HashMap<String, String>,
+ pub checkboxes: HashMap<String, bool>,
+ pub number_fields: HashMap<String, u32>,
+ pub filter_builder: FilterBuilder,
+}
+
+impl Default for RibbonUI {
+ fn default() -> Self {
+ let mut number_fields = HashMap::new();
+ number_fields.insert("inventory_limit".to_string(), 100);
+ number_fields.insert("templates_limit".to_string(), 200);
+
+ Self {
+ active_tab: "Dashboard".to_string(),
+ search_texts: HashMap::new(),
+ checkboxes: HashMap::new(),
+ number_fields,
+ filter_builder: FilterBuilder::new(),
+ }
+ }
+}
+
+impl RibbonUI {
+ pub fn preferred_height(&self) -> f32 {
+ 135.0
+ }
+
+ pub fn get_active_view(&self) -> Option<String> {
+ Some(self.active_tab.clone())
+ }
+
+ pub fn show(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) -> Option<String> {
+ // Clear one-shot trigger flags from previous frame so clicks only fire once
+ // NOTE: inventory_filter_changed and templates_filter_changed are NOT cleared here - they're cleared by their views after processing
+ for key in [
+ "item_lookup_trigger",
+ "inventory_limit_refresh_trigger",
+ // Inventory Actions
+ "inventory_action_add",
+ "inventory_action_delete",
+ "inventory_action_edit_easy",
+ "inventory_action_edit_adv",
+ "inventory_action_print_label",
+ // Inventory Quick Actions
+ "inventory_quick_inventarize_room",
+ "inventory_quick_add_multiple_from_template",
+ // Templates
+ "templates_limit_changed",
+ "templates_action_new",
+ "templates_action_edit",
+ "templates_action_delete",
+ ] {
+ self.checkboxes.insert(key.to_string(), false);
+ }
+
+ ui.vertical(|ui| {
+ // Tab headers row
+ ui.horizontal(|ui| {
+ let tabs = vec![
+ "Dashboard",
+ "Inventory",
+ "Categories",
+ "Zones",
+ "Borrowing",
+ "Audits",
+ "Suppliers",
+ "Issues",
+ "Printers",
+ "Label Templates",
+ "Item Templates",
+ ];
+ for tab in &tabs {
+ if ui.selectable_label(self.active_tab == *tab, *tab).clicked() {
+ self.active_tab = tab.to_string();
+ // Update filter columns based on the active tab
+ match *tab {
+ "Zones" => self.filter_builder.set_columns_for_context("zones"),
+ "Inventory" => self.filter_builder.set_columns_for_context("assets"),
+ _ => {}
+ }
+ }
+ }
+ });
+
+ ui.separator();
+
+ // Content area with fixed height I dont even know what this here fucking does tbh
+ let ribbon_height = 90.0;
+ ui.allocate_ui_with_layout(
+ egui::vec2(ui.available_width(), ribbon_height),
+ egui::Layout::left_to_right(egui::Align::Min),
+ |ui| {
+ egui::ScrollArea::horizontal().show(ui, |ui| {
+ ui.horizontal(|ui| match self.active_tab.as_str() {
+ "Dashboard" => self.show_dashboard_tab(ui, ribbon_height),
+ "Inventory" => self.show_inventory_tab(ui, ribbon_height),
+ "Categories" => self.show_categories_tab(ui, ribbon_height),
+ "Zones" => self.show_zones_tab(ui, ribbon_height),
+ "Borrowing" => self.show_borrowing_tab(ui, ribbon_height),
+ "Audits" => self.show_audits_tab(ui, ribbon_height),
+ "Item Templates" => self.show_templates_tab(ui, ribbon_height),
+ "Suppliers" => self.show_suppliers_tab(ui, ribbon_height),
+ "Issues" => self.show_issues_tab(ui, ribbon_height),
+ "Printers" => self.show_printers_tab(ui, ribbon_height),
+ "Label Templates" => self.show_label_templates_tab(ui, ribbon_height),
+ _ => {}
+ });
+ });
+ },
+ );
+ });
+ None
+ }
+
+ /// Render a 2-row grid of actions with consistent column widths and set checkbox triggers by key.
+ fn render_actions_grid_with_keys(
+ &mut self,
+ ui: &mut egui::Ui,
+ grid_id: &str,
+ items: &[(String, &str)],
+ ) {
+ let rows: usize = 2;
+ if items.is_empty() {
+ return;
+ }
+ let cols: usize = (items.len() + rows - 1) / rows;
+
+ let pad_x = ui.style().spacing.button_padding.x;
+ let row_height: f32 = 24.0;
+ let mut col_widths = vec![0.0f32; cols];
+ for col in 0..cols {
+ for row in 0..rows {
+ let idx = col * rows + row;
+ if idx < items.len() {
+ let text = &items[idx].0;
+ let text_width = 8.0 * text.len() as f32;
+ col_widths[col] = col_widths[col].max(text_width + pad_x * 2.0);
+ }
+ }
+ }
+
+ egui::Grid::new(grid_id)
+ .num_columns(cols)
+ .spacing(egui::vec2(8.0, 8.0))
+ .show(ui, |ui| {
+ for row in 0..rows {
+ for col in 0..cols {
+ let idx = col * rows + row;
+ if idx < items.len() {
+ let (label, key) = &items[idx];
+ let w = col_widths[col];
+ if ui
+ .add_sized([w, row_height], egui::Button::new(label.clone()))
+ .clicked()
+ {
+ self.checkboxes.insert(key.to_string(), true);
+ }
+ } else {
+ ui.allocate_space(egui::vec2(col_widths[col], row_height));
+ }
+ }
+ ui.end_row();
+ }
+ });
+ }
+
+ fn show_dashboard_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) {
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("View");
+ egui::ScrollArea::vertical()
+ .id_salt("dashboard_view_scroll")
+ .show(ui, |ui| if ui.button("Refresh").clicked() {});
+ });
+ });
+ // Stats section removed for now
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Widgets");
+ egui::ScrollArea::vertical()
+ .id_salt("dashboard_widgets_scroll")
+ .show(ui, |ui| {
+ ui.checkbox(
+ self.checkboxes
+ .entry("show_pie".to_string())
+ .or_insert(true),
+ "Show Pie Chart",
+ );
+ ui.checkbox(
+ self.checkboxes
+ .entry("show_timeline".to_string())
+ .or_insert(true),
+ "Show Timeline",
+ );
+ });
+ });
+ });
+ }
+
+ fn show_inventory_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) {
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Actions");
+ // 2x2 grid: Add, Edit, Remove, Print Label (Edit supports Alt for Advanced)
+ let labels = [
+ &format!("{} {}", icons::PLUS, "Add Item"),
+ &format!("{} {}", icons::PENCIL, "Edit"),
+ &format!("{} {}", icons::TRASH, "Remove"),
+ &format!("{} {}", icons::PRINTER, "Print Label"),
+ ]; // 4 labels -> 2x2 grid
+ let rows: usize = 2;
+ let cols: usize = 2;
+ let row_height: f32 = 24.0;
+ let pad_x = ui.style().spacing.button_padding.x;
+ // Compute column widths based on text
+ let mut col_widths = vec![0.0f32; cols];
+ for col in 0..cols {
+ for row in 0..rows {
+ let idx = col * rows + row;
+ let text = labels[idx];
+ let text_width = 8.0 * text.len() as f32;
+ col_widths[col] = col_widths[col].max(text_width + pad_x * 2.0);
+ }
+ }
+ egui::Grid::new("inventory_actions_grid")
+ .num_columns(cols)
+ .spacing(egui::vec2(8.0, 8.0))
+ .show(ui, |ui| {
+ for row in 0..rows {
+ for col in 0..cols {
+ let idx = col * rows + row;
+ let w = col_widths[col];
+ let button = egui::Button::new(labels[idx]);
+ let clicked = ui.add_sized([w, row_height], button).clicked();
+ if clicked {
+ // If user holds Alt while clicking Edit, trigger Advanced Edit instead of Easy
+ let alt_held = ui.input(|i| i.modifiers.alt);
+ match idx {
+ 0 => {
+ self.checkboxes
+ .insert("inventory_action_add".to_string(), true);
+ }
+ 1 => {
+ if alt_held {
+ self.checkboxes.insert(
+ "inventory_action_edit_adv".to_string(),
+ true,
+ );
+ } else {
+ self.checkboxes.insert(
+ "inventory_action_edit_easy".to_string(),
+ true,
+ );
+ }
+ }
+ 2 => {
+ self.checkboxes.insert(
+ "inventory_action_delete".to_string(),
+ true,
+ );
+ }
+ 3 => {
+ self.checkboxes.insert(
+ "inventory_action_print_label".to_string(),
+ true,
+ );
+ }
+ _ => {}
+ }
+ }
+ }
+ ui.end_row();
+ }
+ });
+ });
+ });
+ // Import/Export hidden for now (planned-only)
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.set_width(200.0); // Reduced width for compact design
+ ui.vertical(|ui| {
+ ui.label("Filter");
+ egui::ScrollArea::vertical()
+ .id_salt("inventory_filter_scroll")
+ .show(ui, |ui| {
+ // Compact FilterBuilder with popup
+ let filter_changed = self.filter_builder.show_compact(ui);
+ if filter_changed {
+ // Set trigger for filter application
+ self.checkboxes
+ .insert("inventory_filter_changed".to_string(), true);
+ }
+ });
+ });
+ });
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("View");
+ egui::ScrollArea::vertical()
+ .id_salt("inventory_view_scroll")
+ .show(ui, |ui| {
+ let old_show_retired =
+ self.checkboxes.get("show_retired").copied().unwrap_or(true);
+ let mut show_retired = old_show_retired;
+ ui.checkbox(&mut show_retired, "Show Retired");
+ if show_retired != old_show_retired {
+ self.checkboxes
+ .insert("show_retired".to_string(), show_retired);
+ self.checkboxes
+ .insert("inventory_filter_changed".to_string(), true);
+ }
+ if ui.button("❓ Help").clicked() {
+ self.checkboxes
+ .insert("inventory_show_help".to_string(), true);
+ }
+ });
+ });
+ });
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Quick Actions");
+ egui::ScrollArea::vertical()
+ .id_salt("inventory_quick_actions_scroll")
+ .show(ui, |ui| {
+ // Ensure both controls share the same visual width while stacking vertically
+ let pad_x = ui.style().spacing.button_padding.x;
+ let w1 = 8.0 * "Inventarize Room".len() as f32 + pad_x * 2.0;
+ let w2 = 8.0 * "Add from...".len() as f32 + pad_x * 2.0;
+ let w = w1.max(w2).max(130.0);
+
+ if ui
+ .add_sized([w, 24.0], egui::Button::new("Inventarize Room"))
+ .clicked()
+ {
+ self.checkboxes
+ .insert("inventory_quick_inventarize_room".to_string(), true);
+ }
+
+ // Use a fixed-width ComboBox as a dropdown to ensure equal width
+ egui::ComboBox::from_id_salt("inventory_add_from_combo")
+ .width(w)
+ .selected_text("Add from...")
+ .show_ui(ui, |ui| {
+ if ui.selectable_label(false, "Add from Template").clicked() {
+ self.checkboxes.insert(
+ "inventory_add_from_template_single".to_string(),
+ true,
+ );
+ }
+ if ui
+ .selectable_label(false, "Add Multiple from Template")
+ .clicked()
+ {
+ self.checkboxes.insert(
+ "inventory_add_from_template_multiple".to_string(),
+ true,
+ );
+ }
+ if ui
+ .selectable_label(false, "Add using Another Item")
+ .clicked()
+ {
+ self.checkboxes
+ .insert("inventory_add_from_item_single".to_string(), true);
+ }
+ if ui
+ .selectable_label(false, "Add Multiple from Another Item")
+ .clicked()
+ {
+ self.checkboxes.insert(
+ "inventory_add_from_item_multiple".to_string(),
+ true,
+ );
+ }
+ });
+ });
+ });
+ });
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Query");
+ egui::ScrollArea::vertical()
+ .id_salt("inventory_query_scroll")
+ .show(ui, |ui| {
+ ui.horizontal(|ui| {
+ ui.label("Limit:");
+ let limit = self
+ .number_fields
+ .entry("inventory_limit".to_string())
+ .or_insert(100);
+ let mut limit_str = limit.to_string();
+ let response = ui.add_sized(
+ [60.0, 20.0],
+ egui::TextEdit::singleline(&mut limit_str),
+ );
+
+ // Trigger refresh on Enter or when value changes
+ let enter_pressed = response.lost_focus()
+ && ui.input(|i| i.key_pressed(egui::Key::Enter));
+ if response.changed() || enter_pressed {
+ if let Ok(val) = limit_str.parse::<u32>() {
+ *limit = val;
+ if enter_pressed {
+ // Set trigger for inventory refresh
+ let trigger = self
+ .checkboxes
+ .entry("inventory_limit_refresh_trigger".to_string())
+ .or_insert(false);
+ *trigger = true;
+ }
+ }
+ }
+ });
+
+ let no_limit_changed = ui
+ .checkbox(
+ self.checkboxes
+ .entry("inventory_no_limit".to_string())
+ .or_insert(false),
+ "Maximum",
+ )
+ .changed();
+ if no_limit_changed {
+ // Trigger refresh when no limit checkbox changes
+ let trigger = self
+ .checkboxes
+ .entry("inventory_limit_refresh_trigger".to_string())
+ .or_insert(false);
+ *trigger = true;
+ }
+ });
+ });
+ });
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Lookup");
+ egui::ScrollArea::vertical()
+ .id_salt("inventory_lookup_scroll")
+ .show(ui, |ui| {
+ ui.label("Item Lookup:");
+ ui.horizontal(|ui| {
+ let lookup_text = self
+ .search_texts
+ .entry("item_lookup".to_string())
+ .or_insert_with(String::new);
+ let response = ui.add_sized(
+ [120.0, 30.0],
+ egui::TextEdit::singleline(lookup_text).hint_text("Tag or ID"),
+ );
+
+ // Trigger search on Enter key
+ let enter_pressed = response.lost_focus()
+ && ui.input(|i| i.key_pressed(egui::Key::Enter));
+
+ if ui.button("Search").clicked() || enter_pressed {
+ // Set trigger flag for inventory view to detect
+ let trigger = self
+ .checkboxes
+ .entry("item_lookup_trigger".to_string())
+ .or_insert(false);
+ *trigger = true;
+ }
+ });
+ });
+ });
+ });
+ }
+
+ fn show_categories_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) {
+ // Clear one-shot action triggers from previous frame
+ for key in [
+ "categories_refresh",
+ "categories_add",
+ "categories_edit",
+ "categories_delete",
+ ] {
+ self.checkboxes.insert(key.to_string(), false);
+ }
+
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Actions");
+ // Match Inventory layout: [Add, Edit, Delete, Refresh] (Refresh where Inventory has Print)
+ let items = vec![
+ (
+ format!("{} {}", icons::PLUS, "Add Category"),
+ "categories_add",
+ ),
+ (
+ format!("{} {}", icons::PENCIL, "Edit Category"),
+ "categories_edit",
+ ),
+ (
+ format!("{} {}", icons::TRASH, "Delete Category"),
+ "categories_delete",
+ ),
+ (
+ format!("{} {}", icons::ARROWS_CLOCKWISE, "Refresh"),
+ "categories_refresh",
+ ),
+ ];
+ self.render_actions_grid_with_keys(ui, "categories_actions_grid", &items);
+ });
+ });
+
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Search");
+ egui::ScrollArea::vertical()
+ .id_salt("categories_filter_scroll")
+ .show(ui, |ui| {
+ let search_text = self
+ .search_texts
+ .entry("categories_search".to_string())
+ .or_insert_with(String::new);
+ ui.text_edit_singleline(search_text);
+ });
+ });
+ });
+ }
+
+ fn show_zones_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) {
+ // Clear one-shot action triggers from previous frame
+ for key in [
+ "zones_action_add",
+ "zones_action_edit",
+ "zones_action_remove",
+ ] {
+ self.checkboxes.insert(key.to_string(), false);
+ }
+
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Actions");
+ let items = vec![
+ (
+ format!("{} {}", icons::PLUS, "Add Zone"),
+ "zones_action_add",
+ ),
+ (
+ format!("{} {}", icons::PENCIL, "Edit Zone"),
+ "zones_action_edit",
+ ),
+ ];
+ self.render_actions_grid_with_keys(ui, "zones_actions_grid", &items);
+ });
+ });
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.set_width(200.0); // Reduced width for compact design
+ ui.vertical(|ui| {
+ ui.label("Filter");
+ egui::ScrollArea::vertical()
+ .id_salt("zones_filter_scroll")
+ .show(ui, |ui| {
+ // Compact FilterBuilder with popup (same as Inventory)
+ let filter_changed = self.filter_builder.show_compact(ui);
+ if filter_changed {
+ // Set trigger for filter application
+ self.checkboxes
+ .insert("zones_filter_changed".to_string(), true);
+ }
+ });
+ });
+ });
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("View");
+ egui::ScrollArea::vertical()
+ .id_salt("zones_view_scroll")
+ .show(ui, |ui| {
+ // Use new key `zones_show_empty` but mirror to legacy key for compatibility
+ let show_empty = self
+ .checkboxes
+ .get("zones_show_empty")
+ .copied()
+ .unwrap_or(true);
+ let mut local = show_empty;
+ if ui.checkbox(&mut local, "Show Empty").changed() {
+ self.checkboxes
+ .insert("zones_show_empty".to_string(), local);
+ self.checkboxes
+ .insert("show_empty_zones".to_string(), local);
+ }
+ ui.checkbox(
+ self.checkboxes
+ .entry("zones_show_items".to_string())
+ .or_insert(true),
+ "Show Items in Zones",
+ );
+ });
+ });
+ });
+ }
+
+ fn show_borrowing_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) {
+ // Clear one-shot action triggers from previous frame
+ for key in [
+ "borrowing_action_checkout",
+ "borrowing_action_return",
+ "borrowing_action_register",
+ "borrowing_action_refresh",
+ ] {
+ self.checkboxes.insert(key.to_string(), false);
+ }
+
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Actions");
+ let items = vec![
+ (
+ format!("{} {}", icons::ARROW_LEFT, "Check Out"),
+ "borrowing_action_checkout",
+ ),
+ (
+ format!("{} {}", icons::ARROW_RIGHT, "Return"),
+ "borrowing_action_return",
+ ),
+ (
+ format!("{} {}", icons::PENCIL, "Register"),
+ "borrowing_action_register",
+ ),
+ (
+ format!("{} {}", icons::ARROWS_CLOCKWISE, "Refresh"),
+ "borrowing_action_refresh",
+ ),
+ ];
+ self.render_actions_grid_with_keys(ui, "borrowing_actions_grid", &items);
+ });
+ });
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.set_width(200.0);
+ ui.vertical(|ui| {
+ ui.label("Filter");
+ egui::ScrollArea::vertical()
+ .id_salt("borrowing_filter_scroll")
+ .show(ui, |ui| {
+ // Compact FilterBuilder with popup
+ let filter_changed = self.filter_builder.show_compact(ui);
+ if filter_changed {
+ // Set trigger for filter application
+ self.checkboxes
+ .insert("borrowing_filter_changed".to_string(), true);
+ }
+ });
+ });
+ });
+ }
+
+ fn show_audits_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) {
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Actions");
+ let items = vec![
+ ("New Audit".to_string(), "audits_action_new"),
+ ("View Audit".to_string(), "audits_action_view"),
+ ("Export Report".to_string(), "audits_action_export"),
+ ];
+ self.render_actions_grid_with_keys(ui, "audits_actions_grid", &items);
+ });
+ });
+ }
+
+ fn show_templates_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) {
+ // Clear one-shot action triggers from previous frame
+ for key in [
+ "templates_action_new",
+ "templates_action_edit",
+ "templates_action_delete",
+ ] {
+ self.checkboxes.insert(key.to_string(), false);
+ }
+
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Actions");
+ let items = vec![
+ (
+ format!("{} {}", icons::PLUS, "Add Template"),
+ "templates_action_new",
+ ),
+ (
+ format!("{} {}", icons::PENCIL, "Edit Template"),
+ "templates_action_edit",
+ ),
+ ];
+ self.render_actions_grid_with_keys(ui, "templates_actions_grid", &items);
+ });
+ });
+ // Import/Export removed for now
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.set_width(200.0); // Reduced width for compact design
+ ui.vertical(|ui| {
+ ui.label("Filter");
+ egui::ScrollArea::vertical()
+ .id_salt("templates_filter_scroll")
+ .show(ui, |ui| {
+ // Compact FilterBuilder with popup
+ let filter_changed = self.filter_builder.show_compact(ui);
+ if filter_changed {
+ // Set trigger for filter application
+ self.checkboxes
+ .insert("templates_filter_changed".to_string(), true);
+ }
+ });
+ });
+ });
+
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Query");
+ egui::ScrollArea::vertical()
+ .id_salt("templates_query_scroll")
+ .show(ui, |ui| {
+ ui.horizontal(|ui| {
+ ui.label("Limit:");
+ let limit = self
+ .number_fields
+ .entry("templates_limit".to_string())
+ .or_insert(200);
+ let mut limit_str = limit.to_string();
+ let response = ui.add_sized(
+ [60.0, 20.0],
+ egui::TextEdit::singleline(&mut limit_str),
+ );
+
+ // Trigger refresh on Enter or when value changes
+ let enter_pressed = response.lost_focus()
+ && ui.input(|i| i.key_pressed(egui::Key::Enter));
+ if response.changed() || enter_pressed {
+ if let Ok(val) = limit_str.parse::<u32>() {
+ *limit = val;
+ self.checkboxes
+ .insert("templates_limit_changed".to_string(), true);
+ }
+ }
+ });
+ });
+ });
+ });
+ }
+
+ fn show_issues_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) {
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Actions");
+ let items = vec![
+ ("Report Issue".to_string(), "issues_action_report"),
+ ("View Issue".to_string(), "issues_action_view"),
+ ("Resolve Issue".to_string(), "issues_action_resolve"),
+ ];
+ self.render_actions_grid_with_keys(ui, "issues_actions_grid", &items);
+ });
+ });
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Filter");
+ egui::ScrollArea::vertical()
+ .id_salt("issues_filter_scroll")
+ .show(ui, |ui| {
+ let search_text = self
+ .search_texts
+ .entry("issue_search".to_string())
+ .or_insert_with(String::new);
+ ui.text_edit_singleline(search_text);
+ });
+ });
+ });
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("View");
+ egui::ScrollArea::vertical()
+ .id_salt("issues_view_scroll")
+ .show(ui, |ui| {
+ ui.checkbox(
+ self.checkboxes
+ .entry("show_resolved".to_string())
+ .or_insert(false),
+ "Show Resolved",
+ );
+ ui.checkbox(
+ self.checkboxes
+ .entry("show_high_priority".to_string())
+ .or_insert(true),
+ "High Priority Only",
+ );
+ });
+ });
+ });
+ }
+
+ fn show_suppliers_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) {
+ for key in [
+ "suppliers_action_new",
+ "suppliers_action_edit",
+ "suppliers_action_delete",
+ ] {
+ self.checkboxes.insert(key.to_string(), false);
+ }
+
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Actions");
+ let items = vec![
+ (
+ format!("{} {}", icons::PLUS, "New Supplier"),
+ "suppliers_action_new",
+ ),
+ (
+ format!("{} {}", icons::PENCIL, "Edit Supplier"),
+ "suppliers_action_edit",
+ ),
+ ];
+ self.render_actions_grid_with_keys(ui, "suppliers_actions_grid", &items);
+ });
+ });
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Search");
+ egui::ScrollArea::vertical()
+ .id_salt("suppliers_filter_scroll")
+ .show(ui, |ui| {
+ let search_text = self
+ .search_texts
+ .entry("supplier_search".to_string())
+ .or_insert_with(String::new);
+ ui.text_edit_singleline(search_text);
+ });
+ });
+ });
+ }
+
+ pub fn show_printers_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) {
+ // Clear one-shot action triggers from previous frame
+ for key in [
+ "printers_action_add",
+ "printers_action_refresh",
+ "printers_view_print_history",
+ "printers_default_changed",
+ ] {
+ self.checkboxes.insert(key.to_string(), false);
+ }
+
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Actions");
+ let items = vec![
+ (
+ format!("{} {}", icons::PLUS, "Add Printer"),
+ "printers_action_add",
+ ),
+ (
+ format!("{} {}", icons::ARROWS_CLOCKWISE, "Refresh"),
+ "printers_action_refresh",
+ ),
+ ];
+ self.render_actions_grid_with_keys(ui, "printers_actions_grid", &items);
+ });
+ });
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Settings");
+ egui::ScrollArea::vertical()
+ .id_salt("printers_settings_scroll")
+ .show(ui, |ui| {
+ ui.label("Default Printer:");
+
+ // Get printer data injected by PrintersView
+ let printers_json = self
+ .search_texts
+ .get("_printers_list")
+ .cloned()
+ .unwrap_or_default();
+ let current_default_str = self
+ .search_texts
+ .get("_printers_current_default")
+ .cloned()
+ .unwrap_or_default();
+ let current_default = if current_default_str.is_empty() {
+ None
+ } else {
+ current_default_str.parse::<i64>().ok()
+ };
+
+ if printers_json.is_empty() {
+ ui.label("(Loading...)");
+ } else {
+ // Parse printer list
+ let printers: Vec<serde_json::Value> =
+ serde_json::from_str(&printers_json).unwrap_or_default();
+
+ let current_name = if let Some(id) = current_default {
+ printers
+ .iter()
+ .find(|p| p.get("id").and_then(|v| v.as_i64()) == Some(id))
+ .and_then(|p| p.get("printer_name"))
+ .and_then(|v| v.as_str())
+ .unwrap_or("Unknown")
+ .to_string()
+ } else {
+ "None".to_string()
+ };
+
+ egui::ComboBox::from_id_salt("ribbon_default_printer_selector")
+ .selected_text(&current_name)
+ .show_ui(ui, |ui| {
+ // None option
+ if ui
+ .selectable_label(current_default.is_none(), "None")
+ .clicked()
+ {
+ self.search_texts.insert(
+ "printers_default_id".to_string(),
+ "".to_string(),
+ );
+ self.checkboxes
+ .insert("printers_default_changed".to_string(), true);
+ }
+
+ // Printer options
+ for printer in &printers {
+ if let (Some(id), Some(name)) = (
+ printer.get("id").and_then(|v| v.as_i64()),
+ printer.get("printer_name").and_then(|v| v.as_str()),
+ ) {
+ let is_selected = current_default == Some(id);
+ if ui.selectable_label(is_selected, name).clicked() {
+ self.search_texts.insert(
+ "printers_default_id".to_string(),
+ id.to_string(),
+ );
+ self.checkboxes.insert(
+ "printers_default_changed".to_string(),
+ true,
+ );
+ }
+ }
+ }
+ });
+ }
+ });
+ });
+ });
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("View");
+ egui::ScrollArea::vertical()
+ .id_salt("printers_view_scroll")
+ .show(ui, |ui| {
+ if ui
+ .button(format!(
+ "{} {}",
+ icons::ARROWS_CLOCKWISE,
+ "View Print History"
+ ))
+ .clicked()
+ {
+ self.checkboxes
+ .insert("printers_view_print_history".to_string(), true);
+ }
+ });
+ });
+ });
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Search");
+ egui::ScrollArea::vertical()
+ .id_salt("printers_search_scroll")
+ .show(ui, |ui| {
+ let search_text = self
+ .search_texts
+ .entry("printers_search".to_string())
+ .or_insert_with(String::new);
+ ui.text_edit_singleline(search_text);
+ });
+ });
+ });
+ }
+
+ fn show_label_templates_tab(&mut self, ui: &mut egui::Ui, ribbon_height: f32) {
+ // Clear one-shot action triggers from previous frame
+ for key in ["labels_action_add", "labels_action_refresh"] {
+ self.checkboxes.insert(key.to_string(), false);
+ }
+
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Actions");
+ let items = vec![
+ (
+ format!("{} {}", icons::PLUS, "Add Template"),
+ "labels_action_add",
+ ),
+ (
+ format!("{} {}", icons::ARROWS_CLOCKWISE, "Refresh"),
+ "labels_action_refresh",
+ ),
+ ];
+ self.render_actions_grid_with_keys(ui, "labels_actions_grid", &items);
+ });
+ });
+ ui.group(|ui| {
+ ui.set_height(ribbon_height);
+ ui.vertical(|ui| {
+ ui.label("Search");
+ egui::ScrollArea::vertical()
+ .id_salt("labels_search_scroll")
+ .show(ui, |ui| {
+ let search_text = self
+ .search_texts
+ .entry("labels_search".to_string())
+ .or_insert_with(String::new);
+ ui.text_edit_singleline(search_text);
+ });
+ });
+ });
+ }
+}
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+pub struct RibbonConfig {}