Skip to content

Commit

Permalink
WIP: Add equipment (or gear)
Browse files Browse the repository at this point in the history
Signed-off-by: Jo Vandeginste <[email protected]>
  • Loading branch information
jovandeginste committed May 26, 2024
1 parent 7d9a2b4 commit c0dccda
Show file tree
Hide file tree
Showing 22 changed files with 756 additions and 15 deletions.
136 changes: 136 additions & 0 deletions assets/output.css
Original file line number Diff line number Diff line change
Expand Up @@ -1524,6 +1524,122 @@ table {
}
}

.selectable-pill {
margin: 0.5rem;
margin-top: 0.75rem;
margin-bottom: 0.75rem;
display: inline-block;
min-width: 150px;
border-radius: 9999px;
border-width: 1px;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
text-align: center;
--tw-border-opacity: 1;
border-color: rgb(115 115 115 / var(--tw-border-opacity));
--tw-bg-opacity: 1;
background-color: rgb(212 212 212 / var(--tw-bg-opacity));
}

.selectable-pill:hover {
--tw-bg-opacity: 1;
background-color: rgb(163 163 163 / var(--tw-bg-opacity));
}

@media (prefers-color-scheme: dark) {
.selectable-pill {
--tw-border-opacity: 1;
border-color: rgb(115 115 115 / var(--tw-border-opacity));
--tw-bg-opacity: 1;
background-color: rgb(64 64 64 / var(--tw-bg-opacity));
}

.selectable-pill:hover {
--tw-bg-opacity: 1;
background-color: rgb(82 82 82 / var(--tw-bg-opacity));
}
}

.selectable-pill::before {
font-family: "Font Awesome 5 Free";
vertical-align: middle;
}

.selectable-pill::after {
content: "";
}

.selectable-pill::before,.selectable-pill::after {
font-weight: 900;
}

.icon-space-xs.selectable-pill::before {
margin-right: 0.25rem;
}

.icon-space-sm.selectable-pill::before {
margin-right: 0.5rem;
}

.selectable-pill.icon-before::before {
margin-right: 0.75rem;
}

.icon-space-md.selectable-pill::before {
margin-right: 0.75rem;
}

.selectable-pill.icon-after::after {
margin-left: 0.75rem;
}

.icon-space-lg.selectable-pill::before {
margin-right: 1rem;
}

.icon-space-xl.selectable-pill::before {
margin-right: 1.25rem;
}

.icon-space-2xl.selectable-pill::before {
margin-right: 1.5rem;
}

.icon-space-3xl.selectable-pill::before {
margin-right: 1.75rem;
}

.selectable-pill::before {
vertical-align: baseline;
}

.selectable-pill::after {
vertical-align: baseline;
}

.selectable-pill::before {
content: "\f00d";
}

.selectable-pill::after {
content: "\f00d";
}

.peer:checked ~ .selectable-pill {
--tw-bg-opacity: 1;
background-color: rgb(55 48 163 / var(--tw-bg-opacity));
}

.peer:checked ~ .selectable-pill::before {
content: "\f00c";
}

.peer:checked ~ .selectable-pill::after {
content: "\f00c";
}

.user-pill {
margin: 0.5rem;
margin-top: 0.75rem;
Expand Down Expand Up @@ -1821,6 +1937,10 @@ table {
display: block;
}

.inline {
display: inline;
}

.flex {
display: flex;
}
Expand Down Expand Up @@ -2380,6 +2500,14 @@ table {
content: "\f021";
}

.icon-bicycle::before {
content: "\f206";
}

.icon-bicycle::after {
content: "\f206";
}

.icon-bookmark::before {
content: "\f02e";
}
Expand Down Expand Up @@ -2692,6 +2820,14 @@ table {
content: "\e445";
}

.icon-square-check::before {
content: "\f14a";
}

.icon-square-check::after {
content: "\f14a";
}

.icon-stopwatch::before {
content: "\f2f2";
}
Expand Down
7 changes: 7 additions & 0 deletions main.css
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,13 @@
}
}
}

.selectable-pill {
@apply user-pill icon-baseline icon-space-md icon-before icon-solid;
@apply icon-xmark;
@apply peer-checked:icon-check peer-checked:bg-indigo-800;
}

.user-pill {
@apply inline-block rounded-full my-3 m-2 min-w-[150px] py-2 px-5 border text-center;
@apply bg-neutral-300 hover:bg-neutral-400 border-neutral-500;
Expand Down
29 changes: 29 additions & 0 deletions pkg/app/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,32 @@ func (a *App) getWorkout(c echo.Context) (*database.Workout, error) {

return w, nil
}

func (a *App) addAllEquipment(u *database.User, data map[string]interface{}) error {
if u == nil {
return nil
}

w, err := u.GetAllEquipment(a.db)
if err != nil {
return err
}

data["equipment"] = w

return nil
}

func (a *App) getEquipment(c echo.Context) (*database.Equipment, error) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
return nil, err
}

w, err := a.getCurrentUser(c).GetEquipment(a.db, id)
if err != nil {
return nil, err
}

return w, nil
}
108 changes: 108 additions & 0 deletions pkg/app/equipment_handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package app

import (
"net/http"
"strconv"

"github.com/jovandeginste/workout-tracker/pkg/database"
"github.com/labstack/echo/v4"
)

func (a *App) addEquipment(c echo.Context) error {
u := a.getCurrentUser(c)
p := database.Equipment{}

if err := c.Bind(&p); err != nil {
return a.redirectWithError(c, a.echo.Reverse("add-equipment"), err)
}

p.UserID = u.ID

if err := p.Save(a.db); err != nil {
return a.redirectWithError(c, a.echo.Reverse("add-equipment"), err)
}

return c.Redirect(http.StatusFound, a.echo.Reverse("equipment"))
}

