1use anyhow::Result;
7use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
8
9use super::app::{ActiveDialog, ActiveView, App, FocusedPanel, InputMode};
10use super::commands::{CommandAction, COMMANDS};
11use super::event::Event;
12
13pub fn handle_event(app: &mut App, event: Event) -> Result<()> {
15 match event {
16 Event::Key(key) => handle_key_event(app, key),
17 Event::Mouse(_mouse) => {
18 Ok(())
20 }
21 Event::Tick => Ok(()),
22 Event::Resize(_, _) => Ok(()),
23 }
24}
25
26fn handle_key_event(app: &mut App, key: KeyEvent) -> Result<()> {
28 if app.has_dialog() {
30 return handle_dialog_key(app, key);
31 }
32
33 match app.input_mode {
35 InputMode::Normal => handle_normal_key(app, key),
36 InputMode::Editing => handle_editing_key(app, key),
37 InputMode::Command => handle_command_key(app, key),
38 }
39}
40
41fn handle_normal_key(app: &mut App, key: KeyEvent) -> Result<()> {
43 match key.code {
45 KeyCode::Char('q') | KeyCode::Char('Q') => {
47 app.quit();
48 return Ok(());
49 }
50
51 KeyCode::Char('?') => {
53 app.open_dialog(ActiveDialog::Help);
54 return Ok(());
55 }
56
57 KeyCode::Char(':') | KeyCode::Char('/') => {
59 app.open_dialog(ActiveDialog::CommandPalette);
60 return Ok(());
61 }
62
63 KeyCode::Tab => {
65 app.toggle_panel_focus();
66 return Ok(());
67 }
68 KeyCode::Char('h') | KeyCode::Left if key.modifiers.is_empty() => {
69 if app.focused_panel == FocusedPanel::Main {
70 app.focused_panel = FocusedPanel::Sidebar;
71 return Ok(());
72 }
73 }
74 KeyCode::Char('l') | KeyCode::Right if key.modifiers.is_empty() => {
75 if app.focused_panel == FocusedPanel::Sidebar {
76 app.focused_panel = FocusedPanel::Main;
77 app.ensure_selection_initialized();
78 return Ok(());
79 }
80 }
81
82 _ => {}
83 }
84
85 match app.focused_panel {
87 FocusedPanel::Sidebar => handle_sidebar_key(app, key),
88 FocusedPanel::Main => handle_main_panel_key(app, key),
89 }
90}
91
92fn handle_sidebar_key(app: &mut App, key: KeyEvent) -> Result<()> {
94 let account_count = app
96 .storage
97 .accounts
98 .get_active()
99 .map(|a| a.len())
100 .unwrap_or(0);
101
102 match key.code {
103 KeyCode::Char('j') | KeyCode::Down => {
105 app.move_down(account_count);
106 if let Ok(accounts) = app.storage.accounts.get_active() {
108 if let Some(account) = accounts.get(app.selected_account_index) {
109 app.selected_account = Some(account.id);
110 }
111 }
112 }
113 KeyCode::Char('k') | KeyCode::Up => {
114 app.move_up();
115 if let Ok(accounts) = app.storage.accounts.get_active() {
117 if let Some(account) = accounts.get(app.selected_account_index) {
118 app.selected_account = Some(account.id);
119 }
120 }
121 }
122
123 KeyCode::Enter => {
125 if let Ok(accounts) = app.storage.accounts.get_active() {
126 if let Some(account) = accounts.get(app.selected_account_index) {
127 app.selected_account = Some(account.id);
128 app.switch_view(ActiveView::Register);
129 app.focused_panel = FocusedPanel::Main;
130 }
131 }
132 }
133
134 KeyCode::Char('1') => app.switch_view(ActiveView::Accounts),
136 KeyCode::Char('2') => app.switch_view(ActiveView::Budget),
137 KeyCode::Char('3') => app.switch_view(ActiveView::Reports),
138
139 KeyCode::Char('A') => {
141 app.show_archived = !app.show_archived;
142 }
143
144 KeyCode::Char('a') | KeyCode::Char('n') => {
146 app.open_dialog(ActiveDialog::AddAccount);
147 }
148
149 KeyCode::Char('e') => {
151 if let Ok(accounts) = app.storage.accounts.get_active() {
152 if let Some(account) = accounts.get(app.selected_account_index) {
153 app.open_dialog(ActiveDialog::EditAccount(account.id));
154 }
155 }
156 }
157
158 _ => {}
159 }
160
161 Ok(())
162}
163
164fn handle_main_panel_key(app: &mut App, key: KeyEvent) -> Result<()> {
166 match app.active_view {
167 ActiveView::Accounts => handle_accounts_view_key(app, key),
168 ActiveView::Register => handle_register_view_key(app, key),
169 ActiveView::Budget => handle_budget_view_key(app, key),
170 ActiveView::Reports => handle_reports_view_key(app, key),
171 ActiveView::Reconcile => handle_reconcile_view_key(app, key),
172 }
173}
174
175fn handle_accounts_view_key(app: &mut App, key: KeyEvent) -> Result<()> {
177 let account_count = app
178 .storage
179 .accounts
180 .get_active()
181 .map(|a| a.len())
182 .unwrap_or(0);
183
184 match key.code {
185 KeyCode::Char('j') | KeyCode::Down => {
186 app.move_down(account_count);
187 if let Ok(accounts) = app.storage.accounts.get_active() {
188 if let Some(account) = accounts.get(app.selected_account_index) {
189 app.selected_account = Some(account.id);
190 }
191 }
192 }
193 KeyCode::Char('k') | KeyCode::Up => {
194 app.move_up();
195 if let Ok(accounts) = app.storage.accounts.get_active() {
196 if let Some(account) = accounts.get(app.selected_account_index) {
197 app.selected_account = Some(account.id);
198 }
199 }
200 }
201 KeyCode::Enter => {
202 app.switch_view(ActiveView::Register);
204 }
205 KeyCode::Char('a') | KeyCode::Char('n') => {
207 app.open_dialog(ActiveDialog::AddAccount);
208 }
209 KeyCode::Char('e') => {
211 if let Ok(accounts) = app.storage.accounts.get_active() {
212 if let Some(account) = accounts.get(app.selected_account_index) {
213 app.open_dialog(ActiveDialog::EditAccount(account.id));
214 }
215 }
216 }
217 _ => {}
218 }
219
220 Ok(())
221}
222
223fn get_sorted_transactions(app: &App) -> Vec<crate::models::Transaction> {
225 if let Some(account_id) = app.selected_account {
226 let mut txns = app
227 .storage
228 .transactions
229 .get_by_account(account_id)
230 .unwrap_or_default();
231 txns.sort_by(|a, b| b.date.cmp(&a.date));
233 txns
234 } else {
235 Vec::new()
236 }
237}
238
239fn handle_register_view_key(app: &mut App, key: KeyEvent) -> Result<()> {
241 let txns = get_sorted_transactions(app);
243 let txn_count = txns.len();
244
245 match key.code {
246 KeyCode::Char('j') | KeyCode::Down => {
248 app.move_down(txn_count);
249 if let Some(txn) = txns.get(app.selected_transaction_index) {
251 app.selected_transaction = Some(txn.id);
252 }
253 }
254 KeyCode::Char('k') | KeyCode::Up => {
255 app.move_up();
256 if let Some(txn) = txns.get(app.selected_transaction_index) {
258 app.selected_transaction = Some(txn.id);
259 }
260 }
261
262 KeyCode::Char('G') => {
264 if txn_count > 0 {
266 app.selected_transaction_index = txn_count - 1;
267 if let Some(txn) = txns.get(app.selected_transaction_index) {
268 app.selected_transaction = Some(txn.id);
269 }
270 }
271 }
272 KeyCode::Char('g') => {
273 app.selected_transaction_index = 0;
275 if let Some(txn) = txns.first() {
276 app.selected_transaction = Some(txn.id);
277 }
278 }
279
280 KeyCode::Char('a') | KeyCode::Char('n') => {
282 app.open_dialog(ActiveDialog::AddTransaction);
283 }
284
285 KeyCode::Char('e') => {
287 if app.selected_transaction.is_none() {
289 let txns = get_sorted_transactions(app);
290 if let Some(txn) = txns.get(app.selected_transaction_index) {
291 app.selected_transaction = Some(txn.id);
292 }
293 }
294 if let Some(txn_id) = app.selected_transaction {
295 app.open_dialog(ActiveDialog::EditTransaction(txn_id));
296 }
297 }
298 KeyCode::Enter => {
299 if app.selected_transaction.is_none() {
300 let txns = get_sorted_transactions(app);
301 if let Some(txn) = txns.get(app.selected_transaction_index) {
302 app.selected_transaction = Some(txn.id);
303 }
304 }
305 if let Some(txn_id) = app.selected_transaction {
306 app.open_dialog(ActiveDialog::EditTransaction(txn_id));
307 }
308 }
309
310 KeyCode::Char('c') => {
312 if let Some(txn_id) = app.selected_transaction {
313 if let Ok(Some(txn)) = app.storage.transactions.get(txn_id) {
315 use crate::models::TransactionStatus;
316 let new_status = match txn.status {
317 TransactionStatus::Pending => TransactionStatus::Cleared,
318 TransactionStatus::Cleared => TransactionStatus::Pending,
319 TransactionStatus::Reconciled => TransactionStatus::Reconciled,
320 };
321 if new_status != txn.status {
322 let mut txn = txn.clone();
323 txn.set_status(new_status);
324 let _ = app.storage.transactions.upsert(txn);
325 let _ = app.storage.transactions.save();
326 app.set_status(format!("Transaction marked as {}", new_status));
327 }
328 }
329 }
330 }
331
332 KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => {
334 if app.selected_transaction.is_some() {
335 app.open_dialog(ActiveDialog::Confirm(
336 "Delete this transaction?".to_string(),
337 ));
338 }
339 }
340
341 KeyCode::Char('v') => {
343 app.toggle_multi_select();
344 if app.multi_select_mode {
345 app.set_status("Multi-select mode ON");
346 } else {
347 app.set_status("Multi-select mode OFF");
348 }
349 }
350
351 KeyCode::Char(' ') if app.multi_select_mode => {
353 app.toggle_transaction_selection();
354 }
355
356 KeyCode::Char('C') if app.multi_select_mode && !app.selected_transactions.is_empty() => {
358 app.open_dialog(ActiveDialog::BulkCategorize);
359 }
360
361 _ => {}
362 }
363
364 Ok(())
365}
366
367fn get_categories_in_visual_order(app: &App) -> Vec<crate::models::Category> {
369 let groups = app.storage.categories.get_all_groups().unwrap_or_default();
370 let all_categories = app
371 .storage
372 .categories
373 .get_all_categories()
374 .unwrap_or_default();
375
376 let mut result = Vec::new();
377 for group in &groups {
378 let group_cats: Vec<_> = all_categories
379 .iter()
380 .filter(|c| c.group_id == group.id)
381 .cloned()
382 .collect();
383 result.extend(group_cats);
384 }
385 result
386}
387
388fn handle_budget_view_key(app: &mut App, key: KeyEvent) -> Result<()> {
390 let categories = get_categories_in_visual_order(app);
392 let category_count = categories.len();
393
394 match key.code {
395 KeyCode::Char('j') | KeyCode::Down => {
397 app.move_down(category_count);
398 if let Some(cat) = categories.get(app.selected_category_index) {
399 app.selected_category = Some(cat.id);
400 }
401 }
402 KeyCode::Char('k') | KeyCode::Up => {
403 app.move_up();
404 if let Some(cat) = categories.get(app.selected_category_index) {
405 app.selected_category = Some(cat.id);
406 }
407 }
408
409 KeyCode::Char('[') | KeyCode::Char('H') => {
411 app.prev_period();
412 }
413 KeyCode::Char(']') | KeyCode::Char('L') => {
414 app.next_period();
415 }
416
417 KeyCode::Char('m') => {
419 app.open_dialog(ActiveDialog::MoveFunds);
420 }
421
422 KeyCode::Char('a') => {
424 app.open_dialog(ActiveDialog::AddCategory);
425 }
426
427 KeyCode::Char('A') => {
429 app.open_dialog(ActiveDialog::AddGroup);
430 }
431
432 KeyCode::Enter | KeyCode::Char('b') | KeyCode::Char('t') => {
434 if let Some(cat) = categories.get(app.selected_category_index) {
435 app.selected_category = Some(cat.id);
436 app.open_dialog(ActiveDialog::Budget);
437 }
438 }
439
440 _ => {}
441 }
442
443 Ok(())
444}
445
446fn handle_reports_view_key(_app: &mut App, _key: KeyEvent) -> Result<()> {
448 Ok(())
450}
451
452fn handle_reconcile_view_key(app: &mut App, key: KeyEvent) -> Result<()> {
454 super::views::reconcile::handle_key(app, key.code);
456 Ok(())
457}
458
459fn handle_editing_key(app: &mut App, key: KeyEvent) -> Result<()> {
461 match key.code {
462 KeyCode::Esc => {
463 app.input_mode = InputMode::Normal;
464 }
465 _ => {
466 }
468 }
469 Ok(())
470}
471
472fn handle_command_key(app: &mut App, key: KeyEvent) -> Result<()> {
474 match key.code {
475 KeyCode::Esc => {
476 app.close_dialog();
477 }
478 KeyCode::Enter => {
479 let filtered_commands: Vec<&crate::tui::commands::Command> = COMMANDS
481 .iter()
482 .filter(|cmd| {
483 if app.command_input.is_empty() {
484 true
485 } else {
486 let query = app.command_input.to_lowercase();
487 cmd.name.to_lowercase().contains(&query)
488 || cmd.description.to_lowercase().contains(&query)
489 }
490 })
491 .collect();
492
493 if !filtered_commands.is_empty() {
495 let selected_idx = app
496 .selected_command_index
497 .min(filtered_commands.len().saturating_sub(1));
498 let command = filtered_commands[selected_idx];
499 let action = command.action;
500
501 app.close_dialog();
503
504 execute_command_action(app, action)?;
506 } else {
507 app.close_dialog();
508 }
509 }
510 KeyCode::Char(c) => {
511 app.command_input.push(c);
512 app.selected_command_index = 0;
514 }
515 KeyCode::Backspace => {
516 app.command_input.pop();
517 app.selected_command_index = 0;
519 }
520 KeyCode::Up => {
521 if app.selected_command_index > 0 {
522 app.selected_command_index -= 1;
523 }
524 }
525 KeyCode::Down => {
526 let filtered_count = COMMANDS
528 .iter()
529 .filter(|cmd| {
530 if app.command_input.is_empty() {
531 true
532 } else {
533 let query = app.command_input.to_lowercase();
534 cmd.name.to_lowercase().contains(&query)
535 || cmd.description.to_lowercase().contains(&query)
536 }
537 })
538 .count();
539 if app.selected_command_index + 1 < filtered_count {
540 app.selected_command_index += 1;
541 }
542 }
543 _ => {}
544 }
545 Ok(())
546}
547
548fn execute_command_action(app: &mut App, action: CommandAction) -> Result<()> {
550 match action {
551 CommandAction::ViewAccounts => {
553 app.switch_view(ActiveView::Accounts);
554 }
555 CommandAction::ViewBudget => {
556 app.switch_view(ActiveView::Budget);
557 }
558 CommandAction::ViewReports => {
559 app.switch_view(ActiveView::Reports);
560 }
561 CommandAction::ViewRegister => {
562 app.switch_view(ActiveView::Register);
563 }
564
565 CommandAction::AddAccount => {
567 app.open_dialog(ActiveDialog::AddAccount);
568 }
569 CommandAction::EditAccount => {
570 if let Ok(accounts) = app.storage.accounts.get_active() {
571 if let Some(account) = accounts.get(app.selected_account_index) {
572 app.open_dialog(ActiveDialog::EditAccount(account.id));
573 }
574 }
575 }
576 CommandAction::ArchiveAccount => {
577 if let Ok(accounts) = app.storage.accounts.get_active() {
579 if let Some(account) = accounts.get(app.selected_account_index) {
580 app.open_dialog(ActiveDialog::Confirm(format!(
581 "Archive account '{}'?",
582 account.name
583 )));
584 } else {
585 app.set_status("No account selected".to_string());
586 }
587 }
588 }
589
590 CommandAction::AddTransaction => {
592 app.open_dialog(ActiveDialog::AddTransaction);
593 }
594 CommandAction::EditTransaction => {
595 if let Some(tx_id) = app.selected_transaction {
596 app.open_dialog(ActiveDialog::EditTransaction(tx_id));
597 } else {
598 app.set_status("No transaction selected".to_string());
599 }
600 }
601 CommandAction::DeleteTransaction => {
602 if app.selected_transaction.is_some() {
603 app.open_dialog(ActiveDialog::Confirm("Delete transaction?".to_string()));
604 } else {
605 app.set_status("No transaction selected".to_string());
606 }
607 }
608 CommandAction::ClearTransaction => {
609 if let Some(txn_id) = app.selected_transaction {
611 if let Ok(Some(txn)) = app.storage.transactions.get(txn_id) {
612 use crate::models::TransactionStatus;
613 let new_status = match txn.status {
614 TransactionStatus::Pending => TransactionStatus::Cleared,
615 TransactionStatus::Cleared => TransactionStatus::Pending,
616 TransactionStatus::Reconciled => TransactionStatus::Reconciled,
617 };
618 if new_status != txn.status {
619 let mut txn = txn.clone();
620 txn.set_status(new_status);
621 let _ = app.storage.transactions.upsert(txn);
622 let _ = app.storage.transactions.save();
623 app.set_status(format!("Transaction marked as {}", new_status));
624 }
625 }
626 } else {
627 app.set_status("No transaction selected".to_string());
628 }
629 }
630
631 CommandAction::MoveFunds => {
633 app.open_dialog(ActiveDialog::MoveFunds);
634 }
635 CommandAction::AssignBudget => {
636 if app.selected_category.is_some() {
638 app.open_dialog(ActiveDialog::Budget);
639 } else {
640 app.set_status("No category selected. Switch to Budget view first.".to_string());
641 }
642 }
643 CommandAction::NextPeriod => {
644 app.next_period();
645 }
646 CommandAction::PrevPeriod => {
647 app.prev_period();
648 }
649
650 CommandAction::AddCategory => {
652 let groups: Vec<_> = app
654 .storage
655 .categories
656 .get_all_groups()
657 .unwrap_or_default()
658 .into_iter()
659 .map(|g| (g.id, g.name.clone()))
660 .collect();
661 app.category_form.init_with_groups(groups);
662 app.open_dialog(ActiveDialog::AddCategory);
663 }
664 CommandAction::AddGroup => {
665 app.group_form = super::dialogs::group::GroupFormState::new();
667 app.open_dialog(ActiveDialog::AddGroup);
668 }
669 CommandAction::EditCategory => {
670 if let Some(category_id) = app.selected_category {
672 app.open_dialog(ActiveDialog::EditCategory(category_id));
673 } else {
674 app.set_status("No category selected. Switch to Budget view first.".to_string());
675 }
676 }
677 CommandAction::DeleteCategory => {
678 if let Some(category_id) = app.selected_category {
680 if let Ok(Some(category)) = app.storage.categories.get_category(category_id) {
681 app.open_dialog(ActiveDialog::Confirm(format!(
682 "Delete category '{}'?",
683 category.name
684 )));
685 }
686 } else {
687 app.set_status("No category selected".to_string());
688 }
689 }
690
691 CommandAction::Help => {
693 app.open_dialog(ActiveDialog::Help);
694 }
695 CommandAction::Quit => {
696 app.quit();
697 }
698 CommandAction::Refresh => {
699 if let Err(e) = app.storage.accounts.load() {
701 app.set_status(format!("Failed to refresh accounts: {}", e));
702 return Ok(());
703 }
704 if let Err(e) = app.storage.transactions.load() {
705 app.set_status(format!("Failed to refresh transactions: {}", e));
706 return Ok(());
707 }
708 if let Err(e) = app.storage.categories.load() {
709 app.set_status(format!("Failed to refresh categories: {}", e));
710 return Ok(());
711 }
712 if let Err(e) = app.storage.budget.load() {
713 app.set_status(format!("Failed to refresh budget: {}", e));
714 return Ok(());
715 }
716 app.set_status("Data refreshed from disk".to_string());
717 }
718 CommandAction::ToggleArchived => {
719 app.show_archived = !app.show_archived;
720 }
721
722 CommandAction::AutoFillTargets => {
724 use crate::services::BudgetService;
725 let budget_service = BudgetService::new(app.storage);
726 match budget_service.auto_fill_all_targets(&app.current_period) {
727 Ok(allocations) => {
728 if allocations.is_empty() {
729 app.set_status("No targets to auto-fill".to_string());
730 } else {
731 let count = allocations.len();
732 let plural = if count == 1 { "category" } else { "categories" };
733 app.set_status(format!("{} {} updated from targets", count, plural));
734 }
735 }
736 Err(e) => {
737 app.set_status(format!("Auto-fill failed: {}", e));
738 }
739 }
740 }
741 }
742 Ok(())
743}
744
745fn handle_dialog_key(app: &mut App, key: KeyEvent) -> Result<()> {
747 match &app.active_dialog {
748 ActiveDialog::Help => {
749 app.close_dialog();
751 }
752 ActiveDialog::CommandPalette => {
753 handle_command_key(app, key)?;
754 }
755 ActiveDialog::Confirm(msg) => {
756 let msg = msg.clone();
757 match key.code {
758 KeyCode::Char('y') | KeyCode::Char('Y') | KeyCode::Enter => {
759 app.close_dialog();
761 execute_confirmed_action(app, &msg)?;
762 }
763 KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
764 app.close_dialog();
765 }
766 _ => {}
767 }
768 }
769 ActiveDialog::AddTransaction | ActiveDialog::EditTransaction(_) => {
770 super::dialogs::transaction::handle_key(app, key);
772 }
773 ActiveDialog::MoveFunds => {
774 super::dialogs::move_funds::handle_key(app, key);
775 }
776 ActiveDialog::BulkCategorize => {
777 super::dialogs::bulk_categorize::handle_key(app, key);
778 }
779 ActiveDialog::ReconcileStart => {
780 match key.code {
781 KeyCode::Esc => {
782 app.close_dialog();
783 }
784 KeyCode::Enter => {
785 app.close_dialog();
787 app.switch_view(ActiveView::Reconcile);
788 }
789 _ => {
790 super::dialogs::reconcile_start::handle_key(app, key.code);
791 }
792 }
793 }
794 ActiveDialog::UnlockConfirm(_) => {
795 match key.code {
796 KeyCode::Char('y') | KeyCode::Char('Y') => {
797 app.close_dialog();
799 }
800 KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
801 app.close_dialog();
802 }
803 _ => {}
804 }
805 }
806 ActiveDialog::Adjustment => {
807 match key.code {
808 KeyCode::Esc => {
809 app.close_dialog();
810 }
811 KeyCode::Enter => {
812 app.close_dialog();
814 }
815 _ => {
816 super::dialogs::adjustment::handle_key(app, key.code);
817 }
818 }
819 }
820 ActiveDialog::Budget => {
821 super::dialogs::budget::handle_key(app, key);
822 }
823 ActiveDialog::AddAccount | ActiveDialog::EditAccount(_) => {
824 super::dialogs::account::handle_key(app, key);
825 }
826 ActiveDialog::AddCategory | ActiveDialog::EditCategory(_) => {
827 super::dialogs::category::handle_key(app, key);
828 }
829 ActiveDialog::AddGroup => {
830 super::dialogs::group::handle_key(app, key);
831 }
832 ActiveDialog::None => {}
833 }
834 Ok(())
835}
836
837fn execute_confirmed_action(app: &mut App, message: &str) -> Result<()> {
839 if message.contains("Delete") && message.contains("transaction") {
841 if let Some(txn_id) = app.selected_transaction {
842 if let Err(e) = app.storage.transactions.delete(txn_id) {
843 app.set_status(format!("Failed to delete: {}", e));
844 } else {
845 let _ = app.storage.transactions.save();
846 app.selected_transaction = None;
847 app.set_status("Transaction deleted".to_string());
848 }
849 }
850 }
851 else if message.contains("Archive account") {
853 if let Ok(accounts) = app.storage.accounts.get_active() {
854 if let Some(account) = accounts.get(app.selected_account_index) {
855 let mut account = account.clone();
856 account.archive();
857 if let Err(e) = app.storage.accounts.upsert(account.clone()) {
858 app.set_status(format!("Failed to archive: {}", e));
859 } else {
860 let _ = app.storage.accounts.save();
861 app.set_status(format!("Account '{}' archived", account.name));
862 app.selected_account_index = 0;
864 if let Ok(active) = app.storage.accounts.get_active() {
865 app.selected_account = active.first().map(|a| a.id);
866 }
867 }
868 }
869 }
870 }
871 else if message.contains("Delete category") {
873 if let Some(category_id) = app.selected_category {
874 use crate::services::CategoryService;
875 let category_service = CategoryService::new(app.storage);
876 match category_service.delete_category(category_id) {
877 Ok(()) => {
878 app.set_status("Category deleted".to_string());
879 app.selected_category = None;
880 app.selected_category_index = 0;
881 }
882 Err(e) => {
883 app.set_status(format!("Failed to delete: {}", e));
884 }
885 }
886 }
887 }
888
889 Ok(())
890}