Skip to content

Commit 28e788f

Browse files
committed
Enhance gaps view with full month scrolling
- Expand gaps view to show from start of current month to today - Add horizontal scrolling in both TUI and GUI gaps screens - TUI: Left/right arrows scroll viewport, Home/End jump to start/end - GUI: Horizontal scroll bar for navigating all days - Title now displays date range with scroll indicators - Update SPECIFICATION.md with new user story US-GAP-07
1 parent 00c3847 commit 28e788f

5 files changed

Lines changed: 243 additions & 55 deletions

File tree

PROMPT.log

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1938,3 +1938,27 @@ Completely rewrote `internal/gui/screens/history.go` to implement a master-detai
19381938
### Files Modified:
19391939
- `internal/gui/screens/history.go` - Complete rewrite with two-column layout
19401940

1941+
2026-03-27 23:40:27 - User: the history view should let you scroll horizontally to the left to see all the way to the start of the month if applicable (Note: User clarified they meant the GAPS view, not history view)
1942+
1943+
================================================================================
1944+
Date: 2026-03-28
1945+
Session: Fix Cannot Submit Dialog
1946+
1947+
Prompt:
1948+
The cannot submit dialog needs to have white text for the entries and you should be able to click on the items to open their timesheets to edit
1949+
1950+
Action Taken:
1951+
- Modified `showMissingDescriptionsDialog()` in `internal/gui/screens/history.go`
1952+
- Changed text color from dark gray (#333333) to white for better visibility on dark background
1953+
- Changed entry items from static text to clickable buttons
1954+
- When clicked, entries now:
1955+
1. Close the dialog
1956+
2. Select the entry in the list
1957+
3. Update the detail panel
1958+
4. Open the entry edit form
1959+
- Added helper function `findEntryIndex()` to locate entries by ID
1960+
- Increased dialog size slightly to accommodate clickable buttons
1961+
- Verified code compiles successfully
1962+
1963+
### Files Modified:
1964+
- `internal/gui/screens/history.go` - Updated showMissingDescriptionsDialog() for white text and clickable entries

SPECIFICATION.md

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,9 @@ This document provides a complete specification of the Go Timesheets Go applicat
258258

259259
### 4.5 Gaps Analysis
260260

261-
**US-GAP-01**: As a user, I want to see a visualization of my logged hours vs target for the last 14 days so that I can identify gaps in my timesheet.
261+
**US-GAP-01**: As a user, I want to see a visualization of my logged hours vs target from the start of the current month to today so that I can identify gaps in my timesheet.
262262
- Acceptance: Bar chart with daily hours, target line at 8 hours
263+
- Acceptance: Shows all days from month start to today (minimum 14 days if early in month)
263264

264265
**US-GAP-02**: As a user, I want to click on a gap to create a new entry for that day so that I can fill missing time quickly.
265266
- Acceptance: Click opens entry form pre-filled with date and calculated times
@@ -277,6 +278,12 @@ This document provides a complete specification of the Go Timesheets Go applicat
277278
**US-GAP-06**: As a user, I want the gaps view to refresh after creating or editing entries so that I see current data.
278279
- Acceptance: Automatic refresh after any entry modification
279280

281+
**US-GAP-07**: As a user, I want to scroll horizontally in the gaps view to see earlier days in the month so that I can review and fill gaps from earlier in the month.
282+
- Acceptance (TUI): Left/right arrows scroll the viewport when cursor reaches the edge
283+
- Acceptance (TUI): Home/End keys jump to start/end of the data
284+
- Acceptance (GUI): Horizontal scroll bar allows scrolling through all days
285+
- Acceptance: Title displays the date range of visible days with scroll indicators
286+
280287
### 4.6 Proof of Work (POW)
281288

282289
**US-POW-01**: As a user, I want to capture screenshots during my work sessions so that I have proof of my work.
@@ -355,7 +362,7 @@ This document provides a complete specification of the Go Timesheets Go applicat
355362
| History | View entries | Browse, select for detail |
356363
| Entry Detail | View/edit entry | Edit fields, delete, play POW |
357364
| Timesheet Creator | Create entry | Select project/task/activity, set times |
358-
| Gaps View | 14-day analysis | Navigate days, create entries |
365+
| Gaps View | Month gap analysis | Navigate/scroll days, create entries |
359366
| Settings | Configuration | Navigate to sub-screens |
360367
| Favourites Editor | Configure favourites | Edit slots, set properties |
361368
| Code Repos | Manage git links | Add/edit/remove associations |
@@ -368,7 +375,7 @@ This document provides a complete specification of the Go Timesheets Go applicat
368375
| Login | Authentication | Enter credentials, submit |
369376
| Dashboard | Main hub | View timer, navigate, use favourites |
370377
| History | View entries | Browse cards, click for detail |
371-
| Gaps View | 14-day analysis | Click bars, create entries |
378+
| Gaps View | Month gap analysis | Scroll bars, click to create entries |
372379
| Settings | Configuration | Navigate to sub-screens |
373380
| Favourites | Configure favourites | Click slots to edit |
374381
| Code Repos | Manage git links | Add/edit/remove associations |
@@ -525,20 +532,29 @@ This document provides a complete specification of the Go Timesheets Go applicat
525532

526533
### 9.1 Gaps Analysis
527534

528-
**Purpose**: Visualize missing hours over 14 days
535+
**Purpose**: Visualize missing hours from start of month to today
529536

530537
**Components**:
531-
- Bar chart with daily hour totals
538+
- Horizontally scrollable bar chart with daily hour totals
532539
- Target line at configurable hours (default 8)
533540
- Color coding: Green (met), White (gap)
534541
- Project segments within each bar
535542
- Warning indicators for missing descriptions
543+
- Date range title with scroll indicators (← →)
544+
545+
**Viewport**:
546+
- Shows 14 days at a time in the visible viewport
547+
- Loads all days from start of current month to today
548+
- Minimum 14 days shown (extends into previous month if early in month)
549+
- Auto-scrolls to show most recent days on load
536550

537551
**Interactions**:
538552
- Click gap segment: Create entry with pre-filled times
539553
- Click project segment: Edit existing entries
540-
- Navigate left/right: Select different days
554+
- Navigate left/right: Select different days, scrolls viewport at edges
541555
- Navigate up/down: Select different segments within day
556+
- Home/End: Jump to start/end of available data (TUI)
557+
- Horizontal scroll: Navigate through all days (GUI)
542558

543559
**Calculations**:
544560
- Gap hours = Target hours - Total logged hours
@@ -717,8 +733,10 @@ This document provides a complete specification of the Go Timesheets Go applicat
717733

718734
| Key | Action |
719735
|-----|--------|
720-
| `←/→` | Navigate days |
721-
| `↑/↓` | Navigate segments |
736+
| `←/→` | Navigate days (scrolls viewport at edges) |
737+
| `↑/↓` | Navigate segments within day |
738+
| `Home` | Jump to start of month |
739+
| `End` | Jump to today |
722740
| `Enter` | Create/edit entry |
723741
| `r` | Refresh |
724742
| `Esc` | Back |

internal/gui/screens/gaps.go

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import (
2121
"github.com/kartoza/go-timesheets-go/internal/service"
2222
)
2323

24-
// GapsScreen shows a 14-day view with vertical bars representing logged hours
24+
// GapsScreen shows a scrollable view with vertical bars representing logged hours
2525
type GapsScreen struct {
2626
Container fyne.CanvasObject
2727

@@ -31,9 +31,11 @@ type GapsScreen struct {
3131

3232
// Widgets
3333
barContainer *fyne.Container
34+
barScroll *container.Scroll
3435
detailPanel *fyne.Container
3536
statusLabel *canvas.Text
3637
loadingBar *widget.ProgressBarInfinite
38+
titleLabel *canvas.Text
3739

3840
// Detail panel labels
3941
detailProject *canvas.Text
@@ -64,11 +66,11 @@ func NewGapsScreen(apiClient *api.Client, window fyne.Window) *GapsScreen {
6466
}
6567

6668
func (s *GapsScreen) build() {
67-
// Title
68-
title := canvas.NewText("Time Gaps - Last 14 Days", color.NRGBA{R: 0xDD, G: 0xA0, B: 0x36, A: 0xFF})
69-
title.TextSize = 24
70-
title.TextStyle = fyne.TextStyle{Bold: true}
71-
title.Alignment = fyne.TextAlignCenter
69+
// Title (will be updated when data loads to show date range)
70+
s.titleLabel = canvas.NewText("Time Gaps", color.NRGBA{R: 0xDD, G: 0xA0, B: 0x36, A: 0xFF})
71+
s.titleLabel.TextSize = 24
72+
s.titleLabel.TextStyle = fyne.TextStyle{Bold: true}
73+
s.titleLabel.Alignment = fyne.TextAlignCenter
7274

7375
// Back button
7476
backButton := widget.NewButton("< Back", func() {
@@ -78,7 +80,7 @@ func (s *GapsScreen) build() {
7880
})
7981

8082
// Header row
81-
header := container.NewBorder(nil, nil, backButton, nil, container.NewCenter(title))
83+
header := container.NewBorder(nil, nil, backButton, nil, container.NewCenter(s.titleLabel))
8284

8385
// Status label
8486
s.statusLabel = canvas.NewText("", color.NRGBA{R: 0x9A, G: 0x9E, B: 0xA0, A: 0xFF})
@@ -89,8 +91,10 @@ func (s *GapsScreen) build() {
8991
s.loadingBar = widget.NewProgressBarInfinite()
9092
s.loadingBar.Hide()
9193

92-
// Bar container - will hold the 14 vertical bars
94+
// Bar container - will hold the vertical bars (scrollable horizontally)
9395
s.barContainer = container.NewHBox()
96+
s.barScroll = container.NewHScroll(s.barContainer)
97+
s.barScroll.SetMinSize(fyne.NewSize(600, 280))
9498

9599
// Detail panel - shows info about hovered segment
96100
s.detailProject = canvas.NewText("", color.NRGBA{R: 0xDD, G: 0xA0, B: 0x36, A: 0xFF})
@@ -148,7 +152,7 @@ func (s *GapsScreen) build() {
148152
)
149153

150154
// Instructions
151-
instructions := canvas.NewText("Hover over segments to see details. Click red gaps to fill them.", color.NRGBA{R: 0x9A, G: 0x9E, B: 0xA0, A: 0xFF})
155+
instructions := canvas.NewText("Hover over segments to see details. Click gaps to fill them. Scroll left to see earlier days.", color.NRGBA{R: 0x9A, G: 0x9E, B: 0xA0, A: 0xFF})
152156
instructions.TextSize = 11
153157
instructions.Alignment = fyne.TextAlignCenter
154158

@@ -168,7 +172,7 @@ func (s *GapsScreen) build() {
168172
),
169173
nil,
170174
nil,
171-
container.NewCenter(container.NewPadded(s.barContainer)),
175+
container.NewCenter(container.NewPadded(s.barScroll)),
172176
)
173177
}
174178

@@ -276,9 +280,38 @@ func (s *GapsScreen) Refresh() {
276280
s.setStatus("Error loading data: " + err.Error())
277281
return
278282
}
279-
s.dayData = s.gapsService.CalculateGaps(entries, 14)
283+
284+
// Calculate days from start of month to today
285+
now := time.Now()
286+
startOfMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
287+
daysInPeriod := int(now.Sub(startOfMonth).Hours()/24) + 1 // +1 to include today
288+
289+
// Ensure at least 14 days are shown
290+
if daysInPeriod < 14 {
291+
daysInPeriod = 14
292+
}
293+
294+
s.dayData = s.gapsService.CalculateGaps(entries, daysInPeriod)
280295
s.buildBars()
281-
s.setStatus(fmt.Sprintf("Showing %d days of data", len(s.dayData)))
296+
297+
// Update title with date range
298+
if len(s.dayData) > 0 {
299+
firstDay := s.dayData[0].Date
300+
lastDay := s.dayData[len(s.dayData)-1].Date
301+
s.titleLabel.Text = fmt.Sprintf("Time Gaps: %s - %s", firstDay.Format("Jan 02"), lastDay.Format("Jan 02"))
302+
s.titleLabel.Refresh()
303+
}
304+
305+
s.setStatus(fmt.Sprintf("Showing %d days of data (scroll left to see earlier days)", len(s.dayData)))
306+
307+
// Scroll to the right to show most recent days
308+
// Calculate the offset to show the rightmost content
309+
contentSize := s.barContainer.MinSize()
310+
scrollSize := s.barScroll.Size()
311+
if contentSize.Width > scrollSize.Width {
312+
s.barScroll.Offset = fyne.NewPos(contentSize.Width-scrollSize.Width, 0)
313+
s.barScroll.Refresh()
314+
}
282315
},
283316
)
284317
}

internal/gui/screens/history.go

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,7 +1062,7 @@ func (s *HistoryScreen) doSubmit(entries []api.TimelogEntry) {
10621062
// showMissingDescriptionsDialog shows an error dialog when entries are missing descriptions
10631063
func (s *HistoryScreen) showMissingDescriptionsDialog(entries []api.TimelogEntry) {
10641064
redColor := color.NRGBA{R: 0xE7, G: 0x4C, B: 0x3C, A: 0xFF}
1065-
darkGray := color.NRGBA{R: 0x33, G: 0x33, B: 0x33, A: 0xFF}
1065+
grayColor := color.NRGBA{R: 0x9A, G: 0x9E, B: 0xA0, A: 0xFF}
10661066

10671067
titleText := canvas.NewText("Cannot Submit", redColor)
10681068
titleText.TextSize = 18
@@ -1075,37 +1075,48 @@ func (s *HistoryScreen) showMissingDescriptionsDialog(entries []api.TimelogEntry
10751075
}
10761076
return "ies are"
10771077
}()),
1078-
darkGray,
1078+
color.White,
10791079
)
10801080
messageText.TextSize = 14
10811081

