feat: add expense from overview page
This commit is contained in:
parent
1c210bde67
commit
800a3298d9
4 changed files with 71 additions and 39 deletions
|
|
@ -114,7 +114,14 @@ func (h *SpaceHandler) DashboardPage(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.Render(w, r, pages.SpaceOverviewPage(space, lists, tags))
|
listsWithItems, err := h.listService.GetListsWithUncheckedItems(spaceID)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to get lists with unchecked items", "error", err, "space_id", spaceID)
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.Render(w, r, pages.SpaceOverviewPage(space, lists, tags, listsWithItems))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *SpaceHandler) ListsPage(w http.ResponseWriter, r *http.Request) {
|
func (h *SpaceHandler) ListsPage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -558,6 +565,18 @@ func (h *SpaceHandler) CreateExpense(w http.ResponseWriter, r *http.Request) {
|
||||||
slog.Error("failed to get balance", "error", err, "space_id", spaceID)
|
slog.Error("failed to get balance", "error", err, "space_id", spaceID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if r.URL.Query().Get("from") == "overview" {
|
||||||
|
ui.Render(w, r, toast.Toast(toast.Props{
|
||||||
|
Title: "Expense created",
|
||||||
|
Description: "Your transaction has been recorded.",
|
||||||
|
Variant: toast.VariantSuccess,
|
||||||
|
Icon: true,
|
||||||
|
Dismissible: true,
|
||||||
|
Duration: 5000,
|
||||||
|
}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ui.Render(w, r, pages.ExpenseCreatedResponse(newExpense, balance))
|
ui.Render(w, r, pages.ExpenseCreatedResponse(newExpense, balance))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,36 @@ import (
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/ui/components/tagsinput"
|
"git.juancwu.dev/juancwu/budgit/internal/ui/components/tagsinput"
|
||||||
)
|
)
|
||||||
|
|
||||||
templ AddExpenseForm(space *model.Space, tags []*model.Tag, listsWithItems []model.ListWithUncheckedItems) {
|
type AddExpenseFormProps struct {
|
||||||
|
Space *model.Space
|
||||||
|
Tags []*model.Tag
|
||||||
|
ListsWithItems []model.ListWithUncheckedItems
|
||||||
|
DialogID string // which dialog to close on success
|
||||||
|
FromOverview bool // if true, POSTs with ?from=overview for toast response
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p AddExpenseFormProps) formAttrs() templ.Attributes {
|
||||||
|
closeScript := "on htmx:afterOnLoad if event.detail.xhr.status == 200 then call window.tui.dialog.close('" + p.DialogID + "') then reset() me end"
|
||||||
|
if p.FromOverview {
|
||||||
|
return templ.Attributes{
|
||||||
|
"hx-post": "/app/spaces/" + p.Space.ID + "/expenses?from=overview",
|
||||||
|
"hx-target": "body",
|
||||||
|
"hx-swap": "beforeend",
|
||||||
|
"_": closeScript,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return templ.Attributes{
|
||||||
|
"hx-post": "/app/spaces/" + p.Space.ID + "/expenses",
|
||||||
|
"hx-target": "#expenses-list",
|
||||||
|
"hx-swap": "afterbegin",
|
||||||
|
"_": closeScript,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templ AddExpenseForm(props AddExpenseFormProps) {
|
||||||
<form
|
<form
|
||||||
hx-post={ "/app/spaces/" + space.ID + "/expenses" }
|
|
||||||
hx-target="#expenses-list"
|
|
||||||
hx-swap="afterbegin"
|
|
||||||
_="on htmx:afterOnLoad if event.detail.xhr.status == 200 then call window.tui.dialog.close('add-expense-dialog') reset() me end"
|
|
||||||
class="space-y-4"
|
class="space-y-4"
|
||||||
|
{ props.formAttrs()... }
|
||||||
>
|
>
|
||||||
@csrf.Token()
|
@csrf.Token()
|
||||||
// Type
|
// Type
|
||||||
|
|
@ -108,7 +131,7 @@ templ AddExpenseForm(space *model.Space, tags []*model.Tag, listsWithItems []mod
|
||||||
Tags
|
Tags
|
||||||
}
|
}
|
||||||
<datalist id="available-tags">
|
<datalist id="available-tags">
|
||||||
for _, tag := range tags {
|
for _, tag := range props.Tags {
|
||||||
<option value={ tag.Name }></option>
|
<option value={ tag.Name }></option>
|
||||||
}
|
}
|
||||||
</datalist>
|
</datalist>
|
||||||
|
|
@ -124,11 +147,11 @@ templ AddExpenseForm(space *model.Space, tags []*model.Tag, listsWithItems []mod
|
||||||
@label.Label(label.Props{}) {
|
@label.Label(label.Props{}) {
|
||||||
Link Shopping List Items
|
Link Shopping List Items
|
||||||
}
|
}
|
||||||
if len(listsWithItems) == 0 {
|
if len(props.ListsWithItems) == 0 {
|
||||||
<p class="text-sm text-muted-foreground">No unchecked items available.</p>
|
<p class="text-sm text-muted-foreground">No unchecked items available.</p>
|
||||||
} else {
|
} else {
|
||||||
<div class="max-h-48 overflow-y-auto border rounded-md p-2 space-y-2">
|
<div class="max-h-48 overflow-y-auto border rounded-md p-2 space-y-2">
|
||||||
for i, lwi := range listsWithItems {
|
for i, lwi := range props.ListsWithItems {
|
||||||
{{ toggleID := "toggle-list-" + lwi.List.ID }}
|
{{ toggleID := "toggle-list-" + lwi.List.ID }}
|
||||||
{{ itemsID := "items-" + lwi.List.ID }}
|
{{ itemsID := "items-" + lwi.List.ID }}
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
|
|
@ -167,7 +190,7 @@ templ AddExpenseForm(space *model.Space, tags []*model.Tag, listsWithItems []mod
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
if i < len(listsWithItems) - 1 {
|
if i < len(props.ListsWithItems) - 1 {
|
||||||
<hr class="border-border"/>
|
<hr class="border-border"/>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,12 @@ templ SpaceExpensesPage(space *model.Space, expenses []*model.Expense, balance i
|
||||||
Add a new expense or top-up to your space.
|
Add a new expense or top-up to your space.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@expense.AddExpenseForm(space, tags, listsWithItems)
|
@expense.AddExpenseForm(expense.AddExpenseFormProps{
|
||||||
|
Space: space,
|
||||||
|
Tags: tags,
|
||||||
|
ListsWithItems: listsWithItems,
|
||||||
|
DialogID: "add-expense-dialog",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,53 +3,38 @@ package pages
|
||||||
import (
|
import (
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/model"
|
"git.juancwu.dev/juancwu/budgit/internal/model"
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/ui/components/button"
|
"git.juancwu.dev/juancwu/budgit/internal/ui/components/button"
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/ui/components/csrf"
|
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/ui/components/dialog"
|
"git.juancwu.dev/juancwu/budgit/internal/ui/components/dialog"
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/ui/components/input"
|
"git.juancwu.dev/juancwu/budgit/internal/ui/components/expense"
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/ui/layouts"
|
"git.juancwu.dev/juancwu/budgit/internal/ui/layouts"
|
||||||
)
|
)
|
||||||
|
|
||||||
templ SpaceOverviewPage(space *model.Space, lists []*model.ShoppingList, tags []*model.Tag) {
|
templ SpaceOverviewPage(space *model.Space, lists []*model.ShoppingList, tags []*model.Tag, listsWithItems []model.ListWithUncheckedItems) {
|
||||||
@layouts.Space("Overview", space) {
|
@layouts.Space("Overview", space) {
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<h1 class="text-2xl font-bold">Welcome to { space.Name }!</h1>
|
<h1 class="text-2xl font-bold">Welcome to { space.Name }!</h1>
|
||||||
@dialog.Dialog(dialog.Props{ID: "invite-member-dialog"}) {
|
@dialog.Dialog(dialog.Props{ID: "add-expense-overview-dialog"}) {
|
||||||
@dialog.Trigger() {
|
@dialog.Trigger() {
|
||||||
@button.Button() {
|
@button.Button() {
|
||||||
Invite Member
|
Add Expense
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@dialog.Content() {
|
@dialog.Content() {
|
||||||
@dialog.Header() {
|
@dialog.Header() {
|
||||||
@dialog.Title() {
|
@dialog.Title() {
|
||||||
Invite Member
|
Add Transaction
|
||||||
}
|
}
|
||||||
@dialog.Description() {
|
@dialog.Description() {
|
||||||
Send an invitation email to add a new member to this space.
|
Add a new expense or top-up to your space.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<form
|
@expense.AddExpenseForm(expense.AddExpenseFormProps{
|
||||||
hx-post={ "/app/spaces/" + space.ID + "/invites" }
|
Space: space,
|
||||||
hx-swap="innerHTML"
|
Tags: tags,
|
||||||
class="space-y-4"
|
ListsWithItems: listsWithItems,
|
||||||
>
|
DialogID: "add-expense-overview-dialog",
|
||||||
@csrf.Token()
|
FromOverview: true,
|
||||||
<div>
|
})
|
||||||
<label for="email" class="label">Email Address</label>
|
|
||||||
@input.Input(input.Props{
|
|
||||||
Name: "email",
|
|
||||||
ID: "email",
|
|
||||||
Type: "email",
|
|
||||||
Attributes: templ.Attributes{"required": "true"},
|
|
||||||
})
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-end">
|
|
||||||
@button.Button(button.Props{Type: button.TypeSubmit}) {
|
|
||||||
Send Invitation
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue