package grid import ( "fmt" "strings" "time" "ticktock/internal/store" ) // CellKind tags a LOGGED-column slot. type CellKind int const ( CellEmpty CellKind = iota CellLabel CellCont ) // Cell is one LOGGED-column slot. type Cell struct { Kind CellKind Key string Project string Text string } func hmm(d time.Duration) string { total := int(d.Minutes()) return fmt.Sprintf("%d:%02d", total/60, total%60) } // labelFor renders a session's LOGGED label. func labelFor(e store.Entry) string { tags := "" if len(e.Tags) > 0 { tags = " [" + strings.Join(e.Tags, ",") + "]" } return fmt.Sprintf("✓ %s%s (%s)", e.Description, tags, hmm(e.Duration)) } // paintLogged builds the LOGGED column: one label per session on its first // covered slot, continuation glyphs on later covered slots. Later-start wins a // contested slot. Running entries run to the last row. func paintLogged(g Grid, entries []store.Entry) []Cell { cells := make([]Cell, g.Rows) for _, e := range entries { startRow := g.RowOf(minuteOfDay(e.Start)) var endRow int if e.Running() { endRow = g.Rows - 1 } else { // end is exclusive; last covered slot holds end-1 minute endRow = g.RowOf(minuteOfDay(e.End) - 1) } if endRow < startRow { endRow = startRow } cells[startRow] = Cell{Kind: CellLabel, Key: e.Key, Project: e.Project, Text: labelFor(e)} for r := startRow + 1; r <= endRow && r < g.Rows; r++ { cells[r] = Cell{Kind: CellCont, Key: e.Key, Project: e.Project} } } return cells } // entryKeyAtRow returns the Key painted at row, if any. func entryKeyAtRow(cells []Cell, row int) (string, bool) { if row < 0 || row >= len(cells) { return "", false } if cells[row].Kind == CellEmpty || cells[row].Key == "" { return "", false } return cells[row].Key, true }