322 lines
8.5 KiB
Go
322 lines
8.5 KiB
Go
package handlers
|
||
|
||
import (
|
||
"html/template"
|
||
"mealprep/database"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// WeekPlanHandler handles the week plan page
|
||
func WeekPlanHandler(w http.ResponseWriter, r *http.Request) {
|
||
weekPlan, err := database.GetWeekPlan()
|
||
if err != nil {
|
||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
meals, err := database.GetAllMeals()
|
||
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) {
|
||
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(date, mealID); err != nil {
|
||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
// Get the meal details
|
||
meal, err := database.GetMealByID(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()
|
||
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) {
|
||
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(id); err != nil {
|
||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
w.WriteHeader(http.StatusOK)
|
||
}
|