1082-
// Build list of entries without descriptions (max 5)
1082+
var d dialog.Dialog
1083+
1084+
// Build list of clickable entries without descriptions (max 5)
10831085
var entryList []fyne.CanvasObject
10841086
maxShow := 5
10851087
if len(entries) < maxShow {
10861088
maxShow = len(entries)
10871089
}
10881090
for i := 0; i < maxShow; i++ {
10891091
e := entries[i]
1092+
entryIndex := s.findEntryIndex(e.ID)
10901093
fromTime, _ := e.GetFromTimeAsTime()
1091-
entryText := canvas.NewText(
1092-
fmt.Sprintf("• %s - %s (%s)",
1093-
e.ProjectName,
1094-
fromTime.Local().Format("Mon Jan 02"),
1095-
fmt.Sprintf("%.1fh", e.Hours)),
1096-
darkGray,
1097-
)
1098-
entryText.TextSize = 12
1099-
entryList = append(entryList, entryText)
1094+
entryLabel := fmt.Sprintf("• %s - %s (%.1fh)",
1095+
e.ProjectName,
1096+
fromTime.Local().Format("Mon Jan 02"),
1097+
e.Hours)
1098+
1099+
// Create a clickable button styled as text
1100+
btn := widget.NewButton(entryLabel, func() {
1101+
d.Hide()
1102+
if entryIndex >= 0 {
1103+
s.selectedIndex = entryIndex
1104+
s.entryList.Select(widget.ListItemID(entryIndex))
1105+
s.updateDetailPanel()
1106+
s.showEntryDetail(entryIndex)
1107+
}
1108+
})
1109+
btn.Importance = widget.LowImportance
1110+
entryList = append(entryList, btn)
11001111
}
11011112
if len(entries) > 5 {
1102-
moreText := canvas.NewText(fmt.Sprintf("... and %d more", len(entries)-5), darkGray)
1113+
moreText := canvas.NewText(fmt.Sprintf("... and %d more", len(entries)-5), grayColor)
11031114
moreText.TextSize = 12
11041115
moreText.TextStyle = fyne.TextStyle{Italic: true}
11051116
entryList = append(entryList, moreText)
11061117
}
11071118

