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:
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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}}
|
||||||
|
|||||||
Reference in New Issue
Block a user