This repository has been archived on 2023-12-27. You can view files and clone it, but cannot push or open issues or pull requests.
ledger-tui/pkg/tui/commands/add/add.go

256 lines
5.5 KiB
Go

package add
import (
"errors"
"fmt"
"log"
"os"
"strings"
"time"
"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/spf13/cobra"
"github.com/tgrosinger/ledger-tui/pkg/tui/currencyinput"
"github.com/tgrosinger/ledger-tui/pkg/tui/dateinput"
"github.com/tgrosinger/ledger-tui/pkg/tui/postingsinput"
"github.com/tgrosinger/ledger-tui/pkg/tui/suggestions"
)
var AddCmd = &cobra.Command{
Use: "add",
Short: "Add a new transaction to a ledger file",
Args: cobra.NoArgs,
Run: executeAddTUI,
}
func executeAddTUI(cmd *cobra.Command, args []string) {
f, err := cmd.Flags().GetString("file")
if err != nil {
log.Fatalf("Missing ledger file path: %s", err.Error())
}
log.Println("Using ledger file in " + f)
addTUI := AddTxTUI{
focused: date,
furthestFocus: date,
date: dateinput.New(time.Now(), true),
description: textinput.New(),
total: currencyinput.New("$"),
suggestionBox: suggestions.New(f),
help: help.New(),
keymap: keymap{
nextField: key.NewBinding(
key.WithKeys("tab"),
key.WithHelp("tab", "Next")),
prevField: key.NewBinding(
key.WithKeys("shift+tab"),
key.WithHelp("shift+tab", "Previous")),
quit: key.NewBinding(
key.WithKeys("esc", "ctrl+c"),
key.WithHelp("esc", "Quit")),
},
}
addTUI.description.Placeholder = "Transaction Description"
addTUI.description.Validate = descriptionValidator
addTUI.description.Prompt = ""
p := tea.NewProgram(addTUI, tea.WithAltScreen())
if err := p.Start(); err != nil {
fmt.Printf("Alas, there's been an error: %v", err)
os.Exit(1)
}
}
func descriptionValidator(s string) error {
// The description must not have a line break
if strings.Contains(s, "\n") {
return errors.New("description must not contain a new line")
}
return nil
}
type focus int
const (
none focus = iota
date
description
total
lines
)
type keymap struct {
nextField key.Binding
prevField key.Binding
//increment key.Binding
//decrement key.Binding
quit key.Binding
}
type AddTxTUI struct {
// Add New Tx Form
focused focus
furthestFocus focus
// Fields
date dateinput.Model
description textinput.Model
total currencyinput.Model
suggestionBox suggestions.Model
keymap keymap
help help.Model
// halfFrame is a style which can fit two side by side, separated by a
// simple border. Will be resized when the window resizes.
halfFrame lipgloss.Style
}
func (m AddTxTUI) Init() tea.Cmd {
return tea.Batch(
m.date.Init(),
m.total.Init(),
m.suggestionBox.Init(),
)
}
func (m AddTxTUI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
cmds := make([]tea.Cmd, 3)
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.halfFrame = lipgloss.NewStyle().
Width((msg.Width/2)-2).
Height(msg.Height-4).
Border(lipgloss.NormalBorder(), true)
case dateinput.Msg:
switch msg {
case dateinput.NextInputMsg:
if m.focused == date {
log.Println("Focusing description")
m.focused = description
}
case dateinput.PrevInputMsg:
// No input field before date, so ignore
}
case postingsinput.Msg:
switch msg {
case postingsinput.NextInputMsg:
if m.focused == lines {
// TODO: Focus on save button
}
case postingsinput.PrevInputMsg:
if m.focused == lines {
m.focused = total
}
}
case tea.KeyMsg:
switch msg.Type {
// These keys should exit the program.
case tea.KeyCtrlC, tea.KeyEsc:
return m, tea.Quit
// Navigate between fields
case tea.KeyTab:
if m.focused == description {
m.focused = total
} else if m.focused == total {
m.focused = lines
}
case tea.KeyShiftTab:
if m.focused == description {
m.focused = date
} else if m.focused == total {
m.focused = description
}
}
}
m.setFocus()
var cmd tea.Cmd
m.date, cmd = m.date.Update(msg)
cmds = append(cmds, cmd)
m.description, cmd = m.description.Update(msg)
cmds = append(cmds, cmd)
m.total, cmd = m.total.Update(msg)
cmds = append(cmds, cmd)
m.suggestionBox, cmd = m.suggestionBox.Update(msg)
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...)
}
func (m *AddTxTUI) setFocus() {
if m.focused != date && m.date.Focused() {
m.date.Blur()
}
if m.focused != description && m.description.Focused() {
m.description.Blur()
}
if m.focused != total && m.total.Focused() {
m.total.Blur()
}
// TODO: Blur accounts
switch m.focused {
case date:
m.date.Focus()
if m.furthestFocus < date {
m.furthestFocus = date
}
case description:
m.description.Focus()
if m.furthestFocus < description {
m.furthestFocus = description
}
case total:
m.total.Focus()
if m.furthestFocus < total {
m.furthestFocus = total
}
case lines:
// TODO: Focus accounts
if m.furthestFocus < lines {
m.furthestFocus = lines
}
}
}
func (m AddTxTUI) helpView() string {
return "\n" + m.help.ShortHelpView([]key.Binding{
m.keymap.nextField,
m.keymap.prevField,
m.keymap.quit,
})
}
func (m AddTxTUI) View() string {
output := strings.Builder{}
output.WriteString("Date: " + m.date.View())
if m.furthestFocus >= description {
output.WriteString("\nDesc: " + m.description.View())
}
if m.furthestFocus >= total {
output.WriteString("\nTotal: " + m.total.View())
}
return lipgloss.JoinVertical(lipgloss.Left,
lipgloss.JoinHorizontal(lipgloss.Top,
m.halfFrame.Render(output.String()),
m.halfFrame.Render(m.suggestionBox.View())),
m.helpView(),
)
}