more simpler + torty sectino

This commit is contained in:
bronkuu 2026-06-15 20:30:15 +02:00
parent a74c276cd3
commit 3b184c7c6f
10 changed files with 375 additions and 152 deletions

View file

@ -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) {

View file

@ -25,7 +25,6 @@
--space-lg: 1.5rem;
--space-xl: 2rem;
--sidebar-w: 17.5rem;
--topnav-h: 3.5rem;
--radius-sm: 0.25rem;

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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();

View file

@ -14,11 +14,26 @@ templ Layout(title string, main templ.Component) {
<script src="/static/order.js"></script>
</head>
<body>
<aside>
@Nav()
</aside>
<header class="topnav">
<h1>{ title }</h1>
<nav>
<a href="/orders" hx-get="/orders" hx-target="#main" hx-swap="outerHTML" hx-push-url="true">
<span class="material-symbols-outlined">dashboard</span>
<span>Panel główny</span>
</a>
<a href="/cakes" hx-get="/cakes" hx-target="#main" hx-swap="outerHTML" hx-push-url="true">
<span class="material-symbols-outlined">inventory_2</span>
<span>Katalog</span>
</a>
<a href="/blocked" hx-get="/blocked" hx-target="#main" hx-swap="outerHTML" hx-push-url="true">
<span class="material-symbols-outlined">event_busy</span>
<span>Terminy zablokowane</span>
</a>
</nav>
<a href="/order" class="cta" hx-get="/order" hx-target="#main" hx-swap="outerHTML" hx-push-url="true">
<span class="material-symbols-outlined">add</span>
<span>Nowe zamówienie</span>
</a>
</header>
<main id="main">
<div class="main-content">
@ -32,6 +47,24 @@ templ Layout(title string, main templ.Component) {
templ MainContent(title string, main templ.Component) {
<header class="topnav" hx-swap-oob="true">
<h1>{ title }</h1>
<nav>
<a href="/orders" hx-get="/orders" hx-target="#main" hx-swap="outerHTML" hx-push-url="true">
<span class="material-symbols-outlined">dashboard</span>
<span>Panel główny</span>
</a>
<a href="/cakes" hx-get="/cakes" hx-target="#main" hx-swap="outerHTML" hx-push-url="true">
<span class="material-symbols-outlined">inventory_2</span>
<span>Katalog</span>
</a>
<a href="/blocked" hx-get="/blocked" hx-target="#main" hx-swap="outerHTML" hx-push-url="true">
<span class="material-symbols-outlined">event_busy</span>
<span>Terminy zablokowane</span>
</a>
</nav>
<a href="/order" class="cta" hx-get="/order" hx-target="#main" hx-swap="outerHTML" hx-push-url="true">
<span class="material-symbols-outlined">add</span>
<span>Nowe zamówienie</span>
</a>
</header>
<main id="main">
<title hx-swap-oob="true">{ title } — Zamówienia ciast</title>

View file

@ -1,22 +0,0 @@
package templates
templ Nav() {
<a href="/order" class="cta" hx-get="/order" hx-target="#main" hx-swap="outerHTML" hx-push-url="true">
<span class="material-symbols-outlined">add</span>
<span>Nowe zamówienie</span>
</a>
<nav>
<a href="/orders" hx-get="/orders" hx-target="#main" hx-swap="outerHTML" hx-push-url="true">
<span class="material-symbols-outlined">dashboard</span>
<span>Panel główny</span>
</a>
<a href="/cakes" hx-get="/cakes" hx-target="#main" hx-swap="outerHTML" hx-push-url="true">
<span class="material-symbols-outlined">inventory_2</span>
<span>Katalog</span>
</a>
<a href="/blocked" hx-get="/blocked" hx-target="#main" hx-swap="outerHTML" hx-push-url="true">
<span class="material-symbols-outlined">event_busy</span>
<span>Terminy zablokowane</span>
</a>
</nav>
}

View file

@ -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) {
</div>
}
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)
<div class="metric">
<span class="label">
@ -46,11 +46,12 @@ templ OrdersMain(counts models.OrderCounts, cakeCounts []models.CakeCount, order
<span class="change">Do zrobienia</span>
</div>
<div id="orders-results">
@OrdersContent(orders, cakeCounts, first, last, special)
@OrdersContent(orders, cakeCounts, specialCakes, first, last, special)
</div>
@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) {
<section class="card" id="orders-card">
<header>
<h2>
@ -90,6 +91,7 @@ templ OrdersContent(orders []models.Order, cakeCounts []models.CakeCount, first,
</table>
</section>
@CakeBreakdown(cakeCounts)
@SpecialCakesTable(specialCakes)
}
templ OrderRow(order models.Order) {
@ -156,6 +158,125 @@ templ OrderRow(order models.Order) {
</tr>
}
templ SpecialCakesTable(specialCakes []models.SpecialCakeWithOrder) {
<section class="card" id="special-cakes-card">
<header>
<h2>
<span class="material-symbols-outlined">cake</span>
Torty specjalne
</h2>
</header>
<table class="data-table">
<thead>
<tr>
<th>Nazwa</th>
<th>Klient</th>
<th>Data odbioru</th>
<th class="text-right">Cena</th>
<th class="text-center"></th>
</tr>
</thead>
<tbody>
for _, sc := range specialCakes {
<tr data-sc-id={ strconv.Itoa(sc.ID) }
data-sc-name={ sc.Name }
data-sc-price={ sc.Price.String() }
data-sc-size={ sc.Size }
data-sc-shape={ sc.Shape }
data-sc-flavour={ sc.Flavour }
data-sc-notes={ sc.Notes }
data-sc-tags={ tagNames(sc.Tags) }
data-sc-order-id={ strconv.Itoa(sc.OrderID) }
data-sc-client={ sc.ClientName + " " + sc.ClientSurname }
data-sc-date={ sc.OrderDate.Format("2006-01-02") }>
<td>
<button type="button" class="link-btn" onclick="viewSpecialCake(this)">{ sc.Name }</button>
</td>
<td>{ sc.ClientName } { sc.ClientSurname }</td>
<td class="text-muted">{ sc.OrderDate.Format("2006-01-02") }</td>
<td class="text-right">{ sc.Price.String() } PLN</td>
<td class="text-center">
<a href={ templ.URL("/order/" + strconv.Itoa(sc.OrderID)) } class="btn-icon" title="Zobacz zamówienie">
<span class="material-symbols-outlined">receipt_long</span>
</a>
</td>
</tr>
}
if len(specialCakes) == 0 {
<tr>
<td colspan="5" class="empty-state">Brak tortów specjalnych w tym okresie</td>
</tr>
}
</tbody>
</table>
</section>
}
templ SpecialCakeViewOverlay() {
<div id="sc-view-overlay" class="overlay">
<div class="overlay-backdrop" onclick="closeViewSpecialCake()"></div>
<div class="overlay-panel overlay-panel--view">
<header>
<h2>
<span class="material-symbols-outlined">cake</span>
<span id="sc-view-title">Tort specjalny</span>
</h2>
<button type="button" class="close-btn" onclick="closeViewSpecialCake()">
<span class="material-symbols-outlined">close</span>
</button>
</header>
<div class="overlay-body">
<div class="view-field">
<label>Nazwa</label>
<span id="sc-view-name"></span>
</div>
<div class="view-field">
<label>Cena</label>
<span id="sc-view-price"></span>
</div>
<div class="view-field">
<label>Wielkość</label>
<span id="sc-view-size"></span>
</div>
<div class="view-field">
<label>Kształt</label>
<span id="sc-view-shape"></span>
</div>
<div class="view-field">
<label>Smak</label>
<span id="sc-view-flavour"></span>
</div>
<div class="view-field">
<label>Notatki</label>
<span id="sc-view-notes"></span>
</div>
<div class="view-field" id="sc-view-tags-field">
<label>Tagi</label>
<div id="sc-view-tags"></div>
</div>
<div class="view-field">
<label>Klient</label>
<span id="sc-view-client"></span>
</div>
<div class="view-field">
<label>Data odbioru</label>
<span id="sc-view-date"></span>
</div>
</div>
<footer>
<a href="" id="sc-view-order-link" class="btn btn-outline">
<span class="material-symbols-outlined">receipt_long</span>
Zobacz zamówienie
</a>
<button type="button" class="btn btn-primary" onclick="closeViewSpecialCake()">
<span class="material-symbols-outlined">close</span>
Zamknij
</button>
</footer>
</div>
</div>
}
templ CakeBreakdown(cakeCounts []models.CakeCount) {
<section class="card" id="breakdown-card">
<header>