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"` ItemType pgtype.Text `json:"item_type"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"`
PhotoUrl pgtype.Text `json:"photo_url"`
} }
type Scan struct { type Scan struct {

View File

@@ -31,6 +31,7 @@ type Querier interface {
InsertScan(ctx context.Context, arg InsertScanParams) (Scan, error) InsertScan(ctx context.Context, arg InsertScanParams) (Scan, error)
InsertShortCode(ctx context.Context, arg InsertShortCodeParams) (UrlShortened, error) InsertShortCode(ctx context.Context, arg InsertShortCodeParams) (UrlShortened, error)
InsertTag(ctx context.Context, tagCode string) (Tag, 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) ListTagsByOwner(ctx context.Context, ownerID pgtype.Int8) ([]Tag, error)
SetScanAlertSent(ctx context.Context, arg SetScanAlertSentParams) error SetScanAlertSent(ctx context.Context, arg SetScanAlertSentParams) error
SetScanPhone(ctx context.Context, arg SetScanPhoneParams) 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 ORDER BY scanned_at DESC
LIMIT 1; 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 -- name: GetOrderByID :one
SELECT * FROM orders WHERE id = $1; 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 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 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 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) http.Redirect(w, r, "/t/"+pid, http.StatusFound)
return 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, // 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="/what-is-this" class="hover:underline">What is this?</a> ·
<a href="/contact" class="hover:underline">Get in touch</a> <a href="/contact" class="hover:underline">Get in touch</a>
</p> </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> </div>
{{end}} {{end}}