256 lines
5.5 KiB
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(),
|
|
)
|
|
}
|