use crate::api::ApiClient; use crate::core::asset_fields::AssetDropdownOptions; use crate::core::components::form_builder::FormBuilder; use crate::core::tables::get_templates; use crate::core::{ColumnConfig, LoadingState, TableRenderer}; use crate::core::{EditorField, FieldType}; use eframe::egui; pub struct TemplatesView { templates: Vec, loading_state: LoadingState, table_renderer: TableRenderer, show_column_panel: bool, edit_dialog: FormBuilder, pending_delete_ids: Vec, } impl TemplatesView { pub fn new() -> Self { let columns = vec![ ColumnConfig::new("ID", "id").with_width(60.0).hidden(), ColumnConfig::new("Template Code", "template_code").with_width(120.0), ColumnConfig::new("Name", "name").with_width(200.0), ColumnConfig::new("Asset Type", "asset_type").with_width(80.0), ColumnConfig::new("Description", "description").with_width(250.0), ColumnConfig::new("Asset Tag Generation String", "asset_tag_generation_string") .with_width(200.0), ColumnConfig::new("Label Template", "label_template_name") .with_width(120.0) .hidden(), ColumnConfig::new("Label Template ID", "label_template_id") .with_width(80.0) .hidden(), ColumnConfig::new("Audit Task", "audit_task_name") .with_width(140.0) .hidden(), ColumnConfig::new("Audit Task ID", "audit_task_id") .with_width(80.0) .hidden(), ColumnConfig::new("Category", "category_name").with_width(120.0), ColumnConfig::new("Manufacturer", "manufacturer") .with_width(120.0) .hidden(), ColumnConfig::new("Model", "model") .with_width(120.0) .hidden(), ColumnConfig::new("Zone", "zone_name") .with_width(100.0) .hidden(), ColumnConfig::new("Zone Code", "zone_code") .with_width(80.0) .hidden(), ColumnConfig::new("Zone+", "zone_plus") .with_width(100.0) .hidden(), ColumnConfig::new("Zone Note", "zone_note") .with_width(150.0) .hidden(), ColumnConfig::new("Status", "status") .with_width(100.0) .hidden(), ColumnConfig::new("Price", "price") .with_width(80.0) .hidden(), ColumnConfig::new("Purchase Date", "purchase_date") .with_width(100.0) .hidden(), ColumnConfig::new("Purchase Now?", "purchase_date_now") .with_width(110.0) .hidden(), ColumnConfig::new("Warranty Until", "warranty_until") .with_width(100.0) .hidden(), ColumnConfig::new("Warranty Auto?", "warranty_auto") .with_width(110.0) .hidden(), ColumnConfig::new("Warranty Amount", "warranty_auto_amount") .with_width(110.0) .hidden(), ColumnConfig::new("Warranty Unit", "warranty_auto_unit") .with_width(100.0) .hidden(), ColumnConfig::new("Expiry Date", "expiry_date") .with_width(100.0) .hidden(), ColumnConfig::new("Expiry Auto?", "expiry_auto") .with_width(100.0) .hidden(), ColumnConfig::new("Expiry Amount", "expiry_auto_amount") .with_width(110.0) .hidden(), ColumnConfig::new("Expiry Unit", "expiry_auto_unit") .with_width(90.0) .hidden(), ColumnConfig::new("Qty Total", "quantity_total") .with_width(80.0) .hidden(), ColumnConfig::new("Qty Used", "quantity_used") .with_width(80.0) .hidden(), ColumnConfig::new("Supplier", "supplier_name") .with_width(120.0) .hidden(), ColumnConfig::new("Lendable", "lendable") .with_width(80.0) .hidden(), ColumnConfig::new("Lending Status", "lending_status") .with_width(120.0) .hidden(), ColumnConfig::new("Min Role", "minimum_role_for_lending") .with_width(80.0) .hidden(), ColumnConfig::new("No Scan", "no_scan") .with_width(70.0) .hidden(), ColumnConfig::new("Notes", "notes") .with_width(200.0) .hidden(), ColumnConfig::new("Active", "active") .with_width(70.0) .hidden(), ColumnConfig::new("Created Date", "created_at") .with_width(140.0) .hidden(), ]; Self { templates: Vec::new(), loading_state: LoadingState::new(), table_renderer: TableRenderer::new() .with_columns(columns) .with_default_sort("created_at", false), show_column_panel: false, edit_dialog: FormBuilder::new("Template Editor", vec![]), pending_delete_ids: Vec::new(), } } fn prepare_template_edit_fields(&mut self, api_client: &ApiClient) { let options = AssetDropdownOptions::new(api_client); let fields: Vec = vec![ // Basic identifiers EditorField { name: "template_code".into(), label: "Template Code".into(), field_type: FieldType::Text, required: true, read_only: false, }, EditorField { name: "name".into(), label: "Template Name".into(), field_type: FieldType::Text, required: true, read_only: false, }, // Asset tag generation EditorField { name: "asset_tag_generation_string".into(), label: "Asset Tag Generation String".into(), field_type: FieldType::Text, required: false, read_only: false, }, // Type / status EditorField { name: "asset_type".into(), label: "Asset Type".into(), field_type: FieldType::Dropdown({ let mut asset_type_opts = vec![("".to_string(), "-- None --".to_string())]; asset_type_opts.extend(options.asset_types.clone()); asset_type_opts }), required: false, read_only: false, }, EditorField { name: "status".into(), label: "Default Status".into(), field_type: FieldType::Dropdown({ let mut status_opts = vec![("".to_string(), "-- None --".to_string())]; status_opts.extend(options.status_options.clone()); status_opts }), required: false, read_only: false, }, // Zone and zone-plus EditorField { name: "zone_id".into(), label: "Default Zone".into(), field_type: FieldType::Dropdown({ let mut zone_opts = vec![("".to_string(), "-- None --".to_string())]; zone_opts.extend(options.zone_options.clone()); zone_opts }), required: false, read_only: false, }, EditorField { name: "zone_plus".into(), label: "Zone+".into(), field_type: FieldType::Dropdown({ let mut zone_plus_opts = vec![("".to_string(), "-- None --".to_string())]; zone_plus_opts.extend(options.zone_plus_options.clone()); zone_plus_opts }), required: false, read_only: false, }, // No-scan option EditorField { name: "no_scan".into(), label: "No Scan".into(), field_type: FieldType::Dropdown(options.no_scan_options.clone()), required: false, read_only: false, }, // Purchase / warranty / expiry EditorField { name: "purchase_date".into(), label: "Purchase Date".into(), field_type: FieldType::Date, required: false, read_only: false, }, EditorField { name: "purchase_date_now".into(), label: "Use current date (Purchase)".into(), field_type: FieldType::Checkbox, required: false, read_only: false, }, EditorField { name: "warranty_until".into(), label: "Warranty Until".into(), field_type: FieldType::Date, required: false, read_only: false, }, EditorField { name: "warranty_auto".into(), label: "Auto-calc Warranty".into(), field_type: FieldType::Checkbox, required: false, read_only: false, }, EditorField { name: "warranty_auto_amount".into(), label: "Warranty Auto Amount".into(), field_type: FieldType::Text, required: false, read_only: false, }, EditorField { name: "warranty_auto_unit".into(), label: "Warranty Auto Unit".into(), field_type: FieldType::Dropdown(vec![ ("days".to_string(), "Days".to_string()), ("years".to_string(), "Years".to_string()), ]), required: false, read_only: false, }, EditorField { name: "expiry_date".into(), label: "Expiry Date".into(), field_type: FieldType::Date, required: false, read_only: false, }, EditorField { name: "expiry_auto".into(), label: "Auto-calc Expiry".into(), field_type: FieldType::Checkbox, required: false, read_only: false, }, EditorField { name: "expiry_auto_amount".into(), label: "Expiry Auto Amount".into(), field_type: FieldType::Text, required: false, read_only: false, }, EditorField { name: "expiry_auto_unit".into(), label: "Expiry Auto Unit".into(), field_type: FieldType::Dropdown(vec![ ("days".to_string(), "Days".to_string()), ("years".to_string(), "Years".to_string()), ]), required: false, read_only: false, }, // Financial / lending / supplier EditorField { name: "price".into(), label: "Price".into(), field_type: FieldType::Text, required: false, read_only: false, }, EditorField { name: "lendable".into(), label: "Lendable".into(), field_type: FieldType::Checkbox, required: false, read_only: false, }, EditorField { name: "lending_status".into(), label: "Lending Status".into(), field_type: FieldType::Dropdown({ let mut lending_status_opts = vec![("".to_string(), "-- None --".to_string())]; lending_status_opts.extend(options.lending_status_options.clone()); lending_status_opts }), required: false, read_only: false, }, EditorField { name: "supplier_id".into(), label: "Supplier".into(), field_type: FieldType::Dropdown({ let mut supplier_opts = vec![("".to_string(), "-- None --".to_string())]; supplier_opts.extend(options.supplier_options.clone()); supplier_opts }), required: false, read_only: false, }, // Label template EditorField { name: "label_template_id".into(), label: "Label Template".into(), field_type: FieldType::Dropdown({ let mut label_template_opts = vec![("".to_string(), "-- None --".to_string())]; label_template_opts.extend(options.label_template_options.clone()); label_template_opts }), required: false, read_only: false, }, EditorField { name: "audit_task_id".into(), label: "Default Audit Task".into(), field_type: FieldType::Dropdown(options.audit_task_options.clone()), required: false, read_only: false, }, // Defaults for created assets EditorField { name: "category_id".into(), label: "Default Category".into(), field_type: FieldType::Dropdown({ let mut category_opts = vec![("".to_string(), "-- None --".to_string())]; category_opts.extend(options.category_options.clone()); category_opts }), required: false, read_only: false, }, EditorField { name: "manufacturer".into(), label: "Default Manufacturer".into(), field_type: FieldType::Text, required: false, read_only: false, }, EditorField { name: "model".into(), label: "Default Model".into(), field_type: FieldType::Text, required: false, read_only: false, }, EditorField { name: "description".into(), label: "Description".into(), field_type: FieldType::MultilineText, required: false, read_only: false, }, EditorField { name: "additional_fields_json".into(), label: "Additional Fields (JSON)".into(), field_type: FieldType::MultilineText, required: false, read_only: false, }, ]; self.edit_dialog = FormBuilder::new("Template Editor", fields); } fn parse_additional_fields_input( raw: Option, ) -> Result, String> { match raw { Some(serde_json::Value::String(s)) => { let trimmed = s.trim(); if trimmed.is_empty() { Ok(Some(serde_json::Value::Null)) } else { serde_json::from_str::(trimmed) .map(Some) .map_err(|e| e.to_string()) } } Some(serde_json::Value::Null) => Ok(Some(serde_json::Value::Null)), Some(other) => Ok(Some(other)), None => Ok(None), } } fn load_templates(&mut self, api_client: &ApiClient, limit: Option) { self.loading_state.start_loading(); match get_templates(api_client, limit) { Ok(templates) => { self.templates = templates; self.loading_state.finish_success(); } Err(e) => { self.loading_state.finish_error(e.to_string()); } } } pub fn show( &mut self, ui: &mut egui::Ui, api_client: Option<&ApiClient>, ribbon: Option<&mut crate::ui::ribbon::RibbonUI>, ) -> Vec { let mut flags_to_clear = Vec::new(); // Get limit from ribbon let limit = ribbon .as_ref() .and_then(|r| r.number_fields.get("templates_limit")) .copied() .or(Some(200)); // Top toolbar ui.horizontal(|ui| { ui.heading("Templates"); if self.loading_state.is_loading { ui.spinner(); ui.label("Loading..."); } if let Some(err) = &self.loading_state.last_error { ui.colored_label(egui::Color32::RED, err); if ui.button("Refresh").clicked() { if let Some(api) = api_client { self.loading_state.last_error = None; self.load_templates(api, limit); } } } else if ui.button("Refresh").clicked() { if let Some(api) = api_client { self.load_templates(api, limit); } } ui.separator(); if ui.button("Columns").clicked() { self.show_column_panel = !self.show_column_panel; } }); ui.separator(); // Auto-load on first view if self.templates.is_empty() && !self.loading_state.is_loading && self.loading_state.last_error.is_none() && self.loading_state.last_load_time.is_none() { if let Some(api) = api_client { log::info!("Templates view never loaded, triggering initial auto-load"); self.load_templates(api, limit); } } // Handle ribbon events if let Some(ribbon) = ribbon.as_ref() { // Handle filter changes if *ribbon .checkboxes .get("templates_filter_changed") .unwrap_or(&false) { flags_to_clear.push("templates_filter_changed".to_string()); if let Some(client) = api_client { self.loading_state.last_error = None; // Get user-defined filters from FilterBuilder let user_filter = ribbon.filter_builder.get_filter_json("templates"); // Debug: Log the filter to see what we're getting if let Some(ref cf) = user_filter { log::info!("Template filter: {:?}", cf); } else { log::info!("No filter conditions (showing all templates)"); } self.load_templates(client, limit); return flags_to_clear; // Early return to avoid duplicate loading } } // Handle limit changes if *ribbon .checkboxes .get("templates_limit_changed") .unwrap_or(&false) { flags_to_clear.push("templates_limit_changed".to_string()); if let Some(client) = api_client { self.loading_state.last_error = None; self.load_templates(client, limit); } } // Handle ribbon actions if *ribbon .checkboxes .get("templates_action_new") .unwrap_or(&false) { flags_to_clear.push("templates_action_new".to_string()); // Prepare dynamic dropdown fields before opening dialog if let Some(client) = api_client { self.prepare_template_edit_fields(client); } // Open new template dialog with empty data (comprehensive fields for templates) let empty_template = serde_json::json!({ "id": "", "template_code": "", "name": "", "asset_type": "", "asset_tag_generation_string": "", "description": "", "additional_fields": null, "additional_fields_json": "{}", "category_id": "", "manufacturer": "", "model": "", "zone_id": "", "zone_plus": "", "status": "", "price": "", "warranty_until": "", "expiry_date": "", "quantity_total": "", "quantity_used": "", "supplier_id": "", "label_template_id": "", "audit_task_id": "", "lendable": false, "minimum_role_for_lending": "", "no_scan": "", "notes": "", "active": false }); self.edit_dialog.title = "Add New Template".to_string(); self.open_edit_template_dialog(empty_template); } if *ribbon .checkboxes .get("templates_action_edit") .unwrap_or(&false) { flags_to_clear.push("templates_action_edit".to_string()); // TODO: Implement edit selected template (requires selection tracking) log::info!( "Edit Template clicked (requires table selection - use double-click for now)" ); } if *ribbon .checkboxes .get("templates_action_delete") .unwrap_or(&false) { flags_to_clear.push("templates_action_delete".to_string()); // TODO: Implement delete selected templates (requires selection tracking) log::info!( "Delete Template clicked (requires table selection - use right-click for now)" ); } } // Render table with event handler let mut edit_template: Option = None; let mut delete_template: Option = None; let mut clone_template: Option = None; struct TemplateEventHandler<'a> { edit_action: &'a mut Option, delete_action: &'a mut Option, clone_action: &'a mut Option, } impl<'a> crate::core::table_renderer::TableEventHandler for TemplateEventHandler<'a> { fn on_double_click(&mut self, item: &serde_json::Value, _row_index: usize) { *self.edit_action = Some(item.clone()); } fn on_context_menu( &mut self, ui: &mut egui::Ui, item: &serde_json::Value, _row_index: usize, ) { if ui .button(format!("{} Edit Template", egui_phosphor::regular::PENCIL)) .clicked() { *self.edit_action = Some(item.clone()); ui.close(); } ui.separator(); if ui .button(format!("{} Clone Template", egui_phosphor::regular::COPY)) .clicked() { *self.clone_action = Some(item.clone()); ui.close(); } ui.separator(); if ui .button(format!("{} Delete Template", egui_phosphor::regular::TRASH)) .clicked() { *self.delete_action = Some(item.clone()); ui.close(); } } fn on_selection_changed(&mut self, _selected_indices: &[usize]) { // Not used for now } } let mut handler = TemplateEventHandler { edit_action: &mut edit_template, delete_action: &mut delete_template, clone_action: &mut clone_template, }; let prepared_data = self.table_renderer.prepare_json_data(&self.templates); self.table_renderer .render_json_table(ui, &prepared_data, Some(&mut handler)); // Process actions after rendering if let Some(template) = edit_template { // Prepare dynamic dropdown fields before opening dialog if let Some(client) = api_client { self.prepare_template_edit_fields(client); } self.open_edit_template_dialog(template); } if let Some(template) = delete_template { if let Some(id) = template.get("id").and_then(|v| v.as_i64()) { self.pending_delete_ids.push(id); } } // Handle clone action: open Add New dialog pre-filled with selected template values if let Some(template) = clone_template { // Prepare dynamic dropdown fields before opening dialog if let Some(client) = api_client { self.prepare_template_edit_fields(client); } // Use shared clone helper to prepare new-item payload let cloned = crate::core::components::prepare_cloned_value( &template, &["id", "template_code"], Some("name"), Some(""), ); self.edit_dialog.title = "Add New Template".to_string(); self.open_edit_template_dialog(cloned); } // Show column selector if open if self.show_column_panel { egui::Window::new("Column Configuration") .open(&mut self.show_column_panel) .resizable(true) .movable(true) .default_width(350.0) .min_width(300.0) .max_width(500.0) .max_height(600.0) .default_pos([200.0, 150.0]) .show(ui.ctx(), |ui| { ui.label("Show/Hide Columns:"); ui.separator(); // Scrollable area for columns egui::ScrollArea::vertical() .max_height(450.0) .show(ui, |ui| { // Use columns layout to make better use of width while keeping groups intact ui.columns(2, |columns| { // Left column columns[0].group(|ui| { ui.strong("Basic Information"); ui.separator(); for column in &mut self.table_renderer.columns { if matches!( column.field.as_str(), "template_code" | "name" | "asset_type" | "description" | "asset_tag_generation_string" | "label_template_name" | "label_template_id" ) { ui.checkbox(&mut column.visible, &column.name); } } }); columns[0].add_space(5.0); columns[0].group(|ui| { ui.strong("Classification"); ui.separator(); for column in &mut self.table_renderer.columns { if matches!( column.field.as_str(), "category_name" | "manufacturer" | "model" ) { ui.checkbox(&mut column.visible, &column.name); } } }); columns[0].add_space(5.0); columns[0].group(|ui| { ui.strong("Location & Status"); ui.separator(); for column in &mut self.table_renderer.columns { if matches!( column.field.as_str(), "zone_name" | "zone_code" | "zone_plus" | "zone_note" | "status" ) { ui.checkbox(&mut column.visible, &column.name); } } }); // Right column columns[1].group(|ui| { ui.strong("Financial, Dates & Auto-Calc"); ui.separator(); for column in &mut self.table_renderer.columns { if matches!( column.field.as_str(), "price" | "purchase_date" | "purchase_date_now" | "warranty_until" | "warranty_auto" | "warranty_auto_amount" | "warranty_auto_unit" | "expiry_date" | "expiry_auto" | "expiry_auto_amount" | "expiry_auto_unit" | "created_at" ) { ui.checkbox(&mut column.visible, &column.name); } } }); columns[1].add_space(5.0); columns[1].group(|ui| { ui.strong("Quantities & Lending"); ui.separator(); for column in &mut self.table_renderer.columns { if matches!( column.field.as_str(), "quantity_total" | "quantity_used" | "lendable" | "lending_status" | "minimum_role_for_lending" | "no_scan" ) { ui.checkbox(&mut column.visible, &column.name); } } }); columns[1].add_space(5.0); columns[1].group(|ui| { ui.strong("Metadata & Other"); ui.separator(); for column in &mut self.table_renderer.columns { if matches!( column.field.as_str(), "id" | "supplier_name" | "notes" | "active" ) { ui.checkbox(&mut column.visible, &column.name); } } }); }); }); ui.separator(); ui.columns(3, |columns| { if columns[0].button("Show All").clicked() { for column in &mut self.table_renderer.columns { column.visible = true; } } if columns[1].button("Hide All").clicked() { for column in &mut self.table_renderer.columns { column.visible = false; } } if columns[2].button("Reset to Default").clicked() { // Reset to default visibility (matching the initial setup) for column in &mut self.table_renderer.columns { column.visible = matches!( column.field.as_str(), "template_code" | "name" | "asset_type" | "description" | "category_name" ); } } }); }); } // Handle pending deletes if !self.pending_delete_ids.is_empty() { if let Some(api) = api_client { let ids_to_delete = self.pending_delete_ids.clone(); self.pending_delete_ids.clear(); for id in ids_to_delete { let where_clause = serde_json::json!({"id": id}); match api.delete("templates", where_clause) { Ok(resp) if resp.success => { log::info!("Template {} deleted successfully", id); } Ok(resp) => { self.loading_state.last_error = Some(format!("Delete failed: {:?}", resp.error)); } Err(e) => { self.loading_state.last_error = Some(format!("Delete error: {}", e)); } } } // Reload after deletes self.load_templates(api, limit); } } // Handle edit dialog save let ctx = ui.ctx(); if let Some(result) = self.edit_dialog.show_editor(ctx) { log::info!( "🎯 Templates received editor result: {:?}", result.is_some() ); if let Some(updated) = result { log::info!( "🎯 Processing template save with data keys: {:?}", updated.keys().collect::>() ); if let Some(api) = api_client { let mut id_from_updated: Option = None; if let Some(id_val) = updated.get("id") { log::info!("Raw id_val for template save: {:?}", id_val); id_from_updated = if let Some(s) = id_val.as_str() { if s.trim().is_empty() { None } else { s.trim().parse::().ok() } } else { id_val.as_i64() }; } else if let Some(meta_id_val) = updated.get("__editor_item_id") { log::info!( "No 'id' in diff; checking __editor_item_id: {:?}", meta_id_val ); id_from_updated = match meta_id_val { serde_json::Value::String(s) => { let s = s.trim(); if s.is_empty() { None } else { s.parse::().ok() } } serde_json::Value::Number(n) => n.as_i64(), _ => None, }; } if let Some(id) = id_from_updated { log::info!("Entering UPDATE template path for id {}", id); let mut cleaned = updated.clone(); cleaned.remove("__editor_item_id"); let additional_fields_update = match Self::parse_additional_fields_input( cleaned.remove("additional_fields_json"), ) { Ok(val) => val, Err(err) => { let msg = format!("Additional Fields JSON is invalid: {}", err); log::error!("{}", msg); self.loading_state.last_error = Some(msg); return flags_to_clear; } }; // Filter empty strings to NULL for UPDATE too let mut filtered_values = serde_json::Map::new(); for (key, value) in cleaned.iter() { if key.starts_with("__editor_") { continue; } match value { serde_json::Value::String(s) if s.trim().is_empty() => { filtered_values.insert(key.clone(), serde_json::Value::Null); } _ => { filtered_values.insert(key.clone(), value.clone()); } } } if let Some(val) = additional_fields_update { filtered_values.insert("additional_fields".to_string(), val); } let values = serde_json::Value::Object(filtered_values); let where_clause = serde_json::json!({"id": id}); log::info!( "Sending UPDATE request: values={:?} where={:?}", values, where_clause ); match api.update("templates", values, where_clause) { Ok(resp) if resp.success => { log::info!("Template {} updated successfully", id); self.load_templates(api, limit); } Ok(resp) => { let err = format!("Update failed: {:?}", resp.error); log::error!("{}", err); self.loading_state.last_error = Some(err); } Err(e) => { let err = format!("Update error: {}", e); log::error!("{}", err); self.loading_state.last_error = Some(err); } } } else { log::info!("🆕 Entering INSERT template path (no valid ID detected)"); let mut values = updated.clone(); values.remove("id"); values.remove("__editor_item_id"); let additional_fields_insert = match Self::parse_additional_fields_input( values.remove("additional_fields_json"), ) { Ok(val) => val, Err(err) => { let msg = format!("Additional Fields JSON is invalid: {}", err); log::error!("{}", msg); self.loading_state.last_error = Some(msg); return flags_to_clear; } }; let mut filtered_values = serde_json::Map::new(); for (key, value) in values.iter() { if key.starts_with("__editor_") { continue; } match value { serde_json::Value::String(s) if s.trim().is_empty() => { filtered_values.insert(key.clone(), serde_json::Value::Null); } _ => { filtered_values.insert(key.clone(), value.clone()); } } } if let Some(val) = additional_fields_insert { filtered_values.insert("additional_fields".to_string(), val); } let values = serde_json::Value::Object(filtered_values); log::info!("➡️ Sending INSERT request for template: {:?}", values); match api.insert("templates", values) { Ok(resp) if resp.success => { log::info!( "✅ New template created successfully (id={:?})", resp.data ); self.load_templates(api, limit); } Ok(resp) => { let error_msg = format!("Insert failed: {:?}", resp.error); log::error!("Template insert failed: {}", error_msg); self.loading_state.last_error = Some(error_msg); } Err(e) => { let error_msg = format!("Insert error: {}", e); log::error!("Template insert error: {}", error_msg); self.loading_state.last_error = Some(error_msg); } } } } } } flags_to_clear } fn open_edit_template_dialog(&mut self, mut template: serde_json::Value) { // Determine whether we are creating a new template (no ID or empty/zero ID) let is_new = match template.get("id") { Some(serde_json::Value::String(s)) => s.trim().is_empty(), Some(serde_json::Value::Number(n)) => n.as_i64().map(|id| id <= 0).unwrap_or(true), Some(serde_json::Value::Null) | None => true, _ => false, }; self.edit_dialog.title = if is_new { "Add New Template".to_string() } else { "Edit Template".to_string() }; if let Some(obj) = template.as_object_mut() { let pretty_json = if let Some(existing) = obj.get("additional_fields_json").and_then(|v| v.as_str()) { existing.to_string() } else { match obj.get("additional_fields") { Some(serde_json::Value::Null) | None => String::new(), Some(serde_json::Value::String(s)) => s.clone(), Some(value) => { serde_json::to_string_pretty(value).unwrap_or_else(|_| value.to_string()) } } }; obj.insert( "additional_fields_json".to_string(), serde_json::Value::String(pretty_json), ); } // Debug log to check the template data being passed to editor log::info!( "Template data for editor: {}", serde_json::to_string_pretty(&template) .unwrap_or_else(|_| "failed to serialize".to_string()) ); if is_new { // Use open_new so cloned templates keep their preset values when saved if let Some(obj) = template.as_object() { self.edit_dialog.open_new(Some(obj)); } else { self.edit_dialog.open_new(None); } } else { self.edit_dialog.open(&template); } } }