diff --git a/models/data.go b/models/data.go
index 3d11a08..43b3f41 100644
--- a/models/data.go
+++ b/models/data.go
@@ -96,6 +96,21 @@ type CakeCount struct {
Amount int
}
+type SpecialCakeWithOrder struct {
+ ID int
+ OrderID int
+ Name string
+ Price Price
+ Size string
+ Shape string
+ Flavour string
+ Notes string
+ Tags []Tag
+ ClientName string
+ ClientSurname string
+ OrderDate time.Time
+}
+
type BlockedDate struct {
ID int
Date string
diff --git a/server/get.go b/server/get.go
index 4c438cc..8b90d29 100644
--- a/server/get.go
+++ b/server/get.go
@@ -65,6 +65,10 @@ func (h *Server) orders(r *http.Request) (templ.Component, int, error) {
if err != nil {
return nil, http.StatusInternalServerError, err
}
+ specialCakes, err := h.s.GetSpecialCakesInRange(first, last)
+ if err != nil {
+ return nil, http.StatusInternalServerError, err
+ }
firstStr := first.Format(config.DateFormat)
lastStr := last.Format(config.DateFormat)
@@ -74,11 +78,11 @@ func (h *Server) orders(r *http.Request) (templ.Component, int, error) {
if err != nil {
return nil, http.StatusInternalServerError, err
}
- return templates.MainContent("Panel główny", templates.OrdersMain(counts, cakeCounts, orders, firstStr, lastStr, specialFilter, today, tomorrow)), http.StatusOK, nil
+ return templates.MainContent("Panel główny", templates.OrdersMain(counts, cakeCounts, orders, specialCakes, firstStr, lastStr, specialFilter, today, tomorrow)), http.StatusOK, nil
}
if r.Header.Get("HX-Request") == "true" {
- return templates.OrdersContent(orders, cakeCounts, firstStr, lastStr, specialFilter), http.StatusOK, nil
+ return templates.OrdersContent(orders, cakeCounts, specialCakes, firstStr, lastStr, specialFilter), http.StatusOK, nil
}
counts, err := h.s.GetOrderCounts()
@@ -86,7 +90,7 @@ func (h *Server) orders(r *http.Request) (templ.Component, int, error) {
return nil, http.StatusInternalServerError, err
}
- return templates.OrdersPage(firstStr, lastStr, today, tomorrow, specialFilter, counts, cakeCounts, orders), http.StatusOK, nil
+ return templates.OrdersPage(firstStr, lastStr, today, tomorrow, specialFilter, counts, cakeCounts, specialCakes, orders), http.StatusOK, nil
}
func (h *Server) cakes(r *http.Request) (templ.Component, int, error) {
diff --git a/server/static/base.css b/server/static/base.css
index 18a6f75..0d77d8f 100644
--- a/server/static/base.css
+++ b/server/static/base.css
@@ -25,7 +25,6 @@
--space-lg: 1.5rem;
--space-xl: 2rem;
- --sidebar-w: 17.5rem;
--topnav-h: 3.5rem;
--radius-sm: 0.25rem;
diff --git a/server/static/components.css b/server/static/components.css
index 613f707..0c5b4c0 100644
--- a/server/static/components.css
+++ b/server/static/components.css
@@ -749,3 +749,53 @@ textarea + label {
.hide {
display: none;
}
+
+.link-btn {
+ background: none;
+ border: none;
+ color: var(--primary);
+ font-weight: 600;
+ font-size: inherit;
+ cursor: pointer;
+ padding: 0;
+ text-align: left;
+}
+
+.link-btn:hover {
+ text-decoration: underline;
+}
+
+.overlay-panel--view {
+ width: 24rem;
+}
+
+.view-field {
+ display: flex;
+ flex-direction: column;
+ gap: 0.125rem;
+ padding: var(--space-sm) 0;
+ border-bottom: 1px solid var(--outline-dim);
+}
+
+.view-field:last-child {
+ border-bottom: none;
+}
+
+.view-field > label {
+ font-size: 0.75rem;
+ font-weight: 600;
+ color: var(--on-surface-dim);
+ letter-spacing: 0.02em;
+}
+
+.view-field > span,
+.view-field > div {
+ font-size: 0.875rem;
+ color: var(--on-surface);
+}
+
+#sc-view-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--space-xs);
+}
diff --git a/server/static/layout.css b/server/static/layout.css
index 6d245e4..ec10edf 100644
--- a/server/static/layout.css
+++ b/server/static/layout.css
@@ -1,32 +1,52 @@
body {
display: grid;
- grid-template-columns: var(--sidebar-w) 1fr;
grid-template-rows: var(--topnav-h) 1fr;
- grid-template-areas:
- "sidebar topnav"
- "sidebar main";
height: 100dvh;
}
-aside {
- grid-area: sidebar;
- background: var(--surface-white);
- border-right: 1px solid var(--outline-dim);
- display: flex;
- flex-direction: column;
- padding: var(--space-md);
- gap: var(--space-sm);
- overflow: hidden;
- z-index: 40;
-}
-
-aside > .cta {
- width: 100%;
- height: 2.5rem;
+.topnav {
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
+ padding: 0 var(--space-lg);
+ border-bottom: 1px solid var(--outline-dim);
+ background: var(--surface);
+ z-index: 30;
+}
+
+.topnav > h1 {
+ margin-right: auto;
+}
+
+.topnav > nav {
+ display: flex;
+ gap: var(--space-xs);
+}
+
+.topnav > nav > a {
display: flex;
align-items: center;
- justify-content: center;
gap: var(--space-sm);
+ padding: var(--space-sm) var(--space-md);
+ border-radius: var(--radius-md);
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: var(--on-surface-dim);
+ text-decoration: none;
+}
+
+.topnav > nav > a.active {
+ background: var(--primary-container);
+ color: var(--primary);
+ font-weight: 700;
+}
+
+.topnav > .cta {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+ height: 2.25rem;
+ padding: 0 var(--space-md);
background: var(--primary);
color: var(--on-primary);
border: none;
@@ -35,74 +55,10 @@ aside > .cta {
font-weight: 600;
cursor: pointer;
text-decoration: none;
-}
-
-aside > nav {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: var(--space-xs);
-}
-
-aside > nav > a {
- display: flex;
- align-items: center;
- gap: var(--space-sm);
- padding: var(--space-sm) var(--space-md);
- border-radius: var(--radius-md);
- font-size: 0.875rem;
- font-weight: 500;
- text-decoration: none;
- color: var(--on-surface-dim);
-}
-
-aside > nav > a.active {
- background: var(--primary-container);
- color: var(--primary);
- font-weight: 700;
-}
-
-.topnav {
- grid-area: topnav;
- border-bottom: 1px solid var(--outline-dim);
- background: var(--surface);
- display: flex;
- align-items: center;
- gap: var(--space-lg);
- padding: 0 var(--space-lg);
flex-shrink: 0;
- position: sticky;
- top: 0;
- z-index: 30;
-}
-
-.topnav > h1 {
- margin-right: auto;
-}
-
-.topnav > .search {
- position: relative;
- width: 15rem;
-}
-
-.topnav > .search input {
- height: 2rem;
- padding-left: 2rem;
- font-size: 0.875rem;
-}
-
-.topnav > .search .material-symbols-outlined {
- position: absolute;
- left: var(--space-sm);
- top: 50%;
- transform: translateY(-50%);
- font-size: 1.25rem;
- color: var(--on-surface-dim);
- pointer-events: none;
}
main {
- grid-area: main;
overflow-y: auto;
}
@@ -120,52 +76,38 @@ main {
#orders-results {
grid-column: 1 / -1;
- display: flex;
- flex-direction: column;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
gap: var(--space-md);
}
-@media (max-width: 64em) {
- .topnav > .search {
- width: 10rem;
- }
-}
-
@media (max-width: 48em) {
- aside {
- width: var(--sidebar-w);
- padding: var(--space-xs);
- }
-
- aside > .cta {
- width: 2.5rem;
- height: 2.5rem;
- padding: 0;
- border-radius: 50%;
- }
-
- aside > .cta span:not(.material-symbols-outlined) {
- display: none;
- }
-
- aside > nav > a {
- justify-content: center;
- padding: var(--space-sm);
- }
-
- aside > nav > a span:not(.material-symbols-outlined) {
- display: none;
- }
-
.topnav {
padding: 0 var(--space-sm);
+ gap: var(--space-xs);
}
- .topnav > .search {
+ .topnav > nav > a span:not(.material-symbols-outlined) {
display: none;
}
+ .topnav > .cta span:not(.material-symbols-outlined) {
+ display: none;
+ }
+
+ .topnav > .cta {
+ width: 2.25rem;
+ height: 2.25rem;
+ padding: 0;
+ border-radius: 50%;
+ justify-content: center;
+ }
+
.main-content {
padding: var(--space-sm);
}
+
+ .topnav > h1 {
+ font-size: 1rem;
+ }
}
diff --git a/server/static/order.js b/server/static/order.js
index 526d4a3..b3fb3c5 100644
--- a/server/static/order.js
+++ b/server/static/order.js
@@ -329,6 +329,47 @@ function setActiveLink() {
}
}
+function viewSpecialCake(btn) {
+ var tr = btn.closest('tr');
+ document.getElementById('sc-view-title').textContent = tr.getAttribute('data-sc-name');
+ document.getElementById('sc-view-name').textContent = tr.getAttribute('data-sc-name');
+ document.getElementById('sc-view-price').textContent = tr.getAttribute('data-sc-price') + ' PLN';
+ document.getElementById('sc-view-size').textContent = tr.getAttribute('data-sc-size') || '—';
+ var shapeVal = tr.getAttribute('data-sc-shape');
+ var shapeText = '';
+ if (shapeVal === 'round') shapeText = 'Okrągły';
+ else if (shapeVal === 'square') shapeText = 'Kwadrat';
+ else shapeText = shapeVal || '—';
+ document.getElementById('sc-view-shape').textContent = shapeText;
+ document.getElementById('sc-view-flavour').textContent = tr.getAttribute('data-sc-flavour') || '—';
+ document.getElementById('sc-view-notes').textContent = tr.getAttribute('data-sc-notes') || '—';
+ document.getElementById('sc-view-client').textContent = tr.getAttribute('data-sc-client');
+ document.getElementById('sc-view-date').textContent = tr.getAttribute('data-sc-date');
+ var tagsStr = tr.getAttribute('data-sc-tags');
+ var tagsContainer = document.getElementById('sc-view-tags');
+ tagsContainer.innerHTML = '';
+ if (tagsStr) {
+ var tagNames = tagsStr.split(',');
+ for (var i = 0; i < tagNames.length; i++) {
+ if (tagNames[i].trim()) {
+ var tag = document.createElement('span');
+ tag.className = 'tag-chip sm';
+ tag.textContent = tagNames[i].trim();
+ tagsContainer.appendChild(tag);
+ }
+ }
+ document.getElementById('sc-view-tags-field').classList.remove('hide');
+ } else {
+ document.getElementById('sc-view-tags-field').classList.add('hide');
+ }
+ document.getElementById('sc-view-order-link').href = '/order/' + tr.getAttribute('data-sc-order-id');
+ document.getElementById('sc-view-overlay').classList.add('open');
+}
+
+function closeViewSpecialCake() {
+ document.getElementById('sc-view-overlay').classList.remove('open');
+}
+
document.addEventListener('DOMContentLoaded', function() {
setActiveLink();
initOrderPage();
diff --git a/server/templates/layout.templ b/server/templates/layout.templ
index 3ff8b3a..a0dbd25 100644
--- a/server/templates/layout.templ
+++ b/server/templates/layout.templ
@@ -14,11 +14,26 @@ templ Layout(title string, main templ.Component) {
-
@@ -32,6 +47,24 @@ templ Layout(title string, main templ.Component) {
templ MainContent(title string, main templ.Component) {
{ title } — Zamówienia ciast
diff --git a/server/templates/nav.templ b/server/templates/nav.templ
deleted file mode 100644
index 01b8690..0000000
--- a/server/templates/nav.templ
+++ /dev/null
@@ -1,22 +0,0 @@
-package templates
-
-templ Nav() {
-
- add
- Nowe zamówienie
-
-
-}
diff --git a/server/templates/orders.templ b/server/templates/orders.templ
index f2dfb2a..6a187aa 100644
--- a/server/templates/orders.templ
+++ b/server/templates/orders.templ
@@ -6,8 +6,8 @@ import (
"git.bronku.xyz/bronku/cake-order-tracker/models"
)
-templ OrdersPage(first, last, today, tomorrow, special string, counts models.OrderCounts, cakeCounts []models.CakeCount, orders []models.Order) {
- @Layout("Panel główny", OrdersMain(counts, cakeCounts, orders, first, last, special, today, tomorrow))
+templ OrdersPage(first, last, today, tomorrow, special string, counts models.OrderCounts, cakeCounts []models.CakeCount, specialCakes []models.SpecialCakeWithOrder, orders []models.Order) {
+ @Layout("Panel główny", OrdersMain(counts, cakeCounts, orders, specialCakes, first, last, special, today, tomorrow))
}
templ DateForm(first, last, today, tomorrow, special string) {
@@ -27,7 +27,7 @@ templ DateForm(first, last, today, tomorrow, special string) {
}
-templ OrdersMain(counts models.OrderCounts, cakeCounts []models.CakeCount, orders []models.Order, first, last, special, today, tomorrow string) {
+templ OrdersMain(counts models.OrderCounts, cakeCounts []models.CakeCount, orders []models.Order, specialCakes []models.SpecialCakeWithOrder, first, last, special, today, tomorrow string) {
@DateForm(first, last, today, tomorrow, special)
@@ -46,11 +46,12 @@ templ OrdersMain(counts models.OrderCounts, cakeCounts []models.CakeCount, order
Do zrobienia
- @OrdersContent(orders, cakeCounts, first, last, special)
+ @OrdersContent(orders, cakeCounts, specialCakes, first, last, special)
+ @SpecialCakeViewOverlay()
}
-templ OrdersContent(orders []models.Order, cakeCounts []models.CakeCount, first, last, special string) {
+templ OrdersContent(orders []models.Order, cakeCounts []models.CakeCount, specialCakes []models.SpecialCakeWithOrder, first, last, special string) {
@@ -90,6 +91,7 @@ templ OrdersContent(orders []models.Order, cakeCounts []models.CakeCount, first,
@CakeBreakdown(cakeCounts)
+ @SpecialCakesTable(specialCakes)
}
templ OrderRow(order models.Order) {
@@ -156,6 +158,125 @@ templ OrderRow(order models.Order) {
}
+templ SpecialCakesTable(specialCakes []models.SpecialCakeWithOrder) {
+
+
+
+ cake
+ Torty specjalne
+
+
+
+
+
+ | Nazwa |
+ Klient |
+ Data odbioru |
+ Cena |
+ |
+
+
+
+ for _, sc := range specialCakes {
+
+ |
+
+ |
+ { sc.ClientName } { sc.ClientSurname } |
+ { sc.OrderDate.Format("2006-01-02") } |
+ { sc.Price.String() } PLN |
+
+
+ receipt_long
+
+ |
+
+ }
+ if len(specialCakes) == 0 {
+
+ | Brak tortów specjalnych w tym okresie |
+
+ }
+
+
+
+}
+
+templ SpecialCakeViewOverlay() {
+
+
+
+
+
+ cake
+ Tort specjalny
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
templ CakeBreakdown(cakeCounts []models.CakeCount) {
diff --git a/store/special_cake.go b/store/special_cake.go
index 5cfc704..d2de64d 100644
--- a/store/special_cake.go
+++ b/store/special_cake.go
@@ -3,7 +3,9 @@ package store
import (
"database/sql"
"log"
+ "time"
+ "git.bronku.xyz/bronku/cake-order-tracker/config"
"git.bronku.xyz/bronku/cake-order-tracker/models"
)
@@ -67,6 +69,44 @@ func (s *Store) saveSpecialCakeTags(tx *sql.Tx, specialCakeID int, tagIDs []int)
return nil
}
+func (s *Store) GetSpecialCakesInRange(from, to time.Time) ([]models.SpecialCakeWithOrder, error) {
+ start, end := dateRangeParams(from, to)
+ rows, err := s.db.Query(`
+ SELECT sc.id, sc.customer_order, sc.name, sc.price, sc.size, sc.shape, sc.flavour, sc.notes,
+ co.name, co.surname, co.delivery_date
+ FROM special_cake sc
+ JOIN customer_order co ON sc.customer_order = co.id
+ WHERE co.status != 'picked_up' AND co.delivery_date >= ? AND co.delivery_date <= ?
+ ORDER BY co.delivery_date
+ `, start, end)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ var out []models.SpecialCakeWithOrder
+ for rows.Next() {
+ var sw models.SpecialCakeWithOrder
+ var price int
+ var deliveryDate string
+ if err := rows.Scan(&sw.ID, &sw.OrderID, &sw.Name, &price, &sw.Size, &sw.Shape, &sw.Flavour, &sw.Notes,
+ &sw.ClientName, &sw.ClientSurname, &deliveryDate); err != nil {
+ return nil, err
+ }
+ sw.Price = models.Price(price)
+ sw.OrderDate, err = time.Parse(config.DateTimeFormat, deliveryDate)
+ if err != nil {
+ return nil, err
+ }
+ sw.Tags, err = s.GetSpecialCakeTags(sw.ID)
+ if err != nil {
+ return nil, err
+ }
+ out = append(out, sw)
+ }
+ return out, rows.Err()
+}
+
func (s *Store) SaveSpecialCakes(tx *sql.Tx, orderID int, cakes []models.SpecialCake) error {
if _, err := tx.Exec("delete from special_cake where customer_order = ?;", orderID); err != nil {
return err