From b78837cbab02030fab61abfc4edacefe63f3640c Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 17 Feb 2026 19:51:50 +0000 Subject: [PATCH] Add service request detail page from service request list --- html/func.go | 49 +++ .../{mock => }/service-request-detail.html | 41 +-- html/template/sync/service-request-list.html | 329 +++++++----------- sync/routes.go | 12 +- sync/service-request.go | 105 +++++- 5 files changed, 280 insertions(+), 256 deletions(-) rename html/template/sync/{mock => }/service-request-detail.html (91%) diff --git a/html/func.go b/html/func.go index 4b687258..9e745b15 100644 --- a/html/func.go +++ b/html/func.go @@ -20,6 +20,7 @@ import ( func addFuncMap(t *template.Template) { funcMap := template.FuncMap{ "bigNumber": bigNumber, + "duration": duration, "hasPassed": hasPassed, "html": unescapeHTML, "json": unescapeJS, @@ -53,6 +54,54 @@ func bigNumber(n int) string { return result.String() } +func duration(d time.Duration) string { + seconds := int(d.Seconds()) + + if seconds < 60 { + if seconds == 1 { + return "1 second ago" + } + return fmt.Sprintf("%d seconds ago", seconds) + } + + minutes := int(d.Minutes()) + if minutes < 60 { + if minutes == 1 { + return "1 minute ago" + } + return fmt.Sprintf("%d minutes ago", minutes) + } + + hours := int(d.Hours()) + if hours < 24 { + if hours == 1 { + return "1 hour ago" + } + return fmt.Sprintf("%d hours ago", hours) + } + + days := hours / 24 + if days < 30 { + if days == 1 { + return "1 day ago" + } + return fmt.Sprintf("%d days ago", days) + } + + months := days / 30 + if months < 12 { + if months == 1 { + return "1 month ago" + } + return fmt.Sprintf("%d months ago", months) + } + + years := days / 365 + if years == 1 { + return "1 year ago" + } + return fmt.Sprintf("%d years ago", years) +} func publicReportID(s string) string { if len(s) != 12 { return s diff --git a/html/template/sync/mock/service-request-detail.html b/html/template/sync/service-request-detail.html similarity index 91% rename from html/template/sync/mock/service-request-detail.html rename to html/template/sync/service-request-detail.html index 71745c3e..7521517c 100644 --- a/html/template/sync/mock/service-request-detail.html +++ b/html/template/sync/service-request-detail.html @@ -1,6 +1,6 @@ -{{ template "sync/layout/base.html" . }} +{{ template "sync/layout/authenticated.html" . }} -{{ define "title" }}Dash{{ end }} +{{ define "title" }}Service Request Detail{{ end }} {{ define "extraheader" }} {{ end }} {{ define "content" }} - -
-
-
-
-

[District Name]

-
-
- -
-
-
-
- -
@@ -366,22 +347,4 @@
- - - {{ end }} diff --git a/html/template/sync/service-request-list.html b/html/template/sync/service-request-list.html index 51c0804e..3c1b8964 100644 --- a/html/template/sync/service-request-list.html +++ b/html/template/sync/service-request-list.html @@ -137,134 +137,82 @@ - - 2 hours ago - 2 hours ago - - Schedule Appointment - - 123 Main St, Anytown - 3 - - Biting Nuisance - - - - - - - - 5 hours ago - 1 hour ago - - Answer Question - - 456 Elm St, Anytown - 1 - - Standing Water - - - - - - - - 1 day ago - 3 hours ago - Add to Route - 789 Oak Ave, Anytown - 4 - - Active Breeding - - - - - - - - 2 days ago - 6 hours ago - Review - 101 Pine Lane, Anytown - 2 - - Standing Water - - - - - - - - 3 days ago - 1 day ago - - Confirm Details - - 202 Maple Dr, Anytown - 0 - - Biting Nuisance - - - - - - + {{ range .C.ActiveRequests }} + + {{ .Created|timeRelative }} + {{ .LastAction|timeRelative }} + {{ if eq .NextStep "schedule-appointment" }} + + Schedule Appointment + + {{ else if eq .NextStep "answer-question" }} + + Answer + Question + + {{ else if eq .NextStep "add-to-route" }} + + Add to + Route + + {{ else if eq .NextStep "review" }} + + Review + + {{ else if eq .NextStep "confirm-details" }} + + Confirm + Details + + {{ else }} + + Unknown + + {{ end }} + {{ .Address }} + {{ .PhotoCount }} + {{ if eq .Type "biting-nuisance" }} + + Biting Nuisance + + {{ else if eq .Type "standing-water" }} + + Standing Water + + {{ else if eq .Type "active-breeding" }} + + Active Breeding + + {{ else }} + + Unknown + + {{ end }} + + + + + + + {{ end }} - @@ -290,90 +238,53 @@ - - -
- - John Smith -
- - - Standing Water - - 1 day ago - 303 Cedar St, Anytown - 3 days - - - - - - -
- - Maria Garcia -
- - - Biting Nuisance - - 2 days ago - 404 Birch Ave, Anytown - 1 day - - - - - - -
- - Robert Johnson -
- - - Active Breeding - - 4 days ago - 505 Spruce Ct, Anytown - 5 days - - - - - - -
- - Sarah Lee -
- - - Standing Water - - 1 week ago - 606 Willow Way, Anytown - 2 days - - - - + {{ range .C.ClosedRequests }} + + +
+ + {{ .Employee }} +
+ + {{ if eq .Type "standing-water" }} + + Standing Water + + {{ else if eq .Type "biting-nuisance" }} + + Biting Nuisance + + {{ else if eq .Type "active-breeding" }} + + Active Breeding + + {{ else }} + + Unknown + + {{ end }} + + + {{ .Closed|timeRelative }} + {{ .Address }}/td> + {{ .TimeToResolution|duration }} + + + + + {{ end }} diff --git a/sync/routes.go b/sync/routes.go index 4289c806..b6f72554 100644 --- a/sync/routes.go +++ b/sync/routes.go @@ -30,7 +30,6 @@ func Router() chi.Router { addMock(r, "/mock/report/{code}/evidence", "sync/mock/report-evidence.html") addMock(r, "/mock/report/{code}/schedule", "sync/mock/report-schedule.html") addMock(r, "/mock/report/{code}/update", "sync/mock/report-update.html") - addMock(r, "/mock/service-request/{code}", "sync/mock/service-request-detail.html") // Utility endpoints r.Get("/oauth/refresh", getOAuthRefresh) @@ -56,6 +55,7 @@ func Router() chi.Router { r.Method("POST", "/pool/upload", auth.NewEnsureAuth(postPoolUpload)) r.Method("GET", "/radar", auth.NewEnsureAuth(getRadar)) r.Method("GET", "/service-request", authenticatedHandler(getServiceRequestList)) + r.Method("GET", "/service-request/{id}", authenticatedHandler(getServiceRequestDetail)) r.Method("GET", "/setting", auth.NewEnsureAuth(getSetting)) r.Method("GET", "/setting/organization", auth.NewEnsureAuth(getSettingOrganization)) r.Method("GET", "/setting/integration", auth.NewEnsureAuth(getSettingIntegration)) @@ -83,16 +83,16 @@ func (e *errorWithStatus) Error() string { return e.Message } -type handlerFunction func(context.Context, *models.User) (string, interface{}, *errorWithStatus) +type handlerFunction[T any] func(context.Context, *models.User) (string, T, *errorWithStatus) type wrappedHandler func(http.ResponseWriter, *http.Request) -type contentAuthenticated struct { - C interface{} +type contentAuthenticated[T any] struct { + C T URL ContentURL User User } // w http.ResponseWriter, r *http.Request, u *models.User) { -func authenticatedHandler(f handlerFunction) http.Handler { +func authenticatedHandler[T any](f handlerFunction[T]) http.Handler { return auth.NewEnsureAuth(func(w http.ResponseWriter, r *http.Request, u *models.User) { ctx := r.Context() userContent, err := contentForUser(ctx, u) @@ -106,7 +106,7 @@ func authenticatedHandler(f handlerFunction) http.Handler { http.Error(w, err.Error(), e.Status) return } - html.RenderOrError(w, template, contentAuthenticated{ + html.RenderOrError(w, template, contentAuthenticated[T]{ C: content, URL: newContentURL(), User: userContent, diff --git a/sync/service-request.go b/sync/service-request.go index f81021f2..57a6ae3a 100644 --- a/sync/service-request.go +++ b/sync/service-request.go @@ -2,13 +2,114 @@ package sync import ( "context" + "time" + "github.com/Gleipnir-Technology/nidus-sync/config" "github.com/Gleipnir-Technology/nidus-sync/db/models" ) -type contentServiceRequestPlaceholder struct{} +type contentActiveServiceRequest struct { + Created time.Time + LastAction time.Time + NextStep string + Address string + PhotoCount uint + Type string + URLDetail string +} +type contentClosedServiceRequest struct { + Employee string + Type string + Closed time.Time + Address string + TimeToResolution time.Duration + URLDetail string +} +type contentServiceRequestDetail struct{} +type contentServiceRequestList struct { + ActiveRequests []contentActiveServiceRequest + ClosedRequests []contentClosedServiceRequest +} +func getServiceRequestDetail(ctx context.Context, user *models.User) (string, interface{}, *errorWithStatus) { + content := contentServiceRequestDetail{} + return "sync/service-request-detail.html", content, nil +} func getServiceRequestList(ctx context.Context, user *models.User) (string, interface{}, *errorWithStatus) { - content := contentServiceRequestPlaceholder{} + now := time.Now() + content := contentServiceRequestList{ + ActiveRequests: []contentActiveServiceRequest{ + contentActiveServiceRequest{ + Created: now.Add(-2 * time.Hour), + LastAction: now.Add(-2 * time.Hour), + NextStep: "schedule-appointment", + Address: "123 Main St, Anytown", + PhotoCount: 3, + Type: "biting-nuisance", + URLDetail: config.MakeURLNidus("/service-request/1"), + }, + contentActiveServiceRequest{ + Created: now.Add(-5 * time.Hour), + LastAction: now.Add(-1 * time.Hour), + NextStep: "answer-question", + Address: "456 Elm St, Anytown", + PhotoCount: 1, + Type: "standing-water", + URLDetail: config.MakeURLNidus("/service-request/1"), + }, + contentActiveServiceRequest{ + Created: now.Add(-1 * 24 * time.Hour), + LastAction: now.Add(-3 * time.Hour), + NextStep: "add-to-route", + Address: "789 Oak St, Anytown", + PhotoCount: 4, + Type: "active-breeding", + URLDetail: config.MakeURLNidus("/service-request/1"), + }, + contentActiveServiceRequest{ + Created: now.Add(-2 * 24 * time.Hour), + LastAction: now.Add(-6 * time.Hour), + NextStep: "review", + Address: "101 Pine Ln, Anytown", + PhotoCount: 0, + Type: "standing-water", + URLDetail: config.MakeURLNidus("/service-request/1"), + }, + }, + ClosedRequests: []contentClosedServiceRequest{ + contentClosedServiceRequest{ + Employee: "John Smith", + Type: "standing-water", + Closed: now.Add(-1 * 24 * time.Hour), + Address: "303 Ceder St, Anytown", + TimeToResolution: 3 * 24 * time.Hour, + URLDetail: config.MakeURLNidus("/service-request/2"), + }, + contentClosedServiceRequest{ + Employee: "Maria Garcia", + Type: "biting-nuisance", + Closed: now.Add(-2 * 24 * time.Hour), + Address: "404 Birch St, Anytown", + TimeToResolution: 1 * 24 * time.Hour, + URLDetail: config.MakeURLNidus("/service-request/2"), + }, + contentClosedServiceRequest{ + Employee: "Robert Johnson", + Type: "active-breeding", + Closed: now.Add(-4 * 24 * time.Hour), + Address: "404 Birch St, Anytown", + TimeToResolution: 5 * 24 * time.Hour, + URLDetail: config.MakeURLNidus("/service-request/2"), + }, + contentClosedServiceRequest{ + Employee: "Sarah Lee", + Type: "standing-water", + Closed: now.Add(-7 * 24 * time.Hour), + Address: "606 Willow Way, Anytown", + TimeToResolution: 2 * 24 * time.Hour, + URLDetail: config.MakeURLNidus("/service-request/2"), + }, + }, + } return "sync/service-request-list.html", content, nil }