Skip to content

Commit

Permalink
edit client procedures
Browse files Browse the repository at this point in the history
  • Loading branch information
henriquepw committed Nov 17, 2024
1 parent e1af5d7 commit b9ee8d8
Show file tree
Hide file tree
Showing 10 changed files with 244 additions and 14 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/henriquepw/imperium-tattoo
go 1.23.1

require (
github.com/a-h/templ v0.2.771
github.com/a-h/templ v0.2.793
github.com/go-playground/validator/v10 v10.22.0
github.com/joho/godotenv v1.5.1
github.com/matoous/go-nanoid/v2 v2.1.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/a-h/templ v0.2.771 h1:4KH5ykNigYGGpCe0fRJ7/hzwz72k3qFqIiiLLJskbSo=
github.com/a-h/templ v0.2.771/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w=
github.com/a-h/templ v0.2.793 h1:Io+/ocnfGWYO4VHdR0zBbf39PQlnzVCVVD+wEEs6/qY=
github.com/a-h/templ v0.2.793/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
Expand Down
20 changes: 20 additions & 0 deletions static/css/input.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@
@tailwind utilities;

@layer components {
*::selection {
@apply bg-accent-8 text-white;
}

::-webkit-scrollbar {
width: 8px;
}

::-webkit-scrollbar-track {
@apply bg-gray-1;
}

::-webkit-scrollbar-thumb {
@apply bg-accent-8;
}

::-webkit-scrollbar-thumb:hover {
@apply bg-accent-10;
}

.card {
@apply bg-gray-2 backdrop-blur border border-gray-7 rounded-lg p-4
}
Expand Down
65 changes: 62 additions & 3 deletions web/db/client_procedures_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import (

type ClientProcedureStore interface {
Insert(ctx context.Context, item types.ClientProcedure) error
Update(ctx context.Context, id string, dto types.ClientProcedureUpdateDTO) error
Update(ctx context.Context, dto types.ClientProcedureUpdateDTO) error
List(ctx context.Context, clientID string) ([]types.ClientProcedure, error)
Get(ctx context.Context, procedureID string) (*types.ClientProcedure, error)
}

type clientProcedureStore struct {
Expand Down Expand Up @@ -55,6 +56,7 @@ func (s *clientProcedureStore) List(ctx context.Context, clientID string) ([]typ
cp.id,
cp.description,
cp.done_at,
cp.procedure_id,
p.name
FROM
client_procedure cp
Expand All @@ -77,6 +79,7 @@ func (s *clientProcedureStore) List(ctx context.Context, clientID string) ([]typ
&i.ID,
&i.Description,
&doneAt,
&i.ProcedureID,
&i.Procedure,
)
if err != nil {
Expand All @@ -94,6 +97,62 @@ func (s *clientProcedureStore) List(ctx context.Context, clientID string) ([]typ
return items, nil
}

func (s *clientProcedureStore) Update(ctx context.Context, clientID string, dto types.ClientProcedureUpdateDTO) error {
return nil
func (s *clientProcedureStore) Get(ctx context.Context, procedureID string) (*types.ClientProcedure, error) {
query := `
SELECT
cp.id,
cp.description,
cp.done_at,
cp.procedure_id,
p.name
FROM
client_procedure cp
LEFT JOIN procedure p ON cp.procedure_id = p.id
WHERE
cp.id = ?
`
row := s.db.QueryRowContext(ctx, query, procedureID)

var procedure types.ClientProcedure
doneAt := ""
err := row.Scan(
&procedure.ID,
&procedure.Description,
&doneAt,
&procedure.ProcedureID,
&procedure.Procedure,
)
if err != nil {
return nil, err
}

procedure.DoneAt, err = time.Parse(time.RFC3339, doneAt)
if err != nil {
return nil, err
}

return &procedure, nil
}

func (s *clientProcedureStore) Update(ctx context.Context, dto types.ClientProcedureUpdateDTO) error {
query := `
UPDATE client_procedure
SET
procedure_id = ?,
description = ?,
done_at = ?,
updated_at = ?
WHERE
id = ?
`
_, error := s.db.ExecContext(
ctx, query,
dto.ProcedureID,
dto.Description,
date.FormatToISO(dto.DoneAt),
date.FormatToISO(time.Now()),
dto.ID,
)

return error
}
33 changes: 33 additions & 0 deletions web/handlers/client_handler.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package handlers

import (
"fmt"
"log"
"net/http"
"time"

"github.com/a-h/templ"
"github.com/henriquepw/imperium-tattoo/pkg/date"
Expand Down Expand Up @@ -71,6 +73,37 @@ func (h *ClientHandler) CreateClientAction(w http.ResponseWriter, r *http.Reques
)
}

func (h *ClientHandler) EditClientProcedureAction(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
doneAt, err := time.Parse(time.DateOnly, r.Form.Get("doneAt"))
if err != nil {
fmt.Print(err)
httputil.Render(w, r, http.StatusBadRequest, pages.ClientProcessEditForm(map[string]string{"doneAt": "Data inválida"}))
return
}

payload := types.ClientProcedureUpdateDTO{
ID: r.PathValue("procedureId"),
Description: r.Form.Get("description"),
ProcedureID: r.Form.Get("procedureId"),
DoneAt: doneAt,
}

p, err := h.clientProcedureSVC.EditClientProcedure(r.Context(), payload)
if err != nil {
httputil.RenderError(w, r, err, func(e errors.ServerError) templ.Component {
return pages.ClientProcessEditForm(e.Errors)
})
return
}

httputil.Render(
w, r, http.StatusOK,
pages.ClientProcessEditForm(nil),
pages.OobUpdateClientProcedure(*p),
)
}

func (h *ClientHandler) ClientDetailPage(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")

Expand Down
19 changes: 19 additions & 0 deletions web/services/client_procedure_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

type ClientProcedureService interface {
CreateClientProcedure(ctx context.Context, dto types.ClientProcedureCreateDTO) (*types.ClientProcedure, error)
EditClientProcedure(ctx context.Context, dto types.ClientProcedureUpdateDTO) (*types.ClientProcedure, error)
ListClientProcedures(ctx context.Context, clientID string) ([]types.ClientProcedure, error)
}

Expand Down Expand Up @@ -73,3 +74,21 @@ func (s *clientProcedureService) ListClientProcedures(ctx context.Context, clien

return procedures, nil
}

func (s *clientProcedureService) EditClientProcedure(ctx context.Context, dto types.ClientProcedureUpdateDTO) (*types.ClientProcedure, error) {
if err := validate.CheckPayload(dto); err != nil {
return nil, err
}

err := s.store.Update(ctx, dto)
if err != nil {
return nil, errors.Internal("Não foi possível editar o procedimento")
}

p, err := s.store.Get(ctx, dto.ID)
if err != nil {
return nil, errors.Internal("Não foi possível editar o procedimento")
}

return p, nil
}
2 changes: 2 additions & 0 deletions web/types/client_procedure.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type ClientProcedureCreateDTO struct {
}

type ClientProcedureUpdateDTO struct {
ID string `validate:"required,id,len=18"`
DoneAt time.Time `validate:"required"`
Description string `validate:"required,min=5"`
ProcedureID string `validate:"required,id,len=18"`
}
106 changes: 100 additions & 6 deletions web/view/pages/client_detail_page.templ
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ templ ClientDetailPage(
clientProcedures []types.ClientProcedure,
) {
@layout.Dashbaord(client.Name, boosted) {
<div x-data="{editOpen:false}">
<div x-data={ fmt.Sprintf("{editOpen:false,clientId:'%s'}", client.ID) }>
@layout.PageHeader(client.Name, []ui.BreadcrumbItem{
{Label: "Clientes", Href: "/clients"},
{Label: "Detalhe", Href: "/clients/" + client.ID},
Expand Down Expand Up @@ -241,11 +241,18 @@ func parseProcedures(items []types.Procedure) string {
}

templ ClientProcessSection(id string, procedures []types.Procedure, clientProcedures []types.ClientProcedure) {
<section class="mt-6 mb-10" x-data={ "{ newProcedureOpen: false, procedures:" + parseProcedures(procedures) + " }" }>
<section
class="mt-6 mb-10"
x-data={ "{ procedure: undefined, newProcedureOpen: false, procedureTypes:" +
parseProcedures(procedures) + " }" }
>
<div class="row mb-4">
<h2 class="text-2xl flex-1 font-bold">Procedimentos</h2>
</div>
@ClientProcedureList(clientProcedures)
@ui.Modal("!!procedure", "procedure=undefined", "Editar Procedimento") {
@ClientProcessEditForm(nil)
}
@ui.Modal("newProcedureOpen", "newProcedureOpen=false", "Registrar Procedimento") {
@ClientProcessCreateForm(id, types.ClientProcedureCreateDTO{}, nil)
}
Expand All @@ -259,16 +266,103 @@ templ ClientProcessSection(id string, procedures []types.Procedure, clientProced
</section>
}

templ OobUpdateClientProcedure(p types.ClientProcedure) {
<li
hx-swap-oob="outerHTML"
id={ "client-procedure-" + p.ID }
class="flex flex-col items-start gap-1 p-2 -ml-2 rounded transition-colors hover:bg-gray-2 cursor-pointer"
@click={ fmt.Sprintf("procedure={id:'%s',procedureId:'%s',doneAt:'%s',description:`%s`}", p.ID, p.ProcedureID,
date.FormatToFormInput(p.DoneAt), p.Description) }
>
<span
class="
text-sm text-gray-11 relative
before:absolute before:size-3 before:bg-gray-1
before:border-2 before:border-accent-8
before:rounded-full before:left-0 before:top-1/2 before:-translate-x-[1.45rem] before:-translate-y-1/2
"
x-text={ formatDate(p.DoneAt) }
></span>
<span class="text-sm text-accent-12 bg-accent-3 border border-accent-6 px-1 rounded">
{ p.Procedure }
</span>
<span class="font-medium">{ p.Description }</span>
</li>
}

templ clientProcedureItem(p types.ClientProcedure) {
<li class="flex flex-col items-start gap-1">
<span class="text-sm text-gray-11" x-text={ formatDate(p.DoneAt) }></span>
<li
id={ "client-procedure-" + p.ID }
class="flex flex-col items-start gap-1 p-2 -ml-2 rounded transition-colors hover:bg-gray-2 cursor-pointer"
@click={ fmt.Sprintf("procedure={id:'%s',procedureId:'%s',doneAt:'%s',description:`%s`}", p.ID, p.ProcedureID,
date.FormatToFormInput(p.DoneAt), p.Description) }
>
<span
class="
text-sm text-gray-11 relative
before:absolute before:size-3 before:bg-gray-1
before:border-2 before:border-accent-8
before:rounded-full before:left-0 before:top-1/2 before:-translate-x-[1.45rem] before:-translate-y-1/2
"
x-text={ formatDate(p.DoneAt) }
></span>
<span class="text-sm text-accent-12 bg-accent-3 border border-accent-6 px-1 rounded">
{ p.Procedure }
</span>
<span class="font-medium">{ p.Description }</span>
</li>
}

templ ClientProcessEditForm(errors map[string]string) {
<form
x-init="$watch('procedure', () => htmx.process($el))"
:hx-put="`/clients/${clientId}/procedures/${procedure?.id}`"
hx-target="this"
hx-swap="outerHTML"
hx-indicator="this"
class="flex flex-col gap-4 group [&.htmx-request]:pointer-events-none"
@htmx:before-swap="(e) => {
if (e.detail.xhr.status === 200) {
procedure = undefined;
}
}"
>
<div
class="row"
x-data={ fmt.Sprintf("{selectedProcedure:'%s'}", "") }
x-effect="selectedProcedure=procedure?.procedureId"
>
@ui.FormField("procedureID", "Procedimento", errors["procedureId"], true) {
<select class="input h-9" name="procedureId">
<option value="">Selecione um procedimento</option>
<template x-for="p in procedureTypes">
<option :value="p.id" x-text="p.name" :selected="p.id == selectedProcedure"></option>
</template>
</select>
}
@ui.TextInput(ui.TextInputOps{
Required: true,
Label: "Data de Realização",
Name: "doneAt",
Type: "date",
XValue: "procedure?.doneAt",
Error: errors["doneAt"],
})
</div>
@ui.TextArea(ui.TextAreaOps{
Required: true,
Label: "Descrição",
Name: "description",
Placeholder: "Descreva como foi o procedimento, quais matérias foram usados, etc",
XValue: "procedure?.description",
Error: errors["description"],
})
@ui.SubmitBtn("save", "Salvando...", "ml-auto") {
Salvar
}
</form>
}

templ ClientProcedureList(procedures []types.ClientProcedure) {
if len(procedures) == 0 {
<div class="card bg-warning-2 border-warning-7">
Expand Down Expand Up @@ -305,7 +399,7 @@ templ ClientProcessCreateForm(id string, values types.ClientProcedureCreateDTO,
@ui.FormField("procedureID", "Procedimento", errors["procedureId"], true) {
<select class="input h-9" name="procedureId">
<option value="">Selecione um procedimento</option>
<template x-for="p in procedures">
<template x-for="p in procedureTypes">
<option :value="p.id" x-text="p.name" :selected="p.id == selectedProcedure"></option>
</template>
</select>
Expand All @@ -321,8 +415,8 @@ templ ClientProcessCreateForm(id string, values types.ClientProcedureCreateDTO,
</div>
@ui.TextArea(ui.TextAreaOps{
Required: true,
Label: "Descrição",
Name: "description",
Label: "Descrição",
Placeholder: "Descreva como foi o procedimento, quais matérias foram usados, etc",
Value: values.Description,
Error: errors["description"],
Expand Down
8 changes: 4 additions & 4 deletions web/view/ui/modal.templ
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ templ Modal(show, close, title string) {
<div x-show={ show } x-transition.opacity.duration.300ms class="z-40 absolute inset-0 bg-gray-12/20 backdrop-blur-sm"></div>
<div
x-show={ show }
x-transition:enter="duration-100 md:duration-300"
x-transition:enter-start="opacity-0 md:translate-y-full"
x-transition:enter="duration-300 md:duration-100"
x-transition:enter-start="opacity-0 translate-y-full md:translate-y-0"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="duration-100 md:duration-500"
x-transition:leave="duration-500 md:duration-100"
x-transition:leave-start="opacity-100 translate-y-0"
x-transition:leave-end="opacity-0 md:translate-y-full"
x-transition:leave-end="opacity-0 translate-y-full md:translate-y-0"
class="
flex flex-col
z-50 bg-gray-2 p-4 min-h-[75%] w-full mt-auto rounded-t-2xl transition
Expand Down
Loading

0 comments on commit b9ee8d8

Please sign in to comment.