package tui import ( "context" "time" "punchcard/internal/queries" ) // BoxType represents the different boxes that can be selected type BoxType int const ( TimerBox BoxType = iota ClientsProjectsBox HistoryBox ) func (b BoxType) String() string { switch b { case TimerBox: return "Timer" case ClientsProjectsBox: return "Clients & Projects" case HistoryBox: return "History" default: return "Unknown" } } // AppModel is the main model for the TUI application type AppModel struct { ctx context.Context queries *queries.Queries selectedBox BoxType timerBoxModel TimerBoxModel clientsProjectsModel ClientsProjectsModel historyBoxModel HistoryBoxModel width int height int // Cached data to avoid DB queries in View() stats TimeStats runningTimerStart *time.Time // UTC timestamp when timer started, nil if not active // Modal state showModal bool modalType ModalType textInputModel TextInputModel } // ModalType represents different types of modals type ModalType int const ( ModalDescribeTimer ModalType = iota ) // TextInputModel represents a text input modal type TextInputModel struct { prompt string value string placeholder string cursorPos int } // TimerInfo holds information about the current timer state type TimerInfo struct { IsActive bool Duration time.Duration StartTime time.Time ClientName string ProjectName string Description string BillableRate *float64 } // TimeStats holds time statistics for display type TimeStats struct { TodayTotal time.Duration WeekTotal time.Duration } // TickMsg is sent every second to update the timer type TickMsg time.Time // KeyBinding represents the available key bindings for a view type KeyBinding struct { Key string Description string } // HistoryViewLevel represents the level of detail in history view type HistoryViewLevel int const ( HistoryLevelSummary HistoryViewLevel = iota // Level 1: Date/project summaries HistoryLevelDetails // Level 2: Individual entries ) // Box models for the three main components type TimerBoxModel struct { timerInfo TimerInfo } type ClientsProjectsModel struct { clients []queries.Client projects []queries.ListAllProjectsRow selectedIndex int // Index of selected row (client or project) selectedIsClient bool // True if selected row is a client, false if project } type HistoryBoxModel struct { entries []queries.TimeEntry clients []queries.Client // For looking up client names projects []queries.ListAllProjectsRow // For looking up project names viewLevel HistoryViewLevel selectedIndex int // Index of selected row // Cached running timer data to avoid recalculating in View() runningTimerStart *time.Time // UTC timestamp when timer started, nil if not active // Summary view data (level 1) summaryItems []HistorySummaryItem // Details view data (level 2) detailsEntries []queries.TimeEntry selectedSummaryItem *HistorySummaryItem // Which summary item we drilled down from } // HistorySummaryItem represents a date + client/project combination with total duration type HistorySummaryItem struct { Date time.Time ClientID int64 ClientName string ProjectID *int64 // nil if no project ProjectName *string // nil if no project TotalDuration time.Duration EntryCount int } // HistoryDisplayItem represents an item in the history view (either date header or summary/detail item) type HistoryDisplayItem struct { Type HistoryDisplayItemType DateHeader *string // Set if Type is DateHeader Summary *HistorySummaryItem // Set if Type is Summary Entry *queries.TimeEntry // Set if Type is Entry IsSelectable bool } type HistoryDisplayItemType int const ( HistoryItemDateHeader HistoryDisplayItemType = iota HistoryItemSummary HistoryItemEntry ) // ProjectsDisplayItem represents an item in the projects display order (either client or project) type ProjectsDisplayItem struct { IsClient bool ClientIndex int // Index in m.clients ProjectIndex int // Index in m.projects, only used when IsClient=false Client *queries.Client Project *queries.ListAllProjectsRow }