Added dynamic lists and fixed timezone issue
- **Today:** Display tasks of today and overdue - **Tomorrow:** Tasks scheduled for tomorrow - **Upcoming:** Tasks scheduled for next 7 days - Keep "Today" focused on starting - Order Tasks of dynamic list by Project - Parsing date input using local TZ - Fixes #7
This commit is contained in:
@@ -69,9 +69,9 @@ func (pane *ProjectPane) addNewProject() {
|
|||||||
|
|
||||||
func (pane *ProjectPane) addDynamicLists() {
|
func (pane *ProjectPane) addDynamicLists() {
|
||||||
pane.addSection("Dynamic Lists")
|
pane.addSection("Dynamic Lists")
|
||||||
pane.list.AddItem("- Today", "", 0, yetToImplement("Today's Tasks"))
|
pane.list.AddItem("- Today", "", 0, func() { taskPane.LoadDynamicList("today") })
|
||||||
pane.list.AddItem("- Upcoming", "", 0, yetToImplement("Upcoming Tasks"))
|
pane.list.AddItem("- Tomorrow", "", 0, func() { taskPane.LoadDynamicList("tomorrow") })
|
||||||
pane.list.AddItem("- No Due Date", "", 0, yetToImplement("Unscheduled Tasks"))
|
pane.list.AddItem("- Upcoming", "", 0, func() { taskPane.LoadDynamicList("upcoming") })
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pane *ProjectPane) addProjectList() {
|
func (pane *ProjectPane) addProjectList() {
|
||||||
@@ -89,7 +89,7 @@ func (pane *ProjectPane) addProjectList() {
|
|||||||
pane.addProjectToList(i, false)
|
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) {
|
func (pane *ProjectPane) addProjectToList(i int, selectItem bool) {
|
||||||
|
|||||||
@@ -87,7 +87,8 @@ func (td *TaskDetailPane) makeDateRow() *tview.Flex {
|
|||||||
SetDoneFunc(func(key tcell.Key) {
|
SetDoneFunc(func(key tcell.Key) {
|
||||||
switch key {
|
switch key {
|
||||||
case tcell.KeyEnter:
|
case tcell.KeyEnter:
|
||||||
td.setTaskDate(parseDateInputOrCurrent(td.taskDate.GetText()).Unix(), true)
|
date := parseDateInputOrCurrent(td.taskDate.GetText())
|
||||||
|
td.setTaskDate(date.Unix(), true)
|
||||||
case tcell.KeyEsc:
|
case tcell.KeyEsc:
|
||||||
td.setTaskDate(td.task.DueDate, false)
|
td.setTaskDate(td.task.DueDate, false)
|
||||||
}
|
}
|
||||||
@@ -95,7 +96,7 @@ func (td *TaskDetailPane) makeDateRow() *tview.Flex {
|
|||||||
})
|
})
|
||||||
|
|
||||||
todaySelector := func() {
|
todaySelector := func() {
|
||||||
td.setTaskDate(time.Now().Unix(), true)
|
td.setTaskDate(parseDateInputOrCurrent("").Unix(), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
nextDaySelector := func() {
|
nextDaySelector := func() {
|
||||||
@@ -260,7 +261,7 @@ func (td *TaskDetailPane) editInExternalEditor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @TODO: Mouse events not being captured after returning from Suspend - fix it
|
// @TODO: Mouse events not being captured after returning from Suspend - fix it
|
||||||
// app.EnableMouse(true).
|
app.EnableMouse(true)
|
||||||
|
|
||||||
_ = os.Remove(tmpFileName)
|
_ = os.Remove(tmpFileName)
|
||||||
|
|
||||||
@@ -303,6 +304,7 @@ func (td *TaskDetailPane) handleShortcuts(event *tcell.EventKey) *tcell.EventKey
|
|||||||
case ' ':
|
case ' ':
|
||||||
td.toggleTaskStatus()
|
td.toggleTaskStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return event
|
return event
|
||||||
|
|||||||
45
app/tasks.go
45
app/tasks.go
@@ -2,6 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/asdine/storm/v3"
|
"github.com/asdine/storm/v3"
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
@@ -128,6 +130,49 @@ func (pane *TaskPane) LoadProjectTasks(project model.Project) {
|
|||||||
pane.AddItem(pane.newTask, 1, 0, false)
|
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
|
// ActivateTask marks a task as currently active and loads in TaskDetailPane
|
||||||
func (pane *TaskPane) ActivateTask(idx int) {
|
func (pane *TaskPane) ActivateTask(idx int) {
|
||||||
removeThirdCol()
|
removeThirdCol()
|
||||||
|
|||||||
30
app/util.go
30
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
|
// If input text is a valid date, parse it. Or get current date
|
||||||
func parseDateInputOrCurrent(inputText string) time.Time {
|
func parseDateInputOrCurrent(inputText string) time.Time {
|
||||||
if date, err := time.Parse(dateLayoutISO, inputText); err == nil {
|
if dateTime, err := time.Parse(dateLayoutISO, inputText); err == nil {
|
||||||
return date
|
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 {
|
func makeButton(label string, handler func()) *tview.Button {
|
||||||
@@ -89,5 +93,23 @@ func makeTaskListingTitle(task model.Task) string {
|
|||||||
if task.Completed {
|
if task.Completed {
|
||||||
checkbox = "[x[]"
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,18 @@ func (t *taskRepository) GetAllByProject(project model.Project) ([]model.Task, e
|
|||||||
return tasks, err
|
return tasks, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *taskRepository) GetAllByDate(from, to time.Time) ([]model.Task, error) {
|
func (t *taskRepository) GetAllByDate(date time.Time) ([]model.Task, error) {
|
||||||
panic("implement me")
|
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) {
|
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 {
|
func (t *taskRepository) Delete(task *model.Task) error {
|
||||||
return t.DB.DeleteStruct(task)
|
return t.DB.DeleteStruct(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getRoundedDueDate(date time.Time) int64 {
|
||||||
|
if date.IsZero() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return date.Unix()
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import (
|
|||||||
type TaskRepository interface {
|
type TaskRepository interface {
|
||||||
GetAll() ([]model.Task, error)
|
GetAll() ([]model.Task, error)
|
||||||
GetAllByProject(project model.Project) ([]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)
|
GetByID(ID string) (model.Task, error)
|
||||||
GetByUUID(UUID string) (model.Task, error)
|
GetByUUID(UUID string) (model.Task, error)
|
||||||
Create(project model.Project, title, details, UUID string, dueDate int64) (model.Task, error)
|
Create(project model.Project, title, details, UUID string, dueDate int64) (model.Task, error)
|
||||||
|
|||||||
Reference in New Issue
Block a user