templates.rs 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113
  1. use crate::api::ApiClient;
  2. use crate::core::asset_fields::AssetDropdownOptions;
  3. use crate::core::components::form_builder::FormBuilder;
  4. use crate::core::tables::get_templates;
  5. use crate::core::{ColumnConfig, LoadingState, TableRenderer};
  6. use crate::core::{EditorField, FieldType};
  7. use eframe::egui;
  8. pub struct TemplatesView {
  9. templates: Vec<serde_json::Value>,
  10. loading_state: LoadingState,
  11. table_renderer: TableRenderer,
  12. show_column_panel: bool,
  13. edit_dialog: FormBuilder,
  14. pending_delete_ids: Vec<i64>,
  15. }
  16. impl TemplatesView {
  17. pub fn new() -> Self {
  18. let columns = vec![
  19. ColumnConfig::new("ID", "id").with_width(60.0).hidden(),
  20. ColumnConfig::new("Template Code", "template_code").with_width(120.0),
  21. ColumnConfig::new("Name", "name").with_width(200.0),
  22. ColumnConfig::new("Asset Type", "asset_type").with_width(80.0),
  23. ColumnConfig::new("Description", "description").with_width(250.0),
  24. ColumnConfig::new("Asset Tag Generation String", "asset_tag_generation_string")
  25. .with_width(200.0),
  26. ColumnConfig::new("Label Template", "label_template_name")
  27. .with_width(120.0)
  28. .hidden(),
  29. ColumnConfig::new("Label Template ID", "label_template_id")
  30. .with_width(80.0)
  31. .hidden(),
  32. ColumnConfig::new("Audit Task", "audit_task_name")
  33. .with_width(140.0)
  34. .hidden(),
  35. ColumnConfig::new("Audit Task ID", "audit_task_id")
  36. .with_width(80.0)
  37. .hidden(),
  38. ColumnConfig::new("Category", "category_name").with_width(120.0),
  39. ColumnConfig::new("Manufacturer", "manufacturer")
  40. .with_width(120.0)
  41. .hidden(),
  42. ColumnConfig::new("Model", "model")
  43. .with_width(120.0)
  44. .hidden(),
  45. ColumnConfig::new("Zone", "zone_name")
  46. .with_width(100.0)
  47. .hidden(),
  48. ColumnConfig::new("Zone Code", "zone_code")
  49. .with_width(80.0)
  50. .hidden(),
  51. ColumnConfig::new("Zone+", "zone_plus")
  52. .with_width(100.0)
  53. .hidden(),
  54. ColumnConfig::new("Zone Note", "zone_note")
  55. .with_width(150.0)
  56. .hidden(),
  57. ColumnConfig::new("Status", "status")
  58. .with_width(100.0)
  59. .hidden(),
  60. ColumnConfig::new("Price", "price")
  61. .with_width(80.0)
  62. .hidden(),
  63. ColumnConfig::new("Purchase Date", "purchase_date")
  64. .with_width(100.0)
  65. .hidden(),
  66. ColumnConfig::new("Purchase Now?", "purchase_date_now")
  67. .with_width(110.0)
  68. .hidden(),
  69. ColumnConfig::new("Warranty Until", "warranty_until")
  70. .with_width(100.0)
  71. .hidden(),
  72. ColumnConfig::new("Warranty Auto?", "warranty_auto")
  73. .with_width(110.0)
  74. .hidden(),
  75. ColumnConfig::new("Warranty Amount", "warranty_auto_amount")
  76. .with_width(110.0)
  77. .hidden(),
  78. ColumnConfig::new("Warranty Unit", "warranty_auto_unit")
  79. .with_width(100.0)
  80. .hidden(),
  81. ColumnConfig::new("Expiry Date", "expiry_date")
  82. .with_width(100.0)
  83. .hidden(),
  84. ColumnConfig::new("Expiry Auto?", "expiry_auto")
  85. .with_width(100.0)
  86. .hidden(),
  87. ColumnConfig::new("Expiry Amount", "expiry_auto_amount")
  88. .with_width(110.0)
  89. .hidden(),
  90. ColumnConfig::new("Expiry Unit", "expiry_auto_unit")
  91. .with_width(90.0)
  92. .hidden(),
  93. ColumnConfig::new("Qty Total", "quantity_total")
  94. .with_width(80.0)
  95. .hidden(),
  96. ColumnConfig::new("Qty Used", "quantity_used")
  97. .with_width(80.0)
  98. .hidden(),
  99. ColumnConfig::new("Supplier", "supplier_name")
  100. .with_width(120.0)
  101. .hidden(),
  102. ColumnConfig::new("Lendable", "lendable")
  103. .with_width(80.0)
  104. .hidden(),
  105. ColumnConfig::new("Lending Status", "lending_status")
  106. .with_width(120.0)
  107. .hidden(),
  108. ColumnConfig::new("Min Role", "minimum_role_for_lending")
  109. .with_width(80.0)
  110. .hidden(),
  111. ColumnConfig::new("No Scan", "no_scan")
  112. .with_width(70.0)
  113. .hidden(),
  114. ColumnConfig::new("Notes", "notes")
  115. .with_width(200.0)
  116. .hidden(),
  117. ColumnConfig::new("Active", "active")
  118. .with_width(70.0)
  119. .hidden(),
  120. ColumnConfig::new("Created Date", "created_at")
  121. .with_width(140.0)
  122. .hidden(),
  123. ];
  124. Self {
  125. templates: Vec::new(),
  126. loading_state: LoadingState::new(),
  127. table_renderer: TableRenderer::new()
  128. .with_columns(columns)
  129. .with_default_sort("created_at", false),
  130. show_column_panel: false,
  131. edit_dialog: FormBuilder::new("Template Editor", vec![]),
  132. pending_delete_ids: Vec::new(),
  133. }
  134. }
  135. fn prepare_template_edit_fields(&mut self, api_client: &ApiClient) {
  136. let options = AssetDropdownOptions::new(api_client);
  137. let fields: Vec<EditorField> = vec![
  138. // Basic identifiers
  139. EditorField {
  140. name: "template_code".into(),
  141. label: "Template Code".into(),
  142. field_type: FieldType::Text,
  143. required: true,
  144. read_only: false,
  145. },
  146. EditorField {
  147. name: "name".into(),
  148. label: "Template Name".into(),
  149. field_type: FieldType::Text,
  150. required: true,
  151. read_only: false,
  152. },
  153. // Asset tag generation
  154. EditorField {
  155. name: "asset_tag_generation_string".into(),
  156. label: "Asset Tag Generation String".into(),
  157. field_type: FieldType::Text,
  158. required: false,
  159. read_only: false,
  160. },
  161. // Type / status
  162. EditorField {
  163. name: "asset_type".into(),
  164. label: "Asset Type".into(),
  165. field_type: FieldType::Dropdown({
  166. let mut asset_type_opts = vec![("".to_string(), "-- None --".to_string())];
  167. asset_type_opts.extend(options.asset_types.clone());
  168. asset_type_opts
  169. }),
  170. required: false,
  171. read_only: false,
  172. },
  173. EditorField {
  174. name: "status".into(),
  175. label: "Default Status".into(),
  176. field_type: FieldType::Dropdown({
  177. let mut status_opts = vec![("".to_string(), "-- None --".to_string())];
  178. status_opts.extend(options.status_options.clone());
  179. status_opts
  180. }),
  181. required: false,
  182. read_only: false,
  183. },
  184. // Zone and zone-plus
  185. EditorField {
  186. name: "zone_id".into(),
  187. label: "Default Zone".into(),
  188. field_type: FieldType::Dropdown({
  189. let mut zone_opts = vec![("".to_string(), "-- None --".to_string())];
  190. zone_opts.extend(options.zone_options.clone());
  191. zone_opts
  192. }),
  193. required: false,
  194. read_only: false,
  195. },
  196. EditorField {
  197. name: "zone_plus".into(),
  198. label: "Zone+".into(),
  199. field_type: FieldType::Dropdown({
  200. let mut zone_plus_opts = vec![("".to_string(), "-- None --".to_string())];
  201. zone_plus_opts.extend(options.zone_plus_options.clone());
  202. zone_plus_opts
  203. }),
  204. required: false,
  205. read_only: false,
  206. },
  207. // No-scan option
  208. EditorField {
  209. name: "no_scan".into(),
  210. label: "No Scan".into(),
  211. field_type: FieldType::Dropdown(options.no_scan_options.clone()),
  212. required: false,
  213. read_only: false,
  214. },
  215. // Purchase / warranty / expiry
  216. EditorField {
  217. name: "purchase_date".into(),
  218. label: "Purchase Date".into(),
  219. field_type: FieldType::Date,
  220. required: false,
  221. read_only: false,
  222. },
  223. EditorField {
  224. name: "purchase_date_now".into(),
  225. label: "Use current date (Purchase)".into(),
  226. field_type: FieldType::Checkbox,
  227. required: false,
  228. read_only: false,
  229. },
  230. EditorField {
  231. name: "warranty_until".into(),
  232. label: "Warranty Until".into(),
  233. field_type: FieldType::Date,
  234. required: false,
  235. read_only: false,
  236. },
  237. EditorField {
  238. name: "warranty_auto".into(),
  239. label: "Auto-calc Warranty".into(),
  240. field_type: FieldType::Checkbox,
  241. required: false,
  242. read_only: false,
  243. },
  244. EditorField {
  245. name: "warranty_auto_amount".into(),
  246. label: "Warranty Auto Amount".into(),
  247. field_type: FieldType::Text,
  248. required: false,
  249. read_only: false,
  250. },
  251. EditorField {
  252. name: "warranty_auto_unit".into(),
  253. label: "Warranty Auto Unit".into(),
  254. field_type: FieldType::Dropdown(vec![
  255. ("days".to_string(), "Days".to_string()),
  256. ("years".to_string(), "Years".to_string()),
  257. ]),
  258. required: false,
  259. read_only: false,
  260. },
  261. EditorField {
  262. name: "expiry_date".into(),
  263. label: "Expiry Date".into(),
  264. field_type: FieldType::Date,
  265. required: false,
  266. read_only: false,
  267. },
  268. EditorField {
  269. name: "expiry_auto".into(),
  270. label: "Auto-calc Expiry".into(),
  271. field_type: FieldType::Checkbox,
  272. required: false,
  273. read_only: false,
  274. },
  275. EditorField {
  276. name: "expiry_auto_amount".into(),
  277. label: "Expiry Auto Amount".into(),
  278. field_type: FieldType::Text,
  279. required: false,
  280. read_only: false,
  281. },
  282. EditorField {
  283. name: "expiry_auto_unit".into(),
  284. label: "Expiry Auto Unit".into(),
  285. field_type: FieldType::Dropdown(vec![
  286. ("days".to_string(), "Days".to_string()),
  287. ("years".to_string(), "Years".to_string()),
  288. ]),
  289. required: false,
  290. read_only: false,
  291. },
  292. // Financial / lending / supplier
  293. EditorField {
  294. name: "price".into(),
  295. label: "Price".into(),
  296. field_type: FieldType::Text,
  297. required: false,
  298. read_only: false,
  299. },
  300. EditorField {
  301. name: "lendable".into(),
  302. label: "Lendable".into(),
  303. field_type: FieldType::Checkbox,
  304. required: false,
  305. read_only: false,
  306. },
  307. EditorField {
  308. name: "lending_status".into(),
  309. label: "Lending Status".into(),
  310. field_type: FieldType::Dropdown({
  311. let mut lending_status_opts = vec![("".to_string(), "-- None --".to_string())];
  312. lending_status_opts.extend(options.lending_status_options.clone());
  313. lending_status_opts
  314. }),
  315. required: false,
  316. read_only: false,
  317. },
  318. EditorField {
  319. name: "supplier_id".into(),
  320. label: "Supplier".into(),
  321. field_type: FieldType::Dropdown({
  322. let mut supplier_opts = vec![("".to_string(), "-- None --".to_string())];
  323. supplier_opts.extend(options.supplier_options.clone());
  324. supplier_opts
  325. }),
  326. required: false,
  327. read_only: false,
  328. },
  329. // Label template
  330. EditorField {
  331. name: "label_template_id".into(),
  332. label: "Label Template".into(),
  333. field_type: FieldType::Dropdown({
  334. let mut label_template_opts = vec![("".to_string(), "-- None --".to_string())];
  335. label_template_opts.extend(options.label_template_options.clone());
  336. label_template_opts
  337. }),
  338. required: false,
  339. read_only: false,
  340. },
  341. EditorField {
  342. name: "audit_task_id".into(),
  343. label: "Default Audit Task".into(),
  344. field_type: FieldType::Dropdown(options.audit_task_options.clone()),
  345. required: false,
  346. read_only: false,
  347. },
  348. // Defaults for created assets
  349. EditorField {
  350. name: "category_id".into(),
  351. label: "Default Category".into(),
  352. field_type: FieldType::Dropdown({
  353. let mut category_opts = vec![("".to_string(), "-- None --".to_string())];
  354. category_opts.extend(options.category_options.clone());
  355. category_opts
  356. }),
  357. required: false,
  358. read_only: false,
  359. },
  360. EditorField {
  361. name: "manufacturer".into(),
  362. label: "Default Manufacturer".into(),
  363. field_type: FieldType::Text,
  364. required: false,
  365. read_only: false,
  366. },
  367. EditorField {
  368. name: "model".into(),
  369. label: "Default Model".into(),
  370. field_type: FieldType::Text,
  371. required: false,
  372. read_only: false,
  373. },
  374. EditorField {
  375. name: "description".into(),
  376. label: "Description".into(),
  377. field_type: FieldType::MultilineText,
  378. required: false,
  379. read_only: false,
  380. },
  381. EditorField {
  382. name: "additional_fields_json".into(),
  383. label: "Additional Fields (JSON)".into(),
  384. field_type: FieldType::MultilineText,
  385. required: false,
  386. read_only: false,
  387. },
  388. ];
  389. self.edit_dialog = FormBuilder::new("Template Editor", fields);
  390. }
  391. fn parse_additional_fields_input(
  392. raw: Option<serde_json::Value>,
  393. ) -> Result<Option<serde_json::Value>, String> {
  394. match raw {
  395. Some(serde_json::Value::String(s)) => {
  396. let trimmed = s.trim();
  397. if trimmed.is_empty() {
  398. Ok(Some(serde_json::Value::Null))
  399. } else {
  400. serde_json::from_str::<serde_json::Value>(trimmed)
  401. .map(Some)
  402. .map_err(|e| e.to_string())
  403. }
  404. }
  405. Some(serde_json::Value::Null) => Ok(Some(serde_json::Value::Null)),
  406. Some(other) => Ok(Some(other)),
  407. None => Ok(None),
  408. }
  409. }
  410. fn load_templates(&mut self, api_client: &ApiClient, limit: Option<u32>) {
  411. self.loading_state.start_loading();
  412. match get_templates(api_client, limit) {
  413. Ok(templates) => {
  414. self.templates = templates;
  415. self.loading_state.finish_success();
  416. }
  417. Err(e) => {
  418. self.loading_state.finish_error(e.to_string());
  419. }
  420. }
  421. }
  422. pub fn show(
  423. &mut self,
  424. ui: &mut egui::Ui,
  425. api_client: Option<&ApiClient>,
  426. ribbon: Option<&mut crate::ui::ribbon::RibbonUI>,
  427. ) -> Vec<String> {
  428. let mut flags_to_clear = Vec::new();
  429. // Get limit from ribbon
  430. let limit = ribbon
  431. .as_ref()
  432. .and_then(|r| r.number_fields.get("templates_limit"))
  433. .copied()
  434. .or(Some(200));
  435. // Top toolbar
  436. ui.horizontal(|ui| {
  437. ui.heading("Templates");
  438. if self.loading_state.is_loading {
  439. ui.spinner();
  440. ui.label("Loading...");
  441. }
  442. if let Some(err) = &self.loading_state.last_error {
  443. ui.colored_label(egui::Color32::RED, err);
  444. if ui.button("Refresh").clicked() {
  445. if let Some(api) = api_client {
  446. self.loading_state.last_error = None;
  447. self.load_templates(api, limit);
  448. }
  449. }
  450. } else if ui.button("Refresh").clicked() {
  451. if let Some(api) = api_client {
  452. self.load_templates(api, limit);
  453. }
  454. }
  455. ui.separator();
  456. if ui.button("Columns").clicked() {
  457. self.show_column_panel = !self.show_column_panel;
  458. }
  459. });
  460. ui.separator();
  461. // Auto-load on first view
  462. if self.templates.is_empty()
  463. && !self.loading_state.is_loading
  464. && self.loading_state.last_error.is_none()
  465. && self.loading_state.last_load_time.is_none()
  466. {
  467. if let Some(api) = api_client {
  468. log::info!("Templates view never loaded, triggering initial auto-load");
  469. self.load_templates(api, limit);
  470. }
  471. }
  472. // Handle ribbon events
  473. if let Some(ribbon) = ribbon.as_ref() {
  474. // Handle filter changes
  475. if *ribbon
  476. .checkboxes
  477. .get("templates_filter_changed")
  478. .unwrap_or(&false)
  479. {
  480. flags_to_clear.push("templates_filter_changed".to_string());
  481. if let Some(client) = api_client {
  482. self.loading_state.last_error = None;
  483. // Get user-defined filters from FilterBuilder
  484. let user_filter = ribbon.filter_builder.get_filter_json("templates");
  485. // Debug: Log the filter to see what we're getting
  486. if let Some(ref cf) = user_filter {
  487. log::info!("Template filter: {:?}", cf);
  488. } else {
  489. log::info!("No filter conditions (showing all templates)");
  490. }
  491. self.load_templates(client, limit);
  492. return flags_to_clear; // Early return to avoid duplicate loading
  493. }
  494. }
  495. // Handle limit changes
  496. if *ribbon
  497. .checkboxes
  498. .get("templates_limit_changed")
  499. .unwrap_or(&false)
  500. {
  501. flags_to_clear.push("templates_limit_changed".to_string());
  502. if let Some(client) = api_client {
  503. self.loading_state.last_error = None;
  504. self.load_templates(client, limit);
  505. }
  506. }
  507. // Handle ribbon actions
  508. if *ribbon
  509. .checkboxes
  510. .get("templates_action_new")
  511. .unwrap_or(&false)
  512. {
  513. flags_to_clear.push("templates_action_new".to_string());
  514. // Prepare dynamic dropdown fields before opening dialog
  515. if let Some(client) = api_client {
  516. self.prepare_template_edit_fields(client);
  517. }
  518. // Open new template dialog with empty data (comprehensive fields for templates)
  519. let empty_template = serde_json::json!({
  520. "id": "",
  521. "template_code": "",
  522. "name": "",
  523. "asset_type": "",
  524. "asset_tag_generation_string": "",
  525. "description": "",
  526. "additional_fields": null,
  527. "additional_fields_json": "{}",
  528. "category_id": "",
  529. "manufacturer": "",
  530. "model": "",
  531. "zone_id": "",
  532. "zone_plus": "",
  533. "status": "",
  534. "price": "",
  535. "warranty_until": "",
  536. "expiry_date": "",
  537. "quantity_total": "",
  538. "quantity_used": "",
  539. "supplier_id": "",
  540. "label_template_id": "",
  541. "audit_task_id": "",
  542. "lendable": false,
  543. "minimum_role_for_lending": "",
  544. "no_scan": "",
  545. "notes": "",
  546. "active": false
  547. });
  548. self.edit_dialog.title = "Add New Template".to_string();
  549. self.open_edit_template_dialog(empty_template);
  550. }
  551. if *ribbon
  552. .checkboxes
  553. .get("templates_action_edit")
  554. .unwrap_or(&false)
  555. {
  556. flags_to_clear.push("templates_action_edit".to_string());
  557. // TODO: Implement edit selected template (requires selection tracking)
  558. log::info!(
  559. "Edit Template clicked (requires table selection - use double-click for now)"
  560. );
  561. }
  562. if *ribbon
  563. .checkboxes
  564. .get("templates_action_delete")
  565. .unwrap_or(&false)
  566. {
  567. flags_to_clear.push("templates_action_delete".to_string());
  568. // TODO: Implement delete selected templates (requires selection tracking)
  569. log::info!(
  570. "Delete Template clicked (requires table selection - use right-click for now)"
  571. );
  572. }
  573. }
  574. // Render table with event handler
  575. let mut edit_template: Option<serde_json::Value> = None;
  576. let mut delete_template: Option<serde_json::Value> = None;
  577. let mut clone_template: Option<serde_json::Value> = None;
  578. struct TemplateEventHandler<'a> {
  579. edit_action: &'a mut Option<serde_json::Value>,
  580. delete_action: &'a mut Option<serde_json::Value>,
  581. clone_action: &'a mut Option<serde_json::Value>,
  582. }
  583. impl<'a> crate::core::table_renderer::TableEventHandler<serde_json::Value>
  584. for TemplateEventHandler<'a>
  585. {
  586. fn on_double_click(&mut self, item: &serde_json::Value, _row_index: usize) {
  587. *self.edit_action = Some(item.clone());
  588. }
  589. fn on_context_menu(
  590. &mut self,
  591. ui: &mut egui::Ui,
  592. item: &serde_json::Value,
  593. _row_index: usize,
  594. ) {
  595. if ui
  596. .button(format!("{} Edit Template", egui_phosphor::regular::PENCIL))
  597. .clicked()
  598. {
  599. *self.edit_action = Some(item.clone());
  600. ui.close();
  601. }
  602. ui.separator();
  603. if ui
  604. .button(format!("{} Clone Template", egui_phosphor::regular::COPY))
  605. .clicked()
  606. {
  607. *self.clone_action = Some(item.clone());
  608. ui.close();
  609. }
  610. ui.separator();
  611. if ui
  612. .button(format!("{} Delete Template", egui_phosphor::regular::TRASH))
  613. .clicked()
  614. {
  615. *self.delete_action = Some(item.clone());
  616. ui.close();
  617. }
  618. }
  619. fn on_selection_changed(&mut self, _selected_indices: &[usize]) {
  620. // Not used for now
  621. }
  622. }
  623. let mut handler = TemplateEventHandler {
  624. edit_action: &mut edit_template,
  625. delete_action: &mut delete_template,
  626. clone_action: &mut clone_template,
  627. };
  628. let prepared_data = self.table_renderer.prepare_json_data(&self.templates);
  629. self.table_renderer
  630. .render_json_table(ui, &prepared_data, Some(&mut handler));
  631. // Process actions after rendering
  632. if let Some(template) = edit_template {
  633. // Prepare dynamic dropdown fields before opening dialog
  634. if let Some(client) = api_client {
  635. self.prepare_template_edit_fields(client);
  636. }
  637. self.open_edit_template_dialog(template);
  638. }
  639. if let Some(template) = delete_template {
  640. if let Some(id) = template.get("id").and_then(|v| v.as_i64()) {
  641. self.pending_delete_ids.push(id);
  642. }
  643. }
  644. // Handle clone action: open Add New dialog pre-filled with selected template values
  645. if let Some(template) = clone_template {
  646. // Prepare dynamic dropdown fields before opening dialog
  647. if let Some(client) = api_client {
  648. self.prepare_template_edit_fields(client);
  649. }
  650. // Use shared clone helper to prepare new-item payload
  651. let cloned = crate::core::components::prepare_cloned_value(
  652. &template,
  653. &["id", "template_code"],
  654. Some("name"),
  655. Some(""),
  656. );
  657. self.edit_dialog.title = "Add New Template".to_string();
  658. self.open_edit_template_dialog(cloned);
  659. }
  660. // Show column selector if open
  661. if self.show_column_panel {
  662. egui::Window::new("Column Configuration")
  663. .open(&mut self.show_column_panel)
  664. .resizable(true)
  665. .movable(true)
  666. .default_width(350.0)
  667. .min_width(300.0)
  668. .max_width(500.0)
  669. .max_height(600.0)
  670. .default_pos([200.0, 150.0])
  671. .show(ui.ctx(), |ui| {
  672. ui.label("Show/Hide Columns:");
  673. ui.separator();
  674. // Scrollable area for columns
  675. egui::ScrollArea::vertical()
  676. .max_height(450.0)
  677. .show(ui, |ui| {
  678. // Use columns layout to make better use of width while keeping groups intact
  679. ui.columns(2, |columns| {
  680. // Left column
  681. columns[0].group(|ui| {
  682. ui.strong("Basic Information");
  683. ui.separator();
  684. for column in &mut self.table_renderer.columns {
  685. if matches!(
  686. column.field.as_str(),
  687. "template_code"
  688. | "name"
  689. | "asset_type"
  690. | "description"
  691. | "asset_tag_generation_string"
  692. | "label_template_name"
  693. | "label_template_id"
  694. ) {
  695. ui.checkbox(&mut column.visible, &column.name);
  696. }
  697. }
  698. });
  699. columns[0].add_space(5.0);
  700. columns[0].group(|ui| {
  701. ui.strong("Classification");
  702. ui.separator();
  703. for column in &mut self.table_renderer.columns {
  704. if matches!(
  705. column.field.as_str(),
  706. "category_name" | "manufacturer" | "model"
  707. ) {
  708. ui.checkbox(&mut column.visible, &column.name);
  709. }
  710. }
  711. });
  712. columns[0].add_space(5.0);
  713. columns[0].group(|ui| {
  714. ui.strong("Location & Status");
  715. ui.separator();
  716. for column in &mut self.table_renderer.columns {
  717. if matches!(
  718. column.field.as_str(),
  719. "zone_name"
  720. | "zone_code"
  721. | "zone_plus"
  722. | "zone_note"
  723. | "status"
  724. ) {
  725. ui.checkbox(&mut column.visible, &column.name);
  726. }
  727. }
  728. });
  729. // Right column
  730. columns[1].group(|ui| {
  731. ui.strong("Financial, Dates & Auto-Calc");
  732. ui.separator();
  733. for column in &mut self.table_renderer.columns {
  734. if matches!(
  735. column.field.as_str(),
  736. "price"
  737. | "purchase_date"
  738. | "purchase_date_now"
  739. | "warranty_until"
  740. | "warranty_auto"
  741. | "warranty_auto_amount"
  742. | "warranty_auto_unit"
  743. | "expiry_date"
  744. | "expiry_auto"
  745. | "expiry_auto_amount"
  746. | "expiry_auto_unit"
  747. | "created_at"
  748. ) {
  749. ui.checkbox(&mut column.visible, &column.name);
  750. }
  751. }
  752. });
  753. columns[1].add_space(5.0);
  754. columns[1].group(|ui| {
  755. ui.strong("Quantities & Lending");
  756. ui.separator();
  757. for column in &mut self.table_renderer.columns {
  758. if matches!(
  759. column.field.as_str(),
  760. "quantity_total"
  761. | "quantity_used"
  762. | "lendable"
  763. | "lending_status"
  764. | "minimum_role_for_lending"
  765. | "no_scan"
  766. ) {
  767. ui.checkbox(&mut column.visible, &column.name);
  768. }
  769. }
  770. });
  771. columns[1].add_space(5.0);
  772. columns[1].group(|ui| {
  773. ui.strong("Metadata & Other");
  774. ui.separator();
  775. for column in &mut self.table_renderer.columns {
  776. if matches!(
  777. column.field.as_str(),
  778. "id" | "supplier_name" | "notes" | "active"
  779. ) {
  780. ui.checkbox(&mut column.visible, &column.name);
  781. }
  782. }
  783. });
  784. });
  785. });
  786. ui.separator();
  787. ui.columns(3, |columns| {
  788. if columns[0].button("Show All").clicked() {
  789. for column in &mut self.table_renderer.columns {
  790. column.visible = true;
  791. }
  792. }
  793. if columns[1].button("Hide All").clicked() {
  794. for column in &mut self.table_renderer.columns {
  795. column.visible = false;
  796. }
  797. }
  798. if columns[2].button("Reset to Default").clicked() {
  799. // Reset to default visibility (matching the initial setup)
  800. for column in &mut self.table_renderer.columns {
  801. column.visible = matches!(
  802. column.field.as_str(),
  803. "template_code"
  804. | "name"
  805. | "asset_type"
  806. | "description"
  807. | "category_name"
  808. );
  809. }
  810. }
  811. });
  812. });
  813. }
  814. // Handle pending deletes
  815. if !self.pending_delete_ids.is_empty() {
  816. if let Some(api) = api_client {
  817. let ids_to_delete = self.pending_delete_ids.clone();
  818. self.pending_delete_ids.clear();
  819. for id in ids_to_delete {
  820. let where_clause = serde_json::json!({"id": id});
  821. match api.delete("templates", where_clause) {
  822. Ok(resp) if resp.success => {
  823. log::info!("Template {} deleted successfully", id);
  824. }
  825. Ok(resp) => {
  826. self.loading_state.last_error =
  827. Some(format!("Delete failed: {:?}", resp.error));
  828. }
  829. Err(e) => {
  830. self.loading_state.last_error = Some(format!("Delete error: {}", e));
  831. }
  832. }
  833. }
  834. // Reload after deletes
  835. self.load_templates(api, limit);
  836. }
  837. }
  838. // Handle edit dialog save
  839. let ctx = ui.ctx();
  840. if let Some(result) = self.edit_dialog.show_editor(ctx) {
  841. log::info!(
  842. "🎯 Templates received editor result: {:?}",
  843. result.is_some()
  844. );
  845. if let Some(updated) = result {
  846. log::info!(
  847. "🎯 Processing template save with data keys: {:?}",
  848. updated.keys().collect::<Vec<_>>()
  849. );
  850. if let Some(api) = api_client {
  851. let mut id_from_updated: Option<i64> = None;
  852. if let Some(id_val) = updated.get("id") {
  853. log::info!("Raw id_val for template save: {:?}", id_val);
  854. id_from_updated = if let Some(s) = id_val.as_str() {
  855. if s.trim().is_empty() {
  856. None
  857. } else {
  858. s.trim().parse::<i64>().ok()
  859. }
  860. } else {
  861. id_val.as_i64()
  862. };
  863. } else if let Some(meta_id_val) = updated.get("__editor_item_id") {
  864. log::info!(
  865. "No 'id' in diff; checking __editor_item_id: {:?}",
  866. meta_id_val
  867. );
  868. id_from_updated = match meta_id_val {
  869. serde_json::Value::String(s) => {
  870. let s = s.trim();
  871. if s.is_empty() {
  872. None
  873. } else {
  874. s.parse::<i64>().ok()
  875. }
  876. }
  877. serde_json::Value::Number(n) => n.as_i64(),
  878. _ => None,
  879. };
  880. }
  881. if let Some(id) = id_from_updated {
  882. log::info!("Entering UPDATE template path for id {}", id);
  883. let mut cleaned = updated.clone();
  884. cleaned.remove("__editor_item_id");
  885. let additional_fields_update = match Self::parse_additional_fields_input(
  886. cleaned.remove("additional_fields_json"),
  887. ) {
  888. Ok(val) => val,
  889. Err(err) => {
  890. let msg = format!("Additional Fields JSON is invalid: {}", err);
  891. log::error!("{}", msg);
  892. self.loading_state.last_error = Some(msg);
  893. return flags_to_clear;
  894. }
  895. };
  896. // Filter empty strings to NULL for UPDATE too
  897. let mut filtered_values = serde_json::Map::new();
  898. for (key, value) in cleaned.iter() {
  899. if key.starts_with("__editor_") {
  900. continue;
  901. }
  902. match value {
  903. serde_json::Value::String(s) if s.trim().is_empty() => {
  904. filtered_values.insert(key.clone(), serde_json::Value::Null);
  905. }
  906. _ => {
  907. filtered_values.insert(key.clone(), value.clone());
  908. }
  909. }
  910. }
  911. if let Some(val) = additional_fields_update {
  912. filtered_values.insert("additional_fields".to_string(), val);
  913. }
  914. let values = serde_json::Value::Object(filtered_values);
  915. let where_clause = serde_json::json!({"id": id});
  916. log::info!(
  917. "Sending UPDATE request: values={:?} where={:?}",
  918. values,
  919. where_clause
  920. );
  921. match api.update("templates", values, where_clause) {
  922. Ok(resp) if resp.success => {
  923. log::info!("Template {} updated successfully", id);
  924. self.load_templates(api, limit);
  925. }
  926. Ok(resp) => {
  927. let err = format!("Update failed: {:?}", resp.error);
  928. log::error!("{}", err);
  929. self.loading_state.last_error = Some(err);
  930. }
  931. Err(e) => {
  932. let err = format!("Update error: {}", e);
  933. log::error!("{}", err);
  934. self.loading_state.last_error = Some(err);
  935. }
  936. }
  937. } else {
  938. log::info!("🆕 Entering INSERT template path (no valid ID detected)");
  939. let mut values = updated.clone();
  940. values.remove("id");
  941. values.remove("__editor_item_id");
  942. let additional_fields_insert = match Self::parse_additional_fields_input(
  943. values.remove("additional_fields_json"),
  944. ) {
  945. Ok(val) => val,
  946. Err(err) => {
  947. let msg = format!("Additional Fields JSON is invalid: {}", err);
  948. log::error!("{}", msg);
  949. self.loading_state.last_error = Some(msg);
  950. return flags_to_clear;
  951. }
  952. };
  953. let mut filtered_values = serde_json::Map::new();
  954. for (key, value) in values.iter() {
  955. if key.starts_with("__editor_") {
  956. continue;
  957. }
  958. match value {
  959. serde_json::Value::String(s) if s.trim().is_empty() => {
  960. filtered_values.insert(key.clone(), serde_json::Value::Null);
  961. }
  962. _ => {
  963. filtered_values.insert(key.clone(), value.clone());
  964. }
  965. }
  966. }
  967. if let Some(val) = additional_fields_insert {
  968. filtered_values.insert("additional_fields".to_string(), val);
  969. }
  970. let values = serde_json::Value::Object(filtered_values);
  971. log::info!("➡️ Sending INSERT request for template: {:?}", values);
  972. match api.insert("templates", values) {
  973. Ok(resp) if resp.success => {
  974. log::info!(
  975. "✅ New template created successfully (id={:?})",
  976. resp.data
  977. );
  978. self.load_templates(api, limit);
  979. }
  980. Ok(resp) => {
  981. let error_msg = format!("Insert failed: {:?}", resp.error);
  982. log::error!("Template insert failed: {}", error_msg);
  983. self.loading_state.last_error = Some(error_msg);
  984. }
  985. Err(e) => {
  986. let error_msg = format!("Insert error: {}", e);
  987. log::error!("Template insert error: {}", error_msg);
  988. self.loading_state.last_error = Some(error_msg);
  989. }
  990. }
  991. }
  992. }
  993. }
  994. }
  995. flags_to_clear
  996. }
  997. fn open_edit_template_dialog(&mut self, mut template: serde_json::Value) {
  998. // Determine whether we are creating a new template (no ID or empty/zero ID)
  999. let is_new = match template.get("id") {
  1000. Some(serde_json::Value::String(s)) => s.trim().is_empty(),
  1001. Some(serde_json::Value::Number(n)) => n.as_i64().map(|id| id <= 0).unwrap_or(true),
  1002. Some(serde_json::Value::Null) | None => true,
  1003. _ => false,
  1004. };
  1005. self.edit_dialog.title = if is_new {
  1006. "Add New Template".to_string()
  1007. } else {
  1008. "Edit Template".to_string()
  1009. };
  1010. if let Some(obj) = template.as_object_mut() {
  1011. let pretty_json = if let Some(existing) =
  1012. obj.get("additional_fields_json").and_then(|v| v.as_str())
  1013. {
  1014. existing.to_string()
  1015. } else {
  1016. match obj.get("additional_fields") {
  1017. Some(serde_json::Value::Null) | None => String::new(),
  1018. Some(serde_json::Value::String(s)) => s.clone(),
  1019. Some(value) => {
  1020. serde_json::to_string_pretty(value).unwrap_or_else(|_| value.to_string())
  1021. }
  1022. }
  1023. };
  1024. obj.insert(
  1025. "additional_fields_json".to_string(),
  1026. serde_json::Value::String(pretty_json),
  1027. );
  1028. }
  1029. // Debug log to check the template data being passed to editor
  1030. log::info!(
  1031. "Template data for editor: {}",
  1032. serde_json::to_string_pretty(&template)
  1033. .unwrap_or_else(|_| "failed to serialize".to_string())
  1034. );
  1035. if is_new {
  1036. // Use open_new so cloned templates keep their preset values when saved
  1037. if let Some(obj) = template.as_object() {
  1038. self.edit_dialog.open_new(Some(obj));
  1039. } else {
  1040. self.edit_dialog.open_new(None);
  1041. }
  1042. } else {
  1043. self.edit_dialog.open(&template);
  1044. }
  1045. }
  1046. }