Rework template system, merge templates
The embedded portion doesn't work yet.
|
|
@ -1,99 +1,124 @@
|
||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>Welcome</title>
|
<title>Welcome</title>
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: #333333;
|
color: #333333;
|
||||||
}
|
}
|
||||||
.container {
|
.container {
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
.view-browser {
|
.view-browser {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #777777;
|
color: #777777;
|
||||||
padding-bottom: 15px;
|
padding-bottom: 15px;
|
||||||
}
|
}
|
||||||
.view-browser a {
|
.view-browser a {
|
||||||
color: #555555;
|
color: #555555;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
.header {
|
.header {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
}
|
}
|
||||||
.logo {
|
.logo {
|
||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
padding: 30px;
|
padding: 30px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
.button {
|
.button {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-color: #0066cc;
|
background-color: #0066cc;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
padding: 12px 25px;
|
padding: 12px 25px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.footer {
|
.footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #777777;
|
color: #777777;
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
}
|
}
|
||||||
.footer a {
|
.footer a {
|
||||||
color: #777777;
|
color: #777777;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{{if not .IsBrowser}}
|
{{ if not .IsBrowser }}
|
||||||
<div class="view-browser">
|
<div class="view-browser">
|
||||||
Email not displaying correctly? <a href="{{.C.URLBrowser}}">View it in your browser</a>
|
Email not displaying correctly?
|
||||||
</div>
|
<a href="{{ .C.URLBrowser }}">View it in your browser</a>
|
||||||
{{end}}
|
</div>
|
||||||
|
{{ end }}
|
||||||
<div class="header">
|
|
||||||
<!-- Logo Placeholder -->
|
|
||||||
<img src="{{.C.URLLogo}}" alt="Report Mosquitoes Online Logo" class="logo"></img>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
<h1>Welcome</h1>
|
|
||||||
|
|
||||||
<p>We're sending you this email because it's the first time we've gotten this email address ({{.C.Destination}}).</p>
|
|
||||||
|
|
||||||
<p>If you'd rather not receive emails from us you can reply with "Unsubscribe" in the subject or body of the email. You can also use the "Unsubscribe" feature of your mail client, if it supports list unsubscribes.</p>
|
|
||||||
|
|
||||||
<p>If instead you'd like to confirm that you're willing to receive emails at this address, you can do so by clicking below:</p>
|
|
||||||
|
|
||||||
<div style="text-align: center;">
|
|
||||||
<a href="{{.C.URLSubscribe}}" class="button">I want emails from Report Mosquitoes Online</a>
|
<div class="header">
|
||||||
|
<!-- Logo Placeholder -->
|
||||||
|
<img
|
||||||
|
src="{{ .C.URLLogo }}"
|
||||||
|
alt="Report Mosquitoes Online Logo"
|
||||||
|
class="logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<h1>Welcome</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
We're sending you this email because it's the first time we've gotten
|
||||||
|
this email address ({{ .C.Destination }}).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If you'd rather not receive emails from us you can reply with
|
||||||
|
"Unsubscribe" in the subject or body of the email. You can also use
|
||||||
|
the "Unsubscribe" feature of your mail client, if it supports list
|
||||||
|
unsubscribes.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If instead you'd like to confirm that you're willing to receive emails
|
||||||
|
at this address, you can do so by clicking below:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<a href="{{ .C.URLSubscribe }}" class="button"
|
||||||
|
>I want emails from Report Mosquitoes Online</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>
|
||||||
|
This email was sent to you because you or someone else gave your email
|
||||||
|
address to Report Mosquitoes Online.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If you no longer wish to receive these updates,
|
||||||
|
<a href="{{ .C.URLUnsubscribe }}">click here to unsubscribe</a>.
|
||||||
|
</p>
|
||||||
|
<p>© 2026 Gleipnir LLC. All rights reserved.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</body>
|
||||||
<div class="footer">
|
|
||||||
<p>This email was sent to you because you or someone else gave your email address to Report Mosquitoes Online.</p>
|
|
||||||
<p>If you no longer wish to receive these updates, <a href="{{.C.URLUnsubscribe}}">click here to unsubscribe</a>.</p>
|
|
||||||
<p>© 2026 Gleipnir LLC. All rights reserved.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
106
html/embed.go
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
package html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"embed"
|
||||||
|
//"errors"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
//"io"
|
||||||
|
"io/fs"
|
||||||
|
//"math"
|
||||||
|
"net/http"
|
||||||
|
//"path"
|
||||||
|
//"strconv"
|
||||||
|
"strings"
|
||||||
|
//"time"
|
||||||
|
|
||||||
|
//"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||||
|
//"github.com/aarondl/opt/null"
|
||||||
|
//"github.com/google/uuid"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed template/*
|
||||||
|
var embeddedFiles embed.FS
|
||||||
|
|
||||||
|
type templateSystemEmbed struct {
|
||||||
|
allTemplates *template.Template
|
||||||
|
nameToTemplate map[string]*template.Template
|
||||||
|
sourceFS fs.FS
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts templateSystemEmbed) loadAll() error {
|
||||||
|
ts.nameToTemplate = make(map[string]*template.Template, 0)
|
||||||
|
err := fs.WalkDir(ts.sourceFS, "template", func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
short_path := removeLeadingDir(path)
|
||||||
|
|
||||||
|
new_t, err := loadTemplateEmbedded(ts.sourceFS, short_path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to add load template '%s': %w", short_path, err)
|
||||||
|
}
|
||||||
|
_, err = ts.allTemplates.AddParseTree(new_t.Name(), new_t.Tree)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to add parsed template '%s': %w", path, err)
|
||||||
|
}
|
||||||
|
ts.nameToTemplate[short_path] = new_t
|
||||||
|
log.Debug().Str("path", path).Str("short_path", short_path).Msg("Loaded template")
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load embeded templates: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (ts templateSystemEmbed) renderOrError(w http.ResponseWriter, template_name string, context interface{}) {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
t, err := loadTemplateEmbedded(ts.sourceFS, template_name)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("template_name", template_name).Msg("Failed to load embedded template")
|
||||||
|
RespondError(w, "Failed to load template", err, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for name, templ := range ts.nameToTemplate {
|
||||||
|
_, err := t.AddParseTree(name, templ.Tree)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("name", name).Msg("Failed to add parse tree")
|
||||||
|
RespondError(w, "Failed to add template", err, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = t.ExecuteTemplate(buf, template_name, context)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("template_name", template_name).Msg("Failed to render embedded template")
|
||||||
|
RespondError(w, "Failed to render template", err, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf.WriteTo(w)
|
||||||
|
}
|
||||||
|
func loadTemplateEmbedded(sourceFS fs.FS, path string) (*template.Template, error) {
|
||||||
|
content, err := fs.ReadFile(sourceFS, "template/"+path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading template template/%s: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
new_t := template.New(path)
|
||||||
|
addFuncMap(new_t)
|
||||||
|
_, err = new_t.Parse(string(content))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing '%s': %w", path, err)
|
||||||
|
}
|
||||||
|
return new_t, nil
|
||||||
|
}
|
||||||
|
func removeLeadingDir(path string) string {
|
||||||
|
parts := strings.SplitN(path, "/", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
return parts[1]
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
244
html/filesystem.go
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
package html
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
//"embed"
|
||||||
|
//"errors"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
//"io"
|
||||||
|
"io/fs"
|
||||||
|
//"math"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
//"path"
|
||||||
|
//"strconv"
|
||||||
|
//"strings"
|
||||||
|
//"time"
|
||||||
|
|
||||||
|
//"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||||
|
//"github.com/aarondl/opt/null"
|
||||||
|
//"github.com/google/uuid"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The filesystem being used
|
||||||
|
var templates templateSystem
|
||||||
|
|
||||||
|
type templateSystem interface {
|
||||||
|
loadAll() error
|
||||||
|
renderOrError(http.ResponseWriter, string, interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type templateSystemDisk struct {
|
||||||
|
sourceFS fs.FS
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadTemplates() error {
|
||||||
|
_, err := os.Stat("html/template")
|
||||||
|
if err == nil {
|
||||||
|
templates = templateSystemDisk{
|
||||||
|
sourceFS: os.DirFS("./html/template"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
templates = templateSystemEmbed{
|
||||||
|
allTemplates: template.New("all"),
|
||||||
|
sourceFS: embeddedFiles,
|
||||||
|
}
|
||||||
|
templates.loadAll()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts templateSystemDisk) loadAll() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (ts templateSystemDisk) renderOrError(w http.ResponseWriter, template_name string, context interface{}) {
|
||||||
|
t, err := ts.parseTemplate(template_name)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("template_name", template_name).Msg("Failed to parse template")
|
||||||
|
RespondError(w, "Failed to parse template", err, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = ts.addSupportingTemplates(t)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("template_name", template_name).Msg("Failed to add supporting templates")
|
||||||
|
RespondError(w, "Failed to add supporting templates", err, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = ts.addSVGTemplates(t)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("template_name", template_name).Msg("Failed to add supporting templates")
|
||||||
|
RespondError(w, "Failed to add supporting templates", err, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
err = t.Execute(buf, context)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to render template")
|
||||||
|
RespondError(w, "Failed to render template", err, http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf.WriteTo(w)
|
||||||
|
}
|
||||||
|
func (ts templateSystemDisk) addSupportingTemplates(t *template.Template) error {
|
||||||
|
err := fs.WalkDir(ts.sourceFS, ".", func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := fs.ReadFile(ts.sourceFS, path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading template %s: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
new_t := template.New(path)
|
||||||
|
addFuncMap(new_t)
|
||||||
|
_, err = new_t.Parse(string(content))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing '%s': %w", path, err)
|
||||||
|
}
|
||||||
|
_, err = t.AddParseTree(new_t.Name(), new_t.Tree)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error adding parse tree '%s': %w", path, err)
|
||||||
|
}
|
||||||
|
log.Debug().Str("path", path).Msg("Read template")
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error walking template directory: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (ts templateSystemDisk) addSVGTemplates(t *template.Template) error {
|
||||||
|
svg_fs, err := fs.Sub(ts.sourceFS, "svg")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to get svg subdir: %w", err)
|
||||||
|
}
|
||||||
|
svgs, err := fs.ReadDir(svg_fs, ".")
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Msg("Failed to read svg directory")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, svg := range svgs {
|
||||||
|
content, err := fs.ReadFile(svg_fs, svg.Name())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to read svg '%s' from embedded filesystem: %w", svg, err)
|
||||||
|
}
|
||||||
|
svg_name := svg.Name()
|
||||||
|
svg_template := fmt.Sprintf("{{define \"%s\"}}%s{{end}}", svg_name, string(content))
|
||||||
|
svg_t, err := template.New(svg_name).Parse(svg_template)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to parse svg '%s' from embedded filesystem: %v", svg, err)
|
||||||
|
}
|
||||||
|
_, err = t.AddParseTree(svg_t.Name(), svg_t.Tree)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to add svg '%s' to embedded template: %v", svg, err)
|
||||||
|
}
|
||||||
|
log.Debug().Str("name", svg_name).Msg("add svg template")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (ts templateSystemDisk) parseTemplate(filename string) (*template.Template, error) {
|
||||||
|
t := template.New(filename)
|
||||||
|
log.Debug().Str("filename", filename).Msg("parsing template")
|
||||||
|
addFuncMap(t)
|
||||||
|
content, err := fs.ReadFile(ts.sourceFS, filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading template %s: %w", filename, err)
|
||||||
|
}
|
||||||
|
_, err = t.Parse(string(content))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing '%s': %w", filename, err)
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
func addSVGTemplates(fsys fs.FS, templ *template.Template) error {
|
||||||
|
svgs, err := fs.ReadDir(fsys, ".")
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Msg("Failed to read svg directory")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, svg := range svgs {
|
||||||
|
content, err := fs.ReadFile(fsys, svg.Name())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to read svg '%s' from embedded filesystem: %w", svg, err)
|
||||||
|
}
|
||||||
|
svg_name := svg.Name()
|
||||||
|
svg_template := fmt.Sprintf("{{define \"%s\"}}%s{{end}}", svg_name, string(content))
|
||||||
|
svg_t, err := template.New(svg_name).Parse(svg_template)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to parse svg '%s' from embedded filesystem: %v", svg, err)
|
||||||
|
}
|
||||||
|
_, err = templ.AddParseTree(svg_t.Name(), svg_t.Tree)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to add svg '%s' to embedded template: %v", svg, err)
|
||||||
|
}
|
||||||
|
//log.Debug().Str("name", svg_name).Msg("add svg template")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func executeTemplate(w io.Writer, data any) error {
|
||||||
|
if bt.template == nil {
|
||||||
|
name := path.Base(bt.files[0])
|
||||||
|
templ, err := parseFromDisk(bt.subdir, 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")
|
||||||
|
}
|
||||||
|
//log.Debug().Str("name", templ.Name()).Msg("Parsed template")
|
||||||
|
return templ.ExecuteTemplate(w, name, data)
|
||||||
|
} else {
|
||||||
|
name := path.Base(bt.files[0])
|
||||||
|
return bt.template.ExecuteTemplate(w, name, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func parseEmbedded(embeddedFiles embed.FS, subdir string, files []string) *template.Template {
|
||||||
|
funcMap := makeFuncMap()
|
||||||
|
// Remap the file names to embedded paths
|
||||||
|
embeddedFilePaths := make([]string, 0)
|
||||||
|
for _, f := range files {
|
||||||
|
embeddedFilePaths = append(embeddedFilePaths, strings.TrimPrefix(f, subdir))
|
||||||
|
}
|
||||||
|
name := path.Base(embeddedFilePaths[0])
|
||||||
|
log.Debug().Str("name", name).Strs("paths", embeddedFilePaths).Msg("Parsing embedded template")
|
||||||
|
t, err := template.New(name).Funcs(funcMap).ParseFS(embeddedFiles, embeddedFilePaths...)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Failed to parse embedded template %s: %v", name, err))
|
||||||
|
}
|
||||||
|
svg_fs, err := fs.Sub(embeddedFiles, "template/svg")
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Failed to read static/svg: %v", err))
|
||||||
|
}
|
||||||
|
err = addSVGTemplates(svg_fs, t)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Failed to add SVG templates: %v", err))
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
func parseFromDisk(subdir string, files []string) (*template.Template, error) {
|
||||||
|
funcMap := makeFuncMap()
|
||||||
|
name := path.Base(files[0])
|
||||||
|
//log.Debug().Str("name", name).Strs("files", files).Msg("parsing from disk")
|
||||||
|
templ, err := template.New(name).Funcs(funcMap).ParseFiles(files...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to parse %s: %w", files, err)
|
||||||
|
}
|
||||||
|
fsys := os.DirFS(subdir + "/template/svg")
|
||||||
|
err = addSVGTemplates(fsys, templ)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to add SVGs from disk: %w", err)
|
||||||
|
}
|
||||||
|
return templ, nil
|
||||||
|
}
|
||||||
|
*/
|
||||||
211
html/func.go
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
package html
|
||||||
|
|
||||||
|
import (
|
||||||
|
//"bytes"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
//"io/fs"
|
||||||
|
"math"
|
||||||
|
//"net/http"
|
||||||
|
//"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aarondl/opt/null"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addFuncMap(t *template.Template) {
|
||||||
|
funcMap := template.FuncMap{
|
||||||
|
"bigNumber": bigNumber,
|
||||||
|
"html": unescapeHTML,
|
||||||
|
"json": unescapeJS,
|
||||||
|
"GISStatement": gisStatement,
|
||||||
|
"latLngDisplay": latLngDisplay,
|
||||||
|
"publicReportID": publicReportID,
|
||||||
|
"timeAsRelativeDate": timeAsRelativeDate,
|
||||||
|
"timeDelta": timeDelta,
|
||||||
|
"timeElapsed": timeElapsed,
|
||||||
|
"timeInterval": timeInterval,
|
||||||
|
"timeSince": timeSince,
|
||||||
|
"timeSincePtr": timeSincePtr,
|
||||||
|
"uuidShort": uuidShort,
|
||||||
|
}
|
||||||
|
t.Funcs(funcMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 publicReportID(s string) string {
|
||||||
|
if len(s) != 12 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return s[0:4] + "-" + s[4:8] + "-" + s[8:12]
|
||||||
|
}
|
||||||
|
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 timeSincePtr(t *time.Time) string {
|
||||||
|
if t == nil {
|
||||||
|
return "never"
|
||||||
|
}
|
||||||
|
return timeSince(*t)
|
||||||
|
}
|
||||||
|
func timeSince(t time.Time) string {
|
||||||
|
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 unescapeHTML(s string) template.HTML {
|
||||||
|
return template.HTML(s)
|
||||||
|
}
|
||||||
|
func unescapeJS(s string) template.JS {
|
||||||
|
return template.JS(s)
|
||||||
|
}
|
||||||
|
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:]
|
||||||
|
}
|
||||||
346
html/html.go
|
|
@ -1,351 +1,9 @@
|
||||||
package html
|
package html
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"embed"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"math"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
|
||||||
"github.com/aarondl/opt/null"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var TemplatesByFilename = make(map[string]BuiltTemplate, 0)
|
func RenderOrError(w http.ResponseWriter, template_name string, content interface{}) {
|
||||||
|
templates.renderOrError(w, template_name, content)
|
||||||
type BuiltTemplate struct {
|
|
||||||
files []string
|
|
||||||
subdir string
|
|
||||||
// Nil if we are going to read templates off disk every time we render
|
|
||||||
// because we are in development mode.
|
|
||||||
template *template.Template
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bt *BuiltTemplate) executeTemplate(w io.Writer, data any) error {
|
|
||||||
if bt.template == nil {
|
|
||||||
name := path.Base(bt.files[0])
|
|
||||||
templ, err := parseFromDisk(bt.subdir, 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")
|
|
||||||
}
|
|
||||||
//log.Debug().Str("name", templ.Name()).Msg("Parsed template")
|
|
||||||
return templ.ExecuteTemplate(w, name, data)
|
|
||||||
} else {
|
|
||||||
name := path.Base(bt.files[0])
|
|
||||||
return bt.template.ExecuteTemplate(w, name, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBuiltTemplate(embeddedFiles embed.FS, subdir string, files ...string) *BuiltTemplate {
|
|
||||||
files_on_disk := true
|
|
||||||
for _, f := range files {
|
|
||||||
_, err := os.Stat(f)
|
|
||||||
if err != nil {
|
|
||||||
files_on_disk = false
|
|
||||||
if !config.IsProductionEnvironment() {
|
|
||||||
log.Warn().Str("file", f).Msg("template file is not on disk")
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var result BuiltTemplate
|
|
||||||
if files_on_disk {
|
|
||||||
result = BuiltTemplate{
|
|
||||||
files: files,
|
|
||||||
subdir: subdir,
|
|
||||||
template: nil,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = BuiltTemplate{
|
|
||||||
files: files,
|
|
||||||
subdir: subdir,
|
|
||||||
template: parseEmbedded(embeddedFiles, subdir, files),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TemplatesByFilename[path.Base(files[0])] = result
|
|
||||||
return &result
|
|
||||||
}
|
|
||||||
|
|
||||||
func RenderOrError(w http.ResponseWriter, template *BuiltTemplate, context interface{}) {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
err := template.executeTemplate(buf, context)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Strs("files", template.files).Msg("Failed to render template")
|
|
||||||
RespondError(w, "Failed to render template", err, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buf.WriteTo(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 makeFuncMap() template.FuncMap {
|
|
||||||
funcMap := template.FuncMap{
|
|
||||||
"bigNumber": bigNumber,
|
|
||||||
"html": unescapeHTML,
|
|
||||||
"json": unescapeJS,
|
|
||||||
"GISStatement": gisStatement,
|
|
||||||
"latLngDisplay": latLngDisplay,
|
|
||||||
"publicReportID": publicReportID,
|
|
||||||
"timeAsRelativeDate": timeAsRelativeDate,
|
|
||||||
"timeDelta": timeDelta,
|
|
||||||
"timeElapsed": timeElapsed,
|
|
||||||
"timeInterval": timeInterval,
|
|
||||||
"timeSince": timeSince,
|
|
||||||
"timeSincePtr": timeSincePtr,
|
|
||||||
"uuidShort": uuidShort,
|
|
||||||
}
|
|
||||||
return funcMap
|
|
||||||
}
|
|
||||||
func addSVGTemplates(fsys fs.FS, templ *template.Template) error {
|
|
||||||
svgs, err := fs.ReadDir(fsys, ".")
|
|
||||||
if err != nil {
|
|
||||||
log.Warn().Msg("Failed to read svg directory")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, svg := range svgs {
|
|
||||||
content, err := fs.ReadFile(fsys, svg.Name())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to read svg '%s' from embedded filesystem: %w", svg, err)
|
|
||||||
}
|
|
||||||
svg_name := svg.Name()
|
|
||||||
svg_template := fmt.Sprintf("{{define \"%s\"}}%s{{end}}", svg_name, string(content))
|
|
||||||
svg_t, err := template.New(svg_name).Parse(svg_template)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to parse svg '%s' from embedded filesystem: %v", svg, err)
|
|
||||||
}
|
|
||||||
_, err = templ.AddParseTree(svg_t.Name(), svg_t.Tree)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to add svg '%s' to embedded template: %v", svg, err)
|
|
||||||
}
|
|
||||||
//log.Debug().Str("name", svg_name).Msg("add svg template")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func parseEmbedded(embeddedFiles embed.FS, subdir string, files []string) *template.Template {
|
|
||||||
funcMap := makeFuncMap()
|
|
||||||
// Remap the file names to embedded paths
|
|
||||||
embeddedFilePaths := make([]string, 0)
|
|
||||||
for _, f := range files {
|
|
||||||
embeddedFilePaths = append(embeddedFilePaths, strings.TrimPrefix(f, subdir))
|
|
||||||
}
|
|
||||||
name := path.Base(embeddedFilePaths[0])
|
|
||||||
log.Debug().Str("name", name).Strs("paths", embeddedFilePaths).Msg("Parsing embedded template")
|
|
||||||
t, err := template.New(name).Funcs(funcMap).ParseFS(embeddedFiles, embeddedFilePaths...)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("Failed to parse embedded template %s: %v", name, err))
|
|
||||||
}
|
|
||||||
svg_fs, err := fs.Sub(embeddedFiles, "template/svg")
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("Failed to read static/svg: %v", err))
|
|
||||||
}
|
|
||||||
err = addSVGTemplates(svg_fs, t)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("Failed to add SVG templates: %v", err))
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseFromDisk(subdir string, files []string) (*template.Template, error) {
|
|
||||||
funcMap := makeFuncMap()
|
|
||||||
name := path.Base(files[0])
|
|
||||||
//log.Debug().Str("name", name).Strs("files", files).Msg("parsing from disk")
|
|
||||||
templ, err := template.New(name).Funcs(funcMap).ParseFiles(files...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to parse %s: %w", files, err)
|
|
||||||
}
|
|
||||||
fsys := os.DirFS(subdir + "/template/svg")
|
|
||||||
err = addSVGTemplates(fsys, templ)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to add SVGs from disk: %w", err)
|
|
||||||
}
|
|
||||||
return templ, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func publicReportID(s string) string {
|
|
||||||
if len(s) != 12 {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return s[0:4] + "-" + s[4:8] + "-" + s[8:12]
|
|
||||||
}
|
|
||||||
|
|
||||||
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 timeSincePtr(t *time.Time) string {
|
|
||||||
if t == nil {
|
|
||||||
return "never"
|
|
||||||
}
|
|
||||||
return timeSince(*t)
|
|
||||||
}
|
|
||||||
func timeSince(t time.Time) string {
|
|
||||||
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 unescapeHTML(s string) template.HTML {
|
|
||||||
return template.HTML(s)
|
|
||||||
}
|
|
||||||
func unescapeJS(s string) template.JS {
|
|
||||||
return template.JS(s)
|
|
||||||
}
|
|
||||||
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:]
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{{ template "content" . }}
|
{{ template "content" . }}
|
||||||
{{ template "footer" . }}
|
{{ template "rmo/component/footer.html" . }}
|
||||||
<script src="/static/vendor/js/bootstrap.bundle.min.js"></script>
|
<script src="/static/vendor/js/bootstrap.bundle.min.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ define "footer" }}
|
{{ define "rmo/component/footer.html" }}
|
||||||
<footer class="bg-dark text-white py-4">
|
<footer class="bg-dark text-white py-4">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
14
html/template/rmo/component/header-district.html
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{{ define "rmo/component/header-district.html" }}
|
||||||
|
<header
|
||||||
|
class="navbar navbar-expand-lg navbar-dark text-light bg-primary shadow-sm"
|
||||||
|
>
|
||||||
|
<div class="container">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<a class="navbar-brand d-flex align-items-center" href="/">
|
||||||
|
<img src="{{ .URLLogo }}" style="height: 48px;" alt="District logo" />
|
||||||
|
</a>
|
||||||
|
<h1 class="mb-0 ms-3">{{ .Name }}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
{{ end }}
|
||||||
17
html/template/rmo/component/header-rmo.html
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
{{ define "rmo/component/header-rmo.html" }}
|
||||||
|
<header
|
||||||
|
class="navbar navbar-expand-lg navbar-dark text-light bg-primary shadow-sm"
|
||||||
|
>
|
||||||
|
<div class="container">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<a class="navbar-brand d-flex align-items-center" href="/">
|
||||||
|
<img
|
||||||
|
class="banner"
|
||||||
|
src="/static/img/rmo/banner.jpg"
|
||||||
|
alt="Report Mosquitoes Online logo"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
{{ end }}
|
||||||
2
html/template/rmo/component/map-header.html
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
{{ define "rmo/component/map-header.html" }}
|
||||||
|
{{ end }}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ define "map" }}
|
{{ define "rmo/component/map.html" }}
|
||||||
<script src="https://api.mapbox.com/mapbox-gl-js/v3.17.0-beta.1/mapbox-gl.js"></script>
|
<script src="https://api.mapbox.com/mapbox-gl-js/v3.17.0-beta.1/mapbox-gl.js"></script>
|
||||||
<link
|
<link
|
||||||
href="https://api.mapbox.com/mapbox-gl-js/v3.17.0-beta.1/mapbox-gl.css"
|
href="https://api.mapbox.com/mapbox-gl-js/v3.17.0-beta.1/mapbox-gl.css"
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ define "photo-upload-header" }}
|
{{ define "rmo/compnonent/photo-upload-header.html" }}
|
||||||
<style>
|
<style>
|
||||||
.photo-upload-area {
|
.photo-upload-area {
|
||||||
border: 2px dashed #ccc;
|
border: 2px dashed #ccc;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ define "photo-upload" }}
|
{{ define "rmo/component/photo-upload.html" }}
|
||||||
<div class="photo-upload-area">
|
<div class="photo-upload-area">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
29
html/template/rmo/district-list.html
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{{ template "rmo/base.html" . }}
|
||||||
|
|
||||||
|
{{ define "title" }}Districts{{ end }}
|
||||||
|
{{ define "extraheader" }}
|
||||||
|
{{ end }}
|
||||||
|
{{ define "content" }}
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main>
|
||||||
|
<h1>District List</h1>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Logo</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>URL</th>
|
||||||
|
</tr>
|
||||||
|
{{ range .Districts }}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{{ .URLWebsite }}"><img src="{{ .URLLogo }}" /></a>
|
||||||
|
</td>
|
||||||
|
<td>{{ .Name }}</td>
|
||||||
|
<td><a href="{{ .URLRMO }}">{{ .URLRMO }}</a></td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</main>
|
||||||
|
{{ end }}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "rmo/base.html" . }}
|
||||||
|
|
||||||
{{ define "title" }}Main{{ end }}
|
{{ define "title" }}Main{{ end }}
|
||||||
{{ define "extraheader" }}
|
{{ define "extraheader" }}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "rmo/base.html" . }}
|
||||||
|
|
||||||
{{ define "title" }}Main{{ end }}
|
{{ define "title" }}Main{{ end }}
|
||||||
{{ define "extraheader" }}
|
{{ define "extraheader" }}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "rmo/base.html" . }}
|
||||||
|
|
||||||
{{ define "title" }}Main{{ end }}
|
{{ define "title" }}Main{{ end }}
|
||||||
{{ define "extraheader" }}
|
{{ define "extraheader" }}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "rmo/base.html" . }}
|
||||||
|
|
||||||
{{ define "title" }}Main{{ end }}
|
{{ define "title" }}Main{{ end }}
|
||||||
{{ define "extraheader" }}
|
{{ define "extraheader" }}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "rmo/base.html" . }}
|
||||||
|
|
||||||
{{ define "title" }}Main{{ end }}
|
{{ define "title" }}Main{{ end }}
|
||||||
{{ define "extraheader" }}
|
{{ define "extraheader" }}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "rmo/base.html" . }}
|
||||||
|
|
||||||
{{ define "title" }}Main{{ end }}
|
{{ define "title" }}Main{{ end }}
|
||||||
{{ define "extraheader" }}
|
{{ define "extraheader" }}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "rmo/base.html" . }}
|
||||||
|
|
||||||
{{ define "title" }}Report Nuisance{{ end }}
|
{{ define "title" }}Report Nuisance{{ end }}
|
||||||
{{ define "extraheader" }}
|
{{ define "extraheader" }}
|
||||||
|
|
@ -165,9 +165,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
{{ if (eq .District nil) }}
|
{{ if (eq .District nil) }}
|
||||||
{{ template "header-rmo" . }}
|
{{ template "rmo/component/header-rmo.html" . }}
|
||||||
{{ else }}
|
{{ else }}
|
||||||
{{ template "header-district" .District }}
|
{{ template "rmo/component/header-district.html" .District }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "rmo/base.html" . }}
|
||||||
{{ define "title" }}Privacy Policy{{ end }}
|
{{ define "title" }}Privacy Policy{{ end }}
|
||||||
{{ define "extraheader" }}
|
{{ define "extraheader" }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "rmo/base.html" . }}
|
||||||
|
|
||||||
{{ define "title" }}Quick Report Complete{{ end }}
|
{{ define "title" }}Quick Report Complete{{ end }}
|
||||||
{{ define "extraheader" }}
|
{{ define "extraheader" }}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "rmo/base.html" . }}
|
||||||
|
|
||||||
{{ define "title" }}Quick Report{{ end }}
|
{{ define "title" }}Quick Report{{ end }}
|
||||||
{{ define "extraheader" }}
|
{{ define "extraheader" }}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "rmo/base.html" . }}
|
||||||
|
|
||||||
{{ define "title" }}Notification Request Complete{{ end }}
|
{{ define "title" }}Notification Request Complete{{ end }}
|
||||||
{{ define "extraheader" }}
|
{{ define "extraheader" }}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "rmo/base.html" . }}
|
||||||
|
|
||||||
{{ define "title" }}Main{{ end }}
|
{{ define "title" }}Main{{ end }}
|
||||||
{{ define "extraheader" }}
|
{{ define "extraheader" }}
|
||||||
265
html/template/rmo/search.html
Normal file
|
|
@ -0,0 +1,265 @@
|
||||||
|
{{ template "rmo/base.html" . }}
|
||||||
|
|
||||||
|
{{ define "title" }}Status{{ 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 src="/static/js/geocode.js"></script>
|
||||||
|
<script src="/static/js/location.js"></script>
|
||||||
|
<script src="/static/js/map-multipoint.js"></script>
|
||||||
|
<script src="/static/js/report-table.js"></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;
|
||||||
|
}
|
||||||
|
.search-box {
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.map-container {
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
const MAPBOX_ACCESS_TOKEN = '{{.MapboxToken}}';
|
||||||
|
var markers = [];
|
||||||
|
// Because features come from tiled vector data, feature geometries may be split
|
||||||
|
// or duplicated across tile boundaries. As a result, features may appear
|
||||||
|
// multiple times in query results.
|
||||||
|
function getUniqueFeatures(features, comparatorProperty) {
|
||||||
|
const uniqueIds = new Set();
|
||||||
|
const uniqueFeatures = [];
|
||||||
|
for (const feature of features) {
|
||||||
|
const id = feature.properties[comparatorProperty];
|
||||||
|
if (!uniqueIds.has(id)) {
|
||||||
|
uniqueIds.add(id);
|
||||||
|
uniqueFeatures.push(feature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uniqueFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderReports(features) {
|
||||||
|
console.log("render reports", features);
|
||||||
|
|
||||||
|
const report_table = document.querySelector('report-table');
|
||||||
|
let reports = [];
|
||||||
|
for (const feature of features) {
|
||||||
|
reports.push({
|
||||||
|
address: feature.properties.address,
|
||||||
|
created: feature.properties.created,
|
||||||
|
id: feature.properties.public_id,
|
||||||
|
status: feature.properties.status,
|
||||||
|
type: feature.properties.table_name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
report_table.reports = reports;
|
||||||
|
}
|
||||||
|
function onLoad() {
|
||||||
|
const map = document.querySelector("map-multipoint");
|
||||||
|
map.addEventListener("load", (event) => {
|
||||||
|
map.addSource('tegola-mosquito', {
|
||||||
|
'type': 'vector',
|
||||||
|
'tiles': [
|
||||||
|
'{{.URLTegola}}maps/mosquito/{z}/{x}/{y}'
|
||||||
|
]
|
||||||
|
});
|
||||||
|
map.addLayer({
|
||||||
|
'id': 'mosquito',
|
||||||
|
'source': 'tegola-mosquito',
|
||||||
|
'source-layer': 'report_location',
|
||||||
|
'type': 'circle',
|
||||||
|
'paint': {
|
||||||
|
'circle-color': '#4264fb',
|
||||||
|
'circle-radius': 7,
|
||||||
|
'circle-stroke-width': 2,
|
||||||
|
'circle-stroke-color': '#ffffff'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
map.on('render', () => {
|
||||||
|
const features = map.queryRenderedFeatures({target: {layerId: 'mosquito'}});
|
||||||
|
|
||||||
|
if (features) {
|
||||||
|
const uniqueFeatures = getUniqueFeatures(features, 'public_id');
|
||||||
|
// Populate features for the listing overlay.
|
||||||
|
renderReports(uniqueFeatures);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
getGeolocation({
|
||||||
|
enableHighAccuracy: true,
|
||||||
|
timeout: 10000,
|
||||||
|
maximumAge: 0
|
||||||
|
}).then(position => {
|
||||||
|
map.jumpTo({
|
||||||
|
center: {
|
||||||
|
lng: position.coords.longitude,
|
||||||
|
lat: position.coords.latitude,
|
||||||
|
},
|
||||||
|
zoom: 14,
|
||||||
|
});
|
||||||
|
}).catch(error => {
|
||||||
|
console.log("location error", error);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', onLoad);
|
||||||
|
</script>
|
||||||
|
{{ end }}
|
||||||
|
{{ define "content" }}
|
||||||
|
<div class="container my-4">
|
||||||
|
<!-- Search Box -->
|
||||||
|
<div class="card search-box mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<form class="row g-3 align-items-center">
|
||||||
|
<div class="col-md-9">
|
||||||
|
<label for="addressSearch" class="visually-hidden"
|
||||||
|
>Search by address</label
|
||||||
|
>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text"><i class="bi bi-search"></i></span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control form-control-lg"
|
||||||
|
id="addressSearch"
|
||||||
|
placeholder="Enter an address, neighborhood, or zip code"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg w-100">
|
||||||
|
Search
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="mosquitoNuisance"
|
||||||
|
checked
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="mosquitoNuisance"
|
||||||
|
>Mosquito Nuisance</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="greenPool"
|
||||||
|
checked
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="greenPool">Green Pool</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="quickReport"
|
||||||
|
checked
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="quickReport"
|
||||||
|
>Quick Report</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Map Section -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header bg-info text-white">
|
||||||
|
<h5 class="mb-0"><i class="bi bi-pin-map-fill me-2"></i>Reports Map</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<map-multipoint
|
||||||
|
api-key="{{ .MapboxToken }}"
|
||||||
|
latitude="36.3"
|
||||||
|
longitude="-119.2"
|
||||||
|
tegola="{{ .URLTegola }}"
|
||||||
|
zoom="9"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Results Section -->
|
||||||
|
<div class="card">
|
||||||
|
<div
|
||||||
|
class="card-header bg-primary text-white d-flex justify-content-between align-items-center"
|
||||||
|
>
|
||||||
|
<h5 class="mb-0"><i class="bi bi-list-ol me-2"></i>Reports Near You</h5>
|
||||||
|
<span class="badge bg-light text-dark">15 Reports Found</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<report-table />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--
|
||||||
|
<div class="card-footer">
|
||||||
|
<nav aria-label="Page navigation">
|
||||||
|
<ul class="pagination justify-content-center mb-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="#">3</a></li>
|
||||||
|
<li class="page-item">
|
||||||
|
<a class="page-link" href="#">Next</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
--></div>
|
||||||
|
|
||||||
|
<!-- Create Report Button (Fixed at bottom on mobile) -->
|
||||||
|
<div
|
||||||
|
class="d-md-none position-fixed bottom-0 start-0 end-0 p-3"
|
||||||
|
style="z-index: 1030;"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="btn btn-primary btn-lg w-100"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#reportModal"
|
||||||
|
>
|
||||||
|
<i class="bi bi-plus-circle-fill me-2"></i>Create New Report
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Desktop Create Report Button -->
|
||||||
|
<div class="d-none d-md-block text-center mt-4">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary btn-lg"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#reportModal"
|
||||||
|
>
|
||||||
|
<i class="bi bi-plus-circle-fill me-2"></i>Create New Report
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "rmo/base.html" . }}
|
||||||
|
|
||||||
{{ define "title" }}Status of report {{ .Report.ID|publicReportID }}{{ end }}
|
{{ define "title" }}Status of report {{ .Report.ID|publicReportID }}{{ end }}
|
||||||
{{ define "extraheader" }}
|
{{ define "extraheader" }}
|
||||||
|
|
@ -57,9 +57,9 @@ document.addEventListener("DOMContentLoaded", onLoad);
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
{{ if (eq .District nil) }}
|
{{ if (eq .District nil) }}
|
||||||
{{ template "header-rmo" . }}
|
{{ template "rmo/component/header-rmo.html" . }}
|
||||||
{{ else }}
|
{{ else }}
|
||||||
{{ template "header-district" .District }}
|
{{ template "rmo/component/header-district.html" .District }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<div class="container my-4">
|
<div class="container my-4">
|
||||||
<!-- Report ID and Status Section -->
|
<!-- Report ID and Status Section -->
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "rmo/base.html" . }}
|
||||||
|
|
||||||
{{ define "title" }}Status{{ end }}
|
{{ define "title" }}Status{{ end }}
|
||||||
{{ define "extraheader" }}
|
{{ define "extraheader" }}
|
||||||
|
|
@ -168,9 +168,9 @@ document.addEventListener('DOMContentLoaded', onLoad);
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
{{ if (eq .District nil) }}
|
{{ if (eq .District nil) }}
|
||||||
{{ template "header-rmo" . }}
|
{{ template "rmo/component/header-rmo.html" . }}
|
||||||
{{ else }}
|
{{ else }}
|
||||||
{{ template "header-district" .District }}
|
{{ template "rmo/component/header-district.html" .District }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<div class="container my-4">
|
<div class="container my-4">
|
||||||
<!-- Search Box -->
|
<!-- Search Box -->
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "rmo/base.html" . }}
|
||||||
|
|
||||||
{{ define "title" }}Report Submission Complete{{ end }}
|
{{ define "title" }}Report Submission Complete{{ end }}
|
||||||
{{ define "extraheader" }}
|
{{ define "extraheader" }}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "rmo/base.html" . }}
|
||||||
{{ define "title" }}Privacy Policy{{ end }}
|
{{ define "title" }}Privacy Policy{{ end }}
|
||||||
{{ define "extraheader" }}
|
{{ define "extraheader" }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "rmo/base.html" . }}
|
||||||
|
|
||||||
{{ define "title" }}Report Standing Water{{ end }}
|
{{ define "title" }}Report Standing Water{{ end }}
|
||||||
{{ define "extraheader" }}
|
{{ define "extraheader" }}
|
||||||
|
|
@ -146,9 +146,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
{{ if (eq .District nil) }}
|
{{ if (eq .District nil) }}
|
||||||
{{ template "header-rmo" . }}
|
{{ template "rmo/component/header-rmo.html" . }}
|
||||||
{{ else }}
|
{{ else }}
|
||||||
{{ template "header-district" .District }}
|
{{ template "rmo/component/header-district.html" .District }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<main class="py-5">
|
<main class="py-5">
|
||||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
503
html/template/sync/admin.html
Normal file
|
|
@ -0,0 +1,503 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
42
html/template/sync/authenticated.html
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<!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/css/bootstrap.css" rel="stylesheet" />
|
||||||
|
<!-- Fontawesome Icons -->
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
|
||||||
|
/>
|
||||||
|
<!-- Bootstrap Icons -->
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"
|
||||||
|
/>
|
||||||
|
<!-- favicon -->
|
||||||
|
<link rel="icon" href="/static/favicon.ico" type="image/x-icon" />
|
||||||
|
{{ block "extraheader" . }}{{ end }}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{ template "sync/component/icons.html" }}
|
||||||
|
<div class="d-flex">
|
||||||
|
{{ if .User }}
|
||||||
|
{{ template "sync/component/sidebar.html" .User }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ template "content" . }}
|
||||||
|
<script src="/static/vendor/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
document
|
||||||
|
.getElementById("sidebarToggle")
|
||||||
|
.addEventListener("click", function () {
|
||||||
|
document.getElementById("sidebar").classList.toggle("collapsed");
|
||||||
|
document.getElementById("content").classList.toggle("expanded");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
27
html/template/sync/base.html
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<!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"
|
||||||
|
/>
|
||||||
|
<!-- favicon -->
|
||||||
|
<link rel="icon" href="/static/favicon-sync.ico" type="image/x-icon" />
|
||||||
|
{{ block "extraheader" . }}{{ end }}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{ template "content" . }}
|
||||||
|
<script src="/static/vendor/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
257
html/template/sync/cell.html
Normal file
|
|
@ -0,0 +1,257 @@
|
||||||
|
{{ template "sync/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|timeSincePtr }}</td>
|
||||||
|
<td>{{ .LastTreated|timeSincePtr }}</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|timeSincePtr }}</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">
|
||||||
|
<h2 class="section-header">Traps</h2>
|
||||||
|
{{ if gt (len .Traps) 0 }}
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Active</th>
|
||||||
|
<th>Comments</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{ range .Traps }}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="/trap/{{ .GlobalID }}"
|
||||||
|
>{{ .GlobalID|uuidShort }}</a
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td>{{ .Active }}</td>
|
||||||
|
<td>{{ .Comments }}</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ else }}
|
||||||
|
<p>No traps</p>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Treatments Section -->
|
||||||
|
<h2 class="section-header">Treatment History</h2>
|
||||||
|
<div class="card mb-4">
|
||||||
|
<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|timeSincePtr }}</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 }}
|
||||||
149
html/template/sync/component/header.html
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
{{ define "sync/component/header.html" }}
|
||||||
|
<header class="navbar navbar-expand-lg navbar-light bg-white shadow-sm">
|
||||||
|
<div class="container">
|
||||||
|
<!-- Logo -->
|
||||||
|
<a class="navbar-brand" href="/">
|
||||||
|
<img
|
||||||
|
src="/static/img/nidus-logo-no-lettering-64.png"
|
||||||
|
style="height: 48px; display: flex; align-items: center; justify-content: center; "
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
</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 | timeSincePtr }}</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="/signout"
|
||||||
|
>Sign out</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
{{ end }}
|
||||||
109
html/template/sync/component/icons.html
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
{{ define "sync/component/icons.html" }}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||||
|
<symbol id="bootstrap" viewBox="0 0 118 94">
|
||||||
|
<title>Bootstrap</title>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M24.509 0c-6.733 0-11.715 5.893-11.492 12.284.214 6.14-.064 14.092-2.066 20.577C8.943 39.365 5.547 43.485 0 44.014v5.972c5.547.529 8.943 4.649 10.951 11.153 2.002 6.485 2.28 14.437 2.066 20.577C12.794 88.106 17.776 94 24.51 94H93.5c6.733 0 11.714-5.893 11.491-12.284-.214-6.14.064-14.092 2.066-20.577 2.009-6.504 5.396-10.624 10.943-11.153v-5.972c-5.547-.529-8.934-4.649-10.943-11.153-2.002-6.484-2.28-14.437-2.066-20.577C105.214 5.894 100.233 0 93.5 0H24.508zM80 57.863C80 66.663 73.436 72 62.543 72H44a2 2 0 01-2-2V24a2 2 0 012-2h18.437c9.083 0 15.044 4.92 15.044 12.474 0 5.302-4.01 10.049-9.119 10.88v.277C75.317 46.394 80 51.21 80 57.863zM60.521 28.34H49.948v14.934h8.905c6.884 0 10.68-2.772 10.68-7.727 0-4.643-3.264-7.207-9.012-7.207zM49.948 49.2v16.458H60.91c7.167 0 10.964-2.876 10.964-8.281 0-5.406-3.903-8.178-11.425-8.178H49.948z"
|
||||||
|
></path>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="home" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4.5a.5.5 0 0 0 .5-.5v-4h2v4a.5.5 0 0 0 .5.5H14a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146zM2.5 14V7.707l5.5-5.5 5.5 5.5V14H10v-4a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5v4H2.5z"
|
||||||
|
/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="speedometer2" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M8 4a.5.5 0 0 1 .5.5V6a.5.5 0 0 1-1 0V4.5A.5.5 0 0 1 8 4zM3.732 5.732a.5.5 0 0 1 .707 0l.915.914a.5.5 0 1 1-.708.708l-.914-.915a.5.5 0 0 1 0-.707zM2 10a.5.5 0 0 1 .5-.5h1.586a.5.5 0 0 1 0 1H2.5A.5.5 0 0 1 2 10zm9.5 0a.5.5 0 0 1 .5-.5h1.5a.5.5 0 0 1 0 1H12a.5.5 0 0 1-.5-.5zm.754-4.246a.389.389 0 0 0-.527-.02L7.547 9.31a.91.91 0 1 0 1.302 1.258l3.434-4.297a.389.389 0 0 0-.029-.518z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M0 10a8 8 0 1 1 15.547 2.661c-.442 1.253-1.845 1.602-2.932 1.25C11.309 13.488 9.475 13 8 13c-1.474 0-3.31.488-4.615.911-1.087.352-2.49.003-2.932-1.25A7.988 7.988 0 0 1 0 10zm8-7a7 7 0 0 0-6.603 9.329c.203.575.923.876 1.68.63C4.397 12.533 6.358 12 8 12s3.604.532 4.923.96c.757.245 1.477-.056 1.68-.631A7 7 0 0 0 8 3z"
|
||||||
|
/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="table" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm15 2h-4v3h4V4zm0 4h-4v3h4V8zm0 4h-4v3h3a1 1 0 0 0 1-1v-2zm-5 3v-3H6v3h4zm-5 0v-3H1v2a1 1 0 0 0 1 1h3zm-4-4h4V8H1v3zm0-4h4V4H1v3zm5-3v3h4V4H6zm4 4H6v3h4V8z"
|
||||||
|
/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="people-circle" viewBox="0 0 16 16">
|
||||||
|
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z" />
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"
|
||||||
|
/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="grid" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M1 2.5A1.5 1.5 0 0 1 2.5 1h3A1.5 1.5 0 0 1 7 2.5v3A1.5 1.5 0 0 1 5.5 7h-3A1.5 1.5 0 0 1 1 5.5v-3zM2.5 2a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zm6.5.5A1.5 1.5 0 0 1 10.5 1h3A1.5 1.5 0 0 1 15 2.5v3A1.5 1.5 0 0 1 13.5 7h-3A1.5 1.5 0 0 1 9 5.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zM1 10.5A1.5 1.5 0 0 1 2.5 9h3A1.5 1.5 0 0 1 7 10.5v3A1.5 1.5 0 0 1 5.5 15h-3A1.5 1.5 0 0 1 1 13.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3zm6.5.5A1.5 1.5 0 0 1 10.5 9h3a1.5 1.5 0 0 1 1.5 1.5v3a1.5 1.5 0 0 1-1.5 1.5h-3A1.5 1.5 0 0 1 9 13.5v-3zm1.5-.5a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z"
|
||||||
|
/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="collection" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M2.5 3.5a.5.5 0 0 1 0-1h11a.5.5 0 0 1 0 1h-11zm2-2a.5.5 0 0 1 0-1h7a.5.5 0 0 1 0 1h-7zM0 13a1.5 1.5 0 0 0 1.5 1.5h13A1.5 1.5 0 0 0 16 13V6a1.5 1.5 0 0 0-1.5-1.5h-13A1.5 1.5 0 0 0 0 6v7zm1.5.5A.5.5 0 0 1 1 13V6a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-.5.5h-13z"
|
||||||
|
/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="calendar3" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M14 0H2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zM1 3.857C1 3.384 1.448 3 2 3h12c.552 0 1 .384 1 .857v10.286c0 .473-.448.857-1 .857H2c-.552 0-1-.384-1-.857V3.857z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M6.5 7a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"
|
||||||
|
/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="chat-quote-fill" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M16 8c0 3.866-3.582 7-8 7a9.06 9.06 0 0 1-2.347-.306c-.584.296-1.925.864-4.181 1.234-.2.032-.352-.176-.273-.362.354-.836.674-1.95.77-2.966C.744 11.37 0 9.76 0 8c0-3.866 3.582-7 8-7s8 3.134 8 7zM7.194 6.766a1.688 1.688 0 0 0-.227-.272 1.467 1.467 0 0 0-.469-.324l-.008-.004A1.785 1.785 0 0 0 5.734 6C4.776 6 4 6.746 4 7.667c0 .92.776 1.666 1.734 1.666.343 0 .662-.095.931-.26-.137.389-.39.804-.81 1.22a.405.405 0 0 0 .011.59c.173.16.447.155.614-.01 1.334-1.329 1.37-2.758.941-3.706a2.461 2.461 0 0 0-.227-.4zM11 9.073c-.136.389-.39.804-.81 1.22a.405.405 0 0 0 .012.59c.172.16.446.155.613-.01 1.334-1.329 1.37-2.758.942-3.706a2.466 2.466 0 0 0-.228-.4 1.686 1.686 0 0 0-.227-.273 1.466 1.466 0 0 0-.469-.324l-.008-.004A1.785 1.785 0 0 0 10.07 6c-.957 0-1.734.746-1.734 1.667 0 .92.777 1.666 1.734 1.666.343 0 .662-.095.931-.26z"
|
||||||
|
/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="cpu-fill" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M6.5 6a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M5.5.5a.5.5 0 0 0-1 0V2A2.5 2.5 0 0 0 2 4.5H.5a.5.5 0 0 0 0 1H2v1H.5a.5.5 0 0 0 0 1H2v1H.5a.5.5 0 0 0 0 1H2v1H.5a.5.5 0 0 0 0 1H2A2.5 2.5 0 0 0 4.5 14v1.5a.5.5 0 0 0 1 0V14h1v1.5a.5.5 0 0 0 1 0V14h1v1.5a.5.5 0 0 0 1 0V14h1v1.5a.5.5 0 0 0 1 0V14a2.5 2.5 0 0 0 2.5-2.5h1.5a.5.5 0 0 0 0-1H14v-1h1.5a.5.5 0 0 0 0-1H14v-1h1.5a.5.5 0 0 0 0-1H14v-1h1.5a.5.5 0 0 0 0-1H14A2.5 2.5 0 0 0 11.5 2V.5a.5.5 0 0 0-1 0V2h-1V.5a.5.5 0 0 0-1 0V2h-1V.5a.5.5 0 0 0-1 0V2h-1V.5zm1 4.5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3A1.5 1.5 0 0 1 6.5 5z"
|
||||||
|
/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="gear-fill" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"
|
||||||
|
/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="speedometer" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M8 2a.5.5 0 0 1 .5.5V4a.5.5 0 0 1-1 0V2.5A.5.5 0 0 1 8 2zM3.732 3.732a.5.5 0 0 1 .707 0l.915.914a.5.5 0 1 1-.708.708l-.914-.915a.5.5 0 0 1 0-.707zM2 8a.5.5 0 0 1 .5-.5h1.586a.5.5 0 0 1 0 1H2.5A.5.5 0 0 1 2 8zm9.5 0a.5.5 0 0 1 .5-.5h1.5a.5.5 0 0 1 0 1H12a.5.5 0 0 1-.5-.5zm.754-4.246a.389.389 0 0 0-.527-.02L7.547 7.31A.91.91 0 1 0 8.85 8.569l3.434-4.297a.389.389 0 0 0-.029-.518z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M6.664 15.889A8 8 0 1 1 9.336.11a8 8 0 0 1-2.672 15.78zm-4.665-4.283A11.945 11.945 0 0 1 8 10c2.186 0 4.236.585 6.001 1.606a7 7 0 1 0-12.002 0z"
|
||||||
|
/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="toggles2" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M9.465 10H12a2 2 0 1 1 0 4H9.465c.34-.588.535-1.271.535-2 0-.729-.195-1.412-.535-2z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M6 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm0 1a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm.535-10a3.975 3.975 0 0 1-.409-1H4a1 1 0 0 1 0-2h2.126c.091-.355.23-.69.41-1H4a2 2 0 1 0 0 4h2.535z"
|
||||||
|
/>
|
||||||
|
<path d="M14 4a4 4 0 1 1-8 0 4 4 0 0 1 8 0z" />
|
||||||
|
</symbol>
|
||||||
|
<symbol id="tools" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M1 0L0 1l2.2 3.081a1 1 0 0 0 .815.419h.07a1 1 0 0 1 .708.293l2.675 2.675-2.617 2.654A3.003 3.003 0 0 0 0 13a3 3 0 1 0 5.878-.851l2.654-2.617.968.968-.305.914a1 1 0 0 0 .242 1.023l3.356 3.356a1 1 0 0 0 1.414 0l1.586-1.586a1 1 0 0 0 0-1.414l-3.356-3.356a1 1 0 0 0-1.023-.242L10.5 9.5l-.96-.96 2.68-2.643A3.005 3.005 0 0 0 16 3c0-.269-.035-.53-.102-.777l-2.14 2.141L12 4l-.364-1.757L13.777.102a3 3 0 0 0-3.675 3.68L7.462 6.46 4.793 3.793a1 1 0 0 1-.293-.707v-.071a1 1 0 0 0-.419-.814L1 0zm9.646 10.646a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708zM3 11l.471.242.529.026.287.445.445.287.026.529L5 13l-.242.471-.026.529-.445.287-.287.445-.529.026L3 15l-.471-.242L2 14.732l-.287-.445L1.268 14l-.026-.529L1 13l.242-.471.026-.529.445-.287.287-.445.529-.026L3 11z"
|
||||||
|
/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="chevron-right" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"
|
||||||
|
/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="geo-fill" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M4 4a4 4 0 1 1 4.5 3.969V13.5a.5.5 0 0 1-1 0V7.97A4 4 0 0 1 4 3.999zm2.493 8.574a.5.5 0 0 1-.411.575c-.712.118-1.28.295-1.655.493a1.319 1.319 0 0 0-.37.265.301.301 0 0 0-.057.09V14l.002.008a.147.147 0 0 0 .016.033.617.617 0 0 0 .145.15c.165.13.435.27.813.395.751.25 1.82.414 3.024.414s2.273-.163 3.024-.414c.378-.126.648-.265.813-.395a.619.619 0 0 0 .146-.15.148.148 0 0 0 .015-.033L12 14v-.004a.301.301 0 0 0-.057-.09 1.318 1.318 0 0 0-.37-.264c-.376-.198-.943-.375-1.655-.493a.5.5 0 1 1 .164-.986c.77.127 1.452.328 1.957.594C12.5 13 13 13.4 13 14c0 .426-.26.752-.544.977-.29.228-.68.413-1.116.558-.878.293-2.059.465-3.34.465-1.281 0-2.462-.172-3.34-.465-.436-.145-.826-.33-1.116-.558C3.26 14.752 3 14.426 3 14c0-.599.5-1 .961-1.243.505-.266 1.187-.467 1.957-.594a.5.5 0 0 1 .575.411z"
|
||||||
|
/>
|
||||||
|
</symbol>
|
||||||
|
</svg>
|
||||||
|
{{ end }}
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
{{define "map"}}
|
{{ define "sync/component/map.html" }}
|
||||||
<script src='https://api.mapbox.com/mapbox-gl-js/v3.17.0-beta.1/mapbox-gl.js'></script>
|
<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' />
|
<link
|
||||||
|
href="https://api.mapbox.com/mapbox-gl-js/v3.17.0-beta.1/mapbox-gl.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
<script>
|
<script>
|
||||||
const geojson = JSON.parse({{.GeoJSON}})
|
const geojson = JSON.parse({{.GeoJSON}})
|
||||||
function addMarkers(map, markers) {
|
function addMarkers(map, markers) {
|
||||||
|
|
@ -59,26 +62,26 @@ function onLoad() {
|
||||||
}
|
}
|
||||||
window.addEventListener("load", onLoad);
|
window.addEventListener("load", onLoad);
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.map-container {
|
.map-container {
|
||||||
background-color: #e9ecef;
|
background-color: #e9ecef;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||||
height: 500px;
|
height: 500px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
#map {
|
#map {
|
||||||
height: 500px;
|
height: 500px;
|
||||||
width:100%;
|
width: 100%;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
#map img {
|
#map img {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
min-width: 0px;
|
min-width: 0px;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{{end}}
|
{{ end }}
|
||||||
57
html/template/sync/component/sidebar.html
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
{{ define "sync/component/sidebar.html" }}
|
||||||
|
<div id="sidebar">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<div class="logo-container">
|
||||||
|
<img class="logo" src="/static/img/nidus-logo-256-transparent.png" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button id="sidebarToggle" class="btn btn-sm p-0">
|
||||||
|
<i id="sidebarToggleIcon" class="bi bi-chevron-left"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<ul class="sidebar-menu">
|
||||||
|
<li>
|
||||||
|
<a href="/" data-toggle="tooltip" data-placement="right" title="Home">
|
||||||
|
<div class="menu-icon"><i class="bi bi-house"></i></div>
|
||||||
|
<span class="menu-text ms-2">Home</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#">
|
||||||
|
<div class="menu-icon"><i class="bi bi-graph-up"></i></div>
|
||||||
|
<span class="menu-text ms-2">Analytics</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#">
|
||||||
|
<div class="menu-icon"><i class="bi bi-file-earmark"></i></div>
|
||||||
|
<span class="menu-text ms-2">Reports</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#">
|
||||||
|
<div class="menu-icon"><i class="bi bi-people"></i></div>
|
||||||
|
<span class="menu-text ms-2">Users</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#">
|
||||||
|
<div class="menu-icon"><i class="bi bi-chat-dots"></i></div>
|
||||||
|
<span class="menu-text ms-2">Messages</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#">
|
||||||
|
<div class="menu-icon"><i class="bi bi-gear"></i></div>
|
||||||
|
<span class="menu-text ms-2">Settings</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#">
|
||||||
|
<div class="menu-icon"><i class="bi bi-question-circle"></i></div>
|
||||||
|
<span class="menu-text ms-2">Help</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
246
html/template/sync/dashboard.html
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
{{ template "sync/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 src="/static/js/map-aggregate.js"></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);
|
||||||
|
}
|
||||||
|
.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>
|
||||||
|
<script>
|
||||||
|
function onLoad() {
|
||||||
|
const map = document.querySelector("map-aggregate");
|
||||||
|
map.addEventListener("cell-click", (event) => {
|
||||||
|
window.location.href = "/cell/" + event.detail.cell;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
window.addEventListener("load", onLoad);
|
||||||
|
</script>
|
||||||
|
{{ end }}
|
||||||
|
{{ define "content" }}
|
||||||
|
<div class="container dashboard-container">
|
||||||
|
<!-- Dashboard Header -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h1>{{ .User.Organization.Name }} 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 | timeSincePtr }}</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-success bg-opacity-10 text-white">
|
||||||
|
<i class="fas fa-clock"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="card-title">Last Data Refresh</h5>
|
||||||
|
<p class="metric-value">{{ .LastSync | timeSincePtr }}</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-white">
|
||||||
|
<i class="fas fa-ticket-alt"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="card-title">Service Requests</h5>
|
||||||
|
{{ if .IsSyncOngoing }}
|
||||||
|
<p class="metric-value">
|
||||||
|
{{ .CountServiceRequests | bigNumber }}...?
|
||||||
|
</p>
|
||||||
|
{{ else }}
|
||||||
|
<p class="metric-value">
|
||||||
|
{{ .CountServiceRequests | bigNumber }}
|
||||||
|
</p>
|
||||||
|
{{ end }}
|
||||||
|
<!--<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-white">
|
||||||
|
<i class="fas fa-bug"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="card-title">Mosquito Sources</h5>
|
||||||
|
{{ if .IsSyncOngoing }}
|
||||||
|
<p class="metric-value">
|
||||||
|
{{ .CountMosquitoSources | bigNumber }}..?
|
||||||
|
</p>
|
||||||
|
{{ else }}
|
||||||
|
<p class="metric-value">
|
||||||
|
{{ .CountMosquitoSources | bigNumber }}
|
||||||
|
</p>
|
||||||
|
{{ end }}
|
||||||
|
<!-- <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-info bg-opacity-10 text-white">
|
||||||
|
<i class="fas fa-clipboard-check"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="card-title">Traps</h5>
|
||||||
|
{{ if .IsSyncOngoing }}
|
||||||
|
<p class="metric-value">{{ .CountTraps | bigNumber }}...?</p>
|
||||||
|
{{ else }}
|
||||||
|
<p class="metric-value">{{ .CountTraps | bigNumber }}</p>
|
||||||
|
{{ end }}
|
||||||
|
<!-- <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">
|
||||||
|
<map-aggregate
|
||||||
|
api-key="{{ .MapData.MapboxToken }}"
|
||||||
|
latitude="36.3"
|
||||||
|
longitude="-119.2"
|
||||||
|
organization-id="{{ .User.Organization.ID }}"
|
||||||
|
tegola="{{ .Config.URLTegola }}"
|
||||||
|
zoom="9"
|
||||||
|
/>
|
||||||
|
</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 | timeSincePtr }}</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 }}
|
||||||
235
html/template/sync/data-entry-bad.html
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
243
html/template/sync/data-entry-good.html
Normal file
|
|
@ -0,0 +1,243 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
131
html/template/sync/data-entry.html
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
330
html/template/sync/dispatch-results.html
Normal file
|
|
@ -0,0 +1,330 @@
|
||||||
|
{{ template "sync/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>
|
||||||
|
|
||||||
|
<!-- 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 }}
|
||||||
308
html/template/sync/dispatch.html
Normal file
|
|
@ -0,0 +1,308 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
{{template "base.html" .}}
|
{{ template "sync/base.html" . }}
|
||||||
|
|
||||||
{{define "title"}}Dash{{end}}
|
{{ define "title" }}Dash{{ end }}
|
||||||
{{define "extraheader"}}
|
{{ define "extraheader" }}
|
||||||
<script src='https://api.mapbox.com/mapbox-gl-js/v3.17.0-beta.1/mapbox-gl.js'></script>
|
<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' />
|
<link
|
||||||
<script src="/static/js/address-display.js"></script>
|
href="https://api.mapbox.com/mapbox-gl-js/v3.17.0-beta.1/mapbox-gl.css"
|
||||||
<script src="/static/js/address-suggestion.js"></script>
|
rel="stylesheet"
|
||||||
<script src="/static/js/geocode.js"></script>
|
/>
|
||||||
<script src="/static/js/location.js"></script>
|
<script src="/static/js/address-display.js"></script>
|
||||||
<script src="/static/js/map.js"></script>
|
<script src="/static/js/address-suggestion.js"></script>
|
||||||
|
<script src="/static/js/geocode.js"></script>
|
||||||
|
<script src="/static/js/location.js"></script>
|
||||||
|
<script src="/static/js/map.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const MAPBOX_ACCESS_TOKEN = '{{.MapboxToken}}';
|
const MAPBOX_ACCESS_TOKEN = '{{.MapboxToken}}';
|
||||||
function setDistrictColors(map) {
|
function setDistrictColors(map) {
|
||||||
|
|
@ -123,7 +126,7 @@ function onLoad() {
|
||||||
});
|
});
|
||||||
console.log("Map post-load done.");
|
console.log("Map post-load done.");
|
||||||
});
|
});
|
||||||
|
|
||||||
map.addControl(new mapboxgl.NavigationControl());
|
map.addControl(new mapboxgl.NavigationControl());
|
||||||
console.log("Map init done.");
|
console.log("Map init done.");
|
||||||
|
|
||||||
|
|
@ -153,132 +156,132 @@ function onLoad() {
|
||||||
}
|
}
|
||||||
document.addEventListener("DOMContentLoaded", onLoad);
|
document.addEventListener("DOMContentLoaded", onLoad);
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
}
|
}
|
||||||
.detail-label {
|
.detail-label {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
.dashboard-container {
|
.dashboard-container {
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
}
|
}
|
||||||
.stats-card {
|
.stats-card {
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||||
transition: transform 0.2s;
|
transition: transform 0.2s;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.stats-card:hover {
|
.stats-card:hover {
|
||||||
transform: translateY(-5px);
|
transform: translateY(-5px);
|
||||||
}
|
}
|
||||||
.map-container {
|
.map-container {
|
||||||
background-color: #e9ecef;
|
background-color: #e9ecef;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||||
height: 500px;
|
height: 500px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
#map {
|
#map {
|
||||||
height: 500px;
|
height: 500px;
|
||||||
width:100%;
|
width: 100%;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
#map img {
|
#map img {
|
||||||
max-width: none;
|
max-width: none;
|
||||||
min-width: 0px;
|
min-width: 0px;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
.section-title {
|
.section-title {
|
||||||
margin: 30px 0 20px;
|
margin: 30px 0 20px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
border-bottom: 1px solid #dee2e6;
|
border-bottom: 1px solid #dee2e6;
|
||||||
}
|
}
|
||||||
.last-refreshed {
|
.last-refreshed {
|
||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
}
|
}
|
||||||
.logo-placeholder {
|
.logo-placeholder {
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
background-color: #e9ecef;
|
background-color: #e9ecef;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
.metric-icon {
|
.metric-icon {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
.metric-value {
|
.metric-value {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.syncing {
|
.syncing {
|
||||||
color: #28a745;
|
color: #28a745;
|
||||||
animation: fa-spin 2s linear infinite;
|
animation: fa-spin 2s linear infinite;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{{end}}
|
{{ end }}
|
||||||
{{define "content"}}
|
{{ define "content" }}
|
||||||
<div class="container my-4">
|
<div class="container my-4">
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<div class="mb-3 position-relative">
|
<div class="mb-3 position-relative">
|
||||||
<address-input
|
<address-input
|
||||||
placeholder="Start typing an address (min 3 characters)"
|
placeholder="Start typing an address (min 3 characters)"
|
||||||
api-key="{{ .MapboxToken }}">
|
api-key="{{ .MapboxToken }}"
|
||||||
</address-input>
|
>
|
||||||
|
</address-input>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3">
|
<div class="card-body p-0">
|
||||||
</div>
|
<div class="map-container" id="map-container">
|
||||||
</div>
|
<div id="map"></div>
|
||||||
<div class="card-body p-0">
|
|
||||||
<div class="map-container" id="map-container">
|
|
||||||
<div id="map"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-6">
|
|
||||||
<div class="card-body p-0">
|
|
||||||
<address-display></address-display>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="row">
|
||||||
<div class="district-display" class="mt-4 ">
|
<div class="col-6">
|
||||||
<h5 class="mb-3">District Details</h5>
|
<div class="card-body p-0">
|
||||||
<div class="location-card p-3 mb-3">
|
<address-display></address-display>
|
||||||
<div class="district-detail">
|
</div>
|
||||||
<div class="detail-label">Agency</div>
|
</div>
|
||||||
<div class="detail-value" id="district-agency">-</div>
|
<div class="col-6">
|
||||||
</div>
|
<div class="district-display" class="mt-4 ">
|
||||||
<div class="district-detail">
|
<h5 class="mb-3">District Details</h5>
|
||||||
<div class="detail-label">Manager</div>
|
<div class="location-card p-3 mb-3">
|
||||||
<div class="agency detail-value" id="district-manager">-</div>
|
<div class="district-detail">
|
||||||
</div>
|
<div class="detail-label">Agency</div>
|
||||||
<div class="district-detail">
|
<div class="detail-value" id="district-agency">-</div>
|
||||||
<div class="detail-label">Phone</div>
|
</div>
|
||||||
<div class="agency detail-value" id="district-phone">-</div>
|
<div class="district-detail">
|
||||||
</div>
|
<div class="detail-label">Manager</div>
|
||||||
<div class="district-detail">
|
<div class="agency detail-value" id="district-manager">-</div>
|
||||||
<div class="detail-label">Website</div>
|
</div>
|
||||||
<div class="agency detail-value" id="district-website">-</div>
|
<div class="district-detail">
|
||||||
|
<div class="detail-label">Phone</div>
|
||||||
|
<div class="agency detail-value" id="district-phone">-</div>
|
||||||
|
</div>
|
||||||
|
<div class="district-detail">
|
||||||
|
<div class="detail-label">Website</div>
|
||||||
|
<div class="agency detail-value" id="district-website">-</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{{ end }}
|
||||||
{{end}}
|
|
||||||
7
html/template/sync/empty-auth.html
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{{ template "sync/authenticated.html" . }}
|
||||||
|
|
||||||
|
{{ define "title" }}Dash{{ end }}
|
||||||
|
{{ define "extraheader" }}
|
||||||
|
{{ end }}
|
||||||
|
{{ define "content" }}
|
||||||
|
{{ end }}
|
||||||
7
html/template/sync/empty.html
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{{ template "sync/base.html" . }}
|
||||||
|
|
||||||
|
{{ define "title" }}Dash{{ end }}
|
||||||
|
{{ define "extraheader" }}
|
||||||
|
{{ end }}
|
||||||
|
{{ define "content" }}
|
||||||
|
{{ end }}
|
||||||
77
html/template/sync/layout-test.html
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
{{ template "sync/authenticated.html" . }}
|
||||||
|
{{ define "title" }}Layout Test{{ end }}
|
||||||
|
{{ define "extraheader" }}
|
||||||
|
<style>
|
||||||
|
.bd-example {
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
border-width: 1px;
|
||||||
|
border-top-left-radius: 0.25rem;
|
||||||
|
border-top-right-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{{ end }}
|
||||||
|
{{ define "content" }}
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div id="content">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="bd-example">
|
||||||
|
<button type="button" class="btn btn-primary">Primary</button>
|
||||||
|
<button type="button" class="btn btn-secondary">Secondary</button>
|
||||||
|
<button type="button" class="btn btn-success">Success</button>
|
||||||
|
<button type="button" class="btn btn-danger">Danger</button>
|
||||||
|
<button type="button" class="btn btn-warning">Warning</button>
|
||||||
|
<button type="button" class="btn btn-info">Info</button>
|
||||||
|
<button type="button" class="btn btn-light">Light</button>
|
||||||
|
<button type="button" class="btn btn-dark">Dark</button>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-link">Link</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Card 1</h5>
|
||||||
|
<p class="card-text">Some example content for the first card.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Card 2</h5>
|
||||||
|
<p class="card-text">Some example content for the second card.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="p-3 mb-3 bg-primary text-white">Primary</div>
|
||||||
|
<div class="p-3 mb-3 bg-primary-100 text-white">Primary-100</div>
|
||||||
|
<div class="p-3 mb-3 bg-primary-200 text-white">Primary-200</div>
|
||||||
|
<div class="p-3 mb-3 bg-primary-300 text-white">Primary-300</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="p-3 mb-3 bg-secondary text-white">Secondary</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="p-3 mb-3 bg-success text-white">Success</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="p-3 mb-3 bg-danger text-white">Danger</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="p-3 mb-3 bg-warning text-white">Warning</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="p-3 mb-3 bg-info text-white">Info</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
66
html/template/sync/mock-root.html
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
115
html/template/sync/oauth-prompt.html
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
{{ template "sync/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;
|
||||||
|
}
|
||||||
|
.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">
|
||||||
|
<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 }}
|
||||||
561
html/template/sync/privacy.html
Normal file
|
|
@ -0,0 +1,561 @@
|
||||||
|
{{ template "sync/base.html" . }}
|
||||||
|
{{ define "title" }}Privacy Policy{{ end }}
|
||||||
|
{{ define "extraheader" }}
|
||||||
|
{{ end }}
|
||||||
|
{{ define "content" }}
|
||||||
|
<div class="container">
|
||||||
|
<h1>Privacy Policy</h1>
|
||||||
|
<p>Last updated: January 20, 2026</p>
|
||||||
|
<p>
|
||||||
|
This Privacy Policy describes Our policies and procedures on the
|
||||||
|
collection, use and disclosure of Your information when You use the
|
||||||
|
Service and tells You about Your privacy rights and how the law protects
|
||||||
|
You.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We use Your Personal Data to provide and improve the Service. By using the
|
||||||
|
Service, You agree to the collection and use of information in accordance
|
||||||
|
with this Privacy Policy.
|
||||||
|
</p>
|
||||||
|
<h2>Interpretation and Definitions</h2>
|
||||||
|
<h3>Interpretation</h3>
|
||||||
|
<p>
|
||||||
|
The words whose initial letters are capitalized have meanings defined
|
||||||
|
under the following conditions. The following definitions shall have the
|
||||||
|
same meaning regardless of whether they appear in singular or in plural.
|
||||||
|
</p>
|
||||||
|
<h3>Definitions</h3>
|
||||||
|
<p>For the purposes of this Privacy Policy:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Account</strong> means a unique account created for You to
|
||||||
|
access our Service or parts of our Service.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Affiliate</strong> means an entity that controls, is
|
||||||
|
controlled by, or is under common control with a party, where
|
||||||
|
"control" means ownership of 50% or more of the shares,
|
||||||
|
equity interest or other securities entitled to vote for election of
|
||||||
|
directors or other managing authority.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Company</strong> (referred to as either "the
|
||||||
|
Company", "We", "Us" or "Our" in
|
||||||
|
this Privacy Policy) refers to {{ .Company }},
|
||||||
|
{{ .Address }}
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Cookies</strong> are small files that are placed on Your
|
||||||
|
computer, mobile device or any other device by a website, containing
|
||||||
|
the details of Your browsing history on that website among its many
|
||||||
|
uses.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p><strong>Country</strong> refers to: Arizona, United States</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Device</strong> means any device that can access the Service
|
||||||
|
such as a computer, a cell phone or a digital tablet.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Personal Data</strong> (or "Personal Information")
|
||||||
|
is any information that relates to an identified or identifiable
|
||||||
|
individual.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We use "Personal Data" and "Personal Information"
|
||||||
|
interchangeably unless a law uses a specific term.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p><strong>Service</strong> refers to the Website.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Service Provider</strong> means any natural or legal person
|
||||||
|
who processes the data on behalf of the Company. It refers to
|
||||||
|
third-party companies or individuals employed by the Company to
|
||||||
|
facilitate the Service, to provide the Service on behalf of the
|
||||||
|
Company, to perform services related to the Service or to assist the
|
||||||
|
Company in analyzing how the Service is used.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Usage Data</strong> refers to data collected automatically,
|
||||||
|
either generated by the use of the Service or from the Service
|
||||||
|
infrastructure itself (for example, the duration of a page visit).
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>Website</strong> refers to {{ .Site }}, accessible from
|
||||||
|
<a
|
||||||
|
href="{{ .URLSync }}"
|
||||||
|
rel="external nofollow noopener"
|
||||||
|
target="_blank"
|
||||||
|
>{{ .URLSync }}</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>You</strong> means the individual accessing or using the
|
||||||
|
Service, or the company, or other legal entity on behalf of which such
|
||||||
|
individual is accessing or using the Service, as applicable.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Collecting and Using Your Personal Data</h2>
|
||||||
|
<h3>Types of Data Collected</h3>
|
||||||
|
<h4>Personal Data</h4>
|
||||||
|
<p>
|
||||||
|
While using Our Service, We may ask You to provide Us with certain
|
||||||
|
personally identifiable information that can be used to contact or
|
||||||
|
identify You. Personally identifiable information may include, but is not
|
||||||
|
limited to:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Email address</li>
|
||||||
|
<li>First name and last name</li>
|
||||||
|
<li>Phone number</li>
|
||||||
|
<li>Address, State, Province, ZIP/Postal code, City</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Usage Data</h4>
|
||||||
|
<p>Usage Data is collected automatically when using the Service.</p>
|
||||||
|
<p>
|
||||||
|
Usage Data may include information such as Your Device's Internet Protocol
|
||||||
|
address (e.g. IP address), browser type, browser version, the pages of our
|
||||||
|
Service that You visit, the time and date of Your visit, the time spent on
|
||||||
|
those pages, unique device identifiers and other diagnostic data.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
When You access the Service by or through a mobile device, We may collect
|
||||||
|
certain information automatically, including, but not limited to, the type
|
||||||
|
of mobile device You use, Your mobile device's unique ID, the IP address
|
||||||
|
of Your mobile device, Your mobile operating system, the type of mobile
|
||||||
|
Internet browser You use, unique device identifiers and other diagnostic
|
||||||
|
data.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We may also collect information that Your browser sends whenever You visit
|
||||||
|
Our Service or when You access the Service by or through a mobile device.
|
||||||
|
</p>
|
||||||
|
<h4>Tracking Technologies and Cookies</h4>
|
||||||
|
<p>
|
||||||
|
We use Cookies and similar tracking technologies to track the activity on
|
||||||
|
Our Service and store certain information. Tracking technologies We use
|
||||||
|
include beacons, tags, and scripts to collect and track information and to
|
||||||
|
improve and analyze Our Service. The technologies We use may include:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>Cookies or Browser Cookies.</strong> A cookie is a small file
|
||||||
|
placed on Your Device. You can instruct Your browser to refuse all
|
||||||
|
Cookies or to indicate when a Cookie is being sent. However, if You do
|
||||||
|
not accept Cookies, You may not be able to use some parts of our
|
||||||
|
Service.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Cookies can be "Persistent" or "Session" Cookies.
|
||||||
|
Persistent Cookies remain on Your personal computer or mobile device when
|
||||||
|
You go offline, while Session Cookies are deleted as soon as You close
|
||||||
|
Your web browser.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Where required by law, we use non-essential cookies (such as analytics,
|
||||||
|
advertising, and remarketing cookies) only with Your consent. You can
|
||||||
|
withdraw or change Your consent at any time using Our cookie preferences
|
||||||
|
tool (if available) or through Your browser/device settings. Withdrawing
|
||||||
|
consent does not affect the lawfulness of processing based on consent
|
||||||
|
before its withdrawal.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We use both Session and Persistent Cookies for the purposes set out below:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p><strong>Necessary / Essential Cookies</strong></p>
|
||||||
|
<p>Type: Session Cookies</p>
|
||||||
|
<p>Administered by: Us</p>
|
||||||
|
<p>
|
||||||
|
Purpose: These Cookies are essential to provide You with services
|
||||||
|
available through the Website and to enable You to use some of its
|
||||||
|
features. They help to authenticate users and prevent fraudulent use
|
||||||
|
of user accounts. Without these Cookies, the services that You have
|
||||||
|
asked for cannot be provided, and We only use these Cookies to provide
|
||||||
|
You with those services.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p><strong>Functionality Cookies</strong></p>
|
||||||
|
<p>Type: Persistent Cookies</p>
|
||||||
|
<p>Administered by: Us</p>
|
||||||
|
<p>
|
||||||
|
Purpose: These Cookies allow Us to remember choices You make when You
|
||||||
|
use the Website, such as remembering your login details or language
|
||||||
|
preference. The purpose of these Cookies is to provide You with a more
|
||||||
|
personal experience and to avoid You having to re-enter your
|
||||||
|
preferences every time You use the Website.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
For more information about the cookies we use and your choices regarding
|
||||||
|
cookies, please visit our Cookies Policy or the Cookies section of Our
|
||||||
|
Privacy Policy.
|
||||||
|
</p>
|
||||||
|
<h3>Use of Your Personal Data</h3>
|
||||||
|
<p>The Company may use Personal Data for the following purposes:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>To provide and maintain our Service</strong>, including to
|
||||||
|
monitor the usage of our Service.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>To manage Your Account:</strong> to manage Your registration
|
||||||
|
as a user of the Service. The Personal Data You provide can give You
|
||||||
|
access to different functionalities of the Service that are available
|
||||||
|
to You as a registered user.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>For the performance of a contract:</strong> the development,
|
||||||
|
compliance and undertaking of the purchase contract for the products,
|
||||||
|
items or services You have purchased or of any other contract with Us
|
||||||
|
through the Service.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>To contact You:</strong> To contact You by email, telephone
|
||||||
|
calls, SMS, or other equivalent forms of electronic communication,
|
||||||
|
such as a mobile application's push notifications regarding updates or
|
||||||
|
informative communications related to the functionalities, products or
|
||||||
|
contracted services, including the security updates, when necessary or
|
||||||
|
reasonable for their implementation.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>To provide You</strong> with news, special offers, and general
|
||||||
|
information about other goods, services and events which We offer that
|
||||||
|
are similar to those that you have already purchased or inquired about
|
||||||
|
unless You have opted not to receive such information.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>To manage Your requests:</strong> To attend and manage Your
|
||||||
|
requests to Us.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>For business transfers:</strong> We may use Your Personal Data
|
||||||
|
to evaluate or conduct a merger, divestiture, restructuring,
|
||||||
|
reorganization, dissolution, or other sale or transfer of some or all
|
||||||
|
of Our assets, whether as a going concern or as part of bankruptcy,
|
||||||
|
liquidation, or similar proceeding, in which Personal Data held by Us
|
||||||
|
about our Service users is among the assets transferred.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
<strong>For other purposes</strong>: We may use Your information for
|
||||||
|
other purposes, such as data analysis, identifying usage trends,
|
||||||
|
determining the effectiveness of our promotional campaigns and to
|
||||||
|
evaluate and improve our Service, products, services, marketing and
|
||||||
|
your experience.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>We may share Your Personal Data in the following situations:</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<strong>With Service Providers:</strong> We may share Your Personal Data
|
||||||
|
with Service Providers to monitor and analyze the use of our Service, to
|
||||||
|
contact You.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>For business transfers:</strong> We may share or transfer Your
|
||||||
|
Personal Data in connection with, or during negotiations of, any merger,
|
||||||
|
sale of Company assets, financing, or acquisition of all or a portion of
|
||||||
|
Our business to another company.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>With Affiliates:</strong> We may share Your Personal Data with
|
||||||
|
Our affiliates, in which case we will require those affiliates to honor
|
||||||
|
this Privacy Policy. Affiliates include Our parent company and any other
|
||||||
|
subsidiaries, joint venture partners or other companies that We control
|
||||||
|
or that are under common control with Us.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>With business partners:</strong> We may share Your Personal Data
|
||||||
|
with Our business partners to offer You certain products, services or
|
||||||
|
promotions.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>With other users:</strong> If Our Service offers public areas,
|
||||||
|
when You share Personal Data or otherwise interact in the public areas
|
||||||
|
with other users, such information may be viewed by all users and may be
|
||||||
|
publicly distributed outside.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>With Your consent</strong>: We may disclose Your Personal Data
|
||||||
|
for any other purpose with Your consent.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Retention of Your Personal Data</h3>
|
||||||
|
<p>
|
||||||
|
The Company will retain Your Personal Data only for as long as is
|
||||||
|
necessary for the purposes set out in this Privacy Policy. We will retain
|
||||||
|
and use Your Personal Data to the extent necessary to comply with our
|
||||||
|
legal obligations (for example, if We are required to retain Your data to
|
||||||
|
comply with applicable laws), resolve disputes, and enforce our legal
|
||||||
|
agreements and policies.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Where possible, We apply shorter retention periods and/or reduce
|
||||||
|
identifiability by deleting, aggregating, or anonymizing data. Unless
|
||||||
|
otherwise stated, the retention periods below are maximum periods
|
||||||
|
("up to") and We may delete or anonymize data sooner when it is
|
||||||
|
no longer needed for the relevant purpose. We apply different retention
|
||||||
|
periods to different categories of Personal Data based on the purpose of
|
||||||
|
processing and legal obligations:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>Account Information</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
User Accounts: retained for the duration of your account
|
||||||
|
relationship plus up to 24 months after account closure to handle
|
||||||
|
any post-termination issues or resolve disputes.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Customer Support Data</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Support tickets and correspondence: up to 24 months from the date of
|
||||||
|
ticket closure to resolve follow-up inquiries, track service
|
||||||
|
quality, and defend against potential legal claims
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Chat transcripts: up to 24 months for quality assurance and staff
|
||||||
|
training purposes.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>Usage Data</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Website analytics data (cookies, IP addresses, device
|
||||||
|
identifiers): up to 24 months from the date of collection, which
|
||||||
|
allows us to analyze trends while respecting privacy principles.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
|
Server logs (IP addresses, access times): up to 24 months for
|
||||||
|
security monitoring and troubleshooting purposes.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Usage Data is retained in accordance with the retention periods described
|
||||||
|
above, and may be retained longer only where necessary for security, fraud
|
||||||
|
prevention, or legal compliance.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We may retain Personal Data beyond the periods stated above for different
|
||||||
|
reasons:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Legal obligation: We are required by law to retain specific data (e.g.,
|
||||||
|
financial records for tax authorities).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Legal claims: Data is necessary to establish, exercise, or defend legal
|
||||||
|
claims.
|
||||||
|
</li>
|
||||||
|
<li>Your explicit request: You ask Us to retain specific information.</li>
|
||||||
|
<li>
|
||||||
|
Technical limitations: Data exists in backup systems that are scheduled
|
||||||
|
for routine deletion.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
You may request information about how long We will retain Your Personal
|
||||||
|
Data by contacting Us.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
When retention periods expire, We securely delete or anonymize Personal
|
||||||
|
Data according to the following procedures:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Deletion: Personal Data is removed from Our systems and no longer
|
||||||
|
actively processed.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Backup retention: Residual copies may remain in encrypted backups for a
|
||||||
|
limited period consistent with our backup retention schedule and are not
|
||||||
|
restored except where necessary for security, disaster recovery, or
|
||||||
|
legal compliance.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Anonymization: In some cases, We convert Personal Data into anonymous
|
||||||
|
statistical data that cannot be linked back to You. This anonymized data
|
||||||
|
may be retained indefinitely for research and analytics.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Transfer of Your Personal Data</h3>
|
||||||
|
<p>
|
||||||
|
Your information, including Personal Data, is processed at the Company's
|
||||||
|
operating offices and in any other places where the parties involved in
|
||||||
|
the processing are located. It means that this information may be
|
||||||
|
transferred to — and maintained on — computers located outside of Your
|
||||||
|
state, province, country or other governmental jurisdiction where the data
|
||||||
|
protection laws may differ from those from Your jurisdiction.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Where required by applicable law, We will ensure that international
|
||||||
|
transfers of Your Personal Data are subject to appropriate safeguards and
|
||||||
|
supplementary measures where appropriate. The Company will take all steps
|
||||||
|
reasonably necessary to ensure that Your data is treated securely and in
|
||||||
|
accordance with this Privacy Policy and no transfer of Your Personal Data
|
||||||
|
will take place to an organization or a country unless there are adequate
|
||||||
|
controls in place including the security of Your data and other personal
|
||||||
|
information.
|
||||||
|
</p>
|
||||||
|
<h3>Delete Your Personal Data</h3>
|
||||||
|
<p>
|
||||||
|
You have the right to delete or request that We assist in deleting the
|
||||||
|
Personal Data that We have collected about You.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Our Service may give You the ability to delete certain information about
|
||||||
|
You from within the Service.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You may update, amend, or delete Your information at any time by signing
|
||||||
|
in to Your Account, if you have one, and visiting the account settings
|
||||||
|
section that allows you to manage Your personal information. You may also
|
||||||
|
contact Us to request access to, correct, or delete any Personal Data that
|
||||||
|
You have provided to Us.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Please note, however, that We may need to retain certain information when
|
||||||
|
we have a legal obligation or lawful basis to do so.
|
||||||
|
</p>
|
||||||
|
<h3>Disclosure of Your Personal Data</h3>
|
||||||
|
<h4>Business Transactions</h4>
|
||||||
|
<p>
|
||||||
|
If the Company is involved in a merger, acquisition or asset sale, Your
|
||||||
|
Personal Data may be transferred. We will provide notice before Your
|
||||||
|
Personal Data is transferred and becomes subject to a different Privacy
|
||||||
|
Policy.
|
||||||
|
</p>
|
||||||
|
<h4>Law enforcement</h4>
|
||||||
|
<p>
|
||||||
|
Under certain circumstances, the Company may be required to disclose Your
|
||||||
|
Personal Data if required to do so by law or in response to valid requests
|
||||||
|
by public authorities (e.g. a court or a government agency).
|
||||||
|
</p>
|
||||||
|
<h4>Other legal requirements</h4>
|
||||||
|
<p>
|
||||||
|
The Company may disclose Your Personal Data in the good faith belief that
|
||||||
|
such action is necessary to:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Comply with a legal obligation</li>
|
||||||
|
<li>Protect and defend the rights or property of the Company</li>
|
||||||
|
<li>
|
||||||
|
Prevent or investigate possible wrongdoing in connection with the
|
||||||
|
Service
|
||||||
|
</li>
|
||||||
|
<li>Protect the personal safety of Users of the Service or the public</li>
|
||||||
|
<li>Protect against legal liability</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Security of Your Personal Data</h3>
|
||||||
|
<p>
|
||||||
|
The security of Your Personal Data is important to Us, but remember that
|
||||||
|
no method of transmission over the Internet, or method of electronic
|
||||||
|
storage is 100% secure. While We strive to use commercially reasonable
|
||||||
|
means to protect Your Personal Data, We cannot guarantee its absolute
|
||||||
|
security.
|
||||||
|
</p>
|
||||||
|
<h2>Children's Privacy</h2>
|
||||||
|
<p>
|
||||||
|
Our Service does not address anyone under the age of 16. We do not
|
||||||
|
knowingly collect personally identifiable information from anyone under
|
||||||
|
the age of 16. If You are a parent or guardian and You are aware that Your
|
||||||
|
child has provided Us with Personal Data, please contact Us. If We become
|
||||||
|
aware that We have collected Personal Data from anyone under the age of 16
|
||||||
|
without verification of parental consent, We take steps to remove that
|
||||||
|
information from Our servers.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If We need to rely on consent as a legal basis for processing Your
|
||||||
|
information and Your country requires consent from a parent, We may
|
||||||
|
require Your parent's consent before We collect and use that information.
|
||||||
|
</p>
|
||||||
|
<h2>Links to Other Websites</h2>
|
||||||
|
<p>
|
||||||
|
Our Service may contain links to other websites that are not operated by
|
||||||
|
Us. If You click on a third party link, You will be directed to that third
|
||||||
|
party's site. We strongly advise You to review the Privacy Policy of every
|
||||||
|
site You visit.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We have no control over and assume no responsibility for the content,
|
||||||
|
privacy policies or practices of any third party sites or services.
|
||||||
|
</p>
|
||||||
|
<h2>Changes to this Privacy Policy</h2>
|
||||||
|
<p>
|
||||||
|
We may update Our Privacy Policy from time to time. We will notify You of
|
||||||
|
any changes by posting the new Privacy Policy on this page.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We will let You know via email and/or a prominent notice on Our Service,
|
||||||
|
prior to the change becoming effective and update the "Last
|
||||||
|
updated" date at the top of this Privacy Policy.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You are advised to review this Privacy Policy periodically for any
|
||||||
|
changes. Changes to this Privacy Policy are effective when they are posted
|
||||||
|
on this page.
|
||||||
|
</p>
|
||||||
|
<h2>Contact Us</h2>
|
||||||
|
<p>
|
||||||
|
If you have any questions about this Privacy Policy, You can contact us:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>By email: privacy@gleipnir.technology</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
250
html/template/sync/report-confirmation.html
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
295
html/template/sync/report-contribute.html
Normal file
|
|
@ -0,0 +1,295 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
151
html/template/sync/report-detail.html
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
281
html/template/sync/report-evidence.html
Normal file
|
|
@ -0,0 +1,281 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
344
html/template/sync/report-schedule.html
Normal file
|
|
@ -0,0 +1,344 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
225
html/template/sync/report-update.html
Normal file
|
|
@ -0,0 +1,225 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
266
html/template/sync/report.html
Normal file
|
|
@ -0,0 +1,266 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
387
html/template/sync/service-request-detail.html
Normal file
|
|
@ -0,0 +1,387 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
423
html/template/sync/service-request-location.html
Normal file
|
|
@ -0,0 +1,423 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
738
html/template/sync/service-request-mosquito.html
Normal file
|
|
@ -0,0 +1,738 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
|
|
@ -1,82 +1,83 @@
|
||||||
{{template "base.html" .}}
|
{{ template "sync/base.html" . }}
|
||||||
|
|
||||||
{{define "title"}}Dash{{end}}
|
{{ define "title" }}Dash{{ end }}
|
||||||
{{define "extraheader"}}
|
{{ define "extraheader" }}
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
const photoUpload = document.getElementById('photoUpload');
|
const photoUpload = document.getElementById("photoUpload");
|
||||||
const imagePreview = document.getElementById('imagePreview');
|
const imagePreview = document.getElementById("imagePreview");
|
||||||
const dropArea = document.getElementById('dropArea');
|
const dropArea = document.getElementById("dropArea");
|
||||||
|
|
||||||
// Handle file selection
|
// Handle file selection
|
||||||
photoUpload.addEventListener('change', handleFileSelect);
|
photoUpload.addEventListener("change", handleFileSelect);
|
||||||
|
|
||||||
// Handle drag and drop
|
// Handle drag and drop
|
||||||
dropArea.addEventListener('dragover', function(e) {
|
dropArea.addEventListener("dragover", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
dropArea.style.backgroundColor = '#e9ecef';
|
dropArea.style.backgroundColor = "#e9ecef";
|
||||||
});
|
});
|
||||||
|
|
||||||
dropArea.addEventListener('dragleave', function() {
|
dropArea.addEventListener("dragleave", function () {
|
||||||
dropArea.style.backgroundColor = '#f8f9fa';
|
dropArea.style.backgroundColor = "#f8f9fa";
|
||||||
});
|
});
|
||||||
|
|
||||||
dropArea.addEventListener('drop', function(e) {
|
dropArea.addEventListener("drop", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
dropArea.style.backgroundColor = '#f8f9fa';
|
dropArea.style.backgroundColor = "#f8f9fa";
|
||||||
|
|
||||||
if (e.dataTransfer.files.length) {
|
if (e.dataTransfer.files.length) {
|
||||||
handleFiles(e.dataTransfer.files);
|
handleFiles(e.dataTransfer.files);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleFileSelect(e) {
|
function handleFileSelect(e) {
|
||||||
const files = e.target.files;
|
const files = e.target.files;
|
||||||
handleFiles(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();
|
function handleFiles(files) {
|
||||||
|
const maxFiles = 5;
|
||||||
reader.onload = (function(theFile) {
|
const currentFiles =
|
||||||
return function(e) {
|
imagePreview.querySelectorAll(".preview-item").length;
|
||||||
const previewItem = document.createElement('div');
|
|
||||||
previewItem.className = 'preview-item';
|
for (let i = 0; i < files.length && currentFiles + i < maxFiles; i++) {
|
||||||
|
const file = files[i];
|
||||||
const img = document.createElement('img');
|
|
||||||
img.src = e.target.result;
|
if (!file.type.match("image.*")) {
|
||||||
|
continue;
|
||||||
const removeBtn = document.createElement('div');
|
}
|
||||||
removeBtn.className = 'preview-remove';
|
|
||||||
removeBtn.innerHTML = 'x';
|
const reader = new FileReader();
|
||||||
removeBtn.addEventListener('click', function() {
|
|
||||||
imagePreview.removeChild(previewItem);
|
reader.onload = (function (theFile) {
|
||||||
});
|
return function (e) {
|
||||||
|
const previewItem = document.createElement("div");
|
||||||
previewItem.appendChild(img);
|
previewItem.className = "preview-item";
|
||||||
previewItem.appendChild(removeBtn);
|
|
||||||
imagePreview.appendChild(previewItem);
|
const img = document.createElement("img");
|
||||||
};
|
img.src = e.target.result;
|
||||||
})(file);
|
|
||||||
|
const removeBtn = document.createElement("div");
|
||||||
reader.readAsDataURL(file);
|
removeBtn.className = "preview-remove";
|
||||||
}
|
removeBtn.innerHTML = "x";
|
||||||
|
removeBtn.addEventListener("click", function () {
|
||||||
photoUpload.value = '';
|
imagePreview.removeChild(previewItem);
|
||||||
}
|
});
|
||||||
});
|
|
||||||
</script>
|
previewItem.appendChild(img);
|
||||||
<style>
|
previewItem.appendChild(removeBtn);
|
||||||
|
imagePreview.appendChild(previewItem);
|
||||||
|
};
|
||||||
|
})(file);
|
||||||
|
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
photoUpload.value = "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
.district-logo {
|
.district-logo {
|
||||||
max-height: 80px;
|
max-height: 80px;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
|
@ -125,7 +126,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
right: 5px;
|
right: 5px;
|
||||||
background-color: rgba(0,0,0,0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
|
|
@ -168,9 +169,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{{end}}
|
{{ end }}
|
||||||
{{define "content"}}
|
{{ define "content" }}
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<header class="bg-light py-3">
|
<header class="bg-light py-3">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
@ -179,7 +180,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
<h1 class="district-name">{{ .DistrictName }}</h1>
|
<h1 class="district-name">{{ .DistrictName }}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 text-md-end">
|
<div class="col-md-6 text-md-end">
|
||||||
<img src="placeholder-logo.png" alt="District Logo" class="district-logo">
|
<img
|
||||||
|
src="placeholder-logo.png"
|
||||||
|
alt="District Logo"
|
||||||
|
class="district-logo"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -192,7 +197,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<h2>Report a Green Pool or Mosquito Source</h2>
|
<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>
|
<p class="lead">
|
||||||
|
Help us locate and treat potential mosquito breeding sources in your
|
||||||
|
area
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -200,8 +208,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="alert alert-info" role="alert">
|
<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>
|
<h5 class="alert-heading">
|
||||||
<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>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -215,21 +229,36 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
<h3>Photos</h3>
|
<h3>Photos</h3>
|
||||||
<span class="optional-label">optional</span>
|
<span class="optional-label">optional</span>
|
||||||
</div>
|
</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>
|
<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">
|
<div class="file-upload-container" id="dropArea">
|
||||||
<input type="file" id="photoUpload" multiple accept="image/*" class="d-none">
|
<input
|
||||||
|
type="file"
|
||||||
|
id="photoUpload"
|
||||||
|
multiple
|
||||||
|
accept="image/*"
|
||||||
|
class="d-none"
|
||||||
|
/>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<i class="bi bi-cloud-arrow-up fs-1 text-primary"></i>
|
<i class="bi bi-cloud-arrow-up fs-1 text-primary"></i>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-1"><strong>Drag and drop photos here</strong></p>
|
<p class="mb-1"><strong>Drag and drop photos here</strong></p>
|
||||||
<p class="mb-1">- or -</p>
|
<p class="mb-1">- or -</p>
|
||||||
<button type="button" class="btn btn-primary mt-2" onclick="document.getElementById('photoUpload').click()">
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary mt-2"
|
||||||
|
onclick="document.getElementById('photoUpload').click()"
|
||||||
|
>
|
||||||
Select Photos
|
Select Photos
|
||||||
</button>
|
</button>
|
||||||
<p class="small text-muted mt-2 mb-0">You can upload multiple photos (maximum 5)</p>
|
<p class="small text-muted mt-2 mb-0">
|
||||||
|
You can upload multiple photos (maximum 5)
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Image Preview Area -->
|
<!-- Image Preview Area -->
|
||||||
<div class="image-preview" id="imagePreview">
|
<div class="image-preview" id="imagePreview">
|
||||||
<!-- Preview items will be added here dynamically -->
|
<!-- Preview items will be added here dynamically -->
|
||||||
|
|
@ -243,21 +272,36 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
<h3>Location</h3>
|
<h3>Location</h3>
|
||||||
<span class="optional-label">optional</span>
|
<span class="optional-label">optional</span>
|
||||||
</div>
|
</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>
|
<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="row mb-3">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<label for="address" class="form-label">Address or Description of Location</label>
|
<label for="address" class="form-label"
|
||||||
<input type="text" class="form-control" id="address" placeholder="123 Main St, City, State or nearby landmark description">
|
>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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="small text-muted mb-2">You can also click on the map to mark the location precisely</p>
|
<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="map-container">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<i class="bi bi-map fs-1 text-secondary"></i>
|
<i class="bi bi-map fs-1 text-secondary"></i>
|
||||||
<p class="mb-0">Interactive Map</p>
|
<p class="mb-0">Interactive Map</p>
|
||||||
<p class="text-muted small">Click to set the location of the mosquito source</p>
|
<p class="text-muted small">
|
||||||
|
Click to set the location of the mosquito source
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -269,10 +313,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
<h3>Source Details</h3>
|
<h3>Source Details</h3>
|
||||||
<span class="optional-label">optional</span>
|
<span class="optional-label">optional</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label for="duration" class="form-label">How long has this source been present?</label>
|
<label for="duration" class="form-label"
|
||||||
|
>How long has this source been present?</label
|
||||||
|
>
|
||||||
<select class="form-select" id="duration">
|
<select class="form-select" id="duration">
|
||||||
<option value="">I don't know</option>
|
<option value="">I don't know</option>
|
||||||
<option value="less-than-week">Less than a week</option>
|
<option value="less-than-week">Less than a week</option>
|
||||||
|
|
@ -282,23 +328,34 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
<option value="more-than-3-months">More than 3 months</option>
|
<option value="more-than-3-months">More than 3 months</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<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>
|
<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">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="larvae">
|
<input class="form-check-input" type="checkbox" id="larvae" />
|
||||||
<label class="form-check-label" for="larvae">
|
<label class="form-check-label" for="larvae">
|
||||||
Larvae (wigglers) in water
|
Larvae (wigglers) in water
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="pupae">
|
<input class="form-check-input" type="checkbox" id="pupae" />
|
||||||
<label class="form-check-label" for="pupae">
|
<label class="form-check-label" for="pupae">
|
||||||
Pupae (tumblers) in water
|
Pupae (tumblers) in water
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="adultMosquitoes">
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="adultMosquitoes"
|
||||||
|
/>
|
||||||
<label class="form-check-label" for="adultMosquitoes">
|
<label class="form-check-label" for="adultMosquitoes">
|
||||||
Adult mosquitoes near the source
|
Adult mosquitoes near the source
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -314,43 +371,71 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
<h3>Access Information</h3>
|
<h3>Access Information</h3>
|
||||||
<span class="optional-label">optional</span>
|
<span class="optional-label">optional</span>
|
||||||
</div>
|
</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>
|
<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="row mb-3">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<label for="accessInfo" class="form-label">How can the source be accessed?</label>
|
<label for="accessInfo" class="form-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>
|
>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>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<label class="form-label d-block">Access obstacles (check all that apply):</label>
|
<label class="form-label d-block"
|
||||||
|
>Access obstacles (check all that apply):</label
|
||||||
|
>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="gate">
|
<input class="form-check-input" type="checkbox" id="gate" />
|
||||||
<label class="form-check-label" for="gate">Gate</label>
|
<label class="form-check-label" for="gate">Gate</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="fence">
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="fence"
|
||||||
|
/>
|
||||||
<label class="form-check-label" for="fence">Fence</label>
|
<label class="form-check-label" for="fence">Fence</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="lockedEntrance">
|
<input
|
||||||
<label class="form-check-label" for="lockedEntrance">Locked entrance</label>
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="lockedEntrance"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="lockedEntrance"
|
||||||
|
>Locked entrance</label
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="dogs">
|
<input class="form-check-input" type="checkbox" id="dogs" />
|
||||||
<label class="form-check-label" for="dogs">Dogs/pets</label>
|
<label class="form-check-label" for="dogs">Dogs/pets</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="otherObstacle">
|
<input
|
||||||
<label class="form-check-label" for="otherObstacle">Other obstacle</label>
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="otherObstacle"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label" for="otherObstacle"
|
||||||
|
>Other obstacle</label
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -365,42 +450,47 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
<h3>Contact Information</h3>
|
<h3>Contact Information</h3>
|
||||||
<span class="optional-label">optional</span>
|
<span class="optional-label">optional</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Property Owner Information -->
|
<!-- Property Owner Information -->
|
||||||
<h5 class="mb-3">Property Owner Information (if known)</h5>
|
<h5 class="mb-3">Property Owner Information (if known)</h5>
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label for="ownerName" class="form-label">Owner Name</label>
|
<label for="ownerName" class="form-label">Owner Name</label>
|
||||||
<input type="text" class="form-control" id="ownerName">
|
<input type="text" class="form-control" id="ownerName" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label for="ownerPhone" class="form-label">Owner Phone</label>
|
<label for="ownerPhone" class="form-label">Owner Phone</label>
|
||||||
<input type="tel" class="form-control" id="ownerPhone">
|
<input type="tel" class="form-control" id="ownerPhone" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<label for="ownerEmail" class="form-label">Owner Email</label>
|
<label for="ownerEmail" class="form-label">Owner Email</label>
|
||||||
<input type="email" class="form-control" id="ownerEmail">
|
<input type="email" class="form-control" id="ownerEmail" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Your Contact Information -->
|
<!-- Your Contact Information -->
|
||||||
<h5 class="mb-3">Your Contact Information (for updates)</h5>
|
<h5 class="mb-3">Your Contact Information (for updates)</h5>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label for="reporterName" class="form-label">Your Name</label>
|
<label for="reporterName" class="form-label">Your Name</label>
|
||||||
<input type="text" class="form-control" id="reporterName">
|
<input type="text" class="form-control" id="reporterName" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label for="reporterPhone" class="form-label">Your Phone</label>
|
<label for="reporterPhone" class="form-label">Your Phone</label>
|
||||||
<input type="tel" class="form-control" id="reporterPhone">
|
<input type="tel" class="form-control" id="reporterPhone" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-12 mb-3">
|
<div class="col-md-12 mb-3">
|
||||||
<label for="reporterEmail" class="form-label">Your Email</label>
|
<label for="reporterEmail" class="form-label">Your Email</label>
|
||||||
<input type="email" class="form-control" id="reporterEmail">
|
<input type="email" class="form-control" id="reporterEmail" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox" id="receiveUpdates" checked>
|
<input
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
id="receiveUpdates"
|
||||||
|
checked
|
||||||
|
/>
|
||||||
<label class="form-check-label" for="receiveUpdates">
|
<label class="form-check-label" for="receiveUpdates">
|
||||||
I would like to receive updates on this report
|
I would like to receive updates on this report
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -416,12 +506,22 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
<h3>Additional Information</h3>
|
<h3>Additional Information</h3>
|
||||||
<span class="optional-label">optional</span>
|
<span class="optional-label">optional</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-3">Please provide any other information that might help us address this mosquito source.</p>
|
<p class="mb-3">
|
||||||
|
Please provide any other information that might help us address this
|
||||||
|
mosquito source.
|
||||||
|
</p>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<label for="additionalInfo" class="form-label">Additional Details</label>
|
<label for="additionalInfo" class="form-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>
|
>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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -430,8 +530,16 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
<div class="submit-container">
|
<div class="submit-container">
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col-md-8">
|
<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">
|
||||||
<p class="mb-0 small text-muted">After submission, you will receive a confirmation with a report ID for tracking purposes.</p>
|
<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>
|
||||||
<div class="col-md-4 text-md-end mt-3 mt-md-0">
|
<div class="col-md-4 text-md-end mt-3 mt-md-0">
|
||||||
<button type="submit" class="btn btn-primary btn-lg">
|
<button type="submit" class="btn btn-primary btn-lg">
|
||||||
|
|
@ -454,12 +562,25 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Larvae Info Modal -->
|
<!-- Larvae Info Modal -->
|
||||||
<div class="modal fade" id="larvaeInfoModal" tabindex="-1" aria-labelledby="larvaeInfoModalLabel" aria-hidden="true">
|
<div
|
||||||
|
class="modal fade"
|
||||||
|
id="larvaeInfoModal"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-labelledby="larvaeInfoModalLabel"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
<div class="modal-dialog modal-lg">
|
<div class="modal-dialog modal-lg">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" id="larvaeInfoModalLabel">How to Identify Mosquito Larvae and Pupae</h5>
|
<h5 class="modal-title" id="larvaeInfoModalLabel">
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
How to Identify Mosquito Larvae and Pupae
|
||||||
|
</h5>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn-close"
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
aria-label="Close"
|
||||||
|
></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
|
|
@ -486,7 +607,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p>When looking for mosquito larvae and pupae, check standing water sources like:</p>
|
<p>
|
||||||
|
When looking for mosquito larvae and pupae, check standing water
|
||||||
|
sources like:
|
||||||
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Swimming pools</li>
|
<li>Swimming pools</li>
|
||||||
<li>Bird baths</li>
|
<li>Bird baths</li>
|
||||||
|
|
@ -495,13 +619,24 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
<li>Plant saucers</li>
|
<li>Plant saucers</li>
|
||||||
<li>Rain gutters</li>
|
<li>Rain gutters</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>If you see small creatures moving in standing water, there's a good chance they're mosquito larvae or pupae.</p>
|
<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">
|
<div class="text-center">
|
||||||
<a href="#" class="btn btn-outline-primary">View Detailed Identification Guide</a>
|
<a href="#" class="btn btn-outline-primary"
|
||||||
|
>View Detailed Identification Guide</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-secondary"
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -512,13 +647,16 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<p class="mb-0">© 2023 {{ .DistrictName }} Mosquito Management District</p>
|
<p class="mb-0">
|
||||||
|
© 2023 {{ .DistrictName }} Mosquito Management District
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 text-md-end">
|
<div class="col-md-6 text-md-end">
|
||||||
<p class="mb-0">Contact: (555) 123-4567 | info@mosquitodistrict.gov</p>
|
<p class="mb-0">
|
||||||
|
Contact: (555) 123-4567 | info@mosquitodistrict.gov
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
{{ end }}
|
||||||
{{end}}
|
|
||||||
150
html/template/sync/service-request-quick-confirmation.html
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
233
html/template/sync/service-request-quick.html
Normal file
|
|
@ -0,0 +1,233 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
222
html/template/sync/service-request-updates.html
Normal file
|
|
@ -0,0 +1,222 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
230
html/template/sync/service-request.html
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
367
html/template/sync/setting-integration.html
Normal file
|
|
@ -0,0 +1,367 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
218
html/template/sync/setting-mock.html
Normal file
|
|
@ -0,0 +1,218 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
253
html/template/sync/setting-pesticide-add.html
Normal file
|
|
@ -0,0 +1,253 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
293
html/template/sync/setting-pesticide.html
Normal file
|
|
@ -0,0 +1,293 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
161
html/template/sync/setting-user-add.html
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
{{ template "sync/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 }}
|
||||||
168
html/template/sync/setting-user.html
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
{{ template "sync/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
html/template/sync/settings.html
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{{ template "sync/authenticated.html" . }}
|
||||||
|
|
||||||
|
{{ define "title" }}Dash{{ end }}
|
||||||
|
{{ define "extraheader" }}
|
||||||
|
{{ end }}
|
||||||
|
{{ define "content" }}
|
||||||
|
<p>Imagine settings here</p>
|
||||||
|
{{ end }}
|
||||||
105
html/template/sync/signin.html
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
{{ template "sync/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" />
|
||||||
|
<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 }}
|
||||||
131
html/template/sync/signup.html
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
{{ template "sync/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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{{ end }}
|
||||||
|
{{ define "content" }}
|
||||||
|
<div
|
||||||
|
class="container min-vh-100 d-flex align-items-center justify-content-center py-5"
|
||||||
|
>
|
||||||
|
<div class="register-container">
|
||||||
|
<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="/signin">Sign in</a></p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right side: Account Information -->
|
||||||
|
<div class="col-md-6 register-info-section">
|
||||||
|
<div>
|
||||||
|
<div class="logo-area">
|
||||||
|
<img src="/static/img/nidus-logo-256-transparent.png" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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 }}
|
||||||
367
html/template/sync/source.html
Normal file
|
|
@ -0,0 +1,367 @@
|
||||||
|
{{ template "sync/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|timeSincePtr }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="info-label">Edit date</td>
|
||||||
|
<td>{{ .Source.EditedAt|timeSincePtr }}</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|timeSincePtr }}</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|timeSincePtr }}</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|timeSincePtr }}</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|timeSincePtr }}</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|timeSincePtr }}</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|timeSincePtr }}</td>
|
||||||
|
<td>{{ .Females }}</td>
|
||||||
|
<td>{{ .Males }}</td>
|
||||||
|
<td>{{ .Total }}</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
3
html/template/sync/template-test.html
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<html>
|
||||||
|
<h1>Hi Eli</h1>
|
||||||
|
</html>
|
||||||
181
html/template/sync/text-messages.html
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
{{ template "sync/authenticated.html" . }}
|
||||||
|
|
||||||
|
{{ define "title" }}Dash{{ end }}
|
||||||
|
{{ define "extraheader" }}
|
||||||
|
<style>
|
||||||
|
.chat-container {
|
||||||
|
height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
max-width: 75%;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
.sender-message {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
.receiver-message {
|
||||||
|
background-color: #d1e7ff;
|
||||||
|
}
|
||||||
|
.message-time {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: #6c757d;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
.avatar {
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{{ end }}
|
||||||
|
{{ define "content" }}
|
||||||
|
<div class="container py-4">
|
||||||
|
<!-- Chat Header -->
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body d-flex align-items-center">
|
||||||
|
<div class="me-3">
|
||||||
|
<img
|
||||||
|
src="https://via.placeholder.com/40"
|
||||||
|
class="avatar"
|
||||||
|
alt="User avatar"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="mb-0">Chat with Sarah Johnson</h5>
|
||||||
|
<small class="text-muted">Last active 5 minutes ago</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chat Messages -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body chat-container">
|
||||||
|
<!-- Date Divider -->
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<span class="badge bg-light text-dark">Today, 2:30 PM</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Receiver Message -->
|
||||||
|
<div class="d-flex mb-4">
|
||||||
|
<img
|
||||||
|
src="https://via.placeholder.com/40"
|
||||||
|
class="avatar me-2 align-self-end"
|
||||||
|
alt="Receiver avatar"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div class="message sender-message p-3">
|
||||||
|
Hi there! How's the project coming along?
|
||||||
|
</div>
|
||||||
|
<div class="message-time">2:31 PM</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sender Message -->
|
||||||
|
<div class="d-flex flex-row-reverse mb-4">
|
||||||
|
<img
|
||||||
|
src="https://via.placeholder.com/40"
|
||||||
|
class="avatar ms-2 align-self-end"
|
||||||
|
alt="Sender avatar"
|
||||||
|
/>
|
||||||
|
<div class="d-flex flex-column align-items-end">
|
||||||
|
<div class="message receiver-message p-3">
|
||||||
|
Hey! It's going pretty well. I'm working on the UI mockups
|
||||||
|
right now.
|
||||||
|
</div>
|
||||||
|
<div class="message-time">2:33 PM</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Receiver Message -->
|
||||||
|
<div class="d-flex mb-4">
|
||||||
|
<img
|
||||||
|
src="https://via.placeholder.com/40"
|
||||||
|
class="avatar me-2 align-self-end"
|
||||||
|
alt="Receiver avatar"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div class="message sender-message p-3">
|
||||||
|
That's great to hear! When do you think you'll be able to
|
||||||
|
share them with the team?
|
||||||
|
</div>
|
||||||
|
<div class="message-time">2:35 PM</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sender Message -->
|
||||||
|
<div class="d-flex flex-row-reverse mb-4">
|
||||||
|
<img
|
||||||
|
src="https://via.placeholder.com/40"
|
||||||
|
class="avatar ms-2 align-self-end"
|
||||||
|
alt="Sender avatar"
|
||||||
|
/>
|
||||||
|
<div class="d-flex flex-column align-items-end">
|
||||||
|
<div class="message receiver-message p-3">
|
||||||
|
I'm hoping to have something ready by tomorrow afternoon. I'm
|
||||||
|
just working out some details with the responsive design.
|
||||||
|
</div>
|
||||||
|
<div class="message-time">2:36 PM</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sender Message -->
|
||||||
|
<div class="d-flex flex-row-reverse mb-4">
|
||||||
|
<img
|
||||||
|
src="https://via.placeholder.com/40"
|
||||||
|
class="avatar ms-2 align-self-end"
|
||||||
|
alt="Sender avatar"
|
||||||
|
/>
|
||||||
|
<div class="d-flex flex-column align-items-end">
|
||||||
|
<div class="message receiver-message p-3">
|
||||||
|
Do you have any specific feedback on the initial concept I
|
||||||
|
shared last week?
|
||||||
|
</div>
|
||||||
|
<div class="message-time">2:37 PM</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Receiver Message -->
|
||||||
|
<div class="d-flex mb-4">
|
||||||
|
<img
|
||||||
|
src="https://via.placeholder.com/40"
|
||||||
|
class="avatar me-2 align-self-end"
|
||||||
|
alt="Receiver avatar"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<div class="message sender-message p-3">
|
||||||
|
Yes! The team loved it. The color scheme was particularly well
|
||||||
|
received. We just had some minor suggestions about the
|
||||||
|
navigation that I can share during our next call.
|
||||||
|
</div>
|
||||||
|
<div class="message-time">2:40 PM</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sender Message -->
|
||||||
|
<div class="d-flex flex-row-reverse mb-4">
|
||||||
|
<img
|
||||||
|
src="https://via.placeholder.com/40"
|
||||||
|
class="avatar ms-2 align-self-end"
|
||||||
|
alt="Sender avatar"
|
||||||
|
/>
|
||||||
|
<div class="d-flex flex-column align-items-end">
|
||||||
|
<div class="message receiver-message p-3">
|
||||||
|
That sounds great! Looking forward to the feedback.
|
||||||
|
</div>
|
||||||
|
<div class="message-time">2:41 PM</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
131
html/template/sync/trap.html
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
{{ template "sync/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">
|
||||||
|
<!-- Trap Header Section -->
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-12">
|
||||||
|
<h1>Trap 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">Trap ID: {{ .Trap.GlobalID }}</div>
|
||||||
|
<table class="info-table">
|
||||||
|
<tr>
|
||||||
|
<td class="info-label">Active:</td>
|
||||||
|
<td>{{ .Trap.Active }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="info-label">Comments:</td>
|
||||||
|
<td>{{ .Trap.Comments }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="info-label">Description:</td>
|
||||||
|
<td>{{ .Trap.Description }}</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>
|
||||||
|
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-12">
|
||||||
|
<h2 class="section-header">Trap Collections</h2>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Collection Date</th>
|
||||||
|
<th>Collection ID</th>
|
||||||
|
<th>Females</th>
|
||||||
|
<th>Male</th>
|
||||||
|
<th>Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<!-- Trap 1 with multiple collections -->
|
||||||
|
{{ range .Trap.Collections }}
|
||||||
|
<tr>
|
||||||
|
<td>{{ .EndDateTime|timeSincePtr }}</td>
|
||||||
|
<td>{{ .GlobalID }}</td>
|
||||||
|
<td>{{ .Count.Females }}</td>
|
||||||
|
<td>{{ .Count.Males }}</td>
|
||||||
|
<td>{{ .Count.Total }}</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
12
main.go
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/comms/email"
|
"github.com/Gleipnir-Technology/nidus-sync/comms/email"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||||
|
"github.com/Gleipnir-Technology/nidus-sync/html"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/llm"
|
"github.com/Gleipnir-Technology/nidus-sync/llm"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/platform/text"
|
"github.com/Gleipnir-Technology/nidus-sync/platform/text"
|
||||||
"github.com/Gleipnir-Technology/nidus-sync/rmo"
|
"github.com/Gleipnir-Technology/nidus-sync/rmo"
|
||||||
|
|
@ -75,16 +76,21 @@ func main() {
|
||||||
os.Exit(3)
|
os.Exit(3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = html.LoadTemplates()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to load html templates")
|
||||||
|
os.Exit(4)
|
||||||
|
}
|
||||||
err = email.LoadTemplates()
|
err = email.LoadTemplates()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to load email templates")
|
log.Error().Err(err).Msg("Failed to load email templates")
|
||||||
os.Exit(4)
|
os.Exit(5)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = text.StoreSources()
|
err = text.StoreSources()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to store text source phone numbers")
|
log.Error().Err(err).Msg("Failed to store text source phone numbers")
|
||||||
os.Exit(5)
|
os.Exit(6)
|
||||||
}
|
}
|
||||||
|
|
||||||
router_logger := log.With().Logger()
|
router_logger := log.With().Logger()
|
||||||
|
|
@ -121,7 +127,7 @@ func main() {
|
||||||
err = llm.CreateOpenAIClient(ctx, &openai_logger)
|
err = llm.CreateOpenAIClient(ctx, &openai_logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to start openAI client")
|
log.Error().Err(err).Msg("Failed to start openAI client")
|
||||||
os.Exit(6)
|
os.Exit(7)
|
||||||
}
|
}
|
||||||
background.Start(ctx)
|
background.Start(ctx)
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,6 @@ type ContentDistrictList struct {
|
||||||
URL ContentURL
|
URL ContentURL
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
DistrictListT = buildTemplate("district-list", "base")
|
|
||||||
)
|
|
||||||
|
|
||||||
func districtBySlug(r *http.Request) (*models.Organization, error) {
|
func districtBySlug(r *http.Request) (*models.Organization, error) {
|
||||||
slug := chi.URLParam(r, "slug")
|
slug := chi.URLParam(r, "slug")
|
||||||
district, err := models.Organizations.Query(
|
district, err := models.Organizations.Query(
|
||||||
|
|
@ -49,7 +45,7 @@ func getDistrictList(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
html.RenderOrError(
|
html.RenderOrError(
|
||||||
w,
|
w,
|
||||||
DistrictListT,
|
"rmo/district-list.html",
|
||||||
ContentDistrictList{
|
ContentDistrictList{
|
||||||
Districts: districts,
|
Districts: districts,
|
||||||
URL: makeContentURL(nil),
|
URL: makeContentURL(nil),
|
||||||
|
|
|
||||||
17
rmo/email.go
|
|
@ -15,13 +15,6 @@ type ContentEmail struct {
|
||||||
Email string
|
Email string
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
EmailConfirmT = buildTemplate("email-confirm", "base")
|
|
||||||
EmailConfirmCompleteT = buildTemplate("email-confirm-complete", "base")
|
|
||||||
EmailUnsubscribeT = buildTemplate("email-unsubscribe", "base")
|
|
||||||
EmailUnsubscribeCompleteT = buildTemplate("email-unsubscribe-complete", "base")
|
|
||||||
)
|
|
||||||
|
|
||||||
func getEmailByCode(w http.ResponseWriter, r *http.Request) {
|
func getEmailByCode(w http.ResponseWriter, r *http.Request) {
|
||||||
id := chi.URLParam(r, "code")
|
id := chi.URLParam(r, "code")
|
||||||
//id := r.FormValue("id")
|
//id := r.FormValue("id")
|
||||||
|
|
@ -48,7 +41,7 @@ func getEmailReportUnsubscribe(w http.ResponseWriter, r *http.Request) {
|
||||||
email := r.FormValue("email")
|
email := r.FormValue("email")
|
||||||
html.RenderOrError(
|
html.RenderOrError(
|
||||||
w,
|
w,
|
||||||
EmailConfirmT,
|
"rmo/email-unsubscribe.html",
|
||||||
ContentEmail{
|
ContentEmail{
|
||||||
Email: email,
|
Email: email,
|
||||||
},
|
},
|
||||||
|
|
@ -63,7 +56,7 @@ func getEmailConfirm(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
html.RenderOrError(
|
html.RenderOrError(
|
||||||
w,
|
w,
|
||||||
EmailConfirmT,
|
"rmo/email-confirm.html",
|
||||||
ContentEmail{
|
ContentEmail{
|
||||||
Email: email,
|
Email: email,
|
||||||
},
|
},
|
||||||
|
|
@ -72,7 +65,7 @@ func getEmailConfirm(w http.ResponseWriter, r *http.Request) {
|
||||||
func getEmailConfirmComplete(w http.ResponseWriter, r *http.Request) {
|
func getEmailConfirmComplete(w http.ResponseWriter, r *http.Request) {
|
||||||
html.RenderOrError(
|
html.RenderOrError(
|
||||||
w,
|
w,
|
||||||
EmailConfirmCompleteT,
|
"rmo/email-confirm-complete.html",
|
||||||
map[string]string{},
|
map[string]string{},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +73,7 @@ func getEmailUnsubscribe(w http.ResponseWriter, r *http.Request) {
|
||||||
email := r.FormValue("email")
|
email := r.FormValue("email")
|
||||||
html.RenderOrError(
|
html.RenderOrError(
|
||||||
w,
|
w,
|
||||||
EmailUnsubscribeT,
|
"rmo/email-unsubscribe.html",
|
||||||
ContentEmail{
|
ContentEmail{
|
||||||
Email: email,
|
Email: email,
|
||||||
},
|
},
|
||||||
|
|
@ -89,7 +82,7 @@ func getEmailUnsubscribe(w http.ResponseWriter, r *http.Request) {
|
||||||
func getEmailUnsubscribeComplete(w http.ResponseWriter, r *http.Request) {
|
func getEmailUnsubscribeComplete(w http.ResponseWriter, r *http.Request) {
|
||||||
html.RenderOrError(
|
html.RenderOrError(
|
||||||
w,
|
w,
|
||||||
EmailUnsubscribeCompleteT,
|
"rmo/email-unsubscribe-complete.html",
|
||||||
map[string]string{},
|
map[string]string{},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
rmo/mock.go
|
|
@ -8,14 +8,6 @@ import (
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
mockDistrictRootT = buildTemplate("mock/district-root", "base")
|
|
||||||
mockNuisanceT = buildTemplate("mock/nuisance", "base")
|
|
||||||
mockNuisanceSubmitCompleteT = buildTemplate("mock/nuisance-submit-complete", "base")
|
|
||||||
mockRootT = buildTemplate("mock/root", "base")
|
|
||||||
mockWaterT = buildTemplate("mock/water", "base")
|
|
||||||
)
|
|
||||||
|
|
||||||
type ContentMock struct {
|
type ContentMock struct {
|
||||||
District ContentDistrict
|
District ContentDistrict
|
||||||
MapboxToken string
|
MapboxToken string
|
||||||
|
|
@ -24,13 +16,13 @@ type ContentMock struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func addMockRoutes(r chi.Router) {
|
func addMockRoutes(r chi.Router) {
|
||||||
r.Get("/", renderMock(mockRootT))
|
r.Get("/", renderMock("rmo/mock/root.html"))
|
||||||
r.Get("/district/{slug}", renderMock(mockDistrictRootT))
|
r.Get("/district/{slug}", renderMock("rmo/mock/district-root.html"))
|
||||||
r.Get("/district/{slug}/nuisance", renderMock(mockNuisanceT))
|
r.Get("/district/{slug}/nuisance", renderMock("rmo/mock/nuisance.html"))
|
||||||
r.Get("/district/{slug}/nuisance-submit-complete", renderMock(mockNuisanceSubmitCompleteT))
|
r.Get("/district/{slug}/nuisance-submit-complete", renderMock("rmo/mock/nuisance-submit-complete.html"))
|
||||||
r.Get("/district/{slug}/water", renderMock(mockWaterT))
|
r.Get("/district/{slug}/water", renderMock("rmo/mock/water.html"))
|
||||||
r.Get("/nuisance", renderMock(mockNuisanceT))
|
r.Get("/nuisance", renderMock("rmo/mock/nuisance.html"))
|
||||||
r.Get("/nuisance-submit-complete", renderMock(mockNuisanceSubmitCompleteT))
|
r.Get("/nuisance-submit-complete", renderMock("rmo/mock/nuisance-submit-complete.html"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeContentURLMock(slug string) ContentURL {
|
func makeContentURLMock(slug string) ContentURL {
|
||||||
|
|
@ -44,7 +36,7 @@ func makeContentURLMock(slug string) ContentURL {
|
||||||
func makeURLMock(slug, p string) string {
|
func makeURLMock(slug, p string) string {
|
||||||
return config.MakeURLReport("/mock/district/%s/%s", slug, p)
|
return config.MakeURLReport("/mock/district/%s/%s", slug, p)
|
||||||
}
|
}
|
||||||
func renderMock(t *html.BuiltTemplate) func(http.ResponseWriter, *http.Request) {
|
func renderMock(t string) func(http.ResponseWriter, *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
slug := chi.URLParam(r, "slug")
|
slug := chi.URLParam(r, "slug")
|
||||||
if slug == "" {
|
if slug == "" {
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,6 @@ import (
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
registerNotificationsCompleteT = buildTemplate("register-notifications-complete", "base")
|
|
||||||
)
|
|
||||||
|
|
||||||
func postRegisterNotifications(w http.ResponseWriter, r *http.Request) {
|
func postRegisterNotifications(w http.ResponseWriter, r *http.Request) {
|
||||||
err := r.ParseForm()
|
err := r.ParseForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -33,15 +33,10 @@ type ContentNuisanceSubmitComplete struct {
|
||||||
URL ContentURL
|
URL ContentURL
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
NuisanceT = buildTemplate("nuisance", "base")
|
|
||||||
SubmitCompleteT = buildTemplate("submit-complete", "base")
|
|
||||||
)
|
|
||||||
|
|
||||||
func getNuisance(w http.ResponseWriter, r *http.Request) {
|
func getNuisance(w http.ResponseWriter, r *http.Request) {
|
||||||
html.RenderOrError(
|
html.RenderOrError(
|
||||||
w,
|
w,
|
||||||
NuisanceT,
|
"rmo/nuisance.html",
|
||||||
ContentNuisance{
|
ContentNuisance{
|
||||||
District: nil,
|
District: nil,
|
||||||
MapboxToken: config.MapboxToken,
|
MapboxToken: config.MapboxToken,
|
||||||
|
|
@ -57,7 +52,7 @@ func getNuisanceDistrict(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
html.RenderOrError(
|
html.RenderOrError(
|
||||||
w,
|
w,
|
||||||
NuisanceT,
|
"rmo/nuisance.html",
|
||||||
ContentNuisance{
|
ContentNuisance{
|
||||||
District: newContentDistrict(district),
|
District: newContentDistrict(district),
|
||||||
MapboxToken: config.MapboxToken,
|
MapboxToken: config.MapboxToken,
|
||||||
|
|
@ -74,7 +69,7 @@ func getSubmitComplete(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
html.RenderOrError(
|
html.RenderOrError(
|
||||||
w,
|
w,
|
||||||
SubmitCompleteT,
|
"rmo/submit-complete.html",
|
||||||
ContentNuisanceSubmitComplete{
|
ContentNuisanceSubmitComplete{
|
||||||
District: newContentDistrict(district),
|
District: newContentDistrict(district),
|
||||||
ReportID: report_id,
|
ReportID: report_id,
|
||||||
|
|
|
||||||
11
rmo/quick.go
|
|
@ -36,15 +36,10 @@ type District struct {
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
quickT = buildTemplate("quick", "base")
|
|
||||||
quickSubmitCompleteT = buildTemplate("quick-submit-complete", "base")
|
|
||||||
)
|
|
||||||
|
|
||||||
func getQuick(w http.ResponseWriter, r *http.Request) {
|
func getQuick(w http.ResponseWriter, r *http.Request) {
|
||||||
html.RenderOrError(
|
html.RenderOrError(
|
||||||
w,
|
w,
|
||||||
quickT,
|
"rmo/quick.html",
|
||||||
ContentQuick{},
|
ContentQuick{},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +75,7 @@ func getQuickSubmitComplete(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
html.RenderOrError(
|
html.RenderOrError(
|
||||||
w,
|
w,
|
||||||
quickSubmitCompleteT,
|
"rmo/quick-submit-complete.html",
|
||||||
ContentQuickSubmitComplete{
|
ContentQuickSubmitComplete{
|
||||||
District: district,
|
District: district,
|
||||||
ReportID: report.PublicID,
|
ReportID: report.PublicID,
|
||||||
|
|
@ -91,7 +86,7 @@ func getRegisterNotificationsComplete(w http.ResponseWriter, r *http.Request) {
|
||||||
report := r.URL.Query().Get("report")
|
report := r.URL.Query().Get("report")
|
||||||
html.RenderOrError(
|
html.RenderOrError(
|
||||||
w,
|
w,
|
||||||
registerNotificationsCompleteT,
|
"rmo/register-notificition-complete.html",
|
||||||
ContentRegisterNotificationsComplete{
|
ContentRegisterNotificationsComplete{
|
||||||
ReportID: report,
|
ReportID: report,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
14
rmo/root.go
|
|
@ -30,12 +30,6 @@ type ContentURL struct {
|
||||||
WaterSubmit string
|
WaterSubmit string
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
PrivacyT = buildTemplate("privacy", "base")
|
|
||||||
RootT = buildTemplate("root", "base")
|
|
||||||
TermsT = buildTemplate("terms", "base")
|
|
||||||
)
|
|
||||||
|
|
||||||
func boolFromForm(r *http.Request, k string) bool {
|
func boolFromForm(r *http.Request, k string) bool {
|
||||||
s := r.PostFormValue(k)
|
s := r.PostFormValue(k)
|
||||||
if s == "on" {
|
if s == "on" {
|
||||||
|
|
@ -47,7 +41,7 @@ func boolFromForm(r *http.Request, k string) bool {
|
||||||
func getPrivacy(w http.ResponseWriter, r *http.Request) {
|
func getPrivacy(w http.ResponseWriter, r *http.Request) {
|
||||||
html.RenderOrError(
|
html.RenderOrError(
|
||||||
w,
|
w,
|
||||||
PrivacyT,
|
"rmo/privacy.html",
|
||||||
ContentPrivacy{
|
ContentPrivacy{
|
||||||
Address: "2726 S Quinn Ave, Gilbert, AZ, USA",
|
Address: "2726 S Quinn Ave, Gilbert, AZ, USA",
|
||||||
Company: "Gleipnir LLC",
|
Company: "Gleipnir LLC",
|
||||||
|
|
@ -59,7 +53,7 @@ func getPrivacy(w http.ResponseWriter, r *http.Request) {
|
||||||
func getRoot(w http.ResponseWriter, r *http.Request) {
|
func getRoot(w http.ResponseWriter, r *http.Request) {
|
||||||
html.RenderOrError(
|
html.RenderOrError(
|
||||||
w,
|
w,
|
||||||
RootT,
|
"rmo/root.html",
|
||||||
ContentRoot{
|
ContentRoot{
|
||||||
URL: makeContentURL(nil),
|
URL: makeContentURL(nil),
|
||||||
},
|
},
|
||||||
|
|
@ -73,7 +67,7 @@ func getRootDistrict(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
html.RenderOrError(
|
html.RenderOrError(
|
||||||
w,
|
w,
|
||||||
RootT,
|
"rmo/root.html",
|
||||||
ContentRoot{
|
ContentRoot{
|
||||||
District: newContentDistrict(district),
|
District: newContentDistrict(district),
|
||||||
URL: makeContentURL(district),
|
URL: makeContentURL(district),
|
||||||
|
|
@ -88,7 +82,7 @@ func getRobots(w http.ResponseWriter, r *http.Request) {
|
||||||
func getTerms(w http.ResponseWriter, r *http.Request) {
|
func getTerms(w http.ResponseWriter, r *http.Request) {
|
||||||
html.RenderOrError(
|
html.RenderOrError(
|
||||||
w,
|
w,
|
||||||
TermsT,
|
"rmo/terms.html",
|
||||||
ContentRoot{
|
ContentRoot{
|
||||||
URL: makeContentURL(nil),
|
URL: makeContentURL(nil),
|
||||||
},
|
},
|
||||||
|
|
|
||||||