func (a *App) equipmentHandler(c echo.Context) error {
data := a.defaultData(c)

if err := a.addAllEquipment(a.getCurrentUser(c), data); err != nil {
return a.redirectWithError(c, a.echo.Reverse("dashboard"), err)
}

return c.Render(http.StatusOK, "equipment_list.html", data)
}

func (a *App) equipmentShowHandler(c echo.Context) error {
data := a.defaultData(c)

id, err := strconv.Atoi(c.Param("id"))
if err != nil {
return a.redirectWithError(c, "/equipment", err)
}

e, err := database.GetEquipment(a.db, id)
if err != nil {
return a.redirectWithError(c, "/equipment", err)
}

data["equipment"] = e

return c.Render(http.StatusOK, "equipment_show.html", data)
}

func (a *App) equipmentAddHandler(c echo.Context) error {
data := a.defaultData(c)
return c.Render(http.StatusOK, "equipment_add.html", data)
}

func (a *App) equipmentDeleteHandler(c echo.Context) error {
equipment, err := a.getEquipment(c)
if err != nil {
return a.redirectWithError(c, a.echo.Reverse("equipment-show", c.Param("id")), err)
}

if err := equipment.Delete(a.db); err != nil {
return a.redirectWithError(c, a.echo.Reverse("equipment-show", c.Param("id")), err)
}

a.setNotice(c, "The equipment '%s' has been deleted.", equipment.Name)

return c.Redirect(http.StatusFound, a.echo.Reverse("equipment"))
}

func (a *App) equipmentUpdateHandler(c echo.Context) error {
equipment, err := a.getEquipment(c)
if err != nil {
return a.redirectWithError(c, a.echo.Reverse("equipment-edit", c.Param("id")), err)
}

if err := c.Bind(equipment); err != nil {
return a.redirectWithError(c, a.echo.Reverse("equipment-edit", c.Param("id")), err)
}

equipment.Active = (c.FormValue("active") == "true")

if err := equipment.Save(a.db); err != nil {
return a.redirectWithError(c, a.echo.Reverse("equipment-edit", c.Param("id")), err)
}

a.setNotice(c, "The equipment '%s' has been updated.", equipment.Name)

return c.Redirect(http.StatusFound, a.echo.Reverse("equipment-show", c.Param("id")))
}

func (a *App) equipmentEditHandler(c echo.Context) error {
data := a.defaultData(c)

equipment, err := a.getEquipment(c)
if err != nil {
return a.redirectWithError(c, "/equipment", err)
}

data["equipment"] = equipment

return c.Render(http.StatusOK, "equipment_edit.html", data)
}
9 changes: 9 additions & 0 deletions pkg/app/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,5 +139,14 @@ func (a *App) secureRoutes(e *echo.Group) *echo.Group {
workoutsGroup.POST("/:id/refresh", a.workoutsRefreshHandler).Name = "workout-refresh"
workoutsGroup.GET("/add", a.workoutsAddHandler).Name = "workout-add"

equipmentGroup := secureGroup.Group("/equipment")
equipmentGroup.GET("", a.equipmentHandler).Name = "equipment"
equipmentGroup.POST("", a.addEquipment).Name = "equipment-create"
equipmentGroup.GET("/:id", a.equipmentShowHandler).Name = "equipment-show"
equipmentGroup.POST("/:id", a.equipmentUpdateHandler).Name = "equipment-update"
equipmentGroup.GET("/:id/edit", a.equipmentEditHandler).Name = "equipment-edit"
equipmentGroup.POST("/:id/delete", a.equipmentDeleteHandler).Name = "equipment-delete"
equipmentGroup.GET("/add", a.equipmentAddHandler).Name = "equipment-add"

return secureGroup
}
60 changes: 60 additions & 0 deletions pkg/database/equipment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package database

import (
"slices"

"gorm.io/gorm"
"gorm.io/gorm/clause"
)

type Equipment struct {
gorm.Model
Name string `gorm:"not null;uniqueIndex" json:"name" form:"name"` // The name of the gear
UserID uint `gorm:"not null;index"` // The ID of the user who owns the workout
Description string `gorm:"" json:"description" form:"description"` // More information about the equipment
Active bool `gorm:"default:true" json:"active" form:"active"` // Whether this equipment is active
DefaultFor []WorkoutType `gorm:"serializer:json;column:default_for" form:"default_for"` // Which workout types to add this equipment by default

User User
Workouts []Workout `gorm:"many2many:workout_equipment"`
}

type WorkoutEquipment struct {
gorm.Model
WorkoutID uint `gorm:"not null;uniqueIndex:idx_workout_equipment"` // The ID of the workout
Workout Workout
EquipmentID uint `gorm:"not null;uniqueIndex:idx_workout_equipment"` // The ID of the equipment
Equipment Equipment
}

func GetAllEquipment(db *gorm.DB) ([]*Equipment, error) {
var e []*Equipment

if err := db.Order("date DESC").Find(&e).Error; err != nil {
return nil, err
}

return e, nil
}

func GetEquipment(db *gorm.DB, id int) (*Equipment, error) {
var e Equipment

if err := db.Preload("User").First(&e, id).Error; err != nil {
return nil, err
}

return &e, nil
}

func (e *Equipment) ValidFor(wt *WorkoutType) bool {
return slices.Contains(e.DefaultFor, *wt)
}

func (e *Equipment) Delete(db *gorm.DB) error {
return db.Unscoped().Delete(e).Error
}

func (e *Equipment) Save(db *gorm.DB) error {
return db.Omit(clause.Associations).Save(e).Error
}
Loading

0 comments on commit c0dccda

Please sign in to comment.