move to templ
This commit is contained in:
parent
31a33cc220
commit
bb32802324
34 changed files with 1011 additions and 528 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -22,6 +22,9 @@
|
|||
go.work
|
||||
go.work.sum
|
||||
|
||||
# Generated templ code
|
||||
*_templ.go
|
||||
|
||||
# env file
|
||||
.env
|
||||
.idea
|
||||
|
|
|
|||
23
go.mod
23
go.mod
|
|
@ -1,5 +1,26 @@
|
|||
module git.bronku.xyz/bronku/cake-order-tracker
|
||||
|
||||
go 1.23.5
|
||||
go 1.25.0
|
||||
|
||||
require github.com/mattn/go-sqlite3 v1.14.24
|
||||
|
||||
require github.com/a-h/templ v0.3.1020
|
||||
|
||||
require (
|
||||
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cli/browser v1.3.0 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/natefinch/atomic v1.0.1 // indirect
|
||||
golang.org/x/mod v0.26.0 // indirect
|
||||
golang.org/x/net v0.51.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/tools v0.35.0 // indirect
|
||||
)
|
||||
|
||||
tool github.com/a-h/templ/cmd/templ
|
||||
|
|
|
|||
43
go.sum
43
go.sum
|
|
@ -1,2 +1,45 @@
|
|||
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e h1:HjVbSQHy+dnlS6C3XajZ69NYAb5jbGNfHanvm1+iYlo=
|
||||
github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ=
|
||||
github.com/a-h/templ v0.3.1020 h1:ypAT/L5ySWEnZ6Zft/5yfoWXYYkhFNvEFOeeqecg4tw=
|
||||
github.com/a-h/templ v0.3.1020/go.mod h1:A2DlK61v+K+NRoGnhmYbNYVmtYHcFO5/AisMvBdDxTM=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo=
|
||||
github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A=
|
||||
github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
|||
8
main.go
8
main.go
|
|
@ -1,5 +1,7 @@
|
|||
package main
|
||||
|
||||
//go:generate go tool templ generate
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
|
@ -12,9 +14,9 @@ import (
|
|||
func main() {
|
||||
s := store.OpenStore("./foo.db")
|
||||
defer s.Close()
|
||||
h := server.New(s)
|
||||
defer h.Close()
|
||||
var h http.Handler = server.New(s)
|
||||
h = logging.Middleware(h)
|
||||
|
||||
log.Println("starting server")
|
||||
log.Fatal(http.ListenAndServe(":8080", logging.Middleware(h)))
|
||||
log.Fatal(http.ListenAndServe(":8080", h))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
package server
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
type fetcher func(r *http.Request) (any, int, error)
|
||||
"github.com/a-h/templ"
|
||||
)
|
||||
|
||||
type fetcher func(r *http.Request) (templ.Component, int, error)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
|
||||
"git.bronku.xyz/bronku/cake-order-tracker/models"
|
||||
"git.bronku.xyz/bronku/cake-order-tracker/server/templates"
|
||||
)
|
||||
|
||||
func monthInterval(y int, m time.Month) (firstDay, lastDay time.Time) {
|
||||
|
|
@ -16,24 +18,18 @@ func monthInterval(y int, m time.Month) (firstDay, lastDay time.Time) {
|
|||
return firstDay, lastDay
|
||||
}
|
||||
|
||||
func (h *Server) orders(_ *http.Request) (any, int, error) {
|
||||
func (h *Server) orders(_ *http.Request) (templ.Component, int, error) {
|
||||
var (
|
||||
y int
|
||||
m time.Month
|
||||
)
|
||||
y, m, _ = time.Now().Date()
|
||||
first, last := monthInterval(y, m)
|
||||
fmt.Println(first, last)
|
||||
orders, err := h.s.GetOrders(first, last)
|
||||
data := struct {
|
||||
First string
|
||||
Last string
|
||||
Orders []models.Order
|
||||
}{first.Format("2006-01-02"), last.Format("2006-01-02"), orders}
|
||||
return data, http.StatusOK, err
|
||||
return templates.OrdersPage(first.Format("2006-01-02"), last.Format("2006-01-02"), orders), http.StatusOK, err
|
||||
}
|
||||
|
||||
func (h *Server) ordersSearch(r *http.Request) (any, int, error) {
|
||||
func (h *Server) ordersSearch(r *http.Request) (templ.Component, int, error) {
|
||||
from, err := time.Parse("2006-01-02", r.URL.Query().Get("from"))
|
||||
if err != nil {
|
||||
from = time.Time{}
|
||||
|
|
@ -42,20 +38,19 @@ func (h *Server) ordersSearch(r *http.Request) (any, int, error) {
|
|||
if err != nil {
|
||||
to = time.Time{}
|
||||
}
|
||||
fmt.Println(from, to)
|
||||
data, err := h.s.GetOrders(from, to)
|
||||
return data, http.StatusOK, err
|
||||
return templates.OrdersTable(data), http.StatusOK, err
|
||||
}
|
||||
|
||||
func (h *Server) cakes(_ *http.Request) (any, int, error) {
|
||||
func (h *Server) cakes(_ *http.Request) (templ.Component, int, error) {
|
||||
data, err := h.s.GetCakes()
|
||||
return data, http.StatusOK, err
|
||||
return templates.CakesPage(data), http.StatusOK, err
|
||||
}
|
||||
|
||||
func (h *Server) cake(r *http.Request) (any, int, error) {
|
||||
func (h *Server) cake(r *http.Request) (templ.Component, int, error) {
|
||||
url := strings.Split(r.URL.String(), "/")
|
||||
if len(url) < 3 || url[2] == "" {
|
||||
return models.Cake{}, http.StatusOK, nil
|
||||
return templates.CakePage(models.Cake{}), http.StatusOK, nil
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(url[2])
|
||||
|
|
@ -68,26 +63,19 @@ func (h *Server) cake(r *http.Request) (any, int, error) {
|
|||
return nil, http.StatusNotFound, err
|
||||
}
|
||||
|
||||
return data, http.StatusOK, nil
|
||||
return templates.CakePage(data), http.StatusOK, nil
|
||||
}
|
||||
|
||||
func (h *Server) order(r *http.Request) (any, int, error) {
|
||||
type formData struct {
|
||||
Order models.Order
|
||||
Catalogue []models.Cake
|
||||
}
|
||||
func (h *Server) order(r *http.Request) (templ.Component, int, error) {
|
||||
var err error
|
||||
var data formData
|
||||
|
||||
data.Catalogue, err = h.s.GetCakes()
|
||||
catalogue, err := h.s.GetCakes()
|
||||
if err != nil {
|
||||
return nil, http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
url := strings.Split(r.URL.String(), "/")
|
||||
if len(url) < 3 || url[2] == "" {
|
||||
data.Order.Date = time.Now()
|
||||
return data, http.StatusOK, nil
|
||||
return templates.OrderPage(models.Order{Date: time.Now()}, catalogue), http.StatusOK, nil
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(url[2])
|
||||
|
|
@ -95,10 +83,10 @@ func (h *Server) order(r *http.Request) (any, int, error) {
|
|||
return nil, http.StatusBadRequest, err
|
||||
}
|
||||
|
||||
data.Order, err = h.s.GetOrder(id)
|
||||
order, err := h.s.GetOrder(id)
|
||||
if err != nil {
|
||||
return nil, http.StatusNotFound, err
|
||||
}
|
||||
|
||||
return data, http.StatusOK, nil
|
||||
return templates.OrderPage(order, catalogue), http.StatusOK, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,13 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
|
||||
"git.bronku.xyz/bronku/cake-order-tracker/models"
|
||||
"git.bronku.xyz/bronku/cake-order-tracker/server/templates"
|
||||
)
|
||||
|
||||
func (h *Server) postCake(r *http.Request) (any, int, error) {
|
||||
func (h *Server) postCake(r *http.Request) (templ.Component, int, error) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
return nil, http.StatusInternalServerError, err
|
||||
|
|
@ -28,10 +31,10 @@ func (h *Server) postCake(r *http.Request) (any, int, error) {
|
|||
}
|
||||
n.ID, err = h.s.SaveCake(n)
|
||||
fmt.Println(err)
|
||||
return n, http.StatusAccepted, err
|
||||
return templates.Confirmation("Potwierdzone", n.ID), http.StatusAccepted, err
|
||||
}
|
||||
|
||||
func (h *Server) postOrder(r *http.Request) (any, int, error) {
|
||||
func (h *Server) postOrder(r *http.Request) (templ.Component, int, error) {
|
||||
cakes, err := h.s.GetCakes()
|
||||
if err != nil {
|
||||
return nil, http.StatusInternalServerError, err
|
||||
|
|
@ -75,5 +78,5 @@ func (h *Server) postOrder(r *http.Request) (any, int, error) {
|
|||
|
||||
n.ID, err = h.s.SaveOrder(n)
|
||||
fmt.Println(n)
|
||||
return n, http.StatusAccepted, err
|
||||
return templates.Confirmation("Potwierdzone", n.ID), http.StatusAccepted, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,27 +2,19 @@ package server
|
|||
|
||||
import (
|
||||
"embed"
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"git.bronku.xyz/bronku/cake-order-tracker/store"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
tmpl map[string]*template.Template
|
||||
s *store.Store
|
||||
routes map[string]route
|
||||
http.Handler
|
||||
}
|
||||
|
||||
type route struct {
|
||||
function fetcher
|
||||
template string
|
||||
templateEntry string
|
||||
}
|
||||
|
||||
func (h *Server) Close() {
|
||||
h.Close()
|
||||
function fetcher
|
||||
}
|
||||
|
||||
//go:embed static/*
|
||||
|
|
@ -32,7 +24,7 @@ func (h *Server) loadHandler() {
|
|||
mux := http.NewServeMux()
|
||||
|
||||
for i, e := range h.routes {
|
||||
mux.HandleFunc(i, h.render(e.function, e.template, e.templateEntry))
|
||||
mux.HandleFunc(i, h.render(e.function))
|
||||
}
|
||||
|
||||
mux.HandleFunc("GET /", h.redirect("/orders", http.StatusSeeOther))
|
||||
|
|
@ -47,16 +39,15 @@ func New(store *store.Store) *Server {
|
|||
var server Server
|
||||
|
||||
server.routes = map[string]route{
|
||||
"GET /order/": {server.order, "order", "layout"},
|
||||
"GET /orders": {server.orders, "orders", "layout"},
|
||||
"GET /orders/search/": {server.ordersSearch, "orders", "orders-table"},
|
||||
"GET /cake/": {server.cake, "cake", "layout"},
|
||||
"GET /cakes": {server.cakes, "cakes", "layout"},
|
||||
"POST /order/": {server.postOrder, "confirmation", "layout"},
|
||||
"POST /cake/": {server.postCake, "confirmation", "layout"},
|
||||
"GET /order/": {server.order},
|
||||
"GET /orders": {server.orders},
|
||||
"GET /orders/search/": {server.ordersSearch},
|
||||
"GET /cake/": {server.cake},
|
||||
"GET /cakes": {server.cakes},
|
||||
"POST /order/": {server.postOrder},
|
||||
"POST /cake/": {server.postCake},
|
||||
}
|
||||
|
||||
server.loadTemplates()
|
||||
server.s = store
|
||||
server.loadHandler()
|
||||
|
||||
|
|
|
|||
Binary file not shown.
BIN
server/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2
Normal file
BIN
server/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
315
server/static/fonts/inter.css
Normal file
315
server/static/fonts/inter.css
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
Binary file not shown.
24
server/static/fonts/material.css
Normal file
24
server/static/fonts/material.css
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/* fallback */
|
||||
@font-face {
|
||||
font-family: 'Material Symbols Outlined';
|
||||
font-style: normal;
|
||||
font-weight: 100 700;
|
||||
font-display: swap;
|
||||
src: url(/static/fonts/kJESBvYX7BgnkSrUwT8OhrdQw4oELdPIeeII9v6oDMzBwG-RpA6RzaxHMO1W.woff2) format('woff2');
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
font-family: 'Material Symbols Outlined';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
direction: ltr;
|
||||
-webkit-font-feature-settings: 'liga';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
|
@ -1,47 +1,23 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"git.bronku.xyz/bronku/cake-order-tracker/logging"
|
||||
)
|
||||
|
||||
//go:embed templates/*
|
||||
var templates embed.FS
|
||||
|
||||
func (h *Server) loadTemplates() {
|
||||
h.tmpl = make(map[string]*template.Template)
|
||||
funcs := template.FuncMap{
|
||||
"add": func(a, b int) int { return a + b },
|
||||
"mult": func(a, b int) int { return a * b },
|
||||
}
|
||||
for _, page := range h.routes {
|
||||
tmpl, err := template.New("").Funcs(funcs).ParseFS(templates,
|
||||
"templates/layout/*.gohtml",
|
||||
"templates/"+page.template+".gohtml",
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
h.tmpl[page.template] = tmpl
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Server) render(fetch fetcher, templateFile string, templateEntry string) http.HandlerFunc {
|
||||
func (h *Server) render(fetch fetcher) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
data, code, err := fetch(r)
|
||||
component, code, err := fetch(r)
|
||||
if err != nil {
|
||||
logging.ErrorPage(err, code).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
err = h.tmpl[templateFile].ExecuteTemplate(w, templateEntry, data)
|
||||
w.Header().Set("content-type", "text/html")
|
||||
err = component.Render(r.Context(), w)
|
||||
if err != nil {
|
||||
logging.ErrorPage(err, http.StatusInternalServerError).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("content-type", "text/html")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
{{define "title"}}
|
||||
Ciasto {{if .ID}}#{{.ID}}{{else}}nowe{{end}}
|
||||
{{end}}
|
||||
{{define "main"}}
|
||||
<div class="form-card">
|
||||
<section class="card">
|
||||
<div class="card-header">
|
||||
<h2>
|
||||
<span class="material-symbols-outlined">cake</span>
|
||||
{{if .ID}}Edytuj ciasto #{{.ID}}{{else}}Nowe ciasto{{end}}
|
||||
</h2>
|
||||
</div>
|
||||
<form method="post" class="card-body">
|
||||
<input type="hidden" name="id" value="{{.ID}}">
|
||||
<div class="form-group">
|
||||
<label>Nazwa *</label>
|
||||
<input class="form-input" type="text" name="name" value="{{.Name}}" placeholder="Nazwa ciasta">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Cena</label>
|
||||
<input class="form-input" type="number" name="price" min="0" value="{{.Price}}">
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<a href="/cakes" class="btn btn-outline">Anuluj</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="material-symbols-outlined">save</span>
|
||||
Zapisz ciasto
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
{{end}}
|
||||
46
server/templates/cake.templ
Normal file
46
server/templates/cake.templ
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"git.bronku.xyz/bronku/cake-order-tracker/models"
|
||||
)
|
||||
|
||||
templ CakePage(cake models.Cake) {
|
||||
@Layout(cakeTitle(cake), CakeForm(cake))
|
||||
}
|
||||
|
||||
templ CakeForm(cake models.Cake) {
|
||||
<div class="form-card">
|
||||
<section class="card">
|
||||
<div class="card-header">
|
||||
<h2>
|
||||
<span class="material-symbols-outlined">cake</span>
|
||||
if cake.ID != 0 {
|
||||
Edytuj ciasto #{ strconv.Itoa(cake.ID) }
|
||||
} else {
|
||||
Nowe ciasto
|
||||
}
|
||||
</h2>
|
||||
</div>
|
||||
<form method="post" class="card-body">
|
||||
<input type="hidden" name="id" value={ strconv.Itoa(cake.ID) } />
|
||||
<div class="form-group">
|
||||
<label>Nazwa *</label>
|
||||
<input class="form-input" type="text" name="name" value={ cake.Name } placeholder="Nazwa ciasta" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Cena</label>
|
||||
<input class="form-input" type="number" name="price" min="0" value={ strconv.Itoa(cake.Price) } />
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<a href="/cakes" class="btn btn-outline">Anuluj</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="material-symbols-outlined">save</span>
|
||||
Zapisz ciasto
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
{{define "title"}}
|
||||
Katalog
|
||||
{{end}}
|
||||
{{define "main"}}
|
||||
<section class="card">
|
||||
<div class="card-header">
|
||||
<h2>
|
||||
<span class="material-symbols-outlined">inventory_2</span>
|
||||
Zarządzanie katalogiem
|
||||
</h2>
|
||||
<a href="/cake" class="btn btn-primary btn-sm">
|
||||
<span class="material-symbols-outlined">add</span>
|
||||
Nowe ciasto
|
||||
</a>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:32px"></th>
|
||||
<th>Nazwa</th>
|
||||
<th>SKU</th>
|
||||
<th class="right">Cena bazowa</th>
|
||||
<th class="center"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .}}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="catalogue-item-icon icon-cake" style="width:28px;height:28px">
|
||||
<span class="material-symbols-outlined" style="font-size:14px">cake</span>
|
||||
</div>
|
||||
</td>
|
||||
<td style="font-weight:500">{{.Name}}</td>
|
||||
<td class="text-on-surface-variant">#{{.ID}}</td>
|
||||
<td class="text-right">{{.Price}} PLN</td>
|
||||
<td class="text-center">
|
||||
<a href="/cake/{{.ID}}" class="action-btn">
|
||||
<span class="material-symbols-outlined">edit</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="table-footer">
|
||||
<span>Pokazuje 1-{{len .}} z {{len .}}</span>
|
||||
<div class="pagination">
|
||||
<button class="pagination-btn" disabled>
|
||||
<span class="material-symbols-outlined">chevron_left</span>
|
||||
</button>
|
||||
<button class="pagination-btn active">1</button>
|
||||
<button class="pagination-btn" disabled>
|
||||
<span class="material-symbols-outlined">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
70
server/templates/cakes.templ
Normal file
70
server/templates/cakes.templ
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"git.bronku.xyz/bronku/cake-order-tracker/models"
|
||||
)
|
||||
|
||||
templ CakesPage(cakes []models.Cake) {
|
||||
@Layout("Katalog", CakesMain(cakes))
|
||||
}
|
||||
|
||||
templ CakesMain(cakes []models.Cake) {
|
||||
<section class="card">
|
||||
<div class="card-header">
|
||||
<h2>
|
||||
<span class="material-symbols-outlined">inventory_2</span>
|
||||
Zarządzanie katalogiem
|
||||
</h2>
|
||||
<a href="/cake" class="btn btn-primary btn-sm">
|
||||
<span class="material-symbols-outlined">add</span>
|
||||
Nowe ciasto
|
||||
</a>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:32px"></th>
|
||||
<th>Nazwa</th>
|
||||
<th>SKU</th>
|
||||
<th class="right">Cena bazowa</th>
|
||||
<th class="center"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
for _, c := range cakes {
|
||||
<tr>
|
||||
<td>
|
||||
<div class="catalogue-item-icon icon-cake" style="width:28px;height:28px">
|
||||
<span class="material-symbols-outlined" style="font-size:14px">cake</span>
|
||||
</div>
|
||||
</td>
|
||||
<td style="font-weight:500">{ c.Name }</td>
|
||||
<td class="text-on-surface-variant">#{ strconv.Itoa(c.ID) }</td>
|
||||
<td class="text-right">{ strconv.Itoa(c.Price) } PLN</td>
|
||||
<td class="text-center">
|
||||
<a href={ templ.URL("/cake/" + strconv.Itoa(c.ID)) } class="action-btn">
|
||||
<span class="material-symbols-outlined">edit</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="table-footer">
|
||||
<span>Pokazuje 1-{ strconv.Itoa(len(cakes)) } z { strconv.Itoa(len(cakes)) }</span>
|
||||
<div class="pagination">
|
||||
<button class="pagination-btn" disabled>
|
||||
<span class="material-symbols-outlined">chevron_left</span>
|
||||
</button>
|
||||
<button class="pagination-btn active">1</button>
|
||||
<button class="pagination-btn" disabled>
|
||||
<span class="material-symbols-outlined">chevron_right</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
{{define "title"}}
|
||||
Potwierdzone
|
||||
{{end}}
|
||||
{{define "main"}}
|
||||
<div class="confirmation">
|
||||
<span class="material-symbols-outlined" style="font-size:48px;color:var(--success-text)">check_circle</span>
|
||||
<h2>{{.ID}} — zapisano</h2>
|
||||
<a href="/orders" class="btn btn-primary" style="margin-top:16px">
|
||||
<span class="material-symbols-outlined">arrow_back</span>
|
||||
Powrót do zamówień
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
18
server/templates/confirmation.templ
Normal file
18
server/templates/confirmation.templ
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package templates
|
||||
|
||||
import "strconv"
|
||||
|
||||
templ Confirmation(title string, id int) {
|
||||
@Layout(title, ConfirmationBody(id))
|
||||
}
|
||||
|
||||
templ ConfirmationBody(id int) {
|
||||
<div class="confirmation">
|
||||
<span class="material-symbols-outlined" style="font-size:48px;color:var(--success-text)">check_circle</span>
|
||||
<h2>{ strconv.Itoa(id) } — zapisano</h2>
|
||||
<a href="/orders" class="btn btn-primary" style="margin-top:16px">
|
||||
<span class="material-symbols-outlined">arrow_back</span>
|
||||
Powrót do zamówień
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
84
server/templates/helpers.go
Normal file
84
server/templates/helpers.go
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.bronku.xyz/bronku/cake-order-tracker/models"
|
||||
)
|
||||
|
||||
func cakeTitle(cake models.Cake) string {
|
||||
if cake.ID != 0 {
|
||||
return "Ciasto #" + strconv.Itoa(cake.ID)
|
||||
}
|
||||
return "Ciasto nowe"
|
||||
}
|
||||
|
||||
func orderTitle(order models.Order) string {
|
||||
if order.ID != 0 {
|
||||
return "Edytuj zamówienie #" + strconv.Itoa(order.ID)
|
||||
}
|
||||
return "Nowe zamówienie"
|
||||
}
|
||||
|
||||
func orderSubtotal(order models.Order) int {
|
||||
sub := 0
|
||||
for _, c := range order.Cakes {
|
||||
sub += c.Price * c.Amount
|
||||
}
|
||||
return sub
|
||||
}
|
||||
|
||||
func countPending(orders []models.Order) int {
|
||||
n := 0
|
||||
for _, o := range orders {
|
||||
if o.Status != "done" {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func countDone(orders []models.Order) int {
|
||||
n := 0
|
||||
for _, o := range orders {
|
||||
if o.Status == "done" {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func estimateRevenue(orders []models.Order) int {
|
||||
total := 0
|
||||
for _, o := range orders {
|
||||
for _, c := range o.Cakes {
|
||||
total += c.Price * c.Amount
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func cakeItemsDesc(cakes []models.Cake) string {
|
||||
desc := ""
|
||||
for i, c := range cakes {
|
||||
if i > 0 {
|
||||
desc += ", "
|
||||
}
|
||||
desc += c.Name + " (x" + strconv.Itoa(c.Amount) + ")"
|
||||
}
|
||||
return desc
|
||||
}
|
||||
|
||||
func orderScript(order models.Order) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("<script>")
|
||||
if order.ID != 0 {
|
||||
for _, c := range order.Cakes {
|
||||
b.WriteString(fmt.Sprintf("updateCatalogueQty(%d,%d);", c.ID, c.Amount))
|
||||
}
|
||||
}
|
||||
b.WriteString("updateTotals();</script>")
|
||||
return b.String()
|
||||
}
|
||||
43
server/templates/layout.templ
Normal file
43
server/templates/layout.templ
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
package templates
|
||||
|
||||
templ Layout(title string, main templ.Component) {
|
||||
<!doctype html>
|
||||
<html lang="pl">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{ title } — Zamówienia ciast</title>
|
||||
<link rel="stylesheet" href="/static/fonts/inter.css" />
|
||||
<link rel="stylesheet" href="/static/fonts/material.css" />
|
||||
<script src="/static/htmx2.04.js" defer></script>
|
||||
<script src="/static/order.js"></script>
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<aside class="sidebar">
|
||||
@Nav()
|
||||
</aside>
|
||||
<div class="app-body">
|
||||
<header class="topnav">
|
||||
<div class="topnav-left">
|
||||
<h1 class="topnav-title">{ title }</h1>
|
||||
</div>
|
||||
</header>
|
||||
<div class="main-content">
|
||||
@main
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
var path = window.location.pathname;
|
||||
var links = document.querySelectorAll('.sidebar-link');
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
if (links[i].getAttribute('href') === path) {
|
||||
links[i].classList.add('active');
|
||||
}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
{{define "layout"}}
|
||||
<!doctype html>
|
||||
<html lang="pl">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{{template "title" .}} — Zamówienia ciast</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,0&display=swap" rel="stylesheet">
|
||||
<script src="/static/htmx2.04.js" defer></script>
|
||||
<script src="/static/order.js"></script>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<aside class="sidebar">
|
||||
{{template "nav" .}}
|
||||
</aside>
|
||||
<div class="app-body">
|
||||
<header class="topnav">
|
||||
<div class="topnav-left">
|
||||
<h1 class="topnav-title">{{template "title" .}}</h1>
|
||||
</div>
|
||||
</header>
|
||||
<div class="main-content">
|
||||
{{template "main" .}}
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
var path = window.location.pathname;
|
||||
var links = document.querySelectorAll('.sidebar-link');
|
||||
for (var i = 0; i < links.length; i++) {
|
||||
if (links[i].getAttribute('href') === path) {
|
||||
links[i].classList.add('active');
|
||||
}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
{{define "nav"}}
|
||||
<a href="/order" class="sidebar-cta">
|
||||
<span class="material-symbols-outlined">add</span>
|
||||
<span>Nowe zamówienie</span>
|
||||
</a>
|
||||
<div class="sidebar-nav">
|
||||
<a href="/orders" class="sidebar-link">
|
||||
<span class="material-symbols-outlined">dashboard</span>
|
||||
<span>Panel główny</span>
|
||||
</a>
|
||||
<a href="/cakes" class="sidebar-link">
|
||||
<span class="material-symbols-outlined">inventory_2</span>
|
||||
<span>Katalog</span>
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
18
server/templates/nav.templ
Normal file
18
server/templates/nav.templ
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package templates
|
||||
|
||||
templ Nav() {
|
||||
<a href="/order" class="sidebar-cta">
|
||||
<span class="material-symbols-outlined">add</span>
|
||||
<span>Nowe zamówienie</span>
|
||||
</a>
|
||||
<div class="sidebar-nav">
|
||||
<a href="/orders" class="sidebar-link">
|
||||
<span class="material-symbols-outlined">dashboard</span>
|
||||
<span>Panel główny</span>
|
||||
</a>
|
||||
<a href="/cakes" class="sidebar-link">
|
||||
<span class="material-symbols-outlined">inventory_2</span>
|
||||
<span>Katalog</span>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
{{define "title"}}
|
||||
{{if .Order.ID}}
|
||||
Edytuj zamówienie #{{.Order.ID}}
|
||||
{{else}}
|
||||
Nowe zamówienie
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{define "main"}}
|
||||
<form method="post" class="split-pane" style="flex:1;min-height:0">
|
||||
{{$catalogue := .Catalogue}}
|
||||
{{with .Order}}
|
||||
<input type="hidden" name="id" value="{{.ID}}">
|
||||
<div class="split-pane-left">
|
||||
<section class="card">
|
||||
<div class="card-header">
|
||||
<h2>
|
||||
<span class="material-symbols-outlined">person_add</span>
|
||||
Dane klienta
|
||||
</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label>Imię *</label>
|
||||
<input class="form-input" type="text" name="name" value="{{.Name}}" placeholder="Jan">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Nazwisko *</label>
|
||||
<input class="form-input" type="text" name="surname" value="{{.Surname}}" placeholder="Kowalski">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Telefon</label>
|
||||
<input class="form-input" type="tel" name="phone" value="{{.Phone}}" placeholder="+48 123 456 789">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Lokalizacja</label>
|
||||
<select class="form-input" name="location">
|
||||
<option {{if eq .Location "Kartuzy"}}selected{{end}} value="Kartuzy">Kartuzy</option>
|
||||
<option {{if eq .Location "Somonino"}}selected{{end}} value="Somonino">Somonino</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Data dostawy *</label>
|
||||
<input class="form-input" type="date" name="date" value="{{.Date.Format "2006-01-02"}}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Zaliczka</label>
|
||||
<input class="form-input" type="number" name="paid" min="0" value="{{.Paid}}">
|
||||
</div>
|
||||
<div class="form-group" style="grid-column:span 2">
|
||||
<label>Uwagi</label>
|
||||
<input class="form-input" type="text" placeholder="Alergie, specjalne życzenia...">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="card" style="flex:1;min-height:200px">
|
||||
<div class="card-header">
|
||||
<h2>
|
||||
<span class="material-symbols-outlined">inventory_2</span>
|
||||
Katalog produktów
|
||||
</h2>
|
||||
<div class="topnav-search" style="width:180px">
|
||||
<span class="material-symbols-outlined" style="left:6px;font-size:14px">search</span>
|
||||
<input class="form-input" style="height:28px;padding-left:26px;font-size:12px" type="text" placeholder="Filtruj produkty..." oninput="filterCatalogue(this)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body" style="overflow-y:auto">
|
||||
<div class="catalogue-grid" id="catalogue-grid">
|
||||
{{range $catalogue}}
|
||||
<div class="catalogue-item" data-name="{{.Name}}">
|
||||
<div class="catalogue-item-top">
|
||||
<div class="catalogue-item-icon icon-cake">
|
||||
<span class="material-symbols-outlined" style="font-size:16px">cake</span>
|
||||
</div>
|
||||
<div style="min-width:0">
|
||||
<div class="catalogue-item-name">{{.Name}}</div>
|
||||
<div class="catalogue-item-price">{{.Price}} PLN</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="catalogue-item-actions">
|
||||
<button type="button" class="qty-btn qty-minus" onclick="changeCake({{.ID}},'{{.Name}}',{{.Price}},-1)">
|
||||
<span class="material-symbols-outlined">remove</span>
|
||||
</button>
|
||||
<span class="qty-value" id="qty-{{.ID}}">0</span>
|
||||
<button type="button" class="qty-btn qty-plus" onclick="changeCake({{.ID}},'{{.Name}}',{{.Price}},1)">
|
||||
<span class="material-symbols-outlined">add</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<aside class="split-pane-right">
|
||||
<section class="card" style="flex:1;display:flex;flex-direction:column">
|
||||
<div class="card-header">
|
||||
<h2>
|
||||
<span class="material-symbols-outlined">receipt_long</span>
|
||||
Podsumowanie
|
||||
</h2>
|
||||
</div>
|
||||
<div class="summary-lineitems" id="basket_items">
|
||||
{{$sub := 0}}
|
||||
{{range .Cakes}}
|
||||
{{$sub = add $sub (mult .Price .Amount)}}
|
||||
<div class="summary-item" data-cake-id="{{.ID}}">
|
||||
<div class="summary-item-info">
|
||||
<div class="summary-item-name cake-name">{{.Name}}</div>
|
||||
<div class="summary-item-detail cake-price">{{.Price}} PLN × <span class="cake-qty">{{.Amount}}</span></div>
|
||||
</div>
|
||||
<div class="summary-item-price cake-total">{{.Total}} PLN</div>
|
||||
<button type="button" class="summary-item-remove" onclick="removeCake(this)">
|
||||
<span class="material-symbols-outlined">close</span>
|
||||
</button>
|
||||
<input type="hidden" name="cake[{{.ID}}]" value="{{.Amount}}">
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="summary-totals">
|
||||
<div class="summary-total-row">
|
||||
<span>Subtotal</span>
|
||||
<span id="subtotal-price">{{$sub}} PLN</span>
|
||||
</div>
|
||||
<div class="summary-total-row">
|
||||
<span>Zaliczka</span>
|
||||
<span id="paid-amount">{{.Paid}} PLN</span>
|
||||
</div>
|
||||
<div class="summary-total-row total">
|
||||
<span>Do zapłaty</span>
|
||||
<span id="total-price">{{add $sub (mult .Paid -1)}} PLN</span>
|
||||
</div>
|
||||
<button type="submit" class="btn-finalize">
|
||||
<span class="material-symbols-outlined">check_circle</span>
|
||||
Finalizuj zamówienie
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</aside>
|
||||
{{end}}
|
||||
</form>
|
||||
<script>
|
||||
{{if .Order.ID}}
|
||||
{{range .Order.Cakes}}
|
||||
updateCatalogueQty({{.ID}}, {{.Amount}});
|
||||
{{end}}
|
||||
{{end}}
|
||||
updateTotals();
|
||||
</script>
|
||||
{{end}}
|
||||
144
server/templates/order.templ
Normal file
144
server/templates/order.templ
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"git.bronku.xyz/bronku/cake-order-tracker/models"
|
||||
)
|
||||
|
||||
templ OrderPage(order models.Order, catalogue []models.Cake) {
|
||||
@Layout(orderTitle(order), OrderForm(order, catalogue))
|
||||
}
|
||||
|
||||
templ OrderForm(order models.Order, catalogue []models.Cake) {
|
||||
<form method="post" class="split-pane" style="flex:1;min-height:0">
|
||||
<input type="hidden" name="id" value={ strconv.Itoa(order.ID) } />
|
||||
<div class="split-pane-left">
|
||||
<section class="card">
|
||||
<div class="card-header">
|
||||
<h2>
|
||||
<span class="material-symbols-outlined">person_add</span>
|
||||
Dane klienta
|
||||
</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label>Imię *</label>
|
||||
<input class="form-input" type="text" name="name" value={ order.Name } placeholder="Jan" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Nazwisko *</label>
|
||||
<input class="form-input" type="text" name="surname" value={ order.Surname } placeholder="Kowalski" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Telefon</label>
|
||||
<input class="form-input" type="tel" name="phone" value={ order.Phone } placeholder="+48 123 456 789" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Lokalizacja</label>
|
||||
<select class="form-input" name="location">
|
||||
<option if order.Location == "Kartuzy" { selected } value="Kartuzy">Kartuzy</option>
|
||||
<option if order.Location == "Somonino" { selected } value="Somonino">Somonino</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Data dostawy *</label>
|
||||
<input class="form-input" type="date" name="date" value={ order.Date.Format("2006-01-02") } />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Zaliczka</label>
|
||||
<input class="form-input" type="number" name="paid" min="0" value={ strconv.Itoa(order.Paid) } />
|
||||
</div>
|
||||
<div class="form-group" style="grid-column:span 2">
|
||||
<label>Uwagi</label>
|
||||
<input class="form-input" type="text" placeholder="Alergie, specjalne życzenia..." />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="card" style="flex:1;min-height:200px">
|
||||
<div class="card-header">
|
||||
<h2>
|
||||
<span class="material-symbols-outlined">inventory_2</span>
|
||||
Katalog produktów
|
||||
</h2>
|
||||
<div class="topnav-search" style="width:180px">
|
||||
<span class="material-symbols-outlined" style="left:6px;font-size:14px">search</span>
|
||||
<input class="form-input" style="height:28px;padding-left:26px;font-size:12px" type="text" placeholder="Filtruj produkty..." oninput="filterCatalogue(this)" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body" style="overflow-y:auto">
|
||||
<div class="catalogue-grid" id="catalogue-grid">
|
||||
for _, c := range catalogue {
|
||||
<div class="catalogue-item" data-name={ c.Name }>
|
||||
<div class="catalogue-item-top">
|
||||
<div class="catalogue-item-icon icon-cake">
|
||||
<span class="material-symbols-outlined" style="font-size:16px">cake</span>
|
||||
</div>
|
||||
<div style="min-width:0">
|
||||
<div class="catalogue-item-name">{ c.Name }</div>
|
||||
<div class="catalogue-item-price">{ strconv.Itoa(c.Price) } PLN</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="catalogue-item-actions">
|
||||
<button type="button" class="qty-btn qty-minus" onclick={ templ.JSFuncCall("changeCake", c.ID, c.Name, c.Price, -1) }>
|
||||
<span class="material-symbols-outlined">remove</span>
|
||||
</button>
|
||||
<span class="qty-value" id={ "qty-" + strconv.Itoa(c.ID) }>0</span>
|
||||
<button type="button" class="qty-btn qty-plus" onclick={ templ.JSFuncCall("changeCake", c.ID, c.Name, c.Price, 1) }>
|
||||
<span class="material-symbols-outlined">add</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<aside class="split-pane-right">
|
||||
<section class="card" style="flex:1;display:flex;flex-direction:column">
|
||||
<div class="card-header">
|
||||
<h2>
|
||||
<span class="material-symbols-outlined">receipt_long</span>
|
||||
Podsumowanie
|
||||
</h2>
|
||||
</div>
|
||||
<div class="summary-lineitems" id="basket_items">
|
||||
for _, c := range order.Cakes {
|
||||
<div class="summary-item" data-cake-id={ strconv.Itoa(c.ID) }>
|
||||
<div class="summary-item-info">
|
||||
<div class="summary-item-name cake-name">{ c.Name }</div>
|
||||
<div class="summary-item-detail cake-price">{ strconv.Itoa(c.Price) } PLN × <span class="cake-qty">{ strconv.Itoa(c.Amount) }</span></div>
|
||||
</div>
|
||||
<div class="summary-item-price cake-total">{ strconv.Itoa(c.Total()) } PLN</div>
|
||||
<button type="button" class="summary-item-remove" onclick="removeCake(this)">
|
||||
<span class="material-symbols-outlined">close</span>
|
||||
</button>
|
||||
<input type="hidden" name={ "cake[" + strconv.Itoa(c.ID) + "]" } value={ strconv.Itoa(c.Amount) } />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="summary-totals">
|
||||
<div class="summary-total-row">
|
||||
<span>Subtotal</span>
|
||||
<span id="subtotal-price">{ strconv.Itoa(orderSubtotal(order)) } PLN</span>
|
||||
</div>
|
||||
<div class="summary-total-row">
|
||||
<span>Zaliczka</span>
|
||||
<span id="paid-amount">{ strconv.Itoa(order.Paid) } PLN</span>
|
||||
</div>
|
||||
<div class="summary-total-row total">
|
||||
<span>Do zapłaty</span>
|
||||
<span id="total-price">{ strconv.Itoa(orderSubtotal(order) - order.Paid) } PLN</span>
|
||||
</div>
|
||||
<button type="submit" class="btn-finalize">
|
||||
<span class="material-symbols-outlined">check_circle</span>
|
||||
Finalizuj zamówienie
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</aside>
|
||||
</form>
|
||||
@templ.Raw(orderScript(order))
|
||||
}
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
{{define "title"}}
|
||||
Panel główny
|
||||
{{end}}
|
||||
{{define "main"}}
|
||||
<div class="metrics-grid">
|
||||
<div class="metric-card">
|
||||
<span class="metric-label">
|
||||
<span class="material-symbols-outlined">receipt_long</span>
|
||||
Zamówienia dzisiaj
|
||||
</span>
|
||||
<span class="metric-value">{{len .Orders}}</span>
|
||||
<span class="metric-change positive">
|
||||
<span class="material-symbols-outlined">arrow_upward</span>
|
||||
Wszystkie aktywne
|
||||
</span>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span class="metric-label">
|
||||
<span class="material-symbols-outlined">cake</span>
|
||||
Do realizacji
|
||||
</span>
|
||||
<span class="metric-value">
|
||||
{{$pending := 0}}
|
||||
{{range .Orders}}{{if ne .Status "done"}}{{$pending = add $pending 1}}{{end}}{{end}}
|
||||
{{$pending}}
|
||||
</span>
|
||||
<span class="metric-change warning">
|
||||
<span class="material-symbols-outlined">priority_high</span>
|
||||
Oczekujące
|
||||
</span>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span class="metric-label">
|
||||
<span class="material-symbols-outlined">check_circle</span>
|
||||
Gotowe do odbioru
|
||||
</span>
|
||||
<span class="metric-value">
|
||||
{{$done := 0}}
|
||||
{{range .Orders}}{{if eq .Status "done"}}{{$done = add $done 1}}{{end}}{{end}}
|
||||
{{$done}}
|
||||
</span>
|
||||
<span class="metric-change">Zrealizowane</span>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span class="metric-label">
|
||||
<span class="material-symbols-outlined">payments</span>
|
||||
Przychód (szac.)
|
||||
</span>
|
||||
<span class="metric-value">
|
||||
{{$total := 0}}
|
||||
{{range .Orders}}{{range .Cakes}}{{$total = add $total (mult .Price .Amount)}}{{end}}{{end}}
|
||||
{{$total}} PLN
|
||||
</span>
|
||||
<span class="metric-change positive">
|
||||
<span class="material-symbols-outlined">trending_up</span>
|
||||
W bieżącym miesiącu
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="card">
|
||||
<div class="card-header">
|
||||
<h2>
|
||||
<span class="material-symbols-outlined">list_alt</span>
|
||||
Aktywne zamówienia
|
||||
</h2>
|
||||
<div style="display:flex;align-items:center;gap:8px">
|
||||
<form hx-get="/orders/search" hx-target="#results-table" hx-trigger="keyup delay:500ms, change" style="display:flex;align-items:center;gap:8px">
|
||||
<div class="form-group" style="flex-direction:row;align-items:center;gap:4px">
|
||||
<label style="font-size:11px;white-space:nowrap">Od</label>
|
||||
<input class="form-input" style="width:140px" type="date" value="{{.First}}" name="from">
|
||||
</div>
|
||||
<div class="form-group" style="flex-direction:row;align-items:center;gap:4px">
|
||||
<label style="font-size:11px;white-space:nowrap">Do</label>
|
||||
<input class="form-input" style="width:140px" type="date" value="{{.Last}}" name="to">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-container" id="results-table">
|
||||
{{template "orders-table" .Orders}}
|
||||
</div>
|
||||
</section>
|
||||
{{end}}
|
||||
{{define "orders-table"}}
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID zamówienia</th>
|
||||
<th>Klient</th>
|
||||
<th>Data odbioru</th>
|
||||
<th>Pozycje</th>
|
||||
<th class="right">Suma</th>
|
||||
<th class="center">Status</th>
|
||||
<th class="center"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .}}
|
||||
<tr>
|
||||
<td><a href="/order/{{.ID}}" class="order-id">#{{.ID}}</a></td>
|
||||
<td class="customer-name">{{.Surname}} {{.Name}}<br><span class="text-on-surface-variant" style="font-size:11px;font-weight:400">{{.Phone}}</span></td>
|
||||
<td class="text-on-surface-variant">{{.Date.Format "2006-01-02 15:04"}}</td>
|
||||
<td>
|
||||
<span class="truncate" title="{{range .Cakes}}{{.Name}} (x{{.Amount}}), {{end}}">
|
||||
{{range $i, $c := .Cakes}}{{if $i}}, {{end}}{{.Name}} (x{{.Amount}}){{end}}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-right" style="font-weight:700">{{.Total}} PLN</td>
|
||||
<td class="text-center">
|
||||
<span class="status-chip {{.Status}}">
|
||||
{{if eq .Status "accepted"}}Przyjęte
|
||||
{{else if eq .Status "done"}}Gotowe
|
||||
{{else}}Oczekujące{{end}}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="/order/{{.ID}}" class="action-btn">
|
||||
<span class="material-symbols-outlined">edit</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
||||
133
server/templates/orders.templ
Normal file
133
server/templates/orders.templ
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"git.bronku.xyz/bronku/cake-order-tracker/models"
|
||||
)
|
||||
|
||||
templ OrdersPage(first, last string, orders []models.Order) {
|
||||
@Layout("Panel główny", OrdersMain(first, last, orders))
|
||||
}
|
||||
|
||||
templ OrdersMain(first, last string, orders []models.Order) {
|
||||
<div class="metrics-grid">
|
||||
<div class="metric-card">
|
||||
<span class="metric-label">
|
||||
<span class="material-symbols-outlined">receipt_long</span>
|
||||
Zamówienia dzisiaj
|
||||
</span>
|
||||
<span class="metric-value">{ len(orders) }</span>
|
||||
<span class="metric-change positive">
|
||||
<span class="material-symbols-outlined">arrow_upward</span>
|
||||
Wszystkie aktywne
|
||||
</span>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span class="metric-label">
|
||||
<span class="material-symbols-outlined">cake</span>
|
||||
Do realizacji
|
||||
</span>
|
||||
<span class="metric-value">{ strconv.Itoa(countPending(orders)) }</span>
|
||||
<span class="metric-change warning">
|
||||
<span class="material-symbols-outlined">priority_high</span>
|
||||
Oczekujące
|
||||
</span>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span class="metric-label">
|
||||
<span class="material-symbols-outlined">check_circle</span>
|
||||
Gotowe do odbioru
|
||||
</span>
|
||||
<span class="metric-value">{ strconv.Itoa(countDone(orders)) }</span>
|
||||
<span class="metric-change">Zrealizowane</span>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<span class="metric-label">
|
||||
<span class="material-symbols-outlined">payments</span>
|
||||
Przychód (szac.)
|
||||
</span>
|
||||
<span class="metric-value">{ strconv.Itoa(estimateRevenue(orders)) } PLN</span>
|
||||
<span class="metric-change positive">
|
||||
<span class="material-symbols-outlined">trending_up</span>
|
||||
W bieżącym miesiącu
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="card">
|
||||
<div class="card-header">
|
||||
<h2>
|
||||
<span class="material-symbols-outlined">list_alt</span>
|
||||
Aktywne zamówienia
|
||||
</h2>
|
||||
<div style="display:flex;align-items:center;gap:8px">
|
||||
<form hx-get="/orders/search" hx-target="#results-table" hx-trigger="keyup delay:500ms, change" style="display:flex;align-items:center;gap:8px">
|
||||
<div class="form-group" style="flex-direction:row;align-items:center;gap:4px">
|
||||
<label style="font-size:11px;white-space:nowrap">Od</label>
|
||||
<input class="form-input" style="width:140px" type="date" value={ first } name="from">
|
||||
</div>
|
||||
<div class="form-group" style="flex-direction:row;align-items:center;gap:4px">
|
||||
<label style="font-size:11px;white-space:nowrap">Do</label>
|
||||
<input class="form-input" style="width:140px" type="date" value={ last } name="to">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-container" id="results-table">
|
||||
@OrdersTable(orders)
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
templ OrdersTable(orders []models.Order) {
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID zamówienia</th>
|
||||
<th>Klient</th>
|
||||
<th>Data odbioru</th>
|
||||
<th>Pozycje</th>
|
||||
<th class="right">Suma</th>
|
||||
<th class="center">Status</th>
|
||||
<th class="center"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
for _, o := range orders {
|
||||
<tr>
|
||||
<td><a href={ templ.URL("/order/" + strconv.Itoa(o.ID)) } class="order-id">#{ strconv.Itoa(o.ID) }</a></td>
|
||||
<td class="customer-name">{ o.Surname } { o.Name }<br /><span class="text-on-surface-variant" style="font-size:11px;font-weight:400">{ o.Phone }</span></td>
|
||||
<td class="text-on-surface-variant">{ o.Date.Format("2006-01-02 15:04") }</td>
|
||||
<td>
|
||||
<span class="truncate" title={ cakeItemsDesc(o.Cakes) }>
|
||||
for i, c := range o.Cakes {
|
||||
if i > 0 {
|
||||
{ ", " }
|
||||
}
|
||||
{ c.Name } (x{ strconv.Itoa(c.Amount) })
|
||||
}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-right" style="font-weight:700">{ strconv.Itoa(o.Total()) } PLN</td>
|
||||
<td class="text-center">
|
||||
<span class={ "status-chip " + o.Status }>
|
||||
if o.Status == "accepted" {
|
||||
Przyjęte
|
||||
} else if o.Status == "done" {
|
||||
Gotowe
|
||||
} else {
|
||||
Oczekujące
|
||||
}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href={ templ.URL("/order/" + strconv.Itoa(o.ID)) } class="action-btn">
|
||||
<span class="material-symbols-outlined">edit</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue