diff --git a/arcgis.go b/arcgis.go index f77d1c1a..6afc19e2 100644 --- a/arcgis.go +++ b/arcgis.go @@ -358,6 +358,7 @@ func periodicallyExportFieldseeker(ctx context.Context, org *models.Organization if err != nil { return fmt.Errorf("Failed to export Fieldseeker data: %v", err) } + slog.Info("Completed exporting data, waiting 15 minutes to go agoin.") pollTicker = time.NewTicker(15 * time.Minute) } } @@ -400,7 +401,9 @@ func exportFieldseekerData(ctx context.Context, org *models.Organization, oauth RecordsUnchanged: omit.From(int32(stats.Unchanged)), } err = org.InsertFieldseekerSyncs(ctx, PGInstance.BobDB, &setter) - //err = user.InsertUserOauthTokens(ctx, PGInstance.BobDB, &setter) + if err != nil { + return fmt.Errorf("Failed to insert sync: %v", err) + } return nil } diff --git a/endpoint.go b/endpoint.go index 263bedd9..fa930581 100644 --- a/endpoint.go +++ b/endpoint.go @@ -182,6 +182,50 @@ func getRoot(w http.ResponseWriter, r *http.Request) { respondError(w, "Failed to render root", err, http.StatusInternalServerError) } } + +func getServiceRequest(w http.ResponseWriter, r *http.Request) { + err := htmlServiceRequest(w) + if err != nil { + respondError(w, "Failed to generate service request page", err, http.StatusInternalServerError) + } +} + +func getServiceRequestDetail(w http.ResponseWriter, r *http.Request) { + code := chi.URLParam(r, "code") + err := htmlServiceRequestDetail(w, code) + if err != nil { + respondError(w, "Failed to generate service request page", err, http.StatusInternalServerError) + } +} + +func getServiceRequestLocation(w http.ResponseWriter, r *http.Request) { + err := htmlServiceRequestLocation(w) + if err != nil { + respondError(w, "Failed to generate service request location page", err, http.StatusInternalServerError) + } +} + +func getServiceRequestMosquito(w http.ResponseWriter, r *http.Request) { + err := htmlServiceRequestMosquito(w) + if err != nil { + respondError(w, "Failed to generate service request mosquito page", err, http.StatusInternalServerError) + } +} + +func getServiceRequestPool(w http.ResponseWriter, r *http.Request) { + err := htmlServiceRequestPool(w) + if err != nil { + respondError(w, "Failed to generate service request pool page", err, http.StatusInternalServerError) + } +} + +func getServiceRequestUpdates(w http.ResponseWriter, r *http.Request) { + err := htmlServiceRequestUpdates(w) + if err != nil { + respondError(w, "Failed to generate service request updates page", err, http.StatusInternalServerError) + } +} + func getSignup(w http.ResponseWriter, r *http.Request) { err := htmlSignup(w, r.URL.Path) if err != nil { diff --git a/html.go b/html.go index eb0e57d7..a6c26b0a 100644 --- a/html.go +++ b/html.go @@ -18,17 +18,23 @@ import ( ) var ( - dashboard = newBuiltTemplate("dashboard", "authenticated") - oauthPrompt = newBuiltTemplate("oauth-prompt", "authenticated") - report = newBuiltTemplate("report", "base") - reportConfirmation = newBuiltTemplate("report-confirmation", "base") - reportContribute = newBuiltTemplate("report-contribute", "base") - reportDetail = newBuiltTemplate("report-detail", "base") - reportEvidence = newBuiltTemplate("report-evidence", "base") - reportSchedule = newBuiltTemplate("report-schedule", "base") - reportUpdate = newBuiltTemplate("report-update", "base") - signin = newBuiltTemplate("signin", "base") - signup = newBuiltTemplate("signup", "base") + dashboard = newBuiltTemplate("dashboard", "authenticated") + oauthPrompt = newBuiltTemplate("oauth-prompt", "authenticated") + report = newBuiltTemplate("report", "base") + reportConfirmation = newBuiltTemplate("report-confirmation", "base") + reportContribute = newBuiltTemplate("report-contribute", "base") + reportDetail = newBuiltTemplate("report-detail", "base") + reportEvidence = newBuiltTemplate("report-evidence", "base") + reportSchedule = newBuiltTemplate("report-schedule", "base") + reportUpdate = newBuiltTemplate("report-update", "base") + serviceRequest = newBuiltTemplate("service-request", "base") + serviceRequestDetail = newBuiltTemplate("service-request-detail", "base") + serviceRequestLocation = newBuiltTemplate("service-request-location", "base") + serviceRequestMosquito = newBuiltTemplate("service-request-mosquito", "base") + serviceRequestPool = newBuiltTemplate("service-request-pool", "base") + serviceRequestUpdates = newBuiltTemplate("service-request-updates", "base") + signin = newBuiltTemplate("signin", "base") + signup = newBuiltTemplate("signup", "base") ) var components = [...]string{"header"} @@ -52,7 +58,7 @@ type ContentDashboard struct { CountInspections int CountMosquitoSources int CountServiceRequests int - LastSync string + LastSync time.Time Org string RecentRequests []ServiceRequestSummary User User @@ -109,13 +115,12 @@ func htmlDashboard(ctx context.Context, w io.Writer, user *models.User) error { if err != nil { return fmt.Errorf("Failed to get org: %v", err) } - var syncString string + var lastSync time.Time sync, err := org.FieldseekerSyncs(sm.OrderBy("created")).One(ctx, PGInstance.BobDB) if err != nil { - //return fmt.Errorf("Failed to get sync: %v", err) - syncString = "never" + return fmt.Errorf("Failed to get sync: %v", err) } else { - syncString = sync.Created.String() + lastSync = sync.Created } inspectionCount, err := org.FSMosquitoinspections().Count(ctx, PGInstance.BobDB) if err != nil { @@ -146,7 +151,7 @@ func htmlDashboard(ctx context.Context, w io.Writer, user *models.User) error { CountInspections: int(inspectionCount), CountMosquitoSources: int(sourceCount), CountServiceRequests: int(serviceCount), - LastSync: syncString, + LastSync: lastSync, Org: org.Name.MustGet(), RecentRequests: requests, User: User{ @@ -226,6 +231,36 @@ func htmlReportUpdate(w io.Writer, code string) error { return reportUpdate.ExecuteTemplate(w, data) } +func htmlServiceRequest(w io.Writer) error { + data := ContentPlaceholder{} + return serviceRequest.ExecuteTemplate(w, data) +} + +func htmlServiceRequestDetail(w io.Writer, code string) error { + data := ContentPlaceholder{} + return serviceRequestDetail.ExecuteTemplate(w, data) +} + +func htmlServiceRequestLocation(w io.Writer) error { + data := ContentPlaceholder{} + return serviceRequestLocation.ExecuteTemplate(w, data) +} + +func htmlServiceRequestMosquito(w io.Writer) error { + data := ContentPlaceholder{} + return serviceRequestMosquito.ExecuteTemplate(w, data) +} + +func htmlServiceRequestPool(w io.Writer) error { + data := ContentPlaceholder{} + return serviceRequestPool.ExecuteTemplate(w, data) +} + +func htmlServiceRequestUpdates(w io.Writer) error { + data := ContentPlaceholder{} + return serviceRequestUpdates.ExecuteTemplate(w, data) +} + func htmlSignin(w io.Writer, errorCode string) error { data := ContentSignin{ InvalidCredentials: errorCode == "invalid-credentials", @@ -311,6 +346,7 @@ func timeSince(t time.Time) string { diff := now.Sub(t) hours := diff.Hours() + slog.Info("time since", slog.String("t", t.String()), slog.Float64("hours", hours)) if hours < 1 { minutes := diff.Minutes() return fmt.Sprintf("%d minutes ago", int(minutes)) diff --git a/main.go b/main.go index ebe69626..939fcadd 100644 --- a/main.go +++ b/main.go @@ -71,6 +71,12 @@ func main() { r.Get("/report/{code}/evidence", getReportEvidence) r.Get("/report/{code}/schedule", getReportSchedule) r.Get("/report/{code}/update", getReportUpdate) + r.Get("/service-request", getServiceRequest) + r.Get("/service-request/{code}", getServiceRequestDetail) + r.Get("/service-request-location", getServiceRequestLocation) + r.Get("/service-request-mosquito", getServiceRequestMosquito) + r.Get("/service-request-pool", getServiceRequestPool) + r.Get("/service-request-updates", getServiceRequestUpdates) r.Post("/signin", postSignin) r.Get("/signup", getSignup) r.Post("/signup", postSignup) diff --git a/templates/base.html b/templates/base.html index b71d267f..b60842b0 100644 --- a/templates/base.html +++ b/templates/base.html @@ -3,8 +3,14 @@ + {{template "title" .}} - Nidus Sync + + + diff --git a/templates/dashboard.html b/templates/dashboard.html index eb848191..58d5fa7b 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -69,7 +69,7 @@ body {

