Skip to content

Commit 9d1438a

Browse files
Merge pull request #26 from bit8bytes/refactor-equipment
refactor: forms, custom types for properties, htmx lazy loading of co…
2 parents 6dcab80 + d4b65aa commit 9d1438a

54 files changed

Lines changed: 1295 additions & 1170 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cmd/web/handlers:equipment.go

Lines changed: 102 additions & 170 deletions
Large diffs are not rendered by default.

cmd/web/handlers:equipment:categories.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,18 @@ import (
99
"github.com/bit8bytes/gearberg/internal/database"
1010
"github.com/bit8bytes/gearberg/internal/equipment/categories"
1111
"github.com/bit8bytes/gearberg/internal/httperr"
12+
"github.com/bit8bytes/gearberg/internal/templates/fragments"
1213
"github.com/bit8bytes/gearberg/internal/templates/pages"
14+
"github.com/bit8bytes/gearberg/pkg/htmx"
1315
"github.com/segmentio/ksuid"
1416
)
1517

1618
type equipmentCategoriesData struct {
1719
OrgID string
1820
Categories []categories.EquipmentCategory
1921
MaxCategories int
22+
SelectedID string
23+
SelectedName string
2024
}
2125

2226
type equipmentCategoryData struct {
@@ -205,3 +209,29 @@ func (app *application) postDeleteEquipmentCategory(w http.ResponseWriter, r *ht
205209
http.Redirect(w, r, "/orgs/"+url.PathEscape(orgID)+"/settings/equipment-categories", http.StatusSeeOther)
206210
return nil
207211
}
212+
213+
func (app *application) getEquipmentCategoriesFragment(w http.ResponseWriter, r *http.Request) *httperr.Error {
214+
ctx := r.Context()
215+
orgID := r.PathValue("org_id")
216+
217+
if !htmx.IsRequest(r) {
218+
http.Redirect(w, r, "/orgs/"+url.PathEscape(orgID)+"/settings/equipment-categories", http.StatusSeeOther)
219+
return nil
220+
}
221+
222+
selected := r.URL.Query().Get("selected")
223+
selectedName := r.URL.Query().Get("selected_name")
224+
225+
cats, err := app.services.equipmentcategories.GetByOrgID(ctx, orgID)
226+
if err != nil {
227+
return &httperr.Error{
228+
Error: fmt.Errorf("getEquipmentCategoriesFragment: %w", err),
229+
Message: "Failed to retrieve categories.",
230+
Code: http.StatusInternalServerError,
231+
}
232+
}
233+
234+
tmplData := app.html.TemplateData(r)
235+
tmplData.Data = equipmentCategoriesData{OrgID: orgID, Categories: cats, SelectedID: selected, SelectedName: selectedName}
236+
return app.html.RenderFragment(w, r, http.StatusOK, fragments.EquipmentCategories, tmplData)
237+
}

cmd/web/handlers:equipment:export.go

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package main
22

33
import (
44
"encoding/csv"
5-
"fmt"
65
"net/http"
76
"strconv"
87

@@ -47,34 +46,20 @@ func (app *application) getEquipmentExport(w http.ResponseWriter, r *http.Reques
4746
item.CategoryName,
4847
mfrByID[item.ManufacturerID],
4948
item.LocationName,
50-
exportPrice(item.RentalPrice),
51-
exportPrice(item.PurchasePrice),
49+
item.Pricing.RentalPrice.ToDecimal(),
50+
item.Pricing.PurchasePrice.ToDecimal(),
5251
item.Notes,
53-
exportOptInt(item.WeightG),
54-
exportOptInt(item.WidthMM),
55-
exportOptInt(item.HeightMM),
56-
exportOptInt(item.DepthMM),
57-
exportOptInt(item.VoltageV),
58-
exportOptInt(item.CurrentMA),
59-
exportOptInt(item.PowerMW),
60-
exportOptInt(item.WireGaugeMM2X100),
52+
item.Properties.Weight.ToKG(),
53+
item.Properties.Width.ToCM(),
54+
item.Properties.Height.ToCM(),
55+
item.Properties.Depth.ToCM(),
56+
item.Properties.Voltage.ToV(),
57+
item.Properties.Current.ToA(),
58+
item.Properties.Power.ToW(),
59+
item.Properties.WireGauge.String(),
6160
strconv.FormatInt(item.TotalStock, 10),
6261
})
6362
}
6463
cw.Flush()
6564
return nil
6665
}
67-
68-
func exportPrice(v *int64) string {
69-
if v == nil {
70-
return ""
71-
}
72-
return fmt.Sprintf("%.2f", float64(*v)/100)
73-
}
74-
75-
func exportOptInt(v *int64) string {
76-
if v == nil {
77-
return ""
78-
}
79-
return strconv.FormatInt(*v, 10)
80-
}

