Files
Gardomatic/internal/web/plant_tasks.go
T
kleiax 904d14b64c
CI / test (push) Canceled after 0s
Initial commit
2026-09-12 22:22:17 +02:00

245 lines
8.6 KiB
Go

package web
import (
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"gardomatic.kleiax.de/lib/client"
)
func (app *application) savePlantTasks(r *http.Request, gardenID, plantID int, forms []plantTaskForm) error {
apiClient := client.FromContext(r.Context())
for _, form := range forms {
if form.ID > 0 {
existing, _, err := apiClient.Task(r.Context(), gardenID, form.ID)
if err != nil {
return err
}
if existing.PlantID == nil || *existing.PlantID != plantID || existing.TemplateID != nil {
return fmt.Errorf("task %d does not belong to plant %d or was generated from a template", form.ID, plantID)
}
if form.Delete {
if _, err = apiClient.DeleteTask(r.Context(), gardenID, form.ID); err != nil {
return err
}
continue
}
}
if form.Delete {
continue
}
start, _ := parseTaskTime(form.DueAtStart, "", map[string]string{})
end, _ := parseTaskTime(form.DueAtEnd, "", map[string]string{})
active := form.Active
input := client.TaskInput{PlantID: &plantID, Title: &form.Title, Description: &form.Description, DueAtStart: start, DueAtEnd: end, ClearDueAtStart: start == nil, ClearDueAtEnd: end == nil, Priority: &form.Priority, Active: &active, Recurrence: &form.Recurrence, RecurrenceInterval: &form.RecurrenceInterval, Tags: parseKeywords(form.Keywords), PlantStatusOnCompletion: &form.PlantStatusOnCompletion}
if form.LocationID > 0 {
input.LocationID = &form.LocationID
} else {
input.ClearLocationID = true
}
if form.ID > 0 {
if _, _, err := apiClient.UpdateTask(r.Context(), gardenID, form.ID, input); err != nil {
return err
}
} else if _, _, err := apiClient.CreateTask(r.Context(), gardenID, input); err != nil {
return err
}
}
return nil
}
func (app *application) savePlantTemplateStates(r *http.Request, gardenID, plantID, speciesID int) error {
templates, _, err := client.FromContext(r.Context()).SpeciesTaskTemplates(r.Context(), gardenID, speciesID)
if err != nil {
return err
}
for _, item := range templates {
values, present := r.PostForm["plant_template_active_"+strconv.Itoa(item.ID)]
if !present {
continue
}
enabled := len(values) > 0 && values[len(values)-1] == "true"
if _, err = client.FromContext(r.Context()).SetTaskTemplateOptOut(r.Context(), gardenID, plantID, item.ID, !enabled); err != nil {
return err
}
}
return nil
}
func (app *application) plantTaskRow(w http.ResponseWriter, r *http.Request) {
gardenID, err := app.readPathID(r, "gardenID")
if err != nil {
app.notFound(w)
return
}
form := plantTaskFromValues(r.URL.Query())
newTask := r.URL.Query().Get("new") == "true" || form.RowKey == ""
if form.RowKey == "" {
form.RowKey = "task-new-" + strconv.FormatInt(time.Now().UnixNano(), 36)
form.Active = true
}
form.Errors = map[string]string{}
data := app.newTemplateData(r)
if !app.loadPlantTaskDialogData(w, r, gardenID, data) {
return
}
data.PendingPlantName = form.PendingPlantName
data.Garden = &client.Garden{ID: gardenID}
data.Form, data.TaskDialogNew = form, newTask
app.renderTemplate(w, http.StatusOK, "plant_form.tmpl", "plant_task_dialog", data)
}
func (app *application) plantTaskRowPost(w http.ResponseWriter, r *http.Request) {
gardenID, err := app.readPathID(r, "gardenID")
if err != nil {
app.notFound(w)
return
}
if err = r.ParseForm(); err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
form := plantTaskFromValues(r.PostForm)
form.Title, form.Description = strings.TrimSpace(form.Title), strings.TrimSpace(form.Description)
form.Errors = map[string]string{}
if form.Title == "" {
form.Errors["title"] = "Ein Titel ist erforderlich."
}
start, _ := parseTaskTime(form.DueAtStart, "due_at_start", form.Errors)
end, _ := parseTaskTime(form.DueAtEnd, "due_at_end", form.Errors)
if start != nil && end != nil && start.After(*end) {
form.Errors["due_at_end"] = "Das Ende darf nicht vor dem Beginn liegen."
}
newTask := r.PostForm.Get("new") == "true"
data := app.newTemplateData(r)
data.Garden, data.Form, data.TaskDialogNew = &client.Garden{ID: gardenID}, form, newTask
data.PendingPlantName = form.PendingPlantName
if len(form.Errors) > 0 {
if !app.loadPlantTaskDialogData(w, r, gardenID, data) {
return
}
w.Header().Set("HX-Retarget", "#plant-task-dialog-host")
w.Header().Set("HX-Reswap", "innerHTML")
app.renderTemplate(w, http.StatusOK, "plant_form.tmpl", "plant_task_dialog", data)
return
}
w.Header().Set("HX-Trigger", "plantTaskSaved")
data.PlantTasks = []plantTaskForm{form}
app.renderTemplate(w, http.StatusOK, "plant_form.tmpl", "plant_task_row_fragment", data)
}
func (app *application) loadPlantTaskDialogData(w http.ResponseWriter, r *http.Request, gardenID int, data *templateData) bool {
apiClient := client.FromContext(r.Context())
var err error
data.TaskPriorities, _, err = apiClient.TaskPriorities(r.Context())
if err == nil {
data.Locations, _, err = apiClient.Locations(r.Context(), gardenID)
}
if err == nil {
data.Plants, _, err = apiClient.Plants(r.Context(), gardenID)
}
if err != nil {
app.handleAPIError(w, r, err)
return false
}
data.TagSuggestions = app.loadTagSuggestions(r, gardenID)
return true
}
func plantTaskFromValues(values url.Values) plantTaskForm {
id, _ := strconv.Atoi(values.Get("plant_task_id"))
priority, _ := strconv.Atoi(values.Get("plant_task_priority"))
plantID, _ := strconv.Atoi(values.Get("plant_task_plant_id"))
if plantID == 0 {
plantID, _ = strconv.Atoi(values.Get("plant_id"))
}
locationID, _ := strconv.Atoi(values.Get("plant_task_location_id"))
if locationID == 0 {
locationID, _ = strconv.Atoi(values.Get("location_id"))
}
recurrenceInterval, _ := strconv.Atoi(values.Get("plant_task_recurrence_interval"))
if recurrenceInterval < 1 {
recurrenceInterval = 1
}
pendingPlantName := values.Get("pending_plant_name")
if pendingPlantName == "" {
pendingPlantName = values.Get("name")
}
return plantTaskForm{
ID: id, RowKey: values.Get("plant_task_row_key"), Title: values.Get("plant_task_title"),
Description: values.Get("plant_task_description"), DueAtStart: values.Get("plant_task_due_at_start"), PlantID: plantID,
DueAtEnd: values.Get("plant_task_due_at_end"), Priority: priority, LocationID: locationID, Recurrence: values.Get("plant_task_recurrence"), RecurrenceInterval: recurrenceInterval, Keywords: values.Get("plant_task_keywords"), PlantStatusOnCompletion: values.Get("plant_task_status_on_completion"),
PendingPlantName: strings.TrimSpace(pendingPlantName),
Active: values.Get("plant_task_active") != "false", Delete: values.Get("plant_task_delete") == "true",
}
}
func (app *application) plantTemplateTasks(w http.ResponseWriter, r *http.Request) {
gardenID, err := app.readPathID(r, "gardenID")
if err != nil {
app.notFound(w)
return
}
speciesID, _ := strconv.Atoi(r.URL.Query().Get("species_id"))
plantID, _ := strconv.Atoi(r.URL.Query().Get("plant_id"))
data := app.newTemplateData(r)
data.Garden, data.SpeciesID, data.PlantID = &client.Garden{ID: gardenID}, speciesID, plantID
data.TaskTemplates, data.TemplateOptOuts = map[int][]client.SpeciesTaskTemplate{}, map[int]bool{}
if speciesID > 0 {
templates, _, loadErr := client.FromContext(r.Context()).SpeciesTaskTemplates(r.Context(), gardenID, speciesID)
if loadErr != nil {
app.handleAPIError(w, r, loadErr)
return
}
data.TaskTemplates[speciesID] = templates
}
if plantID > 0 {
ids, _, loadErr := client.FromContext(r.Context()).TaskTemplateOptOuts(r.Context(), gardenID, plantID)
if loadErr != nil {
app.handleAPIError(w, r, loadErr)
return
}
for _, id := range ids {
data.TemplateOptOuts[id] = true
}
}
app.renderTemplate(w, http.StatusOK, "plant_form.tmpl", "plant_template_tasks", data)
}
func (app *application) plantTemplateTaskView(w http.ResponseWriter, r *http.Request) {
gardenID, err := app.readPathID(r, "gardenID")
if err != nil {
app.notFound(w)
return
}
templateID, err := app.readPathID(r, "templateID")
if err != nil {
app.notFound(w)
return
}
speciesID, _ := strconv.Atoi(r.URL.Query().Get("species_id"))
if speciesID < 1 {
app.notFound(w)
return
}
item, _, err := client.FromContext(r.Context()).SpeciesTaskTemplate(r.Context(), gardenID, speciesID, templateID)
if err != nil {
app.handleAPIError(w, r, err)
return
}
data := app.newTemplateData(r)
data.Garden, data.SpeciesID = &client.Garden{ID: gardenID}, speciesID
priorities, _, err := client.FromContext(r.Context()).TaskPriorities(r.Context())
if err != nil {
app.handleAPIError(w, r, err)
return
}
data.TaskPriorities = priorities
data.TaskTemplates = map[int][]client.SpeciesTaskTemplate{speciesID: {item}}
app.renderTemplate(w, http.StatusOK, "plant_form.tmpl", "plant_template_task_dialog", data)
}