diff --git a/internal/tui/model.go b/internal/tui/model.go index a303b33..18d283c 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -144,9 +144,10 @@ type Model struct { scheduleTable table.Model // User picker (schedule assignment) - users []api.User - usersLoading bool - userPickerTable table.Model + users []api.User + usersLoading bool + userPickerTable table.Model + pickerAssignWeek bool // User management section userManageTable table.Model @@ -328,7 +329,7 @@ func alertColumns(width int) []table.Column { } func scheduleColumns(width int) []table.Column { - dateW := 16 + dateW := 18 onCallW := width - dateW - 6 if onCallW < 15 { onCallW = 15 @@ -381,9 +382,15 @@ func scheduleRows(days []scheduleDay) []table.Row { rows := make([]table.Row, len(days)) for i, d := range days { dateStr := d.date.Format("2006-01-02") - label := d.date.Format("Jan 02 Mon") + showWeek := i == 0 || d.date.Weekday() == time.Monday + _, week := d.date.ISOWeek() + weekPrefix := " " + if showWeek { + weekPrefix = fmt.Sprintf("W%02d ", week) + } + label := weekPrefix + d.date.Format("Jan 02 Mon") if dateStr == today { - label = "Today " + d.date.Format("Mon") + label = weekPrefix + "Today " + d.date.Format("Mon") } onCall := "—" if d.entry != nil { @@ -583,9 +590,9 @@ func fetchScheduleCmd(client *api.Client, from, to time.Time) tea.Cmd { } } -func assignScheduleCmd(client *api.Client, userID int64, date string, from, to time.Time) tea.Cmd { +func assignScheduleCmd(client *api.Client, userID int64, dates []string, from, to time.Time) tea.Cmd { return func() tea.Msg { - if _, err := client.AssignSchedule(userID, []string{date}); err != nil { + if _, err := client.AssignSchedule(userID, dates); err != nil { return scheduleActionErrMsg{err} } entries, err := client.GetSchedule(from.Format("2006-01-02"), to.Format("2006-01-02")) diff --git a/internal/tui/update.go b/internal/tui/update.go index 337105e..fa74047 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -355,6 +355,20 @@ func (m Model) handleDashboardKey(msg tea.KeyMsg) (Model, tea.Cmd) { if m.activeSection != sectionSchedule || !m.connected { return m, nil } + m.pickerAssignWeek = false + m.mode = modeScheduleUserPicker + if len(m.users) == 0 { + m.usersLoading = true + return m, fetchUsersCmd(m.client) + } + m.rebuildUserPickerTable() + return m, nil + + case "W": + if m.activeSection != sectionSchedule || !m.connected { + return m, nil + } + m.pickerAssignWeek = true m.mode = modeScheduleUserPicker if len(m.users) == 0 { m.usersLoading = true @@ -596,12 +610,25 @@ func (m Model) handleUserPickerKey(msg tea.KeyMsg) (Model, tea.Cmd) { m.mode = modeDashboard return m, nil } - date := m.scheduleDays[scheduleCursor].date.Format("2006-01-02") + d := m.scheduleDays[scheduleCursor].date + var dates []string + if m.pickerAssignWeek { + weekday := int(d.Weekday()) + if weekday == 0 { + weekday = 7 // ISO: Sunday = 7 + } + monday := d.AddDate(0, 0, -(weekday - 1)) + for i := 0; i < 7; i++ { + dates = append(dates, monday.AddDate(0, 0, i).Format("2006-01-02")) + } + } else { + dates = []string{d.Format("2006-01-02")} + } from := m.scheduleWindow to := m.scheduleWindow.AddDate(0, 0, 13) m.mode = modeDashboard m.scheduleLoading = true - return m, assignScheduleCmd(m.client, user.ID, date, from, to) + return m, assignScheduleCmd(m.client, user.ID, dates, from, to) } return m, nil diff --git a/internal/tui/view.go b/internal/tui/view.go index eb8f180..625cb01 100644 --- a/internal/tui/view.go +++ b/internal/tui/view.go @@ -128,7 +128,11 @@ func (m Model) renderFooter() string { return "\n" + styleFooter.Render(" esc·back") case modeScheduleUserPicker: - return "\n" + styleFooter.Render(" j/k·navigate enter·select esc·cancel") + mode := "day" + if m.pickerAssignWeek { + mode = "week" + } + return "\n" + styleFooter.Render(fmt.Sprintf(" j/k·navigate enter·assign %s esc·cancel", mode)) case modeUserCreate: if m.statusMsg != "" { @@ -160,7 +164,7 @@ func (m Model) renderFooter() string { default: if m.activeSection == sectionSchedule { - actions := styleFooter.Render(" +·assign d·del ←/→·shift week tab·section r·refresh q·quit") + actions := styleFooter.Render(" +·assign day W·assign week d·del ←/→·shift week tab·section r·refresh q·quit") if m.statusMsg != "" { return styleStatus.Render(" "+m.statusMsg) + "\n" + actions } @@ -235,19 +239,31 @@ func (m Model) renderUserPicker() string { return "\n" + styleMuted.Render(" Loading users…") } - selectedDate := "" + var scope string cursor := m.scheduleTable.Cursor() if cursor < len(m.scheduleDays) { - d := m.scheduleDays[cursor] - if d.date.Format("2006-01-02") == time.Now().UTC().Format("2006-01-02") { - selectedDate = "Today (" + d.date.Format("Mon") + ")" + d := m.scheduleDays[cursor].date + if m.pickerAssignWeek { + weekday := int(d.Weekday()) + if weekday == 0 { + weekday = 7 + } + monday := d.AddDate(0, 0, -(weekday - 1)) + sunday := monday.AddDate(0, 0, 6) + _, week := d.ISOWeek() + scope = fmt.Sprintf("week W%02d (%s–%s)", + week, monday.Format("Jan 02"), sunday.Format("Jan 02")) } else { - selectedDate = d.date.Format("Jan 02 (Mon)") + if d.Format("2006-01-02") == time.Now().UTC().Format("2006-01-02") { + scope = "Today (" + d.Format("Mon") + ")" + } else { + scope = d.Format("Jan 02 (Mon)") + } } } header := fmt.Sprintf("\n Assign on-call for %s — select a user:\n\n", - styleBold.Render(selectedDate)) + styleBold.Render(scope)) return header + m.userPickerTable.View() }