From 34790df4494e54e92ea1b47ff8456f1f10a1a300 Mon Sep 17 00:00:00 2001 From: Bartosz Nikitiuk Date: Wed, 19 Nov 2025 11:12:48 +0100 Subject: [PATCH] Poprawki vikunja: - Dodanie mozliwosci ustawienia projektu przy dodawaniu zadania - poprawka pobierania zadan --- internal/glance/glance.go | 44 ++++++++++++++++++-- internal/glance/static/js/vikunja.js | 27 ++++++++++++- internal/glance/templates/vikunja.html | 6 +++ internal/glance/widget-vikunja.go | 56 ++++++++++++++++++++++---- 4 files changed, 120 insertions(+), 13 deletions(-) diff --git a/internal/glance/glance.go b/internal/glance/glance.go index 879a60f..6ebca75 100644 --- a/internal/glance/glance.go +++ b/internal/glance/glance.go @@ -456,6 +456,7 @@ func (a *application) server() (func() error, func() error) { mux.HandleFunc("POST /api/vikunja/{widgetID}/add-label", a.handleVikunjaAddLabel) mux.HandleFunc("POST /api/vikunja/{widgetID}/remove-label", a.handleVikunjaRemoveLabel) mux.HandleFunc("GET /api/vikunja/{widgetID}/labels", a.handleVikunjaGetLabels) + mux.HandleFunc("GET /api/vikunja/{widgetID}/projects", a.handleVikunjaGetProjects) mux.HandleFunc("GET /api/vikunja/{widgetID}/refresh", a.handleVikunjaRefresh) mux.HandleFunc("POST /api/vikunja/{widgetID}/create-task", a.handleVikunjaCreateTask) @@ -734,6 +735,40 @@ func (a *application) handleVikunjaGetLabels(w http.ResponseWriter, r *http.Requ json.NewEncoder(w).Encode(labels) } +func (a *application) handleVikunjaGetProjects(w http.ResponseWriter, r *http.Request) { + widgetIDStr := r.PathValue("widgetID") + widgetID, err := strconv.ParseUint(widgetIDStr, 10, 64) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Invalid widget ID")) + return + } + + widget, exists := a.widgetByID[widgetID] + if !exists { + w.WriteHeader(http.StatusNotFound) + w.Write([]byte("Widget not found")) + return + } + + vikunjaWidget, ok := widget.(*vikunjaWidget) + if !ok { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Widget is not a Vikunja widget")) + return + } + + projects, err := vikunjaWidget.fetchProjects() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(projects) +} + func (a *application) handleVikunjaRefresh(w http.ResponseWriter, r *http.Request) { widgetIDStr := r.PathValue("widgetID") widgetID, err := strconv.ParseUint(widgetIDStr, 10, 64) @@ -792,9 +827,10 @@ func (a *application) handleVikunjaCreateTask(w http.ResponseWriter, r *http.Req } var request struct { - Title string `json:"title"` - DueDate string `json:"due_date"` - LabelIDs []int `json:"label_ids"` + Title string `json:"title"` + DueDate string `json:"due_date"` + LabelIDs []int `json:"label_ids"` + ProjectID int `json:"project_id"` } if err := json.NewDecoder(r.Body).Decode(&request); err != nil { @@ -803,7 +839,7 @@ func (a *application) handleVikunjaCreateTask(w http.ResponseWriter, r *http.Req return } - task, err := vikunjaWidget.createTask(request.Title, request.DueDate, request.LabelIDs) + task, err := vikunjaWidget.createTask(request.Title, request.DueDate, request.LabelIDs, request.ProjectID) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Failed to create task: %v", err))) diff --git a/internal/glance/static/js/vikunja.js b/internal/glance/static/js/vikunja.js index 1b820e4..a9e340f 100644 --- a/internal/glance/static/js/vikunja.js +++ b/internal/glance/static/js/vikunja.js @@ -303,11 +303,34 @@ function openCreateModal(widgetID) { const modal = document.getElementById('vikunja-create-modal'); const titleInput = document.getElementById('vikunja-create-title'); const dueDateInput = document.getElementById('vikunja-create-due-date'); + const projectSelect = document.getElementById('vikunja-create-project'); const labelsContainer = document.getElementById('vikunja-create-labels-container'); // Clear the form titleInput.value = ''; dueDateInput.value = ''; + + // Fetch and populate projects + projectSelect.innerHTML = ''; + + fetch(`${pageData.baseURL}/api/vikunja/${widgetID}/projects`) + .then(response => response.json()) + .then(projects => { + projectSelect.innerHTML = ''; + + if (projects && projects.length > 0) { + projects.forEach(project => { + const option = document.createElement('option'); + option.value = project.id; + option.textContent = project.title; + projectSelect.appendChild(option); + }); + } + }) + .catch(error => { + console.error('Error fetching projects:', error); + projectSelect.innerHTML = ''; + }); // Fetch and display labels labelsContainer.innerHTML = '

Ładowanie etykiet...

'; @@ -360,6 +383,7 @@ function openCreateModal(widgetID) { async function createTask() { const title = titleInput.value.trim(); const dueDate = dueDateInput.value; + const projectID = projectSelect.value ? parseInt(projectSelect.value) : 0; // Get selected label IDs const selectedLabels = Array.from(labelsContainer.querySelectorAll('input[type="checkbox"]:checked')) @@ -387,7 +411,8 @@ function openCreateModal(widgetID) { body: JSON.stringify({ title: title, due_date: formattedDueDate, - label_ids: selectedLabels + label_ids: selectedLabels, + project_id: projectID }) }); diff --git a/internal/glance/templates/vikunja.html b/internal/glance/templates/vikunja.html index 8ab6a95..ef757f3 100644 --- a/internal/glance/templates/vikunja.html +++ b/internal/glance/templates/vikunja.html @@ -79,6 +79,12 @@ +
+ + +
diff --git a/internal/glance/widget-vikunja.go b/internal/glance/widget-vikunja.go index 25e75c0..84cbd1c 100644 --- a/internal/glance/widget-vikunja.go +++ b/internal/glance/widget-vikunja.go @@ -8,6 +8,7 @@ import ( "html/template" "io" "net/http" + "net/url" "sort" "strings" "time" @@ -56,6 +57,11 @@ type vikunjaAPILabel struct { HexColor string `json:"hex_color"` } +type vikunjaProject struct { + ID int `json:"id"` + Title string `json:"title"` +} + func (widget *vikunjaWidget) initialize() error { widget.withTitle("Vikunja").withCacheDuration(5 * time.Minute) @@ -89,9 +95,20 @@ func (widget *vikunjaWidget) update(ctx context.Context) { } func (widget *vikunjaWidget) fetchTasks() ([]vikunjaTask, error) { - url := widget.URL + "/api/v1/tasks/all" + fullURL := widget.URL + "/api/v1/tasks/all" - request, err := http.NewRequest("GET", url, nil) + u, err := url.Parse(fullURL) + if err != nil { + return nil, err + } + + q := u.Query() + q.Set("sort_by", "due_date") + q.Set("order_by", "asc") + q.Set("limit", "250") + u.RawQuery = q.Encode() + + request, err := http.NewRequest("GET", u.String(), nil) if err != nil { return nil, err } @@ -331,14 +348,14 @@ func (widget *vikunjaWidget) addLabelToTask(taskID int, labelID int) error { if response.StatusCode < 200 || response.StatusCode >= 300 { body, _ := io.ReadAll(response.Body) bodyStr := string(body) - + // Vikunja returns error code 8001 when label already exists // This is not an error for us - we want the label on the task if response.StatusCode == 400 && (strings.Contains(bodyStr, "8001") || strings.Contains(bodyStr, "already exists")) { // Label already exists, which is fine - we wanted it there anyway return nil } - + return fmt.Errorf("unexpected status code %d: %s", response.StatusCode, bodyStr) } @@ -390,9 +407,32 @@ func (widget *vikunjaWidget) fetchAllLabels() ([]vikunjaAPILabel, error) { return labels, nil } -func (widget *vikunjaWidget) createTask(title string, dueDate string, labelIDs []int) (*vikunjaAPITask, error) { - // Use the configured project ID for creating tasks - url := fmt.Sprintf("%s/api/v1/projects/%d/tasks", widget.URL, widget.ProjectID) +func (widget *vikunjaWidget) fetchProjects() ([]vikunjaProject, error) { + url := widget.URL + "/api/v1/projects" + + request, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + + request.Header.Set("Authorization", "Bearer "+widget.Token) + + projects, err := decodeJsonFromRequest[[]vikunjaProject](defaultHTTPClient, request) + if err != nil { + return nil, err + } + + return projects, nil +} + +func (widget *vikunjaWidget) createTask(title string, dueDate string, labelIDs []int, projectID int) (*vikunjaAPITask, error) { + // Use the configured project ID for creating tasks unless a specific project ID is provided + targetProjectID := widget.ProjectID + if projectID > 0 { + targetProjectID = projectID + } + + url := fmt.Sprintf("%s/api/v1/projects/%d/tasks", widget.URL, targetProjectID) // Build payload matching Vikunja API structure // Based on Vikunja API documentation and user-provided payload structure @@ -403,7 +443,7 @@ func (widget *vikunjaWidget) createTask(title string, dueDate string, labelIDs [ "done": false, "priority": 0, "labels": []interface{}{}, // Empty - labels added separately - "project_id": widget.ProjectID, + "project_id": targetProjectID, } // Add due_date if provided