diff --git a/Makefile b/Makefile index bd39a8b..6b41981 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ start: build ./$(BINARY) debugv3: + rm -f puffin.log go run . -debug -v3 test: diff --git a/ui/v2/balance.go b/ui/v2/balance.go new file mode 100644 index 0000000..06a85ef --- /dev/null +++ b/ui/v2/balance.go @@ -0,0 +1,197 @@ +package ui + +import ( + "log" + + "github.com/charmbracelet/bubbles/table" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/siddhantac/puffin/ui/v2/interfaces" +) + +type queryBalanceMsg struct{} +type updateBalanceMsg struct { + rows []table.Row + columns []table.Column +} + +func queryBalanceCmd() tea.Msg { + return queryBalanceMsg{} +} + +type balanceReports struct { + height, width int + filterGroup *filterGroup + displayOptionsGroup *displayOptionsGroup + dataProvider interfaces.DataProvider + cmdRunner *cmdRunner + + assets *customTable + expenses *customTable +} + +func newBalanceReports(dataProvider interfaces.DataProvider, cmdRunner *cmdRunner) *balanceReports { + assetsTbl := newCustomTable("assets") + assetsTbl.SetReady(true) + assetsTbl.Focus() + + expensesTbl := newCustomTable("expenses") + expensesTbl.SetReady(true) + + optionFactory := displayOptionsGroupFactory{} + filterGroupFactory := filterGroupFactory{} + br := &balanceReports{ + assets: assetsTbl, + expenses: expensesTbl, + dataProvider: dataProvider, + filterGroup: filterGroupFactory.NewGroupBalance(), + displayOptionsGroup: optionFactory.NewReportsGroup(interfaces.Yearly, 3, interfaces.ByAccount), + cmdRunner: cmdRunner, + } + + return br +} + +func (b *balanceReports) Init() tea.Cmd { + return tea.Sequence( + b.assets.Init(), + b.expenses.Init(), + queryBalanceCmd, + ) +} + +func (b *balanceReports) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + b.width = msg.Width + b.height = msg.Height + + fg, _ := b.filterGroup.Update(msg) + b.filterGroup = fg.(*filterGroup) + + b.assets.SetHeight(msg.Height - 11) + b.expenses.SetHeight(msg.Height - 11) + + return b, nil + + case focusFilterMsg: + log.Printf("balances: msg: %T", msg) + b.filterGroup.Focus() + return b, nil + + case blurFilterMsg: + log.Printf("balances: msg: %T", msg) + b.filterGroup.Blur() + return b, nil + + case refreshDataMsg: + log.Printf("balances: msg: %T", msg) + b.filterGroup.Blur() + return b, queryBalanceCmd + + case tea.KeyMsg: + log.Printf("balances: msg: %T | %v", msg, msg) + if b.filterGroup.Focused() { + fg, cmd := b.filterGroup.Update(msg) + b.filterGroup = fg.(*filterGroup) + return b, cmd + } + + dg, cmd := b.displayOptionsGroup.Update(msg) + b.displayOptionsGroup = dg.(*displayOptionsGroup) + if cmd != nil { + return b, cmd + } + + b.assets, _ = b.assets.Update(msg) + b.expenses, _ = b.expenses.Update(msg) + return b, nil + + case queryBalanceMsg: + b.assets.SetReady(false) + b.expenses.SetReady(false) + f := func() tea.Msg { + return b.balanceData() + } + b.cmdRunner.Run(f) + return b, nil + + case updateBalanceMsg: + b.assets.SetRows(nil) + b.assets.SetColumns(msg.columns) + b.assets.SetRows(msg.rows) + b.assets.SetReady(true) + b.assets.SetCursor(0) + + b.expenses.SetRows(nil) + b.expenses.SetColumns(msg.columns) + b.expenses.SetRows(msg.rows) + b.expenses.SetReady(true) + b.expenses.SetCursor(0) + return b, nil + + default: + var cmd tea.Cmd + b.assets, cmd = b.assets.Update(msg) + b.expenses, cmd = b.expenses.Update(msg) + return b, cmd + } +} + +func (b *balanceReports) View() string { + filterView := lipgloss.JoinHorizontal( + lipgloss.Center, + b.filterGroup.View(), + " ", + lipgloss.NewStyle(). + Border(lipgloss.RoundedBorder(), false, false, false, true). + BorderForeground(lipgloss.Color("240")). + Render(divider.View()), + " ", + b.displayOptionsGroup.View(), + ) + return lipgloss.JoinVertical( + lipgloss.Left, + filterView, + b.assets.View(), + ) +} + +func (b *balanceReports) balanceData() updateBalanceMsg { + filter := interfaces.Filter{ + AccountType: "assets", + Account: b.filterGroup.AccountName(), + DateStart: b.filterGroup.DateStart(), + DateEnd: b.filterGroup.DateEnd(), + } + + displayOptions := interfaces.DisplayOptions{ + Interval: b.displayOptionsGroup.IntervalValue(), + Depth: b.displayOptionsGroup.DepthValue(), + Sort: b.displayOptionsGroup.SortValue(), + } + + balanceData, err := b.dataProvider.Balance(filter, displayOptions) + if err != nil { + panic(err) + } + + if len(balanceData) <= 1 { + return updateBalanceMsg{} + } + + cols := calculateColumns(balanceData[0], b.width) + + cols[0].Title = "account" + + data := balanceData[1:] + rows := make([]table.Row, 0, len(data)) + for _, row := range data { + rows = append(rows, row) + } + + return updateBalanceMsg{ + rows: rows, + columns: cols, + } +} diff --git a/ui/v2/command_runner.go b/ui/v2/command_runner.go index c581417..55986ee 100644 --- a/ui/v2/command_runner.go +++ b/ui/v2/command_runner.go @@ -29,10 +29,9 @@ func (cr *cmdRunner) Run(cmd command) { func (cr *cmdRunner) listen() { go func() { - log.Printf(">> listening") + log.Println("command runner listening") for f := range cr.workChan { if cr.p == nil { - log.Printf(">> program is nil") continue } diff --git a/ui/v2/complex_table.go b/ui/v2/complex_table.go index 83935ef..db06023 100644 --- a/ui/v2/complex_table.go +++ b/ui/v2/complex_table.go @@ -11,17 +11,20 @@ import ( type complexTable struct { title, upperTitle, lowerTitle string bottomBar table.Model - upper, lower table.Model + upper, lower *customTable focus bool columns []string } func newComplexTable() *complexTable { - return &complexTable{ - upper: table.New(), - lower: table.New(), + ct := &complexTable{ + upper: newCustomTable(""), + lower: newCustomTable(""), bottomBar: table.New(), } + ct.upper.SetReady(true) + ct.lower.SetReady(true) + return ct } func (c *complexTable) Focus() { @@ -72,9 +75,6 @@ func (c *complexTable) Update(msg tea.Msg) (*complexTable, tea.Cmd) { } func (c *complexTable) View() string { - tableStyleActive, styleActive := tblStyleActive() - tableStyleInactive, styleInactive := tblStyleInactive() - nonInteractiveTableStyle := table.DefaultStyles() nonInteractiveTableStyle.Header = nonInteractiveTableStyle.Header. BorderStyle(lipgloss.NormalBorder()). @@ -83,21 +83,11 @@ func (c *complexTable) View() string { Bold(false) c.bottomBar.SetStyles(nonInteractiveTableStyle) - var upper, lower string - - if c.upper.Focused() { - c.upper.SetStyles(tableStyleActive) - upper = styleActive.Render(c.upper.View()) + upper := c.upper.View() + lower := c.lower.View() - c.lower.SetStyles(tableStyleInactive) - lower = styleInactive.Render(c.lower.View()) - } else { - c.upper.SetStyles(tableStyleInactive) - upper = styleInactive.Render(c.upper.View()) + _, styleInactive := tableStyleInactive() - c.lower.SetStyles(tableStyleActive) - lower = styleActive.Render(c.lower.View()) - } return lipgloss.JoinVertical( lipgloss.Left, lipgloss.JoinVertical( @@ -145,18 +135,41 @@ func setColumns(complexTable *complexTable, width int) { return } + cols := calculateColumns(complexTable.columns, width) + + upperCols := make([]table.Column, len(cols)) + copy(upperCols, cols) + upperCols[0].Title = complexTable.upperTitle + complexTable.upper.SetColumns(upperCols) + + lowerCols := make([]table.Column, len(cols)) + copy(lowerCols, cols) + lowerCols[0].Title = complexTable.lowerTitle + complexTable.lower.SetColumns(lowerCols) + + netCols := make([]table.Column, len(cols)) + copy(netCols, cols) + netCols[0].Title = "Net" + complexTable.bottomBar.SetColumns(netCols) +} + +func calculateColumns(columnData []string, width int) []table.Column { accountColWidth := percent(width, 20) commodityColWidth := 10 remainingWidth := width - accountColWidth - commodityColWidth - 2 - otherColumnsWidth := remainingWidth/(len(complexTable.columns)-2) - 2 + otherColumnsWidth := remainingWidth/(len(columnData)-2) - 2 cols := []table.Column{ { - Title: complexTable.columns[1], + Title: "", + Width: accountColWidth, + }, + { + Title: columnData[1], Width: commodityColWidth, }, } - for _, c := range complexTable.columns[2:] { + for _, c := range columnData[2:] { cols = append(cols, table.Column{ Title: c, @@ -164,30 +177,5 @@ func setColumns(complexTable *complexTable, width int) { }) } - upperCols := make([]table.Column, 0) - upperCols = append(upperCols, table.Column{ - Title: complexTable.upperTitle, - Width: accountColWidth, - }, - ) - upperCols = append(upperCols, cols...) - complexTable.upper.SetColumns(upperCols) - - lowerCols := []table.Column{ - { - Title: complexTable.lowerTitle, - Width: accountColWidth, - }, - } - lowerCols = append(lowerCols, cols...) - complexTable.lower.SetColumns(lowerCols) - - netCols := []table.Column{ - { - Title: "Net", - Width: accountColWidth, - }, - } - netCols = append(netCols, cols...) - complexTable.bottomBar.SetColumns(netCols) + return cols } diff --git a/ui/v2/custom_table.go b/ui/v2/custom_table.go new file mode 100644 index 0000000..d8c1432 --- /dev/null +++ b/ui/v2/custom_table.go @@ -0,0 +1,95 @@ +package ui + +import ( + "github.com/charmbracelet/bubbles/spinner" + "github.com/charmbracelet/bubbles/table" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +type customTable struct { + table.Model + ready bool + name string + title string + titleModifier string + spinner spinner.Model +} + +func newCustomTable(title string) *customTable { + return &customTable{ + Model: table.New(), + title: title, + spinner: newSpinner(), + } +} + +func (c *customTable) Init() tea.Cmd { + return c.spinner.Tick +} + +func (c *customTable) Update(msg tea.Msg) (*customTable, tea.Cmd) { + var cmd tea.Cmd + switch msg := msg.(type) { + case spinner.TickMsg: + c.spinner, cmd = c.spinner.Update(msg) + return c, cmd + } + + c.Model, cmd = c.Model.Update(msg) + return c, cmd +} + +func (c *customTable) View() string { + tblStyleActive, styleActive := tableStyleActive() + tblStyleInactive, styleInactive := tableStyleInactive() + tblStyleUnready := tableStyleUnready() + + var ( + style lipgloss.Style + tableStyle table.Styles + ) + + if c.Model.Focused() { + style = styleActive + } else { + style = styleInactive + } + + title := " " + c.title + c.titleModifier + if !c.ready { + tableStyle = tblStyleUnready + title = title + " " + c.spinner.View() + } else { + if c.Model.Focused() { + tableStyle = tblStyleActive + } else { + tableStyle = tblStyleInactive + } + } + + c.Model.SetStyles(tableStyle) + + if c.title != "" { + return lipgloss.JoinVertical( + lipgloss.Left, + title, + style.Render(c.Model.View()), + ) + } + + return style.Render(c.Model.View()) + +} + +func (c *customTable) Ready() bool { + return c.ready +} + +func (c *customTable) SetReady(ready bool) { + c.ready = ready +} + +func (c *customTable) SetTitleModifier(s string) { + c.titleModifier = s +} diff --git a/ui/v2/display_option.go b/ui/v2/display_option.go index 9b18ba8..ccba08a 100644 --- a/ui/v2/display_option.go +++ b/ui/v2/display_option.go @@ -42,32 +42,6 @@ type displayOptionsGroup struct { options []*displayOption } -func newDisplayOptionsGroupHome(defaultDepth int, defaultSort interfaces.SortBy) *displayOptionsGroup { - dg := &displayOptionsGroup{ - depth: depth(defaultDepth), - sort: sort(string(defaultSort)), - } - dg.options = []*displayOption{ - dg.depth, - dg.sort, - } - return dg -} - -func newDisplayOptionsGroupReports(defaultInterval interfaces.Interval, defaultDepth int, defaultSort interfaces.SortBy) *displayOptionsGroup { - dg := &displayOptionsGroup{ - interval: interval(defaultInterval), - depth: depth(defaultDepth), - sort: sort(string(defaultSort)), - } - dg.options = []*displayOption{ - dg.interval, - dg.depth, - dg.sort, - } - return dg -} - func (dg *displayOptionsGroup) SortValue() interfaces.SortBy { if v, ok := dg.sort.value.(interfaces.SortBy); ok { return v @@ -150,3 +124,45 @@ func (dg *displayOptionsGroup) View() string { return view } + +type displayOptionsGroupFactory struct{} + +func (f displayOptionsGroupFactory) NewHomeGroup(defaultDepth int, defaultSort interfaces.SortBy) *displayOptionsGroup { + dg := &displayOptionsGroup{ + depth: depth(defaultDepth), + sort: sort(string(defaultSort)), + } + dg.options = []*displayOption{ + dg.depth, + dg.sort, + } + return dg +} + +func (f displayOptionsGroupFactory) NewReportsGroup(defaultInterval interfaces.Interval, defaultDepth int, defaultSort interfaces.SortBy) *displayOptionsGroup { + dg := &displayOptionsGroup{ + interval: interval(defaultInterval), + depth: depth(defaultDepth), + sort: sort(string(defaultSort)), + } + dg.options = []*displayOption{ + dg.interval, + dg.depth, + dg.sort, + } + return dg +} + +func (f displayOptionsGroupFactory) NewBalancesGroup(defaultInterval interfaces.Interval, defaultDepth int, defaultSort interfaces.SortBy) *displayOptionsGroup { + dg := &displayOptionsGroup{ + interval: interval(defaultInterval), + depth: depth(defaultDepth), + sort: sort(string(defaultSort)), + } + dg.options = []*displayOption{ + dg.interval, + dg.depth, + dg.sort, + } + return dg +} diff --git a/ui/v2/filter.go b/ui/v2/filter.go index a135b18..7057bb2 100644 --- a/ui/v2/filter.go +++ b/ui/v2/filter.go @@ -72,26 +72,6 @@ type filterGroup struct { focusedFilter int } -func newFilterGroupHome() *filterGroup { - fg := newFilterGroup() - fg.filters = []*filter{ - fg.startDate, - fg.endDate, - fg.account, - fg.description, - } - return fg -} -func newFilterGroupReports() *filterGroup { - fg := newFilterGroup() - fg.filters = []*filter{ - fg.startDate, - fg.endDate, - fg.account, - } - return fg -} - func newFilterGroup() *filterGroup { fg := &filterGroup{ @@ -216,3 +196,36 @@ func (fg *filterGroup) focusPrev() { fg.filters[fg.focusedFilter].Focus() log.Printf("focus prev filter: %d", fg.focusedFilter) } + +type filterGroupFactory struct{} + +func (f filterGroupFactory) NewGroupHome() *filterGroup { + fg := newFilterGroup() + fg.filters = []*filter{ + fg.startDate, + fg.endDate, + fg.account, + fg.description, + } + return fg +} + +func (f filterGroupFactory) NewGroupReports() *filterGroup { + fg := newFilterGroup() + fg.filters = []*filter{ + fg.startDate, + fg.endDate, + fg.account, + } + return fg +} + +func (f filterGroupFactory) NewGroupBalance() *filterGroup { + fg := newFilterGroup() + fg.filters = []*filter{ + fg.startDate, + fg.endDate, + fg.account, + } + return fg +} diff --git a/ui/v2/hledger/hledger.go b/ui/v2/hledger/hledger.go index 8e37fc9..484d260 100644 --- a/ui/v2/hledger/hledger.go +++ b/ui/v2/hledger/hledger.go @@ -107,7 +107,7 @@ func (hd HledgerData) IncomeStatement(filter interfaces.Filter, displayOptions i return nil, fmt.Errorf("failed to run command: %w", err) } - ct, err := hd.csvToComplexTable(r) + ct, err := hd.csvToComplexTable(r, "Revenues", "Expenses") if err != nil { return nil, fmt.Errorf("failed to convert csv to complexTable: %w", err) } @@ -128,7 +128,7 @@ func (hd HledgerData) BalanceSheet(filter interfaces.Filter, displayOptions inte return nil, fmt.Errorf("failed to run command: %w", err) } - ct, err := hd.csvToComplexTable(r) + ct, err := hd.csvToComplexTable(r, "Assets", "Liabilities") if err != nil { return nil, fmt.Errorf("failed to convert csv to complexTable: %w", err) } @@ -136,7 +136,7 @@ func (hd HledgerData) BalanceSheet(filter interfaces.Filter, displayOptions inte return ct, nil } -func (hd HledgerData) csvToComplexTable(r io.Reader) (*interfaces.ComplexTable, error) { +func (hd HledgerData) csvToComplexTable(r io.Reader, upperTitle, lowerTitle string) (*interfaces.ComplexTable, error) { ct := new(interfaces.ComplexTable) rows, err := hd.parseCSV(r) if err != nil { @@ -145,23 +145,23 @@ func (hd HledgerData) csvToComplexTable(r io.Reader) (*interfaces.ComplexTable, ct.Title = rows[0][0] ct.Columns = rows[1] - ct.Upper = make([][]string, 0) - ct.Lower = make([][]string, 0) + ct.UpperTitle = upperTitle + ct.LowerTitle = lowerTitle - index := 0 - ct.UpperTitle = rows[2][0] + var upperIndex int for i, row := range rows[3:] { - ct.Upper = append(ct.Upper, row) - if row[0] == "Total:" { - index = i + 4 // 4 to offset because we started iterating from row 3 + upperIndex = i + if row[0] == lowerTitle { break } } - ct.LowerTitle = rows[index][0] - for _, row := range rows[index+1 : len(rows)-1] { // start from index+1 to skip row 'Expenses' - ct.Lower = append(ct.Lower, row) - } + ct.Upper = make([][]string, upperIndex) + copy(ct.Upper, rows[3:3+upperIndex]) // 3 = 2 title rows, 1 upper title + + ct.Lower = make([][]string, len(rows)-upperIndex-4) // 4 = 2 title rows, 1 upper title, 1 lower title + copy(ct.Lower, rows[4+upperIndex:]) + ct.BottomBar = rows[len(rows)-1] return ct, nil } diff --git a/ui/v2/home.go b/ui/v2/home.go index 41cf0b0..74e70c1 100644 --- a/ui/v2/home.go +++ b/ui/v2/home.go @@ -6,7 +6,6 @@ import ( "github.com/siddhantac/puffin/ui/v2/interfaces" - "github.com/charmbracelet/bubbles/spinner" "github.com/charmbracelet/bubbles/table" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" @@ -14,7 +13,7 @@ import ( type home struct { height, width int - accounts table.Model + accounts *customTable filterGroup *filterGroup displayOptionsGroup *displayOptionsGroup cmdRunner *cmdRunner @@ -23,40 +22,39 @@ type home struct { selectedSubAccount string dataProvider interfaces.DataProvider - register table.Model - spinner spinner.Model - registerReady bool - - balance table.Model - balanceReady bool + register *customTable + balance *customTable } func newHome(dataProvider interfaces.DataProvider, cmdRunner *cmdRunner) *home { - regTbl := table.New( - table.WithHeight(20), - ) + regTbl := newCustomTable("(3) register") + regTbl.name = "register" + regTbl.SetHeight(20) col, row := accountsData(20) - accTbl := table.New( - table.WithFocused(true), - table.WithHeight(6), - table.WithColumns(col), - table.WithRows(row), - ) + accTbl := newCustomTable("(1) accounts") + accTbl.name = "accounts" + accTbl.SetReady(true) + accTbl.Focus() + accTbl.SetHeight(6) + accTbl.SetColumns(col) + accTbl.SetRows(row) - balTbl := table.New( - table.WithHeight(6), - ) + balTbl := newCustomTable("(2) balance") + balTbl.SetHeight(6) + balTbl.name = "balance" + + optionFactory := displayOptionsGroupFactory{} + filterGroupFactory := filterGroupFactory{} return &home{ register: regTbl, accounts: accTbl, balance: balTbl, dataProvider: dataProvider, - filterGroup: newFilterGroupHome(), - displayOptionsGroup: newDisplayOptionsGroupHome(3, interfaces.ByAccount), + filterGroup: filterGroupFactory.NewGroupHome(), + displayOptionsGroup: optionFactory.NewHomeGroup(3, interfaces.ByAccount), cmdRunner: cmdRunner, - spinner: newSpinner(), } } @@ -80,7 +78,9 @@ func (h *home) Init() tea.Cmd { return tea.Batch( h.filterGroup.Init(), h.queryBalanceTableCmd, - h.spinner.Tick, + h.accounts.Init(), + h.balance.Init(), + h.register.Init(), ) } @@ -112,11 +112,6 @@ func (h *home) Update(msg tea.Msg) (tea.Model, tea.Cmd) { h.filterGroup = fg.(*filterGroup) return h, cmd - case spinner.TickMsg: - var cmd tea.Cmd - h.spinner, cmd = h.spinner.Update(msg) - return h, cmd - case focusFilterMsg: log.Printf("home: msg: %T", msg) h.accounts.Blur() @@ -190,8 +185,8 @@ func (h *home) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } case queryBalance: - h.balanceReady = false - h.registerReady = false + h.balance.SetReady(false) + h.register.SetReady(false) f := func() tea.Msg { rows := h.balanceData(msg.account) return updateBalance{rows} @@ -200,28 +195,37 @@ func (h *home) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return h, nil case updateBalance: - h.balanceReady = true + h.balance.SetReady(true) h.balance.SetRows(msg.rows) h.balance.SetCursor(0) return h, h.queryRegisterTableCmd case queryRegister: - h.registerReady = false + h.register.SetReady(false) f := func() tea.Msg { rows := h.registerData(msg.subAccount) + h.register.SetTitleModifier(fmt.Sprintf(" (%s)", msg.subAccount)) return updateRegister{rows} } h.cmdRunner.Run(f) return h, nil case updateRegister: - h.registerReady = true + h.register.SetReady(true) h.register.SetRows(msg.rows) return h, nil case clearRegister: + h.register.SetTitleModifier("") h.register.SetRows(nil) + h.register.SetReady(true) return h, nil + + default: + var cmd1, cmd2 tea.Cmd + h.balance, cmd1 = h.balance.Update(msg) + h.register, cmd2 = h.register.Update(msg) + return h, tea.Batch(cmd1, cmd2) } return h, nil @@ -244,98 +248,13 @@ func (h *home) queryRegisterTableCmd() tea.Msg { } func (m *home) View() string { - s := table.DefaultStyles() - s.Header = s.Header. - BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")). - BorderBottom(true). - Bold(false) - s.Selected = s.Selected. - Foreground(lipgloss.Color("229")). - Background(lipgloss.Color("60")). - Bold(false) - - withSelected := table.DefaultStyles() - withSelected.Header = s.Header - withSelected.Selected = withSelected.Selected. - Foreground(lipgloss.Color("229")). - Background(lipgloss.Color("57")). - Bold(false) - - tblStyleActive := lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).BorderForeground(lipgloss.Color("White")) - tblStyleInactive := lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).BorderForeground(lipgloss.Color("240")) - - tblStyleUnready := table.DefaultStyles() - tblStyleUnready.Header. - BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")). - BorderBottom(true). - Foreground(lipgloss.Color("#666666")) - tblStyleUnready.Cell.Foreground(lipgloss.Color("#666666")) - - titleStyleInactive := lipgloss.NewStyle().Padding(0, 1).Foreground(lipgloss.Color("#AAAAAA")).Bold(true) - titleStyleActive := lipgloss.NewStyle().Padding(0, 1).Foreground(lipgloss.Color("White")).Bold(true) - - var ( - accTableStyle = tblStyleInactive - balTableStyle = tblStyleInactive - regTableStyle = tblStyleInactive - - accTitleStyle = titleStyleInactive - balTitleStyle = titleStyleInactive - recTitleStyle = titleStyleInactive - ) - - m.accounts.SetStyles(s) - m.balance.SetStyles(s) - m.register.SetStyles(s) - - if m.accounts.Focused() { - m.accounts.SetStyles(withSelected) - accTableStyle = tblStyleActive - accTitleStyle = titleStyleActive - } - - if m.balance.Focused() { - m.balance.SetStyles(withSelected) - balTableStyle = tblStyleActive - balTitleStyle = titleStyleActive - } - - if m.register.Focused() { - m.register.SetStyles(withSelected) - regTableStyle = tblStyleActive - recTitleStyle = titleStyleActive - } - - balanceTitleStr := " (2) Balances" - if !m.balanceReady { - balanceTitleStr = fmt.Sprintf("%s (2) Balances", m.spinner.View()) - m.balance.SetStyles(tblStyleUnready) - } - left := lipgloss.JoinVertical( lipgloss.Left, - accTitleStyle.Render("(1) Account Types"), - accTableStyle.Render(m.accounts.View()), - balTitleStyle.Render(balanceTitleStr), - balTableStyle.Render(m.balance.View()), + m.accounts.View(), + m.balance.View(), ) - recordsTitleStr := fmt.Sprintf(" (3) Records (%s)", m.selectedSubAccount) - if !m.registerReady { - recordsTitleStr = fmt.Sprintf("%s (3) Records (%s)", m.spinner.View(), m.selectedSubAccount) - m.register.SetStyles(tblStyleUnready) - } - recordsTitle := lipgloss.JoinHorizontal( - lipgloss.Top, - recTitleStyle.Render(recordsTitleStr), - ) - right := lipgloss.JoinVertical( - lipgloss.Left, - recordsTitle, - regTableStyle.Render(m.register.View()), - ) + right := m.register.View() filterView := lipgloss.JoinHorizontal( lipgloss.Center, diff --git a/ui/v2/reports.go b/ui/v2/reports.go index 9c01913..472348d 100644 --- a/ui/v2/reports.go +++ b/ui/v2/reports.go @@ -20,10 +20,12 @@ type reports struct { } func newReports(dataProvider interfaces.DataProvider, cmdRunner *cmdRunner) *reports { + optionFactory := displayOptionsGroupFactory{} + filterGroupFactory := filterGroupFactory{} a := &reports{ dataProvider: dataProvider, - filterGroup: newFilterGroupReports(), - displayOptionsGroup: newDisplayOptionsGroupReports(interfaces.Yearly, 3, interfaces.ByAccount), + filterGroup: filterGroupFactory.NewGroupReports(), + displayOptionsGroup: optionFactory.NewReportsGroup(interfaces.Yearly, 3, interfaces.ByAccount), cmdRunner: cmdRunner, } a.newIncomeStatement() @@ -128,6 +130,8 @@ func (a *reports) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return a, tea.Batch(queryIncomeStatementCmd, queryBalanceSheetCmd) case queryIncomeStatement: + a.incomeStatement.upper.SetReady(false) + a.incomeStatement.lower.SetReady(false) log.Printf("reports: msg: %T", msg) f := func() tea.Msg { data := a.setIncomeStatementData() @@ -139,9 +143,13 @@ func (a *reports) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case updateIncomeStatement: log.Printf("reports: msg: %T", msg) updateComplexTable(a.incomeStatement, msg.data, a.width) + a.incomeStatement.upper.SetReady(true) + a.incomeStatement.lower.SetReady(true) return a, nil case queryBalanceSheet: + a.balanceSheet.upper.SetReady(false) + a.balanceSheet.lower.SetReady(false) log.Printf("reports: msg: %T", msg) f := func() tea.Msg { data := a.setBalanceSheetData() @@ -153,6 +161,8 @@ func (a *reports) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case updateBalanceSheet: log.Printf("reports: msg: %T", msg) updateComplexTable(a.balanceSheet, msg.data, a.width) + a.balanceSheet.upper.SetReady(true) + a.balanceSheet.lower.SetReady(true) return a, nil } diff --git a/ui/v2/styles.go b/ui/v2/styles.go index 8ba0fe4..a58441f 100644 --- a/ui/v2/styles.go +++ b/ui/v2/styles.go @@ -10,7 +10,7 @@ var ( inactiveTitleStyle = lipgloss.NewStyle().Bold(true).PaddingLeft(1).PaddingRight(1) ) -func tblStyleActive() (table.Styles, lipgloss.Style) { +func tableStyleActive() (table.Styles, lipgloss.Style) { s := table.DefaultStyles() s.Header = s.Header. BorderStyle(lipgloss.NormalBorder()). @@ -24,7 +24,7 @@ func tblStyleActive() (table.Styles, lipgloss.Style) { return s, lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).BorderForeground(lipgloss.Color("White")) } -func tblStyleInactive() (table.Styles, lipgloss.Style) { +func tableStyleInactive() (table.Styles, lipgloss.Style) { s := table.DefaultStyles() s.Header = s.Header. BorderStyle(lipgloss.NormalBorder()). @@ -37,3 +37,15 @@ func tblStyleInactive() (table.Styles, lipgloss.Style) { Bold(false) return s, lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).BorderForeground(lipgloss.Color("240")) } + +func tableStyleUnready() table.Styles { + tblStyleUnready := table.DefaultStyles() + tblStyleUnready.Header. + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("240")). + BorderBottom(true). + Foreground(lipgloss.Color("#666666")) + tblStyleUnready.Cell.Foreground(lipgloss.Color("#666666")) + + return tblStyleUnready +} diff --git a/ui/v2/ui.go b/ui/v2/ui.go index eaf6514..c4cebcf 100644 --- a/ui/v2/ui.go +++ b/ui/v2/ui.go @@ -61,10 +61,12 @@ func newUI(cr *cmdRunner) *ui { tabTitles: []string{ "Home", "Reports", + "Balances", }, tabContent: []tea.Model{ newHome(hledger.HledgerData{}, cr), newReports(hledger.HledgerData{}, cr), + newBalanceReports(hledger.HledgerData{}, cr), }, captureKeysMode: true, cmdRunner: cr, @@ -76,6 +78,7 @@ func (u *ui) Init() tea.Cmd { tea.EnterAltScreen, u.tabContent[0].Init(), u.tabContent[1].Init(), + u.tabContent[2].Init(), } return tea.Sequence(batchCmds...) } @@ -107,6 +110,11 @@ func (u *ui) Update(msg tea.Msg) (tea.Model, tea.Cmd) { u.tabContent[1], cmd = u.tabContent[1].Update(msg) return u, cmd + case queryBalanceMsg, updateBalanceMsg: + log.Printf("ui: msg: %T", msg) + u.tabContent[2], cmd = u.tabContent[2].Update(msg) + return u, cmd + case tea.KeyMsg: log.Printf("ui: msg: %T | %v", msg, msg) if u.captureKeysMode {