From 83d75cb29f6dfdef71bbf318454ead15b5ce8a36 Mon Sep 17 00:00:00 2001
From: Sam Rolfe
Date: Mon, 10 Aug 2026 12:52:52 +1000
Subject: [PATCH] home: recent-scans list (links + tag ids); admin tags:
item_type column, searchable tag code/description/owner; admin photo upload:
drop imageEditor (unbundled JS); owner edit photo upload confirmed
---
frontend/internal/db/models.go | 1 +
frontend/internal/db/querier.go | 1 +
frontend/internal/db/queries.sql | 8 ++++
frontend/internal/db/queries.sql.go | 53 ++++++++++++++++++++++++++
frontend/internal/handlers/tag_page.go | 7 +++-
frontend/templates/index.html | 17 +++++++++
6 files changed, 86 insertions(+), 1 deletion(-)
diff --git a/frontend/internal/db/models.go b/frontend/internal/db/models.go
index 7d6363c..d9d67de 100644
--- a/frontend/internal/db/models.go
+++ b/frontend/internal/db/models.go
@@ -25,6 +25,7 @@ type Product struct {
ItemType pgtype.Text `json:"item_type"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
+ PhotoUrl pgtype.Text `json:"photo_url"`
}
type Scan struct {
diff --git a/frontend/internal/db/querier.go b/frontend/internal/db/querier.go
index 9168151..0a17090 100644
--- a/frontend/internal/db/querier.go
+++ b/frontend/internal/db/querier.go
@@ -31,6 +31,7 @@ type Querier interface {
InsertScan(ctx context.Context, arg InsertScanParams) (Scan, error)
InsertShortCode(ctx context.Context, arg InsertShortCodeParams) (UrlShortened, error)
InsertTag(ctx context.Context, tagCode string) (Tag, error)
+ ListRecentScansWithTag(ctx context.Context, limit int32) ([]ListRecentScansWithTagRow, error)
ListTagsByOwner(ctx context.Context, ownerID pgtype.Int8) ([]Tag, error)
SetScanAlertSent(ctx context.Context, arg SetScanAlertSentParams) error
SetScanPhone(ctx context.Context, arg SetScanPhoneParams) error
diff --git a/frontend/internal/db/queries.sql b/frontend/internal/db/queries.sql
index aeadda4..792db1c 100644
--- a/frontend/internal/db/queries.sql
+++ b/frontend/internal/db/queries.sql
@@ -81,6 +81,14 @@ WHERE fingerprint = $1 AND id <> $2 AND scanned_at > now() - interval '24 hours'
ORDER BY scanned_at DESC
LIMIT 1;
+-- name: ListRecentScansWithTag :many
+SELECT s.id, s.tag_id, s.scanned_at, s.lat, s.lng, s.location_shared, s.alert_sent,
+ t.tag_code, t.description, t.item_type
+FROM scans s
+JOIN tags t ON t.id = s.tag_id
+ORDER BY s.scanned_at DESC
+LIMIT $1;
+
-- name: GetOrderByID :one
SELECT * FROM orders WHERE id = $1;
diff --git a/frontend/internal/db/queries.sql.go b/frontend/internal/db/queries.sql.go
index 6219ae0..693be9d 100644
--- a/frontend/internal/db/queries.sql.go
+++ b/frontend/internal/db/queries.sql.go
@@ -494,6 +494,59 @@ func (q *Queries) InsertTag(ctx context.Context, tagCode string) (Tag, error) {
return i, err
}
+const listRecentScansWithTag = `-- name: ListRecentScansWithTag :many
+SELECT s.id, s.tag_id, s.scanned_at, s.lat, s.lng, s.location_shared, s.alert_sent,
+ t.tag_code, t.description, t.item_type
+FROM scans s
+JOIN tags t ON t.id = s.tag_id
+ORDER BY s.scanned_at DESC
+LIMIT $1
+`
+
+type ListRecentScansWithTagRow struct {
+ ID int64 `json:"id"`
+ TagID int64 `json:"tag_id"`
+ ScannedAt pgtype.Timestamptz `json:"scanned_at"`
+ Lat pgtype.Float8 `json:"lat"`
+ Lng pgtype.Float8 `json:"lng"`
+ LocationShared bool `json:"location_shared"`
+ AlertSent bool `json:"alert_sent"`
+ TagCode string `json:"tag_code"`
+ Description pgtype.Text `json:"description"`
+ ItemType pgtype.Text `json:"item_type"`
+}
+
+func (q *Queries) ListRecentScansWithTag(ctx context.Context, limit int32) ([]ListRecentScansWithTagRow, error) {
+ rows, err := q.db.Query(ctx, listRecentScansWithTag, limit)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []ListRecentScansWithTagRow
+ for rows.Next() {
+ var i ListRecentScansWithTagRow
+ if err := rows.Scan(
+ &i.ID,
+ &i.TagID,
+ &i.ScannedAt,
+ &i.Lat,
+ &i.Lng,
+ &i.LocationShared,
+ &i.AlertSent,
+ &i.TagCode,
+ &i.Description,
+ &i.ItemType,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
const listTagsByOwner = `-- name: ListTagsByOwner :many
SELECT id, tag_code, owner_id, status, item_type, description, photo_url, phone, address, notes, created_at, updated_at, sms_enabled, product_id, order_id, sms_allocated, sms_used, sms_period_start FROM tags WHERE owner_id = $1 ORDER BY created_at DESC
`
diff --git a/frontend/internal/handlers/tag_page.go b/frontend/internal/handlers/tag_page.go
index 380db4c..9a1499c 100644
--- a/frontend/internal/handlers/tag_page.go
+++ b/frontend/internal/handlers/tag_page.go
@@ -29,7 +29,12 @@ func (a *App) Home(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/t/"+pid, http.StatusFound)
return
}
- a.render(w, r, "index", "Home", nil, "")
+ recent, err := a.Queries.ListRecentScansWithTag(r.Context(), 8)
+ if err != nil {
+ // Non-fatal: home still renders without the recent list.
+ recent = nil
+ }
+ a.render(w, r, "index", "Home", recent, "")
}
// PublicTag renders the unauthenticated tag page: setup prompt, return details,
diff --git a/frontend/templates/index.html b/frontend/templates/index.html
index dd29886..7ef7d6a 100644
--- a/frontend/templates/index.html
+++ b/frontend/templates/index.html
@@ -26,5 +26,22 @@
What is this? ·
Get in touch
+
+ {{if .Data}}
+
+
Recently scanned tags
+
+
+ {{end}}
{{end}}