feat(budget): further improvements
Some checks failed
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Failing after 1m11s
Some checks failed
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Failing after 1m11s
This commit is contained in:
@@ -2,7 +2,6 @@ package budget
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"spend-sparrow/internal/core"
|
"spend-sparrow/internal/core"
|
||||||
@@ -33,6 +32,7 @@ func NewHandler(s Service, r *core.Render) Handler {
|
|||||||
|
|
||||||
func (h HandlerImpl) Handle(r *http.ServeMux) {
|
func (h HandlerImpl) Handle(r *http.ServeMux) {
|
||||||
r.Handle("GET /budget", h.handlePage())
|
r.Handle("GET /budget", h.handlePage())
|
||||||
|
r.Handle("GET /budget/new", h.handleNew())
|
||||||
r.Handle("GET /budget/{id}", h.handleEdit())
|
r.Handle("GET /budget/{id}", h.handleEdit())
|
||||||
r.Handle("POST /budget/{id}", h.handlePost())
|
r.Handle("POST /budget/{id}", h.handlePost())
|
||||||
// r.Handle("DELETE /budget/{id}", h.handleDelete())
|
// r.Handle("DELETE /budget/{id}", h.handleDelete())
|
||||||
@@ -59,7 +59,49 @@ func (h HandlerImpl) handlePage() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h HandlerImpl) handleNew() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
core.UpdateSpan(r)
|
||||||
|
|
||||||
|
user := core.GetUser(r)
|
||||||
|
if user == nil {
|
||||||
|
core.DoRedirect(w, r, "/auth/signin")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
comp := editNew()
|
||||||
|
h.r.RenderLayout(r, w, comp, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (h HandlerImpl) handleEdit() http.HandlerFunc {
|
func (h HandlerImpl) handleEdit() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
core.UpdateSpan(r)
|
||||||
|
|
||||||
|
user := core.GetUser(r)
|
||||||
|
if user == nil {
|
||||||
|
core.DoRedirect(w, r, "/auth/signin")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := uuid.Parse(r.PathValue("id"))
|
||||||
|
if err != nil {
|
||||||
|
core.HandleError(w, r, fmt.Errorf("could not parse Id: %w", core.ErrBadRequest))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
budget, err := h.s.Get(r.Context(), user, id)
|
||||||
|
if err != nil {
|
||||||
|
core.HandleError(w, r, fmt.Errorf("could not parse Id: %w", core.ErrBadRequest))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
comp := edit(*budget)
|
||||||
|
h.r.RenderLayout(r, w, comp, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HandlerImpl) handlePost() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
core.UpdateSpan(r)
|
core.UpdateSpan(r)
|
||||||
|
|
||||||
@@ -83,35 +125,6 @@ func (h HandlerImpl) handleEdit() http.HandlerFunc {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
comp := editNew()
|
|
||||||
h.r.Render(r, w, comp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h HandlerImpl) handlePost() http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
core.UpdateSpan(r)
|
|
||||||
|
|
||||||
user := core.GetUser(r)
|
|
||||||
if user == nil {
|
|
||||||
core.DoRedirect(w, r, "/auth/signin")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
id uuid.UUID
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
idStr := r.PathValue("id")
|
|
||||||
if idStr != "new" {
|
|
||||||
id, err = uuid.Parse(idStr)
|
|
||||||
if err != nil {
|
|
||||||
core.HandleError(w, r, fmt.Errorf("could not parse Id: %w", core.ErrBadRequest))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
valueF, err := strconv.ParseFloat(r.FormValue("value"), 64)
|
valueF, err := strconv.ParseFloat(r.FormValue("value"), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
core.HandleError(w, r, fmt.Errorf("could not parse value: %w", core.ErrBadRequest))
|
core.HandleError(w, r, fmt.Errorf("could not parse value: %w", core.ErrBadRequest))
|
||||||
@@ -120,9 +133,8 @@ func (h HandlerImpl) handlePost() http.HandlerFunc {
|
|||||||
value := int64(math.Round(valueF * DECIMALS_MULTIPLIER))
|
value := int64(math.Round(valueF * DECIMALS_MULTIPLIER))
|
||||||
|
|
||||||
input := Budget{
|
input := Budget{
|
||||||
Id: id,
|
|
||||||
Value: value,
|
Value: value,
|
||||||
Description: r.FormValue("description"),
|
Description: r.FormValue("name"),
|
||||||
}
|
}
|
||||||
|
|
||||||
var budget *Budget
|
var budget *Budget
|
||||||
@@ -132,17 +144,14 @@ func (h HandlerImpl) handlePost() http.HandlerFunc {
|
|||||||
core.HandleError(w, r, err)
|
core.HandleError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
// } else {
|
||||||
budget, err = h.s.Update(r.Context(), user, input)
|
// budget, err = h.s.Update(r.Context(), user, input)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
core.HandleError(w, r, err)
|
// core.HandleError(w, r, err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// To disable unused variable
|
core.DoRedirect(w, r, "/budget/"+budget.Id.String())
|
||||||
slog.Info("test", "item", budget)
|
|
||||||
// comp := item()
|
|
||||||
// h.r.Render(r, w, comp)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package budget
|
package budget
|
||||||
|
|
||||||
import "spend-sparrow/internal/template/svg"
|
import (
|
||||||
|
"spend-sparrow/internal/core"
|
||||||
|
"spend-sparrow/internal/template/svg"
|
||||||
|
)
|
||||||
|
|
||||||
templ page(budgets []Budget) {
|
templ page(budgets []Budget) {
|
||||||
<!-- for _,name:=range([]string{"Lebensmittel", "Tanken", "Drogerie", "Milch", "Parken"}) { -->
|
@core.Breadcrumb([]string{"Home", "Budget"}, []string{"/", "/budget"})
|
||||||
<div class="flex flex-wrap gap-20 text-xl mt-10 justify-center">
|
<div class="flex flex-wrap gap-20 text-xl mt-10 justify-center">
|
||||||
@newItem()
|
@newItem()
|
||||||
for _,budget:=range(budgets ) {
|
for _,budget:=range(budgets ) {
|
||||||
@@ -13,64 +16,110 @@ templ page(budgets []Budget) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
templ editNew() {
|
templ editNew() {
|
||||||
<dialog id="newBudget" class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-lg openDialogModal">
|
<div class="flex flex-col">
|
||||||
<form
|
@core.Breadcrumb([]string{"Home", "Budget", "New"}, []string{"/", "/budget", "/budget/new"})
|
||||||
hx-post={ "/transaction/new" }
|
<div class="flex justify-center items-center flex-1">
|
||||||
class="flex justify-end gap-4 items-center "
|
<form
|
||||||
>
|
hx-post={ "/budget/new" }
|
||||||
<div class="grid grid-cols-[auto_auto] items-center gap-4 mr-auto">
|
class="grid grid-cols-4 items-center gap-4 mr-auto max-w-2xl h-full"
|
||||||
|
>
|
||||||
<label for="timestamp" class="text-sm text-gray-500">Name</label>
|
<label for="timestamp" class="text-sm text-gray-500">Name</label>
|
||||||
<input
|
<input
|
||||||
autofocus
|
autofocus
|
||||||
name="name"
|
name="name"
|
||||||
type="text"
|
type="text"
|
||||||
class="bg-white input datetime"
|
class="bg-white input datetime col-span-3"
|
||||||
/>
|
/>
|
||||||
<label for="value" class="text-sm text-gray-500">Value</label>
|
<label for="value" class="text-sm text-gray-500">Value</label>
|
||||||
<input
|
<input
|
||||||
name="value"
|
name="value"
|
||||||
type="number"
|
type="number"
|
||||||
class="mr-auto bg-white input"
|
class="bg-white input col-span-3"
|
||||||
/>
|
/>
|
||||||
</div>
|
<div class="flex gap-6 justify-end col-span-4">
|
||||||
<button type="submit" class="button button-neglect px-1 flex items-center gap-2">
|
<a href="/budget" class="col-start-3 p-2 px-4 decoration-yellow-400 decoration-[0.25rem] hover:bg-gray-200 rounded-lg hover:underline flex items-center gap-2 justify-center">
|
||||||
@svg.Save()
|
<span class="h-4 w-4">
|
||||||
<span>
|
@svg.Cancel()
|
||||||
Save
|
</span>
|
||||||
</span>
|
<span>
|
||||||
</button>
|
Cancel
|
||||||
<button commandfor="newBudget" command="close" class="button button-neglect px-1 flex items-center gap-2">
|
</span>
|
||||||
<span class="h-4 w-4">
|
</a>
|
||||||
@svg.Cancel()
|
<button type="submit" class="col-start-4 p-2 px-4 decoration-yellow-400 decoration-[0.25rem] hover:bg-gray-200 rounded-lg hover:underline flex items-center gap-2 justify-center">
|
||||||
</span>
|
@svg.Save()
|
||||||
<span>
|
<span>
|
||||||
Cancel
|
Save
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</div>
|
||||||
</dialog>
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ edit(budget Budget) {
|
||||||
|
<div class="flex flex-col">
|
||||||
|
@core.Breadcrumb([]string{"Home", "Budget", budget.Description}, []string{"/", "/budget", "/budget/" + budget.Id.String()})
|
||||||
|
<div class="flex justify-center items-center flex-1">
|
||||||
|
<form
|
||||||
|
hx-post={ "/budget/new" }
|
||||||
|
class="grid grid-cols-4 items-center gap-4 mr-auto max-w-2xl h-full"
|
||||||
|
>
|
||||||
|
<label for="timestamp" class="text-sm text-gray-500">Name</label>
|
||||||
|
<input
|
||||||
|
autofocus
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
value={ budget.Description }
|
||||||
|
class="bg-white input datetime col-span-3"
|
||||||
|
/>
|
||||||
|
<label for="value" class="text-sm text-gray-500">Value</label>
|
||||||
|
<input
|
||||||
|
name="value"
|
||||||
|
type="number"
|
||||||
|
value={ budget.Value / 100 }
|
||||||
|
class="bg-white input col-span-3"
|
||||||
|
/>
|
||||||
|
<div class="flex gap-6 justify-end col-span-4">
|
||||||
|
<a href="/budget" class="col-start-3 p-2 px-4 decoration-yellow-400 decoration-[0.25rem] hover:bg-gray-200 rounded-lg hover:underline flex items-center gap-2 justify-center">
|
||||||
|
<span class="h-4 w-4">
|
||||||
|
@svg.Cancel()
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
Cancel
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="col-start-4 p-2 px-4 decoration-yellow-400 decoration-[0.25rem] hover:bg-gray-200 rounded-lg hover:underline flex items-center gap-2 justify-center">
|
||||||
|
@svg.Save()
|
||||||
|
<span>
|
||||||
|
Save
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
templ newItem() {
|
templ newItem() {
|
||||||
<button
|
<a
|
||||||
hx-get="/budget/new"
|
href="/budget/new"
|
||||||
hx-target="#dialogContainer"
|
|
||||||
class="p-5 w-64 h-64 flex gap-10 active:bg-gray-200 flex-col justify-center items-center hover:bg-gray-100 transition-all cursor-pointer rounded-lg bg-gray-50 border-1 border-gray-300"
|
class="p-5 w-64 h-64 flex gap-10 active:bg-gray-200 flex-col justify-center items-center hover:bg-gray-100 transition-all cursor-pointer rounded-lg bg-gray-50 border-1 border-gray-300"
|
||||||
>
|
>
|
||||||
New Budget
|
New Budget
|
||||||
<div class="w-10">
|
<div class="w-10">
|
||||||
@svg.Plus()
|
@svg.Plus()
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</a>
|
||||||
}
|
}
|
||||||
|
|
||||||
templ item(budget Budget) {
|
templ item(budget Budget) {
|
||||||
<section class="flex flex-col w-64 h-64 p-5 rounded-lg bg-gray-50 border-1 border-gray-300 ">
|
<a href={ "/budget/" + budget.Id.String() } class="flex flex-col w-64 h-64 p-5 rounded-lg bg-gray-50 border-1 border-gray-300 ">
|
||||||
<span>
|
<span>
|
||||||
{ budget.Description }
|
{ budget.Description }
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
{ 200 }€
|
{ 200 }€
|
||||||
</span>
|
</span>
|
||||||
</section>
|
</a>
|
||||||
}
|
}
|
||||||
|
|||||||
13
internal/core/breadcrumb.templ
Normal file
13
internal/core/breadcrumb.templ
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
templ Breadcrumb(elements []string, links []string) {
|
||||||
|
<div class="flex gap-5 mb-10 text-lg items-center">
|
||||||
|
for i, element := range(elements) {
|
||||||
|
if (i>0) {
|
||||||
|
<span class="text-gray-500">></span><a class="p-2 decoration-yellow-400 decoration-[0.25rem] hover:bg-gray-200 rounded-lg hover:underline" href={ links[i] }>{ element }</a>
|
||||||
|
} else {
|
||||||
|
<a class="p-2 decoration-yellow-400 decoration-[0.25rem] hover:bg-gray-200 rounded-lg hover:underline" href={ links[i] }>{ element }</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import "spend-sparrow/internal/template/svg"
|
import (
|
||||||
|
"spend-sparrow/internal/template/svg"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
func layoutLinkClass(isActive bool) string {
|
func layoutLinkClass(isActive bool) string {
|
||||||
common := "text-2xl p-2 text-gray-900 decoration-yellow-400 decoration-[0.25rem] hover:bg-gray-200 rounded-lg"
|
common := "text-2xl p-2 text-gray-900 decoration-yellow-400 decoration-[0.25rem] hover:bg-gray-200 rounded-lg"
|
||||||
@@ -37,7 +40,6 @@ templ Layout(slot templ.Component, user templ.Component, loggedIn bool, path str
|
|||||||
<script src="/static/js/dashboard.js" defer></script>
|
<script src="/static/js/dashboard.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body hx-headers='{"Csrf-Token": "CSRF_TOKEN"}'>
|
<body hx-headers='{"Csrf-Token": "CSRF_TOKEN"}'>
|
||||||
<div id="dialogContainer"></div>
|
|
||||||
<div class="flex flex-col min-h-screen">
|
<div class="flex flex-col min-h-screen">
|
||||||
<header class="sticky top-0 z-50 bg-white flex items-center gap-6 p-4 border-b-1 border-gray-200">
|
<header class="sticky top-0 z-50 bg-white flex items-center gap-6 p-4 border-b-1 border-gray-200">
|
||||||
<button id="menuButton" class="w-10 h-10 block xl:hidden">
|
<button id="menuButton" class="w-10 h-10 block xl:hidden">
|
||||||
@@ -88,10 +90,10 @@ templ Layout(slot templ.Component, user templ.Component, loggedIn bool, path str
|
|||||||
|
|
||||||
templ navigation(path string) {
|
templ navigation(path string) {
|
||||||
<nav class="w-64 text-nowrap flex gap-2 flex-col text-lg mt-5 px-5 pt-2">
|
<nav class="w-64 text-nowrap flex gap-2 flex-col text-lg mt-5 px-5 pt-2">
|
||||||
<a class={ layoutLinkClass(path == "/dashboard") } href="/dashboard">Dashboard</a>
|
<a class={ layoutLinkClass(strings.HasPrefix(path, "/dashboard")) } href="/dashboard">Dashboard</a>
|
||||||
<a class={ layoutLinkClass(path == "/transaction") } href="/transaction">Transaction</a>
|
<a class={ layoutLinkClass(strings.HasPrefix(path, "/transaction")) } href="/transaction">Transaction</a>
|
||||||
<a class={ layoutLinkClass(path == "/treasurechest") } href="/treasurechest">Treasure Chest</a>
|
<a class={ layoutLinkClass(strings.HasPrefix(path, "/treasurechest")) } href="/treasurechest">Treasure Chest</a>
|
||||||
<a class={ layoutLinkClass(path == "/account") } href="/account">Account</a>
|
<a class={ layoutLinkClass(strings.HasPrefix(path, "/account")) } href="/account">Account</a>
|
||||||
<a class={ layoutLinkClass(path == "/budget") } href="/budget">Budget</a>
|
<a class={ layoutLinkClass(strings.HasPrefix(path, "/budget")) } href="/budget">Budget</a>
|
||||||
</nav>
|
</nav>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,18 +8,3 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
htmx.on("htmx:afterSwap", () => {
|
|
||||||
dialogs = dialogContainer.getElementsByClassName("openDialogModal");
|
|
||||||
Array.from(dialogs).forEach((dialog) => {
|
|
||||||
dialog.showModal()
|
|
||||||
// buttons = dialog.getElementsByClassName("closeModalDialog");
|
|
||||||
// Array.from(buttons).forEach((button) => button.addEventListener("click", closeModalDialog))
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
// function closeModalDialog(e) {
|
|
||||||
// e.preventDefault();
|
|
||||||
// console.log(e)
|
|
||||||
// e.target.close()
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user