Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 33 additions & 15 deletions internal/ui/account_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ func NewAccountListModel[T ListEntity](api any, config *AccountListConfig[T]) Ac
}
m.list.Title = config.Title
m.list.SetShowStatusBar(false)
m.list.SetFilteringEnabled(false)
m.list.SetFilteringEnabled(true)
m.list.FilterInput.Blur()
m.list.FilterInput.Width = 20
m.list.SetShowHelp(false)
m.list.DisableQuitKeybindings()

Expand All @@ -55,8 +57,8 @@ func (m AccountListModel[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

if matchMsgType(msg, m.config.RefreshMsgType) {
return m, func() tea.Msg {
startLoading("Loading accounts...")
defer stopLoading()
opID := startLoading("Loading accounts...")
defer stopLoading(opID)
err := m.config.RefreshItems(m.api, m.config.AccountType)
if err != nil {
return notify.NotifyWarn(err.Error())()
Expand All @@ -66,7 +68,10 @@ func (m AccountListModel[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}

if matchMsgType(msg, m.config.UpdateMsgType) {
return m, m.updateItemsCmd()
return m, tea.Batch(
m.updateItemsCmd(),
Cmd(DataLoadCompletedMsg{DataType: m.config.AccountType}),
)
}

if msg, ok := msg.(UpdatePositions); ok {
Expand All @@ -80,19 +85,37 @@ func (m AccountListModel[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
m.list.SetSize(msg.layout.Width-h, height)
}
m.list.FilterInput.Width = 20
return m, nil
}

if !m.focus {
return m, nil
}

// Common keys
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, m.keymap.Filter):
if !m.list.FilterInput.Focused() {
m.list.FilterInput.Focus()
}
case key.Matches(msg, m.keymap.Quit):
return m, SetView(transactionsView)
if !m.list.FilterInput.Focused() {
return m, SetView(transactionsView)
}
m.list.FilterInput.Blur()
}
}
if m.list.FilterInput.Focused() {
m.list, cmd = m.list.Update(msg)
return m, cmd
}

// Common keys
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, m.keymap.ViewTransactions):
return m, SetView(transactionsView)
case key.Matches(msg, m.keymap.ViewAssets):
Expand All @@ -116,7 +139,7 @@ func (m AccountListModel[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
case key.Matches(msg, m.keymap.New):
return m, m.config.PromptNewFunc()
case key.Matches(msg, m.keymap.Filter):
case key.Matches(msg, m.keymap.FilterBy):
i, ok := m.list.SelectedItem().(accountListItem[T])
if ok {
if m.config.HasTotalRow && i.Entity.GetName() == "Total" {
Expand Down Expand Up @@ -146,12 +169,10 @@ func (m AccountListModel[T]) View() string {
}

func (m *AccountListModel[T]) Focus() {
m.list.FilterInput.Focus()
m.focus = true
}

func (m *AccountListModel[T]) Blur() {
m.list.FilterInput.Blur()
m.focus = false
}

Expand All @@ -167,6 +188,8 @@ func (m AccountListModel[T]) createTotalEntity(primary float64) list.Item {
}

func (m *AccountListModel[T]) updateItemsCmd() tea.Cmd {
opID := startLoading("Updating account list...")
defer stopLoading(opID)
items := m.config.GetItems(m.api, m.sorted)

if m.config.HasTotalRow && m.config.GetTotalFunc != nil {
Expand All @@ -178,15 +201,10 @@ func (m *AccountListModel[T]) updateItemsCmd() tea.Cmd {
m.list.InsertItem(0, totalEntity),
}

cmds = append(cmds, Cmd(DataLoadCompletedMsg{DataType: m.config.AccountType}))

return tea.Sequence(cmds...)
}

return tea.Batch(
m.list.SetItems(items),
Cmd(DataLoadCompletedMsg{DataType: m.config.AccountType}),
)
return m.list.SetItems(items)
}

func matchMsgType(msg, ty tea.Msg) bool {
Expand Down
7 changes: 2 additions & 5 deletions internal/ui/assets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,11 +461,11 @@ func TestModelAssets_View_UsesLeftPanelStyle(t *testing.T) {
func TestModelAssets_KeyQuit_SetsTransactionsView(t *testing.T) {
m := newFocusedAssetsModelWithAccount(t, firefly.Account{ID: "a1", Name: "Checking", CurrencyCode: "USD", Type: "asset"})

_, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("q")})
_, cmd := m.Update(tea.KeyMsg{Type: tea.KeyEsc})
msgs := collectMsgsFromCmd(cmd)

if len(msgs) != 1 {
t.Fatalf("expected 2 messages, got %d (%T)", len(msgs), msgs)
t.Fatalf("expected 1 message, got %d (%T)", len(msgs), msgs)
}

focused, ok := msgs[0].(SetFocusedViewMsg)
Expand All @@ -475,9 +475,6 @@ func TestModelAssets_KeyQuit_SetsTransactionsView(t *testing.T) {
if focused.state != transactionsView {
t.Fatalf("expected transactionsView, got %v", focused.state)
}
// if _, ok := msgs[1].(UpdatePositions); !ok {
// t.Fatalf("expected UpdatePositions, got %T", msgs[1])
// }
}

func TestModelAssets_KeySelect_SequencesFilterAndView(t *testing.T) {
Expand Down
66 changes: 46 additions & 20 deletions internal/ui/categories.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ func newModelCategories(api CategoryAPI) modelCategories {
}
m.list.Title = "Categories"
m.list.Styles.HelpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1)
m.list.SetFilteringEnabled(false)
m.list.SetFilteringEnabled(true)
m.list.FilterInput.Blur()
m.list.SetShowStatusBar(false)
m.list.SetShowHelp(false)
m.list.DisableQuitKeybindings()
Expand All @@ -89,11 +90,13 @@ func (m modelCategories) Init() tea.Cmd {
}

func (m modelCategories) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd

switch msg := msg.(type) {
case RefreshCategoryInsightsMsg:
return m, func() tea.Msg {
startLoading("Loading category insights...")
defer stopLoading()
opID := startLoading("Loading category insights...")
defer stopLoading(opID)
err := m.api.UpdateCategoriesInsights()
if err != nil {
return notify.NotifyWarn(err.Error())()
Expand All @@ -102,28 +105,22 @@ func (m modelCategories) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
case RefreshCategoriesMsg:
return m, func() tea.Msg {
startLoading("Loading categories...")
defer stopLoading()
opID := startLoading("Loading categories...")
defer stopLoading(opID)
err := m.api.UpdateCategories()
if err != nil {
return notify.NotifyWarn(err.Error())()
}
return CategoriesUpdateMsg{}
}
case CategoriesUpdateMsg:
tSpent, tEarned := m.api.GetTotalSpentEarnedCategories()
return m, tea.Batch(
m.list.SetItems(getCategoriesItems(m.api, m.sorted)),
m.list.InsertItem(0, categoryItem{
category: totalCategory,
spent: tSpent,
earned: tEarned,
}),
m.updateItemsCmd(),
Cmd(DataLoadCompletedMsg{DataType: "categories"}),
)
case NewCategoryMsg:
startLoading("Creating category...")
defer stopLoading()
opID := startLoading("Creating category...")
defer stopLoading(opID)
err := m.api.CreateCategory(msg.Category, "")
if err != nil {
return m, notify.NotifyWarn(err.Error())
Expand All @@ -140,22 +137,38 @@ func (m modelCategories) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
msg.layout.Height-v-msg.layout.TopSize,
)
}
m.list.FilterInput.Width = 20
}

if !m.focus {
return m, nil
}

var cmd tea.Cmd

switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, m.keymap.Filter):
m.list.FilterInput.Focus()
case key.Matches(msg, m.keymap.Quit):
return m, SetView(transactionsView)
if m.list.FilterInput.Focused() {
m.list.FilterInput.Blur()
} else {
return m, SetView(transactionsView)
}
}
}

if m.list.FilterInput.Focused() {
m.list, cmd = m.list.Update(msg)
return m, cmd
}

switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, m.keymap.New):
return m, CmdPromptNewCategory(SetView(categoriesView))
case key.Matches(msg, m.keymap.Filter):
case key.Matches(msg, m.keymap.FilterBy):
i, ok := m.list.SelectedItem().(categoryItem)
if ok {
if i.category == totalCategory {
Expand Down Expand Up @@ -205,12 +218,10 @@ func (m modelCategories) View() string {
}

func (m *modelCategories) Focus() {
m.list.FilterInput.Focus()
m.focus = true
}

func (m *modelCategories) Blur() {
m.list.FilterInput.Blur()
m.focus = false
}

Expand Down Expand Up @@ -258,3 +269,18 @@ func CmdPromptNewCategory(backCmd tea.Cmd) tea.Cmd {
},
)
}

func (m *modelCategories) updateItemsCmd() tea.Cmd {
opID := startLoading("Updating caterogy list...")
defer stopLoading(opID)
items := getCategoriesItems(m.api, m.sorted)
tSpent, tEarned := m.api.GetTotalSpentEarnedCategories()
return tea.Sequence(
m.list.SetItems(items),
m.list.InsertItem(0, categoryItem{
category: totalCategory,
spent: tSpent,
earned: tEarned,
}),
)
}
24 changes: 23 additions & 1 deletion internal/ui/categories_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,6 @@ func TestKeyPresses_NavigateToCorrectViews(t *testing.T) {
{"transactions", 't', transactionsView, false, 1},
{"liabilities", 'o', liabilitiesView, false, 1},
{"revenues", 'i', revenuesView, false, 1},
{"quit to transactions", 'q', transactionsView, false, 1},
}

for _, tt := range tests {
Expand Down Expand Up @@ -807,6 +806,29 @@ func TestKeyPresses_NavigateToCorrectViews(t *testing.T) {
}
})
}

// Test ESC key separately
t.Run("quit to transactions", func(t *testing.T) {
m := newFocusedCategoriesModelWithCategory(t, cat)
_, cmd := m.Update(tea.KeyMsg{Type: tea.KeyEsc})

if cmd == nil {
t.Fatal("expected cmd for esc key")
}

msgs := collectMsgsFromCmd(cmd)
if len(msgs) != 1 {
t.Fatalf("esc key: expected 1 message, got %d (%T)", len(msgs), msgs)
}

focused, ok := msgs[0].(SetFocusedViewMsg)
if !ok {
t.Fatalf("esc key: expected SetFocusedViewMsg, got %T", msgs[0])
}
if focused.state != transactionsView {
t.Fatalf("esc key: expected view %v, got %v", transactionsView, focused.state)
}
})
}

// Prompt callback tests
Expand Down
4 changes: 2 additions & 2 deletions internal/ui/expenses.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ func (m modelExpenses) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg.(type) {
case RefreshExpenseInsightsMsg:
return m, func() tea.Msg {
startLoading("Loading expense insights...")
defer stopLoading()
opID := startLoading("Loading expense insights...")
defer stopLoading(opID)
err := m.api.(ExpenseAPI).UpdateExpenseInsights()
if err != nil {
return notify.NotifyWarn(err.Error())()
Expand Down
30 changes: 29 additions & 1 deletion internal/ui/expenses_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,6 @@ func TestModelExpenses_KeyViewNavigation(t *testing.T) {
{"transactions", 't', transactionsView, false, 1},
{"liabilities", 'o', liabilitiesView, false, 1},
{"expenses (self)", 'e', expensesView, false, 1},
{"quit to transactions", 'q', transactionsView, false, 1},
}

for _, tt := range tests {
Expand Down Expand Up @@ -741,6 +740,35 @@ func TestModelExpenses_KeyViewNavigation(t *testing.T) {
}
})
}

// Test ESC key separately
t.Run("quit to transactions", func(t *testing.T) {
m := newFocusedExpensesModelWithAccount(t, firefly.Account{
ID: "e1",
Name: "Groceries",
CurrencyCode: "USD",
Type: "expense",
})

_, cmd := m.Update(tea.KeyMsg{Type: tea.KeyEsc})

if cmd == nil {
t.Fatal("expected cmd for esc key")
}

msgs := collectMsgsFromCmd(cmd)
if len(msgs) != 1 {
t.Fatalf("esc key: expected 1 message, got %d (%T)", len(msgs), msgs)
}

focused, ok := msgs[0].(SetFocusedViewMsg)
if !ok {
t.Fatalf("esc key: expected SetFocusedViewMsg, got %T", msgs[0])
}
if focused.state != transactionsView {
t.Fatalf("esc key: expected view %v, got %v", transactionsView, focused.state)
}
})
}

// Prompt callback tests
Expand Down
Loading