templates.rs 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144
  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. permissions: Option<&serde_json::Value>,
  428. ) -> Vec<String> {
  429. let mut flags_to_clear = Vec::new();
  430. // Get limit from ribbon
  431. let limit = ribbon
  432. .as_ref()
  433. .and_then(|r| r.number_fields.get("templates_limit"))
  434. .copied()
  435. .or(Some(200));
  436. // Top toolbar
  437. ui.horizontal(|ui| {
  438. ui.heading("Templates");
  439. if self.loading_state.is_loading {
  440. ui.spinner();
  441. ui.label("Loading...");
  442. }
  443. if let Some(err) = &self.loading_state.last_error {
  444. ui.colored_label(egui::Color32::RED, err);
  445. if ui.button("Refresh").clicked() {
  446. if let Some(api) = api_client {
  447. self.loading_state.last_error = None;
  448. self.load_templates(api, limit);
  449. }
  450. }
  451. } else if ui.button("Refresh").clicked() {
  452. if let Some(api) = api_client {
  453. self.load_templates(api, limit);
  454. }
  455. }
  456. ui.separator();
  457. if ui.button("Columns").clicked() {
  458. self.show_column_panel = !self.show_column_panel;
  459. }
  460. });
  461. ui.separator();
  462. // Auto-load on first view
  463. if self.templates.is_empty()
  464. && !self.loading_state.is_loading
  465. && self.loading_state.last_error.is_none()
  466. && self.loading_state.last_load_time.is_none()
  467. {
  468. if let Some(api) = api_client {
  469. log::info!("Templates view never loaded, triggering initial auto-load");
  470. self.load_templates(api, limit);
  471. }
  472. }
  473. // Handle ribbon events
  474. if let Some(ribbon) = ribbon.as_ref() {
  475. // Handle filter changes
  476. if *ribbon
  477. .checkboxes
  478. .get("templates_filter_changed")
  479. .unwrap_or(&false)
  480. {
  481. flags_to_clear.push("templates_filter_changed".to_string());
  482. if let Some(client) = api_client {
  483. self.loading_state.last_error = None;
  484. // Get user-defined filters from FilterBuilder
  485. let user_filter = ribbon.filter_builder.get_filter_json("templates");
  486. // Debug: Log the filter to see what we're getting
  487. if let Some(ref cf) = user_filter {
  488. log::info!("Template filter: {:?}", cf);
  489. } else {
  490. log::info!("No filter conditions (showing all templates)");
  491. }
  492. self.load_templates(client, limit);
  493. return flags_to_clear; // Early return to avoid duplicate loading
  494. }
  495. }
  496. // Handle limit changes
  497. if *ribbon
  498. .checkboxes
  499. .get("templates_limit_changed")
  500. .unwrap_or(&false)
  501. {
  502. flags_to_clear.push("templates_limit_changed".to_string());
  503. if let Some(client) = api_client {
  504. self.loading_state.last_error = None;
  505. self.load_templates(client, limit);
  506. }
  507. }
  508. // Handle ribbon actions
  509. if *ribbon
  510. .checkboxes
  511. .get("templates_action_new")
  512. .unwrap_or(&false)
  513. {
  514. flags_to_clear.push("templates_action_new".to_string());
  515. if crate::ui::ribbon::RibbonUI::check_permission(permissions, "create_template") {
  516. // Prepare dynamic dropdown fields before opening dialog
  517. if let Some(client) = api_client {
  518. self.prepare_template_edit_fields(client);
  519. }
  520. // Open new template dialog with empty data (comprehensive fields for templates)
  521. let empty_template = serde_json::json!({
  522. "id": "",
  523. "template_code": "",
  524. "name": "",
  525. "asset_type": "",
  526. "asset_tag_generation_string": "",
  527. "description": "",
  528. "additional_fields": null,
  529. "additional_fields_json": "{}",
  530. "category_id": "",
  531. "manufacturer": "",
  532. "model": "",
  533. "zone_id": "",
  534. "zone_plus": "",
  535. "status": "",
  536. "price": "",
  537. "warranty_until": "",
  538. "expiry_date": "",
  539. "quantity_total": "",
  540. "quantity_used": "",
  541. "supplier_id": "",
  542. "label_template_id": "",
  543. "audit_task_id": "",
  544. "lendable": false,
  545. "minimum_role_for_lending": "",
  546. "no_scan": "",
  547. "notes": "",
  548. "active": false
  549. });
  550. self.edit_dialog.title = "Add New Template".to_string();
  551. self.open_edit_template_dialog(empty_template);
  552. }
  553. }
  554. if *ribbon
  555. .checkboxes
  556. .get("templates_action_edit")
  557. .unwrap_or(&false)
  558. {
  559. flags_to_clear.push("templates_action_edit".to_string());
  560. if crate::ui::ribbon::RibbonUI::check_permission(permissions, "edit_template") {
  561. // TODO: Implement edit selected template (requires selection tracking)
  562. log::info!(
  563. "Edit Template clicked (requires table selection - use double-click for now)"
  564. );
  565. }
  566. }
  567. if *ribbon
  568. .checkboxes
  569. .get("templates_action_delete")
  570. .unwrap_or(&false)
  571. {
  572. flags_to_clear.push("templates_action_delete".to_string());
  573. if crate::ui::ribbon::RibbonUI::check_permission(permissions, "delete_template") {
  574. // TODO: Implement delete selected templates (requires selection tracking)
  575. log::info!(
  576. "Delete Template clicked (requires table selection - use right-click for now)"
  577. );
  578. }
  579. }
  580. }
  581. // Render table with event handler
  582. let mut edit_template: Option<serde_json::Value> = None;
  583. let mut delete_template: Option<serde_json::Value> = None;
  584. let mut clone_template: Option<serde_json::Value> = None;
  585. struct TemplateEventHandler<'a> {
  586. edit_action: &'a mut Option<serde_json::Value>,
  587. delete_action: &'a mut Option<serde_json::Value>,
  588. clone_action: &'a mut Option<serde_json::Value>,
  589. }
  590. impl<'a> crate::core::table_renderer::TableEventHandler<serde_json::Value>
  591. for TemplateEventHandler<'a>
  592. {
  593. fn on_double_click(&mut self, item: &serde_json::Value, _row_index: usize) {
  594. *self.edit_action = Some(item.clone());
  595. }
  596. fn on_context_menu(
  597. &mut self,
  598. ui: &mut egui::Ui,
  599. item: &serde_json::Value,
  600. _row_index: usize,
  601. ) {
  602. if ui
  603. .button(format!("{} Edit Template", egui_phosphor::regular::PENCIL))
  604. .clicked()
  605. {
  606. *self.edit_action = Some(item.clone());
  607. ui.close();
  608. }
  609. ui.separator();
  610. if ui
  611. .button(format!("{} Clone Template", egui_phosphor::regular::COPY))
  612. .clicked()
  613. {
  614. *self.clone_action = Some(item.clone());
  615. ui.close();
  616. }
  617. ui.separator();
  618. if ui
  619. .button(format!("{} Delete Template", egui_phosphor::regular::TRASH))
  620. .clicked()
  621. {
  622. *self.delete_action = Some(item.clone());
  623. ui.close();
  624. }
  625. }
  626. fn on_selection_changed(&mut self, _selected_indices: &[usize]) {
  627. // Not used for now
  628. }
  629. }
  630. let mut handler = TemplateEventHandler {
  631. edit_action: &mut edit_template,
  632. delete_action: &mut delete_template,
  633. clone_action: &mut clone_template,
  634. };
  635. let prepared_data = self.table_renderer.prepare_json_data(&self.templates);
  636. self.table_renderer
  637. .render_json_table(ui, &prepared_data, Some(&mut handler));
  638. // Process actions after rendering
  639. if let Some(template) = edit_template {
  640. // Prepare dynamic dropdown fields before opening dialog
  641. if let Some(client) = api_client {
  642. self.prepare_template_edit_fields(client);
  643. }
  644. self.open_edit_template_dialog(template);
  645. }
  646. if let Some(template) = delete_template {
  647. if let Some(id) = template.get("id").and_then(|v| v.as_i64()) {
  648. self.pending_delete_ids.push(id);
  649. }
  650. }
  651. // Handle clone action: open Add New dialog pre-filled with selected template values
  652. if let Some(template) = clone_template {
  653. // Prepare dynamic dropdown fields before opening dialog
  654. if let Some(client) = api_client {
  655. self.prepare_template_edit_fields(client);
  656. }
  657. // Use shared clone helper to prepare new-item payload
  658. let cloned = crate::core::components::prepare_cloned_value(
  659. &template,
  660. &["id", "template_code"],
  661. Some("name"),
  662. Some(""),
  663. );
  664. self.edit_dialog.title = "Add New Template".to_string();
  665. self.open_edit_template_dialog(cloned);
  666. }
  667. // Show column selector if open
  668. if self.show_column_panel {
  669. egui::Window::new("Column Configuration")
  670. .open(&mut self.show_column_panel)
  671. .resizable(true)
  672. .movable(true)
  673. .default_width(350.0)
  674. .min_width(300.0)
  675. .max_width(500.0)
  676. .max_height(600.0)
  677. .default_pos([200.0, 150.0])
  678. .show(ui.ctx(), |ui| {
  679. ui.label("Show/Hide Columns:");
  680. ui.separator();
  681. // Scrollable area for columns
  682. egui::ScrollArea::vertical()
  683. .max_height(450.0)
  684. .show(ui, |ui| {
  685. // Use columns layout to make better use of width while keeping groups intact
  686. ui.columns(2, |columns| {
  687. // Left column
  688. columns[0].group(|ui| {
  689. ui.strong("Basic Information");
  690. ui.separator();
  691. for column in &mut self.table_renderer.columns {
  692. if matches!(
  693. column.field.as_str(),
  694. "template_code"
  695. | "name"
  696. | "asset_type"
  697. | "description"
  698. | "asset_tag_generation_string"
  699. | "label_template_name"
  700. | "label_template_id"
  701. ) {
  702. ui.checkbox(&mut column.visible, &column.name);
  703. }
  704. }
  705. });
  706. columns[0].add_space(5.0);
  707. columns[0].group(|ui| {
  708. ui.strong("Classification");
  709. ui.separator();
  710. for column in &mut self.table_renderer.columns {
  711. if matches!(
  712. column.field.as_str(),
  713. "category_name" | "manufacturer" | "model"
  714. ) {
  715. ui.checkbox(&mut column.visible, &column.name);
  716. }
  717. }
  718. });
  719. columns[0].add_space(5.0);
  720. columns[0].group(|ui| {
  721. ui.strong("Location & Status");
  722. ui.separator();
  723. for column in &mut self.table_renderer.columns {
  724. if matches!(
  725. column.field.as_str(),
  726. "zone_name"
  727. | "zone_code"
  728. | "zone_plus"
  729. | "zone_note"
  730. | "status"
  731. ) {
  732. ui.checkbox(&mut column.visible, &column.name);
  733. }
  734. }
  735. });
  736. // Right column
  737. columns[1].group(|ui| {
  738. ui.strong("Financial, Dates & Auto-Calc");
  739. ui.separator();
  740. for column in &mut self.table_renderer.columns {
  741. if matches!(
  742. column.field.as_str(),
  743. "price"
  744. | "purchase_date"
  745. | "purchase_date_now"
  746. | "warranty_until"
  747. | "warranty_auto"
  748. | "warranty_auto_amount"
  749. | "warranty_auto_unit"
  750. | "expiry_date"
  751. | "expiry_auto"
  752. | "expiry_auto_amount"
  753. | "expiry_auto_unit"
  754. | "created_at"
  755. ) {
  756. ui.checkbox(&mut column.visible, &column.name);
  757. }
  758. }
  759. });
  760. columns[1].add_space(5.0);
  761. columns[1].group(|ui| {
  762. ui.strong("Quantities & Lending");
  763. ui.separator();
  764. for column in &mut self.table_renderer.columns {
  765. if matches!(
  766. column.field.as_str(),
  767. "quantity_total"
  768. | "quantity_used"
  769. | "lendable"
  770. | "lending_status"
  771. | "minimum_role_for_lending"
  772. | "no_scan"
  773. ) {
  774. ui.checkbox(&mut column.visible, &column.name);
  775. }
  776. }
  777. });
  778. columns[1].add_space(5.0);
  779. columns[1].group(|ui| {
  780. ui.strong("Metadata & Other");
  781. ui.separator();
  782. for column in &mut self.table_renderer.columns {
  783. if matches!(
  784. column.field.as_str(),
  785. "id" | "supplier_name" | "notes" | "active"
  786. ) {
  787. ui.checkbox(&mut column.visible, &column.name);
  788. }
  789. }
  790. });
  791. });
  792. });
  793. ui.separator();
  794. ui.columns(3, |columns| {
  795. if columns[0].button("Show All").clicked() {
  796. for column in &mut self.table_renderer.columns {
  797. column.visible = true;
  798. }
  799. }
  800. if columns[1].button("Hide All").clicked() {
  801. for column in &mut self.table_renderer.columns {
  802. column.visible = false;
  803. }
  804. }
  805. if columns[2].button("Reset to Default").clicked() {
  806. // Reset to default visibility (matching the initial setup)
  807. for column in &mut self.table_renderer.columns {
  808. column.visible = matches!(
  809. column.field.as_str(),
  810. "template_code"
  811. | "name"
  812. | "asset_type"
  813. | "description"
  814. | "category_name"
  815. );
  816. }
  817. }
  818. });
  819. });
  820. }
  821. // Handle pending deletes
  822. if !self.pending_delete_ids.is_empty() {
  823. if let Some(api) = api_client {
  824. let ids_to_delete = self.pending_delete_ids.clone();
  825. self.pending_delete_ids.clear();
  826. for id in ids_to_delete {
  827. let where_clause = serde_json::json!({"id": id});
  828. match api.delete("templates", where_clause) {
  829. Ok(resp) if resp.success => {
  830. log::info!("Template {} deleted successfully", id);
  831. }
  832. Ok(resp) => {
  833. self.loading_state.last_error =
  834. Some(format!("Delete failed: {:?}", resp.error));
  835. }
  836. Err(e) => {
  837. self.loading_state.last_error = Some(format!("Delete error: {}", e));
  838. }
  839. }
  840. }
  841. // Reload after deletes
  842. self.load_templates(api, limit);
  843. }
  844. }
  845. // Handle edit dialog save
  846. let ctx = ui.ctx();
  847. if let Some(result) = self.edit_dialog.show_editor(ctx) {
  848. log::info!(
  849. "🎯 Templates received editor result: {:?}",
  850. result.is_some()
  851. );
  852. if let Some(updated) = result {
  853. log::info!(
  854. "🎯 Processing template save with data keys: {:?}",
  855. updated.keys().collect::<Vec<_>>()
  856. );
  857. if let Some(api) = api_client {
  858. let mut id_from_updated: Option<i64> = None;
  859. if let Some(id_val) = updated.get("id") {
  860. log::info!("Raw id_val for template save: {:?}", id_val);
  861. id_from_updated = if let Some(s) = id_val.as_str() {
  862. if s.trim().is_empty() {
  863. None
  864. } else {
  865. s.trim().parse::<i64>().ok()
  866. }
  867. } else {
  868. id_val.as_i64()
  869. };
  870. } else if let Some(meta_id_val) = updated.get("__editor_item_id") {
  871. log::info!(
  872. "No 'id' in diff; checking __editor_item_id: {:?}",
  873. meta_id_val
  874. );
  875. id_from_updated = match meta_id_val {
  876. serde_json::Value::String(s) => {
  877. let s = s.trim();
  878. if s.is_empty() {
  879. None
  880. } else {
  881. s.parse::<i64>().ok()
  882. }
  883. }
  884. serde_json::Value::Number(n) => n.as_i64(),
  885. _ => None,
  886. };
  887. }
  888. if let Some(id) = id_from_updated {
  889. log::info!("Entering UPDATE template path for id {}", id);
  890. let mut cleaned = updated.clone();
  891. cleaned.remove("__editor_item_id");
  892. let additional_fields_update = match Self::parse_additional_fields_input(
  893. cleaned.remove("additional_fields_json"),
  894. ) {
  895. Ok(val) => val,
  896. Err(err) => {
  897. let msg = format!("Additional Fields JSON is invalid: {}", err);
  898. log::error!("{}", msg);
  899. self.loading_state.last_error = Some(msg);
  900. return flags_to_clear;
  901. }
  902. };
  903. // Filter empty strings to NULL for UPDATE too
  904. let mut filtered_values = serde_json::Map::new();
  905. let ignored_fields = [
  906. "id",
  907. "created_at",
  908. "audit_task_name",
  909. "category_name",
  910. "category_code",
  911. "zone_name",
  912. "supplier_name",
  913. "label_template_name",
  914. "zone_code",
  915. ];
  916. for (key, value) in cleaned.iter() {
  917. if key.starts_with("__editor_") || ignored_fields.contains(&key.as_str()) {
  918. continue;
  919. }
  920. match value {
  921. serde_json::Value::String(s) if s.trim().is_empty() => {
  922. filtered_values.insert(key.clone(), serde_json::Value::Null);
  923. }
  924. _ => {
  925. filtered_values.insert(key.clone(), value.clone());
  926. }
  927. }
  928. }
  929. if let Some(val) = additional_fields_update {
  930. filtered_values.insert("additional_fields".to_string(), val);
  931. }
  932. let values = serde_json::Value::Object(filtered_values);
  933. let where_clause = serde_json::json!({"id": id});
  934. log::info!(
  935. "Sending UPDATE request: values={:?} where={:?}",
  936. values,
  937. where_clause
  938. );
  939. match api.update("templates", values, where_clause) {
  940. Ok(resp) if resp.success => {
  941. log::info!("Template {} updated successfully", id);
  942. self.load_templates(api, limit);
  943. }
  944. Ok(resp) => {
  945. let err = format!("Update failed: {:?}", resp.error);
  946. log::error!("{}", err);
  947. self.loading_state.last_error = Some(err);
  948. }
  949. Err(e) => {
  950. let err = format!("Update error: {}", e);
  951. log::error!("{}", err);
  952. self.loading_state.last_error = Some(err);
  953. }
  954. }
  955. } else {
  956. log::info!("🆕 Entering INSERT template path (no valid ID detected)");
  957. let mut values = updated.clone();
  958. values.remove("id");
  959. values.remove("__editor_item_id");
  960. let additional_fields_insert = match Self::parse_additional_fields_input(
  961. values.remove("additional_fields_json"),
  962. ) {
  963. Ok(val) => val,
  964. Err(err) => {
  965. let msg = format!("Additional Fields JSON is invalid: {}", err);
  966. log::error!("{}", msg);
  967. self.loading_state.last_error = Some(msg);
  968. return flags_to_clear;
  969. }
  970. };
  971. let mut filtered_values = serde_json::Map::new();
  972. let ignored_fields = [
  973. "id",
  974. "created_at",
  975. "audit_task_name",
  976. "category_name",
  977. "category_code",
  978. "zone_name",
  979. "supplier_name",
  980. "label_template_name",
  981. "zone_code",
  982. ];
  983. for (key, value) in values.iter() {
  984. if key.starts_with("__editor_") || ignored_fields.contains(&key.as_str()) {
  985. continue;
  986. }
  987. match value {
  988. serde_json::Value::String(s) if s.trim().is_empty() => {
  989. filtered_values.insert(key.clone(), serde_json::Value::Null);
  990. }
  991. _ => {
  992. filtered_values.insert(key.clone(), value.clone());
  993. }
  994. }
  995. }
  996. if let Some(val) = additional_fields_insert {
  997. filtered_values.insert("additional_fields".to_string(), val);
  998. }
  999. let values = serde_json::Value::Object(filtered_values);
  1000. log::info!("➡️ Sending INSERT request for template: {:?}", values);
  1001. match api.insert("templates", values) {
  1002. Ok(resp) if resp.success => {
  1003. log::info!(
  1004. "✅ New template created successfully (id={:?})",
  1005. resp.data
  1006. );
  1007. self.load_templates(api, limit);
  1008. }
  1009. Ok(resp) => {
  1010. let error_msg = format!("Insert failed: {:?}", resp.error);
  1011. log::error!("Template insert failed: {}", error_msg);
  1012. self.loading_state.last_error = Some(error_msg);
  1013. }
  1014. Err(e) => {
  1015. let error_msg = format!("Insert error: {}", e);
  1016. log::error!("Template insert error: {}", error_msg);
  1017. self.loading_state.last_error = Some(error_msg);
  1018. }
  1019. }
  1020. }
  1021. }
  1022. }
  1023. }
  1024. flags_to_clear
  1025. }
  1026. fn open_edit_template_dialog(&mut self, mut template: serde_json::Value) {
  1027. // Determine whether we are creating a new template (no ID or empty/zero ID)
  1028. let is_new = match template.get("id") {
  1029. Some(serde_json::Value::String(s)) => s.trim().is_empty(),
  1030. Some(serde_json::Value::Number(n)) => n.as_i64().map(|id| id <= 0).unwrap_or(true),
  1031. Some(serde_json::Value::Null) | None => true,
  1032. _ => false,
  1033. };
  1034. self.edit_dialog.title = if is_new {
  1035. "Add New Template".to_string()
  1036. } else {
  1037. "Edit Template".to_string()
  1038. };
  1039. if let Some(obj) = template.as_object_mut() {
  1040. let pretty_json = if let Some(existing) =
  1041. obj.get("additional_fields_json").and_then(|v| v.as_str())
  1042. {
  1043. existing.to_string()
  1044. } else {
  1045. match obj.get("additional_fields") {
  1046. Some(serde_json::Value::Null) | None => String::new(),
  1047. Some(serde_json::Value::String(s)) => s.clone(),
  1048. Some(value) => {
  1049. serde_json::to_string_pretty(value).unwrap_or_else(|_| value.to_string())
  1050. }
  1051. }
  1052. };
  1053. obj.insert(
  1054. "additional_fields_json".to_string(),
  1055. serde_json::Value::String(pretty_json),
  1056. );
  1057. }
  1058. // Debug log to check the template data being passed to editor
  1059. log::info!(
  1060. "Template data for editor: {}",
  1061. serde_json::to_string_pretty(&template)
  1062. .unwrap_or_else(|_| "failed to serialize".to_string())
  1063. );
  1064. if is_new {
  1065. // Use open_new so cloned templates keep their preset values when saved
  1066. if let Some(obj) = template.as_object() {
  1067. self.edit_dialog.open_new(Some(obj));
  1068. } else {
  1069. self.edit_dialog.open_new(None);
  1070. }
  1071. } else {
  1072. self.edit_dialog.open(&template);
  1073. }
  1074. }
  1075. }