summaryrefslogtreecommitdiff
path: root/internal/tui/history_box.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/tui/history_box.go')
-rw-r--r--internal/tui/history_box.go683
1 files changed, 268 insertions, 415 deletions
diff --git a/internal/tui/history_box.go b/internal/tui/history_box.go
index 813eb17..a524d6d 100644
--- a/internal/tui/history_box.go
+++ b/internal/tui/history_box.go
@@ -2,134 +2,238 @@ package tui
import (
"fmt"
- "sort"
+ "slices"
+ "strconv"
"time"
"punchcard/internal/queries"
- tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss/v2"
)
+// 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
+)
+
+type HistorySummaryKey struct {
+ Date time.Time
+ ClientID int64
+ ProjectID int64
+}
+
+type HistoryBoxModel struct {
+ viewLevel HistoryViewLevel
+
+ summaryItems []HistorySummaryItem
+ summarySelection int
+
+ entries map[HistorySummaryKey][]queries.TimeEntry
+ detailSelection int
+}
+
+// HistorySummaryItem represents a date + client/project combination with total duration
+type HistorySummaryItem struct {
+ Date time.Time
+ ClientID int64
+ ClientName string
+ ProjectID *int64
+ ProjectName *string
+ TotalDuration time.Duration // will exclude the currently running timer, if any
+ EntryCount int
+}
+
// NewHistoryBoxModel creates a new history box model
func NewHistoryBoxModel() HistoryBoxModel {
- return HistoryBoxModel{
- viewLevel: HistoryLevelSummary,
- selectedIndex: 0,
+ return HistoryBoxModel{}
+}
+
+func buildIndex[T any, K comparable](items []T, keyf func(T) K) map[K][]T {
+ idx := make(map[K][]T)
+ for _, item := range items {
+ key := keyf(item)
+ idx[key] = append(idx[key], item)
}
+ return idx
}
-// Update handles messages for the history box
-func (m HistoryBoxModel) Update(msg tea.Msg) (HistoryBoxModel, tea.Cmd) {
- return m, nil
+func (m *HistoryBoxModel) regenerateSummaries(
+ clients []queries.Client,
+ projects map[int64][]queries.Project,
+ entries []queries.TimeEntry,
+ active TimerInfo,
+) {
+ m.summaryItems = make([]HistorySummaryItem, 0)
+
+ clientNames := make(map[int64]string)
+ for _, client := range clients {
+ clientNames[client.ID] = client.Name
+ }
+ projectNames := make(map[int64]string)
+ for _, group := range projects {
+ for _, project := range group {
+ projectNames[project.ID] = project.Name
+ }
+ }
+
+ m.entries = buildIndex(entries, func(entry queries.TimeEntry) HistorySummaryKey {
+ var projectID int64 = 0
+ if entry.ProjectID.Valid {
+ projectID = entry.ProjectID.Int64
+ }
+ return HistorySummaryKey{dateOnly(entry.StartTime), entry.ClientID, projectID}
+ })
+
+ for key, entries := range m.entries {
+ var totalDur time.Duration = 0
+ for _, entry := range entries {
+ if active.IsActive && active.EntryID == entry.ID {
+ continue
+ }
+ totalDur += entry.EndTime.Time.Sub(entry.StartTime)
+ }
+
+ item := HistorySummaryItem{
+ Date: key.Date,
+ ClientID: key.ClientID,
+ ClientName: clientNames[key.ClientID],
+ TotalDuration: totalDur,
+ EntryCount: len(entries),
+ }
+ if key.ProjectID != 0 {
+ item.ProjectID = &key.ProjectID
+ for _, project := range projects[key.ClientID] {
+ if project.ID == key.ProjectID {
+ item.ProjectName = &project.Name
+ break
+ }
+ }
+ }
+
+ m.summaryItems = append(m.summaryItems, item)
+ }
+
+ slices.SortFunc(m.summaryItems, func(a, b HistorySummaryItem) int {
+ if a.Date.Before(b.Date) {
+ return 1
+ } else if a.Date.After(b.Date) {
+ return -1
+ }
+
+ if a.ClientName < b.ClientName {
+ return -1
+ } else if a.ClientName > b.ClientName {
+ return 1
+ }
+
+ if a.ProjectName == nil {
+ return -1
+ }
+ if b.ProjectName == nil {
+ return 1
+ }
+ if *a.ProjectName < *b.ProjectName {
+ return -1
+ }
+ return 1
+ })
}
// View renders the history box
-func (m HistoryBoxModel) View(width, height int, isSelected bool) string {
+func (m HistoryBoxModel) View(width, height int, isSelected bool, timer TimerBoxModel) string {
var content string
- var title string
-
+
if len(m.entries) == 0 {
- content = "No recent entries\n\nStart tracking time to\nsee your history here."
- title = "📝 Recent History"
+ content = "📝 Recent History\n\nNo recent entries\n\nStart tracking time to\nsee your history here."
} else {
- if m.viewLevel == HistoryLevelDetails && m.selectedSummaryItem != nil {
- // Details view
- title = fmt.Sprintf("📝 Details: %s", m.formatSummaryTitle(*m.selectedSummaryItem))
- content = m.renderDetailsView()
- } else {
- // Summary view
- title = "📝 Recent History"
+ switch m.viewLevel {
+ case HistoryLevelSummary:
content = m.renderSummaryView()
+ case HistoryLevelDetails:
+ content = m.renderDetailsView(timer)
}
}
-
- // Apply box styling
+
style := unselectedBoxStyle
if isSelected {
style = selectedBoxStyle
}
-
- return style.Width(width).Height(height).Render(
- fmt.Sprintf("%s\n\n%s", title, content),
- )
+
+ return style.Width(width).Height(height).Render(content)
}
+var (
+ dateStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("3"))
+ summaryItemStyle = lipgloss.NewStyle()
+ selectedItemStyle = lipgloss.NewStyle().Background(lipgloss.Color("62")).Foreground(lipgloss.Color("230"))
+ entryStyle = lipgloss.NewStyle()
+ selectedEntryStyle = lipgloss.NewStyle().Background(lipgloss.Color("62")).Foreground(lipgloss.Color("230"))
+ activeEntryStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("196"))
+ selectedActiveEntryStyle = lipgloss.NewStyle().Background(lipgloss.Color("196")).Foreground(lipgloss.Color("230"))
+ descriptionStyle = lipgloss.NewStyle()
+ activeDescriptionStyle = lipgloss.NewStyle().Background(lipgloss.Color("62")).Foreground(lipgloss.Color("230"))
+)
+
// renderSummaryView renders the summary view (level 1) with date headers and client/project summaries
func (m HistoryBoxModel) renderSummaryView() string {
- var content string
- displayItems := m.getDisplayItems()
-
- if len(displayItems) == 0 {
- return "No recent entries found."
+ content := "📝 Recent History"
+
+ if len(m.summaryItems) == 0 {
+ return "\n\nNo recent entries found."
}
-
- // Find a valid selected index for rendering (don't modify the model)
- selectedIndex := m.selectedIndex
- if selectedIndex < 0 || selectedIndex >= len(displayItems) || !displayItems[selectedIndex].IsSelectable {
- // Find the first selectable item for display purposes
- for i, item := range displayItems {
- if item.IsSelectable {
- selectedIndex = i
- break
- }
+
+ var date *time.Time
+ for i, item := range m.summaryItems {
+ if date == nil || !date.Equal(item.Date) {
+ date = &item.Date
+ content += fmt.Sprintf("\n\n%s\n", dateStyle.Render(date.Format("2006/01/02")))
}
- }
-
- for i, item := range displayItems {
- var itemStyle lipgloss.Style
- var line string
-
- switch item.Type {
- case HistoryItemDateHeader:
- // Date header
- line = *item.DateHeader
- itemStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("3"))
-
- case HistoryItemSummary:
- // Summary item
- summary := item.Summary
- clientProject := m.formatSummaryTitle(*summary)
- line = fmt.Sprintf(" %s (%s)", clientProject, FormatDuration(summary.TotalDuration))
-
- // Highlight if selected
- if item.IsSelectable && selectedIndex == i {
- itemStyle = lipgloss.NewStyle().Background(lipgloss.Color("62")).Foreground(lipgloss.Color("230"))
- } else {
- itemStyle = lipgloss.NewStyle()
- }
+
+ style := summaryItemStyle
+ if m.summarySelection == i {
+ style = selectedItemStyle
}
-
- content += itemStyle.Render(line) + "\n"
+
+ // TODO: add in duration from the currently running timer (requires other data from AppModel)
+ line := fmt.Sprintf(" %s (%s)", m.formatSummaryTitle(item), FormatDuration(item.TotalDuration))
+ content += fmt.Sprintf("\n%s", style.Render(line))
}
-
+
return content
}
+func (m HistoryBoxModel) selectedEntries() []queries.TimeEntry {
+ summary := m.summaryItems[m.summarySelection]
+ key := HistorySummaryKey{
+ Date: summary.Date,
+ ClientID: summary.ClientID,
+ }
+ if summary.ProjectID != nil {
+ key.ProjectID = *summary.ProjectID
+ }
+ return m.entries[key]
+}
+
// renderDetailsView renders the details view (level 2) showing individual entries
-func (m HistoryBoxModel) renderDetailsView() string {
- var content string
-
- if len(m.detailsEntries) == 0 {
+func (m HistoryBoxModel) renderDetailsView(timer TimerBoxModel) string {
+ content := fmt.Sprintf("📝 Details: %s\n\n", m.formatSummaryTitle(m.summaryItems[m.summarySelection]))
+ entries := m.selectedEntries()
+
+ if len(entries) == 0 {
return "No entries found for this selection."
}
-
- for i, entry := range m.detailsEntries {
- // Calculate duration
+
+ for i, entry := range entries {
var duration time.Duration
if entry.EndTime.Valid {
duration = entry.EndTime.Time.Sub(entry.StartTime)
} else {
- // Active entry - use cached running timer data if available
- if m.runningTimerStart != nil {
- duration = time.Since(*m.runningTimerStart)
- } else {
- // Fallback to entry start time if cache not available
- duration = time.Since(entry.StartTime)
- }
+ duration = timer.currentTime.Sub(entry.StartTime)
}
-
- // Format time range
+
startTime := entry.StartTime.Local().Format("3:04 PM")
var timeRange string
if entry.EndTime.Valid {
@@ -138,379 +242,128 @@ func (m HistoryBoxModel) renderDetailsView() string {
} else {
timeRange = fmt.Sprintf("%s - now", startTime)
}
-
- // Entry line
+
entryLine := fmt.Sprintf("%s (%s)", timeRange, FormatDuration(duration))
-
- // Apply selection highlighting
- entryStyle := lipgloss.NewStyle()
- if m.selectedIndex == i {
- entryStyle = entryStyle.Background(lipgloss.Color("62")).Foreground(lipgloss.Color("230"))
- }
-
- // Also highlight active entries differently
- if !entry.EndTime.Valid {
- if m.selectedIndex == i {
- // Selected active entry
- entryStyle = entryStyle.Background(lipgloss.Color("196")).Foreground(lipgloss.Color("230"))
+
+ var style lipgloss.Style
+ if m.detailSelection == i {
+ if !entry.EndTime.Valid {
+ style = selectedActiveEntryStyle
} else {
- // Non-selected active entry
- entryStyle = activeTimerStyle
+ style = selectedEntryStyle
}
- }
-
- content += entryStyle.Render(entryLine) + "\n"
-
- // Description if available
- if entry.Description.Valid && entry.Description.String != "" {
- descStyle := lipgloss.NewStyle()
- if m.selectedIndex == i {
- descStyle = descStyle.Background(lipgloss.Color("62")).Foreground(lipgloss.Color("230"))
+ } else {
+ if !entry.EndTime.Valid {
+ style = activeEntryStyle
+ } else {
+ style = entryStyle
}
- content += descStyle.Render(fmt.Sprintf(" \"%s\"", entry.Description.String)) + "\n"
}
-
+
+ content += style.Render(entryLine)
+
+ descStyle := descriptionStyle
+ if m.detailSelection == i {
+ descStyle = activeDescriptionStyle
+ }
+ if entry.Description.Valid {
+ content += descStyle.Render(fmt.Sprintf(" \"%s\"", entry.Description.String))
+ }
+ content += "\n"
+
// Add spacing between entries
- if i < len(m.detailsEntries)-1 {
+ if i < len(entries)-1 {
content += "\n"
}
}
-
+
return content
}
// formatSummaryTitle creates a display title for a summary item
func (m HistoryBoxModel) formatSummaryTitle(summary HistorySummaryItem) string {
- var title string
- if summary.ClientName != "" {
- title = summary.ClientName
- } else {
- title = fmt.Sprintf("Client %d", summary.ClientID)
- }
-
if summary.ProjectID != nil {
- if summary.ProjectName != nil && *summary.ProjectName != "" {
- title += fmt.Sprintf(" / %s", *summary.ProjectName)
- } else {
- title += fmt.Sprintf(" / Project %d", *summary.ProjectID)
- }
+ return fmt.Sprintf("%s / %s", summary.ClientName, *summary.ProjectName)
}
-
- return title
+ return fmt.Sprintf("%s / General work", summary.ClientName)
}
-// UpdateEntries updates the history entries and regenerates summary data
-func (m HistoryBoxModel) UpdateEntries(entries []queries.TimeEntry) HistoryBoxModel {
- m.entries = entries
- // Reset view to summary level
- m.viewLevel = HistoryLevelSummary
- m.selectedSummaryItem = nil
- // Regenerate summary data
- m.summaryItems = m.generateSummaryItems(entries)
- // Ensure we have a valid selection pointing to a selectable item
- m.selectedIndex = 0
- m = m.ensureValidSelection()
- return m
+func dateOnly(t time.Time) time.Time {
+ return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
}
-// UpdateData updates the history entries along with client and project data for name lookups
-func (m HistoryBoxModel) UpdateData(entries []queries.TimeEntry, clients []queries.Client, projects []queries.ListAllProjectsRow) HistoryBoxModel {
- m.entries = entries
- m.clients = clients
- m.projects = projects
- // Reset view to summary level
- m.viewLevel = HistoryLevelSummary
- m.selectedSummaryItem = nil
- // Regenerate summary data with the new client/project data
- m.summaryItems = m.generateSummaryItems(entries)
- // Ensure we have a valid selection pointing to a selectable item
- m.selectedIndex = 0
- m = m.ensureValidSelection()
- return m
-}
-
-// NextSelection moves to the next selectable row
-func (m HistoryBoxModel) NextSelection() HistoryBoxModel {
- displayItems := m.getDisplayItems()
- if len(displayItems) == 0 {
- return m
- }
-
- // Ensure current selection is valid
- m = m.ensureValidSelection()
-
- // Find next selectable item
- for i := m.selectedIndex + 1; i < len(displayItems); i++ {
- if displayItems[i].IsSelectable {
- m.selectedIndex = i
- break
- }
+func (m *HistoryBoxModel) changeSelection(forward bool) {
+ switch m.viewLevel {
+ case HistoryLevelSummary:
+ m.changeSummarySelection(forward)
+ case HistoryLevelDetails:
+ m.changeDetailsSelection(forward)
}
-
- return m
}
-// PrevSelection moves to the previous selectable row
-func (m HistoryBoxModel) PrevSelection() HistoryBoxModel {
- displayItems := m.getDisplayItems()
- if len(displayItems) == 0 {
- return m
- }
-
- // Ensure current selection is valid
- m = m.ensureValidSelection()
-
- // Find previous selectable item
- for i := m.selectedIndex - 1; i >= 0; i-- {
- if displayItems[i].IsSelectable {
- m.selectedIndex = i
- break
+func (m *HistoryBoxModel) changeSummarySelection(forward bool) {
+ newIdx := m.summarySelection
+ if forward {
+ newIdx++
+ if newIdx < len(m.summaryItems) {
+ m.summarySelection = newIdx
}
- }
-
- return m
-}
-
-// ensureValidSelection ensures the selected index points to a valid selectable item
-func (m HistoryBoxModel) ensureValidSelection() HistoryBoxModel {
- displayItems := m.getDisplayItems()
- if len(displayItems) == 0 {
- m.selectedIndex = 0
- return m
- }
-
- // If current selection is valid and selectable, keep it
- if m.selectedIndex >= 0 && m.selectedIndex < len(displayItems) && displayItems[m.selectedIndex].IsSelectable {
- return m
- }
-
- // Find the first selectable item
- for i, item := range displayItems {
- if item.IsSelectable {
- m.selectedIndex = i
- break
+ } else {
+ newIdx--
+ if newIdx >= 0 {
+ m.summarySelection = newIdx
}
}
-
- return m
}
-// GetSelectedEntry returns the currently selected entry
-func (m HistoryBoxModel) GetSelectedEntry() *queries.TimeEntry {
- if m.viewLevel == HistoryLevelDetails {
- if m.selectedIndex >= 0 && m.selectedIndex < len(m.detailsEntries) {
- return &m.detailsEntries[m.selectedIndex]
+func (m *HistoryBoxModel) changeDetailsSelection(forward bool) {
+ newIdx := m.detailSelection
+ entries := m.selectedEntries()
+ if forward {
+ newIdx++
+ if newIdx < len(entries) {
+ m.detailSelection = newIdx
}
} else {
- if m.selectedIndex >= 0 && m.selectedIndex < len(m.entries) {
- return &m.entries[m.selectedIndex]
+ newIdx--
+ if newIdx >= 0 {
+ m.detailSelection = newIdx
}
}
- return nil
}
-// generateSummaryItems creates summary items grouped by date and client/project
-func (m HistoryBoxModel) generateSummaryItems(entries []queries.TimeEntry) []HistorySummaryItem {
- // Group entries by date and client/project combination
- groupMap := make(map[string]*HistorySummaryItem)
-
- for _, entry := range entries {
- // Get the date (year-month-day only)
- date := entry.StartTime.Truncate(24 * time.Hour)
-
- // Create a key for grouping
- key := fmt.Sprintf("%s-%d", date.Format("2006-01-02"), entry.ClientID)
- if entry.ProjectID.Valid {
- key += fmt.Sprintf("-%d", entry.ProjectID.Int64)
- }
-
- // Calculate duration for this entry
- var duration time.Duration
- if entry.EndTime.Valid {
- duration = entry.EndTime.Time.Sub(entry.StartTime)
- } else {
- // Active entry - use cached running timer data if available
- if m.runningTimerStart != nil {
- duration = time.Since(*m.runningTimerStart)
- } else {
- // Fallback to entry start time if cache not available
- duration = time.Since(entry.StartTime)
- }
- }
-
- // Add to or update existing group
- if existing, exists := groupMap[key]; exists {
- existing.TotalDuration += duration
- existing.EntryCount++
- } else {
- // Create new summary item
- item := &HistorySummaryItem{
- Date: date,
- ClientID: entry.ClientID,
- ClientName: m.lookupClientName(entry.ClientID),
- TotalDuration: duration,
- EntryCount: 1,
- }
-
- if entry.ProjectID.Valid {
- projectID := entry.ProjectID.Int64
- item.ProjectID = &projectID
- projectName := m.lookupProjectName(projectID)
- item.ProjectName = &projectName
- }
-
- groupMap[key] = item
- }
- }
-
- // Convert map to slice and sort by date (descending) then by client name
- var items []HistorySummaryItem
- for _, item := range groupMap {
- items = append(items, *item)
- }
-
- sort.Slice(items, func(i, j int) bool {
- // Sort by date descending, then by client name ascending
- if !items[i].Date.Equal(items[j].Date) {
- return items[i].Date.After(items[j].Date)
- }
- return items[i].ClientName < items[j].ClientName
- })
-
- return items
-}
+func (m HistoryBoxModel) selection() (string, string, string, *float64) {
+ item := m.summaryItems[m.summarySelection]
-// lookupClientName finds the client name by ID
-func (m HistoryBoxModel) lookupClientName(clientID int64) string {
- for _, client := range m.clients {
- if client.ID == clientID {
- return client.Name
- }
- }
- return fmt.Sprintf("Client %d", clientID) // Fallback if not found
-}
+ clientID := strconv.FormatInt(item.ClientID, 10)
-// lookupProjectName finds the project name by ID
-func (m HistoryBoxModel) lookupProjectName(projectID int64) string {
- for _, project := range m.projects {
- if project.ID == projectID {
- return project.Name
- }
+ projectID := ""
+ if item.ProjectID != nil {
+ projectID = strconv.FormatInt(*item.ProjectID, 10)
}
- return fmt.Sprintf("Project %d", projectID) // Fallback if not found
-}
-// DrillDown drills down into the selected summary item
-func (m HistoryBoxModel) DrillDown() HistoryBoxModel {
- if m.viewLevel != HistoryLevelSummary {
- return m
- }
-
- // Get the selected summary item
- displayItems := m.getDisplayItems()
- if m.selectedIndex >= 0 && m.selectedIndex < len(displayItems) {
- item := displayItems[m.selectedIndex]
- if item.Type == HistoryItemSummary && item.Summary != nil {
- // Switch to details view
- m.viewLevel = HistoryLevelDetails
- m.selectedSummaryItem = item.Summary
- m.selectedIndex = 0
-
- // Filter entries for this date/client/project combination
- m.detailsEntries = m.getEntriesForSummaryItem(*item.Summary)
+ description := ""
+ var rate *float64
+ if m.viewLevel == HistoryLevelDetails {
+ entry := m.selectedEntries()[m.detailSelection]
+ if entry.Description.Valid {
+ description = entry.Description.String
+ }
+ if entry.BillableRate.Valid {
+ cents := entry.BillableRate.Int64
+ dollars := float64(cents) / 100
+ rate = &dollars
}
}
-
- return m
-}
-// GoBack goes back to summary view from details view
-func (m HistoryBoxModel) GoBack() HistoryBoxModel {
- if m.viewLevel == HistoryLevelDetails {
- m.viewLevel = HistoryLevelSummary
- m.selectedSummaryItem = nil
- m.selectedIndex = 0
- m.detailsEntries = nil
- // Ensure we have a valid selection pointing to a selectable item
- m = m.ensureValidSelection()
- }
- return m
+ return clientID, projectID, description, rate
}
-// getEntriesForSummaryItem returns all entries that match the given summary item
-func (m HistoryBoxModel) getEntriesForSummaryItem(summary HistorySummaryItem) []queries.TimeEntry {
- var matchingEntries []queries.TimeEntry
-
- for _, entry := range m.entries {
- // Check if entry matches the summary item criteria
- entryDate := entry.StartTime.Truncate(24 * time.Hour)
- if !entryDate.Equal(summary.Date) {
- continue
- }
-
- if entry.ClientID != summary.ClientID {
- continue
- }
-
- // Check project ID match
- if summary.ProjectID == nil && entry.ProjectID.Valid {
- continue
- }
- if summary.ProjectID != nil && (!entry.ProjectID.Valid || entry.ProjectID.Int64 != *summary.ProjectID) {
- continue
- }
-
- matchingEntries = append(matchingEntries, entry)
- }
-
- // Sort by start time descending (most recent first)
- sort.Slice(matchingEntries, func(i, j int) bool {
- return matchingEntries[i].StartTime.After(matchingEntries[j].StartTime)
- })
-
- return matchingEntries
+func (m *HistoryBoxModel) drillDown() {
+ m.viewLevel = HistoryLevelDetails
+ m.detailSelection = 0
}
-// getDisplayItems returns the items to display based on current view level
-func (m HistoryBoxModel) getDisplayItems() []HistoryDisplayItem {
- if m.viewLevel == HistoryLevelDetails {
- // Details view - show individual entries
- var items []HistoryDisplayItem
- for _, entry := range m.detailsEntries {
- entryCopy := entry
- items = append(items, HistoryDisplayItem{
- Type: HistoryItemEntry,
- Entry: &entryCopy,
- IsSelectable: true,
- })
- }
- return items
- } else {
- // Summary view - show date headers and summary items
- var items []HistoryDisplayItem
- var currentDate *time.Time
-
- for _, summary := range m.summaryItems {
- // Add date header if this is a new date
- if currentDate == nil || !currentDate.Equal(summary.Date) {
- dateStr := summary.Date.Format("Monday, January 2, 2006")
- items = append(items, HistoryDisplayItem{
- Type: HistoryItemDateHeader,
- DateHeader: &dateStr,
- IsSelectable: false,
- })
- currentDate = &summary.Date
- }
-
- // Add summary item
- summaryCopy := summary
- items = append(items, HistoryDisplayItem{
- Type: HistoryItemSummary,
- Summary: &summaryCopy,
- IsSelectable: true,
- })
- }
-
- return items
- }
-} \ No newline at end of file
+func (m *HistoryBoxModel) drillUp() {
+ m.viewLevel = HistoryLevelSummary
+}