- Last updated: {{ .LastSync }} + Last updated: {{ .LastSync | timeSince }}

@@ -85,7 +85,7 @@ body {
Last Data Refresh
-

{{ .LastSync }}

+

{{ .LastSync | timeSince }}

Last sync: 12:45 PM

diff --git a/templates/empty.html b/templates/empty.html new file mode 100644 index 00000000..dde29a29 --- /dev/null +++ b/templates/empty.html @@ -0,0 +1,7 @@ +{{template "base.html" .}} + +{{define "title"}}Dash{{end}} +{{define "style"}} +{{end}} +{{define "content"}} +{{end}} diff --git a/templates/service-request-detail.html b/templates/service-request-detail.html new file mode 100644 index 00000000..732a42ba --- /dev/null +++ b/templates/service-request-detail.html @@ -0,0 +1,316 @@ +{{template "base.html" .}} + +{{define "title"}}Dash{{end}} +{{define "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; +} +{{end}} +{{define "content"}} + +
+
+
+
+

[District Name]

+
+
+ +
+
+
+
+ + +
+
+ +
+
+

Report #MMD-2023-12345

+
+
+ + Green Pool + +
+
+ + +
+
+
+ +
+
Submitted
+
+
+
+ +
+
Accepted
+
+
+
+ +
+
Scheduled
+
+
+
+ +
+
Complete
+
+
+ +
+ +
+
+
+
Location
+
+
+ +
+
+ +

Map of Report Location

+
+
+ + +
+
Address
+

123 Mosquito Ave, Lakeside, CA 92040

+

+ 32.8573° N, 116.9222° W +

+
+
+
+
+ + +
+
+
+
Report Details
+
+
+
+
+
Report Source
+

Phone Call

+
+
+
Report Date
+

October 15, 2023 at 2:45 PM

+
+
+ +
+
Description
+

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.

+
+ +
+
+
Pool Status
+

Stagnant/Green

+
+
+
Scheduled Appointment
+

October 20, 2023, 9:00 AM - 11:00 AM

+
+
+ +
+
Contact Information
+
+
+

Reported By: John Smith

+

Phone: (555) 123-4567

+
+
+

Email: john.smith@example.com

+

Preferred Contact: Phone

+
+
+
+
+
+
+
+ + +
+
+
+
+
Notes & Updates
+ 3 Notes +
+
+
+
Added by System on Oct 15, 2023, 2:45 PM
+
Report created via phone call to district office.
+
+
+
Added by Sarah Johnson (Office Staff) on Oct 16, 2023, 9:30 AM
+
Verified location information. Property appears to be vacant according to county records. Left voicemail with property management company listed in county database.
+
+
+
Added by Mike Davis (Technician) on Oct 18, 2023, 11:15 AM
+
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.
+
+
+
+
+
+ + +
+
+
+
+
Next Steps
+

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.

+

Note: You will receive a notification when the status of this report changes.

+
+
+
+
+ + +
+
+
+
+
Add Information
+
+
+

Do you have additional information about this report? Add it below to update the technician.

+
+
+ + +
+
+ + +
Provide your phone number if you'd like to be contacted about this update.
+
+ +
+
+
+
+
+ + + +
+
+ + + +{{end}} diff --git a/templates/service-request-location.html b/templates/service-request-location.html new file mode 100644 index 00000000..42be3f7f --- /dev/null +++ b/templates/service-request-location.html @@ -0,0 +1,365 @@ +{{template "base.html" .}} + +{{define "title"}}Dash{{end}} +{{define "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; +} +{{end}} +{{define "content"}} + +
+
+
+
+

[District Name]

+
+
+ +
+
+
+
+ + +
+
+ +
+
+

Lookup Reports by Location

+

Find reports and mosquito activity in your area

+
+
+ + +
+
+
+
+
How to use this tool
+

You can either enter an address in the search box or navigate the map by dragging and zooming to find reports in your area. The table below will update automatically to show reports within the current map view.

+
+
+
+
+ + +
+
+ +
+
+
Search by Address
+
+
+
+
+ +
+ + +
+
+
+ + +
+
+ +
+ Currently showing reports within 1 mile of map center +
+
+
+
+ +
+ +
+
+ +

Interactive Map Area

+

The map will display here and allow you to navigate the area

+
+ + +
+ + + +
+ + +
+
Current View
+
Lakeside, CA
+
32.857° N, 116.922° W
+
+
+
+
+ + +
+
+
+
+
Reports in This Area
+ Showing 12 of 37 total reports +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeLocationSubmittedStatusReport ID
+ + + + Green Pool + 123 Mosquito Ave + Oct 15, 2023 +
5 days ago
+
+ Scheduled + + MMD-2023-12345 +
+ + + + Mosquito Nuisance + 456 Lake Dr + Oct 12, 2023 +
8 days ago
+
+ Complete + + MMD-2023-12341 +
+ + + + Fish Request + 789 Creek Rd + Oct 18, 2023 +
2 days ago
+
+ Acknowledged + + MMD-2023-12350 +
+ + + + Mosquito Nuisance + 101 Pond Ln + Sep 25, 2023 +
25 days ago
+
+ Complete + + MMD-2023-12289 +
+ + + + Green Pool + 202 Highland Ave + Oct 19, 2023 +
1 day ago
+
+ Submitted + + MMD-2023-12356 +
+ + + + Green Pool + 303 Marsh Way + Aug 15, 2023 +
2 months ago
+
+ Complete + + MMD-2023-12056 +
+
+
+ +
+
+
+ + + +
+
+ + + +{{end}} diff --git a/templates/service-request-mosquito.html b/templates/service-request-mosquito.html new file mode 100644 index 00000000..1e8eeddc --- /dev/null +++ b/templates/service-request-mosquito.html @@ -0,0 +1,539 @@ +{{template "base.html" .}} + +{{define "title"}}Dash{{end}} +{{define "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'; + } + }); + }); +}); +{{end}} +{{define "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; +} +{{end}} +{{define "content"}} + +
+
+
+
+

[District Name]

+
+
+ +
+
+
+
+ + +
+
+ +
+
+

Report Mosquito Nuisance

+

Help us identify mosquito activity in your area

+
+
+ + +
+
+ +
+
+ + +
+ +
+
+ +

Mosquito Activity Information

+ optional +
+

The time when mosquitoes are active can help us identify the species and likely breeding sources.

+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+ + +
+ + +
+
+
Minor
+ Occasional mosquito +
+
+
Moderate
+ Regular presence +
+
+
Severe
+ Many mosquitoes +
+
+
+ Current selection: 3/5 +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ +

Potential Mosquito Sources

+ optional +
+

Have you noticed any of these common mosquito breeding sources in your area?

+ +
+
+
Did you know?
+

Mosquitoes can breed in as little as a bottle cap of water! Eliminating standing water is the most effective way to reduce mosquito populations.

+
+
+ +
+ +
+
+
+
+ +
+
Stagnant Water
+

Green pools, ponds, fountains, or birdbaths that aren't maintained

+
+ + +
+
+
+
+ + +
+
+
+
+ +
+
Containers
+

Buckets, planters, toys, tires, or any items that collect rainwater

+
+ + +
+
+
+
+ + +
+
+
+
+ +
+
Roof & Gutters
+

Clogged gutters, flat roofs, or AC units that collect water

+
+ + +
+
+
+
+
+ + + +
+
+ + +
+
+
+ + +
+
+ +

Inspection Request

+
+

Would you like our technicians to inspect for potential mosquito sources?

+ +
+
+
+
Property Inspection
+

Request a technician to inspect your property for mosquito sources. We'll contact you to schedule a convenient time.

+
+ + +
+
+
+ +
+
+
Neighborhood Inspection
+

Request a general inspection of your neighborhood. We'll survey the area for potential mosquito breeding sources.

+
+ + +
+
+
+
+ + + +
+ + +
+
+ +

Location & Contact Information

+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
We'll use this to send you a confirmation and follow-up information.
+
+
+
+ + +
+
+ +

Additional Information

+ optional +
+ +
+
+ + +
+
+
+ + +
+
+
+

Thank you for reporting this mosquito issue.

+

After submission, you'll receive a confirmation with a report ID and further information.

+
+
+ +
+
+
+
+ + + +
+
+ + + +{{end}} diff --git a/templates/service-request-pool.html b/templates/service-request-pool.html new file mode 100644 index 00000000..00603199 --- /dev/null +++ b/templates/service-request-pool.html @@ -0,0 +1,522 @@ +{{template "base.html" .}} + +{{define "title"}}Dash{{end}} +{{define "script"}} +document.addEventListener('DOMContentLoaded', function() { + const photoUpload = document.getElementById('photoUpload'); + const imagePreview = document.getElementById('imagePreview'); + const dropArea = document.getElementById('dropArea'); + + // Handle file selection + photoUpload.addEventListener('change', handleFileSelect); + + // Handle drag and drop + dropArea.addEventListener('dragover', function(e) { + e.preventDefault(); + dropArea.style.backgroundColor = '#e9ecef'; + }); + + dropArea.addEventListener('dragleave', function() { + dropArea.style.backgroundColor = '#f8f9fa'; + }); + + dropArea.addEventListener('drop', function(e) { + e.preventDefault(); + dropArea.style.backgroundColor = '#f8f9fa'; + + if (e.dataTransfer.files.length) { + handleFiles(e.dataTransfer.files); + } + }); + + function handleFileSelect(e) { + const files = e.target.files; + handleFiles(files); + } + + function handleFiles(files) { + const maxFiles = 5; + const currentFiles = imagePreview.querySelectorAll('.preview-item').length; + + for (let i = 0; i < files.length && currentFiles + i < maxFiles; i++) { + const file = files[i]; + + if (!file.type.match('image.*')) { + continue; + } + + const reader = new FileReader(); + + reader.onload = (function(theFile) { + return function(e) { + const previewItem = document.createElement('div'); + previewItem.className = 'preview-item'; + + const img = document.createElement('img'); + img.src = e.target.result; + + const removeBtn = document.createElement('div'); + removeBtn.className = 'preview-remove'; + removeBtn.innerHTML = 'x'; + removeBtn.addEventListener('click', function() { + imagePreview.removeChild(previewItem); + }); + + previewItem.appendChild(img); + previewItem.appendChild(removeBtn); + imagePreview.appendChild(previewItem); + }; + })(file); + + reader.readAsDataURL(file); + } + + photoUpload.value = ''; + } +}); +{{end}} +{{define "style"}} + .district-logo { + max-height: 80px; + width: auto; + } + .map-container { + height: 300px; + background-color: #e9ecef; + border-radius: 5px; + border: 1px solid #dee2e6; + display: flex; + align-items: center; + justify-content: center; + margin-top: 10px; + } + .file-upload-container { + border: 2px dashed #dee2e6; + border-radius: 5px; + padding: 20px; + text-align: center; + background-color: #f8f9fa; + transition: background-color 0.3s; + } + .file-upload-container:hover { + background-color: #e9ecef; + cursor: pointer; + } + .image-preview { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 15px; + } + .preview-item { + width: 100px; + height: 100px; + border-radius: 5px; + overflow: hidden; + position: relative; + } + .preview-item img { + width: 100%; + height: 100%; + object-fit: cover; + } + .preview-remove { + position: absolute; + top: 5px; + right: 5px; + background-color: rgba(0,0,0,0.5); + color: white; + border-radius: 50%; + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + cursor: pointer; + } + .form-section { + margin-bottom: 2.5rem; + padding-bottom: 2rem; + border-bottom: 1px solid #dee2e6; + } + .form-section:last-child { + border-bottom: none; + margin-bottom: 1rem; + padding-bottom: 0; + } + .section-heading { + margin-bottom: 1.5rem; + display: flex; + align-items: center; + } + .section-heading i { + margin-right: 10px; + font-size: 1.5rem; + color: #0d6efd; + } + .optional-label { + font-size: 0.875rem; + color: #6c757d; + font-weight: normal; + margin-left: 8px; + } + .submit-container { + background-color: #f8f9fa; + padding: 20px; + border-radius: 5px; + margin-top: 2rem; + } +{{end}} +{{define "content"}} + +
+
+
+
+

[District Name]

+
+
+ +
+
+
+
+ + +
+
+ +
+
+

Report a Green Pool or Mosquito Source

+

Help us locate and treat potential mosquito breeding sources in your area

+
+
+ + +
+
+ +
+
+ + +
+ +
+
+ +

Photos

+ optional +
+

Photos help us identify the severity of the issue and may contain location data that can help us find the source.

+ +
+ +
+ +
+

Drag and drop photos here

+

- or -

+ +

You can upload multiple photos (maximum 5)

+
+ + +
+ +
+
+ + +
+
+ +

Location

+ optional +
+

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.

+ +
+
+ + +
+
+ +

You can also click on the map to mark the location precisely

+
+
+ +

Interactive Map

+

Click to set the location of the mosquito source

+
+
+
+ + +
+
+ +

Source Details

+ optional +
+ +
+
+ + +
+ +
+ +
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ +

Access Information

+ optional +
+

Please provide any details about how to access the mosquito source. This helps our technicians when they visit the site.

+ +
+
+ + +
+
+ +
+
+ +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+ +

Contact Information

+ optional +
+ + +
Property Owner Information (if known)
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
Your Contact Information (for updates)
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+ +

Additional Information

+ optional +
+

Please provide any other information that might help us address this mosquito source.

+ +
+
+ + +
+
+
+ + +
+
+
+

Thank you for helping us keep our community safe from mosquito-borne illnesses.

+

After submission, you will receive a confirmation with a report ID for tracking purposes.

+
+
+ +
+
+
+
+ + + +
+
+ + + + + + + +{{end}} diff --git a/templates/service-request-updates.html b/templates/service-request-updates.html new file mode 100644 index 00000000..7356b82a --- /dev/null +++ b/templates/service-request-updates.html @@ -0,0 +1,160 @@ +{{template "base.html" .}} + +{{define "title"}}Dash{{end}} +{{define "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; + } +} +{{end}} +{{define "content"}} + +
+
+
+
+

[District Name]

+
+
+ +
+
+
+
+ + +
+ +
+
+

Check Status or Follow-up

+
+
+ + +
+
+
+
+

+ Choose one of the following options to check on mosquito activity or follow up on a previous report. +

+
+
+ +
+ +
+
+
+
+ + + + +
+

Look up by Report ID

+

+ If you have a report ID from a previous request, enter it below to view the details and current status. +

+ +
+
+ + +
Example: MMD-2023-12345
+
+ +
+
+
+
+ + +
+
+
+ + +
+
+
+
+ + + + +
+

Look up by Location

+

+ Don't have a report ID? You can check mosquito activity and reports in your area by providing your location information. +

+

+ This option will guide you through selecting your location to find relevant information about mosquito activity near you. +

+ +
+
+
+
+ + + +
+
+
+ + + +{{end}} diff --git a/templates/service-request.html b/templates/service-request.html new file mode 100644 index 00000000..139fe4cc --- /dev/null +++ b/templates/service-request.html @@ -0,0 +1,121 @@ +{{template "base.html" .}} + +{{define "title"}}Dash{{end}} +{{define "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; +} +{{end}} +{{define "content"}} + +
+
+
+
+

[District Name]

+
+
+ +
+
+
+
+ + +
+ +
+
+
+
+

Welcome to Our Mosquito Management Services

+

+ 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. +

+
+
+
+
+ + +
+
+

How Can We Help You Today?

+
+ +
+
+
+
+ + + +
+

Follow-up or Check Status

+

Check on a previous request or view current mosquito activity in your area.

+ Get Updates +
+
+
+ + +
+
+
+
+ + + +
+

Report a Green Pool

+

Report stagnant water sources like abandoned pools that may breed mosquitoes.

+ Report Source +
+
+
+ + +
+
+
+
+ + + +
+

Report Mosquito Nuisance

+

Report areas with high adult mosquito activity causing discomfort or concern.

+ Report Problem +
+
+
+
+
+
+
+ + + +{{end}}