feat(tag): draft for inline editing
Some checks failed
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Failing after 1m15s
Some checks failed
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Failing after 1m15s
This commit is contained in:
@@ -2,6 +2,7 @@ package budget
|
||||
|
||||
import (
|
||||
"spend-sparrow/internal/core"
|
||||
"spend-sparrow/internal/tag"
|
||||
"spend-sparrow/internal/template/svg"
|
||||
)
|
||||
|
||||
@@ -80,6 +81,8 @@ templ edit(budget Budget) {
|
||||
value={ budget.Value / 100 }
|
||||
class="bg-white input col-span-3"
|
||||
/>
|
||||
<label for="tag" class="text-sm text-gray-500">Tags</label>
|
||||
@tag.InlineEditInput("col-span-3")
|
||||
<div class="flex flex-row-reverse gap-6 justify-end col-span-4">
|
||||
<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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user