summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/handlers/widget.go37
-rw-r--r--internal/handlers/widget_test.go59
2 files changed, 87 insertions, 9 deletions
diff --git a/internal/handlers/widget.go b/internal/handlers/widget.go
index 5e515c3..5880314 100644
--- a/internal/handlers/widget.go
+++ b/internal/handlers/widget.go
@@ -612,15 +612,17 @@ func (h *Handler) HandleWidgetLabelsColorSet(w http.ResponseWriter, r *http.Requ
}
type taskDetailResponse struct {
- ID string `json:"id"`
- Title string `json:"title"`
- Description string `json:"description"`
- DueDate *time.Time `json:"due_date,omitempty"`
- Completed bool `json:"completed"`
- Recurrence *recurrenceResponse `json:"recurrence,omitempty"`
- NextDate *time.Time `json:"next_date,omitempty"`
- Project *projectResponse `json:"project,omitempty"`
- Labels []string `json:"labels,omitempty"`
+ ID string `json:"id"`
+ Title string `json:"title"`
+ Description string `json:"description"`
+ DueDate *time.Time `json:"due_date,omitempty"`
+ Completed bool `json:"completed"`
+ Recurrence *recurrenceResponse `json:"recurrence,omitempty"`
+ NextDate *time.Time `json:"next_date,omitempty"`
+ Project *projectResponse `json:"project,omitempty"`
+ Labels []string `json:"labels,omitempty"`
+ EstimatedMinutes int `json:"estimated_minutes,omitempty"`
+ SuggestedEstimateMinutes *int `json:"suggested_estimate_minutes,omitempty"`
}
// HandleWidgetTaskDetail returns full detail for a single doot-native task,
@@ -670,6 +672,23 @@ func (h *Handler) HandleWidgetTaskDetail(w http.ResponseWriter, r *http.Request)
}
}
+ resp.EstimatedMinutes = task.EstimatedMinutes
+ if task.EstimatedMinutes == 0 {
+ if task.ProjectID != "" {
+ if avg, ok, err := h.store.AverageEstimateForProject(task.ProjectID); err == nil && ok {
+ resp.SuggestedEstimateMinutes = &avg
+ }
+ }
+ if resp.SuggestedEstimateMinutes == nil {
+ for _, label := range task.Labels {
+ if avg, ok, err := h.store.AverageEstimateForLabel(label); err == nil && ok {
+ resp.SuggestedEstimateMinutes = &avg
+ break
+ }
+ }
+ }
+ }
+
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(resp)
}
diff --git a/internal/handlers/widget_test.go b/internal/handlers/widget_test.go
index 10a5c02..02df111 100644
--- a/internal/handlers/widget_test.go
+++ b/internal/handlers/widget_test.go
@@ -1427,3 +1427,62 @@ func TestHandleWidgetLabelsBudgetTracked_SetsFlag(t *testing.T) {
t.Error("expected 'errands' tracked")
}
}
+
+func TestHandleWidgetTaskDetail_SuggestsEstimateFromProjectAverage(t *testing.T) {
+ db, cleanup := setupTestDB(t)
+ defer cleanup()
+ h := &Handler{store: db}
+ project, err := h.store.CreateProject("Sailing prep", "#3B82F6")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := h.store.CreateNativeTask(models.Task{ID: "t-past", Content: "past", ProjectID: project.ID, EstimatedMinutes: 40}); err != nil {
+ t.Fatal(err)
+ }
+ if err := h.store.CreateNativeTask(models.Task{ID: "t-new", Content: "new", ProjectID: project.ID}); err != nil {
+ t.Fatal(err)
+ }
+
+ req := httptest.NewRequest("GET", "/api/widget/task?id=t-new&source=doot", nil)
+ w := httptest.NewRecorder()
+ h.HandleWidgetTaskDetail(w, req)
+
+ var resp taskDetailResponse
+ if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
+ t.Fatal(err)
+ }
+ if resp.SuggestedEstimateMinutes == nil || *resp.SuggestedEstimateMinutes != 40 {
+ t.Errorf("SuggestedEstimateMinutes = %v, want pointer to 40", resp.SuggestedEstimateMinutes)
+ }
+}
+
+func TestHandleWidgetTaskDetail_AlreadyEstimated_NoSuggestion(t *testing.T) {
+ db, cleanup := setupTestDB(t)
+ defer cleanup()
+ h := &Handler{store: db}
+ project, err := h.store.CreateProject("P", "#111111")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := h.store.CreateNativeTask(models.Task{ID: "t-past", Content: "past", ProjectID: project.ID, EstimatedMinutes: 40}); err != nil {
+ t.Fatal(err)
+ }
+ if err := h.store.CreateNativeTask(models.Task{ID: "t-estimated", Content: "already estimated", ProjectID: project.ID, EstimatedMinutes: 15}); err != nil {
+ t.Fatal(err)
+ }
+
+ req := httptest.NewRequest("GET", "/api/widget/task?id=t-estimated&source=doot", nil)
+ w := httptest.NewRecorder()
+ h.HandleWidgetTaskDetail(w, req)
+
+ var resp taskDetailResponse
+ if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
+ t.Fatal(err)
+ }
+ if resp.SuggestedEstimateMinutes != nil {
+ t.Errorf("expected no suggestion for an already-estimated task, got %v", *resp.SuggestedEstimateMinutes)
+ }
+ if resp.EstimatedMinutes != 15 {
+ t.Errorf("EstimatedMinutes = %d, want 15", resp.EstimatedMinutes)
+ }
+}