Add traps to cell details page

This commit is contained in:
Eli Ribble 2026-01-15 21:00:42 +00:00
parent 0bd1a10753
commit 885b58a0ab
No known key found for this signature in database
6 changed files with 109 additions and 22 deletions

View file

@ -132,14 +132,13 @@ class MapAggregate extends HTMLElement {
'fill-color': '#0dcaf0'
}
});
var self = this;
map.addInteraction("tegola-click-interaction", {
type: "click",
target: { layerId: "mosquito_source" },
handler: (e) => {
const coordinates = e.feature.geometry.coordinates.slice();
const properties = e.feature.properties;
self.dispatchEvent(new CustomEvent("cell-click", {
this.dispatchEvent(new CustomEvent("cell-click", {
bubbles: true,
composed: true, // Allows event to cross shadow DOM boundary
detail: {

View file

@ -33,6 +33,15 @@ type Config struct {
URLTegola string
}
type ContextCell struct {
BreedingSources []BreedingSourceSummary
CellBoundary h3.CellBoundary
Inspections []Inspection
MapData ComponentMap
Traps []Trap
Treatments []Treatment
User User
}
type ContextDashboard struct {
Config Config
CountTraps int
@ -161,12 +170,18 @@ func cell(ctx context.Context, w http.ResponseWriter, user *models.User, c int64
respondError(w, "Failed to get sources", err, http.StatusInternalServerError)
return
}
traps, err := trapsByCell(ctx, org, h3.Cell(c))
if err != nil {
respondError(w, "Failed to get traps", err, http.StatusInternalServerError)
return
}
treatments, err := treatmentsByCell(ctx, org, h3.Cell(c))
if err != nil {
respondError(w, "Failed to get treatments", err, http.StatusInternalServerError)
return
}
data := ContentCell{
data := ContextCell{
BreedingSources: sources,
CellBoundary: boundary,
Inspections: inspections,
@ -179,6 +194,7 @@ func cell(ctx context.Context, w http.ResponseWriter, user *models.User, c int64
MapboxToken: config.MapboxToken,
Zoom: resolution + 5,
},
Traps: traps,
Treatments: treatments,
User: userContent,
}

View file

@ -154,6 +154,13 @@ type TrapData struct {
Comments string `json:"comments"`
}
type Trap struct {
Active bool
Comments string
Description string
GlobalID uuid.UUID
}
type Treatment struct {
CadenceDelta time.Duration
Date *time.Time
@ -162,7 +169,18 @@ type Treatment struct {
Product string
}
func toTemplateTraps(locations []sql.TrapLocationBySourceIDRow, trap_data []sql.TrapDataByLocationIDRecentRow, counts []sql.TrapCountByLocationIDRow) ([]TrapNearby, error) {
func toTemplateTrap(traps models.FieldseekerTraplocationSlice) (results []Trap, err error) {
for _, t := range traps {
results = append(results, Trap{
Active: toBool16Or(t.Active, false),
Comments: t.Comments.GetOr(""),
Description: t.Description.GetOr(""),
GlobalID: t.Globalid,
})
}
return results, err
}
func toTemplateTrapsNearby(locations []sql.TrapLocationBySourceIDRow, trap_data []sql.TrapDataByLocationIDRecentRow, counts []sql.TrapCountByLocationIDRow) ([]TrapNearby, error) {
results := make([]TrapNearby, 0)
count_by_trap_data_id := make(map[uuid.UUID]*sql.TrapCountByLocationIDRow)
for _, c := range counts {
@ -409,3 +427,17 @@ func getTimeOrNull(v null.Val[time.Time]) *time.Time {
val := v.MustGet()
return &val
}
func toBool16Or(t null.Val[int16], def bool) bool {
if t.IsNull() {
return def
}
val := t.MustGet()
var b bool
if val == 0 {
b = false
} else {
b = true
}
return b
}

View file

@ -160,6 +160,36 @@
<!-- Right Column -->
<div class="col-md-6">
<h2 class="section-header">Traps</h2>
{{ if gt (len .Traps) 0}}
<div class="card mb-4">
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>ID</th>
<th>Active</th>
<th>Comments</th>
</tr>
</thead>
<tbody>
{{ range .Traps }}
<tr>
<td><a href="/trap/{{.GlobalID}}">{{.GlobalID|uuidShort}}</a></td>
<td>{{.Active}}</td>
<td>{{.Comments}}</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
</div>
</div>
{{ else }}
<p>No traps</p>
{{ end }}
<!-- Treatments Section -->
<h2 class="section-header">Treatment History</h2>
<div class="card mb-4">

View file

@ -28,14 +28,6 @@ type ComponentMap struct {
type ContentAuthenticatedPlaceholder struct {
User User
}
type ContentCell struct {
BreedingSources []BreedingSourceSummary
CellBoundary h3.CellBoundary
Inspections []Inspection
MapData ComponentMap
Treatments []Treatment
User User
}
type ContentMockURLs struct {
Dispatch string
DispatchResults string

View file

@ -100,7 +100,7 @@ func contentForUser(ctx context.Context, user *models.User) (User, error) {
DisplayName: user.DisplayName,
Initials: extractInitials(user.DisplayName),
Notifications: notifications,
Organization: Organization {
Organization: Organization{
ID: int(org.ID),
Name: org.Name,
},
@ -181,7 +181,7 @@ func trapsBySource(ctx context.Context, org *models.Organization, sourceID uuid.
return nil, fmt.Errorf("Failed to query trap counts: %w", err)
}
traps, err := toTemplateTraps(locations, trap_data, counts)
traps, err := toTemplateTrapsNearby(locations, trap_data, counts)
if err != nil {
return nil, fmt.Errorf("Failed to convert trap data: %w", err)
}
@ -203,6 +203,24 @@ func treatmentsBySource(ctx context.Context, org *models.Organization, sourceID
return toTemplateTreatment(rows)
}
func trapsByCell(ctx context.Context, org *models.Organization, c h3.Cell) (results []Trap, err error) {
boundary, err := c.Boundary()
if err != nil {
return results, fmt.Errorf("Failed to get cell boundary: %w", err)
}
geom_query := gisStatement(boundary)
rows, err := org.Traplocations(
sm.Where(
psql.F("ST_Within", "geospatial", geom_query),
),
sm.OrderBy("objectid"),
).All(ctx, db.PGInstance.BobDB)
if err != nil {
return results, fmt.Errorf("Failed to query rows: %w", err)
}
return toTemplateTrap(rows)
}
func treatmentsByCell(ctx context.Context, org *models.Organization, c h3.Cell) ([]Treatment, error) {
var results []Treatment
boundary, err := c.Boundary()