245 lines
8.6 KiB
Go
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)
|
|
}
|