1108-
helpText := canvas.NewText("Click on each entry to add a description.", darkGray)
1119+
helpText := canvas.NewText("Click on each entry to add a description.", grayColor)
11091120
helpText.TextSize = 12
11101121
helpText.TextStyle = fyne.TextStyle{Italic: true}
11111122

@@ -1121,7 +1132,6 @@ func (s *HistoryScreen) showMissingDescriptionsDialog(entries []api.TimelogEntry
11211132
content.Add(widget.NewSeparator())
11221133
content.Add(container.NewCenter(helpText))
11231134

1124-
var d dialog.Dialog
11251135
okBtn := widget.NewButton("OK", func() {
11261136
d.Hide()
11271137
})
@@ -1130,6 +1140,16 @@ func (s *HistoryScreen) showMissingDescriptionsDialog(entries []api.TimelogEntry
11301140
fullContent := container.NewVBox(content, widget.NewSeparator(), buttons)
11311141

11321142
d = dialog.NewCustomWithoutButtons("", fullContent, s.window)
1133-
d.Resize(fyne.NewSize(400, 300))
1143+
d.Resize(fyne.NewSize(450, 350))
11341144
d.Show()
11351145
}
1146+
1147+
// findEntryIndex finds the index of an entry by ID in s.entries
1148+
func (s *HistoryScreen) findEntryIndex(entryID int) int {
1149+
for i, e := range s.entries {
1150+
if e.ID == entryID {
1151+
return i
1152+
}
1153+
}
1154+
return -1
1155+
}

0 commit comments

Comments
 (0)