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('<') | KeyCode::Char(',') => {
419 app.budget_header_display = app.budget_header_display.prev();
420 }
421 KeyCode::Char('>') | KeyCode::Char('.') => {
422 app.budget_header_display = app.budget_header_display.next();
423 }
424
425 KeyCode::Char('m') => {
427 app.open_dialog(ActiveDialog::MoveFunds);
428 }
429
430 KeyCode::Char('a') => {
432 app.open_dialog(ActiveDialog::AddCategory);
433 }
434
435 KeyCode::Char('A') => {
437 app.open_dialog(ActiveDialog::AddGroup);
438 }
439
440 KeyCode::Enter | KeyCode::Char('b') | KeyCode::Char('t') => {
442 if let Some(cat) = categories.get(app.selected_category_index) {
443 app.selected_category = Some(cat.id);
444 app.open_dialog(ActiveDialog::Budget);
445 }
446 }
447
448 _ => {}
449 }
450
451 Ok(())
452}
453
454fn handle_reports_view_key(_app: &mut App, _key: KeyEvent) -> Result<()> {
456 Ok(())
458}
459
460fn handle_reconcile_view_key(app: &mut App, key: KeyEvent) -> Result<()> {
462 super::views::reconcile::handle_key(app, key.code);
464 Ok(())
465}
466
467fn handle_editing_key(app: &mut App, key: KeyEvent) -> Result<()> {
469 match key.code {
470 KeyCode::Esc => {
471 app.input_mode = InputMode::Normal;
472 }
473 _ => {
474 }
476 }
477 Ok(())
478}
479
480fn handle_command_key(app: &mut App, key: KeyEvent) -> Result<()> {
482 match key.code {
483 KeyCode::Esc => {
484 app.close_dialog();
485 }
486 KeyCode::Enter => {
487 let filtered_commands: Vec<&crate::tui::commands::Command> = COMMANDS
489 .iter()
490 .filter(|cmd| {
491 if app.command_input.is_empty() {
492 true
493 } else {
494 let query = app.command_input.to_lowercase();
495 cmd.name.to_lowercase().contains(&query)
496 || cmd.description.to_lowercase().contains(&query)
497 }
498 })
499 .collect();
500
501 if !filtered_commands.is_empty() {
503 let selected_idx = app
504 .selected_command_index
505 .min(filtered_commands.len().saturating_sub(1));
506 let command = filtered_commands[selected_idx];
507 let action = command.action;
508
509 app.close_dialog();
511
512 execute_command_action(app, action)?;
514 } else {
515 app.close_dialog();
516 }
517 }
518 KeyCode::Char(c) => {
519 app.command_input.push(c);
520 app.selected_command_index = 0;
522 }
523 KeyCode::Backspace => {
524 app.command_input.pop();
525 app.selected_command_index = 0;
527 }
528 KeyCode::Up => {
529 if app.selected_command_index > 0 {
530 app.selected_command_index -= 1;
531 }
532 }
533 KeyCode::Down => {
534 let filtered_count = COMMANDS
536 .iter()
537 .filter(|cmd| {
538 if app.command_input.is_empty() {
539 true
540 } else {
541 let query = app.command_input.to_lowercase();
542 cmd.name.to_lowercase().contains(&query)
543 || cmd.description.to_lowercase().contains(&query)
544 }
545 })
546 .count();
547 if app.selected_command_index + 1 < filtered_count {
548 app.selected_command_index += 1;
549 }
550 }
551 _ => {}
552 }
553 Ok(())
554}
555
556fn execute_command_action(app: &mut App, action: CommandAction) -> Result<()> {
558 match action {
559 CommandAction::ViewAccounts => {
561 app.switch_view(ActiveView::Accounts);
562 }
563 CommandAction::ViewBudget => {
564 app.switch_view(ActiveView::Budget);
565 }
566 CommandAction::ViewReports => {
567 app.switch_view(ActiveView::Reports);
568 }
569 CommandAction::ViewRegister => {
570 app.switch_view(ActiveView::Register);
571 }
572
573 CommandAction::AddAccount => {
575 app.open_dialog(ActiveDialog::AddAccount);
576 }
577 CommandAction::EditAccount => {
578 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::EditAccount(account.id));
581 }
582 }
583 }
584 CommandAction::ArchiveAccount => {
585 if let Ok(accounts) = app.storage.accounts.get_active() {
587 if let Some(account) = accounts.get(app.selected_account_index) {
588 app.open_dialog(ActiveDialog::Confirm(format!(
589 "Archive account '{}'?",
590 account.name
591 )));
592 } else {
593 app.set_status("No account selected".to_string());
594 }
595 }
596 }
597
598 CommandAction::AddTransaction => {
600 app.open_dialog(ActiveDialog::AddTransaction);
601 }
602 CommandAction::EditTransaction => {
603 if let Some(tx_id) = app.selected_transaction {
604 app.open_dialog(ActiveDialog::EditTransaction(tx_id));
605 } else {
606 app.set_status("No transaction selected".to_string());
607 }
608 }
609 CommandAction::DeleteTransaction => {
610 if app.selected_transaction.is_some() {
611 app.open_dialog(ActiveDialog::Confirm("Delete transaction?".to_string()));
612 } else {
613 app.set_status("No transaction selected".to_string());
614 }
615 }
616 CommandAction::ClearTransaction => {
617 if let Some(txn_id) = app.selected_transaction {
619 if let Ok(Some(txn)) = app.storage.transactions.get(txn_id) {
620 use crate::models::TransactionStatus;
621 let new_status = match txn.status {
622 TransactionStatus::Pending => TransactionStatus::Cleared,
623 TransactionStatus::Cleared => TransactionStatus::Pending,
624 TransactionStatus::Reconciled => TransactionStatus::Reconciled,
625 };
626 if new_status != txn.status {
627 let mut txn = txn.clone();
628 txn.set_status(new_status);
629 let _ = app.storage.transactions.upsert(txn);
630 let _ = app.storage.transactions.save();
631 app.set_status(format!("Transaction marked as {}", new_status));
632 }
633 }
634 } else {
635 app.set_status("No transaction selected".to_string());
636 }
637 }
638
639 CommandAction::MoveFunds => {
641 app.open_dialog(ActiveDialog::MoveFunds);
642 }
643 CommandAction::AssignBudget => {
644 if app.selected_category.is_some() {
646 app.open_dialog(ActiveDialog::Budget);
647 } else {
648 app.set_status("No category selected. Switch to Budget view first.".to_string());
649 }
650 }
651 CommandAction::NextPeriod => {
652 app.next_period();
653 }
654 CommandAction::PrevPeriod => {
655 app.prev_period();
656 }
657
658 CommandAction::AddCategory => {
660 let groups: Vec<_> = app
662 .storage
663 .categories
664 .get_all_groups()
665 .unwrap_or_default()
666 .into_iter()
667 .map(|g| (g.id, g.name.clone()))
668 .collect();
669 app.category_form.init_with_groups(groups);
670 app.open_dialog(ActiveDialog::AddCategory);
671 }
672 CommandAction::AddGroup => {
673 app.group_form = super::dialogs::group::GroupFormState::new();
675 app.open_dialog(ActiveDialog::AddGroup);
676 }
677 CommandAction::EditCategory => {
678 if let Some(category_id) = app.selected_category {
680 app.open_dialog(ActiveDialog::EditCategory(category_id));
681 } else {
682 app.set_status("No category selected. Switch to Budget view first.".to_string());
683 }
684 }
685 CommandAction::DeleteCategory => {
686 if let Some(category_id) = app.selected_category {
688 if let Ok(Some(category)) = app.storage.categories.get_category(category_id) {
689 app.open_dialog(ActiveDialog::Confirm(format!(
690 "Delete category '{}'?",
691 category.name
692 )));
693 }
694 } else {
695 app.set_status("No category selected".to_string());
696 }
697 }
698
699 CommandAction::Help => {
701 app.open_dialog(ActiveDialog::Help);
702 }
703 CommandAction::Quit => {
704 app.quit();
705 }
706 CommandAction::Refresh => {
707 if let Err(e) = app.storage.accounts.load() {
709 app.set_status(format!("Failed to refresh accounts: {}", e));
710 return Ok(());
711 }
712 if let Err(e) = app.storage.transactions.load() {
713 app.set_status(format!("Failed to refresh transactions: {}", e));
714 return Ok(());
715 }
716 if let Err(e) = app.storage.categories.load() {
717 app.set_status(format!("Failed to refresh categories: {}", e));
718 return Ok(());
719 }
720 if let Err(e) = app.storage.budget.load() {
721 app.set_status(format!("Failed to refresh budget: {}", e));
722 return Ok(());
723 }
724 app.set_status("Data refreshed from disk".to_string());
725 }
726 CommandAction::ToggleArchived => {
727 app.show_archived = !app.show_archived;
728 }
729
730 CommandAction::AutoFillTargets => {
732 use crate::services::BudgetService;
733 let budget_service = BudgetService::new(app.storage);
734 match budget_service.auto_fill_all_targets(&app.current_period) {
735 Ok(allocations) => {
736 if allocations.is_empty() {
737 app.set_status("No targets to auto-fill".to_string());
738 } else {
739 let count = allocations.len();
740 let plural = if count == 1 { "category" } else { "categories" };
741 app.set_status(format!("{} {} updated from targets", count, plural));
742 }
743 }
744 Err(e) => {
745 app.set_status(format!("Auto-fill failed: {}", e));
746 }
747 }
748 }
749 }
750 Ok(())
751}
752
753fn handle_dialog_key(app: &mut App, key: KeyEvent) -> Result<()> {
755 match &app.active_dialog {
756 ActiveDialog::Help => {
757 app.close_dialog();
759 }
760 ActiveDialog::CommandPalette => {
761 handle_command_key(app, key)?;
762 }
763 ActiveDialog::Confirm(msg) => {
764 let msg = msg.clone();
765 match key.code {
766 KeyCode::Char('y') | KeyCode::Char('Y') | KeyCode::Enter => {
767 app.close_dialog();
769 execute_confirmed_action(app, &msg)?;
770 }
771 KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
772 app.close_dialog();
773 }
774 _ => {}
775 }
776 }
777 ActiveDialog::AddTransaction | ActiveDialog::EditTransaction(_) => {
778 super::dialogs::transaction::handle_key(app, key);
780 }
781 ActiveDialog::MoveFunds => {
782 super::dialogs::move_funds::handle_key(app, key);
783 }
784 ActiveDialog::BulkCategorize => {
785 super::dialogs::bulk_categorize::handle_key(app, key);
786 }
787 ActiveDialog::ReconcileStart => {
788 match key.code {
789 KeyCode::Esc => {
790 app.close_dialog();
791 }
792 KeyCode::Enter => {
793 app.close_dialog();
795 app.switch_view(ActiveView::Reconcile);
796 }
797 _ => {
798 super::dialogs::reconcile_start::handle_key(app, key.code);
799 }
800 }
801 }
802 ActiveDialog::UnlockConfirm(_) => {
803 match key.code {
804 KeyCode::Char('y') | KeyCode::Char('Y') => {
805 app.close_dialog();
807 }
808 KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
809 app.close_dialog();
810 }
811 _ => {}
812 }
813 }
814 ActiveDialog::Adjustment => {
815 match key.code {
816 KeyCode::Esc => {
817 app.close_dialog();
818 }
819 KeyCode::Enter => {
820 app.close_dialog();
822 }
823 _ => {
824 super::dialogs::adjustment::handle_key(app, key.code);
825 }
826 }
827 }
828 ActiveDialog::Budget => {
829 super::dialogs::budget::handle_key(app, key);
830 }
831 ActiveDialog::AddAccount | ActiveDialog::EditAccount(_) => {
832 super::dialogs::account::handle_key(app, key);
833 }
834 ActiveDialog::AddCategory | ActiveDialog::EditCategory(_) => {
835 super::dialogs::category::handle_key(app, key);
836 }
837 ActiveDialog::AddGroup => {
838 super::dialogs::group::handle_key(app, key);
839 }
840 ActiveDialog::None => {}
841 }
842 Ok(())
843}
844
845fn execute_confirmed_action(app: &mut App, message: &str) -> Result<()> {
847 if message.contains("Delete") && message.contains("transaction") {
849 if let Some(txn_id) = app.selected_transaction {
850 if let Err(e) = app.storage.transactions.delete(txn_id) {
851 app.set_status(format!("Failed to delete: {}", e));
852 } else {
853 let _ = app.storage.transactions.save();
854 app.selected_transaction = None;
855 app.set_status("Transaction deleted".to_string());
856 }
857 }
858 }
859 else if message.contains("Archive account") {
861 if let Ok(accounts) = app.storage.accounts.get_active() {
862 if let Some(account) = accounts.get(app.selected_account_index) {
863 let mut account = account.clone();
864 account.archive();
865 if let Err(e) = app.storage.accounts.upsert(account.clone()) {
866 app.set_status(format!("Failed to archive: {}", e));
867 } else {
868 let _ = app.storage.accounts.save();
869 app.set_status(format!("Account '{}' archived", account.name));
870 app.selected_account_index = 0;
872 if let Ok(active) = app.storage.accounts.get_active() {
873 app.selected_account = active.first().map(|a| a.id);
874 }
875 }
876 }
877 }
878 }
879 else if message.contains("Delete category") {
881 if let Some(category_id) = app.selected_category {
882 use crate::services::CategoryService;
883 let category_service = CategoryService::new(app.storage);
884 match category_service.delete_category(category_id) {
885 Ok(()) => {
886 app.set_status("Category deleted".to_string());
887 app.selected_category = None;
888 app.selected_category_index = 0;
889 }
890 Err(e) => {
891 app.set_status(format!("Failed to delete: {}", e));
892 }
893 }
894 }
895 }
896
897 Ok(())
898}