feat(tag): draft for inline editing
Some checks failed
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Failing after 1m15s

This commit is contained in:
2026-01-08 18:54:18 +01:00
parent 5af5ab2a0c
commit a570c44d75
5 changed files with 89 additions and 2 deletions

View File

@@ -15,6 +15,7 @@ type Db interface {
delete(ctx context.Context, userId uuid.UUID, id uuid.UUID) error
get(ctx context.Context, userId uuid.UUID, id uuid.UUID) (*Tag, error)
getAll(ctx context.Context, userId uuid.UUID) ([]Tag, error)
find(ctx context.Context, userId uuid.UUID, search string) ([]Tag, error)
}
type DbSqlite struct {
@@ -73,7 +74,7 @@ func (db DbSqlite) delete(ctx context.Context, userId uuid.UUID, id uuid.UUID) e
func (db DbSqlite) get(ctx context.Context, userId uuid.UUID, id uuid.UUID) (*Tag, error) {
var tag Tag
err := db.db.Get(&tag, "SELECT * FROM tag WHERE id = ? AND user_id = ?", id, userId)
err := db.db.GetContext(ctx, &tag, "SELECT * FROM tag WHERE id = ? AND user_id = ?", id, userId)
if err != nil {
slog.ErrorContext(ctx, "Could not get tag", "err", err)
@@ -85,7 +86,7 @@ func (db DbSqlite) get(ctx context.Context, userId uuid.UUID, id uuid.UUID) (*Ta
func (db DbSqlite) getAll(ctx context.Context, userId uuid.UUID) ([]Tag, error) {
var tags []Tag
err := db.db.Select(&tags, "SELECT * FROM tag WHERE user_id = ?", userId)
err := db.db.SelectContext(ctx, &tags, "SELECT * FROM tag WHERE user_id = ?", userId)
if err != nil {
slog.ErrorContext(ctx, "Could not GetAll tag", "err", err)
@@ -94,3 +95,17 @@ func (db DbSqlite) getAll(ctx context.Context, userId uuid.UUID) ([]Tag, error)
return tags, nil
}
func (db DbSqlite) find(ctx context.Context, userId uuid.UUID, search string) ([]Tag, error) {
var tags []Tag
err := db.db.SelectContext(ctx, &tags, "SELECT * FROM tag WHERE user_id = ? AND name LIKE ?", userId, "%"+search+"%")
slog.InfoContext(ctx, "find", "len", len(tags), "search", search)
if err != nil {
slog.ErrorContext(ctx, "Could not find tag", "err", err)
return nil, core.ErrInternal
}
return tags, nil
}

View File

@@ -30,6 +30,7 @@ func NewHandler(s Service, r *core.Render) Handler {
func (h HandlerImpl) Handle(r *http.ServeMux) {
r.Handle("GET /tag", h.handlePage())
r.Handle("POST /tag/search", h.handleInlineEditSearch())
r.Handle("GET /tag/new", h.handleNew())
r.Handle("GET /tag/{id}", h.handleEdit())
r.Handle("POST /tag/{id}", h.handlePost())
@@ -56,6 +57,26 @@ func (h HandlerImpl) handlePage() http.HandlerFunc {
h.r.RenderLayout(r, w, comp, user)
}
}
func (h HandlerImpl) handleInlineEditSearch() 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
}
search := r.FormValue("search")
tags, err := h.s.find(r.Context(), user, search)
if err != nil {
core.HandleError(w, r, fmt.Errorf("Could not find tags: %w", core.ErrInternal))
}
comp := inlineEditSearch(tags)
h.r.Render(r, w, comp)
}
}
func (h HandlerImpl) handleNew() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {

View File

@@ -14,6 +14,7 @@ type Service interface {
delete(ctx context.Context, user *auth_types.User, tagId uuid.UUID) error
get(ctx context.Context, user *auth_types.User, tagId uuid.UUID) (*Tag, error)
getAll(ctx context.Context, user *auth_types.User) ([]Tag, error)
find(ctx context.Context, user *auth_types.User, search string) ([]Tag, error)
}
type ServiceImpl struct {
@@ -105,6 +106,13 @@ func (s ServiceImpl) getAll(ctx context.Context, user *auth_types.User) ([]Tag,
return s.db.getAll(ctx, user.Id)
}
func (s ServiceImpl) find(ctx context.Context, user *auth_types.User, search string) ([]Tag, error) {
if user == nil {
return nil, core.ErrUnauthorized
}
return s.db.find(ctx, user.Id, search)
}
func (s ServiceImpl) isTagValid(tag Tag) bool {
err := core.ValidateString(tag.Name, "name")
return err == nil

View File

@@ -5,6 +5,46 @@ import (
"spend-sparrow/internal/template/svg"
)
templ InlineEditInput(classes string) {
<div class={ "flex flex-wrap gap-2 input max-w-full " + classes }>
<span class="flex items-center gap-1 p-1 bg-green-100 rounded-sm">
Lebensmittel
<button class="hover:bg-red-900 rounded p-1 w-5 transition-all">
@svg.Cancel()
</button>
</span>
<span class="flex items-center gap-1 p-1 bg-yellow-100 rounded-sm">
Sparen
<button class="hover:bg-red-900 rounded p-1 w-5 transition-all">
@svg.Cancel()
</button>
</span>
<span class="flex items-center gap-1 p-1 bg-red-100 rounded-sm">
Tanken
<button class="hover:bg-red-900 rounded p-1 w-5 transition-all">
@svg.Cancel()
</button>
</span>
<div>
<input
class="inline"
name="search"
placeholder="Begin Typing To Search ..."
hx-post="/tag/search"
hx-trigger="input changed delay:250ms, keyup[key=='Enter'], load"
hx-target="#tag-search-results"
/>
<div id="tag-search-results" class="absolute bg-white border-gray-200 border-1 rounded-lg p-4"></div>
</div>
</div>
}
templ inlineEditSearch(tags []Tag) {
for _,tag:=range(tags) {
<p x-data={ tag.Id.String() }>{ tag.Name }</p>
}
}
templ page(tags []Tag) {
@core.Breadcrumb([]string{"Home", "Tag"}, []string{"/", "/tag"})
<div class="flex flex-wrap gap-20 text-xl mt-10 justify-center">