Skip to content

Commit

Permalink
add a user overview to the admin page
Browse files Browse the repository at this point in the history
  • Loading branch information
foodelevator committed Nov 13, 2024
1 parent 45c5dae commit e8b1612
Show file tree
Hide file tree
Showing 10 changed files with 529 additions and 53 deletions.
11 changes: 11 additions & 0 deletions database/sql/user.sql
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ select *
from users
where kthid = $1;

-- name: ListUsers :many
select *
from users
where case
when @search::text = '' then true
else kthid = @search or first_name ~* @search or family_name ~* @search
end
order by kthid
limit $1
offset $2;

-- name: UserSetMemberTo :exec
update users
set member_to = $2
Expand Down
49 changes: 49 additions & 0 deletions database/user.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 24 additions & 1 deletion handlers/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,33 @@ func admin(s *service.Service, w http.ResponseWriter, r *http.Request) httputil.
return templates.AdminPage()
}

func members(s *service.Service, w http.ResponseWriter, r *http.Request) httputil.ToResponse {
func membersPage(s *service.Service, w http.ResponseWriter, r *http.Request) httputil.ToResponse {
return templates.Members()
}

func adminUsersForm(s *service.Service, w http.ResponseWriter, r *http.Request) httputil.ToResponse {
search := r.FormValue("search")
offsetStr := r.FormValue("offset")
offset, err := strconv.ParseInt(offsetStr, 10, 32)
if err != nil && offsetStr != "" {
return httputil.BadRequest("Invalid int for offset")
}
users, err := s.DB.ListUsers(r.Context(), database.ListUsersParams{
Search: search,
Limit: 21,
Offset: int32(offset),
})
if err != nil {
return err
}
more := false
if len(users) == 21 {
users = users[0:20:20]
more = true
}
return templates.MemberList(service.DBUsersToModel(users), search, int(offset), more)
}

func invites(s *service.Service, w http.ResponseWriter, r *http.Request) httputil.ToResponse {
invs, err := s.DB.ListInvites(r.Context())
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion handlers/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ func MountRoutes(s *service.Service) {
// admin.go
http.Handle("GET /admin", authAdmin(s, httputil.Route(s, admin)))

http.Handle("GET /admin/members", authAdmin(s, httputil.Route(s, members)))
http.Handle("GET /admin/members", authAdmin(s, httputil.Route(s, membersPage)))
http.Handle("GET /admin/users", authAdmin(s, httputil.Route(s, adminUsersForm)))
http.Handle("POST /admin/members/upload-sheet", authAdmin(s, httputil.Route(s, uploadSheet)))
http.Handle("GET /admin/members/upload-sheet", authAdmin(s, httputil.Route(s, processSheet)))

Expand Down
74 changes: 70 additions & 4 deletions pkg/static/public/style.dist.css
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,22 @@ html {
grid-template-columns: subgrid;
}

.grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}

.grid-cols-5 {
grid-template-columns: repeat(5, minmax(0, 1fr));
}

.grid-cols-\[repeat\(4\2c auto\)\] {
grid-template-columns: repeat(4,auto);
}

.grid-rows-subgrid {
grid-template-rows: subgrid;
}

.flex-col {
flex-direction: column;
}
Expand Down Expand Up @@ -736,6 +752,24 @@ html {
gap: 2rem;
}

.gap-x-1 {
-moz-column-gap: 0.25rem;
column-gap: 0.25rem;
}

.gap-y-2 {
row-gap: 0.5rem;
}

.gap-x-2 {
-moz-column-gap: 0.5rem;
column-gap: 0.5rem;
}

.gap-y-1 {
row-gap: 0.25rem;
}

.overflow-auto {
overflow: auto;
}
Expand Down Expand Up @@ -847,6 +881,11 @@ html {
padding-right: 0.375rem;
}

.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}

.pb-4 {
padding-bottom: 1rem;
}
Expand All @@ -863,10 +902,6 @@ html {
text-align: center;
}

.align-middle {
vertical-align: middle;
}

.text-lg {
font-size: 1.125rem;
line-height: 1.75rem;
Expand Down Expand Up @@ -1049,12 +1084,43 @@ html {
border-color: rgb(238 42 123 / var(--tw-border-opacity));
}

.enabled\:hover\:border-cerise-light:hover:enabled {
--tw-border-opacity: 1;
border-color: rgb(236 95 153 / var(--tw-border-opacity));
}

.enabled\:focus\:border-cerise-strong:focus:enabled {
--tw-border-opacity: 1;
border-color: rgb(238 42 123 / var(--tw-border-opacity));
}

.disabled\:text-gray-500:disabled {
--tw-text-opacity: 1;
color: rgb(107 114 128 / var(--tw-text-opacity));
}

@media (min-width: 768px) {
.md\:max-w-\[calc\(100vw-8rem\)\] {
max-width: calc(100vw - 8rem);
}
}

.\[\&\>\*\]\:h-4>* {
height: 1rem;
}

.\[\&\>\*\]\:h-6>* {
height: 1.5rem;
}

.\[\&\>\*\]\:h-8>* {
height: 2rem;
}

.\[\&\>\*\]\:h-7>* {
height: 1.75rem;
}

.\[\&\>\.error\]\:mt-2>.error {
margin-top: 0.5rem;
}
Expand Down
8 changes: 8 additions & 0 deletions service/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ func dbUserToModel(user database.User) models.User {
}
}

func DBUsersToModel(users []database.User) []models.User {
us := make([]models.User, len(users))
for i, u := range users {
us[i] = dbUserToModel(u)
}
return us
}

func (s *Service) GetUser(ctx context.Context, kthid string) (*models.User, error) {
user, err := s.DB.GetUser(ctx, kthid)
if err == pgx.ErrNoRows {
Expand Down
91 changes: 75 additions & 16 deletions templates/admin.templ
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package templates

import "fmt"
import (
"fmt"
"github.com/datasektionen/logout/models"
"time"
)

templ adminNav() {
<a href="/admin/members">Members</a>
Expand All @@ -17,34 +21,89 @@ templ AdminPage() {
templ Members() {
@AdminPage() {
<div class="p-8">
<section hx-get="/admin/users" hx-trigger="load">
@MemberList([]models.User{}, "", 0, false)
</section>
@uploadForm()
@UploadStatus(false)
</div>
}
}

templ MemberList(users []models.User, search string, offset int, more bool) {
<div class="grid grid-cols-[repeat(4,auto)] gap-x-2 [&>*]:h-7">
<div class="grid grid-cols-subgrid col-span-full">
<span>Username</span>
<span>Name</span>
<span>Year</span>
<span>Member until</span>
</div>
for _, user := range users {
<div class="grid grid-cols-subgrid col-span-full">
<span>{ user.KTHID }</span>
<span>{ user.FirstName } { user.FamilyName }</span>
<span>{ user.YearTag }</span>
<span>{ user.MemberTo.Format(time.DateOnly) }</span>
</div>
}
for i := len(users); i < 20; i++ {
<div class="grid grid-cols-subgrid col-span-full"></div>
}
</div>
<div class="flex gap-2 pt-2" hx-target="closest section">
<input
hx-get="/admin/users"
type="text"
name="search"
placeholder="Search..."
class={ input }
value={ search }
if search != "" {
autofocus
}
/>
<button
hx-get="/admin/users"
class={ button }
hx-vals={ templ.JSONString(map[string]any{"offset": max(0, offset-20)}) }
hx-include='input[name="search"]'
if offset == 0 {
disabled
}
>prev</button>
<button
hx-get="/admin/users"
class={ button }
hx-vals={ templ.JSONString(map[string]any{"offset": offset + 20}) }
hx-include='input[name="search"]'
if !more {
disabled
}
>next</button>
</div>
}

templ uploadForm() {
<form
class="flex flex-col gap-2 p-2"
class="py-2 flex gap-2 items-center"
hx-post="/admin/members/upload-sheet"
hx-encoding="multipart/form-data"
hx-swap="outerHTML"
hx-target="#upload-status"
>
<div class="flex gap-2">
<input
name="sheet"
type="file"
required
class="
bg-[#3f4c66] rounded p-1 grid place-items-center pointer w-full
border border-transparent outline-none focus:border-cerise-strong hover:border-cerise-light relative
"
/>
<button
class={ button + "h-auto" }
>Upload</button>
</div>
<p class="shrink-0">Upload THS membership sheet</p>
<input
name="sheet"
type="file"
required
class="
bg-[#3f4c66] rounded p-1 grid place-items-center pointer w-full
border border-transparent outline-none focus:border-cerise-strong hover:border-cerise-light relative
"
/>
<button
class={ button + "h-auto" }
>Upload</button>
</form>
}

Expand Down
Loading

0 comments on commit e8b1612

Please sign in to comment.