cmd/web/handlers:equipment:import.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func (app *application) postEquipmentImport(w http.ResponseWriter, r *http.Reque
6363

6464
rawRows, parseErr := parseImportCSV(f)
6565
if parseErr != "" {
66-
return reRender("Failed to parse CSV.")
66+
return reRender(parseErr)
6767
}
6868

6969
importID, err := app.services.equipmentImports.Stage(ctx, orgID, rawRows)
@@ -125,9 +125,9 @@ func (app *application) postEquipmentImportConfirm(w http.ResponseWriter, r *htt
125125
return nil
126126
}
127127

128-
const importTemplateCSV = "Name,Type,Usage,Category,Manufacturer,Location,Rental Price,Resale Price,Notes,Weight (g),Width (mm),Height (mm),Depth (mm),Voltage (V),Current (mA),Power (mW),Wire Gauge (mm² ×100),Quantity\n" +
129-
"Shure SM58,Bulk,Rental,Audio,Shure,Main Warehouse,15.00,99.00,Cardioid dynamic vocal microphone,298,47,47,162,,,,,7\n" +
130-
"Sony SRS-XB43,Serialized,Rental,Audio,Sony,Main Warehouse,25.00,180.00,Portable Bluetooth speaker with extra bass,900,220,220,95,5,2400,12000,,4\n"
128+
const importTemplateCSV = "Name,Type,Usage,Category,Manufacturer,Location,Rental Price,Resale Price,Notes,Weight (kg),Width (cm),Height (cm),Depth (cm),Voltage (V),Current (A),Power (W),Wire Gauge (mm² ×100),Quantity\n" +
129+
"Shure SM58,Bulk,Rental,Audio,Shure,Main Warehouse,15.00,99.00,Cardioid dynamic vocal microphone,0.298,4.7,4.7,16.2,,,,,7\n" +
130+
"Sony SRS-XB43,Serialized,Rental,Audio,Sony,Main Warehouse,25.00,180.00,Portable Bluetooth speaker with extra bass,0.9,22,22,9.5,5,2.4,12,,4\n"
131131

132132
// getEquipmentImportTemplate serves a ready-to-fill CSV template for download.
133133
func (app *application) getEquipmentImportTemplate(w http.ResponseWriter, _ *http.Request) *httperr.Error {
@@ -199,8 +199,8 @@ func readImportRows(cr *csv.Reader) ([]imports.RawRow, string) {
199199
HeightMm: strings.TrimSpace(record[11]),
200200
DepthMm: strings.TrimSpace(record[12]),
201201
VoltageV: strings.TrimSpace(record[13]),
202-
CurrentMa: strings.TrimSpace(record[14]),
203-
PowerMw: strings.TrimSpace(record[15]),
202+
CurrentA: strings.TrimSpace(record[14]),
203+
PowerW: strings.TrimSpace(record[15]),
204204
WireGaugeMM2X100: strings.TrimSpace(record[16]),
205205
Quantity: strings.TrimSpace(record[17]),
206206
})

cmd/web/handlers:equipment:locations.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,18 @@ import (
99
"github.com/bit8bytes/gearberg/internal/database"
1010
"github.com/bit8bytes/gearberg/internal/equipment/locations"
1111
"github.com/bit8bytes/gearberg/internal/httperr"
12+
"github.com/bit8bytes/gearberg/internal/templates/fragments"
1213
"github.com/bit8bytes/gearberg/internal/templates/pages"
14+
"github.com/bit8bytes/gearberg/pkg/htmx"
1315
"github.com/segmentio/ksuid"
1416
)
1517

1618
type locationsData struct {
1719
OrgID string
1820
Locations []locations.Location
1921
MaxLocations int
22+
SelectedID string
23+
SelectedName string
2024
}
2125

2226
type locationData struct {
@@ -192,3 +196,29 @@ func (app *application) postDeleteLocation(w http.ResponseWriter, r *http.Reques
192196
http.Redirect(w, r, dest, http.StatusSeeOther) //nolint:gosec // dest is a hard-coded path with org_id from path value.
193197
return nil
194198
}
199+
200+
func (app *application) getEquipmentLocationsFragment(w http.ResponseWriter, r *http.Request) *httperr.Error {
201+
ctx := r.Context()
202+
orgID := r.PathValue("org_id")
203+
204+
if !htmx.IsRequest(r) {
205+
http.Redirect(w, r, "/orgs/"+url.PathEscape(orgID)+"/settings/locations", http.StatusSeeOther)
206+
return nil
207+
}
208+
209+
selected := r.URL.Query().Get("selected")
210+
selectedName := r.URL.Query().Get("selected_name")
211+
212+
locs, err := app.services.locations.GetByOrgID(ctx, orgID)
213+
if err != nil {
214+
return &httperr.Error{
215+
Error: fmt.Errorf("getEquipmentLocationsFragment: %w", err),
216+
Message: "Failed to retrieve locations.",
217+
Code: http.StatusInternalServerError,
218+
}
219+
}
220+
221+
tmplData := app.html.TemplateData(r)
222+
tmplData.Data = locationsData{OrgID: orgID, Locations: locs, SelectedID: selected, SelectedName: selectedName}
223+
return app.html.RenderFragment(w, r, http.StatusOK, fragments.WarehouseLocations, tmplData)
224+
}

cmd/web/handlers:equipment:manufacturers.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,18 @@ import (
99
"github.com/bit8bytes/gearberg/internal/database"
1010
"github.com/bit8bytes/gearberg/internal/equipment/manufacturers"
1111
"github.com/bit8bytes/gearberg/internal/httperr"
12+
"github.com/bit8bytes/gearberg/internal/templates/fragments"
1213
"github.com/bit8bytes/gearberg/internal/templates/pages"
14+
"github.com/bit8bytes/gearberg/pkg/htmx"
1315
"github.com/segmentio/ksuid"
1416
)
1517

1618
type manufacturersData struct {
1719
OrgID string
1820
Manufacturers []manufacturers.Manufacturer
1921
MaxManufacturers int
22+
SelectedID string
23+
SelectedName string
2024
}
2125

2226
type manufacturerData struct {
@@ -207,3 +211,29 @@ func (app *application) postDeleteManufacturer(w http.ResponseWriter, r *http.Re
207211
http.Redirect(w, r, dest, http.StatusSeeOther) //nolint:gosec
208212
return nil
209213
}
214+
215+
func (app *application) getEquipmentManufacturersFragment(w http.ResponseWriter, r *http.Request) *httperr.Error {
216+
ctx := r.Context()
217+
orgID := r.PathValue("org_id")
218+
219+
if !htmx.IsRequest(r) {
220+
http.Redirect(w, r, "/orgs/"+url.PathEscape(orgID)+"/settings/manufacturers", http.StatusSeeOther)
221+
return nil
222+
}
223+
224+
selected := r.URL.Query().Get("selected")
225+
selectedName := r.URL.Query().Get("selected_name")
226+
227+
mfrs, err := app.services.manufacturers.GetByOrgID(ctx, orgID)
228+
if err != nil {
229+
return &httperr.Error{
230+
Error: fmt.Errorf("getEquipmentManufacturersFragment: %w", err),
231+
Message: "Failed to retrieve manufacturers.",
232+
Code: http.StatusInternalServerError,
233+
}
234+
}
235+
236+
tmplData := app.html.TemplateData(r)
237+
tmplData.Data = manufacturersData{OrgID: orgID, Manufacturers: mfrs, SelectedID: selected, SelectedName: selectedName}
238+
return app.html.RenderFragment(w, r, http.StatusOK, fragments.EquipmentManufacturers, tmplData)
239+
}

cmd/web/handlers:orgs.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/bit8bytes/gearberg/internal/database"
99
"github.com/bit8bytes/gearberg/internal/httperr"
1010
"github.com/bit8bytes/gearberg/internal/orgs"
11+
"github.com/bit8bytes/gearberg/internal/orgs/settings"
1112
"github.com/bit8bytes/gearberg/internal/storage"
1213
"github.com/bit8bytes/gearberg/internal/templates/pages"
1314
"github.com/segmentio/ksuid"
@@ -84,6 +85,21 @@ func (app *application) postOrgsNew(w http.ResponseWriter, r *http.Request) *htt
8485
}
8586
}
8687

88+
_, err = app.services.orgsettings.Upsert(ctx, settings.UpsertOrgSettings{
89+
ID: ksuid.New().String(),
90+
OrgID: org.ID,
91+
Currency: app.options.DefaultCurrency,
92+
VatRate: app.options.DefaultVatRateBasisPoints(),
93+
Timezone: app.options.DefaultTimezone,
94+
})
95+
if err != nil {
96+
return &httperr.Error{
97+
Error: fmt.Errorf("postOrgsNew: seed default settings: %w", err),
98+
Message: "Failed to initialise org settings.",
99+
Code: http.StatusInternalServerError,
100+
}
101+
}
102+
87103
http.Redirect(w, r, fmt.Sprintf("/orgs/%s/equipment", org.ID), http.StatusSeeOther)
88104
return nil
89105
}

