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

This commit is contained in:
2026-08-10 12:52:52 +10:00
parent 9158681001
commit 83d75cb29f
6 changed files with 86 additions and 1 deletions

View File

@@ -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 {

View File

@@ -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

View File

@@ -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;

View File

@@ -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
`

View File

@@ -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,

View File

@@ -26,5 +26,22 @@
<a href="/what-is-this" class="hover:underline">What is this?</a> ·
<a href="/contact" class="hover:underline">Get in touch</a>
</p>
{{if .Data}}
<div class="mx-auto mt-12 max-w-2xl">
<h2 class="text-xl font-bold text-left">Recently scanned tags</h2>
<ul class="mt-3 divide-y divide-stone-200 rounded-xl border border-stone-200 bg-white shadow-sm">
{{range .Data}}
<li class="flex items-center justify-between px-4 py-3">
<a href="/t/{{.TagCode}}" class="flex items-center gap-3 hover:opacity-80">
<span class="font-mono text-xs text-stone-500">{{.TagCode}}</span>
<span class="text-sm font-medium text-stone-800">{{if .Description.Valid}}{{.Description.String}}{{else}}{{if .ItemType.Valid}}{{.ItemType.String}}{{else}}Item{{end}}{{end}}</span>
</a>
<span class="text-xs text-stone-400">{{.ScannedAt.Time.Local.Format "2 Jan 3:04 pm"}}{{if .LocationShared}} · 📍{{end}}</span>
</li>
{{end}}
</ul>
</div>
{{end}}
</div>
{{end}}