diff --git a/http/budgeting.go b/http/budgeting.go
index 9023aaa..f827dcf 100644
--- a/http/budgeting.go
+++ b/http/budgeting.go
@@ -60,6 +60,28 @@ func getDate(c *gin.Context) (time.Time, error) {
return getFirstOfMonth(year, month, time.Now().Location()), nil
}
+func (h *Handler) autocompleteCategories(c *gin.Context) {
+ budgetID := c.Param("budgetid")
+ budgetUUID, err := uuid.Parse(budgetID)
+ if err != nil {
+ c.AbortWithError(http.StatusBadRequest, fmt.Errorf("budgetid missing from URL"))
+ return
+ }
+
+ query := c.Request.URL.Query().Get("s")
+ searchParams := postgres.SearchCategoriesParams{
+ BudgetID: budgetUUID,
+ Search: "%" + query + "%",
+ }
+ categories, err := h.Service.SearchCategories(c.Request.Context(), searchParams)
+ if err != nil {
+ c.AbortWithError(http.StatusInternalServerError, err)
+ return
+ }
+
+ c.JSON(http.StatusOK, categories)
+}
+
func (h *Handler) autocompletePayee(c *gin.Context) {
budgetID := c.Param("budgetid")
budgetUUID, err := uuid.Parse(budgetID)
diff --git a/http/http.go b/http/http.go
index 3cb78c5..2ba5920 100644
--- a/http/http.go
+++ b/http/http.go
@@ -67,6 +67,7 @@ func (h *Handler) Serve() {
authenticated.GET("/admin/clear-database", h.clearDatabase)
authenticated.GET("/budget/:budgetid", h.budgeting)
authenticated.GET("/budget/:budgetid/autocomplete/payees", h.autocompletePayee)
+ authenticated.GET("/budget/:budgetid/autocomplete/categories", h.autocompleteCategories)
authenticated.DELETE("/budget/:budgetid", h.deleteBudget)
authenticated.POST("/budget/:budgetid/import/ynab", h.importYNAB)
authenticated.POST("/budget/:budgetid/settings/clear", h.clearBudget)
diff --git a/postgres/categories.sql.go b/postgres/categories.sql.go
index 9649011..7ddb062 100644
--- a/postgres/categories.sql.go
+++ b/postgres/categories.sql.go
@@ -116,3 +116,44 @@ func (q *Queries) GetCategoryGroups(ctx context.Context, budgetID uuid.UUID) ([]
}
return items, nil
}
+
+const searchCategories = `-- name: SearchCategories :many
+SELECT CONCAT(category_groups.name, ' : ', categories.name) as name, categories.id FROM categories
+INNER JOIN category_groups ON categories.category_group_id = category_groups.id
+WHERE category_groups.budget_id = $1
+AND categories.name LIKE $2
+ORDER BY category_groups.name, categories.name
+`
+
+type SearchCategoriesParams struct {
+ BudgetID uuid.UUID
+ Search string
+}
+
+type SearchCategoriesRow struct {
+ Name interface{}
+ ID uuid.UUID
+}
+
+func (q *Queries) SearchCategories(ctx context.Context, arg SearchCategoriesParams) ([]SearchCategoriesRow, error) {
+ rows, err := q.db.QueryContext(ctx, searchCategories, arg.BudgetID, arg.Search)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []SearchCategoriesRow
+ for rows.Next() {
+ var i SearchCategoriesRow
+ if err := rows.Scan(&i.Name, &i.ID); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Close(); err != nil {
+ return nil, err
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
diff --git a/postgres/queries/categories.sql b/postgres/queries/categories.sql
index 389406c..2ff245b 100644
--- a/postgres/queries/categories.sql
+++ b/postgres/queries/categories.sql
@@ -18,4 +18,12 @@ RETURNING *;
SELECT categories.*, category_groups.name as group FROM categories
INNER JOIN category_groups ON categories.category_group_id = category_groups.id
WHERE category_groups.budget_id = $1
-ORDER BY category_groups.name, categories.name;
\ No newline at end of file
+ORDER BY category_groups.name, categories.name;
+
+-- name: SearchCategories :many
+SELECT CONCAT(category_groups.name, ' : ', categories.name) as name, categories.id FROM categories
+INNER JOIN category_groups ON categories.category_group_id = category_groups.id
+WHERE category_groups.budget_id = @budget_id
+AND categories.name LIKE @search
+ORDER BY category_groups.name, categories.name;
+--ORDER BY levenshtein(payees.name, $2);
\ No newline at end of file
diff --git a/web/src/pages/Account.vue b/web/src/pages/Account.vue
index c7a4177..085d9b6 100644
--- a/web/src/pages/Account.vue
+++ b/web/src/pages/Account.vue
@@ -44,7 +44,7 @@ export default defineComponent({