feat: week numbers in schedule and week-wide assignment
Schedule table now shows ISO week number (W21) on the first row of each week visible in the 14-day window (first row and every Monday); other rows get a 4-space indent. Date column widened from 16→18 to accommodate the prefix. New W key in Schedule section opens the user picker in week-assign mode, which assigns the selected user to all 7 days (Mon–Sun) of the ISO week containing the cursor. The + key retains single-day assignment. The user picker header and footer reflect which mode is active.
This commit is contained in:
+12
-5
@@ -147,6 +147,7 @@ type Model struct {
|
|||||||
users []api.User
|
users []api.User
|
||||||
usersLoading bool
|
usersLoading bool
|
||||||
userPickerTable table.Model
|
userPickerTable table.Model
|
||||||
|
pickerAssignWeek bool
|
||||||
|
|
||||||
// User management section
|
// User management section
|
||||||
userManageTable table.Model
|
userManageTable table.Model
|
||||||
@@ -328,7 +329,7 @@ func alertColumns(width int) []table.Column {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func scheduleColumns(width int) []table.Column {
|
func scheduleColumns(width int) []table.Column {
|
||||||
dateW := 16
|
dateW := 18
|
||||||
onCallW := width - dateW - 6
|
onCallW := width - dateW - 6
|
||||||
if onCallW < 15 {
|
if onCallW < 15 {
|
||||||
onCallW = 15
|
onCallW = 15
|
||||||
@@ -381,9 +382,15 @@ func scheduleRows(days []scheduleDay) []table.Row {
|
|||||||
rows := make([]table.Row, len(days))
|
rows := make([]table.Row, len(days))
|
||||||
for i, d := range days {
|
for i, d := range days {
|
||||||
dateStr := d.date.Format("2006-01-02")
|
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 {
|
if dateStr == today {
|
||||||
label = "Today " + d.date.Format("Mon")
|
label = weekPrefix + "Today " + d.date.Format("Mon")
|
||||||
}
|
}
|
||||||
onCall := "—"
|
onCall := "—"
|
||||||
if d.entry != nil {
|
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 {
|
return func() tea.Msg {
|
||||||
if _, err := client.AssignSchedule(userID, []string{date}); err != nil {
|
if _, err := client.AssignSchedule(userID, dates); err != nil {
|
||||||
return scheduleActionErrMsg{err}
|
return scheduleActionErrMsg{err}
|
||||||
}
|
}
|
||||||
entries, err := client.GetSchedule(from.Format("2006-01-02"), to.Format("2006-01-02"))
|
entries, err := client.GetSchedule(from.Format("2006-01-02"), to.Format("2006-01-02"))
|
||||||
|
|||||||
+29
-2
@@ -355,6 +355,20 @@ func (m Model) handleDashboardKey(msg tea.KeyMsg) (Model, tea.Cmd) {
|
|||||||
if m.activeSection != sectionSchedule || !m.connected {
|
if m.activeSection != sectionSchedule || !m.connected {
|
||||||
return m, nil
|
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
|
m.mode = modeScheduleUserPicker
|
||||||
if len(m.users) == 0 {
|
if len(m.users) == 0 {
|
||||||
m.usersLoading = true
|
m.usersLoading = true
|
||||||
@@ -596,12 +610,25 @@ func (m Model) handleUserPickerKey(msg tea.KeyMsg) (Model, tea.Cmd) {
|
|||||||
m.mode = modeDashboard
|
m.mode = modeDashboard
|
||||||
return m, nil
|
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
|
from := m.scheduleWindow
|
||||||
to := m.scheduleWindow.AddDate(0, 0, 13)
|
to := m.scheduleWindow.AddDate(0, 0, 13)
|
||||||
m.mode = modeDashboard
|
m.mode = modeDashboard
|
||||||
m.scheduleLoading = true
|
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
|
return m, nil
|
||||||
|
|||||||
+24
-8
@@ -128,7 +128,11 @@ func (m Model) renderFooter() string {
|
|||||||
return "\n" + styleFooter.Render(" esc·back")
|
return "\n" + styleFooter.Render(" esc·back")
|
||||||
|
|
||||||
case modeScheduleUserPicker:
|
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:
|
case modeUserCreate:
|
||||||
if m.statusMsg != "" {
|
if m.statusMsg != "" {
|
||||||
@@ -160,7 +164,7 @@ func (m Model) renderFooter() string {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
if m.activeSection == sectionSchedule {
|
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 != "" {
|
if m.statusMsg != "" {
|
||||||
return styleStatus.Render(" "+m.statusMsg) + "\n" + actions
|
return styleStatus.Render(" "+m.statusMsg) + "\n" + actions
|
||||||
}
|
}
|
||||||
@@ -235,19 +239,31 @@ func (m Model) renderUserPicker() string {
|
|||||||
return "\n" + styleMuted.Render(" Loading users…")
|
return "\n" + styleMuted.Render(" Loading users…")
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedDate := ""
|
var scope string
|
||||||
cursor := m.scheduleTable.Cursor()
|
cursor := m.scheduleTable.Cursor()
|
||||||
if cursor < len(m.scheduleDays) {
|
if cursor < len(m.scheduleDays) {
|
||||||
d := m.scheduleDays[cursor]
|
d := m.scheduleDays[cursor].date
|
||||||
if d.date.Format("2006-01-02") == time.Now().UTC().Format("2006-01-02") {
|
if m.pickerAssignWeek {
|
||||||
selectedDate = "Today (" + d.date.Format("Mon") + ")"
|
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 {
|
} 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",
|
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()
|
return header + m.userPickerTable.View()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user