feat: add tag combobox to make tagging expenses easier

This commit is contained in:
juancwu 2026-02-17 21:13:46 +00:00
commit ef37360da7
9 changed files with 671 additions and 106 deletions

View file

@ -11,7 +11,7 @@ import (
"git.juancwu.dev/juancwu/budgit/internal/ui/components/input"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/label"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/radio"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/selectbox"
"git.juancwu.dev/juancwu/budgit/internal/ui/components/tagcombobox"
"git.juancwu.dev/juancwu/budgit/internal/ui/layouts"
)
@ -37,15 +37,6 @@ func progressBarColor(status model.BudgetStatus) string {
}
}
func budgetTagSelected(tags []*model.Tag, tagID string) bool {
for _, t := range tags {
if t.ID == tagID {
return true
}
}
return false
}
templ SpaceBudgetsPage(space *model.Space, budgets []*model.BudgetWithSpent, tags []*model.Tag) {
@layouts.Space("Budgets", space) {
<div class="space-y-4">
@ -190,23 +181,17 @@ templ AddBudgetForm(spaceID string, tags []*model.Tag) {
class="space-y-4"
>
@csrf.Token()
// Tag selector (multi-select)
// Tag selector
<div>
@label.Label(label.Props{}) {
@label.Label(label.Props{For: "budget-tags"}) {
Tags
}
@selectbox.SelectBox(selectbox.Props{ID: "budget-tags", Multiple: true}) {
@selectbox.Trigger(selectbox.TriggerProps{Name: "tag_ids", Multiple: true, ShowPills: true, SelectedCountText: "{n} tags selected"}) {
@selectbox.Value(selectbox.ValueProps{Placeholder: "Select tags..."})
}
@selectbox.Content() {
for _, t := range tags {
@selectbox.Item(selectbox.ItemProps{Value: t.ID}) {
{ t.Name }
}
}
}
}
@tagcombobox.TagCombobox(tagcombobox.Props{
ID: "budget-tags",
Name: "tags",
Tags: tags,
Placeholder: "Search or create tags...",
})
</div>
// Amount
<div>
@ -290,6 +275,10 @@ templ AddBudgetForm(spaceID string, tags []*model.Tag) {
templ EditBudgetForm(spaceID string, b *model.BudgetWithSpent, tags []*model.Tag) {
{{ editDialogID := "edit-budget-" + b.ID }}
{{ budgetTagNames := make([]string, len(b.Tags)) }}
for i, t := range b.Tags {
{{ budgetTagNames[i] = t.Name }}
}
<form
hx-patch={ fmt.Sprintf("/app/spaces/%s/budgets/%s", spaceID, b.ID) }
hx-target="#budgets-list-wrapper"
@ -298,23 +287,18 @@ templ EditBudgetForm(spaceID string, b *model.BudgetWithSpent, tags []*model.Tag
class="space-y-4"
>
@csrf.Token()
// Tag selector (multi-select with pre-selected tags)
// Tag selector
<div>
@label.Label(label.Props{}) {
@label.Label(label.Props{For: "edit-budget-tags-" + b.ID}) {
Tags
}
@selectbox.SelectBox(selectbox.Props{ID: "edit-budget-tags-" + b.ID, Multiple: true}) {
@selectbox.Trigger(selectbox.TriggerProps{Name: "tag_ids", Multiple: true, ShowPills: true, SelectedCountText: "{n} tags selected"}) {
@selectbox.Value(selectbox.ValueProps{Placeholder: "Select tags..."})
}
@selectbox.Content() {
for _, t := range tags {
@selectbox.Item(selectbox.ItemProps{Value: t.ID, Selected: budgetTagSelected(b.Tags, t.ID)}) {
{ t.Name }
}
}
}
}
@tagcombobox.TagCombobox(tagcombobox.Props{
ID: "edit-budget-tags-" + b.ID,
Name: "tags",
Value: budgetTagNames,
Tags: tags,
Placeholder: "Search or create tags...",
})
</div>
// Amount
<div>

View file

@ -48,21 +48,21 @@ templ SpaceExpensesPage(space *model.Space, expenses []*model.ExpenseWithTagsAnd
// List of expenses
<div class="border rounded-lg">
<div id="expenses-list-wrapper">
@ExpensesListContent(space.ID, expenses, methods, currentPage, totalPages)
@ExpensesListContent(space.ID, expenses, methods, tags, currentPage, totalPages)
</div>
</div>
</div>
}
}
templ ExpensesListContent(spaceID string, expenses []*model.ExpenseWithTagsAndMethod, methods []*model.PaymentMethod, currentPage, totalPages int) {
templ ExpensesListContent(spaceID string, expenses []*model.ExpenseWithTagsAndMethod, methods []*model.PaymentMethod, tags []*model.Tag, currentPage, totalPages int) {
<h2 class="text-lg font-semibold p-4">History</h2>
<div id="expenses-list" class="divide-y">
if len(expenses) == 0 {
<p class="p-4 text-sm text-muted-foreground">No expenses recorded yet.</p>
}
for _, exp := range expenses {
@ExpenseListItem(spaceID, exp, methods)
@ExpenseListItem(spaceID, exp, methods, tags)
}
</div>
if totalPages > 1 {
@ -109,7 +109,7 @@ templ ExpensesListContent(spaceID string, expenses []*model.ExpenseWithTagsAndMe
}
}
templ ExpenseListItem(spaceID string, exp *model.ExpenseWithTagsAndMethod, methods []*model.PaymentMethod) {
templ ExpenseListItem(spaceID string, exp *model.ExpenseWithTagsAndMethod, methods []*model.PaymentMethod, tags []*model.Tag) {
<div id={ "expense-" + exp.ID } class="p-4 flex justify-between items-start gap-2">
<div class="min-w-0 flex-1">
<div class="flex items-center gap-1.5">
@ -166,7 +166,7 @@ templ ExpenseListItem(spaceID string, exp *model.ExpenseWithTagsAndMethod, metho
Update the details of this transaction.
}
}
@expense.EditExpenseForm(spaceID, exp, methods)
@expense.EditExpenseForm(spaceID, exp, methods, tags)
}
}
// Delete button
@ -208,12 +208,12 @@ templ ExpenseListItem(spaceID string, exp *model.ExpenseWithTagsAndMethod, metho
</div>
}
templ ExpenseCreatedResponse(spaceID string, expenses []*model.ExpenseWithTagsAndMethod, balance int, allocated int, currentPage, totalPages int) {
@ExpensesListContent(spaceID, expenses, nil, currentPage, totalPages)
templ ExpenseCreatedResponse(spaceID string, expenses []*model.ExpenseWithTagsAndMethod, balance int, allocated int, tags []*model.Tag, currentPage, totalPages int) {
@ExpensesListContent(spaceID, expenses, nil, tags, currentPage, totalPages)
@expense.BalanceCard(spaceID, balance, allocated, true)
}
templ ExpenseUpdatedResponse(spaceID string, exp *model.ExpenseWithTagsAndMethod, balance int, allocated int, methods []*model.PaymentMethod) {
@ExpenseListItem(spaceID, exp, methods)
templ ExpenseUpdatedResponse(spaceID string, exp *model.ExpenseWithTagsAndMethod, balance int, allocated int, methods []*model.PaymentMethod, tags []*model.Tag) {
@ExpenseListItem(spaceID, exp, methods, tags)
@expense.BalanceCard(exp.SpaceID, balance, allocated, true)
}

View file

@ -38,7 +38,7 @@ templ SpaceRecurringPage(space *model.Space, recs []*model.RecurringExpenseWithT
<p class="p-4 text-sm text-muted-foreground">No recurring transactions set up yet.</p>
}
for _, re := range recs {
@recurring.RecurringItem(space.ID, re, methods)
@recurring.RecurringItem(space.ID, re, methods, tags)
}
</div>
</div>