Move html pages to a package
That way I can separate out HTML for the public-facing report system and Nidus sync.
This commit is contained in:
parent
8c1026a653
commit
4c23eba5d7
51 changed files with 45 additions and 43 deletions
103
htmlpage/h3.go
Normal file
103
htmlpage/h3.go
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
package htmlpage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Gleipnir-Technology/go-geojson2h3/v2"
|
||||
"github.com/tidwall/geojson"
|
||||
"github.com/uber/h3-go/v4"
|
||||
)
|
||||
|
||||
/*
|
||||
func h3ToBoundsGeoJSON(c h3.Cell) (string, error) {
|
||||
b, err := h3.CellToBoundary(c)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get cell boundary", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
features, err := geojson2h3.ToFeatureCollection(b)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to convert boundary to
|
||||
}
|
||||
*/
|
||||
|
||||
func toH3Cell(s string) (h3.Cell, error) {
|
||||
c := h3.CellFromString(s)
|
||||
if !c.IsValid() {
|
||||
return c, fmt.Errorf("Invalid cell definition '%s'", s)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
func h3ToGeoJSON(indexes []h3.Cell) (interface{}, error) {
|
||||
featureCollection, err := geojson2h3.ToFeatureCollection(indexes)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to get feature collection: %w", err)
|
||||
}
|
||||
return featureCollection.JSON(), nil
|
||||
}
|
||||
|
||||
func main2() {
|
||||
resolution := 9
|
||||
object, err := geojson.Parse(`{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"shape":"Polygon","name":"Unnamed Layer","category":"default"},"geometry":{"type":"Polygon","coordinates":[[[-73.901303,40.756892],[-73.893924,40.743755],[-73.871476,40.756278],[-73.863378,40.764175],[-73.871444,40.768467],[-73.879852,40.760014],[-73.885515,40.764045],[-73.891522,40.761054],[-73.901303,40.756892]]]},"id":"a6ca1b7e-9ddf-4425-ad07-8a895f7d6ccf"}]}`, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
indexes, err := geojson2h3.ToH3(resolution, object)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, index := range indexes {
|
||||
fmt.Printf("h3index: %s\n", index.String())
|
||||
}
|
||||
|
||||
featureCollection, err := geojson2h3.ToFeatureCollection(indexes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Polyfill:")
|
||||
fmt.Println(featureCollection.JSON())
|
||||
}
|
||||
|
||||
// Given a cell at a smaller resolution remap it to the larger resolution
|
||||
func scaleCell(cell h3.Cell, resolution int) (h3.Cell, error) {
|
||||
r := cell.Resolution()
|
||||
if r == resolution {
|
||||
return cell, nil
|
||||
}
|
||||
latLong, err := cell.LatLng()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Failed to get latlng: %w", err)
|
||||
}
|
||||
scaled, err := h3.LatLngToCell(latLong, resolution)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Failed to create latlng: %w", err)
|
||||
}
|
||||
return scaled, nil
|
||||
}
|
||||
|
||||
func getCell(x, y float64, resolution int) (h3.Cell, error) {
|
||||
latLng := h3.NewLatLng(y, x)
|
||||
return h3.LatLngToCell(latLng, resolution)
|
||||
}
|
||||
|
||||
func cellToPostgisGeometry(c h3.Cell) (string, error) {
|
||||
boundary, err := h3.CellToBoundary(c)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Failed to get cell boundary: %w", err)
|
||||
}
|
||||
var sb strings.Builder
|
||||
|
||||
for i, p := range boundary {
|
||||
if i > 0 {
|
||||
sb.WriteString(",")
|
||||
}
|
||||
fmt.Fprintf(&sb, "%g %g", p.Lng, p.Lat)
|
||||
}
|
||||
// add the first point on to the end to close the polygon
|
||||
sb.WriteString(",")
|
||||
fmt.Fprintf(&sb, "%g %g", boundary[0].Lng, boundary[0].Lat)
|
||||
|
||||
return fmt.Sprintf("POLYGON((%s))", sb.String()), nil
|
||||
}
|
||||
965
htmlpage/html.go
Normal file
965
htmlpage/html.go
Normal file
|
|
@ -0,0 +1,965 @@
|
|||
package htmlpage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/sql"
|
||||
"github.com/aarondl/opt/null"
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/stephenafamo/bob"
|
||||
"github.com/stephenafamo/bob/dialect/psql"
|
||||
"github.com/stephenafamo/bob/dialect/psql/sm"
|
||||
"github.com/uber/h3-go/v4"
|
||||
)
|
||||
|
||||
//go:embed templates/*
|
||||
var embeddedFiles embed.FS
|
||||
|
||||
var MapboxToken string
|
||||
|
||||
// Authenticated pages
|
||||
var (
|
||||
cell = newBuiltTemplate("cell", "authenticated")
|
||||
dashboard = newBuiltTemplate("dashboard", "authenticated")
|
||||
oauthPrompt = newBuiltTemplate("oauth-prompt", "authenticated")
|
||||
settings = newBuiltTemplate("settings", "authenticated")
|
||||
source = newBuiltTemplate("source", "authenticated")
|
||||
)
|
||||
|
||||
// Unauthenticated pages
|
||||
var (
|
||||
admin = newBuiltTemplate("admin", "base")
|
||||
dataEntry = newBuiltTemplate("data-entry", "base")
|
||||
dataEntryGood = newBuiltTemplate("data-entry-good", "base")
|
||||
dataEntryBad = newBuiltTemplate("data-entry-bad", "base")
|
||||
dispatch = newBuiltTemplate("dispatch", "base")
|
||||
dispatchResults = newBuiltTemplate("dispatch-results", "base")
|
||||
mockRoot = newBuiltTemplate("mock-root", "base")
|
||||
reportPage = newBuiltTemplate("report", "base")
|
||||
reportConfirmation = newBuiltTemplate("report-confirmation", "base")
|
||||
reportContribute = newBuiltTemplate("report-contribute", "base")
|
||||
reportDetail = newBuiltTemplate("report-detail", "base")
|
||||
reportEvidence = newBuiltTemplate("report-evidence", "base")
|
||||
reportSchedule = newBuiltTemplate("report-schedule", "base")
|
||||
reportUpdate = newBuiltTemplate("report-update", "base")
|
||||
serviceRequest = newBuiltTemplate("service-request", "base")
|
||||
serviceRequestDetail = newBuiltTemplate("service-request-detail", "base")
|
||||
serviceRequestLocation = newBuiltTemplate("service-request-location", "base")
|
||||
serviceRequestMosquito = newBuiltTemplate("service-request-mosquito", "base")
|
||||
serviceRequestPool = newBuiltTemplate("service-request-pool", "base")
|
||||
serviceRequestQuick = newBuiltTemplate("service-request-quick", "base")
|
||||
serviceRequestQuickConfirmation = newBuiltTemplate("service-request-quick-confirmation", "base")
|
||||
serviceRequestUpdates = newBuiltTemplate("service-request-updates", "base")
|
||||
settingRoot = newBuiltTemplate("setting-mock", "base")
|
||||
settingIntegration = newBuiltTemplate("setting-integration", "base")
|
||||
settingPesticide = newBuiltTemplate("setting-pesticide", "base")
|
||||
settingPesticideAdd = newBuiltTemplate("setting-pesticide-add", "base")
|
||||
settingUsers = newBuiltTemplate("setting-user", "base")
|
||||
settingUsersAdd = newBuiltTemplate("setting-user-add", "base")
|
||||
signin = newBuiltTemplate("signin", "base")
|
||||
signup = newBuiltTemplate("signup", "base")
|
||||
)
|
||||
var components = [...]string{"header", "map"}
|
||||
var templatesByFilename = make(map[string]BuiltTemplate, 0)
|
||||
|
||||
type BreedingSourceSummary struct {
|
||||
ID uuid.UUID
|
||||
Type string
|
||||
LastInspected *time.Time
|
||||
LastTreated *time.Time
|
||||
}
|
||||
|
||||
type BuiltTemplate struct {
|
||||
files []string
|
||||
name string
|
||||
template *template.Template
|
||||
}
|
||||
|
||||
type MapMarker struct {
|
||||
LatLng h3.LatLng
|
||||
}
|
||||
type ComponentMap struct {
|
||||
Center h3.LatLng
|
||||
GeoJSON interface{}
|
||||
MapboxToken string
|
||||
Markers []MapMarker
|
||||
Zoom int
|
||||
}
|
||||
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
|
||||
ReportConfirmation string
|
||||
ReportDetail string
|
||||
ReportContribute string
|
||||
ReportEvidence string
|
||||
ReportSchedule string
|
||||
ReportUpdate string
|
||||
Root string
|
||||
Setting string
|
||||
SettingIntegration string
|
||||
SettingPesticide string
|
||||
SettingPesticideAdd string
|
||||
SettingUser string
|
||||
SettingUserAdd string
|
||||
}
|
||||
type ContentMock struct {
|
||||
DistrictName string
|
||||
URLs ContentMockURLs
|
||||
}
|
||||
type ContentReportDetail struct {
|
||||
NextURL string
|
||||
UpdateURL string
|
||||
}
|
||||
type ContentReportDiagnostic struct {
|
||||
}
|
||||
type ContentDashboard struct {
|
||||
CountInspections int
|
||||
CountMosquitoSources int
|
||||
CountServiceRequests int
|
||||
Geo template.JS
|
||||
IsSyncOngoing bool
|
||||
LastSync *time.Time
|
||||
MapData ComponentMap
|
||||
Org string
|
||||
RecentRequests []ServiceRequestSummary
|
||||
User User
|
||||
}
|
||||
|
||||
type ContentDashboardLoading struct {
|
||||
User User
|
||||
}
|
||||
|
||||
type ContentPlaceholder struct {
|
||||
}
|
||||
type ContentSignin struct {
|
||||
InvalidCredentials bool
|
||||
}
|
||||
type ContentSignup struct{}
|
||||
type ContentSource struct {
|
||||
Inspections []Inspection
|
||||
MapData ComponentMap
|
||||
Source *BreedingSourceDetail
|
||||
Traps []TrapNearby
|
||||
Treatments []Treatment
|
||||
//TreatmentCadence TreatmentCadence
|
||||
TreatmentModels []TreatmentModel
|
||||
User User
|
||||
}
|
||||
type Inspection struct {
|
||||
Action string
|
||||
Date *time.Time
|
||||
Notes string
|
||||
Location string
|
||||
LocationID uuid.UUID
|
||||
}
|
||||
type Link struct {
|
||||
Href string
|
||||
Title string
|
||||
}
|
||||
type ServiceRequestSummary struct {
|
||||
Date time.Time
|
||||
Location string
|
||||
Status string
|
||||
}
|
||||
type User struct {
|
||||
DisplayName string
|
||||
Initials string
|
||||
Notifications []Notification
|
||||
Username string
|
||||
}
|
||||
|
||||
func (bt *BuiltTemplate) ExecuteTemplate(w io.Writer, data any) error {
|
||||
name := bt.files[0] + ".html"
|
||||
if bt.template == nil {
|
||||
templ, err := parseFromDisk(bt.files)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to parse template file: %w", err)
|
||||
}
|
||||
if templ == nil {
|
||||
w.Write([]byte("Failed to read from disk: "))
|
||||
return errors.New("Template parsing failed")
|
||||
}
|
||||
return templ.ExecuteTemplate(w, name, data)
|
||||
} else {
|
||||
return bt.template.ExecuteTemplate(w, name, data)
|
||||
}
|
||||
}
|
||||
|
||||
func bigNumber(n int) string {
|
||||
// Convert the number to a string
|
||||
numStr := strconv.FormatInt(int64(n), 10)
|
||||
|
||||
// Add commas every three digits from the right
|
||||
var result strings.Builder
|
||||
for i, char := range numStr {
|
||||
if i > 0 && (len(numStr)-i)%3 == 0 {
|
||||
result.WriteByte(',')
|
||||
}
|
||||
result.WriteRune(char)
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func contentForUser(ctx context.Context, user *models.User) (User, error) {
|
||||
notifications, err := notificationsForUser(ctx, user)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
return User{
|
||||
DisplayName: user.DisplayName,
|
||||
Initials: extractInitials(user.DisplayName),
|
||||
Notifications: notifications,
|
||||
Username: user.Username,
|
||||
}, nil
|
||||
|
||||
}
|
||||
func extractInitials(name string) string {
|
||||
parts := strings.Fields(name)
|
||||
var initials strings.Builder
|
||||
|
||||
for _, part := range parts {
|
||||
if len(part) > 0 {
|
||||
initials.WriteString(strings.ToUpper(string(part[0])))
|
||||
}
|
||||
}
|
||||
|
||||
return initials.String()
|
||||
}
|
||||
|
||||
func Cell(ctx context.Context, w http.ResponseWriter, user *models.User, c int64) {
|
||||
org, err := user.Organization().One(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get org", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
userContent, err := contentForUser(ctx, user)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get user", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
center, err := h3.Cell(c).LatLng()
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get center", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
boundary, err := h3.Cell(c).Boundary()
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get boundary", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
inspections, err := inspectionsByCell(ctx, org, h3.Cell(c))
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get inspections by cell", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
geojson, err := h3ToGeoJSON([]h3.Cell{h3.Cell(c)})
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get boundaries", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
resolution := h3.Cell(c).Resolution()
|
||||
sources, err := breedingSourcesByCell(ctx, org, h3.Cell(c))
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get sources", 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{
|
||||
BreedingSources: sources,
|
||||
CellBoundary: boundary,
|
||||
Inspections: inspections,
|
||||
MapData: ComponentMap{
|
||||
Center: h3.LatLng{
|
||||
Lat: center.Lat,
|
||||
Lng: center.Lng,
|
||||
},
|
||||
GeoJSON: geojson,
|
||||
MapboxToken: MapboxToken,
|
||||
Zoom: resolution + 5,
|
||||
},
|
||||
Treatments: treatments,
|
||||
User: userContent,
|
||||
}
|
||||
renderOrError(w, cell, &data)
|
||||
}
|
||||
|
||||
func Dashboard(ctx context.Context, w http.ResponseWriter, user *models.User) {
|
||||
org, err := user.Organization().One(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get org", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
var lastSync *time.Time
|
||||
sync, err := org.FieldseekerSyncs(sm.OrderBy("created").Desc()).One(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
if err.Error() != "sql: no rows in result set" {
|
||||
respondError(w, "Failed to get syncs", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
lastSync = &sync.Created
|
||||
}
|
||||
is_syncing := isSyncOngoing(org.ID)
|
||||
inspectionCount, err := org.Mosquitoinspections().Count(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get inspection count", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
sourceCount, err := org.Pointlocations().Count(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get source count", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
serviceCount, err := org.Servicerequests().Count(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get service count", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
recentRequests, err := org.Servicerequests(sm.OrderBy("creationdate").Desc(), sm.Limit(10)).All(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get recent service", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
requests := make([]ServiceRequestSummary, 0)
|
||||
for _, r := range recentRequests {
|
||||
requests = append(requests, ServiceRequestSummary{
|
||||
Date: r.Creationdate.MustGet(),
|
||||
Location: r.Reqaddr1.MustGet(),
|
||||
Status: "Completed",
|
||||
})
|
||||
}
|
||||
userContent, err := contentForUser(ctx, user)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get user context", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
data := ContentDashboard{
|
||||
CountInspections: int(inspectionCount),
|
||||
CountMosquitoSources: int(sourceCount),
|
||||
CountServiceRequests: int(serviceCount),
|
||||
IsSyncOngoing: is_syncing,
|
||||
LastSync: lastSync,
|
||||
MapData: ComponentMap{
|
||||
MapboxToken: MapboxToken,
|
||||
},
|
||||
Org: org.Name.MustGet(),
|
||||
RecentRequests: requests,
|
||||
User: userContent,
|
||||
}
|
||||
renderOrError(w, dashboard, data)
|
||||
}
|
||||
|
||||
func Mock(t string, w http.ResponseWriter, code string) {
|
||||
data := ContentMock{
|
||||
DistrictName: "Delta MVCD",
|
||||
URLs: ContentMockURLs{
|
||||
Dispatch: "/mock/dispatch",
|
||||
DispatchResults: "/mock/dispatch-results",
|
||||
ReportConfirmation: fmt.Sprintf("/mock/report/%s/confirm", code),
|
||||
ReportDetail: fmt.Sprintf("/mock/report/%s", code),
|
||||
ReportContribute: fmt.Sprintf("/mock/report/%s/contribute", code),
|
||||
ReportEvidence: fmt.Sprintf("/mock/report/%s/evidence", code),
|
||||
ReportSchedule: fmt.Sprintf("/mock/report/%s/schedule", code),
|
||||
ReportUpdate: fmt.Sprintf("/mock/report/%s/update", code),
|
||||
Root: "/mock",
|
||||
Setting: "/mock/setting",
|
||||
SettingIntegration: "/mock/setting/integration",
|
||||
SettingPesticide: "/mock/setting/pesticide",
|
||||
SettingPesticideAdd: "/mock/setting/pesticide/add",
|
||||
SettingUser: "/mock/setting/user",
|
||||
SettingUserAdd: "/mock/setting/user/add",
|
||||
},
|
||||
}
|
||||
template, ok := templatesByFilename[t]
|
||||
if !ok {
|
||||
log.Error().Str("template", t).Msg("Failed to find template")
|
||||
respondError(w, "Failed to render template", nil, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
renderOrError(w, &template, data)
|
||||
}
|
||||
|
||||
func OauthPrompt(w http.ResponseWriter, user *models.User) {
|
||||
dp := user.DisplayName
|
||||
data := ContentDashboard{
|
||||
User: User{
|
||||
DisplayName: dp,
|
||||
Initials: extractInitials(dp),
|
||||
Username: user.Username,
|
||||
},
|
||||
}
|
||||
renderOrError(w, oauthPrompt, data)
|
||||
}
|
||||
|
||||
func Settings(w http.ResponseWriter, r *http.Request, user *models.User) {
|
||||
userContent, err := contentForUser(r.Context(), user)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get user content", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
data := ContentAuthenticatedPlaceholder{
|
||||
User: userContent,
|
||||
}
|
||||
renderOrError(w, settings, data)
|
||||
}
|
||||
|
||||
func Signin(w http.ResponseWriter, errorCode string) {
|
||||
data := ContentSignin{
|
||||
InvalidCredentials: errorCode == "invalid-credentials",
|
||||
}
|
||||
renderOrError(w, signin, data)
|
||||
}
|
||||
|
||||
func Signup(w http.ResponseWriter, path string) {
|
||||
data := ContentSignup{}
|
||||
renderOrError(w, signup, data)
|
||||
}
|
||||
|
||||
func Source(w http.ResponseWriter, r *http.Request, user *models.User, id uuid.UUID) {
|
||||
org, err := user.Organization().One(r.Context(), db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get org", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
userContent, err := contentForUser(r.Context(), user)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get user content", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
s, err := sourceByGlobalId(r.Context(), org, id)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get source", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
inspections, err := inspectionsBySource(r.Context(), org, id)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get inspections", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
traps, err := trapsBySource(r.Context(), org, id)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get traps", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
treatments, err := treatmentsBySource(r.Context(), org, id)
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get treatments", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
treatment_models := modelTreatment(treatments)
|
||||
latlng, err := s.H3Cell.LatLng()
|
||||
if err != nil {
|
||||
respondError(w, "Failed to get latlng", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
data := ContentSource{
|
||||
Inspections: inspections,
|
||||
MapData: ComponentMap{
|
||||
Center: latlng,
|
||||
//GeoJSON:
|
||||
MapboxToken: MapboxToken,
|
||||
Markers: []MapMarker{
|
||||
MapMarker{
|
||||
LatLng: latlng,
|
||||
},
|
||||
},
|
||||
Zoom: 13,
|
||||
},
|
||||
Source: s,
|
||||
Traps: traps,
|
||||
Treatments: treatments,
|
||||
TreatmentModels: treatment_models,
|
||||
User: userContent,
|
||||
}
|
||||
|
||||
renderOrError(w, source, data)
|
||||
}
|
||||
|
||||
func gisStatement(cb h3.CellBoundary) string {
|
||||
var content strings.Builder
|
||||
for i, p := range cb {
|
||||
if i != 0 {
|
||||
content.WriteString(", ")
|
||||
}
|
||||
content.WriteString(fmt.Sprintf("%f %f", p.Lng, p.Lat))
|
||||
}
|
||||
// Repeat the first coordinate to close the polygon
|
||||
content.WriteString(fmt.Sprintf(", %f %f", cb[0].Lng, cb[0].Lat))
|
||||
return fmt.Sprintf("ST_GeomFromText('POLYGON((%s))', 3857)", content.String())
|
||||
}
|
||||
|
||||
func latLngDisplay(ll h3.LatLng) string {
|
||||
latDir := "N"
|
||||
latVal := ll.Lat
|
||||
if ll.Lat < 0 {
|
||||
latDir = "S"
|
||||
latVal = -ll.Lat
|
||||
}
|
||||
|
||||
lngDir := "E"
|
||||
lngVal := ll.Lng
|
||||
if ll.Lng < 0 {
|
||||
lngDir = "W"
|
||||
lngVal = -ll.Lng
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%.4f° %s, %.4f° %s", latVal, latDir, lngVal, lngDir)
|
||||
}
|
||||
|
||||
func makeFuncMap() template.FuncMap {
|
||||
funcMap := template.FuncMap{
|
||||
"bigNumber": bigNumber,
|
||||
"GISStatement": gisStatement,
|
||||
"latLngDisplay": latLngDisplay,
|
||||
"timeAsRelativeDate": timeAsRelativeDate,
|
||||
"timeDelta": timeDelta,
|
||||
"timeElapsed": timeElapsed,
|
||||
"timeInterval": timeInterval,
|
||||
"timeSince": timeSince,
|
||||
"uuidShort": uuidShort,
|
||||
}
|
||||
return funcMap
|
||||
}
|
||||
func newBuiltTemplate(name string, files ...string) *BuiltTemplate {
|
||||
files_on_disk := true
|
||||
all_files := append([]string{name}, files...)
|
||||
for _, f := range all_files {
|
||||
full_path := "templates/" + f + ".html"
|
||||
_, err := os.Stat(full_path)
|
||||
if err != nil {
|
||||
files_on_disk = false
|
||||
break
|
||||
}
|
||||
}
|
||||
var result BuiltTemplate
|
||||
if files_on_disk {
|
||||
result = BuiltTemplate{
|
||||
files: all_files,
|
||||
name: name,
|
||||
template: nil,
|
||||
}
|
||||
} else {
|
||||
result = BuiltTemplate{
|
||||
files: all_files,
|
||||
name: name,
|
||||
template: parseEmbedded(all_files),
|
||||
}
|
||||
}
|
||||
templatesByFilename[name] = result
|
||||
return &result
|
||||
}
|
||||
|
||||
func parseEmbedded(files []string) *template.Template {
|
||||
funcMap := makeFuncMap()
|
||||
// Remap the file names to embedded paths
|
||||
paths := make([]string, 0)
|
||||
for _, f := range files {
|
||||
paths = append(paths, "templates/"+f+".html")
|
||||
}
|
||||
for _, f := range components {
|
||||
paths = append(paths, "templates/components/"+f+".html")
|
||||
}
|
||||
name := files[0]
|
||||
return template.Must(
|
||||
template.New(name).Funcs(funcMap).ParseFS(embeddedFiles, paths...))
|
||||
}
|
||||
|
||||
func parseFromDisk(files []string) (*template.Template, error) {
|
||||
funcMap := makeFuncMap()
|
||||
paths := make([]string, 0)
|
||||
for _, f := range files {
|
||||
paths = append(paths, "templates/"+f+".html")
|
||||
}
|
||||
name := files[0] + ".html"
|
||||
for _, f := range components {
|
||||
paths = append(paths, "templates/components/"+f+".html")
|
||||
}
|
||||
templ, err := template.New(name).Funcs(funcMap).ParseFiles(paths...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse %s: %w", paths, err)
|
||||
}
|
||||
return templ, nil
|
||||
}
|
||||
|
||||
func timeAsRelativeDate(d time.Time) string {
|
||||
return d.Format("01-02")
|
||||
}
|
||||
|
||||
// FormatTimeDuration returns a human-readable string representing a time.Duration
|
||||
// as "X units early" or "X units late"
|
||||
func timeDelta(d time.Duration) string {
|
||||
suffix := "late"
|
||||
if d < 0 {
|
||||
suffix = "early"
|
||||
d = -d // Make duration positive for calculations
|
||||
}
|
||||
|
||||
const (
|
||||
day = 24 * time.Hour
|
||||
week = 7 * day
|
||||
)
|
||||
|
||||
log.Info().Int64("delta", int64(d)).Str("suffix", suffix).Msg("Time delta")
|
||||
switch {
|
||||
case d >= week:
|
||||
weeks := d / week
|
||||
if weeks == 1 {
|
||||
return "1 week " + suffix
|
||||
}
|
||||
return fmt.Sprintf("%d weeks %s", weeks, suffix)
|
||||
|
||||
case d >= day:
|
||||
days := d / day
|
||||
if days == 1 {
|
||||
return "1 day " + suffix
|
||||
}
|
||||
return fmt.Sprintf("%d days %s", days, suffix)
|
||||
|
||||
case d >= time.Hour:
|
||||
hours := d / time.Hour
|
||||
if hours == 1 {
|
||||
return "1 hour " + suffix
|
||||
}
|
||||
return fmt.Sprintf("%d hours %s", hours, suffix)
|
||||
|
||||
case d >= time.Minute:
|
||||
minutes := d / time.Minute
|
||||
if minutes == 1 {
|
||||
return "1 minute " + suffix
|
||||
}
|
||||
return fmt.Sprintf("%d minutes %s", minutes, suffix)
|
||||
|
||||
default:
|
||||
seconds := d / time.Second
|
||||
if seconds == 1 {
|
||||
return "1 second " + suffix
|
||||
}
|
||||
return fmt.Sprintf("%d seconds %s", seconds, suffix)
|
||||
}
|
||||
}
|
||||
|
||||
func timeElapsed(seconds null.Val[float32]) string {
|
||||
if !seconds.IsValue() {
|
||||
return "none"
|
||||
}
|
||||
s := int(seconds.MustGet())
|
||||
hours := s / 3600
|
||||
remainder := s - (hours * 3600)
|
||||
minutes := remainder / 60
|
||||
remainder = remainder - (minutes * 60)
|
||||
if hours > 0 {
|
||||
return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, remainder)
|
||||
} else if minutes > 0 {
|
||||
return fmt.Sprintf("%02d:%02d", minutes, remainder)
|
||||
} else {
|
||||
return fmt.Sprintf("%d seconds", remainder)
|
||||
}
|
||||
}
|
||||
|
||||
func timeInterval(d time.Duration) string {
|
||||
seconds := d.Seconds()
|
||||
|
||||
// Less than 120 seconds -> show in seconds
|
||||
if seconds < 120 {
|
||||
return fmt.Sprintf("every %d seconds", int(math.Round(seconds)))
|
||||
}
|
||||
|
||||
minutes := d.Minutes()
|
||||
// Less than 120 minutes -> show in minutes
|
||||
if minutes < 120 {
|
||||
return fmt.Sprintf("every %d minutes", int(math.Round(minutes)))
|
||||
}
|
||||
|
||||
hours := d.Hours()
|
||||
// Less than 48 hours -> show in hours
|
||||
if hours < 48 {
|
||||
return fmt.Sprintf("every %d hours", int(math.Round(hours)))
|
||||
}
|
||||
|
||||
days := hours / 24
|
||||
// Less than 14 days -> show in days
|
||||
if days < 14 {
|
||||
return fmt.Sprintf("every %d days", int(math.Round(days)))
|
||||
}
|
||||
|
||||
weeks := days / 7
|
||||
// Less than 8 weeks -> show in weeks
|
||||
if weeks < 8 {
|
||||
return fmt.Sprintf("every %d weeks", int(math.Round(weeks)))
|
||||
}
|
||||
|
||||
months := days / 30
|
||||
// Less than 24 months -> show in months
|
||||
if months < 24 {
|
||||
return fmt.Sprintf("every %d months", int(math.Round(months)))
|
||||
}
|
||||
|
||||
years := days / 365
|
||||
return fmt.Sprintf("every %d years", int(math.Round(years)))
|
||||
}
|
||||
func timeSince(t *time.Time) string {
|
||||
if t == nil {
|
||||
return "never"
|
||||
}
|
||||
now := time.Now()
|
||||
diff := now.Sub(*t)
|
||||
|
||||
hours := diff.Hours()
|
||||
if hours < 1 {
|
||||
minutes := diff.Minutes()
|
||||
return fmt.Sprintf("%d minutes ago", int(minutes))
|
||||
} else if hours < 24 {
|
||||
return fmt.Sprintf("%d hours ago", int(hours))
|
||||
} else {
|
||||
days := hours / 24
|
||||
return fmt.Sprintf("%d days ago", int(days))
|
||||
}
|
||||
}
|
||||
|
||||
func trapsBySource(ctx context.Context, org *models.Organization, sourceID uuid.UUID) ([]TrapNearby, error) {
|
||||
locations, err := sql.TrapLocationBySourceID(org.ID, sourceID).All(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to query rows: %w", err)
|
||||
}
|
||||
|
||||
location_ids := make([]uuid.UUID, 0)
|
||||
var args []bob.Expression
|
||||
for _, location := range locations {
|
||||
location_ids = append(location_ids, location.TrapLocationGlobalid)
|
||||
args = append(args, psql.Arg(location.TrapLocationGlobalid))
|
||||
}
|
||||
/*
|
||||
trap_data, err := org.FSTrapdata(
|
||||
sm.Where(
|
||||
models.FSTrapdata.Columns.LocID.In(args...),
|
||||
),
|
||||
sm.OrderBy("enddatetime"),
|
||||
).All(ctx, db.PGInstance.BobDB)
|
||||
*/
|
||||
|
||||
/*
|
||||
query := org.FSTrapdata(
|
||||
sm.From(
|
||||
psql.Select(
|
||||
sm.From(psql.F("ROW_NUMBER")(
|
||||
fm.Over(
|
||||
wm.PartitionBy(models.FSTrapdata.Columns.LocID),
|
||||
wm.OrderBy(models.FSTrapdata.Columns.Enddatetime).Desc(),
|
||||
),
|
||||
)).As("row_num"),
|
||||
sm.Where(models.FSTrapdata.Columns.LocID.In(args...))),
|
||||
),
|
||||
sm.Where(psql.Quote("row_num").LTE(psql.Arg(10))),
|
||||
sm.OrderBy(models.FSTrapdata.Columns.LocID),
|
||||
sm.OrderBy(models.FSTrapdata.Columns.Enddatetime).Desc(),
|
||||
)
|
||||
*/
|
||||
/*
|
||||
query := psql.Select(
|
||||
sm.From(
|
||||
psql.Select(
|
||||
sm.Columns(
|
||||
models.FSTrapdata.Columns.Globalid,
|
||||
psql.F("ROW_NUMBER")(
|
||||
fm.Over(
|
||||
wm.PartitionBy(models.FSTrapdata.Columns.LocID),
|
||||
wm.OrderBy(models.FSTrapdata.Columns.Enddatetime).Desc(),
|
||||
),
|
||||
).As("row_num"),
|
||||
sm.From(models.FSTrapdata.Name()),
|
||||
),
|
||||
sm.Where(models.FSTrapdata.Columns.LocID.In(args...))),
|
||||
),
|
||||
sm.Where(psql.Quote("row_num").LTE(psql.Arg(10))),
|
||||
sm.OrderBy(models.FSTrapdata.Columns.LocID),
|
||||
sm.OrderBy(models.FSTrapdata.Columns.Enddatetime).Desc(),
|
||||
)
|
||||
log.Info().Str("trapdata", queryToString(query)).Msg("Getting trap data")
|
||||
trap_data, err := query.Exec(ctx, db.PGInstance.BobDB)
|
||||
*/
|
||||
|
||||
trap_data, err := sql.TrapDataByLocationIDRecent(org.ID, location_ids).All(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to query trap data: %w", err)
|
||||
}
|
||||
|
||||
counts, err := sql.TrapCountByLocationID(org.ID, location_ids).All(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to query trap counts: %w", err)
|
||||
}
|
||||
|
||||
traps, err := toTemplateTraps(locations, trap_data, counts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to convert trap data: %w", err)
|
||||
}
|
||||
return traps, nil
|
||||
}
|
||||
|
||||
func renderOrError(w http.ResponseWriter, template *BuiltTemplate, context interface{}) {
|
||||
buf := &bytes.Buffer{}
|
||||
err := template.ExecuteTemplate(buf, context)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("template", template.name).Msg("Failed to render template")
|
||||
respondError(w, "Failed to render template", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
buf.WriteTo(w)
|
||||
}
|
||||
|
||||
func treatmentsBySource(ctx context.Context, org *models.Organization, sourceID uuid.UUID) ([]Treatment, error) {
|
||||
var results []Treatment
|
||||
rows, err := org.Treatments(
|
||||
sm.Where(
|
||||
models.FieldseekerTreatments.Columns.Pointlocid.EQ(psql.Arg(sourceID)),
|
||||
),
|
||||
sm.OrderBy("enddatetime").Desc(),
|
||||
).All(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return results, fmt.Errorf("Failed to query rows: %w", err)
|
||||
}
|
||||
//log.Info().Int("row count", len(rows)).Msg("Getting treatments")
|
||||
return toTemplateTreatment(rows)
|
||||
}
|
||||
|
||||
func treatmentsByCell(ctx context.Context, org *models.Organization, c h3.Cell) ([]Treatment, error) {
|
||||
var results []Treatment
|
||||
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.Treatments(
|
||||
sm.Where(
|
||||
psql.F("ST_Within", "geospatial", geom_query),
|
||||
),
|
||||
sm.OrderBy("pointlocid"),
|
||||
sm.OrderBy("enddatetime"),
|
||||
).All(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return results, fmt.Errorf("Failed to query rows: %w", err)
|
||||
}
|
||||
return toTemplateTreatment(rows)
|
||||
}
|
||||
func inspectionsByCell(ctx context.Context, org *models.Organization, c h3.Cell) ([]Inspection, error) {
|
||||
var results []Inspection
|
||||
|
||||
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.Mosquitoinspections(
|
||||
sm.Where(
|
||||
psql.F("ST_Within", "geospatial", geom_query),
|
||||
),
|
||||
sm.OrderBy("pointlocid"),
|
||||
sm.OrderBy("enddatetime"),
|
||||
).All(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return results, fmt.Errorf("Failed to query rows: %w", err)
|
||||
}
|
||||
return toTemplateInspection(rows)
|
||||
}
|
||||
func inspectionsBySource(ctx context.Context, org *models.Organization, sourceID uuid.UUID) ([]Inspection, error) {
|
||||
var results []Inspection
|
||||
|
||||
rows, err := org.Mosquitoinspections(
|
||||
sm.Where(
|
||||
models.FieldseekerMosquitoinspections.Columns.Pointlocid.EQ(psql.Arg(sourceID)),
|
||||
),
|
||||
sm.OrderBy("enddatetime").Desc(),
|
||||
).All(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return results, fmt.Errorf("Failed to query rows: %w", err)
|
||||
}
|
||||
return toTemplateInspection(rows)
|
||||
}
|
||||
func breedingSourcesByCell(ctx context.Context, org *models.Organization, c h3.Cell) ([]BreedingSourceSummary, error) {
|
||||
var results []BreedingSourceSummary
|
||||
|
||||
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.Pointlocations(
|
||||
sm.Where(
|
||||
psql.F("ST_Within", "geospatial", geom_query),
|
||||
),
|
||||
sm.OrderBy("lasttreatdate"),
|
||||
).All(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return results, fmt.Errorf("Failed to query rows: %w", err)
|
||||
}
|
||||
for _, r := range rows {
|
||||
var last_inspected *time.Time
|
||||
if !r.Lastinspectdate.IsNull() {
|
||||
l := r.Lastinspectdate.MustGet()
|
||||
last_inspected = &l
|
||||
}
|
||||
var last_treat_date *time.Time
|
||||
if !r.Lasttreatdate.IsNull() {
|
||||
l := r.Lasttreatdate.MustGet()
|
||||
last_treat_date = &l
|
||||
}
|
||||
results = append(results, BreedingSourceSummary{
|
||||
ID: r.Globalid,
|
||||
LastInspected: last_inspected,
|
||||
LastTreated: last_treat_date,
|
||||
Type: r.Habitat.GetOr("none"),
|
||||
})
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func uuidShort(uuid uuid.UUID) string {
|
||||
s := uuid.String()
|
||||
if len(s) < 7 {
|
||||
return s // Return as is if too short
|
||||
}
|
||||
|
||||
return s[:3] + "..." + s[len(s)-4:]
|
||||
}
|
||||
|
||||
func sourceByGlobalId(ctx context.Context, org *models.Organization, id uuid.UUID) (*BreedingSourceDetail, error) {
|
||||
row, err := org.Pointlocations(
|
||||
sm.Where(models.FieldseekerPointlocations.Columns.Globalid.EQ(psql.Arg(id))),
|
||||
).One(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to get point location: %w", err)
|
||||
}
|
||||
return toTemplateBreedingSource(row), nil
|
||||
}
|
||||
410
htmlpage/model_conversion.go
Normal file
410
htmlpage/model_conversion.go
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
package htmlpage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/sql"
|
||||
"github.com/aarondl/opt/null"
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/uber/h3-go/v4"
|
||||
)
|
||||
|
||||
type BreedingSourceDetail struct {
|
||||
// Basic Information
|
||||
OrganizationID int32 `json:"organizationId"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
LocationNumber int64 `json:"locationNumber"`
|
||||
ObjectID int64 `json:"objectId"`
|
||||
GlobalID uuid.UUID `json:"globalId"`
|
||||
ExternalID string `json:"externalId"`
|
||||
|
||||
// Status Information
|
||||
Active bool `json:"active"`
|
||||
DeactivateReason string `json:"deactivateReason"`
|
||||
SourceStatus string `json:"sourceStatus"`
|
||||
Priority string `json:"priority"`
|
||||
ScalarPriority int64 `json:"scalarPriority"`
|
||||
|
||||
// Classification
|
||||
SourceType string `json:"sourceType"`
|
||||
Habitat string `json:"habitat"`
|
||||
UseType string `json:"useType"`
|
||||
WaterOrigin string `json:"waterOrigin"`
|
||||
Symbology string `json:"symbology"`
|
||||
|
||||
// Geographical Data
|
||||
H3Cell h3.Cell `json:"h3cell"`
|
||||
Zone string `json:"zone"`
|
||||
Zone2 string `json:"zone2"`
|
||||
Jurisdiction string `json:"jurisdiction"`
|
||||
AccessDescription string `json:"accessDescription"`
|
||||
|
||||
// Inspection Data
|
||||
LarvaeInspectInterval int16 `json:"larvaeInspectInterval"`
|
||||
LastInspectionDate *time.Time `json:"lastInspectionDate"`
|
||||
LastInspectionActivity string `json:"lastInspectionActivity"`
|
||||
LastInspectionActionTaken string `json:"lastInspectionActionTaken"`
|
||||
LastInspectionAverageLarvae float64 `json:"lastInspectionAverageLarvae"`
|
||||
LastInspectionAveragePupae float64 `json:"lastInspectionAveragePupae"`
|
||||
LastInspectionBreeding string `json:"lastInspectionBreeding"`
|
||||
LastInspectionConditions string `json:"lastInspectionConditions"`
|
||||
LastInspectionFieldSpecies string `json:"lastInspectionFieldSpecies"`
|
||||
LastInspectionLifeStages string `json:"lastInspectionLifeStages"`
|
||||
|
||||
// Treatment Data
|
||||
LastTreatmentDate *time.Time `json:"lastTreatmentDate"`
|
||||
LastTreatmentActivity string `json:"lastTreatmentActivity"`
|
||||
LastTreatmentProduct string `json:"lastTreatmentProduct"`
|
||||
LastTreatmentQuantity float64 `json:"lastTreatmentQuantity"`
|
||||
LastTreatmentQuantityUnit string `json:"lastTreatmentQuantityUnit"`
|
||||
|
||||
// Assignment & Schedule
|
||||
AssignedTechnician string `json:"assignedTechnician"`
|
||||
NextActionScheduledDate *time.Time `json:"nextActionScheduledDate"`
|
||||
|
||||
// Metadata
|
||||
Created *time.Time `json:"created"`
|
||||
Creator string `json:"creator"`
|
||||
EditedAt *time.Time `json:"editedAt"`
|
||||
Editor string `json:"editor"`
|
||||
Comments string `json:"comments"`
|
||||
}
|
||||
|
||||
type TrapNearby struct {
|
||||
Counts []*TrapCount
|
||||
Distance string
|
||||
ID uuid.UUID
|
||||
}
|
||||
|
||||
type TrapCount struct {
|
||||
Ended time.Time
|
||||
Females int
|
||||
ID uuid.UUID
|
||||
Males int
|
||||
Total int
|
||||
}
|
||||
|
||||
type TrapData struct {
|
||||
// Basic Identifiers
|
||||
OrganizationID int32 `json:"organizationId"`
|
||||
ObjectID int64 `json:"objectId"`
|
||||
GlobalID uuid.UUID `json:"globalId"`
|
||||
LocationName string `json:"locationName"`
|
||||
LocationID uuid.UUID `json:"locationId"`
|
||||
SRID uuid.UUID `json:"srid"`
|
||||
Field int64 `json:"field"`
|
||||
|
||||
// Trap Information
|
||||
TrapType string `json:"trapType"`
|
||||
TrapCondition string `json:"trapCondition"`
|
||||
TrapActivityType string `json:"trapActivityType"`
|
||||
TrapNights int16 `json:"trapNights"`
|
||||
Lure string `json:"lureType"`
|
||||
|
||||
// Personnel
|
||||
FieldTechnician string `json:"fieldTechnician"`
|
||||
IdentifiedByTechnician string `json:"identifiedByTechnician"`
|
||||
SortedByTechnician string `json:"sortedByTechnician"`
|
||||
|
||||
// Timing
|
||||
StartDateTime *time.Time `json:"startDateTime"`
|
||||
EndDateTime *time.Time `json:"endDateTime"`
|
||||
|
||||
// Environmental Conditions
|
||||
AverageTemperature float64 `json:"averageTemperature"`
|
||||
Rainfall float64 `json:"rainfall"`
|
||||
WindDirection string `json:"windDirection"`
|
||||
WindSpeed float64 `json:"windSpeed"`
|
||||
SiteCondition string `json:"siteCondition"`
|
||||
|
||||
// Status and Processing
|
||||
Processed bool `json:"processed"`
|
||||
RecordStatus int16 `json:"recordStatus"`
|
||||
Reviewed bool `json:"reviewed"`
|
||||
ReviewedBy string `json:"reviewedBy"`
|
||||
ReviewedDate *time.Time `json:"reviewedDate"`
|
||||
GatewaySynced bool `json:"gatewaySynced"`
|
||||
LR bool `json:"laboratoryReported"`
|
||||
Voltage float64 `json:"voltage"`
|
||||
|
||||
// Location Data
|
||||
H3Cell h3.Cell `json:"h3cell"`
|
||||
Zone string `json:"zone"`
|
||||
Zone2 string `json:"zone2"`
|
||||
|
||||
// Vector Survey IDs
|
||||
VectorSurveyTrapDataID string `json:"vectorSurveyTrapDataId"`
|
||||
VectorSurveyTrapLocationID string `json:"vectorSurveyTrapLocationId"`
|
||||
|
||||
// Metadata
|
||||
Created *time.Time `json:"created"`
|
||||
Creator string `json:"creator"`
|
||||
CreatedByUser string `json:"createdByUser"`
|
||||
CreatedDateAlt *time.Time `json:"createdDateAlt"`
|
||||
Edited *time.Time `json:"edited"`
|
||||
Editor string `json:"editor"`
|
||||
LastEditedDate *time.Time `json:"lastEditedDate"`
|
||||
LastEditedUser string `json:"lastEditedUser"`
|
||||
Comments string `json:"comments"`
|
||||
}
|
||||
|
||||
type Treatment struct {
|
||||
CadenceDelta time.Duration
|
||||
Date *time.Time
|
||||
LocationID uuid.UUID
|
||||
Notes string
|
||||
Product string
|
||||
}
|
||||
|
||||
func toTemplateTraps(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 {
|
||||
count_by_trap_data_id[c.TrapdataGlobalid] = &c
|
||||
}
|
||||
counts_by_location_id := make(map[uuid.UUID][]*TrapCount)
|
||||
for _, td := range trap_data {
|
||||
c, ok := count_by_trap_data_id[td.Globalid]
|
||||
if !ok {
|
||||
return results, errors.New(fmt.Sprintf("Failed to find trap count for %s", td.Globalid))
|
||||
}
|
||||
loc_id := td.LocID
|
||||
count := &TrapCount{
|
||||
Ended: td.Enddatetime,
|
||||
Females: int(c.TotalFemales),
|
||||
ID: td.Globalid,
|
||||
Males: int(c.TotalMales),
|
||||
Total: int(c.Total),
|
||||
}
|
||||
counts, ok := counts_by_location_id[loc_id]
|
||||
if !ok {
|
||||
counts = []*TrapCount{count}
|
||||
} else {
|
||||
counts = append(counts, count)
|
||||
}
|
||||
counts_by_location_id[loc_id] = counts
|
||||
}
|
||||
for _, location := range locations {
|
||||
counts, ok := counts_by_location_id[location.TrapLocationGlobalid]
|
||||
if !ok {
|
||||
return results, errors.New(fmt.Sprintf("Failed to find counts for %s", location.TrapLocationGlobalid))
|
||||
}
|
||||
trap := TrapNearby{
|
||||
Counts: counts,
|
||||
Distance: location.Distance,
|
||||
ID: location.TrapLocationGlobalid,
|
||||
}
|
||||
results = append(results, trap)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func toTemplateTrapData(trap_data models.FieldseekerTrapdatumSlice) ([]TrapData, error) {
|
||||
var results []TrapData
|
||||
for _, r := range trap_data {
|
||||
if r.H3cell.IsNull() {
|
||||
continue
|
||||
}
|
||||
cell, err := toH3Cell(r.H3cell.MustGet())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get location for trap data")
|
||||
continue
|
||||
}
|
||||
results = append(results, TrapData{
|
||||
// Basic Identifiers
|
||||
OrganizationID: r.OrganizationID,
|
||||
ObjectID: r.Objectid,
|
||||
GlobalID: r.Globalid,
|
||||
LocationName: r.Locationname.GetOr(""),
|
||||
LocationID: r.LocID.GetOr(uuid.UUID{}),
|
||||
SRID: r.Srid.GetOr(uuid.UUID{}),
|
||||
Field: int64(r.Field.GetOr(0)),
|
||||
|
||||
// Trap Information
|
||||
TrapType: r.Traptype.GetOr(""),
|
||||
TrapCondition: r.Trapcondition.GetOr(""),
|
||||
TrapActivityType: r.Trapactivitytype.GetOr(""),
|
||||
TrapNights: r.Trapnights.GetOr(0),
|
||||
Lure: r.Lure.GetOr(""),
|
||||
|
||||
// Personnel
|
||||
FieldTechnician: r.Fieldtech.GetOr(""),
|
||||
IdentifiedByTechnician: r.Idbytech.GetOr(""),
|
||||
SortedByTechnician: r.Sortbytech.GetOr(""),
|
||||
|
||||
// Timing
|
||||
StartDateTime: getTimeOrNull(r.Startdatetime),
|
||||
EndDateTime: getTimeOrNull(r.Enddatetime),
|
||||
|
||||
// Environmental Conditions
|
||||
AverageTemperature: r.Avetemp.GetOr(0),
|
||||
Rainfall: r.Raingauge.GetOr(0),
|
||||
WindDirection: r.Winddir.GetOr(""),
|
||||
WindSpeed: r.Windspeed.GetOr(0),
|
||||
SiteCondition: r.Sitecond.GetOr(""),
|
||||
|
||||
// Status and Processing
|
||||
Processed: fsIntToBool(r.Processed),
|
||||
RecordStatus: r.Recordstatus.GetOr(0),
|
||||
Reviewed: fsIntToBool(r.Reviewed),
|
||||
ReviewedBy: r.Reviewedby.GetOr(""),
|
||||
ReviewedDate: getTimeOrNull(r.Revieweddate),
|
||||
GatewaySynced: fsIntToBool(r.Gatewaysync),
|
||||
LR: fsIntToBool(r.LR),
|
||||
Voltage: r.Voltage.GetOr(0),
|
||||
|
||||
// Location Data
|
||||
H3Cell: cell,
|
||||
Zone: r.Zone.GetOr(""),
|
||||
Zone2: r.Zone2.GetOr(""),
|
||||
|
||||
// Vector Survey IDs
|
||||
VectorSurveyTrapDataID: r.Vectorsurvtrapdataid.GetOr(""),
|
||||
VectorSurveyTrapLocationID: r.Vectorsurvtraplocationid.GetOr(""),
|
||||
|
||||
// Metadata
|
||||
Created: getTimeOrNull(r.Creationdate),
|
||||
Creator: r.Creator.GetOr(""),
|
||||
CreatedByUser: r.CreatedUser.GetOr(""),
|
||||
CreatedDateAlt: getTimeOrNull(r.CreatedDate),
|
||||
Edited: getTimeOrNull(r.Editdate),
|
||||
Editor: r.Editor.GetOr(""),
|
||||
LastEditedDate: getTimeOrNull(r.LastEditedDate),
|
||||
LastEditedUser: r.LastEditedUser.GetOr(""),
|
||||
Comments: r.Comments.GetOr(""),
|
||||
})
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
func toTemplateTreatment(rows models.FieldseekerTreatmentSlice) ([]Treatment, error) {
|
||||
var results []Treatment
|
||||
for _, r := range rows {
|
||||
results = append(results, Treatment{
|
||||
Date: getTimeOrNull(r.Enddatetime),
|
||||
LocationID: r.Pointlocid.GetOr(uuid.UUID{}),
|
||||
Notes: r.Comments.GetOr("none"),
|
||||
Product: r.Product.GetOr("none"),
|
||||
})
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func toTemplateInspection(rows models.FieldseekerMosquitoinspectionSlice) ([]Inspection, error) {
|
||||
var results []Inspection
|
||||
for _, r := range rows {
|
||||
results = append(results, Inspection{
|
||||
Action: r.Actiontaken.GetOr("none"),
|
||||
Date: getTimeOrNull(r.Enddatetime),
|
||||
Notes: r.Comments.GetOr("none"),
|
||||
Location: r.Locationname.GetOr("none"),
|
||||
LocationID: r.Pointlocid.GetOr(uuid.UUID{}),
|
||||
})
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// Helper function to convert unix timestamp to time.Time
|
||||
func fsToTime(val null.Val[int64]) time.Time {
|
||||
v, ok := val.Get()
|
||||
if !ok {
|
||||
return time.UnixMilli(0)
|
||||
}
|
||||
t := time.UnixMilli(v)
|
||||
return t
|
||||
}
|
||||
|
||||
// Helper function to convert int16 to bool
|
||||
func fsIntToBool(val null.Val[int16]) bool {
|
||||
if !val.IsValue() {
|
||||
return false
|
||||
}
|
||||
b := val.MustGet() != 0
|
||||
return b
|
||||
}
|
||||
|
||||
// toTemplateBreedingSource transforms the DB model into the display model
|
||||
func toTemplateBreedingSource(source *models.FieldseekerPointlocation) *BreedingSourceDetail {
|
||||
if source.H3cell.IsNull() {
|
||||
log.Error().Msg("h3 cell is null")
|
||||
return nil
|
||||
}
|
||||
cell, err := toH3Cell(source.H3cell.MustGet())
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get h3 cell from point location")
|
||||
return nil
|
||||
}
|
||||
return &BreedingSourceDetail{
|
||||
// Basic Information
|
||||
OrganizationID: source.OrganizationID,
|
||||
Name: source.Name.MustGet(),
|
||||
Description: source.Description.MustGet(),
|
||||
LocationNumber: int64(source.Locationnumber.GetOr(0)),
|
||||
ObjectID: source.Objectid,
|
||||
GlobalID: source.Globalid,
|
||||
ExternalID: source.Externalid.GetOr(""),
|
||||
|
||||
// Status Information
|
||||
Active: fsIntToBool(source.Active),
|
||||
DeactivateReason: source.DeactivateReason.GetOr(""),
|
||||
SourceStatus: source.Sourcestatus.GetOr(""),
|
||||
Priority: source.Priority.GetOr(""),
|
||||
ScalarPriority: int64(source.Scalarpriority.GetOr(0)),
|
||||
|
||||
// Classification
|
||||
SourceType: source.Stype.GetOr(""),
|
||||
Habitat: source.Habitat.GetOr(""),
|
||||
UseType: source.Usetype.GetOr(""),
|
||||
WaterOrigin: source.Waterorigin.GetOr(""),
|
||||
Symbology: source.Symbology.GetOr(""),
|
||||
|
||||
// Geographical Data
|
||||
H3Cell: cell,
|
||||
Zone: source.Zone.GetOr(""),
|
||||
Zone2: source.Zone2.GetOr(""),
|
||||
Jurisdiction: source.Jurisdiction.GetOr(""),
|
||||
AccessDescription: source.Accessdesc.GetOr(""),
|
||||
|
||||
// Inspection Data
|
||||
LarvaeInspectInterval: source.Larvinspectinterval.GetOr(0),
|
||||
LastInspectionDate: getTimeOrNull(source.Lastinspectdate),
|
||||
LastInspectionActivity: source.Lastinspectactivity.GetOr(""),
|
||||
LastInspectionActionTaken: source.Lastinspectactiontaken.GetOr(""),
|
||||
LastInspectionAverageLarvae: source.Lastinspectavglarvae.GetOr(0),
|
||||
LastInspectionAveragePupae: source.Lastinspectavgpupae.GetOr(0),
|
||||
LastInspectionBreeding: source.Lastinspectbreeding.GetOr(""),
|
||||
LastInspectionConditions: source.Lastinspectconditions.GetOr(""),
|
||||
LastInspectionFieldSpecies: source.Lastinspectfieldspecies.GetOr(""),
|
||||
LastInspectionLifeStages: source.Lastinspectlstages.GetOr(""),
|
||||
|
||||
// Treatment Data
|
||||
LastTreatmentDate: getTimeOrNull(source.Lasttreatdate),
|
||||
LastTreatmentActivity: source.Lasttreatactivity.GetOr(""),
|
||||
LastTreatmentProduct: source.Lasttreatproduct.GetOr(""),
|
||||
LastTreatmentQuantity: source.Lasttreatqty.GetOr(0),
|
||||
LastTreatmentQuantityUnit: source.Lasttreatqtyunit.GetOr(""),
|
||||
|
||||
// Assignment & Schedule
|
||||
AssignedTechnician: source.Assignedtech.GetOr(""),
|
||||
NextActionScheduledDate: getTimeOrNull(source.Nextactiondatescheduled),
|
||||
|
||||
// Metadata
|
||||
Created: getTimeOrNull(source.Creationdate),
|
||||
Creator: source.Creator.GetOr(""),
|
||||
EditedAt: getTimeOrNull(source.Editdate),
|
||||
Editor: source.Editor.GetOr(""),
|
||||
Comments: source.Comments.GetOr(""),
|
||||
}
|
||||
}
|
||||
|
||||
func getTimeOrNull(v null.Val[time.Time]) *time.Time {
|
||||
if v.IsNull() {
|
||||
return nil
|
||||
}
|
||||
val := v.MustGet()
|
||||
return &val
|
||||
}
|
||||
92
htmlpage/notification.go
Normal file
92
htmlpage/notification.go
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
package htmlpage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
enums "github.com/Gleipnir-Technology/nidus-sync/db/enums"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/debug"
|
||||
"github.com/aarondl/opt/omit"
|
||||
"github.com/aarondl/opt/omitnull"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var (
|
||||
NotificationPathOauthReset string = "/oauth/refresh"
|
||||
)
|
||||
|
||||
type Notification struct {
|
||||
Link string
|
||||
Message string
|
||||
Time time.Time
|
||||
Type string
|
||||
}
|
||||
|
||||
// Clear all notifications for a given user with the given path
|
||||
func clearNotificationsOauth(ctx context.Context, user *models.User) {
|
||||
setter := models.NotificationSetter{
|
||||
ResolvedAt: omitnull.From(time.Now()),
|
||||
}
|
||||
updater := models.Notifications.Update(
|
||||
//models.SelectWhere.Notifications.Link.EQ(NotificationPathOauthReset),
|
||||
models.UpdateWhere.Notifications.Link.EQ(NotificationPathOauthReset),
|
||||
models.UpdateWhere.Notifications.UserID.EQ(user.ID),
|
||||
setter.UpdateMod(),
|
||||
)
|
||||
updater.Exec(ctx, db.PGInstance.BobDB)
|
||||
//user.UserNotifications(
|
||||
//models.SelectWhere.Notifications.Link.EQ(NotificationPathOauthReset),
|
||||
//).UpdateAll()
|
||||
}
|
||||
|
||||
func notifyOauthInvalid(ctx context.Context, user *models.User) {
|
||||
msg := "Oauth token invalidated"
|
||||
notificationSetter := models.NotificationSetter{
|
||||
Created: omit.From(time.Now()),
|
||||
Message: omit.From(msg),
|
||||
Link: omit.From(NotificationPathOauthReset),
|
||||
Type: omit.From(enums.NotificationtypeOauthTokenInvalidated),
|
||||
}
|
||||
err := user.InsertUserNotifications(ctx, db.PGInstance.BobDB, ¬ificationSetter)
|
||||
if err != nil {
|
||||
if strings.HasPrefix(err.Error(), "ERROR: duplicate key value violates unique constraint") {
|
||||
log.Info().Str("msg", msg).Int("user_id", int(user.ID)).Msg("Refusing to add another notification with the same type")
|
||||
return
|
||||
}
|
||||
debug.LogErrorTypeInfo(err)
|
||||
log.Error().Err(err).Msg("Failed to insert new notification. This is a programmer bug.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func notificationsForUser(ctx context.Context, u *models.User) ([]Notification, error) {
|
||||
results := make([]Notification, 0)
|
||||
notifications, err := u.UserNotifications(
|
||||
models.SelectWhere.Notifications.ResolvedAt.IsNull(),
|
||||
).All(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
return results, fmt.Errorf("Failed to get notifications: %w", err)
|
||||
}
|
||||
for _, n := range notifications {
|
||||
results = append(results, Notification{
|
||||
Link: n.Link,
|
||||
Message: n.Message,
|
||||
Time: n.Created,
|
||||
Type: notificationTypeName(n.Type),
|
||||
})
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func notificationTypeName(t enums.Notificationtype) string {
|
||||
switch t {
|
||||
case enums.NotificationtypeOauthTokenInvalidated:
|
||||
return "alert"
|
||||
default:
|
||||
return "unknown-type"
|
||||
}
|
||||
}
|
||||
13
htmlpage/response.go
Normal file
13
htmlpage/response.go
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package htmlpage
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// Respond with an error that is visible to the user
|
||||
func respondError(w http.ResponseWriter, m string, e error, s int) {
|
||||
log.Warn().Int("status", s).Err(e).Str("user message", m).Msg("Responding with an error")
|
||||
http.Error(w, m, s)
|
||||
}
|
||||
412
htmlpage/templates/admin.html
Normal file
412
htmlpage/templates/admin.html
Normal file
|
|
@ -0,0 +1,412 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.map-container {
|
||||
height: 250px;
|
||||
background-color: #e9ecef;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.calendar-container {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.calendar-day {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
line-height: 2.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
.calendar-day.has-events {
|
||||
position: relative;
|
||||
}
|
||||
.calendar-day.has-events::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0.2rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
height: 0.25rem;
|
||||
border-radius: 0.125rem;
|
||||
background-color: #0d6efd;
|
||||
}
|
||||
.calendar-day.has-events.light::after {
|
||||
width: 0.25rem;
|
||||
background-color: #0d6efd;
|
||||
}
|
||||
.calendar-day.has-events.medium::after {
|
||||
width: 0.5rem;
|
||||
background-color: #fd7e14;
|
||||
}
|
||||
.calendar-day.has-events.heavy::after {
|
||||
width: 0.75rem;
|
||||
background-color: #dc3545;
|
||||
}
|
||||
.trap-increase {
|
||||
color: #dc3545;
|
||||
}
|
||||
.trap-decrease {
|
||||
color: #28a745;
|
||||
}
|
||||
.status-active {
|
||||
background-color: #d4edda;
|
||||
}
|
||||
.status-break {
|
||||
background-color: #fff3cd;
|
||||
}
|
||||
.status-transit {
|
||||
background-color: #cce5ff;
|
||||
}
|
||||
.tech-table tr td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<!-- Header -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand d-flex align-items-center" href="#">
|
||||
<img src="https://via.placeholder.com/40" alt="{{.DistrictName}} Logo" class="me-2">
|
||||
<span>{{.DistrictName}} Mosquito Abatement District</span>
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<!--
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#"><i class="bi bi-house-door"></i> Dashboard</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#"><i class="bi bi-people"></i> Residents</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#"><i class="bi bi-clipboard-data"></i> Reports</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#"><i class="bi bi-gear"></i> Settings</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>-->
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid py-3">
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body py-2">
|
||||
<form action="/phone-call/search-results" method="GET">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Search by ID (e.g., easy twin jazz cats), address, GPS, name or phone..." aria-label="Search">
|
||||
<button class="btn btn-primary" type="submit"><i class="bi bi-search"></i> Search</button>
|
||||
</div>
|
||||
<small class="form-text text-muted">Quick search for request ID, address, coordinates, contact name, or phone number</small>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Left Column -->
|
||||
<div class="col-md-8 mb-3">
|
||||
<div class="card shadow-sm h-100">
|
||||
<div class="card-header bg-success text-white d-flex justify-content-between align-items-center py-2">
|
||||
<h5 class="mb-0">Mosquito Activity & Relief</h5>
|
||||
<a href="/mock/admin/service-request" class="btn btn-light btn-sm"><i class="bi bi-plus-circle"></i> New Service Request</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-2">
|
||||
<div class="col-12">
|
||||
<label class="form-label mb-1">Location of Concern</label>
|
||||
<div class="map-container d-flex justify-content-center align-items-center">
|
||||
<div class="text-center text-muted">
|
||||
<i class="bi bi-map fs-1"></i>
|
||||
<p>Interactive map will be loaded here</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-2">
|
||||
<h6 class="mb-1">Recent Trap Counts</h6>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Trap Location</th>
|
||||
<th>Current</th>
|
||||
<th>Week Δ</th>
|
||||
<th>Month Δ</th>
|
||||
<th>YoY</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Elmwood Park</td>
|
||||
<td>47</td>
|
||||
<td class="trap-decrease">-12%</td>
|
||||
<td class="trap-decrease">-23%</td>
|
||||
<td class="trap-increase">+5%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Riverside Dr</td>
|
||||
<td>32</td>
|
||||
<td class="trap-increase">+8%</td>
|
||||
<td class="trap-decrease">-5%</td>
|
||||
<td class="trap-decrease">-10%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Oakdale Creek</td>
|
||||
<td>53</td>
|
||||
<td class="trap-increase">+15%</td>
|
||||
<td class="trap-increase">+22%</td>
|
||||
<td class="trap-increase">+17%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<h6 class="mb-1">Nearby Service Requests</h6>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-bordered">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Status</th>
|
||||
<th>Type</th>
|
||||
<th>Distance</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>10/15/23</td>
|
||||
<td><span class="badge bg-success">Completed</span></td>
|
||||
<td>Green Pool</td>
|
||||
<td>0.2 mi</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10/18/23</td>
|
||||
<td><span class="badge bg-warning text-dark">Scheduled</span></td>
|
||||
<td>Mosquito Nuisance</td>
|
||||
<td>0.3 mi</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10/19/23</td>
|
||||
<td><span class="badge bg-info text-dark">Accepted</span></td>
|
||||
<td>Previous Source</td>
|
||||
<td>0.5 mi</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column - Calendar -->
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card shadow-sm h-100">
|
||||
<div class="card-header bg-dark text-white py-2">
|
||||
<h5 class="mb-0">Calendar</h5>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="calendar-container p-2 mb-2">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<button class="btn btn-sm btn-outline-secondary"><i class="bi bi-chevron-left"></i></button>
|
||||
<h6 class="mb-0">October 2023</h6>
|
||||
<button class="btn btn-sm btn-outline-secondary"><i class="bi bi-chevron-right"></i></button>
|
||||
</div>
|
||||
<table class="table table-sm mb-0">
|
||||
<thead>
|
||||
<tr class="text-center">
|
||||
<th>Su</th><th>Mo</th><th>Tu</th><th>We</th><th>Th</th><th>Fr</th><th>Sa</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="text-center">
|
||||
<td class="calendar-day text-muted">1</td>
|
||||
<td class="calendar-day text-muted has-events light">2</td>
|
||||
<td class="calendar-day text-muted has-events medium">3</td>
|
||||
<td class="calendar-day text-muted">4</td>
|
||||
<td class="calendar-day text-muted has-events light">5</td>
|
||||
<td class="calendar-day text-muted">6</td>
|
||||
<td class="calendar-day text-muted">7</td>
|
||||
</tr>
|
||||
<tr class="text-center">
|
||||
<td class="calendar-day text-muted">8</td>
|
||||
<td class="calendar-day text-muted has-events medium">9</td>
|
||||
<td class="calendar-day text-muted has-events light">10</td>
|
||||
<td class="calendar-day text-muted has-events heavy">11</td>
|
||||
<td class="calendar-day text-muted has-events medium">12</td>
|
||||
<td class="calendar-day text-muted has-events light">13</td>
|
||||
<td class="calendar-day text-muted">14</td>
|
||||
</tr>
|
||||
<tr class="text-center">
|
||||
<td class="calendar-day text-muted">15</td>
|
||||
<td class="calendar-day text-muted has-events medium">16</td>
|
||||
<td class="calendar-day text-muted has-events light">17</td>
|
||||
<td class="calendar-day text-muted has-events medium">18</td>
|
||||
<td class="calendar-day text-muted has-events heavy">19</td>
|
||||
<td class="calendar-day text-muted has-events medium">20</td>
|
||||
<td class="calendar-day text-muted">21</td>
|
||||
</tr>
|
||||
<tr class="text-center">
|
||||
<td class="calendar-day">22</td>
|
||||
<td class="calendar-day bg-primary text-white rounded has-events heavy">23</td>
|
||||
<td class="calendar-day has-events medium">24</td>
|
||||
<td class="calendar-day has-events light">25</td>
|
||||
<td class="calendar-day has-events medium">26</td>
|
||||
<td class="calendar-day has-events heavy">27</td>
|
||||
<td class="calendar-day">28</td>
|
||||
</tr>
|
||||
<tr class="text-center">
|
||||
<td class="calendar-day">29</td>
|
||||
<td class="calendar-day has-events medium">30</td>
|
||||
<td class="calendar-day has-events light">31</td>
|
||||
<td class="calendar-day text-muted">1</td>
|
||||
<td class="calendar-day text-muted has-events light">2</td>
|
||||
<td class="calendar-day text-muted">3</td>
|
||||
<td class="calendar-day text-muted">4</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="d-flex justify-content-center mt-1 small">
|
||||
<span class="me-3"><span class="d-inline-block bg-primary rounded-circle" style="width:8px;height:8px"></span> Light</span>
|
||||
<span class="me-3"><span class="d-inline-block bg-warning rounded-circle" style="width:8px;height:8px"></span> Medium</span>
|
||||
<span><span class="d-inline-block bg-danger rounded-circle" style="width:8px;height:8px"></span> Heavy</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="mb-1">Today's Schedule - October 23, 2023</h6>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Address</th>
|
||||
<th>Type</th>
|
||||
<th>Technician</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>8:00 AM</td>
|
||||
<td>123 Maple St</td>
|
||||
<td>Nuisance</td>
|
||||
<td>S. Johnson</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9:30 AM</td>
|
||||
<td>456 Oak Ave</td>
|
||||
<td>Green Pool</td>
|
||||
<td>M. Williams</td>
|
||||
</tr>
|
||||
<tr class="table-active">
|
||||
<td>11:00 AM</td>
|
||||
<td>789 Pine Ln</td>
|
||||
<td>Prev Source</td>
|
||||
<td>L. Rodriguez</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Technician Roster - Table View -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-info text-white py-2">
|
||||
<h5 class="mb-0">Today's Technician Roster</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-sm tech-table mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Technician</th>
|
||||
<th>Scheduled</th>
|
||||
<th>Completed</th>
|
||||
<th>Phone</th>
|
||||
<th>Status</th>
|
||||
<th>Location</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://via.placeholder.com/32" class="rounded-circle me-2" alt="Photo">
|
||||
<span>Sarah Johnson</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>8</td>
|
||||
<td>5</td>
|
||||
<td>(555) 234-5678</td>
|
||||
<td><span class="badge bg-success">Servicing</span></td>
|
||||
<td>123 Maple St, Zone 3</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://via.placeholder.com/32" class="rounded-circle me-2" alt="Photo">
|
||||
<span>Mark Williams</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>7</td>
|
||||
<td>3</td>
|
||||
<td>(555) 345-6789</td>
|
||||
<td><span class="badge bg-warning text-dark">On Break</span></td>
|
||||
<td>Office - Lunchroom</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://via.placeholder.com/32" class="rounded-circle me-2" alt="Photo">
|
||||
<span>Lisa Rodriguez</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>9</td>
|
||||
<td>6</td>
|
||||
<td>(555) 456-7890</td>
|
||||
<td><span class="badge bg-primary">In Transit</span></td>
|
||||
<td>En route to 789 Pine Ln</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://via.placeholder.com/32" class="rounded-circle me-2" alt="Photo">
|
||||
<span>Carlos Martinez</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>6</td>
|
||||
<td>4</td>
|
||||
<td>(555) 567-8901</td>
|
||||
<td><span class="badge bg-success">Servicing</span></td>
|
||||
<td>202 Birch Dr, Zone 2</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
20
htmlpage/templates/authenticated.html
Normal file
20
htmlpage/templates/authenticated.html
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{template "title" .}} - Nidus Sync</title>
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="/static/vendor/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Fontawesome Icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
{{block "extraheader" .}} {{end}}
|
||||
</head>
|
||||
<body>
|
||||
{{if .User}}
|
||||
{{template "header" .User}}
|
||||
{{end}}
|
||||
{{template "content" .}}
|
||||
<script src="/static/vendor/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
19
htmlpage/templates/base.html
Normal file
19
htmlpage/templates/base.html
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{template "title" .}} - Nidus Sync</title>
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="/static/vendor/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Bootstrap Icons -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
|
||||
<!-- Fontawesome Icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
{{block "extraheader" .}} {{end}}
|
||||
</head>
|
||||
<body>
|
||||
{{template "content" .}}
|
||||
<script src="/static/vendor/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
210
htmlpage/templates/cell.html
Normal file
210
htmlpage/templates/cell.html
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
{{template "authenticated.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
{{template "map" .MapData}}
|
||||
<style>
|
||||
.address-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container mt-4 mb-5">
|
||||
<!-- Location Header Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h1>Location Data View</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Map and Address Section - Side by Side -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-8">
|
||||
<div class="map-container">
|
||||
<div id="map"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body address-container">
|
||||
<h5>Approximate Address:</h5>
|
||||
<p class="lead" id="location-address">123 Main St, Anytown, ST 12345</p>
|
||||
|
||||
<hr class="location-divider">
|
||||
|
||||
<h5>Cell Coordinates (Hexagon):</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="coordinates-table">
|
||||
<tbody>
|
||||
{{ range $i, $cb := .CellBoundary }}
|
||||
<tr>
|
||||
<td><strong>Vertex {{$i}}:</strong></td>
|
||||
<td>{{$cb|latLngDisplay}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<hr class="location-divider">
|
||||
<p>{{.CellBoundary|GISStatement}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Two-Column Layout for Tables -->
|
||||
<div class="row">
|
||||
<!-- Left Column -->
|
||||
<div class="col-md-6">
|
||||
<!-- Breeding Sources Section -->
|
||||
<h2 class="section-header">Mosquito Breeding Sources</h2>
|
||||
<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>Source Type</th>
|
||||
<th>Last Inspected</th>
|
||||
<th>Last Treated</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range .BreedingSources }}
|
||||
<tr>
|
||||
<td><a href="/source/{{.ID}}">{{.ID|uuidShort}}</a></td>
|
||||
<td>{{.Type}}</td>
|
||||
<td>{{.LastInspected|timeSince}}</td>
|
||||
<td>{{.LastTreated|timeSince}}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--
|
||||
<nav aria-label="Breeding sources pagination">
|
||||
<ul class="pagination justify-content-center">
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" href="#" tabindex="-1">Previous</a>
|
||||
</li>
|
||||
<li class="page-item active"><a class="page-link" href="#">1</a></li>
|
||||
<li class="page-item"><a class="page-link" href="#">2</a></li>
|
||||
<li class="page-item"><a class="page-link" href="#">3</a></li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="#">Next</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inspections Section -->
|
||||
<h2 class="section-header">Inspections History</h2>
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>LocationID</th>
|
||||
<th>Location</th>
|
||||
<th>Date</th>
|
||||
<th>Action</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range .Inspections }}
|
||||
<tr>
|
||||
<td><a href="/source/{{.LocationID}}">{{.LocationID|uuidShort}}</a></td>
|
||||
<td>{{.Location}}</td>
|
||||
<td>{{.Date|timeSince}}</td>
|
||||
<td>{{.Action}}</td>
|
||||
<td>{{.Notes}}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<nav aria-label="Inspections pagination">
|
||||
<ul class="pagination justify-content-center">
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" href="#" tabindex="-1">Previous</a>
|
||||
</li>
|
||||
<li class="page-item active"><a class="page-link" href="#">1</a></li>
|
||||
<li class="page-item"><a class="page-link" href="#">2</a></li>
|
||||
<li class="page-item"><a class="page-link" href="#">3</a></li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="#">Next</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column -->
|
||||
<div class="col-md-6">
|
||||
<!-- Treatments Section -->
|
||||
<h2 class="section-header">Treatment History</h2>
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Location</th>
|
||||
<th>Treatment Date</th>
|
||||
<th>Insecticide Used</th>
|
||||
<th>Technician Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range .Treatments }}
|
||||
<tr>
|
||||
<td><a href="/source/{{.LocationID}}">{{.LocationID|uuidShort}}</a></td>
|
||||
<td>{{.Date|timeSince}}</td>
|
||||
<td>{{.Product}}</td>
|
||||
<td>{{.Notes}}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<nav aria-label="Treatments pagination">
|
||||
<ul class="pagination justify-content-center">
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" href="#" tabindex="-1">Previous</a>
|
||||
</li>
|
||||
<li class="page-item active"><a class="page-link" href="#">1</a></li>
|
||||
<li class="page-item"><a class="page-link" href="#">2</a></li>
|
||||
<li class="page-item"><a class="page-link" href="#">3</a></li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="#">Next</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
88
htmlpage/templates/components/header.html
Normal file
88
htmlpage/templates/components/header.html
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
{{define "header"}}
|
||||
<header class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
|
||||
<div class="container">
|
||||
<!-- Logo -->
|
||||
<a class="navbar-brand" href="/">
|
||||
<div class="logo-placeholder" style="width: 100px; height: 40px; background-color: #e9ecef; display: flex; align-items: center; justify-content: center; border-radius: 4px;">
|
||||
<span class="text-muted small">Your Logo</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<!-- Toggle Button for Mobile -->
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<!-- Nav Items -->
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Dashboard</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Projects</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Maps</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- User Info, Notifications & Logout -->
|
||||
<div class="d-flex align-items-center">
|
||||
<!-- Notification Bell with Badge -->
|
||||
<div class="dropdown me-3">
|
||||
<a class="position-relative text-decoration-none" href="#" role="button" id="notificationsDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="bi bi-bell-fill fs-5"></i>
|
||||
{{if gt (len .Notifications) 0}}
|
||||
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
|
||||
{{if gt (len .Notifications) 99}}99+{{else}}{{len .Notifications}}{{end}}
|
||||
<span class="visually-hidden">unread notifications</span>
|
||||
</span>
|
||||
{{end}}
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="notificationsDropdown" style="width: 300px; max-height: 400px; overflow-y: auto;">
|
||||
<li><h6 class="dropdown-header">Notifications</h6></li>
|
||||
{{if gt (len .Notifications) 0}}
|
||||
{{range .Notifications}}
|
||||
<li>
|
||||
<a class="dropdown-item py-2" href="{{.Link}}">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="flex-shrink-0">
|
||||
<div class="{{if eq .Type "alert"}}bg-danger{{else}}bg-primary{{end}} rounded-circle p-1">
|
||||
<i class="bi bi-{{if eq .Type "alert"}}exclamation{{else}}info{{end}}-circle text-white small"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow-1 ms-2">
|
||||
<p class="mb-0 small">{{.Message}}</p>
|
||||
<span class="text-muted x-small">{{.Time | timeSince}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item text-center small" href="notifications.html">View all</a></li>
|
||||
{{else}}
|
||||
<li><p class="dropdown-item text-center py-3 text-muted">No new notifications</p></li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="dropdown">
|
||||
<a class="text-decoration-none dropdown-toggle d-flex align-items-center" href="#" role="button" id="userDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<div class="avatar me-2 bg-primary rounded-circle d-flex align-items-center justify-content-center" style="width: 36px; height: 36px;">
|
||||
<span class="text-white">{{ .Initials }}</span>
|
||||
</div>
|
||||
<span class="me-2">{{ .DisplayName }}</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown">
|
||||
<li><a class="dropdown-item" href="/settings">Settings</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item text-danger" href="login.html">Logout</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{{end}}
|
||||
84
htmlpage/templates/components/map.html
Normal file
84
htmlpage/templates/components/map.html
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
{{define "map"}}
|
||||
<script src='https://api.mapbox.com/mapbox-gl-js/v3.17.0-beta.1/mapbox-gl.js'></script>
|
||||
<link href='https://api.mapbox.com/mapbox-gl-js/v3.17.0-beta.1/mapbox-gl.css' rel='stylesheet' />
|
||||
<script>
|
||||
const geojson = JSON.parse({{.GeoJSON}})
|
||||
function addMarkers(map, markers) {
|
||||
for (let i = 0; i < markers.length; i++) {
|
||||
let marker = markers[i];
|
||||
marker.addTo(map);
|
||||
}
|
||||
}
|
||||
function mapMarkers() {
|
||||
const markers = [
|
||||
{{ range .Markers }}
|
||||
new mapboxgl.Marker().setLngLat([{{.LatLng.Lng}}, {{.LatLng.Lat}}])
|
||||
{{end}}
|
||||
];
|
||||
return markers;
|
||||
}
|
||||
|
||||
function onLoad() {
|
||||
console.log("Setting up the map...", geojson);
|
||||
mapboxgl.accessToken = {{ .MapboxToken }};
|
||||
const map = new mapboxgl.Map({
|
||||
container: "map",
|
||||
center: [{{.Center.Lng}}, {{.Center.Lat}}],
|
||||
style: 'mapbox://styles/mapbox/streets-v12', // style URL
|
||||
zoom: {{.Zoom}},
|
||||
});
|
||||
map.on("load", function() {
|
||||
console.log("Map post-load...");
|
||||
addMarkers(map, mapMarkers());
|
||||
const sourceId = 'h3-hexes';
|
||||
const layerId = 'h3-hexes-layer';
|
||||
let source = map.getSource(sourceId);
|
||||
|
||||
if (!source) {
|
||||
map.addSource(sourceId, {
|
||||
type: 'geojson',
|
||||
data: geojson
|
||||
});
|
||||
map.addLayer({
|
||||
id: layerId,
|
||||
source: sourceId,
|
||||
type: 'fill',
|
||||
interactive: false,
|
||||
paint: {
|
||||
'fill-color': '#F00000',
|
||||
'fill-opacity': 0.3
|
||||
}
|
||||
});
|
||||
source = map.getSource(sourceId);
|
||||
}
|
||||
source.setData(geojson);
|
||||
|
||||
console.log("Map post-load done.");
|
||||
});
|
||||
console.log("Map init done.");
|
||||
}
|
||||
window.addEventListener("load", onLoad);
|
||||
</script>
|
||||
<style>
|
||||
.map-container {
|
||||
background-color: #e9ecef;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
||||
height: 500px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
#map {
|
||||
height: 500px;
|
||||
width:100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
#map img {
|
||||
max-width: none;
|
||||
min-width: 0px;
|
||||
height: auto;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
310
htmlpage/templates/dashboard.html
Normal file
310
htmlpage/templates/dashboard.html
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
{{template "authenticated.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
<script src='https://api.mapbox.com/mapbox-gl-js/v3.17.0-beta.1/mapbox-gl.js'></script>
|
||||
<link href='https://api.mapbox.com/mapbox-gl-js/v3.17.0-beta.1/mapbox-gl.css' rel='stylesheet' />
|
||||
<script>
|
||||
function onLoad() {
|
||||
console.log("Setting up the map...");
|
||||
mapboxgl.accessToken = {{ .MapData.MapboxToken }};
|
||||
const map = new mapboxgl.Map({
|
||||
container: 'map', // container ID
|
||||
style: 'mapbox://styles/mapbox/standard', // style URL
|
||||
center: [-119.3, 36.327], // starting position [lng, lat]
|
||||
//center: [7.01, 50.74],
|
||||
zoom: 9 // starting zoom
|
||||
});
|
||||
map.on("load", function() {
|
||||
console.log("Map post-load...");
|
||||
map.addSource('tegola-bonn', {
|
||||
'type': 'vector',
|
||||
'tiles': [
|
||||
//'https://tiles.mapillary.com/maps/vtp/mly1_public/2/{z}/{x}/{y}?access_token=MLY|4142433049200173|72206abe5035850d6743b23a49c41333'
|
||||
'https://tegola.nidus.cloud/maps/bonn/{z}/{x}/{y}'
|
||||
]
|
||||
//'minzoom': 6,
|
||||
//'maxzoom': 14
|
||||
});
|
||||
map.addSource('tegola-nidus', {
|
||||
'type': 'vector',
|
||||
'tiles': [
|
||||
//'https://tiles.mapillary.com/maps/vtp/mly1_public/2/{z}/{x}/{y}?access_token=MLY|4142433049200173|72206abe5035850d6743b23a49c41333'
|
||||
'https://tegola.nidus.cloud/maps/nidus/{z}/{x}/{y}?organization_id=1'
|
||||
]
|
||||
//'minzoom': 6,
|
||||
//'maxzoom': 14
|
||||
});
|
||||
map.addLayer({
|
||||
'id': 'bonn', // Layer ID
|
||||
'type': 'fill',
|
||||
'source': 'tegola-bonn', // ID of the tile source created above
|
||||
'source-layer': 'lakes',
|
||||
'paint': {
|
||||
'fill-opacity': 0.1,
|
||||
'fill-color': 'rgb(100, 50, 20)'
|
||||
}
|
||||
//slot: 'middle' // middle slot in Mapbox Standard style
|
||||
});
|
||||
map.addLayer({
|
||||
'id': 'nidus', // Layer ID
|
||||
'type': 'fill',
|
||||
'filter': ['==', ['zoom'], ['+', 2, ['to-number', ['get', 'resolution']]]],
|
||||
'source': 'tegola-nidus', // ID of the tile source created above
|
||||
'source-layer': 'h3_aggregation',
|
||||
'paint': {
|
||||
'fill-opacity': 0.3,
|
||||
'fill-color': 'rgb(250, 100, 100)'
|
||||
}
|
||||
//slot: 'middle' // middle slot in Mapbox Standard style
|
||||
});
|
||||
map.addInteraction("nidus-click-interaction", {
|
||||
type: 'click',
|
||||
target: { layerId: 'nidus' },
|
||||
handler: (e) => {
|
||||
const coordinates = e.feature.geometry.coordinates.slice();
|
||||
const properties = e.feature.properties;
|
||||
//console.log("Coordinates", coordinates[0]);
|
||||
//console.log("Properties", properties.cell, properties.count_);
|
||||
/*new mapboxgl.Popup()
|
||||
.setLngLat(coordinates[0][0])
|
||||
.setHTML("Cell: " + properties.cell)
|
||||
.addTo(map);*/
|
||||
window.location.href = '/cell/' + properties.cell;
|
||||
}
|
||||
});
|
||||
map.addInteraction('nidus-mouseenter-interaction', {
|
||||
type: 'mouseenter',
|
||||
target: { layerId: 'nidus' },
|
||||
handler: () => {
|
||||
map.getCanvas().style.cursor = 'pointer';
|
||||
}
|
||||
});
|
||||
map.addInteraction('nidus-mouseleave-interaction', {
|
||||
type: 'mouseleave',
|
||||
target: { layerId: 'nidus' },
|
||||
handler: () => {
|
||||
map.getCanvas().style.cursor = '';
|
||||
}
|
||||
});
|
||||
|
||||
console.log("Map post-load done.");
|
||||
});
|
||||
|
||||
map.addControl(new mapboxgl.NavigationControl());
|
||||
console.log("Map init done.");
|
||||
}
|
||||
window.addEventListener("load", onLoad);
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.dashboard-container {
|
||||
padding: 20px 0;
|
||||
}
|
||||
.stats-card {
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
||||
transition: transform 0.2s;
|
||||
height: 100%;
|
||||
}
|
||||
.stats-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
.map-container {
|
||||
background-color: #e9ecef;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
||||
height: 500px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
#map {
|
||||
height: 500px;
|
||||
width:100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
#map img {
|
||||
max-width: none;
|
||||
min-width: 0px;
|
||||
height: auto;
|
||||
}
|
||||
.section-title {
|
||||
margin: 30px 0 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
.last-refreshed {
|
||||
color: #6c757d;
|
||||
}
|
||||
.logo-placeholder {
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
background-color: #e9ecef;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.metric-icon {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 10px;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.metric-value {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
.syncing {
|
||||
color: #28a745;
|
||||
animation: fa-spin 2s linear infinite;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container dashboard-container">
|
||||
<!-- Dashboard Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<h1>{{ .Org }} Dashboard</h1>
|
||||
<p class="text-muted">Overview of mosquito control activities in your district</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end d-flex align-items-center justify-content-md-end">
|
||||
{{ if .IsSyncOngoing }}
|
||||
<p class="last-refreshed mb-0">
|
||||
<i class="fas fa-sync-alt me-2 syncing"></i>Syncing now...
|
||||
</p>
|
||||
{{ else }}
|
||||
<p class="last-refreshed mb-0">
|
||||
<i class="fas fa-sync-alt me-2"></i>Last updated: <span id="last-refreshed-time">{{ .LastSync | timeSince }}</span>
|
||||
<button class="btn btn-sm btn-outline-primary ms-3">Refresh Data</button>
|
||||
</p>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Key Metrics -->
|
||||
<div class="row g-4">
|
||||
<!-- Last Refreshed -->
|
||||
<div class="col-md-3">
|
||||
<div class="card stats-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="metric-icon bg-info bg-opacity-10 text-info">
|
||||
<i class="fas fa-clock"></i>
|
||||
</div>
|
||||
<h5 class="card-title">Last Data Refresh</h5>
|
||||
<p class="metric-value">{{ .LastSync | timeSince }}</p>
|
||||
<p class="card-text text-muted">Last sync: 12:45 PM</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Service Requests -->
|
||||
<div class="col-md-3">
|
||||
<div class="card stats-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="metric-icon bg-warning bg-opacity-10 text-warning">
|
||||
<i class="fas fa-ticket-alt"></i>
|
||||
</div>
|
||||
<h5 class="card-title">Service Requests</h5>
|
||||
<p class="metric-value">{{ .CountServiceRequests | bigNumber }}</p>
|
||||
<p class="card-text text-muted">
|
||||
<span class="text-success">
|
||||
<i class="fas fa-arrow-up"></i> 12%
|
||||
</span> since last week
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mosquito Sources -->
|
||||
<div class="col-md-3">
|
||||
<div class="card stats-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="metric-icon bg-danger bg-opacity-10 text-danger">
|
||||
<i class="fas fa-bug"></i>
|
||||
</div>
|
||||
<h5 class="card-title">Mosquito Sources</h5>
|
||||
<p class="metric-value">{{ .CountMosquitoSources | bigNumber }}</p>
|
||||
<p class="card-text text-muted">
|
||||
<span class="text-danger">
|
||||
<i class="fas fa-arrow-up"></i> 8%
|
||||
</span> since last month
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inspections -->
|
||||
<div class="col-md-3">
|
||||
<div class="card stats-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="metric-icon bg-success bg-opacity-10 text-success">
|
||||
<i class="fas fa-clipboard-check"></i>
|
||||
</div>
|
||||
<h5 class="card-title">Inspections</h5>
|
||||
<p class="metric-value">{{ .CountInspections | bigNumber }}</p>
|
||||
<p class="card-text text-muted">
|
||||
<span class="text-success">
|
||||
<i class="fas fa-arrow-up"></i> 15%
|
||||
</span> since last week
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Map Section -->
|
||||
<h3 class="section-title">Mosquito Activity Heatmap</h3>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="map-container" id="mosquito-heatmap">
|
||||
<div id='map'></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity Section -->
|
||||
<h3 class="section-title">Recent Activity</h3>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Type</th>
|
||||
<th>Location</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range $i, $sr := .RecentRequests }}
|
||||
<tr>
|
||||
<td>{{ $sr.Date | timeSince }}</td>
|
||||
<td>Service Request</td>
|
||||
<td>{{ $sr.Location }}</td>
|
||||
<td><span class="badge bg-success">Completed</span></td>
|
||||
<td><a href="#" class="btn btn-sm btn-outline-primary">View</a></td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
200
htmlpage/templates/data-entry-bad.html
Normal file
200
htmlpage/templates/data-entry-bad.html
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Data Entry{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.results-container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.error-code {
|
||||
font-family: monospace;
|
||||
padding: 0.2rem 0.4rem;
|
||||
background-color: rgba(220, 53, 69, 0.1);
|
||||
border-radius: 0.2rem;
|
||||
color: #dc3545;
|
||||
}
|
||||
.error-table {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.error-suggestions {
|
||||
color: #495057;
|
||||
}
|
||||
.table-header-error {
|
||||
width: 40%;
|
||||
}
|
||||
.table-header-suggestion {
|
||||
width: 40%;
|
||||
}
|
||||
.table-header-line {
|
||||
width: 20%;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container mt-4 results-container mb-5">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Upload Failed: pools-data-2023.csv</h2>
|
||||
<span class="badge bg-danger rounded-pill">
|
||||
<i class="bi bi-x-circle me-1"></i> Validation Errors
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="me-3">
|
||||
<i class="bi bi-exclamation-triangle-fill" style="font-size: 2rem;"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="alert-heading">CSV Upload Failed</h4>
|
||||
<p class="mb-0">Your file contains several errors that must be fixed before it can be processed. Details are provided below.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Error Summary</h5>
|
||||
<a href="#" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-download me-1"></i> Download Error Log
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>We found <strong>12 errors</strong> in your CSV file. The most common issues are:</p>
|
||||
<ul>
|
||||
<li><strong>Missing required column:</strong> The "Latitude" column is not present in your file</li>
|
||||
<li><strong>Invalid data format:</strong> 8 GPS coordinates contain non-numeric values</li>
|
||||
<li><strong>Empty required fields:</strong> 3 records are missing Plat ID values</li>
|
||||
</ul>
|
||||
|
||||
<div class="alert alert-info" role="alert">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
<strong>Tip:</strong> Make sure your column names exactly match the required format. Column names are case-sensitive.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="mb-0">Detailed Error Report</h5>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped error-table mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th class="table-header-line">Line Number</th>
|
||||
<th class="table-header-error">Error</th>
|
||||
<th class="table-header-suggestion">Suggestion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="fw-bold text-center">1</td>
|
||||
<td>
|
||||
<span class="error-code">MISSING_COLUMN</span><br>
|
||||
Required column "Latitude" is missing from the header row
|
||||
</td>
|
||||
<td class="error-suggestions">
|
||||
Add a "Latitude" column to your CSV file. Make sure the spelling and capitalization match exactly.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold text-center">5</td>
|
||||
<td>
|
||||
<span class="error-code">INVALID_DATA_FORMAT</span><br>
|
||||
GPS coordinate "37.45N" is not a valid decimal number
|
||||
</td>
|
||||
<td class="error-suggestions">
|
||||
Change "37.45N" to a decimal format (e.g., "37.45"). Remove any non-numeric characters except for the decimal point.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold text-center">8</td>
|
||||
<td>
|
||||
<span class="error-code">EMPTY_REQUIRED_FIELD</span><br>
|
||||
Plat ID is empty or missing
|
||||
</td>
|
||||
<td class="error-suggestions">
|
||||
Add a Plat ID value for this record. Each pool must have a unique identifier.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold text-center">12</td>
|
||||
<td>
|
||||
<span class="error-code">INVALID_DATA_FORMAT</span><br>
|
||||
GPS coordinate "unknown" is not a valid decimal number
|
||||
</td>
|
||||
<td class="error-suggestions">
|
||||
Replace "unknown" with the actual longitude value in decimal format (e.g., "-122.4194").
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold text-center">17</td>
|
||||
<td>
|
||||
<span class="error-code">INVALID_DATA_FORMAT</span><br>
|
||||
GPS coordinate "N/A" is not a valid decimal number
|
||||
</td>
|
||||
<td class="error-suggestions">
|
||||
Replace "N/A" with the actual latitude value in decimal format (e.g., "37.7749").
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="fw-bold text-center">21</td>
|
||||
<td>
|
||||
<span class="error-code">EMPTY_REQUIRED_FIELD</span><br>
|
||||
Plat ID is empty or missing
|
||||
</td>
|
||||
<td class="error-suggestions">
|
||||
Add a Plat ID value for this record. Each pool must have a unique identifier.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card-footer bg-white">
|
||||
<nav aria-label="Error pagination">
|
||||
<ul class="pagination justify-content-center m-0">
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" href="#" tabindex="-1" aria-disabled="true">Previous</a>
|
||||
</li>
|
||||
<li class="page-item active"><a class="page-link" href="#">1</a></li>
|
||||
<li class="page-item"><a class="page-link" href="#">2</a></li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="#">Next</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="mb-0">Next Steps</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ol>
|
||||
<li>Download the error log for a complete list of issues (optional)</li>
|
||||
<li>Fix the errors in your CSV file</li>
|
||||
<li>Re-upload the corrected file using the button below</li>
|
||||
</ol>
|
||||
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<i class="bi bi-lightbulb me-2"></i>
|
||||
<strong>Recommendation:</strong> Review our <a href="#" class="alert-link">CSV formatting guide</a> to ensure your file meets all requirements.
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<button class="btn btn-primary btn-lg" id="uploadNewBtn">
|
||||
<i class="bi bi-arrow-repeat me-2"></i> Upload Corrected File
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<a href="#" class="text-muted">
|
||||
<i class="bi bi-question-circle me-1"></i> Need help? Contact support
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
212
htmlpage/templates/data-entry-good.html
Normal file
212
htmlpage/templates/data-entry-good.html
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Data Entry{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.results-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.summary-card {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.summary-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
.warning-row {
|
||||
background-color: rgba(255, 193, 7, 0.15) !important;
|
||||
}
|
||||
.status-badge {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container mt-4 results-container">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Upload Results: pools-data-2023.csv</h2>
|
||||
<span class="badge bg-success rounded-pill">
|
||||
<i class="bi bi-check-circle me-1"></i> File Parsed Successfully
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card summary-card h-100 border-primary">
|
||||
<div class="card-body text-center">
|
||||
<h1 class="display-4 text-primary">45</h1>
|
||||
<h5>Existing Pools</h5>
|
||||
<p class="text-muted">Matches found in previous records</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card summary-card h-100 border-success">
|
||||
<div class="card-body text-center">
|
||||
<h1 class="display-4 text-success">23</h1>
|
||||
<h5>New Pools</h5>
|
||||
<p class="text-muted">Not found in existing records</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card summary-card h-100 border-warning">
|
||||
<div class="card-body text-center">
|
||||
<h1 class="display-4 text-warning">4</h1>
|
||||
<h5>Outside District</h5>
|
||||
<p class="text-muted">Potential geocoding errors</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Data Preview</h5>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="showIssuesOnly">
|
||||
<label class="form-check-label" for="showIssuesOnly">Show issues only</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
<strong>Warning:</strong> 4 entries appear to be outside district boundaries. These are highlighted in yellow below.
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-striped">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Plat ID</th>
|
||||
<th>Latitude</th>
|
||||
<th>Longitude</th>
|
||||
<th>Street Address</th>
|
||||
<th>Status</th>
|
||||
<th>In District</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>P12345</td>
|
||||
<td>37.7749</td>
|
||||
<td>-122.4194</td>
|
||||
<td>123 Main St, Anytown, CA</td>
|
||||
<td><span class="badge bg-primary status-badge">Existing</span></td>
|
||||
<td><i class="bi bi-check-circle-fill text-success"></i> Yes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>P23456</td>
|
||||
<td>37.3352</td>
|
||||
<td>-121.8811</td>
|
||||
<td>456 Oak Ave, Someville, CA</td>
|
||||
<td><span class="badge bg-primary status-badge">Existing</span></td>
|
||||
<td><i class="bi bi-check-circle-fill text-success"></i> Yes</td>
|
||||
</tr>
|
||||
<tr class="warning-row">
|
||||
<td>P34567</td>
|
||||
<td>38.5816</td>
|
||||
<td>-121.4944</td>
|
||||
<td>789 Pine Rd, Outtown, CA</td>
|
||||
<td><span class="badge bg-success status-badge">New</span></td>
|
||||
<td>
|
||||
<i class="bi bi-exclamation-triangle-fill text-warning"></i>
|
||||
<strong>No</strong> - Outside northern boundary
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>P45678</td>
|
||||
<td>37.4419</td>
|
||||
<td>-122.1430</td>
|
||||
<td>101 Elm St, Cityville, CA</td>
|
||||
<td><span class="badge bg-success status-badge">New</span></td>
|
||||
<td><i class="bi bi-check-circle-fill text-success"></i> Yes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>P56789</td>
|
||||
<td>37.3541</td>
|
||||
<td>-121.9552</td>
|
||||
<td>202 Maple Dr, Townburg, CA</td>
|
||||
<td><span class="badge bg-primary status-badge">Existing</span></td>
|
||||
<td><i class="bi bi-check-circle-fill text-success"></i> Yes</td>
|
||||
</tr>
|
||||
<tr class="warning-row">
|
||||
<td>P67890</td>
|
||||
<td>35.3733</td>
|
||||
<td>-119.0187</td>
|
||||
<td>303 Cedar Ln, Farville, CA</td>
|
||||
<td><span class="badge bg-success status-badge">New</span></td>
|
||||
<td>
|
||||
<i class="bi bi-exclamation-triangle-fill text-warning"></i>
|
||||
<strong>No</strong> - Outside southern boundary
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>P78901</td>
|
||||
<td>37.8044</td>
|
||||
<td>-122.2712</td>
|
||||
<td>404 Birch Ave, Metroburg, CA</td>
|
||||
<td><span class="badge bg-primary status-badge">Existing</span></td>
|
||||
<td><i class="bi bi-check-circle-fill text-success"></i> Yes</td>
|
||||
</tr>
|
||||
<tr class="warning-row">
|
||||
<td>P89012</td>
|
||||
<td>37.4032</td>
|
||||
<td>-123.9612</td>
|
||||
<td>505 Walnut St, Edgetown, CA</td>
|
||||
<td><span class="badge bg-success status-badge">New</span></td>
|
||||
<td>
|
||||
<i class="bi bi-exclamation-triangle-fill text-warning"></i>
|
||||
<strong>No</strong> - Outside western boundary
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<nav aria-label="Table navigation">
|
||||
<ul class="pagination justify-content-center mt-3">
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" href="#" tabindex="-1" aria-disabled="true">Previous</a>
|
||||
</li>
|
||||
<li class="page-item active"><a class="page-link" href="#">1</a></li>
|
||||
<li class="page-item"><a class="page-link" href="#">2</a></li>
|
||||
<li class="page-item"><a class="page-link" href="#">3</a></li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="#">Next</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="mb-0">Notes & Recommendations</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<p><strong>Issues detected:</strong></p>
|
||||
<ul>
|
||||
<li>4 pools appear to be outside district boundaries (possible geocoding errors)</li>
|
||||
<li>All required fields are present and properly formatted</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info" role="alert">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
<strong>Note:</strong> You may proceed with this upload or edit your CSV file to fix the issues identified.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between mt-4 mb-5">
|
||||
<a href="#" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-1"></i> Upload Edited File
|
||||
</a>
|
||||
<button class="btn btn-primary" id="confirmUploadBtn">
|
||||
<i class="bi bi-check2 me-1"></i> Confirm and Submit Data
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
114
htmlpage/templates/data-entry.html
Normal file
114
htmlpage/templates/data-entry.html
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Data Entry{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.upload-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.schema-table {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.upload-area {
|
||||
border: 2px dashed #dee2e6;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
margin: 1.5rem 0;
|
||||
border-radius: 5px;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.required-field::after {
|
||||
content: "*";
|
||||
color: red;
|
||||
margin-left: 3px;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container mt-4 upload-container">
|
||||
<h2 class="mb-4">Upload Pool Data</h2>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="mb-0">CSV Upload Requirements</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Your CSV file must contain the following columns in any order. Please ensure your data matches the required format.</p>
|
||||
|
||||
<table class="table table-bordered schema-table">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
<th>Format</th>
|
||||
<th>Required</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="required-field">Latitude</td>
|
||||
<td>GPS latitude coordinate</td>
|
||||
<td>Decimal (e.g., 37.7749)</td>
|
||||
<td>Yes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="required-field">Longitude</td>
|
||||
<td>GPS longitude coordinate</td>
|
||||
<td>Decimal (e.g., -122.4194)</td>
|
||||
<td>Yes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="required-field">Plat ID</td>
|
||||
<td>Unique identifier for the property</td>
|
||||
<td>Alphanumeric (e.g., P12345)</td>
|
||||
<td>Yes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Street Address</td>
|
||||
<td>Nearest street address to the pool</td>
|
||||
<td>Text (e.g., 123 Main St)</td>
|
||||
<td>No</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="alert alert-info small">
|
||||
<i class="bi bi-info-circle"></i> Need a template? <a href="#" class="alert-link">Download sample CSV file</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="mb-0">Upload Data</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="upload-area">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" class="bi bi-cloud-arrow-up text-primary mb-3" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M7.646 5.146a.5.5 0 0 1 .708 0l2 2a.5.5 0 0 1-.708.708L8.5 6.707V10.5a.5.5 0 0 1-1 0V6.707L6.354 7.854a.5.5 0 1 1-.708-.708l2-2z"/>
|
||||
<path d="M4.406 3.342A5.53 5.53 0 0 1 8 2c2.69 0 4.923 2 5.166 4.579C14.758 6.804 16 8.137 16 9.773 16 11.569 14.502 13 12.687 13H3.781C1.708 13 0 11.366 0 9.318c0-1.763 1.266-3.223 2.942-3.593.143-.863.698-1.723 1.464-2.383zm.653.757c-.757.653-1.153 1.44-1.153 2.056v.448l-.445.049C2.064 6.805 1 7.952 1 9.318 1 10.785 2.23 12 3.781 12h8.906C13.98 12 15 10.988 15 9.773c0-1.216-1.02-2.228-2.313-2.228h-.5v-.5C12.188 4.825 10.328 3 8 3a4.53 4.53 0 0 0-2.941 1.1z"/>
|
||||
</svg>
|
||||
<h5>Select your CSV file</h5>
|
||||
<p class="text-muted">Drag and drop a file here or click to browse</p>
|
||||
<input type="file" class="form-control" id="csvFile" accept=".csv">
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<a href="/mock/data-entry/good">
|
||||
<button class="btn btn-primary" type="button" id="uploadButton">
|
||||
Upload and Continue
|
||||
</button>
|
||||
</a>
|
||||
<a href="/mock/data-entry/bad">
|
||||
See what happens on a bad upload
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-muted text-center mt-4">
|
||||
<small>Need assistance? Contact <a href="mailto:support@example.com">support@example.com</a></small>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
260
htmlpage/templates/dispatch-results.html
Normal file
260
htmlpage/templates/dispatch-results.html
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Data Entry{{end}}
|
||||
{{define "extraheader"}}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const checkboxes = document.querySelectorAll('.route-select');
|
||||
const switchBtn = document.getElementById('switchRoutesBtn');
|
||||
|
||||
// Enable/disable switch button based on selection
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('change', () => {
|
||||
const checkedBoxes = document.querySelectorAll('.route-select:checked');
|
||||
switchBtn.disabled = checkedBoxes.length !== 2;
|
||||
});
|
||||
});
|
||||
|
||||
// For demonstration purposes
|
||||
switchBtn.addEventListener('click', function() {
|
||||
const selectedRoutes = Array.from(document.querySelectorAll('.route-select:checked')).map(cb => cb.value);
|
||||
alert(`Switching routes ${selectedRoutes[0]} and ${selectedRoutes[1]}`);
|
||||
// In a real application, this would trigger the route switching logic
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
.main-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.map-container {
|
||||
height: 400px;
|
||||
background-color: #e9ecef;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
}
|
||||
.route-badge {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.tech-photo {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.action-button {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
.completion-alert {
|
||||
border-left: 5px solid #0d6efd;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h1>Route Calculation Results</h1>
|
||||
<a href="{{ .URLs.Dispatch }}" class="btn btn-outline-primary">
|
||||
<i class="bi bi-pencil-square me-1"></i> Edit Parameters
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Map View -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h2 class="h4 mb-0">Route Map</h2>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="map-container d-flex align-items-center justify-content-center">
|
||||
<div class="text-center">
|
||||
<i class="bi bi-map fs-1 text-muted"></i>
|
||||
<h3 class="text-muted">Interactive Map View</h3>
|
||||
<p class="text-muted">Routes are color-coded by technician assignment</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Completion Projection -->
|
||||
<div class="alert alert-info mb-4 completion-alert">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-calendar-check fs-4 me-3"></i>
|
||||
<div>
|
||||
<h4 class="h5 mb-1">Coverage Projection</h4>
|
||||
<p class="mb-0">If every day were like today, all pools would be complete on <strong>October 27, 2023</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Route Summary Table -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
||||
<h2 class="h4 mb-0">Route Summary</h2>
|
||||
<button class="btn btn-sm btn-light" id="switchRoutesBtn" disabled>
|
||||
<i class="bi bi-arrow-left-right me-1"></i> Switch Selected Routes
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th width="40">Select</th>
|
||||
<th width="50">Route</th>
|
||||
<th>Technician</th>
|
||||
<th>Cold Call Pools</th>
|
||||
<th>Drone Inspections</th>
|
||||
<th>Service Calls</th>
|
||||
<th>Warrants</th>
|
||||
<th>Est. Time</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input route-select" type="checkbox" value="A" id="routeA">
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="route-badge bg-primary text-white">A</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://randomuser.me/api/portraits/men/32.jpg" alt="John Davis" class="tech-photo me-2">
|
||||
<span>John Davis</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>12</td>
|
||||
<td>0</td>
|
||||
<td>5</td>
|
||||
<td>2</td>
|
||||
<td>6h 15m</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-eye"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input route-select" type="checkbox" value="B" id="routeB">
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="route-badge bg-success text-white">B</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://randomuser.me/api/portraits/women/65.jpg" alt="Sarah Johnson" class="tech-photo me-2">
|
||||
<span>Sarah Johnson</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>8</td>
|
||||
<td>3</td>
|
||||
<td>4</td>
|
||||
<td>1</td>
|
||||
<td>7h 30m</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-eye"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input route-select" type="checkbox" value="C" id="routeC">
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="route-badge bg-danger text-white">C</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://randomuser.me/api/portraits/men/44.jpg" alt="Michael Chen" class="tech-photo me-2">
|
||||
<span>Michael Chen</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>10</td>
|
||||
<td>4</td>
|
||||
<td>3</td>
|
||||
<td>0</td>
|
||||
<td>7h 45m</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-eye"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input route-select" type="checkbox" value="D" id="routeD">
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="route-badge bg-warning text-dark">D</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://randomuser.me/api/portraits/women/22.jpg" alt="Jessica Martinez" class="tech-photo me-2">
|
||||
<span>Jessica Martinez</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>14</td>
|
||||
<td>2</td>
|
||||
<td>6</td>
|
||||
<td>3</td>
|
||||
<td>8h 00m</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-eye"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{{ .URLs.Dispatch }}" class="btn btn-outline-secondary action-button">
|
||||
<i class="bi bi-arrow-left me-2"></i>Back to Parameters
|
||||
</a>
|
||||
<a href="{{ .URLs.Root }}" class="btn btn-success action-button">
|
||||
<i class="bi bi-check-circle me-2"></i>Approve & Dispatch
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal for Route Details -->
|
||||
<div class="modal fade" id="routeDetailsModal" tabindex="-1" aria-labelledby="routeDetailsModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="routeDetailsModalLabel">Route A Details</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Route details would go here -->
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
172
htmlpage/templates/dispatch.html
Normal file
172
htmlpage/templates/dispatch.html
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Data Entry{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.tech-photo {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.main-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.action-button {
|
||||
padding: 15px 30px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container-fluid p-4 main-container">
|
||||
<h1 class="mb-4">Technician Routing & Dispatch</h1>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h2 class="h4 mb-0">Technician Roster</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Technician</th>
|
||||
<th>Working Hours</th>
|
||||
<th>Truck Assignment</th>
|
||||
<th>Warrant Service</th>
|
||||
<th>Drone Certified</th>
|
||||
<th>Routing Options</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://randomuser.me/api/portraits/men/32.jpg" alt="John Davis" class="tech-photo me-3">
|
||||
<div>
|
||||
<div class="fw-bold">John Davis</div>
|
||||
<div class="small text-muted">ID: T-1001</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>7:00 AM - 3:30 PM</td>
|
||||
<td>Truck #103</td>
|
||||
<td><span class="badge bg-success">Yes</span></td>
|
||||
<td><span class="badge bg-danger">No</span></td>
|
||||
<td>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" id="warrant-partner1" data-bs-toggle="tooltip" data-bs-placement="left" title="Require second person for warrant service">
|
||||
<label class="form-check-label" for="warrant-partner1">Warrant Partner</label>
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" id="resident-history1" data-bs-toggle="tooltip" data-bs-placement="left" title="Route to previously visited residents">
|
||||
<label class="form-check-label" for="resident-history1">Prior Interactions</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="include-route1" checked data-bs-toggle="tooltip" data-bs-placement="left" title="Include in route calculations">
|
||||
<label class="form-check-label" for="include-route1">Include in Routes</label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://randomuser.me/api/portraits/women/65.jpg" alt="Sarah Johnson" class="tech-photo me-3">
|
||||
<div>
|
||||
<div class="fw-bold">Sarah Johnson</div>
|
||||
<div class="small text-muted">ID: T-1042</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>8:00 AM - 4:30 PM</td>
|
||||
<td>Truck #118</td>
|
||||
<td><span class="badge bg-success">Yes</span></td>
|
||||
<td><span class="badge bg-success">Yes</span></td>
|
||||
<td>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" id="warrant-partner2" data-bs-toggle="tooltip" data-bs-placement="left" title="Require second person for warrant service">
|
||||
<label class="form-check-label" for="warrant-partner2">Warrant Partner</label>
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" id="resident-history2" checked data-bs-toggle="tooltip" data-bs-placement="left" title="Route to previously visited residents">
|
||||
<label class="form-check-label" for="resident-history2">Prior Interactions</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="include-route2" checked data-bs-toggle="tooltip" data-bs-placement="left" title="Include in route calculations">
|
||||
<label class="form-check-label" for="include-route2">Include in Routes</label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://randomuser.me/api/portraits/men/44.jpg" alt="Michael Chen" class="tech-photo me-3">
|
||||
<div>
|
||||
<div class="fw-bold">Michael Chen</div>
|
||||
<div class="small text-muted">ID: T-1019</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>6:30 AM - 3:00 PM</td>
|
||||
<td>Truck #107</td>
|
||||
<td><span class="badge bg-danger">No</span></td>
|
||||
<td><span class="badge bg-success">Yes</span></td>
|
||||
<td>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" id="warrant-partner3" disabled data-bs-toggle="tooltip" data-bs-placement="left" title="Require second person for warrant service">
|
||||
<label class="form-check-label" for="warrant-partner3">Warrant Partner</label>
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" id="resident-history3" data-bs-toggle="tooltip" data-bs-placement="left" title="Route to previously visited residents">
|
||||
<label class="form-check-label" for="resident-history3">Prior Interactions</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="include-route3" checked data-bs-toggle="tooltip" data-bs-placement="left" title="Include in route calculations">
|
||||
<label class="form-check-label" for="include-route3">Include in Routes</label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="https://randomuser.me/api/portraits/women/22.jpg" alt="Jessica Martinez" class="tech-photo me-3">
|
||||
<div>
|
||||
<div class="fw-bold">Jessica Martinez</div>
|
||||
<div class="small text-muted">ID: T-1055</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>7:30 AM - 4:00 PM</td>
|
||||
<td>Truck #112</td>
|
||||
<td><span class="badge bg-success">Yes</span></td>
|
||||
<td><span class="badge bg-success">Yes</span></td>
|
||||
<td>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" id="warrant-partner4" checked data-bs-toggle="tooltip" data-bs-placement="left" title="Require second person for warrant service">
|
||||
<label class="form-check-label" for="warrant-partner4">Warrant Partner</label>
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" id="resident-history4" checked data-bs-toggle="tooltip" data-bs-placement="left" title="Route to previously visited residents">
|
||||
<label class="form-check-label" for="resident-history4">Prior Interactions</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="include-route4" checked data-bs-toggle="tooltip" data-bs-placement="left" title="Include in route calculations">
|
||||
<label class="form-check-label" for="include-route4">Include in Routes</label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 col-md-6 mx-auto">
|
||||
<a href="{{ .URLs.DispatchResults }}" class="btn btn-primary btn-lg action-button" type="button">
|
||||
<i class="bi bi-calculator me-2"></i>Calculate Routes
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
7
htmlpage/templates/empty-auth.html
Normal file
7
htmlpage/templates/empty-auth.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{{template "authenticated.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
{{end}}
|
||||
7
htmlpage/templates/empty.html
Normal file
7
htmlpage/templates/empty.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
{{end}}
|
||||
56
htmlpage/templates/mock-root.html
Normal file
56
htmlpage/templates/mock-root.html
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Data Entry{{end}}
|
||||
{{define "extraheader"}}
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container">
|
||||
<h1>Mock Listing</h1>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" style="min-width: 20vw">Link</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><a href="/mock/admin">/mock/admin</a></td>
|
||||
<td>Admin</td>
|
||||
<td>Used by office admins to handle phone calls and other public-facing responsibilities</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="/mock/data-entry">/mock/flyover-data-entry</a></td>
|
||||
<td>Flyover Data Entry</td>
|
||||
<td>Used to upload CSV files with information about problematic pools</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="/mock/dispatch">/mock/dispatch</a></td>
|
||||
<td>Dispatch</td>
|
||||
<td>Used each day to calculate the working routes for technicians</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="/mock/report">/mock/report</a></td>
|
||||
<td>Reporting Overview</td>
|
||||
<td>Shows examples of text message contents, printable QR codes, and email bodies for sending to the public to help them self-report</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="/mock/report/abc-123">/mock/report/abc-123</a></td>
|
||||
<td>Self-Report</td>
|
||||
<td>A page for members of the public to report a green pool.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="/mock/service-request">/mock/service-request</a></td>
|
||||
<td>Service Request</td>
|
||||
<td>A page for members of the public to make a direct service request</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="/mock/setting">/mock/setting</a></td>
|
||||
<td>Settings</td>
|
||||
<td>A page for management to control the behavior of Nidus</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{end}}
|
||||
108
htmlpage/templates/oauth-prompt.html
Normal file
108
htmlpage/templates/oauth-prompt.html
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
{{template "authenticated.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.connect-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.connect-box {
|
||||
box-shadow: 0 0 15px rgba(0,0,0,0.1);
|
||||
border-radius: 10px;
|
||||
padding: 40px;
|
||||
background-color: #fff;
|
||||
}
|
||||
.connect-header {
|
||||
margin-bottom: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
.logo-area {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.logo-placeholder {
|
||||
width: 120px;
|
||||
height: 60px;
|
||||
background-color: #e9ecef;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.steps-container {
|
||||
margin: 30px 0;
|
||||
}
|
||||
.step {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
border-left: 3px solid #0d6efd;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.connect-btn {
|
||||
margin-top: 30px;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container min-vh-100 d-flex align-items-center justify-content-center py-5">
|
||||
<div class="connect-container">
|
||||
<!-- Logo Area -->
|
||||
<div class="logo-area">
|
||||
<div class="logo-placeholder">
|
||||
<span class="text-muted">Your Logo</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="connect-box">
|
||||
<div class="connect-header">
|
||||
<h1>Connect Your ArcGIS Account</h1>
|
||||
<p class="text-muted">Link your data to get started</p>
|
||||
</div>
|
||||
|
||||
<div class="connect-content">
|
||||
<p>To provide you with the best experience, we need to connect to your ArcGIS account. This allows us to securely access and visualize your spatial data within our platform.</p>
|
||||
|
||||
<div class="steps-container">
|
||||
<h4>What to expect:</h4>
|
||||
|
||||
<div class="step">
|
||||
<h5>1. Secure Authentication</h5>
|
||||
<p>When you click the "Connect to ArcGIS" button below, you'll be redirected to the official ArcGIS login page. This connection is secure and uses OAuth 2.0 protocol.</p>
|
||||
</div>
|
||||
|
||||
<div class="step">
|
||||
<h5>2. Grant Permissions</h5>
|
||||
<p>After logging in with your ArcGIS credentials, you'll be asked to approve permissions for our application to access your data. We only request access to what's needed for the platform to function.</p>
|
||||
</div>
|
||||
|
||||
<div class="step">
|
||||
<h5>3. Return to Platform</h5>
|
||||
<p>Once authentication is complete, you'll be automatically redirected back to our platform where your data will be available to work with.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Note:</strong> You'll need an active ArcGIS Online account or ArcGIS Enterprise account to proceed. If you don't have one, you can <a href="https://www.arcgis.com/home/signin.html" target="_blank">create an ArcGIS account here</a>.
|
||||
</div>
|
||||
|
||||
<p>By connecting your ArcGIS account, you'll be able to:</p>
|
||||
<ul>
|
||||
<li>Access and visualize your spatial data</li>
|
||||
<li>Perform advanced analysis using our integrated tools</li>
|
||||
<li>Share results with team members securely</li>
|
||||
<li>Keep your data synchronized across platforms</li>
|
||||
</ul>
|
||||
|
||||
<div class="text-center connect-btn">
|
||||
<a href="/arcgis/oauth/begin" class="btn btn-primary btn-lg">
|
||||
Connect to ArcGIS
|
||||
</a>
|
||||
<p class="mt-2 text-muted"><small>You can disconnect your account at any time in settings</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
216
htmlpage/templates/report-confirmation.html
Normal file
216
htmlpage/templates/report-confirmation.html
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Login{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.page-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.content-card {
|
||||
background-color: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.logo-area {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.logo-placeholder {
|
||||
height: 50px;
|
||||
max-width: 200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.success-icon {
|
||||
font-size: 70px;
|
||||
color: #198754;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
.confirmation-title {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.appointment-summary {
|
||||
background-color: #e8f4f8;
|
||||
border-left: 4px solid #0d6efd;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
.appointment-details {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.appointment-detail {
|
||||
flex: 1;
|
||||
min-width: 150px;
|
||||
padding: 10px;
|
||||
background-color: #fff;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
.detail-label {
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
color: #6c757d;
|
||||
font-weight: 600;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.detail-value {
|
||||
font-weight: 600;
|
||||
}
|
||||
.contact-info {
|
||||
background-color: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-top: 25px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
.contact-method {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.contact-method i {
|
||||
font-size: 1.2rem;
|
||||
margin-right: 10px;
|
||||
color: #0d6efd;
|
||||
width: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
.tracking-link {
|
||||
background-color: #e9f7ef;
|
||||
border: 1px solid #d1e7dd;
|
||||
border-left: 4px solid #198754;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.tracking-link:hover {
|
||||
background-color: #d1e7dd;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
.confirmation-number {
|
||||
background-color: #f8f9fa;
|
||||
padding: 10px 15px;
|
||||
border-radius: 6px;
|
||||
font-family: monospace;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="page-container">
|
||||
<!-- Logo -->
|
||||
<div class="logo-area">
|
||||
<img src="https://placehold.co/200x50/e9ecef/adb5bd?text=County+Vector+Control" alt="County Vector Control" class="logo-placeholder">
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="content-card">
|
||||
<span class="success-icon">
|
||||
<i class="bi bi-check-circle-fill"></i>
|
||||
</span>
|
||||
|
||||
<div class="confirmation-title">
|
||||
<h1 class="h3">Thank You for Your Submission!</h1>
|
||||
<p class="text-muted">Your green pool report has been successfully submitted.</p>
|
||||
</div>
|
||||
|
||||
<!-- Appointment Summary -->
|
||||
<div class="appointment-summary">
|
||||
<h5><i class="bi bi-calendar-check me-2"></i>Appointment Confirmed</h5>
|
||||
<p>Our inspector will visit your property at the scheduled time:</p>
|
||||
|
||||
<div class="appointment-details">
|
||||
<div class="appointment-detail">
|
||||
<div class="detail-label">Date</div>
|
||||
<div class="detail-value">Thursday, June 22, 2023</div>
|
||||
</div>
|
||||
<div class="appointment-detail">
|
||||
<div class="detail-label">Time</div>
|
||||
<div class="detail-value">10:00 AM</div>
|
||||
</div>
|
||||
<div class="appointment-detail">
|
||||
<div class="detail-label">Confirmation #</div>
|
||||
<div class="detail-value">GP-23685</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- What's Next Section -->
|
||||
<div>
|
||||
<h5 class="mb-3">What Happens Next?</h5>
|
||||
<ul class="mb-4">
|
||||
<li>A confirmation email has been sent to the email address you provided.</li>
|
||||
<li>You'll receive a reminder notification 24 hours before your scheduled appointment.</li>
|
||||
<li>Our team will review your report and contact you by the next business day if any additional information is needed.</li>
|
||||
<li>During the scheduled visit, our inspector will assess the pool condition and discuss treatment options if necessary.</li>
|
||||
</ul>
|
||||
<p>You can use the link below to track your report status and view the photos you've submitted.</p>
|
||||
</div>
|
||||
|
||||
<!-- Tracking Link -->
|
||||
<a href="#" class="tracking-link">
|
||||
<div>
|
||||
<strong>Track Your Report Status</strong>
|
||||
<p class="mb-0 text-muted">View photos and check for updates</p>
|
||||
</div>
|
||||
<i class="bi bi-arrow-right"></i>
|
||||
</a>
|
||||
|
||||
<!-- Contact Information -->
|
||||
<div class="contact-info">
|
||||
<h5 class="mb-3">Questions or Concerns?</h5>
|
||||
<p>If you have any questions about your report or need to change your appointment, please contact us:</p>
|
||||
|
||||
<div class="contact-method">
|
||||
<i class="bi bi-telephone-fill"></i>
|
||||
<div>
|
||||
<strong>(555) 123-4567</strong>
|
||||
<div class="small text-muted">Monday-Friday, 8:00 AM - 5:00 PM</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="contact-method">
|
||||
<i class="bi bi-envelope-fill"></i>
|
||||
<div>
|
||||
<strong>greenpool@vectorcontrol.county.gov</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mt-3 mb-0 small">Please include your confirmation number (GP-23685) in all correspondence.</p>
|
||||
</div>
|
||||
|
||||
<!-- Print Option -->
|
||||
<div class="d-flex justify-content-center mt-4">
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="window.print()">
|
||||
<i class="bi bi-printer me-2"></i> Print Confirmation
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Help Text -->
|
||||
<div class="text-center text-muted small">
|
||||
<p>Thank you for helping keep our community safe from mosquito-borne diseases.</p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
239
htmlpage/templates/report-contribute.html
Normal file
239
htmlpage/templates/report-contribute.html
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Login{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.page-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.content-card {
|
||||
background-color: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.logo-area {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.logo-placeholder {
|
||||
height: 50px;
|
||||
max-width: 200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.progress-container {
|
||||
margin: 30px 0 20px;
|
||||
}
|
||||
.progress {
|
||||
height: 8px;
|
||||
}
|
||||
.upload-area {
|
||||
border: 2px dashed #dee2e6;
|
||||
border-radius: 10px;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
margin-bottom: 25px;
|
||||
background-color: #f8f9fa;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.upload-area:hover {
|
||||
border-color: #0d6efd;
|
||||
background-color: #f0f7ff;
|
||||
}
|
||||
.upload-icon {
|
||||
font-size: 48px;
|
||||
color: #6c757d;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.photo-example {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.example-item {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
text-align: center;
|
||||
}
|
||||
.example-img {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
border: 2px solid #dee2e6;
|
||||
}
|
||||
.good-example {
|
||||
border-color: #198754;
|
||||
}
|
||||
.poor-example {
|
||||
border-color: #dc3545;
|
||||
}
|
||||
.upload-options {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.upload-options button {
|
||||
flex: 1;
|
||||
min-width: 150px;
|
||||
}
|
||||
.uploaded-photos {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.uploaded-photo {
|
||||
position: relative;
|
||||
height: 150px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 2px solid #dee2e6;
|
||||
}
|
||||
.uploaded-photo img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.remove-photo {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
color: #dc3545;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.tips-section {
|
||||
background-color: #e8f4f8;
|
||||
border-left: 4px solid #0d6efd;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="page-container">
|
||||
<!-- Logo -->
|
||||
<div class="logo-area">
|
||||
<img src="https://placehold.co/200x50/e9ecef/adb5bd?text=County+Vector+Control" alt="County Vector Control" class="logo-placeholder">
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="progress-container">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="text-muted small">Upload Photos</span>
|
||||
<span class="text-muted small">3 of 4</span>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" style="width: 75%" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="content-card">
|
||||
<h1 class="h4 mb-3">Upload Current Pool Photos</h1>
|
||||
<p>Please provide current photos of your pool to help us assess its condition.</p>
|
||||
|
||||
<!-- Photo Tips Section -->
|
||||
<div class="tips-section">
|
||||
<h5><i class="bi bi-lightbulb me-2"></i>Photo Tips</h5>
|
||||
<ul class="mb-0">
|
||||
<li><strong>Take photos from as high an angle as possible</strong> (second story window, deck, etc.)</li>
|
||||
<li>Try to capture the <strong>entire pool</strong> in your photo</li>
|
||||
<li>Ensure photos are <strong>clear and well-lit</strong></li>
|
||||
<li>You can add <strong>multiple photos</strong> from different angles</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Photo Examples -->
|
||||
<h5 class="mb-3">Photo Examples:</h5>
|
||||
<div class="photo-example">
|
||||
<div class="example-item">
|
||||
<img src="https://placehold.co/400x300/198754/ffffff?text=Good+Example" alt="Good photo example" class="example-img good-example">
|
||||
<div class="text-success"><i class="bi bi-check-circle me-1"></i>Good: High angle, full view</div>
|
||||
</div>
|
||||
|
||||
<div class="example-item">
|
||||
<img src="https://placehold.co/400x300/dc3545/ffffff?text=Poor+Example" alt="Poor photo example" class="example-img poor-example">
|
||||
<div class="text-danger"><i class="bi bi-x-circle me-1"></i>Poor: Ground level, partial view</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload Area -->
|
||||
<div class="upload-area">
|
||||
<div class="upload-icon">
|
||||
<i class="bi bi-camera"></i>
|
||||
</div>
|
||||
<h5>Add Pool Photos</h5>
|
||||
<p class="text-muted">Take a new photo or upload from your device</p>
|
||||
|
||||
<div class="upload-options">
|
||||
<button type="button" class="btn btn-primary">
|
||||
<i class="bi bi-camera-fill me-2"></i> Take Photo
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary">
|
||||
<i class="bi bi-image me-2"></i> Upload from Device
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<input type="file" id="photo-upload" accept="image/*" multiple style="display: none;">
|
||||
</div>
|
||||
|
||||
<!-- Already Uploaded Photos Section -->
|
||||
<div>
|
||||
<h5 class="mb-3">Uploaded Photos (2)</h5>
|
||||
<div class="uploaded-photos">
|
||||
<div class="uploaded-photo">
|
||||
<img src="https://placehold.co/300x300/76b5c5/ffffff?text=Pool+Photo+1" alt="Uploaded pool photo">
|
||||
<button type="button" class="remove-photo" aria-label="Remove photo">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="uploaded-photo">
|
||||
<img src="https://placehold.co/300x300/3e8e7e/ffffff?text=Pool+Photo+2" alt="Uploaded pool photo">
|
||||
<button type="button" class="remove-photo" aria-label="Remove photo">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Information -->
|
||||
<div class="alert alert-secondary mt-4">
|
||||
<small>You can add up to 5 photos to provide a complete view of your pool area. We recommend taking photos from multiple angles.</small>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<a href="{{ .URLs.ReportEvidence }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-2"></i> Back
|
||||
</a>
|
||||
<a href="{{ .URLs.ReportSchedule }}" class="btn btn-primary">
|
||||
Next Step <i class="bi bi-arrow-right ms-2"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Help Text -->
|
||||
<div class="text-center text-muted small">
|
||||
<p>If you need assistance, please contact Vector Control at (555) 123-4567</p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
132
htmlpage/templates/report-detail.html
Normal file
132
htmlpage/templates/report-detail.html
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Login{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.page-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.content-card {
|
||||
background-color: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.logo-area {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.logo-placeholder {
|
||||
height: 50px;
|
||||
max-width: 200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.map-container {
|
||||
height: 300px;
|
||||
background-color: #e9ecef;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.map-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
background-image: url('https://placehold.co/600x300/e9ecef/adb5bd?text=Map+View');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
.map-pin {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -100%);
|
||||
color: #dc3545;
|
||||
font-size: 30px;
|
||||
}
|
||||
.address-container {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
border-left: 4px solid #0d6efd;
|
||||
}
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.progress-container {
|
||||
margin: 30px 0 20px;
|
||||
}
|
||||
.progress {
|
||||
height: 8px;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="page-container">
|
||||
<!-- Logo -->
|
||||
<div class="logo-area">
|
||||
<img src="https://placehold.co/200x50/e9ecef/adb5bd?text=County+Vector+Control" alt="County Vector Control" class="logo-placeholder">
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="progress-container">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="text-muted small">Location</span>
|
||||
<span class="text-muted small">1 of 4</span>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" style="width: 25%" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="content-card">
|
||||
<h1 class="h4 mb-4">Confirm Property Location</h1>
|
||||
|
||||
<!-- Map View -->
|
||||
<div class="map-container">
|
||||
<div class="map-placeholder"></div>
|
||||
<div class="map-pin">
|
||||
<i class="bi bi-geo-alt-fill"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Address Information -->
|
||||
<div class="address-container">
|
||||
<div class="mb-2"><strong>Detected Address:</strong></div>
|
||||
<h5>123 Maple Street, Riverside, CA 92501</h5>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<p>Is this the correct location of the property in question?</p>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="action-buttons">
|
||||
<a href="{{ .URLs.ReportEvidence }}" class="btn btn-success flex-grow-1">
|
||||
<i class="bi bi-check-circle me-2"></i> Correct
|
||||
</a>
|
||||
<a href="{{ .URLs.ReportUpdate }}" class="btn btn-outline-primary flex-grow-1">
|
||||
<i class="bi bi-geo-alt me-2"></i> Update Location
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Help Text -->
|
||||
<div class="text-center text-muted small">
|
||||
<p>If you need assistance, please contact Vector Control at (555) 123-4567</p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
243
htmlpage/templates/report-evidence.html
Normal file
243
htmlpage/templates/report-evidence.html
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Login{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.page-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.content-card {
|
||||
background-color: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.logo-area {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.logo-placeholder {
|
||||
height: 50px;
|
||||
max-width: 200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.photo-gallery {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.photo-item {
|
||||
min-width: 200px;
|
||||
height: 150px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 2px solid #dee2e6;
|
||||
}
|
||||
.photo-item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.data-section {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
.section-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
color: #0d6efd;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.section-title i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.alert-info {
|
||||
background-color: #e8f4f8;
|
||||
border-color: #b8daff;
|
||||
}
|
||||
.progress-container {
|
||||
margin: 30px 0 20px;
|
||||
}
|
||||
.progress {
|
||||
height: 8px;
|
||||
}
|
||||
.trap-high {
|
||||
color: #dc3545;
|
||||
font-weight: bold;
|
||||
}
|
||||
.trap-medium {
|
||||
color: #fd7e14;
|
||||
font-weight: bold;
|
||||
}
|
||||
.trap-low {
|
||||
color: #198754;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="page-container">
|
||||
<!-- Logo -->
|
||||
<div class="logo-area">
|
||||
<img src="https://placehold.co/200x50/e9ecef/adb5bd?text=County+Vector+Control" alt="County Vector Control" class="logo-placeholder">
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="progress-container">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="text-muted small">Evidence</span>
|
||||
<span class="text-muted small">2 of 4</span>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" style="width: 50%" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="content-card">
|
||||
<h1 class="h4 mb-4">Evidence of Potential Breeding Site</h1>
|
||||
|
||||
<!-- Aerial Photos -->
|
||||
<div class="data-section">
|
||||
<div class="section-title">
|
||||
<i class="bi bi-camera-fill"></i> Aerial Surveillance Photos
|
||||
</div>
|
||||
<p class="small text-muted mb-2">These photos were taken during routine aerial surveillance of the area.</p>
|
||||
|
||||
<div class="photo-gallery">
|
||||
<div class="photo-item">
|
||||
<img src="https://placehold.co/200x150/76b5c5/ffffff?text=Aerial+Photo+1" alt="Aerial photo of property">
|
||||
</div>
|
||||
<div class="photo-item">
|
||||
<img src="https://placehold.co/200x150/3e8e7e/ffffff?text=Aerial+Photo+2" alt="Aerial photo of property">
|
||||
</div>
|
||||
<div class="photo-item">
|
||||
<img src="https://placehold.co/200x150/7accc8/ffffff?text=Aerial+Photo+3" alt="Aerial photo of property">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Historical Inspections -->
|
||||
<div class="data-section">
|
||||
<div class="section-title">
|
||||
<i class="bi bi-clipboard-check-fill"></i> Historical Inspection Data
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Inspector</th>
|
||||
<th>Findings</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Mar 15, 2023</td>
|
||||
<td>J. Martinez</td>
|
||||
<td>Pool water stagnant, green</td>
|
||||
<td>Treatment applied, owner notified</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nov 02, 2022</td>
|
||||
<td>L. Johnson</td>
|
||||
<td>Pool water clear, maintained</td>
|
||||
<td>No action needed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Aug 18, 2022</td>
|
||||
<td>S. Williams</td>
|
||||
<td>Minor algae formation</td>
|
||||
<td>Owner provided maintenance resources</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mosquito Trap Data -->
|
||||
<div class="data-section">
|
||||
<div class="section-title">
|
||||
<i class="bi bi-bug-fill"></i> Mosquito Trap Count Data
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table class="table table-striped table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date Collected</th>
|
||||
<th>Count</th>
|
||||
<th>Distance</th>
|
||||
<th>Level</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Jun 12, 2023</td>
|
||||
<td>42</td>
|
||||
<td>0.3 miles</td>
|
||||
<td class="trap-high">High</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Jun 05, 2023</td>
|
||||
<td>36</td>
|
||||
<td>0.3 miles</td>
|
||||
<td class="trap-high">High</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>May 29, 2023</td>
|
||||
<td>28</td>
|
||||
<td>0.3 miles</td>
|
||||
<td class="trap-medium">Medium</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>May 22, 2023</td>
|
||||
<td>15</td>
|
||||
<td>0.3 miles</td>
|
||||
<td class="trap-low">Low</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>May 15, 2023</td>
|
||||
<td>12</td>
|
||||
<td>0.3 miles</td>
|
||||
<td class="trap-low">Low</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Public Health Information -->
|
||||
<div class="alert alert-info">
|
||||
<h5><i class="bi bi-info-circle me-2"></i>Why This Matters</h5>
|
||||
<p>The data above shows mosquito activity in your area. Recent trap counts indicate elevated mosquito populations, which increases the risk of mosquito-borne diseases like West Nile virus.</p>
|
||||
<p>Unmaintained swimming pools can produce thousands of mosquitoes each week. By addressing potential breeding sites, you're helping protect your family and neighbors from these health risks.</p>
|
||||
<p class="mb-0"><strong>We need your help</strong> to ensure we maintain public health by keeping mosquito counts low in your neighborhood. Your cooperation makes a significant difference in community safety.</p>
|
||||
</div>
|
||||
|
||||
<!-- Action Button -->
|
||||
<div class="d-grid gap-2 mt-4">
|
||||
<a href="{{ .URLs.ReportContribute }}" class="btn btn-primary btn-lg">
|
||||
<i class="bi bi-arrow-right-circle me-2"></i> Next Step: Upload Current Photos
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Help Text -->
|
||||
<div class="text-center text-muted small">
|
||||
<p>If you need assistance, please contact Vector Control at (555) 123-4567</p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
319
htmlpage/templates/report-schedule.html
Normal file
319
htmlpage/templates/report-schedule.html
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Login{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.page-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.content-card {
|
||||
background-color: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.logo-area {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.logo-placeholder {
|
||||
height: 50px;
|
||||
max-width: 200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.progress-container {
|
||||
margin: 30px 0 20px;
|
||||
}
|
||||
.progress {
|
||||
height: 8px;
|
||||
}
|
||||
.calendar-section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.dates-container {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
gap: 10px;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.date-card {
|
||||
min-width: 110px;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.date-card:hover {
|
||||
border-color: #0d6efd;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.date-card.selected {
|
||||
border-color: #0d6efd;
|
||||
background-color: #e6f2ff;
|
||||
box-shadow: 0 0 0 1px #0d6efd;
|
||||
}
|
||||
.date-card .month {
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
color: #6c757d;
|
||||
font-weight: 600;
|
||||
}
|
||||
.date-card .day {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.date-card .weekday {
|
||||
font-size: 0.8rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
.time-slots {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.time-slot {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.time-slot:hover {
|
||||
border-color: #0d6efd;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.time-slot.selected {
|
||||
border-color: #0d6efd;
|
||||
background-color: #e6f2ff;
|
||||
box-shadow: 0 0 0 1px #0d6efd;
|
||||
}
|
||||
.time-slot.unavailable {
|
||||
background-color: #f8f9fa;
|
||||
color: #adb5bd;
|
||||
cursor: not-allowed;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.form-section {
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #dee2e6;
|
||||
}
|
||||
.section-title {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.section-title i {
|
||||
margin-right: 10px;
|
||||
color: #0d6efd;
|
||||
}
|
||||
.selected-date-time {
|
||||
background-color: #e8f4f8;
|
||||
border-left: 4px solid #0d6efd;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="page-container">
|
||||
<!-- Logo -->
|
||||
<div class="logo-area">
|
||||
<img src="https://placehold.co/200x50/e9ecef/adb5bd?text=County+Vector+Control" alt="County Vector Control" class="logo-placeholder">
|
||||
</div>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="progress-container">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="text-muted small">Schedule Follow-up</span>
|
||||
<span class="text-muted small">4 of 4</span>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" style="width: 100%" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="content-card">
|
||||
<h1 class="h4 mb-3">Schedule a Follow-up Inspection</h1>
|
||||
<p>Please select a convenient date and time for our inspector to visit your property.</p>
|
||||
|
||||
<!-- Calendar Section -->
|
||||
<div class="calendar-section">
|
||||
<div class="section-title">
|
||||
<i class="bi bi-calendar-date"></i>
|
||||
<h5 class="mb-0">Select Date</h5>
|
||||
</div>
|
||||
|
||||
<!-- Date Selection -->
|
||||
<div class="dates-container">
|
||||
<div class="date-card">
|
||||
<div class="month">Jun</div>
|
||||
<div class="day">20</div>
|
||||
<div class="weekday">Tue</div>
|
||||
</div>
|
||||
<div class="date-card">
|
||||
<div class="month">Jun</div>
|
||||
<div class="day">21</div>
|
||||
<div class="weekday">Wed</div>
|
||||
</div>
|
||||
<div class="date-card selected">
|
||||
<div class="month">Jun</div>
|
||||
<div class="day">22</div>
|
||||
<div class="weekday">Thu</div>
|
||||
</div>
|
||||
<div class="date-card">
|
||||
<div class="month">Jun</div>
|
||||
<div class="day">23</div>
|
||||
<div class="weekday">Fri</div>
|
||||
</div>
|
||||
<div class="date-card">
|
||||
<div class="month">Jun</div>
|
||||
<div class="day">26</div>
|
||||
<div class="weekday">Mon</div>
|
||||
</div>
|
||||
<div class="date-card">
|
||||
<div class="month">Jun</div>
|
||||
<div class="day">27</div>
|
||||
<div class="weekday">Tue</div>
|
||||
</div>
|
||||
<div class="date-card">
|
||||
<div class="month">Jun</div>
|
||||
<div class="day">28</div>
|
||||
<div class="weekday">Wed</div>
|
||||
</div>
|
||||
<div class="date-card">
|
||||
<div class="month">Jun</div>
|
||||
<div class="day">29</div>
|
||||
<div class="weekday">Thu</div>
|
||||
</div>
|
||||
<div class="date-card">
|
||||
<div class="month">Jun</div>
|
||||
<div class="day">30</div>
|
||||
<div class="weekday">Fri</div>
|
||||
</div>
|
||||
<div class="date-card">
|
||||
<div class="month">Jul</div>
|
||||
<div class="day">03</div>
|
||||
<div class="weekday">Mon</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Time Slot Selection -->
|
||||
<div class="section-title">
|
||||
<i class="bi bi-clock"></i>
|
||||
<h5 class="mb-0">Select Time</h5>
|
||||
</div>
|
||||
|
||||
<div class="time-slots">
|
||||
<div class="time-slot unavailable">
|
||||
8:00 AM
|
||||
</div>
|
||||
<div class="time-slot unavailable">
|
||||
9:00 AM
|
||||
</div>
|
||||
<div class="time-slot selected">
|
||||
10:00 AM
|
||||
</div>
|
||||
<div class="time-slot">
|
||||
11:00 AM
|
||||
</div>
|
||||
<div class="time-slot">
|
||||
1:00 PM
|
||||
</div>
|
||||
<div class="time-slot">
|
||||
2:00 PM
|
||||
</div>
|
||||
<div class="time-slot">
|
||||
3:00 PM
|
||||
</div>
|
||||
<div class="time-slot">
|
||||
4:00 PM
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Selected Date & Time Summary -->
|
||||
<div class="selected-date-time">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<strong>Selected Appointment:</strong>
|
||||
<span>Thursday, June 22, 2023 at 10:00 AM</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contact Information Form -->
|
||||
<div class="form-section">
|
||||
<div class="section-title">
|
||||
<i class="bi bi-person-lines-fill"></i>
|
||||
<h5 class="mb-0">Contact Information</h5>
|
||||
</div>
|
||||
|
||||
<form>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="fullName" class="form-label">Full Name</label>
|
||||
<input type="text" class="form-control" id="fullName" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="phone" class="form-label">Phone Number</label>
|
||||
<input type="tel" class="form-control" id="phone" placeholder="(555) 555-5555" required>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="email" class="form-label">Email Address</label>
|
||||
<input type="email" class="form-control" id="email" required>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label for="notes" class="form-label">Additional Notes (Optional)</label>
|
||||
<textarea class="form-control" id="notes" rows="3" placeholder="Any special instructions or information for the inspector"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="reminders" checked>
|
||||
<label class="form-check-label" for="reminders">
|
||||
Send me text reminders about my appointment
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Information -->
|
||||
<div class="alert alert-secondary mt-4">
|
||||
<small>Our inspector will need access to view your pool area. If you won't be home during the appointment, please provide access instructions in the notes.</small>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<a href="{{ .URLs.ReportContribute }}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left me-2"></i> Back
|
||||
</a>
|
||||
<a href="{{ .URLs.ReportConfirmation }}" class="btn btn-success">
|
||||
<i class="bi bi-calendar-check me-2"></i> Confirm Appointment
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Help Text -->
|
||||
<div class="text-center text-muted small">
|
||||
<p>If you need assistance, please contact Vector Control at (555) 123-4567</p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
196
htmlpage/templates/report-update.html
Normal file
196
htmlpage/templates/report-update.html
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Login{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.page-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.content-card {
|
||||
background-color: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.logo-area {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.logo-placeholder {
|
||||
height: 50px;
|
||||
max-width: 200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.map-container {
|
||||
height: 350px;
|
||||
background-color: #e9ecef;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 25px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
.map-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
background-image: url('https://placehold.co/800x350/e9ecef/adb5bd?text=Interactive+Map');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
.map-pin {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -100%);
|
||||
color: #dc3545;
|
||||
font-size: 30px;
|
||||
filter: drop-shadow(0px 2px 2px rgba(0,0,0,0.3));
|
||||
}
|
||||
.map-instructions {
|
||||
position: absolute;
|
||||
bottom: 15px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
padding: 10px 15px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
max-width: 90%;
|
||||
text-align: center;
|
||||
}
|
||||
.or-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 25px 0;
|
||||
color: #6c757d;
|
||||
}
|
||||
.or-divider::before, .or-divider::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
.or-divider::before {
|
||||
margin-right: 15px;
|
||||
}
|
||||
.or-divider::after {
|
||||
margin-left: 15px;
|
||||
}
|
||||
.info-box {
|
||||
background-color: #e8f4f8;
|
||||
border-left: 4px solid #0d6efd;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.info-box p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.form-section {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="page-container">
|
||||
<!-- Logo -->
|
||||
<div class="logo-area">
|
||||
<img src="https://placehold.co/200x50/e9ecef/adb5bd?text=County+Vector+Control" alt="County Vector Control" class="logo-placeholder">
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="content-card">
|
||||
<h1 class="h4 mb-4">Update Property Location</h1>
|
||||
|
||||
<!-- Information Box -->
|
||||
<div class="info-box mb-4">
|
||||
<h5><i class="bi bi-info-circle me-2"></i>Two Ways to Update Location</h5>
|
||||
<p>You can update the property location by either clicking on the map or entering an address below. Both methods will automatically update each other.</p>
|
||||
</div>
|
||||
|
||||
<!-- Map Section -->
|
||||
<div class="form-section">
|
||||
<h5 class="mb-3">Option 1: Select Location on Map</h5>
|
||||
<div class="map-container">
|
||||
<div class="map-placeholder"></div>
|
||||
<div class="map-pin">
|
||||
<i class="bi bi-geo-alt-fill"></i>
|
||||
</div>
|
||||
<div class="map-instructions">
|
||||
<i class="bi bi-hand-index me-2"></i> Click or tap anywhere on the map to set the location
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="or-divider">OR</div>
|
||||
|
||||
<!-- Address Form -->
|
||||
<div class="form-section">
|
||||
<h5 class="mb-3">Option 2: Enter Address</h5>
|
||||
<form>
|
||||
<div class="mb-3">
|
||||
<label for="streetAddress" class="form-label">Street Address</label>
|
||||
<input type="text" class="form-control" id="streetAddress" placeholder="123 Main Street">
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="city" class="form-label">City</label>
|
||||
<input type="text" class="form-control" id="city" placeholder="Riverside">
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="state" class="form-label">State</label>
|
||||
<select class="form-select" id="state">
|
||||
<option selected>CA</option>
|
||||
<option>AZ</option>
|
||||
<option>NV</option>
|
||||
<option>OR</option>
|
||||
<!-- Add more states as needed -->
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="zipCode" class="form-label">ZIP Code</label>
|
||||
<input type="text" class="form-control" id="zipCode" placeholder="92501">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Current Coordinates Display -->
|
||||
<div class="mb-4 p-3 bg-light rounded small text-muted">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>Current Coordinates:</span>
|
||||
<span>33.9806° N, 117.3755° W</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="d-flex justify-content-between">
|
||||
<button type="button" class="btn btn-outline-secondary" id="cancelBtn">
|
||||
Nevermind
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" id="saveBtn">
|
||||
<i class="bi bi-check-circle me-2"></i> Save Updates
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Help Text -->
|
||||
<div class="text-center text-muted small">
|
||||
<p>If you need assistance, please contact Vector Control at (555) 123-4567</p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
224
htmlpage/templates/report.html
Normal file
224
htmlpage/templates/report.html
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Login{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.entry-container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.entry-box {
|
||||
box-shadow: 0 0 15px rgba(0,0,0,0.1);
|
||||
border-radius: 10px;
|
||||
padding: 40px;
|
||||
background-color: #fff;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.entry-header {
|
||||
margin-bottom: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
.logo-area {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.logo-placeholder {
|
||||
width: 120px;
|
||||
height: 60px;
|
||||
background-color: #e9ecef;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.method-section {
|
||||
margin-bottom: 40px;
|
||||
padding: 25px;
|
||||
border-radius: 10px;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.method-title {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.method-title i {
|
||||
font-size: 1.5rem;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.sms-mockup {
|
||||
max-width: 300px;
|
||||
background-color: #dcf8c6;
|
||||
border-radius: 15px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
margin: 20px auto;
|
||||
}
|
||||
.qr-code-placeholder {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background-color: #e9ecef;
|
||||
margin: 20px auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px dashed #ced4da;
|
||||
}
|
||||
.email-mockup {
|
||||
max-width: 550px;
|
||||
background-color: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
.email-header {
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.door-hanger {
|
||||
max-width: 300px;
|
||||
background-color: #fff;
|
||||
border: 2px solid #dc3545;
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
margin: 20px auto;
|
||||
text-align: center;
|
||||
}
|
||||
.door-hanger:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -25px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 40px;
|
||||
height: 25px;
|
||||
background-color: #fff;
|
||||
border: 2px solid #dc3545;
|
||||
border-bottom: none;
|
||||
border-radius: 20px 20px 0 0;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container py-5">
|
||||
<div class="entry-container">
|
||||
<div class="entry-box">
|
||||
<div class="entry-header">
|
||||
<h1>Green Pool Reporting</h1>
|
||||
<p class="text-muted">Entry Points Diagnostic Page</p>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
This page demonstrates the various ways customers can access the Green Pool Reporting system.
|
||||
</div>
|
||||
|
||||
<!-- SMS Entry Point -->
|
||||
<div class="method-section">
|
||||
<div class="method-title">
|
||||
<i class="bi bi-chat-dots-fill text-primary"></i>
|
||||
<h3>Text Message Entry Point</h3>
|
||||
</div>
|
||||
|
||||
<p>Customers will receive the following text message with a link to begin the reporting process:</p>
|
||||
|
||||
<div class="sms-mockup">
|
||||
<strong>Vector Control:</strong> We noticed a potential green pool at your property. Please tap the link to report status or schedule inspection: <a href="{{ .URLs.ReportDetail }}">{{ .URLs.ReportDetail }}</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<p><strong>SMS Details:</strong></p>
|
||||
<ul>
|
||||
<li>Sent via automated system after aerial detection</li>
|
||||
<li>Contains unique tracking link for each property</li>
|
||||
<li>Customers tap link to open mobile browser</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Door Hanger Entry Point -->
|
||||
<div class="method-section">
|
||||
<div class="method-title">
|
||||
<i class="bi bi-qr-code text-success"></i>
|
||||
<h3>Door Hanger QR Code Entry Point</h3>
|
||||
</div>
|
||||
|
||||
<p>Inspectors will leave door hangers with a QR code for properties where no one is home:</p>
|
||||
|
||||
<div class="door-hanger">
|
||||
<h5>IMPORTANT NOTICE</h5>
|
||||
<p>We visited regarding a potential mosquito breeding site.</p>
|
||||
|
||||
<img src="/qr-code/report/t78fd3" width="256" height="256"/>
|
||||
|
||||
<p><strong>Scan this code</strong> with your phone camera to report your pool status or schedule an inspection.</p>
|
||||
<p class="small text-muted">Or visit: {{ .URLs.ReportDetail }}</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<p><strong>Door Hanger Details:</strong></p>
|
||||
<ul>
|
||||
<li>Physical notices left on the door handle</li>
|
||||
<li>QR code contains property-specific link</li>
|
||||
<li>Fallback URL provided for manual entry</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email Entry Point -->
|
||||
<div class="method-section">
|
||||
<div class="method-title">
|
||||
<i class="bi bi-envelope-fill text-danger"></i>
|
||||
<h3>Email Notification Entry Point</h3>
|
||||
</div>
|
||||
|
||||
<p>Property owners will receive this email as a follow-up to other communication attempts:</p>
|
||||
|
||||
<div class="email-mockup">
|
||||
<div class="email-header">
|
||||
<strong>From:</strong> Green Pool Response Team <noreply@vectorcontrol.county.gov><br>
|
||||
<strong>To:</strong> Property Owner <resident@example.com><br>
|
||||
<strong>Subject:</strong> Action Required: Green Pool Detected at Your Property
|
||||
</div>
|
||||
|
||||
<div class="email-body">
|
||||
<p>Dear Property Owner,</p>
|
||||
|
||||
<p>Our recent surveillance has identified a potential unmaintained swimming pool at your property located at <strong>123 Main Street</strong>. Untreated pools can become mosquito breeding grounds and pose public health risks, including the spread of West Nile virus and other diseases.</p>
|
||||
|
||||
<div class="text-center my-4">
|
||||
<a href="/report/t78fd3" class="btn btn-primary">Report Pool Status or Schedule Inspection</a>
|
||||
</div>
|
||||
|
||||
<p>Please click the button above or visit <a href="{{ .URLs.ReportDetail }}">{{ .URLs.ReportDetail }}</a> to complete a brief questionnaire about your pool status. This will help us determine if an inspection is needed or if you've already addressed the issue.</p>
|
||||
|
||||
<p>Thank you for helping keep our community safe and healthy.</p>
|
||||
|
||||
<p>Sincerely,<br>
|
||||
Vector Control Department<br>
|
||||
County Health Services</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<p><strong>Email Details:</strong></p>
|
||||
<ul>
|
||||
<li>Sent as follow-up or for property owners with registered email addresses</li>
|
||||
<li>Contains clear call-to-action button and alternative text link</li>
|
||||
<li>Explains reason for contact and next steps</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<a href="/" class="btn btn-outline-primary">Back to Dashboard</a>
|
||||
<a href="{{ .URLs.ReportDetail }}" class="btn btn-success">Test Reporting Flow</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
318
htmlpage/templates/service-request-detail.html
Normal file
318
htmlpage/templates/service-request-detail.html
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.district-logo {
|
||||
max-height: 80px;
|
||||
width: auto;
|
||||
}
|
||||
.map-container {
|
||||
height: 300px;
|
||||
background-color: #e9ecef;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
.progress-tracker {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 20px 0;
|
||||
position: relative;
|
||||
}
|
||||
.progress-tracker::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: #dee2e6;
|
||||
z-index: 1;
|
||||
}
|
||||
.progress-step {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
text-align: center;
|
||||
width: 60px;
|
||||
}
|
||||
.progress-icon {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #dee2e6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.progress-icon.active {
|
||||
border-color: #0d6efd;
|
||||
background-color: #0d6efd;
|
||||
color: white;
|
||||
}
|
||||
.progress-icon.completed {
|
||||
border-color: #198754;
|
||||
background-color: #198754;
|
||||
color: white;
|
||||
}
|
||||
.progress-label {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.type-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.type-badge i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.coordinates {
|
||||
font-size: 0.85rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
.note-item {
|
||||
border-left: 3px solid #0d6efd;
|
||||
padding: 10px 15px;
|
||||
background-color: rgba(13, 110, 253, 0.05);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.note-date {
|
||||
font-size: 0.8rem;
|
||||
color: #6c757d;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<!-- Header -->
|
||||
<header class="bg-light py-3">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-6">
|
||||
<h1 class="district-name">[District Name]</h1>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<img src="placeholder-logo.png" alt="District Logo" class="district-logo">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="py-5">
|
||||
<div class="container">
|
||||
<!-- Report Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-8">
|
||||
<h2>Report #MMD-2023-12345</h2>
|
||||
</div>
|
||||
<div class="col-md-4 text-md-end">
|
||||
<span class="badge bg-primary type-badge">
|
||||
<i class="bi bi-water"></i> Green Pool
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Progress Tracker -->
|
||||
<div class="progress-tracker mb-5">
|
||||
<div class="progress-step">
|
||||
<div class="progress-icon completed">
|
||||
<i class="bi bi-check-lg"></i>
|
||||
</div>
|
||||
<div class="progress-label">Submitted</div>
|
||||
</div>
|
||||
<div class="progress-step">
|
||||
<div class="progress-icon completed">
|
||||
<i class="bi bi-check-lg"></i>
|
||||
</div>
|
||||
<div class="progress-label">Accepted</div>
|
||||
</div>
|
||||
<div class="progress-step">
|
||||
<div class="progress-icon active">
|
||||
<i class="bi bi-clock"></i>
|
||||
</div>
|
||||
<div class="progress-label">Scheduled</div>
|
||||
</div>
|
||||
<div class="progress-step">
|
||||
<div class="progress-icon">
|
||||
<i class="bi bi-flag"></i>
|
||||
</div>
|
||||
<div class="progress-label">Complete</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Map and Location Information -->
|
||||
<div class="col-lg-5 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Location</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Map Placeholder -->
|
||||
<div class="map-container mb-3">
|
||||
<div class="text-center">
|
||||
<i class="bi bi-map fs-1 text-secondary"></i>
|
||||
<p class="mb-0">Map of Report Location</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Address Information -->
|
||||
<div>
|
||||
<h6>Address</h6>
|
||||
<p class="mb-2">123 Mosquito Ave, Lakeside, CA 92040</p>
|
||||
<p class="coordinates mb-0">
|
||||
<i class="bi bi-geo-alt"></i> 32.8573° N, 116.9222° W
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Report Details -->
|
||||
<div class="col-lg-7 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Report Details</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<h6>Report Source</h6>
|
||||
<p><i class="bi bi-phone"></i> Phone Call</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6>Report Date</h6>
|
||||
<p>October 15, 2023 at 2:45 PM</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6>Description</h6>
|
||||
<p>I noticed my neighbor's backyard pool has turned green and there's nobody living in the house currently. I'm concerned it might be breeding mosquitoes as I've noticed more of them in my yard recently. The house seems to be vacant for about 3 months now.</p>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<h6>Pool Status</h6>
|
||||
<p><span class="badge bg-warning text-dark"><i class="bi bi-exclamation-triangle"></i> Stagnant/Green</span></p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6>Scheduled Appointment</h6>
|
||||
<p>October 20, 2023, 9:00 AM - 11:00 AM</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h6>Contact Information</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p class="mb-1"><strong>Reported By:</strong> John Smith</p>
|
||||
<p class="mb-1"><strong>Phone:</strong> (555) 123-4567</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p class="mb-1"><strong>Email:</strong> john.smith@example.com</p>
|
||||
<p class="mb-1"><strong>Preferred Contact:</strong> Phone</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Technician Notes -->
|
||||
<div class="row">
|
||||
<div class="col-12 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Notes & Updates</h5>
|
||||
<span class="badge bg-info">3 Notes</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="note-item">
|
||||
<div class="note-date">Added by System on Oct 15, 2023, 2:45 PM</div>
|
||||
<div class="note-content">Report created via phone call to district office.</div>
|
||||
</div>
|
||||
<div class="note-item">
|
||||
<div class="note-date">Added by Sarah Johnson (Office Staff) on Oct 16, 2023, 9:30 AM</div>
|
||||
<div class="note-content">Verified location information. Property appears to be vacant according to county records. Left voicemail with property management company listed in county database.</div>
|
||||
</div>
|
||||
<div class="note-item">
|
||||
<div class="note-date">Added by Mike Davis (Technician) on Oct 18, 2023, 11:15 AM</div>
|
||||
<div class="note-content">Scheduled inspection for Oct 20. Will need access to backyard. Contacted reporter to confirm they'll be available to provide access information on day of service.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Next Steps -->
|
||||
<div class="row">
|
||||
<div class="col-12 mb-4">
|
||||
<div class="card bg-light">
|
||||
<div class="card-body">
|
||||
<h5><i class="bi bi-arrow-right-circle"></i> Next Steps</h5>
|
||||
<p>Technician scheduled to inspect the property on October 20, 2023, between 9:00 AM - 11:00 AM. If access to the property is not possible, treatment may be conducted from outside the property or additional follow-up may be required.</p>
|
||||
<p class="mb-0"><strong>Note:</strong> You will receive a notification when the status of this report changes.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Update Form -->
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Add Information</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>Do you have additional information about this report? Add it below to update the technician.</p>
|
||||
<form>
|
||||
<div class="mb-3">
|
||||
<label for="updateMessage" class="form-label">Message</label>
|
||||
<textarea class="form-control" id="updateMessage" rows="3" placeholder="Enter additional information here..."></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="contactPhone" class="form-label">Phone Number (optional)</label>
|
||||
<input type="tel" class="form-control" id="contactPhone" placeholder="(555) 123-4567">
|
||||
<div class="form-text">Provide your phone number if you'd like to be contacted about this update.</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Submit Update</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back Button -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<a href="/check-status" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Back to Status Check
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-dark text-white py-4">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p class="mb-0">© 2023 [District Name] Mosquito Management District</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<p class="mb-0">Contact: (555) 123-4567 | info@mosquitodistrict.gov</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
{{end}}
|
||||
367
htmlpage/templates/service-request-location.html
Normal file
367
htmlpage/templates/service-request-location.html
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.district-logo {
|
||||
max-height: 80px;
|
||||
width: auto;
|
||||
}
|
||||
.map-container {
|
||||
height: 400px;
|
||||
background-color: #e9ecef;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #dee2e6;
|
||||
position: relative;
|
||||
}
|
||||
.map-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
.map-overlay {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
max-width: 250px;
|
||||
}
|
||||
.map-controls {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
.map-control-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: white;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.2rem;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.report-type-icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
.days-ago {
|
||||
font-size: 0.85rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
.status-badge {
|
||||
font-size: 0.85rem;
|
||||
font-weight: normal;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
.report-row:hover {
|
||||
background-color: rgba(13, 110, 253, 0.05);
|
||||
cursor: pointer;
|
||||
}
|
||||
.instruction-card {
|
||||
border-left: 4px solid #0d6efd;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<!-- Header -->
|
||||
<header class="bg-light py-3">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-6">
|
||||
<h1 class="district-name">[District Name]</h1>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<img src="placeholder-logo.png" alt="District Logo" class="district-logo">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="py-5">
|
||||
<div class="container">
|
||||
<!-- Page Title -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h2>Lookup Reports by Location</h2>
|
||||
<p class="lead">Find reports and mosquito activity in your area</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Instructions Card -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card instruction-card">
|
||||
<div class="card-body">
|
||||
<h5><i class="bi bi-info-circle me-2"></i>How to use this tool</h5>
|
||||
<p class="mb-0">You can either <strong>enter an address</strong> in the search box or <strong>navigate the map</strong> by dragging and zooming to find reports in your area. The table below will update automatically to show reports within the current map view.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search and Map Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4 mb-3 mb-md-0">
|
||||
<!-- Address Search -->
|
||||
<div class="card h-100">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Search by Address</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="addressSearchForm">
|
||||
<div class="mb-3">
|
||||
<label for="addressInput" class="form-label">Enter an address or location</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="addressInput" placeholder="123 Main St, City, State">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
<i class="bi bi-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Search radius</label>
|
||||
<select class="form-select" id="searchRadius">
|
||||
<option value="0.5">0.5 miles</option>
|
||||
<option value="1" selected>1 mile</option>
|
||||
<option value="2">2 miles</option>
|
||||
<option value="5">5 miles</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="alert alert-info mt-4">
|
||||
<small><i class="bi bi-eye me-1"></i> Currently showing reports within <strong>1 mile</strong> of map center</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-8">
|
||||
<!-- Map Area -->
|
||||
<div class="map-container">
|
||||
<div class="map-placeholder">
|
||||
<i class="bi bi-map fs-1 text-secondary mb-2"></i>
|
||||
<p class="mb-1 fw-bold">Interactive Map Area</p>
|
||||
<p class="text-muted small mb-0">The map will display here and allow you to navigate the area</p>
|
||||
</div>
|
||||
|
||||
<!-- Simulated map controls -->
|
||||
<div class="map-controls">
|
||||
<button class="map-control-btn">
|
||||
<i class="bi bi-plus"></i>
|
||||
</button>
|
||||
<button class="map-control-btn">
|
||||
<i class="bi bi-dash"></i>
|
||||
</button>
|
||||
<button class="map-control-btn">
|
||||
<i class="bi bi-house"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Map overlay showing current view -->
|
||||
<div class="map-overlay">
|
||||
<div class="small fw-bold mb-1">Current View</div>
|
||||
<div class="small">Lakeside, CA</div>
|
||||
<div class="small text-muted">32.857° N, 116.922° W</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results Section -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Reports in This Area</h5>
|
||||
<span class="badge bg-secondary">Showing 12 of 37 total reports</span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Location</th>
|
||||
<th>Submitted</th>
|
||||
<th>Status</th>
|
||||
<th>Report ID</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Report Row 1 -->
|
||||
<tr class="report-row">
|
||||
<td>
|
||||
<span class="report-type-icon text-primary me-1">
|
||||
<i class="bi bi-water"></i>
|
||||
</span>
|
||||
Green Pool
|
||||
</td>
|
||||
<td>123 Mosquito Ave</td>
|
||||
<td>
|
||||
Oct 15, 2023
|
||||
<div class="days-ago">5 days ago</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-primary status-badge">Scheduled</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/report-details?id=MMD-2023-12345">MMD-2023-12345</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Report Row 2 -->
|
||||
<tr class="report-row">
|
||||
<td>
|
||||
<span class="report-type-icon text-danger me-1">
|
||||
<i class="bi bi-bug"></i>
|
||||
</span>
|
||||
Mosquito Nuisance
|
||||
</td>
|
||||
<td>456 Lake Dr</td>
|
||||
<td>
|
||||
Oct 12, 2023
|
||||
<div class="days-ago">8 days ago</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-success status-badge">Complete</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/report-details?id=MMD-2023-12341">MMD-2023-12341</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Report Row 3 -->
|
||||
<tr class="report-row">
|
||||
<td>
|
||||
<span class="report-type-icon text-success me-1">
|
||||
<i class="bi bi-droplet"></i>
|
||||
</span>
|
||||
Fish Request
|
||||
</td>
|
||||
<td>789 Creek Rd</td>
|
||||
<td>
|
||||
Oct 18, 2023
|
||||
<div class="days-ago">2 days ago</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-warning text-dark status-badge">Acknowledged</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/report-details?id=MMD-2023-12350">MMD-2023-12350</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Report Row 4 -->
|
||||
<tr class="report-row">
|
||||
<td>
|
||||
<span class="report-type-icon text-danger me-1">
|
||||
<i class="bi bi-bug"></i>
|
||||
</span>
|
||||
Mosquito Nuisance
|
||||
</td>
|
||||
<td>101 Pond Ln</td>
|
||||
<td>
|
||||
Sep 25, 2023
|
||||
<div class="days-ago">25 days ago</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-success status-badge">Complete</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/report-details?id=MMD-2023-12289">MMD-2023-12289</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Report Row 5 -->
|
||||
<tr class="report-row">
|
||||
<td>
|
||||
<span class="report-type-icon text-primary me-1">
|
||||
<i class="bi bi-water"></i>
|
||||
</span>
|
||||
Green Pool
|
||||
</td>
|
||||
<td>202 Highland Ave</td>
|
||||
<td>
|
||||
Oct 19, 2023
|
||||
<div class="days-ago">1 day ago</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary status-badge">Submitted</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/report-details?id=MMD-2023-12356">MMD-2023-12356</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Report Row 6 -->
|
||||
<tr class="report-row">
|
||||
<td>
|
||||
<span class="report-type-icon text-primary me-1">
|
||||
<i class="bi bi-water"></i>
|
||||
</span>
|
||||
Green Pool
|
||||
</td>
|
||||
<td>303 Marsh Way</td>
|
||||
<td>
|
||||
Aug 15, 2023
|
||||
<div class="days-ago">2 months ago</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-success status-badge">Complete</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/report-details?id=MMD-2023-12056">MMD-2023-12056</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- More rows would go here -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-center">
|
||||
<p class="mb-0 small text-muted">
|
||||
Showing reports submitted within the last 6 months. The table displays up to 20 reports at a time.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back Button -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<a href="/check-status" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Back to Status Check
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-dark text-white py-4">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p class="mb-0">© 2023 [District Name] Mosquito Management District</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<p class="mb-0">Contact: (555) 123-4567 | info@mosquitodistrict.gov</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
{{end}}
|
||||
541
htmlpage/templates/service-request-mosquito.html
Normal file
541
htmlpage/templates/service-request-mosquito.html
Normal file
|
|
@ -0,0 +1,541 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
<script>
|
||||
// Handle inspection type selection
|
||||
function selectInspectionType(type) {
|
||||
// Remove selected class from both cards
|
||||
document.getElementById('propertyInspection').classList.remove('selected');
|
||||
document.getElementById('neighborhoodInspection').classList.remove('selected');
|
||||
|
||||
// Add selected class to chosen card
|
||||
if (type === 'property') {
|
||||
document.getElementById('propertyInspection').classList.add('selected');
|
||||
document.getElementById('inspectionTypeProperty').checked = true;
|
||||
document.getElementById('schedulingSection').style.display = 'block';
|
||||
} else {
|
||||
document.getElementById('neighborhoodInspection').classList.add('selected');
|
||||
document.getElementById('inspectionTypeNeighborhood').checked = true;
|
||||
document.getElementById('schedulingSection').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Check for source identification
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const sourceCheckboxes = [
|
||||
document.getElementById('sourceStagnantWater'),
|
||||
document.getElementById('sourceContainers'),
|
||||
document.getElementById('sourceGutters')
|
||||
];
|
||||
|
||||
const sourceAlert = document.getElementById('sourceFoundAlert');
|
||||
|
||||
sourceCheckboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('change', function() {
|
||||
// If any source is checked, show the alert
|
||||
if (sourceCheckboxes.some(cb => cb.checked)) {
|
||||
sourceAlert.style.display = 'block';
|
||||
} else {
|
||||
sourceAlert.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
.district-logo {
|
||||
max-height: 80px;
|
||||
width: auto;
|
||||
}
|
||||
.form-section {
|
||||
margin-bottom: 2.5rem;
|
||||
padding-bottom: 2rem;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
.form-section:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.section-heading {
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.section-heading i {
|
||||
margin-right: 10px;
|
||||
font-size: 1.5rem;
|
||||
color: #0d6efd;
|
||||
}
|
||||
.optional-label {
|
||||
font-size: 0.875rem;
|
||||
color: #6c757d;
|
||||
font-weight: normal;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.submit-container {
|
||||
background-color: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.source-card {
|
||||
height: 100%;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
.source-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
.source-icon {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #0d6efd;
|
||||
}
|
||||
.time-of-day-btn {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 15px 0;
|
||||
}
|
||||
.time-of-day-icon {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.time-label {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.severity-item {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
}
|
||||
.severity-scale {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.btn-check:checked + .btn.time-of-day-btn {
|
||||
background-color: #0d6efd;
|
||||
color: white;
|
||||
}
|
||||
.inspection-type-card {
|
||||
cursor: pointer;
|
||||
border: 1px solid #dee2e6;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
height: 100%;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.inspection-type-card.selected {
|
||||
border-color: #0d6efd;
|
||||
background-color: rgba(13, 110, 253, 0.05);
|
||||
}
|
||||
.inspection-type-card:hover {
|
||||
border-color: #0d6efd;
|
||||
}
|
||||
.card-highlight {
|
||||
border-left: 4px solid #0d6efd;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<!-- Header -->
|
||||
<header class="bg-light py-3">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-6">
|
||||
<h1 class="district-name">[District Name]</h1>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<img src="placeholder-logo.png" alt="District Logo" class="district-logo">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="py-5">
|
||||
<div class="container">
|
||||
<!-- Page Title -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h2>Report Mosquito Nuisance</h2>
|
||||
<p class="lead">Help us identify mosquito activity in your area</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info Alert -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info" role="alert">
|
||||
<h5 class="alert-heading"><i class="bi bi-info-circle me-2"></i>About Mosquito Control</h5>
|
||||
<p class="mb-0">While we don't spray for adult mosquitoes based on individual requests, your reports help us identify and eliminate breeding sources. Adult mosquito control is based on trap counts and disease testing. Your detailed information helps us prioritize our work and locate potential breeding sites.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Report Form -->
|
||||
<form id="mosquitoNuisanceForm">
|
||||
<!-- Mosquito Activity Section -->
|
||||
<div class="form-section">
|
||||
<div class="section-heading">
|
||||
<i class="bi bi-bug"></i>
|
||||
<h3>Mosquito Activity Information</h3>
|
||||
<span class="optional-label">optional</span>
|
||||
</div>
|
||||
<p class="mb-4">The time when mosquitoes are active can help us identify the species and likely breeding sources.</p>
|
||||
|
||||
<!-- Time of Day -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<label class="form-label">When do you typically notice mosquitoes? (Select all that apply)</label>
|
||||
<div class="row">
|
||||
<div class="col-6 col-md-3">
|
||||
<input type="checkbox" class="btn-check" id="earlyMorning" autocomplete="off">
|
||||
<label class="btn btn-outline-primary time-of-day-btn" for="earlyMorning">
|
||||
<span class="time-of-day-icon"><i class="bi bi-sunrise"></i></span>
|
||||
<span class="time-label">Early Morning</span>
|
||||
<small class="text-muted">5am-8am</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<input type="checkbox" class="btn-check" id="daytime" autocomplete="off">
|
||||
<label class="btn btn-outline-primary time-of-day-btn" for="daytime">
|
||||
<span class="time-of-day-icon"><i class="bi bi-sun"></i></span>
|
||||
<span class="time-label">Daytime</span>
|
||||
<small class="text-muted">8am-5pm</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<input type="checkbox" class="btn-check" id="evening" autocomplete="off">
|
||||
<label class="btn btn-outline-primary time-of-day-btn" for="evening">
|
||||
<span class="time-of-day-icon"><i class="bi bi-sunset"></i></span>
|
||||
<span class="time-label">Evening</span>
|
||||
<small class="text-muted">5pm-9pm</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<input type="checkbox" class="btn-check" id="night" autocomplete="off">
|
||||
<label class="btn btn-outline-primary time-of-day-btn" for="night">
|
||||
<span class="time-of-day-icon"><i class="bi bi-moon-stars"></i></span>
|
||||
<span class="time-label">Night</span>
|
||||
<small class="text-muted">9pm-5am</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Duration -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="duration" class="form-label">How long have you been experiencing this mosquito problem?</label>
|
||||
<select class="form-select" id="duration">
|
||||
<option value="just-noticed">Just noticed recently</option>
|
||||
<option value="few-days">A few days</option>
|
||||
<option value="1-2-weeks">1-2 weeks</option>
|
||||
<option value="2-4-weeks">2-4 weeks</option>
|
||||
<option value="1-3-months">1-3 months</option>
|
||||
<option value="seasonal">All season (recurring issue)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Severity -->
|
||||
<div class="col-md-6">
|
||||
<label for="severityRange" class="form-label">How would you rate the severity of the mosquito problem?</label>
|
||||
<input type="range" class="form-range" min="1" max="5" id="severityRange" oninput="document.getElementById('severityValue').innerText = this.value">
|
||||
<div class="severity-scale">
|
||||
<div class="severity-item">
|
||||
<div>Minor</div>
|
||||
<small>Occasional mosquito</small>
|
||||
</div>
|
||||
<div class="severity-item">
|
||||
<div>Moderate</div>
|
||||
<small>Regular presence</small>
|
||||
</div>
|
||||
<div class="severity-item">
|
||||
<div>Severe</div>
|
||||
<small>Many mosquitoes</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
Current selection: <span id="severityValue">3</span>/5
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Location -->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label for="activityLocation" class="form-label">Where on your property do you notice the most mosquito activity?</label>
|
||||
<select class="form-select" id="activityLocation">
|
||||
<option value="">Please select</option>
|
||||
<option value="front-yard">Front yard</option>
|
||||
<option value="backyard">Back yard</option>
|
||||
<option value="patio">Patio/deck area</option>
|
||||
<option value="garden">Garden</option>
|
||||
<option value="pool-area">Pool area</option>
|
||||
<option value="throughout">Throughout the property</option>
|
||||
<option value="indoors">Indoors</option>
|
||||
<option value="other">Other area</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Potential Sources Section -->
|
||||
<div class="form-section">
|
||||
<div class="section-heading">
|
||||
<i class="bi bi-search"></i>
|
||||
<h3>Potential Mosquito Sources</h3>
|
||||
<span class="optional-label">optional</span>
|
||||
</div>
|
||||
<p class="mb-3">Have you noticed any of these common mosquito breeding sources in your area?</p>
|
||||
|
||||
<div class="card card-highlight mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Did you know?</h5>
|
||||
<p class="card-text">Mosquitoes can breed in as little as a bottle cap of water! Eliminating standing water is the most effective way to reduce mosquito populations.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<!-- Source 1 -->
|
||||
<div class="col-md-4">
|
||||
<div class="card source-card">
|
||||
<div class="card-body text-center">
|
||||
<div class="source-icon">
|
||||
<i class="bi bi-water"></i>
|
||||
</div>
|
||||
<h5 class="card-title">Stagnant Water</h5>
|
||||
<p class="card-text">Green pools, ponds, fountains, or birdbaths that aren't maintained</p>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="sourceStagnantWater">
|
||||
<label class="form-check-label" for="sourceStagnantWater">
|
||||
I've noticed this
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Source 2 -->
|
||||
<div class="col-md-4">
|
||||
<div class="card source-card">
|
||||
<div class="card-body text-center">
|
||||
<div class="source-icon">
|
||||
<i class="bi bi-droplet"></i>
|
||||
</div>
|
||||
<h5 class="card-title">Containers</h5>
|
||||
<p class="card-text">Buckets, planters, toys, tires, or any items that collect rainwater</p>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="sourceContainers">
|
||||
<label class="form-check-label" for="sourceContainers">
|
||||
I've noticed this
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Source 3 -->
|
||||
<div class="col-md-4">
|
||||
<div class="card source-card">
|
||||
<div class="card-body text-center">
|
||||
<div class="source-icon">
|
||||
<i class="bi bi-house"></i>
|
||||
</div>
|
||||
<h5 class="card-title">Roof & Gutters</h5>
|
||||
<p class="card-text">Clogged gutters, flat roofs, or AC units that collect water</p>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="sourceGutters">
|
||||
<label class="form-check-label" for="sourceGutters">
|
||||
I've noticed this
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning mb-4" id="sourceFoundAlert" style="display: none;">
|
||||
<h5 class="alert-heading"><i class="bi bi-exclamation-triangle me-2"></i>Potential Breeding Source Found!</h5>
|
||||
<p>It looks like you may have identified a mosquito breeding source. If you'd like to report a specific source (like a green pool), please use our <a href="/report-green-pool" class="alert-link">Report a Green Pool</a> form for faster service.</p>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label for="otherSources" class="form-label">Have you noticed any other potential mosquito breeding sources?</label>
|
||||
<textarea class="form-control" id="otherSources" rows="2" placeholder="Describe any other potential breeding sites you've noticed..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inspection Request Section -->
|
||||
<div class="form-section">
|
||||
<div class="section-heading">
|
||||
<i class="bi bi-clipboard-check"></i>
|
||||
<h3>Inspection Request</h3>
|
||||
</div>
|
||||
<p class="mb-4">Would you like our technicians to inspect for potential mosquito sources?</p>
|
||||
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="inspection-type-card" onclick="selectInspectionType('property')" id="propertyInspection">
|
||||
<h5><i class="bi bi-house-door me-2"></i>Property Inspection</h5>
|
||||
<p>Request a technician to inspect your property for mosquito sources. We'll contact you to schedule a convenient time.</p>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="inspectionType" id="inspectionTypeProperty" value="property">
|
||||
<label class="form-check-label" for="inspectionTypeProperty">
|
||||
<strong>Schedule a property inspection</strong>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="inspection-type-card" onclick="selectInspectionType('neighborhood')" id="neighborhoodInspection">
|
||||
<h5><i class="bi bi-map me-2"></i>Neighborhood Inspection</h5>
|
||||
<p>Request a general inspection of your neighborhood. We'll survey the area for potential mosquito breeding sources.</p>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="inspectionType" id="inspectionTypeNeighborhood" value="neighborhood">
|
||||
<label class="form-check-label" for="inspectionTypeNeighborhood">
|
||||
<strong>Request neighborhood inspection</strong>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Property Inspection Scheduling (hidden by default) -->
|
||||
<div id="schedulingSection" style="display: none;">
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Schedule Property Inspection</h5>
|
||||
<p class="card-text">Please indicate your availability for a technician visit.</p>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label for="preferredDateRange" class="form-label">Preferred Date Range</label>
|
||||
<select class="form-select" id="preferredDateRange">
|
||||
<option value="">Select preferred dates</option>
|
||||
<option value="next-week">Next week</option>
|
||||
<option value="in-two-weeks">In two weeks</option>
|
||||
<option value="any-time">Any time</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="preferredTime" class="form-label">Preferred Time of Day</label>
|
||||
<select class="form-select" id="preferredTime">
|
||||
<option value="">Select preferred time</option>
|
||||
<option value="morning">Morning (8am-12pm)</option>
|
||||
<option value="afternoon">Afternoon (12pm-4pm)</option>
|
||||
<option value="any-time">Any time</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="requestCall">
|
||||
<label class="form-check-label" for="requestCall">
|
||||
Please call me to schedule a specific appointment time
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Location & Contact Section -->
|
||||
<div class="form-section">
|
||||
<div class="section-heading">
|
||||
<i class="bi bi-geo-alt"></i>
|
||||
<h3>Location & Contact Information</h3>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-12">
|
||||
<label for="address" class="form-label">Your Address</label>
|
||||
<input type="text" class="form-control" id="address" placeholder="Enter your street address">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="name" class="form-label">Your Name</label>
|
||||
<input type="text" class="form-control" id="name">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="phone" class="form-label">Phone Number</label>
|
||||
<input type="tel" class="form-control" id="phone">
|
||||
</div>
|
||||
<div class="col-md-12 mb-3">
|
||||
<label for="email" class="form-label">Email Address</label>
|
||||
<input type="email" class="form-control" id="email">
|
||||
<div class="form-text">We'll use this to send you a confirmation and follow-up information.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Information Section -->
|
||||
<div class="form-section">
|
||||
<div class="section-heading">
|
||||
<i class="bi bi-card-text"></i>
|
||||
<h3>Additional Information</h3>
|
||||
<span class="optional-label">optional</span>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label for="additionalInfo" class="form-label">Is there anything else you'd like us to know?</label>
|
||||
<textarea class="form-control" id="additionalInfo" rows="4" placeholder="Additional details about the mosquito issue..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit Section -->
|
||||
<div class="submit-container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-8">
|
||||
<p class="mb-0"><strong>Thank you for reporting this mosquito issue.</strong></p>
|
||||
<p class="mb-0 small text-muted">After submission, you'll receive a confirmation with a report ID and further information.</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-md-end mt-3 mt-md-0">
|
||||
<button type="submit" class="btn btn-primary btn-lg">
|
||||
Submit Report
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Back Button -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<a href="/" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Back to Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-dark text-white py-4">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p class="mb-0">© 2023 [District Name] Mosquito Management District</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<p class="mb-0">Contact: (555) 123-4567 | info@mosquitodistrict.gov</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
{{end}}
|
||||
524
htmlpage/templates/service-request-pool.html
Normal file
524
htmlpage/templates/service-request-pool.html
Normal file
|
|
@ -0,0 +1,524 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const photoUpload = document.getElementById('photoUpload');
|
||||
const imagePreview = document.getElementById('imagePreview');
|
||||
const dropArea = document.getElementById('dropArea');
|
||||
|
||||
// Handle file selection
|
||||
photoUpload.addEventListener('change', handleFileSelect);
|
||||
|
||||
// Handle drag and drop
|
||||
dropArea.addEventListener('dragover', function(e) {
|
||||
e.preventDefault();
|
||||
dropArea.style.backgroundColor = '#e9ecef';
|
||||
});
|
||||
|
||||
dropArea.addEventListener('dragleave', function() {
|
||||
dropArea.style.backgroundColor = '#f8f9fa';
|
||||
});
|
||||
|
||||
dropArea.addEventListener('drop', function(e) {
|
||||
e.preventDefault();
|
||||
dropArea.style.backgroundColor = '#f8f9fa';
|
||||
|
||||
if (e.dataTransfer.files.length) {
|
||||
handleFiles(e.dataTransfer.files);
|
||||
}
|
||||
});
|
||||
|
||||
function handleFileSelect(e) {
|
||||
const files = e.target.files;
|
||||
handleFiles(files);
|
||||
}
|
||||
|
||||
function handleFiles(files) {
|
||||
const maxFiles = 5;
|
||||
const currentFiles = imagePreview.querySelectorAll('.preview-item').length;
|
||||
|
||||
for (let i = 0; i < files.length && currentFiles + i < maxFiles; i++) {
|
||||
const file = files[i];
|
||||
|
||||
if (!file.type.match('image.*')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = (function(theFile) {
|
||||
return function(e) {
|
||||
const previewItem = document.createElement('div');
|
||||
previewItem.className = 'preview-item';
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = e.target.result;
|
||||
|
||||
const removeBtn = document.createElement('div');
|
||||
removeBtn.className = 'preview-remove';
|
||||
removeBtn.innerHTML = 'x';
|
||||
removeBtn.addEventListener('click', function() {
|
||||
imagePreview.removeChild(previewItem);
|
||||
});
|
||||
|
||||
previewItem.appendChild(img);
|
||||
previewItem.appendChild(removeBtn);
|
||||
imagePreview.appendChild(previewItem);
|
||||
};
|
||||
})(file);
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
photoUpload.value = '';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
.district-logo {
|
||||
max-height: 80px;
|
||||
width: auto;
|
||||
}
|
||||
.map-container {
|
||||
height: 300px;
|
||||
background-color: #e9ecef;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #dee2e6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.file-upload-container {
|
||||
border: 2px dashed #dee2e6;
|
||||
border-radius: 5px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
background-color: #f8f9fa;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
.file-upload-container:hover {
|
||||
background-color: #e9ecef;
|
||||
cursor: pointer;
|
||||
}
|
||||
.image-preview {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
.preview-item {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.preview-item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.preview-remove {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.form-section {
|
||||
margin-bottom: 2.5rem;
|
||||
padding-bottom: 2rem;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
.form-section:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.section-heading {
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.section-heading i {
|
||||
margin-right: 10px;
|
||||
font-size: 1.5rem;
|
||||
color: #0d6efd;
|
||||
}
|
||||
.optional-label {
|
||||
font-size: 0.875rem;
|
||||
color: #6c757d;
|
||||
font-weight: normal;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.submit-container {
|
||||
background-color: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 5px;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<!-- Header -->
|
||||
<header class="bg-light py-3">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-6">
|
||||
<h1 class="district-name">{{ .DistrictName }}</h1>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<img src="placeholder-logo.png" alt="District Logo" class="district-logo">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="py-5">
|
||||
<div class="container">
|
||||
<!-- Page Title -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h2>Report a Green Pool or Mosquito Source</h2>
|
||||
<p class="lead">Help us locate and treat potential mosquito breeding sources in your area</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info Alert -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info" role="alert">
|
||||
<h5 class="alert-heading"><i class="bi bi-info-circle me-2"></i>All fields are optional</h5>
|
||||
<p class="mb-0">We appreciate any information you can provide. The more details you share, the better we can address the issue. Photos and location information are especially helpful.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Report Form -->
|
||||
<form id="greenPoolForm">
|
||||
<!-- Photo Upload Section -->
|
||||
<div class="form-section">
|
||||
<div class="section-heading">
|
||||
<i class="bi bi-camera"></i>
|
||||
<h3>Photos</h3>
|
||||
<span class="optional-label">optional</span>
|
||||
</div>
|
||||
<p class="mb-3">Photos help us identify the severity of the issue and may contain location data that can help us find the source.</p>
|
||||
|
||||
<div class="file-upload-container" id="dropArea">
|
||||
<input type="file" id="photoUpload" multiple accept="image/*" class="d-none">
|
||||
<div class="mb-2">
|
||||
<i class="bi bi-cloud-arrow-up fs-1 text-primary"></i>
|
||||
</div>
|
||||
<p class="mb-1"><strong>Drag and drop photos here</strong></p>
|
||||
<p class="mb-1">- or -</p>
|
||||
<button type="button" class="btn btn-primary mt-2" onclick="document.getElementById('photoUpload').click()">
|
||||
Select Photos
|
||||
</button>
|
||||
<p class="small text-muted mt-2 mb-0">You can upload multiple photos (maximum 5)</p>
|
||||
</div>
|
||||
|
||||
<!-- Image Preview Area -->
|
||||
<div class="image-preview" id="imagePreview">
|
||||
<!-- Preview items will be added here dynamically -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Location Section -->
|
||||
<div class="form-section">
|
||||
<div class="section-heading">
|
||||
<i class="bi bi-geo-alt"></i>
|
||||
<h3>Location</h3>
|
||||
<span class="optional-label">optional</span>
|
||||
</div>
|
||||
<p class="mb-3">Please provide the location of the potential mosquito breeding source. We may be able to extract this information from your photos if they contain location data.</p>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12">
|
||||
<label for="address" class="form-label">Address or Description of Location</label>
|
||||
<input type="text" class="form-control" id="address" placeholder="123 Main St, City, State or nearby landmark description">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="small text-muted mb-2">You can also click on the map to mark the location precisely</p>
|
||||
<div class="map-container">
|
||||
<div class="text-center">
|
||||
<i class="bi bi-map fs-1 text-secondary"></i>
|
||||
<p class="mb-0">Interactive Map</p>
|
||||
<p class="text-muted small">Click to set the location of the mosquito source</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Source Details Section -->
|
||||
<div class="form-section">
|
||||
<div class="section-heading">
|
||||
<i class="bi bi-water"></i>
|
||||
<h3>Source Details</h3>
|
||||
<span class="optional-label">optional</span>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="duration" class="form-label">How long has this source been present?</label>
|
||||
<select class="form-select" id="duration">
|
||||
<option value="">I don't know</option>
|
||||
<option value="less-than-week">Less than a week</option>
|
||||
<option value="1-2-weeks">1-2 weeks</option>
|
||||
<option value="2-4-weeks">2-4 weeks</option>
|
||||
<option value="1-3-months">1-3 months</option>
|
||||
<option value="more-than-3-months">More than 3 months</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label d-block">Have you observed any of the following? <a href="#" data-bs-toggle="modal" data-bs-target="#larvaeInfoModal"><i class="bi bi-question-circle small ms-1"></i></a></label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="larvae">
|
||||
<label class="form-check-label" for="larvae">
|
||||
Larvae (wigglers) in water
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="pupae">
|
||||
<label class="form-check-label" for="pupae">
|
||||
Pupae (tumblers) in water
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="adultMosquitoes">
|
||||
<label class="form-check-label" for="adultMosquitoes">
|
||||
Adult mosquitoes near the source
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Access Information Section -->
|
||||
<div class="form-section">
|
||||
<div class="section-heading">
|
||||
<i class="bi bi-unlock"></i>
|
||||
<h3>Access Information</h3>
|
||||
<span class="optional-label">optional</span>
|
||||
</div>
|
||||
<p class="mb-3">Please provide any details about how to access the mosquito source. This helps our technicians when they visit the site.</p>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12">
|
||||
<label for="accessInfo" class="form-label">How can the source be accessed?</label>
|
||||
<textarea class="form-control" id="accessInfo" rows="3" placeholder="Example: The pool is in the backyard, which can be accessed through a side gate on the right side of the house."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12">
|
||||
<label class="form-label d-block">Access obstacles (check all that apply):</label>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="gate">
|
||||
<label class="form-check-label" for="gate">Gate</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="fence">
|
||||
<label class="form-check-label" for="fence">Fence</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="lockedEntrance">
|
||||
<label class="form-check-label" for="lockedEntrance">Locked entrance</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="dogs">
|
||||
<label class="form-check-label" for="dogs">Dogs/pets</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="otherObstacle">
|
||||
<label class="form-check-label" for="otherObstacle">Other obstacle</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contact Information Sections -->
|
||||
<div class="form-section">
|
||||
<div class="section-heading">
|
||||
<i class="bi bi-person-lines-fill"></i>
|
||||
<h3>Contact Information</h3>
|
||||
<span class="optional-label">optional</span>
|
||||
</div>
|
||||
|
||||
<!-- Property Owner Information -->
|
||||
<h5 class="mb-3">Property Owner Information (if known)</h5>
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="ownerName" class="form-label">Owner Name</label>
|
||||
<input type="text" class="form-control" id="ownerName">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="ownerPhone" class="form-label">Owner Phone</label>
|
||||
<input type="tel" class="form-control" id="ownerPhone">
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label for="ownerEmail" class="form-label">Owner Email</label>
|
||||
<input type="email" class="form-control" id="ownerEmail">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Your Contact Information -->
|
||||
<h5 class="mb-3">Your Contact Information (for updates)</h5>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="reporterName" class="form-label">Your Name</label>
|
||||
<input type="text" class="form-control" id="reporterName">
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="reporterPhone" class="form-label">Your Phone</label>
|
||||
<input type="tel" class="form-control" id="reporterPhone">
|
||||
</div>
|
||||
<div class="col-md-12 mb-3">
|
||||
<label for="reporterEmail" class="form-label">Your Email</label>
|
||||
<input type="email" class="form-control" id="reporterEmail">
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="receiveUpdates" checked>
|
||||
<label class="form-check-label" for="receiveUpdates">
|
||||
I would like to receive updates on this report
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Information Section -->
|
||||
<div class="form-section">
|
||||
<div class="section-heading">
|
||||
<i class="bi bi-card-text"></i>
|
||||
<h3>Additional Information</h3>
|
||||
<span class="optional-label">optional</span>
|
||||
</div>
|
||||
<p class="mb-3">Please provide any other information that might help us address this mosquito source.</p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label for="additionalInfo" class="form-label">Additional Details</label>
|
||||
<textarea class="form-control" id="additionalInfo" rows="4" placeholder="Example: The house appears to be vacant. There is algae growth in the pool. I've noticed increased mosquito activity in the evenings."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit Section -->
|
||||
<div class="submit-container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-8">
|
||||
<p class="mb-0"><strong>Thank you for helping us keep our community safe from mosquito-borne illnesses.</strong></p>
|
||||
<p class="mb-0 small text-muted">After submission, you will receive a confirmation with a report ID for tracking purposes.</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-md-end mt-3 mt-md-0">
|
||||
<button type="submit" class="btn btn-primary btn-lg">
|
||||
Submit Report
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Back Button -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<a href="/" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left"></i> Back to Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Larvae Info Modal -->
|
||||
<div class="modal fade" id="larvaeInfoModal" tabindex="-1" aria-labelledby="larvaeInfoModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="larvaeInfoModalLabel">How to Identify Mosquito Larvae and Pupae</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<h6>Mosquito Larvae (Wigglers)</h6>
|
||||
<p>Mosquito larvae, often called "wigglers," are:</p>
|
||||
<ul>
|
||||
<li>Small, worm-like aquatic organisms</li>
|
||||
<li>Usually 1/4 to 1/2 inch long</li>
|
||||
<li>Move with a wiggling motion in water</li>
|
||||
<li>Hang upside-down at the water surface to breathe</li>
|
||||
<li>Visible to the naked eye in standing water</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6>Mosquito Pupae (Tumblers)</h6>
|
||||
<p>Mosquito pupae, often called "tumblers," are:</p>
|
||||
<ul>
|
||||
<li>Comma-shaped organisms</li>
|
||||
<li>Typically darker than larvae</li>
|
||||
<li>Move with a tumbling motion when disturbed</li>
|
||||
<li>Rest at the water surface</li>
|
||||
<li>The stage just before adult mosquitoes emerge</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p>When looking for mosquito larvae and pupae, check standing water sources like:</p>
|
||||
<ul>
|
||||
<li>Swimming pools</li>
|
||||
<li>Bird baths</li>
|
||||
<li>Buckets or containers</li>
|
||||
<li>Drainage ditches</li>
|
||||
<li>Plant saucers</li>
|
||||
<li>Rain gutters</li>
|
||||
</ul>
|
||||
<p>If you see small creatures moving in standing water, there's a good chance they're mosquito larvae or pupae.</p>
|
||||
<div class="text-center">
|
||||
<a href="#" class="btn btn-outline-primary">View Detailed Identification Guide</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-dark text-white py-4">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p class="mb-0">© 2023 {{ .DistrictName }} Mosquito Management District</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<p class="mb-0">Contact: (555) 123-4567 | info@mosquitodistrict.gov</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
{{end}}
|
||||
115
htmlpage/templates/service-request-quick-confirmation.html
Normal file
115
htmlpage/templates/service-request-quick-confirmation.html
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.district-logo {
|
||||
max-height: 60px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: #28a745;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 2rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.confirmation-card {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.home-button {
|
||||
padding: 12px 30px;
|
||||
border-radius: 30px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.header-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<!-- Header -->
|
||||
<header class="bg-light py-2 mb-4">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-9">
|
||||
<h1 class="district-name header-title mb-0">[District Name]</h1>
|
||||
</div>
|
||||
<div class="col-3 text-end">
|
||||
<img src="placeholder-logo.png" alt="District Logo" class="district-logo">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mb-5 py-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
<div class="card shadow confirmation-card">
|
||||
<div class="card-body p-4 p-md-5 text-center">
|
||||
<!-- Success Icon -->
|
||||
<div class="success-icon">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" fill="currentColor" class="bi bi-check-lg" viewBox="0 0 16 16">
|
||||
<path d="M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425a.247.247 0 0 1 .02-.022Z"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<h2 class="mb-4">Report Received!</h2>
|
||||
|
||||
<p class="lead mb-4">Thank you for contributing to the health and well-being of our community.</p>
|
||||
|
||||
<div class="mb-4 p-3 bg-light rounded-3">
|
||||
<p class="mb-0">Your mosquito report has been submitted successfully and will be reviewed by our team. Your effort helps us identify problem areas and better manage mosquito populations throughout our district.</p>
|
||||
</div>
|
||||
|
||||
<p class="text-muted mb-4">Report ID: <span class="fw-bold">#MM<script>document.write(Math.floor(Math.random() * 10000) + 10000)</script></span></p>
|
||||
|
||||
<a href="/" class="btn btn-primary home-button">
|
||||
Return Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Info Card -->
|
||||
<div class="card mt-4 border-0 bg-light">
|
||||
<div class="card-body">
|
||||
<h5>What happens next?</h5>
|
||||
<p class="mb-0">Our team reviews all reports daily. Depending on the nature of your report, we may deploy field technicians to assess the area or add it to our scheduled mosquito control activities. For urgent matters, we prioritize responses based on public health risk factors.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-dark text-white py-3 mt-auto">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p class="mb-0 small">© 2023 [District Name] Mosquito Management District</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<p class="mb-0 small">Contact: (555) 123-4567</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
{{end}}
|
||||
154
htmlpage/templates/service-request-quick.html
Normal file
154
htmlpage/templates/service-request-quick.html
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.district-logo {
|
||||
max-height: 60px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.photo-upload-area {
|
||||
border: 2px dashed #ccc;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.photo-preview {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.photo-preview img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
padding: 15px 0;
|
||||
font-size: 1.25rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.location-info {
|
||||
background-color: #e9f5ff;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.header-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<!-- Header -->
|
||||
<header class="bg-light py-2 mb-4">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-9">
|
||||
<h1 class="district-name header-title mb-0">[District Name]</h1>
|
||||
</div>
|
||||
<div class="col-3 text-end">
|
||||
<img src="placeholder-logo.png" alt="District Logo" class="district-logo">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mb-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<h2 class="card-title text-center mb-4">Quick Mosquito Report</h2>
|
||||
|
||||
<!-- Form -->
|
||||
<form action="/service-request-quick-confirmation" method="get">
|
||||
<!-- Location Automatic Collection Note -->
|
||||
<div class="location-info d-flex align-items-center mb-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-geo-alt me-2" viewBox="0 0 16 16">
|
||||
<path d="M12.166 8.94c-.524 1.062-1.234 2.12-1.96 3.07A31.493 31.493 0 0 1 8 14.58a31.481 31.481 0 0 1-2.206-2.57c-.726-.95-1.436-2.008-1.96-3.07C3.304 7.867 3 6.862 3 6a5 5 0 0 1 10 0c0 .862-.305 1.867-.834 2.94zM8 16s6-5.686 6-10A6 6 0 0 0 2 6c0 4.314 6 10 6 10z"/>
|
||||
<path d="M8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 1a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
||||
</svg>
|
||||
<span>Your location and current time will be automatically collected with your report.</span>
|
||||
</div>
|
||||
|
||||
<!-- Photo Upload -->
|
||||
<div class="mb-4">
|
||||
<label for="photos" class="form-label fw-bold">Photos (Optional)</label>
|
||||
<div class="photo-upload-area">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" class="bi bi-camera mb-2" viewBox="0 0 16 16">
|
||||
<path d="M15 12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h1.172a3 3 0 0 0 2.12-.879l.83-.828A1 1 0 0 1 6.827 3h2.344a1 1 0 0 1 .707.293l.828.828A3 3 0 0 0 12.828 5H14a1 1 0 0 1 1 1v6zM2 4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-1.172a2 2 0 0 1-1.414-.586l-.828-.828A2 2 0 0 0 9.172 2H6.828a2 2 0 0 0-1.414.586l-.828.828A2 2 0 0 1 3.172 4H2z"/>
|
||||
<path d="M8 11a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5zm0 1a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7zM3 6.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0z"/>
|
||||
</svg>
|
||||
<div>
|
||||
<input type="file" id="photos" class="d-none" accept="image/*" multiple>
|
||||
<button type="button" class="btn btn-outline-primary mb-2" onclick="document.getElementById('photos').click()">Add Photos</button>
|
||||
</div>
|
||||
<small class="d-block text-muted">Take pictures of the mosquito problem area</small>
|
||||
|
||||
<!-- Photo Preview Area (would be populated by JavaScript) -->
|
||||
<div class="photo-preview mt-3">
|
||||
<!-- Example preview images (these would be dynamically added) -->
|
||||
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='80' viewBox='0 0 80 80' fill='%23dddddd'%3E%3Crect width='80' height='80' fill='%23eeeeee'/%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' font-family='sans-serif' font-size='12' fill='%23999999'%3EPreview%3C/text%3E%3C/svg%3E" alt="Preview">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Comments -->
|
||||
<div class="mb-4">
|
||||
<label for="comments" class="form-label fw-bold">Comments</label>
|
||||
<textarea class="form-control" id="comments" rows="4" placeholder="Describe the mosquito issue (e.g., standing water, high mosquito activity, time of day they're most active)"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<button type="submit" class="btn btn-success w-100 submit-btn mt-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-send-fill me-2" viewBox="0 0 16 16">
|
||||
<path d="M15.964.686a.5.5 0 0 0-.65-.65L.767 5.855H.766l-.452.18a.5.5 0 0 0-.082.887l.41.26.001.002 4.995 3.178 3.178 4.995.002.002.26.41a.5.5 0 0 0 .886-.083l6-15Zm-1.833 1.89L6.637 10.07l-.215-.338a.5.5 0 0 0-.154-.154l-.338-.215 7.494-7.494 1.178-.471-.47 1.178Z"/>
|
||||
</svg>
|
||||
Submit Report
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back Link -->
|
||||
<div class="text-center mt-3">
|
||||
<a href="javascript:history.back()" class="text-decoration-none">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left me-1" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z"/>
|
||||
</svg>
|
||||
Back to home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-dark text-white py-3 mt-auto">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p class="mb-0 small">© 2023 [District Name] Mosquito Management District</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<p class="mb-0 small">Contact: (555) 123-4567</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
{{end}}
|
||||
162
htmlpage/templates/service-request-updates.html
Normal file
162
htmlpage/templates/service-request-updates.html
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.option-card {
|
||||
transition: transform 0.3s;
|
||||
height: 100%;
|
||||
}
|
||||
.option-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
|
||||
}
|
||||
.district-logo {
|
||||
max-height: 80px;
|
||||
width: auto;
|
||||
}
|
||||
.divider {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.divider-line {
|
||||
border-left: 1px solid #dee2e6;
|
||||
height: 80%;
|
||||
}
|
||||
@media (max-width: 767.98px) {
|
||||
.divider-line {
|
||||
border-left: none;
|
||||
border-top: 1px solid #dee2e6;
|
||||
width: 80%;
|
||||
height: auto;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<!-- Header -->
|
||||
<header class="bg-light py-3">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-6">
|
||||
<h1 class="district-name">{{ .DistrictName }}</h1>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<img src="placeholder-logo.png" alt="District Logo" class="district-logo">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main>
|
||||
<!-- Page Title -->
|
||||
<section class="py-4 bg-primary text-white">
|
||||
<div class="container">
|
||||
<h2 class="text-center mb-0">Check Status or Follow-up</h2>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Lookup Options -->
|
||||
<section class="py-5">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 mb-4">
|
||||
<p class="lead text-center">
|
||||
Choose one of the following options to check on mosquito activity or follow up on a previous report.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<!-- Report ID Lookup -->
|
||||
<div class="col-md-5">
|
||||
<div class="card option-card h-100">
|
||||
<div class="card-body p-4">
|
||||
<div class="text-center mb-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" class="bi bi-file-earmark-text" viewBox="0 0 16 16">
|
||||
<path d="M5.5 7a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zM5 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z"/>
|
||||
<path d="M9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.5L9.5 0zm0 1v2A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h4 class="card-title text-center mb-4">Look up by Report ID</h4>
|
||||
<p class="card-text">
|
||||
If you have a report ID from a previous request, enter it below to view the details and current status.
|
||||
</p>
|
||||
|
||||
<form>
|
||||
<div class="mb-3">
|
||||
<label for="reportId" class="form-label">Report ID</label>
|
||||
<input type="text" class="form-control" id="reportId" name="reportId" placeholder="Enter your report ID" required>
|
||||
<div class="form-text">Example: MMD-2023-12345</div>
|
||||
</div>
|
||||
<div class="d-grid gap-2">
|
||||
<a href="/service-request/abc-123" type="submit" class="btn btn-primary">View Report Details</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Divider for visual separation -->
|
||||
<div class="col-md-2 divider">
|
||||
<div class="divider-line"></div>
|
||||
</div>
|
||||
|
||||
<!-- Location Lookup -->
|
||||
<div class="col-md-5">
|
||||
<div class="card option-card h-100">
|
||||
<div class="card-body p-4">
|
||||
<div class="text-center mb-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" class="bi bi-geo-alt" viewBox="0 0 16 16">
|
||||
<path d="M12.166 8.94c-.524 1.062-1.234 2.12-1.96 3.07A31.493 31.493 0 0 1 8 14.58a31.481 31.481 0 0 1-2.206-2.57c-.726-.95-1.436-2.008-1.96-3.07C3.304 7.867 3 6.862 3 6a5 5 0 0 1 10 0c0 .862-.305 1.867-.834 2.94zM8 16s6-5.686 6-10A6 6 0 0 0 2 6c0 4.314 6 10 6 10z"/>
|
||||
<path d="M8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 1a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h4 class="card-title text-center mb-4">Look up by Location</h4>
|
||||
<p class="card-text">
|
||||
Don't have a report ID? You can check mosquito activity and reports in your area by providing your location information.
|
||||
</p>
|
||||
<p class="card-text mb-4">
|
||||
This option will guide you through selecting your location to find relevant information about mosquito activity near you.
|
||||
</p>
|
||||
<div class="d-grid gap-2 mt-auto">
|
||||
<a href="/service-request-location" class="btn btn-primary">Search by Location</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Back button -->
|
||||
<div class="row mt-5">
|
||||
<div class="col-12 text-center">
|
||||
<a href="/" class="btn btn-outline-secondary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left me-2" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z"/>
|
||||
</svg>
|
||||
Back to Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-dark text-white py-4">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p class="mb-0">© 2023 {{ .DistrictName }} Mosquito Management District</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<p class="mb-0">Contact: (555) 123-4567 | info@mosquitodistrict.gov</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
{{end}}
|
||||
163
htmlpage/templates/service-request.html
Normal file
163
htmlpage/templates/service-request.html
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.service-card {
|
||||
transition: transform 0.3s;
|
||||
height: 100%;
|
||||
}
|
||||
.service-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
|
||||
}
|
||||
.district-logo {
|
||||
max-height: 80px;
|
||||
width: auto;
|
||||
}
|
||||
.quick-report-mobile {
|
||||
background-color: #ff9800;
|
||||
}
|
||||
.quick-report-desktop {
|
||||
background-color: #ffefd5;
|
||||
border-left: 4px solid #ff9800;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<!-- Header -->
|
||||
<header class="bg-light py-3">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-6">
|
||||
<h1 class="district-name">{{ .DistrictName }}</h1>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<img src="placeholder-logo.png" alt="District Logo" class="district-logo">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main>
|
||||
<!-- Introduction Section -->
|
||||
<section class="py-5 bg-primary text-white">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
<h2 class="text-center mb-4">Welcome to Our Mosquito Management Services</h2>
|
||||
<p class="lead text-center">
|
||||
We are dedicated to protecting public health and improving quality of life by reducing
|
||||
mosquito populations and the diseases they can carry. Our district provides comprehensive
|
||||
mosquito surveillance, control, and education services to our community.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Quick Report for Mobile - Only visible on small screens -->
|
||||
<section class="py-3 quick-report-mobile d-md-none">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 text-center">
|
||||
<h4 class="mb-2">On the go?</h4>
|
||||
<a href="/mock/service-request-quick" class="btn btn-dark btn-lg">Make a Quick Report</a>
|
||||
<p class="mb-0 mt-2"><small>Report mosquito issues in under 60 seconds</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Services Section -->
|
||||
<section class="py-5">
|
||||
<div class="container">
|
||||
<h3 class="text-center mb-4">How Can We Help You Today?</h3>
|
||||
<div class="row g-4">
|
||||
<!-- Service Option 1 -->
|
||||
<div class="col-md-4">
|
||||
<div class="card service-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" class="bi bi-search" viewBox="0 0 16 16">
|
||||
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h4 class="card-title">Follow-up or Check Status</h4>
|
||||
<p class="card-text">Check on a previous request or view current mosquito activity in your area.</p>
|
||||
<a href="/mock/service-request-updates" class="btn btn-primary mt-3">Get Updates</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Service Option 2 -->
|
||||
<div class="col-md-4">
|
||||
<div class="card service-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" class="bi bi-water" viewBox="0 0 16 16">
|
||||
<path d="M.036 3.314a.5.5 0 0 1 .65-.278l1.757.703a1.5 1.5 0 0 0 1.114 0l1.014-.406a2.5 2.5 0 0 1 1.857 0l1.015.406a1.5 1.5 0 0 0 1.114 0l1.014-.406a2.5 2.5 0 0 1 1.857 0l1.015.406a1.5 1.5 0 0 0 1.114 0l1.757-.703a.5.5 0 1 1 .372.928l-1.758.703a2.5 2.5 0 0 1-1.857 0l-1.014-.406a1.5 1.5 0 0 0-1.114 0l-1.015.406a2.5 2.5 0 0 1-1.857 0l-1.014-.406a1.5 1.5 0 0 0-1.114 0l-1.015.406a2.5 2.5 0 0 1-1.857 0L.664 3.964a.5.5 0 0 1-.278-.65zm0 3a.5.5 0 0 1 .65-.278l1.757.703a1.5 1.5 0 0 0 1.114 0l1.014-.406a2.5 2.5 0 0 1 1.857 0l1.015.406a1.5 1.5 0 0 0 1.114 0l1.014-.406a2.5 2.5 0 0 1 1.857 0l1.015.406a1.5 1.5 0 0 0 1.114 0l1.757-.703a.5.5 0 1 1 .372.928l-1.758.703a2.5 2.5 0 0 1-1.857 0l-1.014-.406a1.5 1.5 0 0 0-1.114 0l-1.015.406a2.5 2.5 0 0 1-1.857 0l-1.014-.406a1.5 1.5 0 0 0-1.114 0l-1.015.406a2.5 2.5 0 0 1-1.857 0L.664 6.964a.5.5 0 0 1-.278-.65zm0 3a.5.5 0 0 1 .65-.278l1.757.703a1.5 1.5 0 0 0 1.114 0l1.014-.406a2.5 2.5 0 0 1 1.857 0l1.015.406a1.5 1.5 0 0 0 1.114 0l1.014-.406a2.5 2.5 0 0 1 1.857 0l1.015.406a1.5 1.5 0 0 0 1.114 0l1.757-.703a.5.5 0 1 1 .372.928l-1.758.703a2.5 2.5 0 0 1-1.857 0l-1.014-.406a1.5 1.5 0 0 0-1.114 0l-1.015.406a2.5 2.5 0 0 1-1.857 0l-1.014-.406a1.5 1.5 0 0 0-1.114 0l-1.015.406a2.5 2.5 0 0 1-1.857 0L.664 9.964a.5.5 0 0 1-.278-.65z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h4 class="card-title">Report a Green Pool</h4>
|
||||
<p class="card-text">Report stagnant water sources like abandoned pools that may breed mosquitoes.</p>
|
||||
<a href="/mock/service-request-pool" class="btn btn-primary mt-3">Report Source</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Service Option 3 -->
|
||||
<div class="col-md-4">
|
||||
<div class="card service-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" class="bi bi-bug" viewBox="0 0 16 16">
|
||||
<path d="M4.355.522a.5.5 0 0 1 .623.333l.291.956A4.979 4.979 0 0 1 8 1c1.007 0 1.946.298 2.731.811l.29-.956a.5.5 0 1 1 .957.29l-.41 1.352A4.985 4.985 0 0 1 13 6h.5a.5.5 0 0 0 .5-.5V5a.5.5 0 0 1 1 0v.5A1.5 1.5 0 0 1 13.5 7H13v1h1.5a.5.5 0 0 1 0 1H13v1h.5a1.5 1.5 0 0 1 1.5 1.5v.5a.5.5 0 1 1-1 0v-.5a.5.5 0 0 0-.5-.5H13a5 5 0 0 1-10 0h-.5a.5.5 0 0 0-.5.5v.5a.5.5 0 1 1-1 0v-.5A1.5 1.5 0 0 1 2.5 10H3V9H1.5a.5.5 0 0 1 0-1H3V7h-.5A1.5 1.5 0 0 1 1 5.5V5a.5.5 0 0 1 1 0v.5a.5.5 0 0 0 .5.5H3c0-1.364.547-2.601 1.432-3.503l-.41-1.352a.5.5 0 0 1 .333-.623zM4 7v4a4 4 0 0 0 3.5 3.97V7H4zm4.5 0v7.97A4 4 0 0 0 12 11V7H8.5zM12 6a3.989 3.989 0 0 0-1.334-2.982A3.983 3.983 0 0 0 8 2a3.983 3.983 0 0 0-2.667 1.018A3.989 3.989 0 0 0 4 6h8z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h4 class="card-title">Report Mosquito Nuisance</h4>
|
||||
<p class="card-text">Report areas with high adult mosquito activity causing discomfort or concern.</p>
|
||||
<a href="/mock/service-request-mosquito" class="btn btn-primary mt-3">Report Problem</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Report for Desktop - Only visible on medium screens and up -->
|
||||
<div class="row mt-4 d-none d-md-block">
|
||||
<div class="col-12">
|
||||
<div class="card quick-report-desktop">
|
||||
<div class="card-body py-3">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-8">
|
||||
<h5 class="mb-1">Need to make a quick report?</h5>
|
||||
<p class="mb-0">Use our streamlined form to report mosquito issues in under 60 seconds</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-md-end mt-3 mt-md-0">
|
||||
<a href="/mock/service-request-quick" class="btn btn-warning">Quick Report</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-dark text-white py-4">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p class="mb-0">© 2023 {{.DistrictName}}</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<p class="mb-0">Contact: (555) 123-4567 | info@mosquitodistrict.gov</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
{{end}}
|
||||
257
htmlpage/templates/setting-integration.html
Normal file
257
htmlpage/templates/setting-integration.html
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.integration-card {
|
||||
border-left: 5px solid #0d6efd;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.integration-card:hover {
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.integration-card.fieldseeker {
|
||||
border-left-color: #0d6efd;
|
||||
}
|
||||
.integration-card.vectorsurv {
|
||||
border-left-color: #198754;
|
||||
}
|
||||
.integration-card.veemac {
|
||||
border-left-color: #dc3545;
|
||||
}
|
||||
.status-active {
|
||||
color: #198754;
|
||||
}
|
||||
.status-inactive {
|
||||
color: #dc3545;
|
||||
}
|
||||
.token-display {
|
||||
font-family: monospace;
|
||||
background-color: #f8f9fa;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container py-4">
|
||||
<!-- Header -->
|
||||
<div class="mb-4">
|
||||
<h1>Integrations</h1>
|
||||
<div class="alert alert-warning">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
<strong>Important:</strong> This page allows you to configure integration with third-party services. The credentials and tokens stored here provide access to external systems and should be protected. Only authorized personnel should modify these settings.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FieldSeeker GIS Integration -->
|
||||
<div class="card mb-4 integration-card fieldseeker">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h2 class="h5 mb-0">Frontier Precision's FieldSeeker GIS</h2>
|
||||
</div>
|
||||
<img src="https://via.placeholder.com/100x40?text=FieldSeeker" alt="FieldSeeker Logo" height="40">
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive mb-3">
|
||||
<table class="table table-borderless">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="30%"><strong>OAuth Token Status</strong></td>
|
||||
<td>
|
||||
<span class="status-active">
|
||||
<i class="bi bi-check-circle-fill me-1"></i> Active
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Token Expiration</strong></td>
|
||||
<td>26 days remaining (Expires on Dec 31, 2025)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Integration Method</strong></td>
|
||||
<td>Web Hooks</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Permission Level</strong></td>
|
||||
<td>Read & Write</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary">
|
||||
<i class="bi bi-arrow-repeat me-2"></i>Refresh OAuth Token
|
||||
</button>
|
||||
<button class="btn btn-outline-danger">
|
||||
<i class="bi bi-trash me-2"></i>Delete Token
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VectorSurv Integration -->
|
||||
<div class="card mb-4 integration-card vectorsurv">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h2 class="h5 mb-0">VectorSurv</h2>
|
||||
</div>
|
||||
<img src="https://via.placeholder.com/100x40?text=VectorSurv" alt="VectorSurv Logo" height="40">
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive mb-3">
|
||||
<table class="table table-borderless">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="30%"><strong>API Token</strong></td>
|
||||
<td>
|
||||
<span class="token-display">vs_9f72b5e3******************************c11d</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Last Synchronization</strong></td>
|
||||
<td>December 5, 2025 at 08:34 AM (2 days ago)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Synchronization Status</strong></td>
|
||||
<td>
|
||||
<span class="status-active">
|
||||
<i class="bi bi-check-circle-fill me-1"></i> Active (Scheduled daily at 2:00 AM)
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#vectorsurvModal">
|
||||
<i class="bi bi-pencil-square me-2"></i>Edit Token
|
||||
</button>
|
||||
<button class="btn btn-outline-danger">
|
||||
<i class="bi bi-trash me-2"></i>Remove Integration
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VeeMac Integration -->
|
||||
<div class="card mb-4 integration-card veemac">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h2 class="h5 mb-0">VeeMac</h2>
|
||||
</div>
|
||||
<img src="https://via.placeholder.com/100x40?text=VeeMac" alt="VeeMac Logo" height="40">
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive mb-3">
|
||||
<table class="table table-borderless">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="30%"><strong>Username</strong></td>
|
||||
<td>mosquito_district21</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Password</strong></td>
|
||||
<td>••••••••••••</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Last Synchronization</strong></td>
|
||||
<td>December 6, 2025 at 11:15 PM (Yesterday)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Synchronization Status</strong></td>
|
||||
<td>
|
||||
<span class="status-inactive">
|
||||
<i class="bi bi-x-circle-fill me-1"></i> Inactive (Manual sync only)
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#veemacModal">
|
||||
<i class="bi bi-pencil-square me-2"></i>Edit Credentials
|
||||
</button>
|
||||
<button class="btn btn-outline-danger">
|
||||
<i class="bi bi-trash me-2"></i>Remove Integration
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VectorSurv Edit Token Modal -->
|
||||
<div class="modal fade" id="vectorsurvModal" tabindex="-1" aria-labelledby="vectorsurvModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="vectorsurvModalLabel">Edit VectorSurv API Token</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="mb-3">
|
||||
<label for="vectorsurvToken" class="form-label">API Token</label>
|
||||
<input type="text" class="form-control" id="vectorsurvToken" value="vs_9f72b5e3c8a1d492f6b7e54321098c11d">
|
||||
<div class="form-text">You can find this token in your VectorSurv account settings.</div>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="vectorsurvSyncCheck" checked>
|
||||
<label class="form-check-label" for="vectorsurvSyncCheck">Enable automatic synchronization</label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="vectorsurvSyncTime" class="form-label">Sync Time</label>
|
||||
<input type="time" class="form-control" id="vectorsurvSyncTime" value="02:00">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-success">Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VeeMac Edit Credentials Modal -->
|
||||
<div class="modal fade" id="veemacModal" tabindex="-1" aria-labelledby="veemacModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="veemacModalLabel">Edit VeeMac Credentials</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="mb-3">
|
||||
<label for="veemacUsername" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="veemacUsername" value="mosquito_district21">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="veemacPassword" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="veemacPassword" value="password123">
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="veemacSyncCheck">
|
||||
<label class="form-check-label" for="veemacSyncCheck">Enable automatic synchronization</label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="veemacSyncFrequency" class="form-label">Sync Frequency</label>
|
||||
<select class="form-select" id="veemacSyncFrequency" disabled>
|
||||
<option value="daily">Daily</option>
|
||||
<option value="weekly">Weekly</option>
|
||||
<option value="hourly">Hourly</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-danger">Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
192
htmlpage/templates/setting-mock.html
Normal file
192
htmlpage/templates/setting-mock.html
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.settings-card {
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
height: 100%;
|
||||
}
|
||||
.settings-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.settings-icon {
|
||||
font-size: 2.5rem;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.icon-users {
|
||||
color: #6f42c1;
|
||||
background-color: rgba(111, 66, 193, 0.1);
|
||||
}
|
||||
.icon-pesticides {
|
||||
color: #198754;
|
||||
background-color: rgba(25, 135, 84, 0.1);
|
||||
}
|
||||
.icon-integrations {
|
||||
color: #0d6efd;
|
||||
background-color: rgba(13, 110, 253, 0.1);
|
||||
}
|
||||
.icon-notifications {
|
||||
color: #fd7e14;
|
||||
background-color: rgba(253, 126, 20, 0.1);
|
||||
}
|
||||
.icon-general {
|
||||
color: #6c757d;
|
||||
background-color: rgba(108, 117, 125, 0.1);
|
||||
}
|
||||
.icon-equipment {
|
||||
color: #dc3545;
|
||||
background-color: rgba(220, 53, 69, 0.1);
|
||||
}
|
||||
.last-updated {
|
||||
font-size: 0.8rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container py-5">
|
||||
<div class="row mb-4">
|
||||
<div class="col">
|
||||
<h1 class="display-5 mb-3">Settings</h1>
|
||||
<p class="text-muted lead">Configure your organization's preferences and integrations</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<!-- User Management Card -->
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
<div class="card settings-card border-0 shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<div class="settings-icon icon-users">
|
||||
<i class="bi bi-people-fill"></i>
|
||||
</div>
|
||||
<h2 class="h4 mb-2">User Management</h2>
|
||||
<p class="text-muted mb-4">Manage staff accounts, roles, and permissions for your organization.</p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<a href="{{ .URLs.SettingUser }}" class="btn btn-outline-primary">
|
||||
Manage Users
|
||||
<i class="bi bi-arrow-right ms-1"></i>
|
||||
</a>
|
||||
<span class="last-updated">23 users</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pesticide Products Card -->
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
<div class="card settings-card border-0 shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<div class="settings-icon icon-pesticides">
|
||||
<i class="bi bi-droplet-fill"></i>
|
||||
</div>
|
||||
<h2 class="h4 mb-2">Pesticide Products</h2>
|
||||
<p class="text-muted mb-4">Configure products, application rates, and field recommendations.</p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<a href="{{ .URLs.SettingPesticide }}" class="btn btn-outline-success">
|
||||
Manage Products
|
||||
<i class="bi bi-arrow-right ms-1"></i>
|
||||
</a>
|
||||
<span class="last-updated">12 active products</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Integrations Card -->
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
<div class="card settings-card border-0 shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<div class="settings-icon icon-integrations">
|
||||
<i class="bi bi-gear-wide-connected"></i>
|
||||
</div>
|
||||
<h2 class="h4 mb-2">Integrations</h2>
|
||||
<p class="text-muted mb-4">Configure connections with FieldSeeker, VectorSurv, and other services.</p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<a href="{{ .URLs.SettingIntegration }}" class="btn btn-outline-primary">
|
||||
Manage Integrations
|
||||
<i class="bi bi-arrow-right ms-1"></i>
|
||||
</a>
|
||||
<span class="last-updated">3 active connections</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Equipment Card -->
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
<div class="card settings-card border-0 shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<div class="settings-icon icon-equipment">
|
||||
<i class="bi bi-tools"></i>
|
||||
</div>
|
||||
<h2 class="h4 mb-2">Equipment</h2>
|
||||
<p class="text-muted mb-4">Manage your field equipment inventory, calibration, and maintenance.</p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<a href="equipment.html" class="btn btn-outline-danger">
|
||||
Manage Equipment
|
||||
<i class="bi bi-arrow-right ms-1"></i>
|
||||
</a>
|
||||
<span class="last-updated">Updated 5 days ago</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notifications Card -->
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
<div class="card settings-card border-0 shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<div class="settings-icon icon-notifications">
|
||||
<i class="bi bi-bell-fill"></i>
|
||||
</div>
|
||||
<h2 class="h4 mb-2">Notifications</h2>
|
||||
<p class="text-muted mb-4">Configure email alerts, SMS notifications, and reporting preferences.</p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<a href="notifications.html" class="btn btn-outline-warning">
|
||||
Manage Notifications
|
||||
<i class="bi bi-arrow-right ms-1"></i>
|
||||
</a>
|
||||
<span class="last-updated">5 active alerts</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- General Settings Card -->
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
<div class="card settings-card border-0 shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<div class="settings-icon icon-general">
|
||||
<i class="bi bi-sliders"></i>
|
||||
</div>
|
||||
<h2 class="h4 mb-2">General Settings</h2>
|
||||
<p class="text-muted mb-4">Configure organization details, branding, and system preferences.</p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<a href="general-settings.html" class="btn btn-outline-secondary">
|
||||
Manage Settings
|
||||
<i class="bi bi-arrow-right ms-1"></i>
|
||||
</a>
|
||||
<span class="last-updated">Updated yesterday</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 text-center text-muted">
|
||||
<p class="small">
|
||||
<i class="bi bi-shield-lock me-1"></i>
|
||||
All changes made in settings are logged for audit purposes
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
231
htmlpage/templates/setting-pesticide-add.html
Normal file
231
htmlpage/templates/setting-pesticide-add.html
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.section-heading {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.target-icon {
|
||||
display: inline-block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
border-radius: 50%;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 4px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.target-active {
|
||||
background-color: #0d6efd;
|
||||
}
|
||||
|
||||
.target-inactive {
|
||||
background-color: #dee2e6;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.4rem 0.8rem;
|
||||
margin: 0.2rem;
|
||||
border-radius: 30px;
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.tag i {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.tag-enabled {
|
||||
background-color: #d1e7dd;
|
||||
color: #0f5132;
|
||||
border-color: #a3cfbb;
|
||||
}
|
||||
|
||||
.tag-ppe {
|
||||
background-color: #e2e3e5;
|
||||
color: #41464b;
|
||||
border-color: #d3d6d8;
|
||||
}
|
||||
|
||||
.tag-equipment {
|
||||
background-color: #cff4fc;
|
||||
color: #055160;
|
||||
border-color: #9eeaf9;
|
||||
}
|
||||
|
||||
.tag-suitability {
|
||||
background-color: #fff3cd;
|
||||
color: #664d03;
|
||||
border-color: #ffecb5;
|
||||
}
|
||||
|
||||
.tag-optional {
|
||||
background-color: #f8f9fa;
|
||||
color: #6c757d;
|
||||
border-color: #dee2e6;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container py-4">
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="#">Settings</a></li>
|
||||
<li class="breadcrumb-item"><a href="pesticide-config.html">Pesticide</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">VectoMax FG</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<!-- Product Header -->
|
||||
<div class="d-flex justify-content-between align-items-start mb-4">
|
||||
<div>
|
||||
<h1 class="mb-2">VectoMax FG</h1>
|
||||
<p class="text-muted mb-0">Biological larvicide granules combining Bacillus thuringiensis subspecies israelensis and Bacillus sphaericus for extended residual control of mosquito larvae.</p>
|
||||
</div>
|
||||
<span class="tag tag-enabled">
|
||||
<i class="bi bi-check-circle-fill"></i> Enabled
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- General Information -->
|
||||
<div class="mb-4">
|
||||
<h2 class="section-heading">General Information</h2>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="info-label">Formulation</div>
|
||||
<div>Granule</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="info-label">EPA Registration Number</div>
|
||||
<div>73049-429</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="info-label">Active Ingredients</div>
|
||||
<div>Bacillus thuringiensis subspecies israelensis (2.7%)<br>
|
||||
Bacillus sphaericus (4.5%)</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="info-label">Biological Targeting</div>
|
||||
<div class="mt-1">
|
||||
<span class="target-icon target-active" title="Instar Stage 1">I1</span>
|
||||
<span class="target-icon target-active" title="Instar Stage 2">I2</span>
|
||||
<span class="target-icon target-active" title="Instar Stage 3">I3</span>
|
||||
<span class="target-icon target-active" title="Instar Stage 4">I4</span>
|
||||
<span class="target-icon target-inactive" title="Pupae">P</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="info-label">Application Rates</div>
|
||||
<div>Low: 5 lbs/acre<br>
|
||||
High: 20 lbs/acre</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="info-label">Residual</div>
|
||||
<div>Up to 30 days (environmental conditions dependent)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Usage Notes -->
|
||||
<div class="alert alert-info mb-4">
|
||||
<div class="d-flex">
|
||||
<div class="me-3">
|
||||
<i class="bi bi-info-circle-fill fs-4"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="alert-heading">Key Usage Notes</h5>
|
||||
<p class="mb-0">Apply evenly across water surface. Use higher rate when L4 present or when organic load is high. Avoid application in ponds with fish unless approved by a supervisor.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PPE Requirements -->
|
||||
<div class="mb-4">
|
||||
<h2 class="section-heading">PPE Requirements</h2>
|
||||
<div>
|
||||
<span class="tag tag-ppe">
|
||||
<i class="bi bi-hand-thumbs-up"></i> Gloves
|
||||
</span>
|
||||
<span class="tag tag-ppe">
|
||||
<i class="bi bi-eyeglasses"></i> Eye Protection
|
||||
</span>
|
||||
<span class="tag tag-optional">
|
||||
<i class="bi bi-mask"></i> Respirator (Optional)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Equipment Supported -->
|
||||
<div class="mb-4">
|
||||
<h2 class="section-heading">Equipment Supported</h2>
|
||||
<div>
|
||||
<span class="tag tag-equipment">
|
||||
<i class="bi bi-backpack"></i> Backpack Spreader
|
||||
</span>
|
||||
<span class="tag tag-equipment">
|
||||
<i class="bi bi-hand-index-thumb"></i> Hand Spreader
|
||||
</span>
|
||||
<span class="tag tag-equipment">
|
||||
<i class="bi bi-truck"></i> Truck Granule Unit
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Suitability -->
|
||||
<div class="mb-4">
|
||||
<h2 class="section-heading">Suitability</h2>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="info-label">Pools</div>
|
||||
<div><span class="badge bg-success">Recommended</span></div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="info-label">Vegetation</div>
|
||||
<div><span class="badge bg-info text-dark">OK</span></div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="info-label">High Organics</div>
|
||||
<div><span class="badge bg-info text-dark">OK</span></div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="info-label">Organic Crop Restriction</div>
|
||||
<div><span class="badge bg-secondary">None</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="d-flex justify-content-between mt-5 pt-3 border-top">
|
||||
<button class="btn btn-outline-danger">
|
||||
<i class="bi bi-trash me-2"></i> Remove from Inventory
|
||||
</button>
|
||||
<button class="btn btn-success">
|
||||
<i class="bi bi-plus-circle me-2"></i> Add to Allowed Inventory
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
211
htmlpage/templates/setting-pesticide.html
Normal file
211
htmlpage/templates/setting-pesticide.html
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.target-icon {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
text-align: center;
|
||||
line-height: 24px;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-right: 2px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.target-active {
|
||||
background-color: #0d6efd;
|
||||
}
|
||||
|
||||
.target-inactive {
|
||||
background-color: #dee2e6;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.table-responsive {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
th {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container-fluid p-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="mb-0">Pesticide Products Configuration</h1>
|
||||
<a href="{{ .URLs.SettingPesticideAdd }}" class="btn btn-primary" id="addProductBtn">
|
||||
<i class="bi bi-plus-circle me-2"></i>Add New Product
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th>Formulation</th>
|
||||
<th>Targets</th>
|
||||
<th>Residual (days)</th>
|
||||
<th>Low Rate</th>
|
||||
<th>Max Rate</th>
|
||||
<th>Pools</th>
|
||||
<th>Info</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Sample pesticide data -->
|
||||
<tr>
|
||||
<td><strong>BVA Oil</strong></td>
|
||||
<td>Liquid</td>
|
||||
<td>
|
||||
<span class="target-icon target-inactive" title="Instar Stage 1">I1</span>
|
||||
<span class="target-icon target-inactive" title="Instar Stage 2">I2</span>
|
||||
<span class="target-icon target-inactive" title="Instar Stage 3">I3</span>
|
||||
<span class="target-icon target-inactive" title="Instar Stage 4">I4</span>
|
||||
<span class="target-icon target-active" title="Pupae">P</span>
|
||||
</td>
|
||||
<td>1</td>
|
||||
<td>0.5 gal/acre</td>
|
||||
<td>5 gal/acre</td>
|
||||
<td><span class="badge bg-success">Recommended</span></td>
|
||||
<td>
|
||||
<a href="product-details.html?id=bva-oil" class="btn btn-sm btn-info" title="Product Information">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" title="Edit">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" title="Delete">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>VectoMax FG</strong></td>
|
||||
<td>Granule</td>
|
||||
<td>
|
||||
<span class="target-icon target-active" title="Instar Stage 1">I1</span>
|
||||
<span class="target-icon target-active" title="Instar Stage 2">I2</span>
|
||||
<span class="target-icon target-active" title="Instar Stage 3">I3</span>
|
||||
<span class="target-icon target-active" title="Instar Stage 4">I4</span>
|
||||
<span class="target-icon target-inactive" title="Pupae">P</span>
|
||||
</td>
|
||||
<td>30</td>
|
||||
<td>5 lbs/acre</td>
|
||||
<td>20 lbs/acre</td>
|
||||
<td><span class="badge bg-success">Recommended</span></td>
|
||||
<td>
|
||||
<a href="product-details.html?id=vectomax-fg" class="btn btn-sm btn-info" title="Product Information">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" title="Edit">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" title="Delete">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Censor</strong></td>
|
||||
<td>Liquid</td>
|
||||
<td>
|
||||
<span class="target-icon target-active" title="Instar Stage 1">I1</span>
|
||||
<span class="target-icon target-active" title="Instar Stage 2">I2</span>
|
||||
<span class="target-icon target-active" title="Instar Stage 3">I3</span>
|
||||
<span class="target-icon target-active" title="Instar Stage 4">I4</span>
|
||||
<span class="target-icon target-inactive" title="Pupae">P</span>
|
||||
</td>
|
||||
<td>21</td>
|
||||
<td>0.75 gal/acre</td>
|
||||
<td>2.5 gal/acre</td>
|
||||
<td><span class="badge bg-warning text-dark">Allowed</span></td>
|
||||
<td>
|
||||
<a href="product-details.html?id=censor" class="btn btn-sm btn-info" title="Product Information">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" title="Edit">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" title="Delete">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>AquaBac XT</strong></td>
|
||||
<td>Liquid</td>
|
||||
<td>
|
||||
<span class="target-icon target-active" title="Instar Stage 1">I1</span>
|
||||
<span class="target-icon target-active" title="Instar Stage 2">I2</span>
|
||||
<span class="target-icon target-active" title="Instar Stage 3">I3</span>
|
||||
<span class="target-icon target-inactive" title="Instar Stage 4">I4</span>
|
||||
<span class="target-icon target-inactive" title="Pupae">P</span>
|
||||
</td>
|
||||
<td>14</td>
|
||||
<td>0.25 gal/acre</td>
|
||||
<td>2 gal/acre</td>
|
||||
<td><span class="badge bg-danger">Prohibited</span></td>
|
||||
<td>
|
||||
<a href="product-details.html?id=aquabac-xt" class="btn btn-sm btn-info" title="Product Information">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" title="Edit">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" title="Delete">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Natular G30</strong></td>
|
||||
<td>Granule</td>
|
||||
<td>
|
||||
<span class="target-icon target-active" title="Instar Stage 1">I1</span>
|
||||
<span class="target-icon target-active" title="Instar Stage 2">I2</span>
|
||||
<span class="target-icon target-active" title="Instar Stage 3">I3</span>
|
||||
<span class="target-icon target-active" title="Instar Stage 4">I4</span>
|
||||
<span class="target-icon target-inactive" title="Pupae">P</span>
|
||||
</td>
|
||||
<td>30</td>
|
||||
<td>5 lbs/acre</td>
|
||||
<td>12 lbs/acre</td>
|
||||
<td><span class="badge bg-secondary">Discouraged</span></td>
|
||||
<td>
|
||||
<a href="product-details.html?id=natular-g30" class="btn btn-sm btn-info" title="Product Information">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary" title="Edit">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" title="Delete">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
123
htmlpage/templates/setting-user-add.html
Normal file
123
htmlpage/templates/setting-user-add.html
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.form-check-input.switch-lg {
|
||||
width: 3em;
|
||||
height: 1.5em;
|
||||
}
|
||||
.required-field::after {
|
||||
content: " *";
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow">
|
||||
<div class="card-header bg-white">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h3 class="mb-0">Add New User</h3>
|
||||
<a href="user-management.html" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="bi bi-arrow-left me-1"></i> Back to Users
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="addUserForm" class="needs-validation" novalidate>
|
||||
<!-- Full Name -->
|
||||
<div class="mb-3">
|
||||
<label for="fullName" class="form-label required-field">Full Name</label>
|
||||
<input type="text" class="form-control" id="fullName" required>
|
||||
<div class="invalid-feedback">
|
||||
Please provide the user's full name.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email Address -->
|
||||
<div class="mb-3">
|
||||
<label for="emailAddress" class="form-label required-field">Email Address</label>
|
||||
<input type="email" class="form-control" id="emailAddress" required>
|
||||
<div class="invalid-feedback">
|
||||
Please provide a valid email address.
|
||||
</div>
|
||||
<div class="form-text">An invitation will be sent to this email address.</div>
|
||||
</div>
|
||||
|
||||
<!-- Username -->
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label required-field">Username</label>
|
||||
<input type="text" class="form-control" id="username" required>
|
||||
<div class="invalid-feedback">
|
||||
Please provide a username.
|
||||
</div>
|
||||
<div class="form-text">Username must be unique and contain only letters, numbers, and underscores.</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Role -->
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="userRole" class="form-label required-field">Role</label>
|
||||
<select class="form-select" id="userRole" required>
|
||||
<option value="" selected disabled>Select a role</option>
|
||||
<option value="lead">Lead</option>
|
||||
<option value="technician">Technician</option>
|
||||
<option value="administrator">Administrator</option>
|
||||
</select>
|
||||
<div class="invalid-feedback">
|
||||
Please select a role.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Initial Status -->
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="initialStatus" class="form-label">Initial Status</label>
|
||||
<select class="form-select" id="initialStatus">
|
||||
<option value="invited" selected>Invited</option>
|
||||
<option value="active">Active</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Permissions -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label d-block">Permissions</label>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input switch-lg" type="checkbox" id="serveWarrants">
|
||||
<label class="form-check-label" for="serveWarrants">
|
||||
Can serve warrants
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Send welcome email checkbox -->
|
||||
<div class="mb-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="sendWelcomeEmail" checked>
|
||||
<label class="form-check-label" for="sendWelcomeEmail">
|
||||
Send welcome email with login instructions
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- Form actions -->
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a href="user-management.html" class="btn btn-secondary">
|
||||
Cancel
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-person-plus me-1"></i> Add User
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
144
htmlpage/templates/setting-user.html
Normal file
144
htmlpage/templates/setting-user.html
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.form-check-input.switch-lg {
|
||||
width: 3em;
|
||||
height: 1.5em;
|
||||
}
|
||||
.status-badge {
|
||||
width: 100px;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container-fluid p-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="mb-0">User Management</h1>
|
||||
<a href="{{ .URLs.SettingUserAdd }}" class="btn btn-primary" id="addUserBtn">
|
||||
<i class="bi bi-plus-circle me-2"></i>Add New User
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Full Name</th>
|
||||
<th>Email Address</th>
|
||||
<th>Username</th>
|
||||
<th>Role</th>
|
||||
<th>Serve Warrants</th>
|
||||
<th>Status</th>
|
||||
<th>Last Login</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Sample user data -->
|
||||
<tr>
|
||||
<td>John Doe</td>
|
||||
<td>john.doe@example.com</td>
|
||||
<td>johndoe</td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm">
|
||||
<option>Lead</option>
|
||||
<option selected>Technician</option>
|
||||
<option>Administrator</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input switch-lg" type="checkbox" checked>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="badge bg-success status-badge">Active</span></td>
|
||||
<td>2023-06-15 09:45 AM</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-warning" title="Deactivate">
|
||||
<i class="bi bi-person-x"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Jane Smith</td>
|
||||
<td>jane.smith@example.com</td>
|
||||
<td>janesmith</td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm">
|
||||
<option selected>Lead</option>
|
||||
<option>Technician</option>
|
||||
<option>Administrator</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input switch-lg" type="checkbox">
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="badge bg-success status-badge">Active</span></td>
|
||||
<td>2023-06-17 14:20 PM</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-warning" title="Deactivate">
|
||||
<i class="bi bi-person-x"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Robert Johnson</td>
|
||||
<td>robert.j@example.com</td>
|
||||
<td>robertj</td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm">
|
||||
<option>Lead</option>
|
||||
<option>Technician</option>
|
||||
<option selected>Administrator</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input switch-lg" type="checkbox" checked>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="badge bg-secondary status-badge">Deactivated</span></td>
|
||||
<td>2023-06-10 11:30 AM</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-success" title="Activate">
|
||||
<i class="bi bi-person-check"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Emily Wilson</td>
|
||||
<td>emily.w@example.com</td>
|
||||
<td>emilyw</td>
|
||||
<td>
|
||||
<select class="form-select form-select-sm">
|
||||
<option>Lead</option>
|
||||
<option selected>Technician</option>
|
||||
<option>Administrator</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input switch-lg" type="checkbox">
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="badge bg-warning text-dark status-badge">Invited</span></td>
|
||||
<td>Never</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-info" title="Resend Invitation">
|
||||
<i class="bi bi-envelope"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
8
htmlpage/templates/settings.html
Normal file
8
htmlpage/templates/settings.html
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{{template "authenticated.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<p>Imagine settings here</p>
|
||||
{{end}}
|
||||
90
htmlpage/templates/signin.html
Normal file
90
htmlpage/templates/signin.html
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Login{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.login-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.login-box {
|
||||
box-shadow: 0 0 15px rgba(0,0,0,0.1);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.login-form-section {
|
||||
padding: 40px;
|
||||
}
|
||||
.product-info-section {
|
||||
padding: 40px;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.login-header {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container min-vh-100 d-flex align-items-center justify-content-center py-5">
|
||||
<div class="login-container">
|
||||
<div class="row login-box g-0">
|
||||
<!-- Left side: Login Form -->
|
||||
<div class="col-md-6 login-form-section">
|
||||
<div class="login-header">
|
||||
<h2>Welcome Back</h2>
|
||||
<p class="text-muted">Please enter your credentials</p>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="/signin">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" name="username" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" name="password" required>
|
||||
</div>
|
||||
|
||||
{{ if .InvalidCredentials }}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
The credentials you provided weren't recognized.
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" class="btn btn-primary">Login</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 text-center">
|
||||
<p>Don't have an account? <a href="/signup">Sign up</a></p>
|
||||
<a href="forgot-password.html">Forgot password?</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Right side: Product Information -->
|
||||
<div class="col-md-6 product-info-section">
|
||||
<div>
|
||||
<img src="/static/img/nidus-logo-256-transparent.png"></img>
|
||||
<h2>Nidus Sync</h2>
|
||||
<p class="lead mb-4">All your field data, sync'd to all your techs</p>
|
||||
|
||||
<div class="mb-4">
|
||||
<p>Something intelligent and intriguing</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h5>Key Features</h5>
|
||||
<ul>
|
||||
<li>Works with <b>Fieldseeker</b></li>
|
||||
<li>Works <i>with</i> Fieldseeker</li>
|
||||
<li><b>Works</b> with Fieldseeker</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
114
htmlpage/templates/signup.html
Normal file
114
htmlpage/templates/signup.html
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
{{template "base.html" .}}
|
||||
|
||||
{{define "title"}}Login{{end}}
|
||||
{{define "extraheader"}}
|
||||
<style>
|
||||
.register-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.register-box {
|
||||
box-shadow: 0 0 15px rgba(0,0,0,0.1);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.register-form-section {
|
||||
padding: 40px;
|
||||
}
|
||||
.register-info-section {
|
||||
padding: 40px;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.register-header {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
.logo-area {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.logo-placeholder {
|
||||
width: 120px;
|
||||
height: 60px;
|
||||
background-color: #e9ecef;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container min-vh-100 d-flex align-items-center justify-content-center py-5">
|
||||
<div class="register-container">
|
||||
<!-- Logo Area -->
|
||||
<div class="logo-area">
|
||||
<div class="logo-placeholder">
|
||||
<span class="text-muted">Your Logo</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row register-box g-0">
|
||||
<!-- Left side: Registration Form -->
|
||||
<div class="col-md-6 register-form-section">
|
||||
<div class="register-header">
|
||||
<h2>Create an Account</h2>
|
||||
<p class="text-muted">Join us today to get started</p>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="/signup">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Display Name</label>
|
||||
<input type="text" class="form-control" name="name" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Username</label>
|
||||
<input type="username" class="form-control" name="username" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" name="password" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" name="terms" required>
|
||||
<label class="form-check-label" for="terms">I agree to the <a href="#">Terms of Service</a> and <a href="#">Privacy Policy</a></label>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" class="btn btn-primary">Register</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 text-center">
|
||||
<p>Already have an account? <a href="login.html">Sign in</a></p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Right side: Account Information -->
|
||||
<div class="col-md-6 register-info-section">
|
||||
<div>
|
||||
<h2>Account Information</h2>
|
||||
<p class="lead mb-4">What you need to know</p>
|
||||
|
||||
<div class="mb-4">
|
||||
<h5>Who should register?</h5>
|
||||
<p>This platform is designed for professionals who need to manage projects and collaborate with team members. Whether you're a freelancer, small business owner, or part of a larger organization, our tools can help streamline your workflow.</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<h5>What happens after registration?</h5>
|
||||
<p>After you register with your email, you'll receive a confirmation message with instructions to complete your account setup. You'll then have access to all features and can customize your workspace based on your specific needs.</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">For any questions about account types or registration, please contact our support team at support@yourproduct.com</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
364
htmlpage/templates/source.html
Normal file
364
htmlpage/templates/source.html
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
{{template "authenticated.html" .}}
|
||||
|
||||
{{define "title"}}Dash{{end}}
|
||||
{{define "extraheader"}}
|
||||
{{template "map" .MapData}}
|
||||
<style>
|
||||
.info-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.info-table td {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: 600;
|
||||
vertical-align: top;
|
||||
}
|
||||
.map-container {
|
||||
background-color: #e9ecef;
|
||||
height: 500px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
margin-top: 30px;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.source-info {
|
||||
background-color: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.time-delta-positive {
|
||||
color: #dc3545; /* red for late */
|
||||
}
|
||||
|
||||
.time-delta-negative {
|
||||
color: #28a745; /* green for early */
|
||||
}
|
||||
|
||||
.time-delta-neutral {
|
||||
color: #6c757d; /* gray for on time */
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container mt-4 mb-5">
|
||||
<!-- Source Header Section -->
|
||||
<div class="row mb-2">
|
||||
<div class="col-12">
|
||||
<h1>Breeding Source Detail</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-2">
|
||||
<div class="col-12">
|
||||
<div class="source-info">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="source-id">Source ID: {{ .Source.GlobalID }}</div>
|
||||
<table class="info-table">
|
||||
<tr>
|
||||
<td class="info-label">Access:</td>
|
||||
<td>{{ .Source.AccessDescription }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Address:</td>
|
||||
<td>Not implemented</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Comments:</td>
|
||||
<td>{{ .Source.Comments }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Deactivate Reason:</td>
|
||||
<td>{{ .Source.DeactivateReason }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Description:</td>
|
||||
<td>{{ .Source.Description }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Habitat:</td>
|
||||
<td>{{ .Source.Habitat }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Jurisdiction:</td>
|
||||
<td>{{ .Source.Jurisdiction }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Location Number:</td>
|
||||
<td>{{ .Source.LocationNumber }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Name:</td>
|
||||
<td>{{ .Source.Name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
<td>
|
||||
{{ if .Source.Active }}<span class="badge bg-warning">Active</span>
|
||||
{{ else }}<span class="badge bg-info">Inactive</span>
|
||||
{{ end }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Priority:</td>
|
||||
<td>{{ .Source.Priority }} ({{.Source.ScalarPriority}})</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">S Type:</td>
|
||||
<td>{{ .Source.SourceType }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Source Status:</td>
|
||||
<td>{{ .Source.SourceStatus }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Symbology:</td>
|
||||
<td>{{ .Source.Symbology }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Use Type:</td>
|
||||
<td>{{ .Source.UseType }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Water Origin:</td>
|
||||
<td>{{ .Source.WaterOrigin }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Zone:</td>
|
||||
<td>{{ .Source.Zone }}.{{ .Source.Zone2 }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<table class="info-table">
|
||||
<tr>
|
||||
<td class="info-label">Creation date</td>
|
||||
<td>{{ .Source.Created|timeSince }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Edit date</td>
|
||||
<td>{{ .Source.EditedAt|timeSince }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Larva Inspect Interval</td>
|
||||
<td>{{ .Source.LarvaeInspectInterval }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Last Inspect Activity</td>
|
||||
<td>{{ .Source.LastInspectionActivity }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Last Inspect Avg Larva</td>
|
||||
<td>{{ .Source.LastInspectionAverageLarvae }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Last Inspect Avg Pupae</td>
|
||||
<td>{{ .Source.LastInspectionAveragePupae }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Last Inspect Breeding</td>
|
||||
<td>{{ .Source.LastInspectionBreeding }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Last Inspect Conditions</td>
|
||||
<td>{{ .Source.LastInspectionConditions }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Last Inspect Date</td>
|
||||
<td>{{ .Source.LastInspectionDate|timeSince }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Last Inspect Species</td>
|
||||
<td>{{ .Source.LastInspectionFieldSpecies }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Last Inspect Life Stages</td>
|
||||
<td>{{ .Source.LastInspectionLifeStages }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Last Treat Activity</td>
|
||||
<td>{{ .Source.LastTreatmentActivity }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Last Treat Date</td>
|
||||
<td>{{ .Source.LastTreatmentDate|timeSince }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Last Treat Product</td>
|
||||
<td>{{ .Source.LastTreatmentProduct }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Last Treat Quantity</td>
|
||||
<td>{{ .Source.LastTreatmentQuantity }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Last Treat Quantity Unit</td>
|
||||
<td>{{ .Source.LastTreatmentQuantityUnit }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Next action date scheduled:</td>
|
||||
<td>{{ .Source.NextActionScheduledDate|timeSince }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Treatment Cadence:</td>
|
||||
<td>Not implemented</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Map Section -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="map-container">
|
||||
<div id="map"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Two-Column Layout for Tables -->
|
||||
<div class="row">
|
||||
<!-- Left Column -->
|
||||
<div class="col-md-6">
|
||||
<!-- Treatments Section -->
|
||||
<h2 class="section-header">Treatment History</h2>
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Year</th>
|
||||
<th>Start</th>
|
||||
<th>End</th>
|
||||
<th>Interval</th>
|
||||
</tr>
|
||||
{{ range .TreatmentModels }}
|
||||
<tr>
|
||||
<td>{{.Year}}</td>
|
||||
<td>{{.SeasonStart|timeAsRelativeDate}}</td>
|
||||
<td>{{.SeasonEnd|timeAsRelativeDate}}</td>
|
||||
<td>{{.Interval|timeInterval}}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Treatment Date</th>
|
||||
<th>Insecticide Used</th>
|
||||
<th>Cadence Delta</th>
|
||||
<th>Technician Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range .Treatments }}
|
||||
<tr>
|
||||
<td>{{.Date|timeSince}}</td>
|
||||
<td>{{.Product}}</td>
|
||||
<td class="time-delta-neutral">{{.CadenceDelta|timeDelta}}</td>
|
||||
<td>{{.Notes}}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Right Column -->
|
||||
<div class="col-md-6">
|
||||
<!-- Inspections Section -->
|
||||
<h2 class="section-header">Inspection History</h2>
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Inspection Date</th>
|
||||
<th>Action</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range .Inspections }}
|
||||
<tr>
|
||||
<td>{{.Date|timeSince}}</td>
|
||||
<td>{{.Action}}</td>
|
||||
<td>{{.Notes}}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-12">
|
||||
<h2 class="section-header">Nearby Mosquito Traps</h2>
|
||||
{{ range .Traps }}
|
||||
<div class="trap-info">
|
||||
<table class="info-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="info-label">Trap ID:</td>
|
||||
<td><a href="/trap/{{.ID}}">{{ .ID }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="info-label">Distance</td>
|
||||
<td>{{ .Distance }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Collection Date</th>
|
||||
<th>Female Count</th>
|
||||
<th>Male Count</th>
|
||||
<th>Total Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Trap 1 with multiple collections -->
|
||||
{{ range .Counts }}
|
||||
<tr>
|
||||
<td>{{ .Ended|timeSince }}</td>
|
||||
<td>{{ .Females }}</td>
|
||||
<td>{{ .Males }}</td>
|
||||
<td>{{ .Total }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
163
htmlpage/time.go
Normal file
163
htmlpage/time.go
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
package htmlpage
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
//"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// TreatmentModel represents the calculated model for a year's treatments
|
||||
type TreatmentModel struct {
|
||||
Year int
|
||||
SeasonStart time.Time
|
||||
SeasonEnd time.Time
|
||||
Interval time.Duration
|
||||
ActualDates []time.Time
|
||||
PredictedDates []time.Time
|
||||
Errors []time.Duration
|
||||
}
|
||||
|
||||
func modelTreatment(treatments []Treatment) []TreatmentModel {
|
||||
treatment_times := make([]time.Time, 0)
|
||||
for _, treatment := range treatments {
|
||||
if treatment.Date != nil {
|
||||
treatment_times = append(treatment_times, *treatment.Date)
|
||||
}
|
||||
}
|
||||
models := calculateTreatmentModels(treatment_times)
|
||||
/*models_by_year := make(map[int]TreatmentModel)
|
||||
for _, m := range models {
|
||||
models_by_year[m.Year] = m
|
||||
}*/
|
||||
offset := 0
|
||||
for _, model := range models {
|
||||
for _, e := range model.Errors {
|
||||
treatments[offset].CadenceDelta = e
|
||||
offset = offset + 1
|
||||
}
|
||||
}
|
||||
/*
|
||||
for i, treatment := range treatments {
|
||||
model
|
||||
treatment.CadenceDelta = deltas[i]
|
||||
treatments[i] = treatment
|
||||
}
|
||||
*/
|
||||
/*cadence, deltas := calculateCadenceVariance(treatment_times)
|
||||
for i, treatment := range treatments {
|
||||
if i >= len(deltas) {
|
||||
break
|
||||
}
|
||||
treatment.CadenceDelta = deltas[i]
|
||||
treatments[i] = treatment
|
||||
}*/
|
||||
return models
|
||||
}
|
||||
|
||||
// calculateTreatmentModels segments treatments by year and calculates a model for each year
|
||||
func calculateTreatmentModels(treatments []time.Time) []TreatmentModel {
|
||||
// Group treatments by year
|
||||
yearMap := make(map[int][]time.Time)
|
||||
for _, t := range treatments {
|
||||
year := t.Year()
|
||||
yearMap[year] = append(yearMap[year], t)
|
||||
}
|
||||
|
||||
// Calculate a model for each year
|
||||
var models []TreatmentModel
|
||||
for year, dates := range yearMap {
|
||||
// Sort dates within the year
|
||||
sort.Slice(dates, func(i, j int) bool {
|
||||
return dates[i].Before(dates[j])
|
||||
})
|
||||
|
||||
model := calculateYearModel(year, dates)
|
||||
models = append(models, model)
|
||||
}
|
||||
|
||||
// Sort models by year
|
||||
sort.Slice(models, func(i, j int) bool {
|
||||
return models[i].Year < models[j].Year
|
||||
})
|
||||
|
||||
return models
|
||||
}
|
||||
|
||||
// calculateYearModel creates a model for a specific year using linear regression
|
||||
func calculateYearModel(year int, dates []time.Time) TreatmentModel {
|
||||
n := len(dates)
|
||||
if n < 2 {
|
||||
// Not enough data for a model
|
||||
return TreatmentModel{
|
||||
Year: year,
|
||||
ActualDates: dates,
|
||||
}
|
||||
}
|
||||
|
||||
// Convert dates to numeric values (seconds since epoch)
|
||||
var x []float64
|
||||
var y []float64
|
||||
for i, date := range dates {
|
||||
x = append(x, float64(i))
|
||||
y = append(y, float64(date.Unix()))
|
||||
}
|
||||
|
||||
// Calculate linear regression
|
||||
slope, intercept := linearRegression(x, y)
|
||||
|
||||
// Convert back to time.Time and time.Duration
|
||||
startTime := time.Unix(int64(intercept), 0)
|
||||
intervalSeconds := int64(slope)
|
||||
interval := time.Duration(intervalSeconds) * time.Second
|
||||
|
||||
// Calculate end of season
|
||||
endTime := time.Unix(int64(intercept+slope*float64(n-1)), 0)
|
||||
|
||||
// Generate predicted dates and calculate errors
|
||||
var predictedDates []time.Time
|
||||
var errors []time.Duration
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
predicted := time.Unix(int64(intercept+slope*float64(i)), 0)
|
||||
predictedDates = append(predictedDates, predicted)
|
||||
|
||||
// Calculate error
|
||||
actualTime := dates[i]
|
||||
error := actualTime.Sub(predicted)
|
||||
errors = append(errors, error)
|
||||
}
|
||||
|
||||
return TreatmentModel{
|
||||
Year: year,
|
||||
SeasonStart: startTime,
|
||||
SeasonEnd: endTime,
|
||||
Interval: interval,
|
||||
ActualDates: dates,
|
||||
PredictedDates: predictedDates,
|
||||
Errors: errors,
|
||||
}
|
||||
}
|
||||
|
||||
// linearRegression calculates the slope and intercept of the best-fit line
|
||||
func linearRegression(x, y []float64) (float64, float64) {
|
||||
n := float64(len(x))
|
||||
if n < 2 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
var sumX, sumY, sumXY, sumX2 float64
|
||||
for i := 0; i < len(x); i++ {
|
||||
sumX += x[i]
|
||||
sumY += y[i]
|
||||
sumXY += x[i] * y[i]
|
||||
sumX2 += x[i] * x[i]
|
||||
}
|
||||
|
||||
// Calculate slope
|
||||
slope := (n*sumXY - sumX*sumY) / (n*sumX2 - sumX*sumX)
|
||||
|
||||
// Calculate intercept
|
||||
intercept := (sumY - slope*sumX) / n
|
||||
|
||||
return slope, intercept
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue