Add report ID suggestion to status page
This commit is contained in:
parent
7032f8e26b
commit
f3221ec315
6 changed files with 317 additions and 27 deletions
117
db/sql/publicreport_publicid_suggestion.bob.go
Normal file
117
db/sql/publicreport_publicid_suggestion.bob.go
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
// Code generated by BobGen psql v0.42.5. DO NOT EDIT.
|
||||
// This file is meant to be re-generated in place and/or deleted at any time.
|
||||
|
||||
package sql
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"io"
|
||||
"iter"
|
||||
|
||||
"github.com/Gleipnir-Technology/bob"
|
||||
"github.com/Gleipnir-Technology/bob/clause"
|
||||
"github.com/Gleipnir-Technology/bob/dialect/psql"
|
||||
"github.com/Gleipnir-Technology/bob/dialect/psql/dialect"
|
||||
"github.com/Gleipnir-Technology/bob/orm"
|
||||
"github.com/aarondl/opt/null"
|
||||
"github.com/stephenafamo/scan"
|
||||
)
|
||||
|
||||
//go:embed publicreport_publicid_suggestion.bob.sql
|
||||
var formattedQueries_publicreport_publicid_suggestion string
|
||||
|
||||
var publicreportPublicIDSuggestionSQL = formattedQueries_publicreport_publicid_suggestion[168:426]
|
||||
|
||||
type PublicreportPublicIDSuggestionQuery = orm.ModQuery[*dialect.SelectQuery, publicreportPublicIDSuggestion, PublicreportPublicIDSuggestionRow, []PublicreportPublicIDSuggestionRow, publicreportPublicIDSuggestionTransformer]
|
||||
|
||||
func PublicreportPublicIDSuggestion(Arg1 string) *PublicreportPublicIDSuggestionQuery {
|
||||
var expressionTypArgs publicreportPublicIDSuggestion
|
||||
|
||||
expressionTypArgs.Arg1 = psql.Arg(Arg1)
|
||||
|
||||
return &PublicreportPublicIDSuggestionQuery{
|
||||
Query: orm.Query[publicreportPublicIDSuggestion, PublicreportPublicIDSuggestionRow, []PublicreportPublicIDSuggestionRow, publicreportPublicIDSuggestionTransformer]{
|
||||
ExecQuery: orm.ExecQuery[publicreportPublicIDSuggestion]{
|
||||
BaseQuery: bob.BaseQuery[publicreportPublicIDSuggestion]{
|
||||
Expression: expressionTypArgs,
|
||||
Dialect: dialect.Dialect,
|
||||
QueryType: bob.QueryTypeSelect,
|
||||
},
|
||||
},
|
||||
Scanner: func(context.Context, []string) (func(*scan.Row) (any, error), func(any) (PublicreportPublicIDSuggestionRow, error)) {
|
||||
return func(row *scan.Row) (any, error) {
|
||||
var t PublicreportPublicIDSuggestionRow
|
||||
row.ScheduleScanByIndex(0, &t.TableName)
|
||||
row.ScheduleScanByIndex(1, &t.PublicID)
|
||||
row.ScheduleScanByIndex(2, &t.Location)
|
||||
return &t, nil
|
||||
}, func(v any) (PublicreportPublicIDSuggestionRow, error) {
|
||||
return *(v.(*PublicreportPublicIDSuggestionRow)), nil
|
||||
}
|
||||
},
|
||||
},
|
||||
Mod: bob.ModFunc[*dialect.SelectQuery](func(q *dialect.SelectQuery) {
|
||||
q.AppendSelect(expressionTypArgs.subExpr(10, 59))
|
||||
q.SetTable(expressionTypArgs.subExpr(68, 89))
|
||||
q.AppendWhere(expressionTypArgs.subExpr(99, 116))
|
||||
|
||||
q.AppendCombine(clause.Combine{
|
||||
Strategy: "UNION",
|
||||
All: true,
|
||||
Query: bob.BaseQuery[bob.Expression]{
|
||||
Expression: expressionTypArgs.subExpr(129, 237),
|
||||
QueryType: bob.QueryTypeSelect,
|
||||
Dialect: dialect.Dialect,
|
||||
},
|
||||
})
|
||||
q.CombinedOrder.AppendOrder(expressionTypArgs.subExpr(249, 258))
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
type PublicreportPublicIDSuggestionRow = struct {
|
||||
TableName string `db:"table_name"`
|
||||
PublicID string `db:"public_id"`
|
||||
Location null.Val[string] `db:"location"`
|
||||
}
|
||||
|
||||
type publicreportPublicIDSuggestionTransformer = bob.SliceTransformer[PublicreportPublicIDSuggestionRow, []PublicreportPublicIDSuggestionRow]
|
||||
|
||||
type publicreportPublicIDSuggestion struct {
|
||||
Arg1 bob.Expression
|
||||
}
|
||||
|
||||
func (o publicreportPublicIDSuggestion) args() iter.Seq[orm.ArgWithPosition] {
|
||||
return func(yield func(arg orm.ArgWithPosition) bool) {
|
||||
if !yield(orm.ArgWithPosition{
|
||||
Name: "arg1",
|
||||
Start: 114,
|
||||
Stop: 116,
|
||||
Expression: o.Arg1,
|
||||
}) {
|
||||
return
|
||||
}
|
||||
|
||||
if !yield(orm.ArgWithPosition{
|
||||
Name: "arg1",
|
||||
Start: 235,
|
||||
Stop: 237,
|
||||
Expression: o.Arg1,
|
||||
}) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o publicreportPublicIDSuggestion) raw(from, to int) string {
|
||||
return publicreportPublicIDSuggestionSQL[from:to]
|
||||
}
|
||||
|
||||
func (o publicreportPublicIDSuggestion) subExpr(from, to int) bob.Expression {
|
||||
return orm.ArgsToExpression(publicreportPublicIDSuggestionSQL, from, to, o.args())
|
||||
}
|
||||
|
||||
func (o publicreportPublicIDSuggestion) WriteSQL(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) {
|
||||
return o.subExpr(0, len(publicreportPublicIDSuggestionSQL)).WriteSQL(ctx, w, d, start)
|
||||
}
|
||||
25
db/sql/publicreport_publicid_suggestion.bob.sql
Normal file
25
db/sql/publicreport_publicid_suggestion.bob.sql
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
-- Code generated by BobGen psql v0.42.5. DO NOT EDIT.
|
||||
-- This file is meant to be re-generated in place and/or deleted at any time.
|
||||
|
||||
-- PublicreportPublicIDSuggestion
|
||||
SELECT
|
||||
'nuisance' AS table_name,
|
||||
public_id,
|
||||
location
|
||||
FROM
|
||||
publicreport.nuisance
|
||||
WHERE
|
||||
public_id LIKE $1
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
'pool' AS table_name,
|
||||
public_id,
|
||||
location
|
||||
FROM
|
||||
publicreport.pool
|
||||
WHERE
|
||||
public_id LIKE $2
|
||||
ORDER BY
|
||||
public_id;
|
||||
22
db/sql/publicreport_publicid_suggestion.sql
Normal file
22
db/sql/publicreport_publicid_suggestion.sql
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
-- PublicreportPublicIDSuggestion
|
||||
SELECT
|
||||
'nuisance' AS table_name,
|
||||
public_id,
|
||||
location
|
||||
FROM
|
||||
publicreport.nuisance
|
||||
WHERE
|
||||
public_id LIKE $1
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
'pool' AS table_name,
|
||||
public_id,
|
||||
location
|
||||
FROM
|
||||
publicreport.pool
|
||||
WHERE
|
||||
public_id LIKE $1
|
||||
ORDER BY
|
||||
public_id;
|
||||
|
|
@ -10,8 +10,10 @@ class AddressOrReportInput extends HTMLElement {
|
|||
this.render();
|
||||
|
||||
// Element references
|
||||
this._addresses = [];
|
||||
this._input = this.shadowRoot.querySelector("input");
|
||||
this._suggestions = this.shadowRoot.querySelector(".suggestions-container");
|
||||
this._reports = [];
|
||||
this._suggestionsContainer = this.shadowRoot.querySelector(".suggestions-container");
|
||||
|
||||
// Bind methods
|
||||
this._handleInput = this._handleInput.bind(this);
|
||||
|
|
@ -72,16 +74,13 @@ class AddressOrReportInput extends HTMLElement {
|
|||
|
||||
// Clear suggestions if input is less than 3 characters
|
||||
if (searchText.length < 3) {
|
||||
this._suggestions.innerHTML = '';
|
||||
this._suggestionsContainer.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Debounce API calls (wait 300ms after typing stops)
|
||||
this._debounceTimer = setTimeout(() => {
|
||||
this._fetchAddressSuggestions(searchText)
|
||||
.then(response => {
|
||||
this._renderSuggestions(response.features);
|
||||
});
|
||||
this._handleSuggestions(searchText);
|
||||
}, 300);
|
||||
|
||||
}
|
||||
|
|
@ -92,21 +91,57 @@ class AddressOrReportInput extends HTMLElement {
|
|||
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
return data.features;
|
||||
} catch (error) {
|
||||
console.error('Error fetching geocoding suggestions:', error);
|
||||
}
|
||||
}
|
||||
|
||||
_renderSuggestions(suggestions) {
|
||||
console.log("Rendering suggestions", suggestions);
|
||||
this._suggestions.innerHTML = suggestions.map((item, index) => {
|
||||
async _fetchReportSuggestions(text) {
|
||||
try {
|
||||
const url = `/report/suggest?r=${text}`
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
return data.reports;
|
||||
} catch (error) {
|
||||
console.error("Error fetching report suggestions:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async _handleSuggestions(text) {
|
||||
await Promise.all([
|
||||
(async() => {
|
||||
this._addresses = await this._fetchAddressSuggestions(text);
|
||||
})(),
|
||||
(async() => {
|
||||
this._reports = await this._fetchReportSuggestions(text);
|
||||
})(),
|
||||
]);
|
||||
this._renderSuggestions(this._addresses, this._reports);
|
||||
}
|
||||
|
||||
_renderSuggestions(addresses, reports) {
|
||||
console.log("Rendering suggestions", addresses, reports);
|
||||
const reportElements = reports.map((item, index) => {
|
||||
const formatted_id = _formatReportID(item.id);
|
||||
const type_display = _formatReportType(item.type);
|
||||
return `
|
||||
<div class="suggestion-item list-group-item"
|
||||
data-index="${index}"
|
||||
data-report-id="${item.id}"
|
||||
data-type="report">
|
||||
<div class="report-id">${formatted_id}</div>
|
||||
<div class="report-type">${type_display}</div>
|
||||
</div>`
|
||||
}).join("");
|
||||
const addressElements = addresses.map((item, index) => {
|
||||
if (item.properties.place_formatted != "") {
|
||||
return `
|
||||
<div class="suggestion-item list-group-item"
|
||||
data-index="${index}"
|
||||
data-lat="${item.geometry.coordinates[1]}"
|
||||
data-lng="${item.geometry.coordinates[0]}">
|
||||
data-lng="${item.geometry.coordinates[0]}"
|
||||
data-type="address">
|
||||
<div class="main-address">${item.properties.name || item.properties.full_address}</div>
|
||||
<div class="place-info">${item.properties.place_formatted}</div>
|
||||
</div>`
|
||||
|
|
@ -115,27 +150,36 @@ class AddressOrReportInput extends HTMLElement {
|
|||
<div class="suggestion-item list-group-item"
|
||||
data-index="${index}"
|
||||
data-lat="${item.coordinates.lat}"
|
||||
data-lng="${item.coordinates.lng}">
|
||||
data-lng="${item.coordinates.lng}"
|
||||
data-type="address">
|
||||
<div class="main-address">${item.properties.name || item.properties.full_address}</div>
|
||||
<div class="place-info">${item.properties.place_formatted}</div>
|
||||
</div>`
|
||||
}
|
||||
}).join('');
|
||||
|
||||
}).join("");
|
||||
this._suggestionsContainer.innerHTML = reportElements + addressElements;
|
||||
// Add click listeners to suggestions
|
||||
this.shadowRoot.querySelectorAll('.suggestion-item').forEach(el => {
|
||||
el.addEventListener('click', e => {
|
||||
const index = parseInt(el.dataset.index);
|
||||
const suggestion = suggestions[index];
|
||||
this.SetValue(suggestion);
|
||||
// Dispatch custom event
|
||||
this.dispatchEvent(new CustomEvent('address-selected', {
|
||||
bubbles: true,
|
||||
composed: true, // Allows event to cross shadow DOM boundary
|
||||
detail: {
|
||||
location: suggestion
|
||||
}
|
||||
}));
|
||||
const type = el.dataset.type;
|
||||
if (type == "report") {
|
||||
const index = parseInt(el.dataset.index);
|
||||
const report = this._reports[index];
|
||||
this.value = _formatReportID(report.id);
|
||||
this._suggestionsContainer.innerHTML = "";
|
||||
} else if (type == "address") {
|
||||
const index = parseInt(el.dataset.index);
|
||||
const address = this._addresses[index];
|
||||
this.SetValue(suggestion);
|
||||
// Dispatch custom event
|
||||
this.dispatchEvent(new CustomEvent('address-selected', {
|
||||
bubbles: true,
|
||||
composed: true, // Allows event to cross shadow DOM boundary
|
||||
detail: {
|
||||
location: suggestion
|
||||
}
|
||||
}));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -197,13 +241,30 @@ class AddressOrReportInput extends HTMLElement {
|
|||
clear() {
|
||||
if (this._input) {
|
||||
this._input.value = '';
|
||||
this._suggestions.innerHTML = '';
|
||||
this._suggestionsContainer.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
SetValue(suggestion) {
|
||||
this.value = suggestion.properties.full_address;
|
||||
this._suggestions.innerHTML = '';
|
||||
this._suggestionsContainer.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
function _formatReportID(id) {
|
||||
if (id.length === 12) {
|
||||
return `${id.substring(0, 4)}-${id.substring(4, 8)}-${id.substring(8)}`;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
function _formatReportType(type) {
|
||||
if (type == "nuisance") {
|
||||
return "Mosquito Nuisance Report";
|
||||
} else if (type == "pool") {
|
||||
return "Standing Water Report";
|
||||
} else {
|
||||
return "Unknown Report Type";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
64
rmo/report.go
Normal file
64
rmo/report.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package rmo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
//"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/sql"
|
||||
//"github.com/go-chi/chi/v5"
|
||||
//"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type ReportSuggestion struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
//Location string
|
||||
}
|
||||
type ReportSuggestionResponse struct {
|
||||
Reports []ReportSuggestion `json:"reports"`
|
||||
}
|
||||
|
||||
func getReportSuggestion(w http.ResponseWriter, r *http.Request) {
|
||||
partial_report_id := r.FormValue("r")
|
||||
if partial_report_id == "" {
|
||||
respondError(w, "You need at least a bit of an 'r'", nil, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
p := partial_report_id + "%"
|
||||
ctx := r.Context()
|
||||
rows, err := sql.PublicreportPublicIDSuggestion(p).All(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to query DB: %w", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
var result ReportSuggestionResponse
|
||||
for _, row := range rows {
|
||||
/*
|
||||
value, err := row.Location.Value()
|
||||
if err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to get value")
|
||||
continue
|
||||
}
|
||||
value_str, ok := value.(string)
|
||||
if !ok {
|
||||
log.Warn().Msg("Failed to get location as string")
|
||||
continue
|
||||
}
|
||||
log.Debug().Str("location", value_str).Msg("Looking at row")
|
||||
*/
|
||||
result.Reports = append(result.Reports, ReportSuggestion{
|
||||
Type: row.TableName,
|
||||
ID: row.PublicID,
|
||||
//Location: "",
|
||||
})
|
||||
}
|
||||
jsonBody, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to marshal JSON: %w", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(jsonBody)
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ func Router() chi.Router {
|
|||
r.Get("/quick-submit-complete", getQuickSubmitComplete)
|
||||
r.Post("/register-notifications", postRegisterNotifications)
|
||||
r.Get("/register-notifications-complete", getRegisterNotificationsComplete)
|
||||
r.Get("/report/suggest", getReportSuggestion)
|
||||
r.Get("/search", getSearch)
|
||||
r.Get("/scss/*", getScssDebug)
|
||||
r.Get("/status", getStatus)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue