base feature
This commit is contained in:
45
handlers/grocerylist.go
Normal file
45
handlers/grocerylist.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"mealprep/database"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// GroceryListHandler handles the grocery list page
|
||||
func GroceryListHandler(w http.ResponseWriter, r *http.Request) {
|
||||
groceryItems, err := database.GetGroceryList()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl := `
|
||||
<div id="grocery-content">
|
||||
<h2>Grocery List</h2>
|
||||
<p class="info">This list is automatically generated from your week plan.</p>
|
||||
|
||||
{{if .Items}}
|
||||
<div class="grocery-list">
|
||||
{{range .Items}}
|
||||
<div class="grocery-item">
|
||||
<span class="grocery-name">{{.IngredientName}}</span>
|
||||
<span class="grocery-quantity">{{printf "%.2f" .TotalQuantity}} {{.Unit}}</span>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}
|
||||
<p class="empty-state">No items in your grocery list. Add meals to your week plan to generate a grocery list.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
`
|
||||
|
||||
data := struct {
|
||||
Items interface{}
|
||||
}{
|
||||
Items: groceryItems,
|
||||
}
|
||||
|
||||
t := template.Must(template.New("grocerylist").Parse(tmpl))
|
||||
t.Execute(w, data)
|
||||
}
|
||||
113
handlers/ingredients.go
Normal file
113
handlers/ingredients.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"mealprep/database"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IngredientsHandler handles the ingredients page
|
||||
func IngredientsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ingredients, err := database.GetAllIngredients()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl := `
|
||||
<div id="ingredients-content">
|
||||
<h2>Ingredients</h2>
|
||||
|
||||
<form hx-post="/ingredients" hx-target="#ingredients-list" hx-swap="beforeend" class="add-form">
|
||||
<input type="text" name="name" placeholder="Ingredient name" required />
|
||||
<input type="text" name="unit" placeholder="Unit (e.g., grams, cups)" required />
|
||||
<button type="submit">Add Ingredient</button>
|
||||
</form>
|
||||
|
||||
<div id="ingredients-list" class="items-list">
|
||||
{{range .}}
|
||||
<div class="item" id="ingredient-{{.ID}}">
|
||||
<span class="item-name">{{.Name}}</span>
|
||||
<span class="item-unit">({{.Unit}})</span>
|
||||
<button
|
||||
hx-delete="/ingredients/{{.ID}}"
|
||||
hx-target="#ingredient-{{.ID}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-confirm="Are you sure you want to delete this ingredient?"
|
||||
class="delete-btn">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
t := template.Must(template.New("ingredients").Parse(tmpl))
|
||||
t.Execute(w, ingredients)
|
||||
}
|
||||
|
||||
// AddIngredientHandler handles adding a new ingredient
|
||||
func AddIngredientHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(r.FormValue("name"))
|
||||
unit := strings.TrimSpace(r.FormValue("unit"))
|
||||
|
||||
if name == "" || unit == "" {
|
||||
http.Error(w, "Name and unit are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := database.AddIngredient(name, unit)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl := `
|
||||
<div class="item" id="ingredient-{{.ID}}">
|
||||
<span class="item-name">{{.Name}}</span>
|
||||
<span class="item-unit">({{.Unit}})</span>
|
||||
<button
|
||||
hx-delete="/ingredients/{{.ID}}"
|
||||
hx-target="#ingredient-{{.ID}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-confirm="Are you sure you want to delete this ingredient?"
|
||||
class="delete-btn">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
`
|
||||
|
||||
data := struct {
|
||||
ID int64
|
||||
Name string
|
||||
Unit string
|
||||
}{id, name, unit}
|
||||
|
||||
t := template.Must(template.New("ingredient").Parse(tmpl))
|
||||
t.Execute(w, data)
|
||||
}
|
||||
|
||||
// DeleteIngredientHandler handles deleting an ingredient
|
||||
func DeleteIngredientHandler(w http.ResponseWriter, r *http.Request) {
|
||||
idStr := strings.TrimPrefix(r.URL.Path, "/ingredients/")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DeleteIngredient(id); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
335
handlers/meals.go
Normal file
335
handlers/meals.go
Normal file
@@ -0,0 +1,335 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"mealprep/database"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MealsHandler handles the meals page
|
||||
func MealsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
meals, err := database.GetAllMeals()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl := `
|
||||
<div id="meals-content">
|
||||
<h2>Meals</h2>
|
||||
|
||||
<form hx-post="/meals" hx-target="#meals-list" hx-swap="beforeend" class="add-form">
|
||||
<input type="text" name="name" placeholder="Meal name" required />
|
||||
<input type="text" name="description" placeholder="Description" />
|
||||
<select name="meal_type" required>
|
||||
<option value="">Select type...</option>
|
||||
<option value="breakfast">Breakfast</option>
|
||||
<option value="lunch">Lunch</option>
|
||||
<option value="snack">Snack</option>
|
||||
</select>
|
||||
<button type="submit">Add Meal</button>
|
||||
</form>
|
||||
|
||||
<div id="meals-list" class="items-list">
|
||||
{{range .}}
|
||||
<div class="meal-item" id="meal-{{.ID}}">
|
||||
<div class="meal-header">
|
||||
<div>
|
||||
<span class="item-name">{{.Name}}</span>
|
||||
<span class="meal-type-tag tag-{{.MealType}}">{{.MealType}}</span>
|
||||
<span class="item-description">{{.Description}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
hx-get="/meals/{{.ID}}/ingredients"
|
||||
hx-target="#meal-{{.ID}}-ingredients"
|
||||
hx-swap="innerHTML"
|
||||
class="view-btn">
|
||||
View Ingredients
|
||||
</button>
|
||||
<button
|
||||
hx-delete="/meals/{{.ID}}"
|
||||
hx-target="#meal-{{.ID}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-confirm="Are you sure you want to delete this meal?"
|
||||
class="delete-btn">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="meal-{{.ID}}-ingredients" class="meal-ingredients"></div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
t := template.Must(template.New("meals").Parse(tmpl))
|
||||
t.Execute(w, meals)
|
||||
}
|
||||
|
||||
// AddMealHandler handles adding a new meal
|
||||
func AddMealHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(r.FormValue("name"))
|
||||
description := strings.TrimSpace(r.FormValue("description"))
|
||||
mealType := r.FormValue("meal_type")
|
||||
|
||||
if name == "" || mealType == "" {
|
||||
http.Error(w, "Name and meal type are required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate meal type
|
||||
if mealType != "breakfast" && mealType != "lunch" && mealType != "snack" {
|
||||
http.Error(w, "Invalid meal type", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := database.AddMeal(name, description, mealType)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl := `
|
||||
<div class="meal-item" id="meal-{{.ID}}">
|
||||
<div class="meal-header">
|
||||
<div>
|
||||
<span class="item-name">{{.Name}}</span>
|
||||
<span class="meal-type-tag tag-{{.MealType}}">{{.MealType}}</span>
|
||||
<span class="item-description">{{.Description}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
hx-get="/meals/{{.ID}}/ingredients"
|
||||
hx-target="#meal-{{.ID}}-ingredients"
|
||||
hx-swap="innerHTML"
|
||||
class="view-btn">
|
||||
View Ingredients
|
||||
</button>
|
||||
<button
|
||||
hx-delete="/meals/{{.ID}}"
|
||||
hx-target="#meal-{{.ID}}"
|
||||
hx-swap="outerHTML"
|
||||
hx-confirm="Are you sure you want to delete this meal?"
|
||||
class="delete-btn">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="meal-{{.ID}}-ingredients" class="meal-ingredients"></div>
|
||||
</div>
|
||||
`
|
||||
|
||||
data := struct {
|
||||
ID int64
|
||||
Name string
|
||||
Description string
|
||||
MealType string
|
||||
}{id, name, description, mealType}
|
||||
|
||||
t := template.Must(template.New("meal").Parse(tmpl))
|
||||
t.Execute(w, data)
|
||||
}
|
||||
|
||||
// DeleteMealHandler handles deleting a meal
|
||||
func DeleteMealHandler(w http.ResponseWriter, r *http.Request) {
|
||||
idStr := strings.TrimPrefix(r.URL.Path, "/meals/")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DeleteMeal(id); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// GetMealIngredientsHandler shows ingredients for a specific meal
|
||||
func GetMealIngredientsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
parts := strings.Split(r.URL.Path, "/")
|
||||
if len(parts) < 3 {
|
||||
http.Error(w, "Invalid URL", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
mealID, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid meal ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
mealIngredients, err := database.GetMealIngredients(mealID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
allIngredients, err := database.GetAllIngredients()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl := `
|
||||
<div class="ingredients-section">
|
||||
<h4>Ingredients:</h4>
|
||||
|
||||
<form hx-post="/meals/{{.MealID}}/ingredients"
|
||||
hx-target="#meal-{{.MealID}}-ingredients-list"
|
||||
hx-swap="beforeend"
|
||||
class="add-ingredient-form">
|
||||
<select name="ingredient_id" required>
|
||||
<option value="">Select ingredient</option>
|
||||
{{range .AllIngredients}}
|
||||
<option value="{{.ID}}">{{.Name}} ({{.Unit}})</option>
|
||||
{{end}}
|
||||
</select>
|
||||
<input type="number" name="quantity" placeholder="Quantity" step="0.01" min="0" required />
|
||||
<button type="submit">Add</button>
|
||||
</form>
|
||||
|
||||
<div id="meal-{{.MealID}}-ingredients-list" class="sub-items-list">
|
||||
{{range .MealIngredients}}
|
||||
<div class="sub-item" id="meal-{{.MealID}}-ingredient-{{.IngredientID}}">
|
||||
<span>{{.IngredientName}}: {{.Quantity}} {{.Unit}}</span>
|
||||
<button
|
||||
hx-delete="/meals/{{.MealID}}/ingredients/{{.IngredientID}}"
|
||||
hx-target="#meal-{{.MealID}}-ingredient-{{.IngredientID}}"
|
||||
hx-swap="outerHTML"
|
||||
class="delete-btn small">
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
data := struct {
|
||||
MealID int
|
||||
MealIngredients interface{}
|
||||
AllIngredients interface{}
|
||||
}{
|
||||
MealID: mealID,
|
||||
MealIngredients: mealIngredients,
|
||||
AllIngredients: allIngredients,
|
||||
}
|
||||
|
||||
t := template.Must(template.New("mealIngredients").Parse(tmpl))
|
||||
t.Execute(w, data)
|
||||
}
|
||||
|
||||
// AddMealIngredientHandler adds an ingredient to a meal
|
||||
func AddMealIngredientHandler(w http.ResponseWriter, r *http.Request) {
|
||||
parts := strings.Split(r.URL.Path, "/")
|
||||
if len(parts) < 3 {
|
||||
http.Error(w, "Invalid URL", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
mealID, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid meal ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ingredientID, err := strconv.Atoi(r.FormValue("ingredient_id"))
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid ingredient ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
quantity, err := strconv.ParseFloat(r.FormValue("quantity"), 64)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid quantity", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.AddMealIngredient(mealID, ingredientID, quantity); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the ingredient details to display
|
||||
ingredients, err := database.GetMealIngredients(mealID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Find the ingredient we just added
|
||||
var addedIngredient *interface{}
|
||||
for _, ing := range ingredients {
|
||||
if ing.IngredientID == ingredientID {
|
||||
var temp interface{} = ing
|
||||
addedIngredient = &temp
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if addedIngredient == nil {
|
||||
http.Error(w, "Could not find added ingredient", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
tmpl := `
|
||||
<div class="sub-item" id="meal-{{.MealID}}-ingredient-{{.IngredientID}}">
|
||||
<span>{{.IngredientName}}: {{.Quantity}} {{.Unit}}</span>
|
||||
<button
|
||||
hx-delete="/meals/{{.MealID}}/ingredients/{{.IngredientID}}"
|
||||
hx-target="#meal-{{.MealID}}-ingredient-{{.IngredientID}}"
|
||||
hx-swap="outerHTML"
|
||||
class="delete-btn small">
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
`
|
||||
|
||||
t := template.Must(template.New("mealIngredient").Parse(tmpl))
|
||||
t.Execute(w, *addedIngredient)
|
||||
}
|
||||
|
||||
// DeleteMealIngredientHandler removes an ingredient from a meal
|
||||
func DeleteMealIngredientHandler(w http.ResponseWriter, r *http.Request) {
|
||||
parts := strings.Split(r.URL.Path, "/")
|
||||
if len(parts) < 5 {
|
||||
http.Error(w, "Invalid URL", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
mealID, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid meal ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ingredientID, err := strconv.Atoi(parts[4])
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid ingredient ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := database.DeleteMealIngredient(mealID, ingredientID); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
321
handlers/weekplan.go
Normal file
321
handlers/weekplan.go
Normal file
@@ -0,0 +1,321 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user