Project, Task and Time management
package main

import (
	"context"
	"fmt"
	"strconv"

	"github.com/charmbracelet/bubbles/spinner"
	"github.com/charmbracelet/bubbles/table"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
)

type taskListModel struct {
	config  *config
	table   table.Model
	err     error
	loading bool
	spinner spinner.Model
}

func newTaskList(config *config) taskListModel {
	m := taskListModel{
		config:  config,
		loading: true,
		spinner: config.newSpinner(),
	}

	t := table.New(
		table.WithColumns([]table.Column{
			{Title: "ID", Width: 4},
			{Title: "Name", Width: 10},
			{Title: "Status", Width: 8},
		}),
		table.WithFocused(true),
		table.WithHeight(8),
	)

	ts := table.DefaultStyles()
	ts.Header = ts.Header.
		BorderStyle(lipgloss.NormalBorder()).
		BorderForeground(lipgloss.Color("240")).
		BorderBottom(true).
		Bold(false)
	ts.Selected = ts.Selected.
		Foreground(lipgloss.Color("229")).
		Background(lipgloss.Color("57")).
		Bold(false)
	t.SetStyles(ts)

	m.table = t
	return m
}

func (m taskListModel) fetchTasks() tea.Msg {
	m.config.mu.RLock()
	defer m.config.mu.RUnlock()

	row, err := m.config.queries.ListTasks(context.Background())
	if err != nil {
		return errMsg{err}
	}

	tableRows := make([]table.Row, len(row), len(row))
	for i, e := range row {
		tableRows[i] = table.Row{strconv.FormatInt(e.ID, 10), e.Name, e.Status}
	}

	return rows{tableRows}
}

func (m taskListModel) Init() tea.Cmd {
	return tea.Batch(m.fetchTasks, m.spinner.Tick)
}

func (m taskListModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	var cmd tea.Cmd
	switch msg := msg.(type) {
	case rows:
		m.table.SetRows(msg.data)
		m.loading = false
		return m, nil

	case errMsg:
		m.err = msg
		m.loading = false
		return m, tea.Quit

	case tea.KeyMsg:
		switch msg.String() {
		case "ctrl+n":
			return m, changeView(newTaskEdit(m.config, nil, ""))

		case "enter":
			id, _ := strconv.ParseInt(m.table.SelectedRow()[0], 10, 64)
			return m, changeView(newTask(m.config, id))
		}
	}

	m.table, cmd = m.table.Update(msg)
	return m, cmd
}

func (m taskListModel) View() string {
	if m.loading {
		return fmt.Sprintf("\n%s Loading\n\n", m.spinner.View())
	}

	if m.err != nil {
		return fmt.Sprintf("\nFailed fetching project: %v\n\n", m.err)
	}

	return m.config.styles.BaseTable.Render(m.table.View()) + "\n"
}