diff --git a/app/projects.go b/app/projects.go index 778fe6f..4b085c0 100644 --- a/app/projects.go +++ b/app/projects.go @@ -69,9 +69,9 @@ func (pane *ProjectPane) addNewProject() { func (pane *ProjectPane) addDynamicLists() { pane.addSection("Dynamic Lists") - pane.list.AddItem("- Today", "", 0, yetToImplement("Today's Tasks")) - pane.list.AddItem("- Upcoming", "", 0, yetToImplement("Upcoming Tasks")) - pane.list.AddItem("- No Due Date", "", 0, yetToImplement("Unscheduled Tasks")) + pane.list.AddItem("- Today", "", 0, func() { taskPane.LoadDynamicList("today") }) + pane.list.AddItem("- Tomorrow", "", 0, func() { taskPane.LoadDynamicList("tomorrow") }) + pane.list.AddItem("- Upcoming", "", 0, func() { taskPane.LoadDynamicList("upcoming") }) } func (pane *ProjectPane) addProjectList() { @@ -89,7 +89,7 @@ func (pane *ProjectPane) addProjectList() { pane.addProjectToList(i, false) } - pane.list.SetCurrentItem(pane.projectListStarting) + pane.list.SetCurrentItem(2) // Keep "Today" selected on start } func (pane *ProjectPane) addProjectToList(i int, selectItem bool) { diff --git a/app/task_detail.go b/app/task_detail.go index fd172ec..d8c1480 100644 --- a/app/task_detail.go +++ b/app/task_detail.go @@ -87,7 +87,8 @@ func (td *TaskDetailPane) makeDateRow() *tview.Flex { SetDoneFunc(func(key tcell.Key) { switch key { case tcell.KeyEnter: - td.setTaskDate(parseDateInputOrCurrent(td.taskDate.GetText()).Unix(), true) + date := parseDateInputOrCurrent(td.taskDate.GetText()) + td.setTaskDate(date.Unix(), true) case tcell.KeyEsc: td.setTaskDate(td.task.DueDate, false) } @@ -95,7 +96,7 @@ func (td *TaskDetailPane) makeDateRow() *tview.Flex { }) todaySelector := func() { - td.setTaskDate(time.Now().Unix(), true) + td.setTaskDate(parseDateInputOrCurrent("").Unix(), true) } nextDaySelector := func() { @@ -260,7 +261,7 @@ func (td *TaskDetailPane) editInExternalEditor() { } // @TODO: Mouse events not being captured after returning from Suspend - fix it - // app.EnableMouse(true). + app.EnableMouse(true) _ = os.Remove(tmpFileName) @@ -303,6 +304,7 @@ func (td *TaskDetailPane) handleShortcuts(event *tcell.EventKey) *tcell.EventKey case ' ': td.toggleTaskStatus() } + } return event diff --git a/app/tasks.go b/app/tasks.go index 2393b47..58e9b41 100644 --- a/app/tasks.go +++ b/app/tasks.go @@ -2,6 +2,8 @@ package main import ( "fmt" + "sort" + "time" "github.com/asdine/storm/v3" "github.com/gdamore/tcell" @@ -128,6 +130,49 @@ func (pane *TaskPane) LoadProjectTasks(project model.Project) { pane.AddItem(pane.newTask, 1, 0, false) } +// LoadDynamicList loads tasks based on logic key +func (pane *TaskPane) LoadDynamicList(logic string) { + var tasks []model.Task + var err error + + today := toDate(time.Now()) + zeroTime := time.Time{} + rangeDesc := "" + + switch logic { + case "today": + tasks, err = pane.taskRepo.GetAllByDateRange(zeroTime, today) + rangeDesc = fmt.Sprintf("today and overdue") + + case "tomorrow": + tomorrow := today.AddDate(0, 0, 1) + tasks, err = pane.taskRepo.GetAllByDate(tomorrow) + rangeDesc = fmt.Sprintf("tomorrow") + + case "upcoming": + week := today.Add(7 * 24 * time.Hour) + tasks, err = pane.taskRepo.GetAllByDateRange(today, week) + rangeDesc = fmt.Sprintf("next 7 days") + } + + projectPane.activeProject = nil + if err == storm.ErrNotFound { + statusBar.showForSeconds("[yellow]No Task was scheduled for "+rangeDesc, 5) + pane.SetList(tasks) + } else if err != nil { + statusBar.showForSeconds("[red]Error: "+err.Error(), 5) + } else { + sort.Slice(tasks, func(i, j int) bool { return tasks[i].ProjectID < tasks[j].ProjectID }) + pane.SetList(tasks) + app.SetFocus(taskPane) + + statusBar.showForSeconds("[yellow] Displaying tasks of "+rangeDesc, 5) + } + + pane.RemoveItem(pane.hint) + removeThirdCol() +} + // ActivateTask marks a task as currently active and loads in TaskDetailPane func (pane *TaskPane) ActivateTask(idx int) { removeThirdCol() diff --git a/app/util.go b/app/util.go index 1c3f900..b44ea60 100644 --- a/app/util.go +++ b/app/util.go @@ -41,11 +41,15 @@ func makeLightTextInput(placeholder string) *tview.InputField { // If input text is a valid date, parse it. Or get current date func parseDateInputOrCurrent(inputText string) time.Time { - if date, err := time.Parse(dateLayoutISO, inputText); err == nil { - return date + if dateTime, err := time.Parse(dateLayoutISO, inputText); err == nil { + return toDate(dateTime) } - return time.Now() + return toDate(time.Now()) +} + +func toDate(dateTime time.Time) time.Time { + return time.Date(dateTime.Year(), dateTime.Month(), dateTime.Day(), 0, 0, 0, 0, time.Local) } func makeButton(label string, handler func()) *tview.Button { @@ -89,5 +93,23 @@ func makeTaskListingTitle(task model.Task) string { if task.Completed { checkbox = "[x[]" } - return fmt.Sprintf("[%s]%s %s", getTaskTitleColor(task), checkbox, task.Title) + + prefix := "" + if projectPane.GetActiveProject() == nil { + if project, err := projectRepo.GetByID(task.ProjectID); err == nil { + prefix = project.Title + ":" + } + } + + return fmt.Sprintf("[%s] %s %s %s", getTaskTitleColor(task), prefix, checkbox, task.Title) +} + +func findProjectByID(id int64) *model.Project { + for i := range projectPane.projects { + if projectPane.projects[i].ID == id { + return &projectPane.projects[i] + } + } + + return nil } diff --git a/repository/storm/task.go b/repository/storm/task.go index 7031959..855a796 100644 --- a/repository/storm/task.go +++ b/repository/storm/task.go @@ -30,8 +30,18 @@ func (t *taskRepository) GetAllByProject(project model.Project) ([]model.Task, e return tasks, err } -func (t *taskRepository) GetAllByDate(from, to time.Time) ([]model.Task, error) { - panic("implement me") +func (t *taskRepository) GetAllByDate(date time.Time) ([]model.Task, error) { + var tasks []model.Task + + err := t.DB.Find("DueDate", getRoundedDueDate(date), &tasks) + return tasks, err +} + +func (t *taskRepository) GetAllByDateRange(from, to time.Time) ([]model.Task, error) { + var tasks []model.Task + + err := t.DB.Range("DueDate", getRoundedDueDate(from), getRoundedDueDate(to), &tasks) + return tasks, err } func (t *taskRepository) GetByID(ID string) (model.Task, error) { @@ -66,3 +76,11 @@ func (t *taskRepository) UpdateField(task *model.Task, field string, value inter func (t *taskRepository) Delete(task *model.Task) error { return t.DB.DeleteStruct(task) } + +func getRoundedDueDate(date time.Time) int64 { + if date.IsZero() { + return 0 + } + + return date.Unix() +} diff --git a/repository/task.go b/repository/task.go index fd7f43e..b88516c 100644 --- a/repository/task.go +++ b/repository/task.go @@ -10,7 +10,8 @@ import ( type TaskRepository interface { GetAll() ([]model.Task, error) GetAllByProject(project model.Project) ([]model.Task, error) - GetAllByDate(from, to time.Time) ([]model.Task, error) + GetAllByDate(date time.Time) ([]model.Task, error) + GetAllByDateRange(from, to time.Time) ([]model.Task, error) GetByID(ID string) (model.Task, error) GetByUUID(UUID string) (model.Task, error) Create(project model.Project, title, details, UUID string, dueDate int64) (model.Task, error)