Files
meal-prep-vibecoded/handlers/weekplan.go
2025-10-25 15:55:25 +02:00

329 lines
8.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handlers
import (
"html/template"
"mealprep/auth"
"mealprep/database"
"net/http"
"strconv"
"strings"
"time"
)
// WeekPlanHandler handles the week plan page
func WeekPlanHandler(w http.ResponseWriter, r *http.Request) {
userID := auth.GetUserID(r)
weekPlan, err := database.GetWeekPlan(userID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
meals, err := database.GetAllMeals(userID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Group by date
planByDate := make(map[string][]interface{})
for _, entry := range weekPlan {
dateStr := entry.Date.Format("2006-01-02")
planByDate[dateStr] = append(planByDate[dateStr], entry)
}
// Get next 7 days starting from upcoming Monday (or current Monday if today is Monday)
today := time.Now()
// Calculate the upcoming Monday
// If today is Monday, use today. Otherwise, find next Monday.
weekday := int(today.Weekday())
daysUntilMonday := (8 - weekday) % 7
if weekday == 1 { // Today is Monday
daysUntilMonday = 0
}
startDate := today.AddDate(0, 0, daysUntilMonday)
var dates []string
for i := 0; i < 7; i++ {
date := startDate.AddDate(0, 0, i)
dates = append(dates, date.Format("2006-01-02"))
}
tmpl := `
<div id="weekplan-content">
<h2>Week Plan</h2>
<p class="info">Planning week: {{.WeekStart}} - {{.WeekEnd}}</p>
<div class="week-container">
{{range $index, $date := .Dates}}
<div class="day-card">
<h3>{{formatDate $date}}</h3>
<!-- Breakfast Section -->
<div class="meal-type-section">
<h4 class="meal-type-header">🌅 Breakfast</h4>
<div class="day-meals" id="day-{{$date}}-breakfast">
{{$entries := index $.PlanByDate $date}}
{{range $entries}}
{{if eq .MealType "breakfast"}}
<div class="planned-meal" id="plan-{{.ID}}">
<span>{{.MealName}}</span>
<button
hx-delete="/week-plan/{{.ID}}"
hx-target="#plan-{{.ID}}"
hx-swap="outerHTML"
class="delete-btn small">
×
</button>
</div>
{{end}}
{{end}}
</div>
<form hx-post="/week-plan"
hx-target="#day-{{$date}}-breakfast"
hx-swap="beforeend"
class="add-meal-to-day">
<input type="hidden" name="date" value="{{$date}}" />
<input type="hidden" name="meal_type" value="breakfast" />
<select name="meal_id" required>
<option value="">Add breakfast...</option>
{{range $.BreakfastMeals}}
<option value="{{.ID}}">{{.Name}}</option>
{{end}}
</select>
<button type="submit">+</button>
</form>
</div>
<!-- Lunch Section -->
<div class="meal-type-section">
<h4 class="meal-type-header">🍽️ Lunch</h4>
<div class="day-meals" id="day-{{$date}}-lunch">
{{$entries := index $.PlanByDate $date}}
{{range $entries}}
{{if eq .MealType "lunch"}}
<div class="planned-meal" id="plan-{{.ID}}">
<span>{{.MealName}}</span>
<button
hx-delete="/week-plan/{{.ID}}"
hx-target="#plan-{{.ID}}"
hx-swap="outerHTML"
class="delete-btn small">
×
</button>
</div>
{{end}}
{{end}}
</div>
<form hx-post="/week-plan"
hx-target="#day-{{$date}}-lunch"
hx-swap="beforeend"
class="add-meal-to-day">
<input type="hidden" name="date" value="{{$date}}" />
<input type="hidden" name="meal_type" value="lunch" />
<select name="meal_id" required>
<option value="">Add lunch...</option>
{{range $.LunchMeals}}
<option value="{{.ID}}">{{.Name}}</option>
{{end}}
</select>
<button type="submit">+</button>
</form>
</div>
<!-- Snack Section -->
<div class="meal-type-section">
<h4 class="meal-type-header">🍪 Snack</h4>
<div class="day-meals" id="day-{{$date}}-snack">
{{$entries := index $.PlanByDate $date}}
{{range $entries}}
{{if eq .MealType "snack"}}
<div class="planned-meal" id="plan-{{.ID}}">
<span>{{.MealName}}</span>
<button
hx-delete="/week-plan/{{.ID}}"
hx-target="#plan-{{.ID}}"
hx-swap="outerHTML"
class="delete-btn small">
×
</button>
</div>
{{end}}
{{end}}
</div>
<form hx-post="/week-plan"
hx-target="#day-{{$date}}-snack"
hx-swap="beforeend"
class="add-meal-to-day">
<input type="hidden" name="date" value="{{$date}}" />
<input type="hidden" name="meal_type" value="snack" />
<select name="meal_id" required>
<option value="">Add snack...</option>
{{range $.SnackMeals}}
<option value="{{.ID}}">{{.Name}}</option>
{{end}}
</select>
<button type="submit">+</button>
</form>
</div>
</div>
{{end}}
</div>
</div>
`
funcMap := template.FuncMap{
"formatDate": func(dateStr string) string {
date, _ := time.Parse("2006-01-02", dateStr)
// Show full date with day name
return date.Format("Monday, Jan 2")
},
"index": func(m map[string][]interface{}, key string) []interface{} {
return m[key]
},
}
// Format week range for display
weekStart := startDate.Format("Mon, Jan 2")
weekEnd := startDate.AddDate(0, 0, 6).Format("Mon, Jan 2")
// Separate meals by type
var breakfastMeals, lunchMeals, snackMeals []interface{}
for _, meal := range meals {
switch meal.MealType {
case "breakfast":
breakfastMeals = append(breakfastMeals, meal)
case "lunch":
lunchMeals = append(lunchMeals, meal)
case "snack":
snackMeals = append(snackMeals, meal)
}
}
data := struct {
Dates []string
PlanByDate map[string][]interface{}
BreakfastMeals interface{}
LunchMeals interface{}
SnackMeals interface{}
WeekStart string
WeekEnd string
}{
Dates: dates,
PlanByDate: planByDate,
BreakfastMeals: breakfastMeals,
LunchMeals: lunchMeals,
SnackMeals: snackMeals,
WeekStart: weekStart,
WeekEnd: weekEnd,
}
t := template.Must(template.New("weekplan").Funcs(funcMap).Parse(tmpl))
t.Execute(w, data)
}
// AddWeekPlanEntryHandler adds a meal to a specific day
func AddWeekPlanEntryHandler(w http.ResponseWriter, r *http.Request) {
userID := auth.GetUserID(r)
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
dateStr := r.FormValue("date")
mealType := r.FormValue("meal_type")
mealID, err := strconv.Atoi(r.FormValue("meal_id"))
if err != nil {
http.Error(w, "Invalid meal ID", http.StatusBadRequest)
return
}
// Validate meal type
if mealType != "breakfast" && mealType != "lunch" && mealType != "snack" {
http.Error(w, "Invalid meal type", http.StatusBadRequest)
return
}
date, err := time.Parse("2006-01-02", dateStr)
if err != nil {
http.Error(w, "Invalid date", http.StatusBadRequest)
return
}
if err := database.AddWeekPlanEntry(userID, date, mealID); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Get the meal details
meal, err := database.GetMealByID(userID, mealID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Verify meal type matches
if meal.MealType != mealType {
http.Error(w, "Meal type does not match", http.StatusBadRequest)
return
}
// Get the ID of the entry we just added
weekPlan, err := database.GetWeekPlan(userID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var entryID int
for _, entry := range weekPlan {
if entry.Date.Format("2006-01-02") == dateStr && entry.MealID == mealID {
entryID = entry.ID
}
}
tmpl := `
<div class="planned-meal" id="plan-{{.ID}}">
<span>{{.MealName}}</span>
<button
hx-delete="/week-plan/{{.ID}}"
hx-target="#plan-{{.ID}}"
hx-swap="outerHTML"
class="delete-btn small">
×
</button>
</div>
`
data := struct {
ID int
MealName string
}{entryID, meal.Name}
t := template.Must(template.New("planEntry").Parse(tmpl))
t.Execute(w, data)
}
// DeleteWeekPlanEntryHandler removes a meal from the week plan
func DeleteWeekPlanEntryHandler(w http.ResponseWriter, r *http.Request) {
userID := auth.GetUserID(r)
idStr := strings.TrimPrefix(r.URL.Path, "/week-plan/")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
if err := database.DeleteWeekPlanEntry(userID, id); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}