From 5f156ab6a04f84523390cc081de27a1e4ae69f35 Mon Sep 17 00:00:00 2001 From: bronkuu Date: Mon, 15 Jun 2026 18:54:48 +0200 Subject: [PATCH] blocked dates page and warning for special-cake orders Add blocked_date table (migration 5), model, and store CRUD methods. Add management page at /blocked with add/delete functionality. Add nav link 'Terminy zablokowane' with event_busy icon. Pass blocked dates to order form as JSON data attribute. On form submit, if the order has special cakes and the date is blocked, show a confirm() dialog warning the user. --- models/data.go | 5 ++++ server/get.go | 30 +++++++++++++++++++---- server/post.go | 23 ++++++++++++++++++ server/server.go | 3 +++ server/static/card.css | 9 +++---- server/static/component.css | 2 +- server/static/order.js | 10 ++++++++ server/templates/blocked.templ | 44 ++++++++++++++++++++++++++++++++++ server/templates/helpers.go | 10 ++++++++ server/templates/nav.templ | 10 +++++--- server/templates/order.templ | 8 +++---- store/blocked_date.go | 33 +++++++++++++++++++++++++ store/migrations/5.sql | 4 ++++ 13 files changed, 175 insertions(+), 16 deletions(-) create mode 100644 server/templates/blocked.templ create mode 100644 store/blocked_date.go create mode 100644 store/migrations/5.sql diff --git a/models/data.go b/models/data.go index a767190..b9b4e12 100644 --- a/models/data.go +++ b/models/data.go @@ -103,3 +103,8 @@ type CakeCount struct { Name string Amount int } + +type BlockedDate struct { + ID int + Date string +} diff --git a/server/get.go b/server/get.go index e930e6d..f5de720 100644 --- a/server/get.go +++ b/server/get.go @@ -141,6 +141,27 @@ func (h *Server) cake(r *http.Request) (templ.Component, int, error) { return templates.CakePage(data, tags), http.StatusOK, nil } +func (h *Server) blocked(r *http.Request) (templ.Component, int, error) { + dates, err := h.s.GetBlockedDates() + if err != nil { + return nil, http.StatusInternalServerError, err + } + + if r.Header.Get("HX-Request") == "true" { + return templates.MainContent("Terminy zablokowane", templ.NopComponent, templates.BlockedMain(dates)), http.StatusOK, nil + } + + return templates.BlockedPage(dates), http.StatusOK, nil +} + +func loadBlockedDates(h *Server) []models.BlockedDate { + dates, err := h.s.GetBlockedDates() + if err != nil { + return nil + } + return dates +} + func (h *Server) order(r *http.Request) (templ.Component, int, error) { catalogue, err := h.s.GetCakes() if err != nil { @@ -150,13 +171,14 @@ func (h *Server) order(r *http.Request) (templ.Component, int, error) { if err != nil { return nil, http.StatusInternalServerError, err } + blockedDates := loadBlockedDates(h) url := strings.Split(r.URL.Path, "/") if len(url) < 3 || url[2] == "" { if r.Header.Get("HX-Request") == "true" { - return templates.MainContent("Nowe zamówienie", templ.NopComponent, templates.OrderForm(models.Order{Date: time.Now()}, catalogue, tags)), http.StatusOK, nil + return templates.MainContent("Nowe zamówienie", templ.NopComponent, templates.OrderForm(models.Order{Date: time.Now()}, catalogue, tags, blockedDates)), http.StatusOK, nil } - return templates.OrderPage(models.Order{Date: time.Now()}, catalogue, tags), http.StatusOK, nil + return templates.OrderPage(models.Order{Date: time.Now()}, catalogue, tags, blockedDates), http.StatusOK, nil } id, err := strconv.Atoi(url[2]) @@ -170,8 +192,8 @@ func (h *Server) order(r *http.Request) (templ.Component, int, error) { } if r.Header.Get("HX-Request") == "true" { - return templates.MainContent(templates.OrderTitle(order), templ.NopComponent, templates.OrderForm(order, catalogue, tags)), http.StatusOK, nil + return templates.MainContent(templates.OrderTitle(order), templ.NopComponent, templates.OrderForm(order, catalogue, tags, blockedDates)), http.StatusOK, nil } - return templates.OrderPage(order, catalogue, tags), http.StatusOK, nil + return templates.OrderPage(order, catalogue, tags, blockedDates), http.StatusOK, nil } diff --git a/server/post.go b/server/post.go index fca74c3..926ed15 100644 --- a/server/post.go +++ b/server/post.go @@ -142,6 +142,29 @@ func safeStr(s []string, i int) string { return "" } +func (h *Server) postBlocked(r *http.Request) (templ.Component, int, error) { + if err := r.ParseForm(); err != nil { + return nil, http.StatusBadRequest, err + } + if err := h.s.AddBlockedDate(r.FormValue("date")); err != nil { + return nil, http.StatusInternalServerError, err + } + dates, _ := h.s.GetBlockedDates() + return templates.BlockedMain(dates), http.StatusOK, nil +} + +func (h *Server) deleteBlocked(r *http.Request) (templ.Component, int, error) { + id, err := strconv.Atoi(r.PathValue("id")) + if err != nil { + return nil, http.StatusBadRequest, err + } + if err := h.s.RemoveBlockedDate(id); err != nil { + return nil, http.StatusInternalServerError, err + } + dates, _ := h.s.GetBlockedDates() + return templates.BlockedMain(dates), http.StatusOK, nil +} + func (h *Server) updateStatus(r *http.Request) (templ.Component, int, error) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil { diff --git a/server/server.go b/server/server.go index 92ddd77..16452c3 100644 --- a/server/server.go +++ b/server/server.go @@ -38,6 +38,9 @@ func New(store *store.Store) *Server { mux.HandleFunc("POST /order/", server.render(server.postOrder)) mux.HandleFunc("POST /order/{id}/status", server.render(server.updateStatus)) mux.HandleFunc("POST /cake/", server.render(server.postCake)) + mux.HandleFunc("GET /blocked", server.render(server.blocked)) + mux.HandleFunc("POST /blocked", server.render(server.postBlocked)) + mux.HandleFunc("POST /blocked/{id}/delete", server.render(server.deleteBlocked)) fs := http.FileServerFS(static) mux.Handle("GET /static/", fs) diff --git a/server/static/card.css b/server/static/card.css index 3ab5d76..b93b2a4 100644 --- a/server/static/card.css +++ b/server/static/card.css @@ -125,6 +125,7 @@ /* ========== SPLIT PANE ========== */ .split-pane { display: flex; + flex-direction: column; gap: 1rem; flex: 1; min-height: 0; @@ -139,19 +140,19 @@ } .order-aside { - width: 20rem; + width: 100%; flex-shrink: 0; display: flex; flex-direction: column; } -@media (max-width: 64em) { +@media (min-width: 64em) { .split-pane { - flex-direction: column; + flex-direction: row; } .order-aside { - width: 100%; + width: 20rem; } } diff --git a/server/static/component.css b/server/static/component.css index 9bced30..dc592d9 100644 --- a/server/static/component.css +++ b/server/static/component.css @@ -219,7 +219,7 @@ /* ========== CAKE BREAKDOWN LIST ========== */ .breakdown-list { list-style: none; padding: 0; margin: 0; } -.breakdown-item { display: flex; justify-content: space-between; padding: 0.375rem 0; border-bottom: 1px solid var(--outline-variant); } +.breakdown-item { display: flex; justify-content: space-between; padding: 0.375rem 1rem; margin: 0 -1rem; border-bottom: 1px solid var(--outline-variant); } .breakdown-amount { font-weight: 600; } .breakdown-empty { color: var(--on-surface-variant); font-size: 0.75rem; } diff --git a/server/static/order.js b/server/static/order.js index dd2c912..c4fbe2a 100644 --- a/server/static/order.js +++ b/server/static/order.js @@ -267,3 +267,13 @@ function applyCatalogueFilters() { function filterCatalogue(input) { applyCatalogueFilters(); } + +function confirmBlockedDate(form) { + var date = form.querySelector('[name="date"]').value; + var blocked = JSON.parse(form.getAttribute("data-blocked-dates") || "[]"); + var hasSpecial = form.querySelector('[name="special_name[]"]'); + if (hasSpecial && blocked.indexOf(date) >= 0) { + return confirm("Data " + date + " jest zablokowana dla zamówień z tortami specjalnymi. Czy na pewno chcesz kontynuować?"); + } + return true; +} diff --git a/server/templates/blocked.templ b/server/templates/blocked.templ new file mode 100644 index 0000000..3251b04 --- /dev/null +++ b/server/templates/blocked.templ @@ -0,0 +1,44 @@ +package templates + +import ( + "strconv" + + "git.bronku.xyz/bronku/cake-order-tracker/models" +) + +templ BlockedPage(blocked []models.BlockedDate) { + @Layout("Terminy zablokowane", templ.NopComponent, BlockedMain(blocked)) +} + +templ BlockedMain(blocked []models.BlockedDate) { +
+
+

+ block + Terminy zablokowane +

+
+
+
+ + +
+ if len(blocked) == 0 { +

Brak zablokowanych terminów

+ } else { +
    + for _, b := range blocked { +
  • + { b.Date } +
    + +
    +
  • + } +
+ } +
+
+} diff --git a/server/templates/helpers.go b/server/templates/helpers.go index 021431b..488e5a2 100644 --- a/server/templates/helpers.go +++ b/server/templates/helpers.go @@ -1,6 +1,7 @@ package templates import ( + "encoding/json" "fmt" "strconv" "strings" @@ -73,6 +74,15 @@ func orderItemsDesc(order models.Order) string { return desc } +func blockedDatesJSON(dates []models.BlockedDate) string { + names := make([]string, len(dates)) + for i, d := range dates { + names[i] = d.Date + } + b, _ := json.Marshal(names) + return string(b) +} + func orderScript(order models.Order) string { var b strings.Builder b.WriteString("