cmd/web/handlers:orgs:settings.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package main
22

33
import (
4+
"fmt"
45
"net/http"
6+
"net/url"
57

68
"github.com/bit8bytes/gearberg/internal/httperr"
79
"github.com/bit8bytes/gearberg/internal/orgs/settings"
10+
"github.com/bit8bytes/gearberg/internal/templates/fragments"
811
"github.com/bit8bytes/gearberg/internal/templates/pages"
12+
"github.com/bit8bytes/gearberg/pkg/htmx"
913
"github.com/segmentio/ksuid"
1014
)
1115

@@ -14,6 +18,38 @@ type orgSettingsData struct {
1418
OrgID string
1519
}
1620

21+
type orgCurrencyData struct {
22+
Currency string
23+
}
24+
25+
func (app *application) getOrgCurrencyFragment(w http.ResponseWriter, r *http.Request) *httperr.Error {
26+
ctx := r.Context()
27+
orgID := r.PathValue("org_id")
28+
29+
if !htmx.IsRequest(r) {
30+
http.Redirect(w, r, "/orgs/"+url.PathEscape(orgID)+"/settings", http.StatusSeeOther)
31+
return nil
32+
}
33+
34+
s, err := app.services.orgsettings.GetByOrgID(ctx, orgID)
35+
if err != nil {
36+
return &httperr.Error{
37+
Error: fmt.Errorf("getOrgCurrencyFragment: %w", err),
38+
Message: "Failed to retrieve org settings.",
39+
Code: http.StatusInternalServerError,
40+
}
41+
}
42+
43+
currency := ""
44+
if s != nil {
45+
currency = s.Currency
46+
}
47+
48+
tmplData := app.html.TemplateData(r)
49+
tmplData.Data = orgCurrencyData{Currency: currency}
50+
return app.html.RenderFragment(w, r, http.StatusOK, fragments.OrgCurrency, tmplData)
51+
}
52+
1753
func (app *application) getOrgSettings(w http.ResponseWriter, r *http.Request) *httperr.Error {
1854
ctx := r.Context()
1955
id := r.PathValue("org_id")

cmd/web/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,12 @@ func runServe(args []string) error {
8585

8686
log := setupLogger(options.LogLevel.Level())
8787

88-
cache, err := parseTemplates()
88+
base, cache, err := parseTemplates()
8989
if err != nil {
9090
return fmt.Errorf("load templates: %w", err)
9191
}
9292

93-
html := htmlpkg.New(log, cache, revision)
93+
html := htmlpkg.New(log, base, cache, revision)
9494

9595
db, err := setupDatabase(ctx, options)
9696
if err != nil {

cmd/web/middleware.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import (
1010

1111
"github.com/bit8bytes/gearberg/internal/nonce"
1212
"github.com/bit8bytes/gearberg/internal/storage"
13-
"github.com/bit8bytes/gearberg/internal/tokens"
1413
"github.com/bit8bytes/gearberg/internal/trace"
14+
"github.com/bit8bytes/gearberg/pkg/tokens"
1515
)
1616

1717
func withTrace(next http.Handler) http.Handler {

0 commit comments

Comments
 (0)