ledgr 0.1.0

A TUI personal finance app: import bank/pension statements, build a local database, and analyze your finances.
use crate::db::Db;
use crate::model::{Account, Transaction};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Screen {
    Accounts,
    Transactions,
}

pub struct App {
    pub db: Db,
    pub screen: Screen,
    pub accounts: Vec<Account>,
    pub selected_account: usize,
    pub transactions: Vec<Transaction>,
    pub selected_transaction: usize,
    pub should_quit: bool,
    pub status: String,
}

impl App {
    pub fn new(db: Db) -> anyhow::Result<Self> {
        let accounts = db.list_accounts()?;
        Ok(Self {
            db,
            screen: Screen::Accounts,
            accounts,
            selected_account: 0,
            transactions: Vec::new(),
            selected_transaction: 0,
            should_quit: false,
            status: "j/k or arrows to move, enter to open, esc back, q to quit".into(),
        })
    }

    pub fn open_selected_account(&mut self) -> anyhow::Result<()> {
        let Some(account) = self.accounts.get(self.selected_account) else {
            return Ok(());
        };
        self.transactions = self.db.list_transactions_for_account(account.id)?;
        self.selected_transaction = 0;
        self.screen = Screen::Transactions;
        Ok(())
    }

    pub fn move_selection(&mut self, delta: i32) {
        let len = match self.screen {
            Screen::Accounts => self.accounts.len(),
            Screen::Transactions => self.transactions.len(),
        };
        if len == 0 {
            return;
        }
        let selected = match self.screen {
            Screen::Accounts => &mut self.selected_account,
            Screen::Transactions => &mut self.selected_transaction,
        };
        let next = *selected as i32 + delta;
        *selected = next.clamp(0, len as i32 - 1) as usize;
    }

    pub fn back(&mut self) {
        if self.screen == Screen::Transactions {
            self.screen = Screen::Accounts;
        }
    }
}