| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113 |
- 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<serde_json::Value>,
- loading_state: LoadingState,
- table_renderer: TableRenderer,
- show_column_panel: bool,
- edit_dialog: FormBuilder,
- pending_delete_ids: Vec<i64>,
- }
- 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<EditorField> = 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<serde_json::Value>,
- ) -> Result<Option<serde_json::Value>, 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::<serde_json::Value>(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<u32>) {
- 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<String> {
- 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<serde_json::Value> = None;
- let mut delete_template: Option<serde_json::Value> = None;
- let mut clone_template: Option<serde_json::Value> = None;
- struct TemplateEventHandler<'a> {
- edit_action: &'a mut Option<serde_json::Value>,
- delete_action: &'a mut Option<serde_json::Value>,
- clone_action: &'a mut Option<serde_json::Value>,
- }
- impl<'a> crate::core::table_renderer::TableEventHandler<serde_json::Value>
- 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::<Vec<_>>()
- );
- if let Some(api) = api_client {
- let mut id_from_updated: Option<i64> = 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::<i64>().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::<i64>().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);
- }
- }
- }
|