Skip to content

RE1-T105 More custom maps work#302

Merged
ucswift merged 3 commits intomasterfrom
develop
Mar 18, 2026
Merged

RE1-T105 More custom maps work#302
ucswift merged 3 commits intomasterfrom
develop

Conversation

@ucswift
Copy link
Copy Markdown
Member

@ucswift ucswift commented Mar 17, 2026

Summary by CodeRabbit

  • New Features

    • Server-side geocoding endpoints (forward/reverse) and client-side use for address/coordinate lookups.
    • Route enhancements: add/delete stops and link calls.
    • Indoor maps: floor and zone search/delete flows.
    • Arabic language added.
  • Refactor

    • Replaced Google Maps with Leaflet/OpenStreetMap + OSRM routing.
    • Switched many assets from CDNs to local libraries.
  • Localization

    • Wide UI internationalization across mapping, routes, indoor maps, dispatch, and related views.

@request-info
Copy link
Copy Markdown

request-info bot commented Mar 17, 2026

Thanks for opening this, but we'd appreciate a little more information. Could you update it with more details?

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

Adds server-side geocoding endpoints and configuration for OSM/OSRM, replaces Google Maps with Leaflet/OSRM across UI and JS, introduces broad localization, migrates CDN assets to local libs, updates route/indoor-map controllers and client-side route/stop/geofence interactions, and adjusts permission/claims logic.

Changes

Cohort / File(s) Summary
Mapping Config & Supported Locales
Core/Resgrid.Config/MappingConfig.cs, Core/Resgrid.Localization/SupportedLocales.cs
Added NominatimUrl and OsrmUrl constants; added Arabic ("ar") to supported locales.
Localization Resource Shells
Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.cs, Core/Resgrid.Localization/Areas/User/IndoorMaps/IndoorMaps.cs, Core/Resgrid.Localization/Areas/User/Mapping/Mapping.cs, Core/Resgrid.Localization/Areas/User/Routes/Routes.cs
Added empty localization resource classes (placeholders for string resources).
Permissions & Claims
Core/Resgrid.Model/PermissionTypes.cs, Providers/Resgrid.Providers.Claims/ClaimsLogic.cs
Removed CreateRoute enum value and shifted ManageRoutes; updated claims assignment logic for route permissions based on admin/group roles and new semantics.
Geocoding API & Models
Web/Resgrid.Web.Services/Controllers/v4/GeocodingController.cs, Web/Resgrid.Web.Services/Models/v4/Geocoding/GeocodingResults.cs, Web/Resgrid.Web.Services/Resgrid.Web.Services.xml, Web/Resgrid.Web.Services/appsettings.json
Added GeocodingController with ForwardGeocode and ReverseGeocode; added request/response models; documented public API types; added rate-limit rules for new endpoints.
Geo Provider Minor Change
Providers/Resgrid.Providers.Geo/GeoLocationProvider.cs
Captured exception into a local variable in catch (no functional change beyond diagnostic capture).
Controller Additions & Hardening
Web/Resgrid.Web/Areas/User/Controllers/.../CustomMapsController.cs, .../DispatchController.cs, .../IndoorMapsController.cs, .../RoutesController.cs
Added antiforgery validation to POSTs; null/permission checks for layer/map access; injected services (IIndoorMapService, ICallsService); added SearchZones, DeleteFloor, GetCallsForLinking, AddStop, DeleteStop actions; applied route-related authorization attributes.
Views: Localization & Asset Migration (many)
Web/Resgrid.Web/Areas/User/Views/... (CustomMaps, IndoorMaps, Mapping, Routes, Dispatch, Calendar, Reports, Account, Shared, etc.)
Injected IStringLocalizer across many Razor views and replaced hard-coded strings with localized lookups; migrated numerous CDN asset references (Bootstrap, jQuery, Plyr, Slick, Twilio) to local ~/lib paths; exposed mapping config vars (osmTileUrl, osmTileAttribution, nominatimUrl, osrmUrl) to client scripts.
Client JS: Geocoding & Mapping Rewrites
Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.*.js, .../groups/resgrid.groups.geofence.js, .../mapping/resgrid.mapping.viewType.js, .../custommaps/*.js, .../indoormaps/*.js, .../routes/*.js, .../resgrid.user.js
Replaced Google Maps geocoding and routing with server-side geocoding endpoints + Leaflet and OSRM client flows; migrated drawing from Leaflet.draw to Geoman; updated map tile/attribution variables to use config; enhanced route/stop management scripts (stop picker, validation, HTML escaping, What3Words integration).
Client JS: Smaller UI/UX Edits
Web/Resgrid.Web/wwwroot/js/app/internal/custommaps/resgrid.custommaps.regioneditor.js, .../indoormaps/*.js, .../routes/resgrid.routes.instancedetail.js, .../resgrid.routes.view.js
Replaced hardcoded OSM URLs/attribution with config variables; strengthened coordinate validation and popup sanitization.
Localization UI: Language Dropdown
Web/Resgrid.Web/Areas/User/Views/Shared/_TopNavbar.cshtml, Web/Resgrid.Web/Views/Account/LogOn.cshtml, Web/Resgrid.Web/wwwroot/js/app/internal/resgrid.user.js
Added Arabic ("ar") option and flag mapping in UI and client-side flag map.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant GeocodingController
    participant IGeoLocationProvider
    participant ExternalService as Nominatim/OSRM

    rect rgba(173, 216, 230, 0.5)
    Note over Client,ExternalService: Forward Geocoding Flow
    Client->>GeocodingController: GET /api/v4/Geocoding/ForwardGeocode?address=...
    activate GeocodingController
    GeocodingController->>IGeoLocationProvider: GetLatLonFromAddress(address)
    activate IGeoLocationProvider
    IGeoLocationProvider->>ExternalService: HTTP Request (Nominatim)
    activate ExternalService
    ExternalService-->>IGeoLocationProvider: Return lat,lon
    deactivate ExternalService
    IGeoLocationProvider-->>GeocodingController: Return "lat,lng"
    deactivate IGeoLocationProvider
    GeocodingController->>GeocodingController: Parse lat, lon
    GeocodingController-->>Client: 200 OK with ForwardGeocodeResult
    deactivate GeocodingController
    end

    rect rgba(255, 182, 193, 0.5)
    Note over Client,ExternalService: Reverse Geocoding Flow
    Client->>GeocodingController: GET /api/v4/Geocoding/ReverseGeocode?lat=...&lon=...
    activate GeocodingController
    GeocodingController->>IGeoLocationProvider: GetAddressFromLatLong(lat,lon)
    activate IGeoLocationProvider
    IGeoLocationProvider->>ExternalService: HTTP Request (Nominatim)
    activate ExternalService
    ExternalService-->>IGeoLocationProvider: Return address string
    deactivate ExternalService
    IGeoLocationProvider-->>GeocodingController: Return address
    deactivate IGeoLocationProvider
    GeocodingController-->>Client: 200 OK with ReverseGeocodeResult
    deactivate GeocodingController
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'RE1-T105 More custom maps work' is vague and does not clearly convey the scope of changes. The PR encompasses extensive changes across maps, routes, geocoding, localization, CDN migrations, and permissions—far broader than just 'custom maps work'. Use a more descriptive title that captures the main focus, such as 'Add geocoding API, localize views, and migrate CDN assets to local libraries' or break into smaller, more focused PRs.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
Web/Resgrid.Web/Areas/User/Views/Subscription/ViewInvoice.cshtml (1)

18-22: ⚠️ Potential issue | 🟡 Minor

Redundant fallback configuration.

Both href and asp-fallback-href point to the same local path (~/lib/bootstrap/dist/css/bootstrap.min.css). This defeats the purpose of the fallback mechanism—if the primary source fails, the fallback will also fail. Consider pointing asp-fallback-href to a CDN (e.g., the original MaxCDN/BootstrapCDN URL) to provide actual resilience.

Additionally, crossorigin="anonymous" is unnecessary for same-origin local resources.

Suggested fix
-    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"
-          crossorigin="anonymous"
-          asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
+    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"
+          asp-fallback-href="https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css"
+          asp-fallback-test-class="hidden"
+          asp-fallback-test-property="visibility" asp-fallback-test-value="hidden" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/Subscription/ViewInvoice.cshtml` around
lines 18 - 22, The <link> element in ViewInvoice.cshtml currently uses the same
local path for both href and asp-fallback-href and includes
crossorigin="anonymous"; update the tag so the asp-fallback-href points to a
public CDN URL for Bootstrap (e.g., the official BootstrapCDN URL for the same
version used locally) to provide a real fallback, and remove the
crossorigin="anonymous" attribute since the primary resource is served from the
same origin; keep the existing asp-fallback-test-* attributes and tests intact
to preserve the fallback detection logic.
Web/Resgrid.Web/Areas/User/Views/Dispatch/Dashboard.cshtml (1)

105-105: ⚠️ Potential issue | 🟠 Major

Remove orphaned Google Maps dependency.

The markerwithlabel.js script (line 105) is a Google Maps library that depends on the google.maps namespace. However, the Google Maps JavaScript API is not loaded in this view, and the markerwithlabel library is not referenced anywhere in the dispatch JavaScript code.

Since the dashboard now uses the Leaflet-based rg-map component (line 72), this Google Maps-specific script should be removed as dead code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/Dispatch/Dashboard.cshtml` at line 105,
Remove the orphaned Google Maps dependency by deleting the markerwithlabel
script include (the line referencing "markerwithlabel/markerwithlabel.js") from
the Dispatch Dashboard view; confirm there are no remaining references to the
Google Maps namespace or the markerwithlabel library in the dispatch JS and that
the view continues to use the Leaflet-based rg-map component (the rg-map usage
around line 72) so removing this dead Google Maps script is safe.
Web/Resgrid.Web/Areas/User/Views/Reports/StaffingReport.cshtml (1)

18-22: ⚠️ Potential issue | 🟡 Minor

Same redundant fallback configuration issue.

As with _StylePartial.cshtml, the href and asp-fallback-href both point to the same local path, making the fallback test ineffective.

🔧 Suggested fix: Remove redundant fallback attributes
-	<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"
-				crossorigin="anonymous"
-				asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
-				asp-fallback-test-class="hidden"
-				asp-fallback-test-property="visibility" asp-fallback-test-value="hidden" />
+	<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/Reports/StaffingReport.cshtml` around lines
18 - 22, The link element in StaffingReport.cshtml repeats the same local path
in href and asp-fallback-href, making the fallback test ineffective; update the
<link> for bootstrap (the tag in StaffingReport.cshtml) by removing the
redundant asp-fallback-href and related asp-fallback-test-* attributes (or
alternatively replace asp-fallback-href with a true CDN fallback URL) so the
fallback mechanism is meaningful and not pointing to the identical local file.
Web/Resgrid.Web/Views/Shared/_StylePartial.cshtml (1)

1-5: ⚠️ Potential issue | 🟡 Minor

Redundant fallback configuration.

The href and asp-fallback-href now point to the same local path (~/lib/bootstrap/dist/css/bootstrap.min.css). This renders the fallback mechanism ineffective since both will fail together if the local file is unavailable.

Consider either:

  1. Remove the asp-fallback-* attributes entirely since they provide no benefit when primary and fallback are identical
  2. Keep a CDN as primary with local fallback, or vice versa
🔧 Suggested fix: Remove redundant fallback attributes
-<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"
-          asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
-          asp-fallback-test-class="sr-only"
-          asp-fallback-test-property="position"
-          asp-fallback-test-value="absolute" />
+<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Views/Shared/_StylePartial.cshtml` around lines 1 - 5, The
link tag in _StylePartial.cshtml uses the same local path for href and
asp-fallback-href, making the asp-fallback-* attributes redundant; either remove
the asp-fallback-href, asp-fallback-test-class, asp-fallback-test-property, and
asp-fallback-test-value attributes from the <link> element to eliminate the
ineffective fallback, or change href to a CDN URL (keeping asp-fallback-href
pointing to the local ~/lib/bootstrap/dist/css/bootstrap.min.css) so the
fallback mechanism functions correctly.
Web/Resgrid.Web/Views/Shared/_ScriptsPartial.cshtml (1)

7-12: ⚠️ Potential issue | 🟡 Minor

Bootstrap fallback path directory mismatch.

The main src points to ~/lib/bootstrap/dist/js/bootstrap.min.js but the asp-fallback-src points to ~/lib/bootstrap/js/bootstrap.min.js (missing dist/ in the path). These should be consistent.

Proposed fix
 <script src="~/lib/bootstrap/dist/js/bootstrap.min.js"
-        asp-fallback-src="~/lib/bootstrap/js/bootstrap.min.js"
+        asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
         asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Views/Shared/_ScriptsPartial.cshtml` around lines 7 - 12, The
asp-fallback-src value in the <script> tag in _ScriptsPartial.cshtml mismatches
the main src (missing "dist/"); update the asp-fallback-src attribute to use the
same path as src (~/lib/bootstrap/dist/js/bootstrap.min.js) so both src and
asp-fallback-src reference the identical bootstrap file and ensure
asp-fallback-test remains unchanged.
Web/Resgrid.Web/Areas/User/Views/Mapping/EditLayer.cshtml (1)

148-152: ⚠️ Potential issue | 🔴 Critical

Avoid raw GeoJSON injection in the <script> block.

Line 151 and Line 181 both write Model.GeoJson directly into JavaScript. That payload includes user-controlled layer metadata from this editor, so a crafted value can break out of the script tag and become stored XSS. Serialize it as a JS string and JSON.parse it client-side instead.

Proposed fix
-			var geoJson = '@(Model.GeoJson)';
+			var geoJson = `@Html.Raw`(Newtonsoft.Json.JsonConvert.SerializeObject(Model.GeoJson));
-			const collection = L.geoJson(`@Html.Raw`(Model.GeoJson), {
+			const collection = L.geoJson(JSON.parse(geoJson), {
 
 			});

Also applies to: 181-185, 201-230

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/Mapping/EditLayer.cshtml` around lines 148 -
152, The view currently injects raw Model.GeoJson into JavaScript (assigned to
the geoJson variable and used elsewhere), which risks stored XSS; instead
serialize the server value into a JavaScript string literal and parse it
client-side — e.g., replace direct uses of Model.GeoJson in the script blocks
(the geoJson assignment and the other occurrences around lines referenced) with
a safe serialized expression such as Html.Raw(Json.Encode(Model.GeoJson)) (so
the output is a quoted/escaped JSON string) and then call JSON.parse(geoJson) in
client script to obtain the object; update every location that writes
Model.GeoJson directly (including the geoJson variable and the other blocks
noted) to follow this pattern.
Web/Resgrid.Web/Areas/User/Views/Dispatch/NewCall.cshtml (1)

543-547: ⚠️ Potential issue | 🟡 Minor

Duplicate declaration of newCallFormData variable.

newCallFormData is already declared at line 534. This conditional block (lines 543-547) re-declares it at line 546, which is redundant and potentially confusing. If the intent is to override the value conditionally, it should be an assignment rather than a new declaration.

🐛 Proposed fix - remove duplicate declaration
     `@if` (Model.CenterCoordinates != null && Model.CenterCoordinates.Latitude.HasValue && Model.CenterCoordinates.Longitude.HasValue)
     {
         <script type="text/javascript">
             var centerLat = @(Model.CenterCoordinates.Latitude.Value);
             var centerLng = @(Model.CenterCoordinates.Longitude.Value);
-            var newCallFormData = '@Html.Raw(Model.NewCallFormData)';
         </script>
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/Dispatch/NewCall.cshtml` around lines 543 -
547, There is a duplicate declaration of newCallFormData in the script block
that redeclares the variable already created earlier; update the block so it
does not re-declare newCallFormData (change "var newCallFormData =
'@Html.Raw(Model.NewCallFormData)';" to an assignment "newCallFormData =
'@Html.Raw(Model.NewCallFormData)';" or remove the line entirely) while leaving
centerLat and centerLng assignments intact so Model.NewCallFormData is only
assigned, not redeclared.
Web/Resgrid.Web/Areas/User/Views/IndoorMaps/ZoneEditor.cshtml (1)

107-114: ⚠️ Potential issue | 🟠 Major

Fix culture-sensitive decimal rendering and JSON-serialize all JavaScript variable assignments in this script block.

Lines 107-114 have two issues:

  1. Lines 111-114 (boundsNE, boundsSW, centerLat, centerLon) directly interpolate decimal values without .ToString("G", System.Globalization.CultureInfo.InvariantCulture). In non-en-US locales that use comma decimals, this produces invalid JavaScript array syntax.
  2. Lines 107-110 (osmTileUrl, osmTileAttribution, floorImageUrl) interpolate strings without JSON serialization. While these specific values are typically safe, using @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(...)) ensures consistent protection and follows the pattern already established at line 115.

The CustomMaps/RegionEditor.cshtml view demonstrates the correct numeric pattern with .ToString("G", InvariantCulture). Apply the same fix to ZoneEditor.cshtml, and extend it to other IndoorMaps and mapping views that lack InvariantCulture on their numeric bounds and coordinates.

Fix pattern
-        var osmTileUrl = '@Resgrid.Config.MappingConfig.GetWebsiteOSMUrl()';
-        var osmTileAttribution = '@Resgrid.Config.MappingConfig.LeafletAttribution';
-        var floorImageUrl = '@Url.Action("GetFloorImage", "IndoorMaps", new { id = Model.Floor.IndoorMapFloorId, area = "User" })';
-        var boundsNE = [@(Model.Floor.BoundsNELat ?? Model.IndoorMap.BoundsNELat), @(Model.Floor.BoundsNELon ?? Model.IndoorMap.BoundsNELon)];
-        var boundsSW = [@(Model.Floor.BoundsSWLat ?? Model.IndoorMap.BoundsSWLat), @(Model.Floor.BoundsSWLon ?? Model.IndoorMap.BoundsSWLon)];
-        var centerLat = `@Model.IndoorMap.CenterLatitude`;
-        var centerLon = `@Model.IndoorMap.CenterLongitude`;
+        var osmTileUrl = `@Html.Raw`(Newtonsoft.Json.JsonConvert.SerializeObject(Resgrid.Config.MappingConfig.GetWebsiteOSMUrl()));
+        var osmTileAttribution = `@Html.Raw`(Newtonsoft.Json.JsonConvert.SerializeObject(Resgrid.Config.MappingConfig.LeafletAttribution));
+        var floorImageUrl = `@Html.Raw`(Newtonsoft.Json.JsonConvert.SerializeObject(Url.Action("GetFloorImage", "IndoorMaps", new { id = Model.Floor.IndoorMapFloorId, area = "User" })));
+        var boundsNE = [@((Model.Floor.BoundsNELat ?? Model.IndoorMap.BoundsNELat).ToString("G", System.Globalization.CultureInfo.InvariantCulture)), @((Model.Floor.BoundsNELon ?? Model.IndoorMap.BoundsNELon).ToString("G", System.Globalization.CultureInfo.InvariantCulture))];
+        var boundsSW = [@((Model.Floor.BoundsSWLat ?? Model.IndoorMap.BoundsSWLat).ToString("G", System.Globalization.CultureInfo.InvariantCulture)), @((Model.Floor.BoundsSWLon ?? Model.IndoorMap.BoundsSWLon).ToString("G", System.Globalization.CultureInfo.InvariantCulture))];
+        var centerLat = `@Model.IndoorMap.CenterLatitude.ToString`("G", System.Globalization.CultureInfo.InvariantCulture);
+        var centerLon = `@Model.IndoorMap.CenterLongitude.ToString`("G", System.Globalization.CultureInfo.InvariantCulture);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/IndoorMaps/ZoneEditor.cshtml` around lines
107 - 114, Update the ZoneEditor.cshtml script block so all JS variable
assignments are JSON-serialized and all numeric values are rendered with
invariant culture: wrap osmTileUrl, osmTileAttribution and floorImageUrl with
`@Html.Raw`(Newtonsoft.Json.JsonConvert.SerializeObject(...)) and render boundsNE,
boundsSW, centerLat and centerLon using .ToString("G",
System.Globalization.CultureInfo.InvariantCulture) (or serialize the numeric
values via JsonConvert.SerializeObject) to prevent culture-specific decimal
commas and ensure valid JavaScript; follow the same pattern used in
CustomMaps/RegionEditor.cshtml and apply the same fixes to other
IndoorMaps/mapping views that currently interpolate raw decimals or strings.
🟡 Minor comments (16)
Web/Resgrid.Web/Views/Account/ForgotPassword.cshtml-68-69 (1)

68-69: ⚠️ Potential issue | 🟡 Minor

Broken jQuery fallback path.

The primary script path on line 68 points to ~/lib/jquery/dist/jquery.min.js, which exists. However, the fallback on line 69 references ~/lib/jquery/jquery.min.js, which does not exist in the repository. Only the dist/ subdirectory contains jQuery files. Update the fallback to use the correct path:

Fix
 <script src="~/lib/jquery/dist/jquery.min.js"></script>
-<script>window.jQuery || document.write('<script src="~/lib/jquery/jquery.min.js"><\/script>')</script>
+<script>window.jQuery || document.write('<script src="~/lib/jquery/dist/jquery.min.js"><\/script>')</script>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Views/Account/ForgotPassword.cshtml` around lines 68 - 69,
The fallback document.write path in the inline script that checks window.jQuery
(the line containing "window.jQuery || document.write('<script src=...')") is
pointing to a non-existent "~/lib/jquery/jquery.min.js"; change that fallback
string to reference the correct existing file in the dist folder (use
"~/lib/jquery/dist/jquery.min.js") so the fallback loads the proper jQuery file
when window.jQuery is undefined.
Web/Resgrid.Web/Views/Account/Register.cshtml-143-144 (1)

143-144: ⚠️ Potential issue | 🟡 Minor

Fix the jQuery fallback URL; it references a file that does not exist and uses unresolved tilde syntax.

Primary script loads ~/lib/jquery/dist/jquery.min.js (dist folder), but fallback still references ~/lib/jquery/jquery.min.js (non-dist). Repository contains no jquery.min.js file in the non-dist location. Additionally, the ~/ token in the raw JavaScript string will not be resolved by the server—the browser receives it literally and cannot load the resource.

Update the fallback to use @Url.Content() to ensure proper path resolution:

Suggested fix
-    <script>window.jQuery || document.write('<script src="~/lib/jquery/jquery.min.js"><\/script>')</script>
+    <script>window.jQuery || document.write('<script src="@Url.Content("~/lib/jquery/dist/jquery.min.js")"><\/script>')</script>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Views/Account/Register.cshtml` around lines 143 - 144, The
jQuery fallback uses a nonexistent path and a raw '~/' token that the browser
can't resolve; update the inline fallback in Register.cshtml so it writes the
same dist path as the primary script and uses Razor path resolution
(Url.Content) to emit a correct URL. Replace the document.write fallback that
currently references ~/lib/jquery/jquery.min.js with one that emits the resolved
path for ~/lib/jquery/dist/jquery.min.js via `@Url.Content` (keep the existing
existence check window.jQuery || document.write(...) logic and ensure proper
escaping of the closing script tag).
Web/Resgrid.Web/Areas/User/Controllers/RoutesController.cs-170-176 (1)

170-176: ⚠️ Potential issue | 🟡 Minor

Use DistinctBy(c => c.CallId) to ensure proper deduplication.

The Call class does not override Equals() or GetHashCode(), so Distinct() uses reference equality. If both queries return different object instances for the same call, duplicates won't be removed. Since CallId is the unique identifier, use DistinctBy() instead:

Suggested change
-var all = calls.Union(active).Distinct().OrderBy(c => c.Name).Select(c => new { id = c.CallId, name = c.Name, address = c.Address }).ToList();
+var all = calls.Union(active).DistinctBy(c => c.CallId).OrderBy(c => c.Name).Select(c => new { id = c.CallId, name = c.Name, address = c.Address }).ToList();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Controllers/RoutesController.cs` around lines 170
- 176, GetCallsForLinking currently uses Union().Distinct(), which relies on
reference equality for Call objects; replace the union+distinct logic with
concatenation followed by DistinctBy(c => c.CallId) so deduplication is based on
the unique CallId (adjust the pipeline in GetCallsForLinking that calls
_callsService.GetAllNonDispatchedScheduledCallsByDepartmentIdAsync and
_callsService.GetActiveCallsByDepartmentAsync to use
calls.Concat(active).DistinctBy(c =>
c.CallId).OrderBy(...).Select(...).ToList()); ensure System.Linq is available if
DistinctBy is not in scope.
Web/Resgrid.Web/Views/Account/AffiliateRegister.cshtml-86-87 (1)

86-87: ⚠️ Potential issue | 🟡 Minor

jQuery version mismatch between primary and fallback.

The primary script loads jQuery from the local library (version 2.2.4 per libman.json), but the fallback references jQuery 1.8.2. This significant version gap (1.8.2 vs 2.2.4) could cause compatibility issues with dependent scripts (jquery-ui.custom, jquery.validate, etc.) if the fallback is triggered.

Consider updating the fallback to reference a local copy of the same jQuery version used in the primary load.

Proposed fix
 	<script src="~/lib/jquery/dist/jquery.min.js"></script>
-	<script>	window.jQuery || document.write('<script src="@Url.Content("~/Scripts/libs/jquery-1.8.2.min.js")"><\/script>')</script>
+	<script>	window.jQuery || document.write('<script src="@Url.Content("~/lib/jquery/dist/jquery.min.js")"><\/script>')</script>

Note: If a true fallback is needed, ensure both paths point to the same jQuery version.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Views/Account/AffiliateRegister.cshtml` around lines 86 - 87,
The fallback for jQuery uses an older 1.8.2 file, causing a version mismatch
with the primary jQuery (2.2.4); update the fallback reference so it points to
the same jQuery build/version as the primary (e.g., the same local
~/lib/jquery/dist/jquery.min.js or a matching 2.2.4 file) so that the fallback
and primary are identical and dependent scripts (jquery-ui.custom,
jquery.validate) remain compatible.
Providers/Resgrid.Providers.Geo/GeoLocationProvider.cs-110-110 (1)

110-110: ⚠️ Potential issue | 🟡 Minor

Debug artifact: unused exception variable.

The exception is captured into test but never used, which appears to be leftover debug code. This is inconsistent with other catch blocks in this file that use empty catches.

🧹 Suggested fix: Remove unused variable or keep consistent with other catch blocks
-				catch (Exception ex) { var test = ex; /* Don't report on GeoLocation failures */ }
+				catch { /* Don't report on GeoLocation failures */ }

Alternatively, if exception logging is desired, consider actually logging the exception rather than assigning it to an unused variable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Providers/Resgrid.Providers.Geo/GeoLocationProvider.cs` at line 110, Remove
the leftover debug artifact in the catch block that assigns the exception to an
unused variable; in the catch (Exception ex) block within the
GeoLocationProvider class/method, either delete the "var test = ex" assignment
so the catch is empty like the others, or replace it with proper logging if you
want to record failures (e.g., call the existing logger); ensure the final catch
matches the file's pattern for handling GeoLocation failures.
Web/Resgrid.Web/Areas/User/Views/CustomMaps/Layers.cshtml-102-102 (1)

102-102: ⚠️ Potential issue | 🟡 Minor

Potential JavaScript injection if localized string contains quotes.

The localized string is inserted directly into a JavaScript string literal. If DeleteLayerConfirm contains single quotes in any locale, the onclick handler will break.

🔧 Suggested fix: Use JavaScript-safe encoding
-<a class="btn btn-danger btn-xs" asp-controller="CustomMaps" asp-action="DeleteLayer" asp-route-id="@layer.IndoorMapFloorId" asp-route-area="User" onclick="return confirm('@localizer["DeleteLayerConfirm"]');"><i class="fa fa-trash"></i> `@localizer`["Delete"]</a>
+<a class="btn btn-danger btn-xs" asp-controller="CustomMaps" asp-action="DeleteLayer" asp-route-id="@layer.IndoorMapFloorId" asp-route-area="User" onclick="return confirm('@Html.Raw(System.Text.Encodings.Web.JavaScriptEncoder.Default.Encode(localizer["DeleteLayerConfirm"].Value))');"><i class="fa fa-trash"></i> `@localizer`["Delete"]</a>

Alternatively, use a data attribute and attach the handler via JavaScript.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/CustomMaps/Layers.cshtml` at line 102, The
inline onclick uses the localized DeleteLayerConfirm directly which can break if
the string contains quotes; change the anchor (in Layers.cshtml) to remove the
onclick and instead add a data-confirm="@localizer["DeleteLayerConfirm"]"
attribute (Razor will HTML-encode the attribute), then add a small client-side
script that binds to the DeleteLayer anchor(s) (e.g., querySelectorAll on links
with asp-action="DeleteLayer" or a CSS class) and calls
window.confirm(this.dataset.confirm) before allowing navigation; this avoids
injecting the localized text into a JS string literal and prevents JavaScript
injection issues.
Web/Resgrid.Web/Views/Shared/_ScriptsPartial.cshtml-1-6 (1)

1-6: ⚠️ Potential issue | 🟡 Minor

jQuery fallback path is inconsistent with main source.

The asp-fallback-src on line 2 references lib/jquery/jquery-1.12.3.min.js which:

  1. Is missing the ~/ prefix for consistent path resolution
  2. References a specific old version (1.12.3) that may differ from the version in ~/lib/jquery/dist/jquery.min.js

The fallback should mirror the main source path.

Proposed fix
 <script src="~/lib/jquery/dist/jquery.min.js"
-        asp-fallback-src="lib/jquery/jquery-1.12.3.min.js"
+        asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
         asp-fallback-test="window.jQuery"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Views/Shared/_ScriptsPartial.cshtml` around lines 1 - 6,
Update the jQuery fallback path in _ScriptsPartial.cshtml so it mirrors the main
src and uses a consistent path and version: change the asp-fallback-src on the
<script> tag (asp-fallback-src) to use the same ~/lib/jquery/dist/ directory and
filename (matching jquery.min.js) rather than the relative
lib/jquery/jquery-1.12.3.min.js reference, ensuring the fallback path includes
the ~/ prefix and matches the deployed jquery.min.js version.
Web/Resgrid.Web/Areas/User/Views/Routes/InstanceDetail.cshtml-33-34 (1)

33-34: ⚠️ Potential issue | 🟡 Minor

Localize the instance status badge too.

Line 34 still renders RouteInstanceStatus directly, while Line 60 already maps status text through the localizer. That leaves mixed-language status text on the same page. Reuse a localized mapping for the summary badge as well.

Also applies to: 59-60

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/Routes/InstanceDetail.cshtml` around lines
33 - 34, The status badge currently prints the enum value via
@((Resgrid.Model.RouteInstanceStatus)Model.Instance.Status) which causes
mixed-language output; change the badge to use the same localized mapping used
for the summary (the existing localizer-based mapping at lines 59-60) so the
badge reads localizer[...] for Model.Instance.Status instead of the raw enum;
locate the code that renders the summary status text (where localizer is used
for the RouteInstanceStatus) and reuse that same expression or helper for the
<dd><span class="label label-primary"> so the badge shows the localized string
for Model.Instance.Status.
Web/Resgrid.Web/Areas/User/Views/Routes/Edit.cshtml-110-115 (1)

110-115: ⚠️ Potential issue | 🟡 Minor

"Add Stop" button text not localized.

The button text "Add Stop" on line 113 should use the localizer for consistency.

🌐 Proposed fix
-                            <button type="button" class="btn btn-sm btn-primary pull-right" data-toggle="modal" data-target="#addStopModal">
-                                <i class="fa fa-plus"></i> Add Stop
-                            </button>
+                            <button type="button" class="btn btn-sm btn-primary pull-right" data-toggle="modal" data-target="#addStopModal">
+                                <i class="fa fa-plus"></i> `@localizer`["AddStop"]
+                            </button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/Routes/Edit.cshtml` around lines 110 - 115,
The "Add Stop" button text is not localized; update the button's inner text in
the Edit.cshtml view (the <button ... data-target="#addStopModal"> element) to
use the localizer (e.g., replace the literal "Add Stop" with `@localizer`["Add
Stop"] or a chosen resource key like `@localizer`["AddStop"]) and ensure the
corresponding key/value is added to the resource file(s) used by this view so
the translated string appears correctly.
Web/Resgrid.Web/Areas/User/Views/Routes/Edit.cshtml-182-186 (1)

182-186: ⚠️ Potential issue | 🟡 Minor

Add the missing "Station" option to the stop type dropdown.

The RouteStopType enum includes a Station value (2), but the dropdown at lines 183–186 only includes Manual (0), Scheduled Call (1), and Waypoint (3). Add the Station option:

Suggested fix
<select id="stopType" class="form-control">
    <option value="0">Manual</option>
    <option value="1">Scheduled Call</option>
    <option value="2">Station</option>
    <option value="3">Waypoint</option>
</select>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/Routes/Edit.cshtml` around lines 182 - 186,
The stop type dropdown (select with id "stopType") is missing the
RouteStopType.Station option (value 2); update the select in Edit.cshtml to
include an <option value="2">Station</option> between the Scheduled Call (value
1) and Waypoint (value 3) entries so the UI matches the RouteStopType enum.
Web/Resgrid.Web.Services/Controllers/v4/GeocodingController.cs-81-97 (1)

81-97: ⚠️ Potential issue | 🟡 Minor

Missing coordinate range validation for ReverseGeocode.

The endpoint accepts any double values for lat and lon without validating they're within valid geographic ranges (-90 to 90 for latitude, -180 to 180 for longitude). Invalid coordinates could cause provider errors or unexpected behavior.

🔧 Proposed fix to add validation
         public async Task<ActionResult<ReverseGeocodeResult>> ReverseGeocode([FromQuery] double lat, [FromQuery] double lon)
         {
+            if (lat < -90 || lat > 90 || lon < -180 || lon > 180)
+                return BadRequest("Invalid coordinate range");
+
             var result = new ReverseGeocodeResult();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web.Services/Controllers/v4/GeocodingController.cs` around lines
81 - 97, In ReverseGeocode (GeocodingController.ReverseGeocode) add validation
for the lat and lon query params before calling _geoLocationProvider: verify lat
is between -90 and 90 and lon between -180 and 180, and if validation fails set
result.Status to an appropriate error (e.g., ResponseHelper.InvalidRequest or a
400 response), populate result with a descriptive error message and return
BadRequest(result) (or the project’s standard V4 error response) instead of
calling GetAddressFromLatLong; keep the existing try/catch for provider calls
and ensure ResponseHelper.PopulateV4ResponseData is still called on success
paths.
Web/Resgrid.Web/Areas/User/Views/Mapping/StationRouting.cshtml-93-99 (1)

93-99: ⚠️ Potential issue | 🟡 Minor

Inconsistent localization: hard-coded "onto" in step instructions.

Same issue as in CallExportEx.cshtml - the word "onto" is hard-coded in English.

🌐 Proposed fix
-                              html += '<li>' + s.maneuver.type + (s.name ? ' onto ' + s.name : '') +
+                              html += '<li>' + s.maneuver.type + (s.name ? ' `@localizer`["OntoDirection"] ' + s.name : '') +
                                   ' (' + (s.distance / 1000).toFixed(1) + ' km)</li>';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/Mapping/StationRouting.cshtml` around lines
93 - 99, Replace the hard-coded English " onto " string in the JavaScript string
concatenation inside the for-loop (variable s in StationRouting.cshtml) with a
server-side localized resource value (e.g., inject the localized string into the
JS using your view/localizer like `@Localizer`["Onto"] or the project’s
equivalent) and preserve spacing and concatenation so the expression becomes:
s.maneuver.type + (s.name ? ' ' + localizedOnto + ' ' + s.name : '') ; also
apply the same localization approach used in CallExportEx.cshtml to keep
translations consistent and ensure proper HTML escaping when embedding the
localized value into the script.
Web/Resgrid.Web/Areas/User/Views/Dispatch/CallExportEx.cshtml-601-610 (1)

601-610: ⚠️ Potential issue | 🟡 Minor

Inconsistent localization: hard-coded "onto" in step instructions.

The step instruction text contains the hard-coded English word "onto" which won't be translated.

🌐 Proposed fix

Consider either:

  1. Moving this string to a localizer key
  2. Or accepting that OSRM maneuver types themselves are English and documenting this limitation
-                                        html += '<li>' + s.maneuver.type + (s.name ? ' onto ' + s.name : '') +
+                                        html += '<li>' + s.maneuver.type + (s.name ? ' `@localizer`["OntoDirection"] ' + s.name : '') +
                                             ' (' + (s.distance / 1000).toFixed(1) + ' km)</li>';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/Dispatch/CallExportEx.cshtml` around lines
601 - 610, The step rendering concatenates a hard-coded English " onto " when
building maneuver text (in the loop over steps, using variables steps, s,
s.maneuver.type and document.getElementById('dvPanel')), which prevents
localization; replace that literal with a localized string pulled from your
resource/localizer (e.g., emit a JS variable from the view containing the
localized "onto" or call a client-side i18n function) and use that variable when
concatenating the label, or alternatively document the OSRM-language limitation
if you choose not to localize; ensure the change is applied in the
CallExportEx.cshtml HTML construction block so all rendered instructions use the
localized token.
Web/Resgrid.Web/Areas/User/Views/Routes/Edit.cshtml-165-278 (1)

165-278: ⚠️ Potential issue | 🟡 Minor

Add Stop modal contains many hard-coded English strings.

The modal has numerous unlocalizable strings while the main form uses @localizer. This breaks i18n consistency. Examples include:

  • "Add Stop" (title and button, lines 171, 274)
  • "Name", "Type", "Priority", "Address", etc. (form labels)
  • "Manual", "Scheduled Call", "Waypoint" (dropdown options)
  • "Cancel" (line 273)
🌐 Proposed fix pattern

Apply the same localization pattern used in the main form:

-                <h4 class="modal-title" id="addStopModalLabel">Add Stop</h4>
+                <h4 class="modal-title" id="addStopModalLabel">@localizer["AddStop"]</h4>
-                        <label class="col-sm-3 control-label">Name <span class="text-danger">*</span></label>
+                        <label class="col-sm-3 control-label">@localizer["Name"] <span class="text-danger">*</span></label>

Apply similar changes to all form labels and button text in the modal.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/Routes/Edit.cshtml` around lines 165 - 278,
The Add Stop modal contains hard-coded UI strings; update all visible text
(modal title, button labels, form labels, select option text, help text,
placeholders) to use the same localization pattern as the main form (i.e.
replace literals with `@localizer`["KeyName"] or the project's equivalent) for
elements inside the addStopModal (references: addStopModal, addStopModalLabel,
saveStopBtn, geocodeStopBtn, findStopW3WBtn, stopName, stopType,
linkedCallGroup/linkedCallId, stopPriority, stopAddress, stopW3W, stopPickerMap,
stopLat, stopLng, stopPlannedArrival, stopPlannedDeparture, stopDwellMinutes,
stopContactName, stopContactNumber, stopNotes and the Cancel button); ensure new
resource keys are added for "Add Stop", "Cancel", "Name", "Type", "Manual",
"Scheduled Call", "Waypoint", "Priority", "Address", "What3Words", "Find", label
help text, placeholders and any other literal so the modal matches the main
form's localization usage.
Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Index.cshtml-53-53 (1)

53-53: ⚠️ Potential issue | 🟡 Minor

Potential JavaScript injection if localized string contains quotes.

The delete confirmation uses an inline onclick handler with a localized string. If DeleteIndoorMapConfirm contains an apostrophe (e.g., "Are you sure you want to delete this map?"), it will break the JavaScript.

🔧 Proposed fix using JavaScript encoding
-<a class="btn btn-danger btn-xs" asp-controller="IndoorMaps" asp-action="Delete" asp-route-id="@map.IndoorMapId" asp-route-area="User" onclick="return confirm('@localizer["DeleteIndoorMapConfirm"]');"><i class="fa fa-trash"></i> `@localizer`["Delete"]</a>
+<a class="btn btn-danger btn-xs" asp-controller="IndoorMaps" asp-action="Delete" asp-route-id="@map.IndoorMapId" asp-route-area="User" onclick="return confirm('@Html.Raw(System.Text.Encodings.Web.JavaScriptEncoder.Default.Encode(localizer["DeleteIndoorMapConfirm"].Value))');"><i class="fa fa-trash"></i> `@localizer`["Delete"]</a>

Alternatively, move the confirmation to an external JS file using data attributes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Index.cshtml` at line 53, The
inline onclick handler with localizer["DeleteIndoorMapConfirm"] can break when
the localized string contains quotes; remove the inline onclick and instead add
a data-confirm attribute to the <a> (the anchor with asp-controller="IndoorMaps"
/ asp-action="Delete" / asp-route-id and the Delete text) carrying the raw
localized string, and implement a small external JS handler that listens for
clicks on anchors with data-confirm (e.g., selector 'a[data-confirm]'), calls
window.confirm using the data-confirm value, and only proceeds with the
navigation if confirmed; alternatively, if you must keep inline JS, encode the
localized string using
HttpUtility.JavaScriptStringEncode(localizer["DeleteIndoorMapConfirm"]) before
embedding it in the onclick to prevent injection.
Web/Resgrid.Web/Areas/User/Views/Dispatch/CallExportEx.cshtml-594-597 (1)

594-597: ⚠️ Potential issue | 🟡 Minor

Inconsistent localization: hard-coded strings in routing display.

The distance and duration labels are hard-coded in English while the rest of the view uses @localizer. This breaks i18n consistency.

🌐 Proposed fix to use localized strings
-                            var dvDistance = document.getElementById('dvDistance');
-                            dvDistance.innerHTML = 'Distance: ' + distKm + ' km<br />Duration: ' + durationMin + ' min';
+                            var dvDistance = document.getElementById('dvDistance');
+                            dvDistance.innerHTML = '@localizer["DistanceLabel"] ' + distKm + ' km<br />@localizer["DurationLabel"] ' + durationMin + ' min';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/Dispatch/CallExportEx.cshtml` around lines
594 - 597, The hard-coded labels in the routing display (the string
concatenation that sets dvDistance.innerHTML using 'Distance:' and 'Duration:')
break localization; replace those literal English labels by injecting localized
strings into the JS (e.g., render server-side/localizer values into JS variables
or data attributes) and use those variables when building the innerHTML for
distKm and durationMin; update the code that computes distKm, durationMin and
writes to element with id 'dvDistance' to use the localized label variables
instead of the hard-coded 'Distance:' and 'Duration:' and 'min'.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b9acba2b-bbed-46c2-a21d-a1aa608642cb

📥 Commits

Reviewing files that changed from the base of the PR and between 1e4c5be and f1e7368.

⛔ Files ignored due to path filters (4)
  • Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/IndoorMaps/IndoorMaps.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Mapping/Mapping.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Routes/Routes.en.resx is excluded by !**/*.resx
📒 Files selected for processing (98)
  • Core/Resgrid.Config/MappingConfig.cs
  • Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.cs
  • Core/Resgrid.Localization/Areas/User/IndoorMaps/IndoorMaps.cs
  • Core/Resgrid.Localization/Areas/User/Mapping/Mapping.cs
  • Core/Resgrid.Localization/Areas/User/Routes/Routes.cs
  • Core/Resgrid.Model/PermissionTypes.cs
  • Providers/Resgrid.Providers.Claims/ClaimsLogic.cs
  • Providers/Resgrid.Providers.Geo/GeoLocationProvider.cs
  • Web/Resgrid.Web.Services/Controllers/v4/GeocodingController.cs
  • Web/Resgrid.Web.Services/Models/v4/Geocoding/GeocodingResults.cs
  • Web/Resgrid.Web.Services/Resgrid.Web.Services.xml
  • Web/Resgrid.Web.Services/appsettings.json
  • Web/Resgrid.Web/Areas/User/Controllers/CustomMapsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/DispatchController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/RoutesController.cs
  • Web/Resgrid.Web/Areas/User/Views/Calendar/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Calendar/New.cshtml
  • Web/Resgrid.Web/Areas/User/Views/CustomMaps/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/CustomMaps/Import.cshtml
  • Web/Resgrid.Web/Areas/User/Views/CustomMaps/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/CustomMaps/Layers.cshtml
  • Web/Resgrid.Web/Areas/User/Views/CustomMaps/New.cshtml
  • Web/Resgrid.Web/Areas/User/Views/CustomMaps/RegionEditor.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/AddArchivedCall.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/CallExport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/CallExportEx.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/Dashboard.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/NewCall.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/UpdateCall.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/ViewCall.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Groups/Geofence.cshtml
  • Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Floors.cshtml
  • Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/IndoorMaps/New.cshtml
  • Web/Resgrid.Web/Areas/User/Views/IndoorMaps/ZoneEditor.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Logs/LogExport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Mapping/AddPOI.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Mapping/AddPOIType.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Mapping/EditLayer.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Mapping/ImportPOIs.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Mapping/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Mapping/Layers.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Mapping/LiveRouting.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Mapping/NewLayer.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Mapping/POIs.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Mapping/StationRouting.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Mapping/ViewType.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/ActionLogs.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/ActiveCallsResourcesReport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/CallSummaryReport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/CertificationsReport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/DepartmentActivityReport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/FlaggedCallNotesReport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/LogReport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelEventsReport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelHoursDetailReport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelHoursReport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelReport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelStaffingHistoryReport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/StaffingReport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/UnitEventsReport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/UnitStateHistoryReport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Reports/UpcomingShiftReadinessReport.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Routes/ActiveRoutes.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Routes/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Routes/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Routes/InstanceDetail.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Routes/Instances.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Routes/New.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Routes/View.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Shared/_ChatWidget.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Subscription/ViewInvoice.cshtml
  • Web/Resgrid.Web/Views/Account/AffiliateRegister.cshtml
  • Web/Resgrid.Web/Views/Account/CompletedInvite.cshtml
  • Web/Resgrid.Web/Views/Account/ForgotPassword.cshtml
  • Web/Resgrid.Web/Views/Account/Lockout.cshtml
  • Web/Resgrid.Web/Views/Account/MissingCode.cshtml
  • Web/Resgrid.Web/Views/Account/MissingInvite.cshtml
  • Web/Resgrid.Web/Views/Account/Register.cshtml
  • Web/Resgrid.Web/Views/Shared/Error.cshtml
  • Web/Resgrid.Web/Views/Shared/Unauthorized.cshtml
  • Web/Resgrid.Web/Views/Shared/_ScriptsPartial.cshtml
  • Web/Resgrid.Web/Views/Shared/_StylePartial.cshtml
  • Web/Resgrid.Web/libman.json
  • Web/Resgrid.Web/wwwroot/js/app/internal/custommaps/resgrid.custommaps.editor.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/custommaps/resgrid.custommaps.regioneditor.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.addArchivedCall.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.editcall.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/groups/resgrid.groups.geofence.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/indoormaps/resgrid.indoormaps.editor.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/indoormaps/resgrid.indoormaps.zoneeditor.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/mapping/resgrid.mapping.viewType.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/routes/resgrid.routes.edit.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/routes/resgrid.routes.instancedetail.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/routes/resgrid.routes.view.js

Comment on lines +62 to +63
}
catch { /* provider errors are non-fatal */ }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Silent exception swallowing loses valuable debugging information.

The empty catch block discards all exceptions, making it impossible to diagnose provider failures. Consider logging the exception even if it's non-fatal for the request.

🔧 Proposed fix to log exceptions
+        private readonly ILogger<GeocodingController> _logger;
+
-        public GeocodingController(IGeoLocationProvider geoLocationProvider)
+        public GeocodingController(IGeoLocationProvider geoLocationProvider, ILogger<GeocodingController> logger)
         {
             _geoLocationProvider = geoLocationProvider;
+            _logger = logger;
         }

         // In ForwardGeocode:
-            catch { /* provider errors are non-fatal */ }
+            catch (Exception ex) 
+            { 
+                _logger.LogWarning(ex, "Forward geocoding failed for address: {Address}", address);
+            }

As per coding guidelines, design for testability and avoid hidden dependencies - silent failures make debugging and testing difficult.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web.Services/Controllers/v4/GeocodingController.cs` around lines
62 - 63, The empty catch in GeocodingController silently swallows provider
exceptions; change it to catch Exception (e.g., catch (Exception ex)) and log
the exception using the controller's logger or a new injected
ILogger<GeocodingController> (e.g., LogWarning/LogError with context like
"Geocoding provider failed") while preserving the non-fatal behavior (do not
rethrow), so provider errors are recorded for debugging and tests.

Comment on lines +65 to +66
var endLat = @Model.EndLat;
var endLon = @Model.EndLon;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Serialize coordinates as JSON/invariant numbers.

Direct Razor numeric output can be culture-formatted (comma decimal) and break JavaScript numeric parsing.

🌍 Proposed fix
-        var endLat = `@Model.EndLat`;
-        var endLon = `@Model.EndLon`;
+        var endLat = `@Html.Raw`(System.Text.Json.JsonSerializer.Serialize(Model.EndLat));
+        var endLon = `@Html.Raw`(System.Text.Json.JsonSerializer.Serialize(Model.EndLon));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/Mapping/LiveRouting.cshtml` around lines 65
- 66, The numeric Razor outputs endLat and endLon are rendered using server
culture and may produce comma decimals; change their rendering to
culture-invariant JSON numbers by serializing the values (e.g., use a JSON
serializer or call ToString(System.Globalization.CultureInfo.InvariantCulture)
and write as raw output) so the JavaScript receives valid numeric literals;
update the endLat and endLon usages in LiveRouting.cshtml to emit
invariant/JSON-formatted numbers instead of direct Razor numeric interpolation.

Comment on lines +1 to +2
<script src="~/lib/twilio/twilio-common.min.js"></script>
<script src="~/lib/twilio/twilio-chat.min.js"></script>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify that the Twilio library files exist in the expected location

# Search for Twilio library files in the repository
fd -t f -e js "twilio" --full-path

# Also check if there's a lib/twilio directory structure
fd -t d "twilio" --full-path

Repository: Resgrid/Core

Length of output: 74


🏁 Script executed:

# Check the Web/Resgrid.Web structure for wwwroot and lib directories
find Web/Resgrid.Web/wwwroot -type d -name "lib" 2>/dev/null | head -20

# Check if the lib/twilio directory exists
find Web/Resgrid.Web/wwwroot -type d -name "twilio" 2>/dev/null | head -20

# List all files in the References/Twilio directory
find References/Twilio -type f 2>/dev/null | head -30

# Check what's in the lib directory if it exists
ls -la Web/Resgrid.Web/wwwroot/lib/ 2>/dev/null | head -50

# Check specifically for the script files mentioned in the view
find . -name "twilio-common.min.js" -o -name "twilio-chat.min.js" 2>/dev/null

Repository: Resgrid/Core

Length of output: 3665


🏁 Script executed:

# Check if the file exists and read its content
cat -n Web/Resgrid.Web/Areas/User/Views/Shared/_ChatWidget.cshtml 2>/dev/null | head -30

# Check the file size to understand how much content we have
wc -l Web/Resgrid.Web/Areas/User/Views/Shared/_ChatWidget.cshtml

# Double-check for any twilio files in the entire repo, including subdirectories
find . -path "*/node_modules" -prune -o -name "*twilio*" -type f -print 2>/dev/null

Repository: Resgrid/Core

Length of output: 972


Missing Twilio JavaScript libraries—chat widget will fail to load.

The referenced files twilio-common.min.js and twilio-chat.min.js do not exist in Web/Resgrid.Web/wwwroot/lib/twilio/. The application will fail at runtime when attempting to load these dependencies. Either add the missing Twilio JavaScript SDK files to the repository or revert these script references if this change was unintended.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/Shared/_ChatWidget.cshtml` around lines 1 -
2, In _ChatWidget.cshtml remove or fix the broken Twilio script references:
either add the missing Twilio SDK files (twilio-common.min.js and
twilio-chat.min.js) into wwwroot/lib/twilio/ so the existing <script> tags load
correctly, or revert/update the two <script
src="~/lib/twilio/twilio-common.min.js"> and <script
src="~/lib/twilio/twilio-chat.min.js"> lines to point to valid local paths or
official CDN URLs; ensure the referenced filenames and paths exactly match the
files you add or the CDN spec so the chat widget can load.

Comment on lines +147 to +148
if (result && result.Data && result.Data.Latitude && result.Data.Longitude) {
setPickerLocation(result.Data.Latitude, result.Data.Longitude, false);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Valid 0 coordinates are currently treated as missing.

Checks like if (Latitude && Longitude) fail at the equator/prime meridian. Use numeric finite checks instead.

🧭 Proposed fix
-                if (result && result.Data && result.Data.Latitude && result.Data.Longitude) {
-                    setPickerLocation(result.Data.Latitude, result.Data.Longitude, false);
+                var lat = Number(result && result.Data ? result.Data.Latitude : NaN);
+                var lng = Number(result && result.Data ? result.Data.Longitude : NaN);
+                if (Number.isFinite(lat) && Number.isFinite(lng)) {
+                    setPickerLocation(lat, lng, false);
                 } else {
                     alert('Address not found.');
                 }
@@
-            if (data && data.Latitude && data.Longitude) {
-                setPickerLocation(data.Latitude, data.Longitude, true);
+            var lat = Number(data ? data.Latitude : NaN);
+            var lng = Number(data ? data.Longitude : NaN);
+            if (Number.isFinite(lat) && Number.isFinite(lng)) {
+                setPickerLocation(lat, lng, true);
             } else {
                 alert('What3Words was unable to find a location for those words. Ensure it is 3 words separated by periods.');
             }

Also applies to: 170-171

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/wwwroot/js/app/internal/routes/resgrid.routes.edit.js` around
lines 147 - 148, The current truthy checks treat 0 coordinates as missing;
replace checks like "if (result && result.Data && result.Data.Latitude &&
result.Data.Longitude)" with numeric finite checks (e.g., use Number.isFinite or
isFinite and ensure values are not null/undefined) before calling
setPickerLocation(result.Data.Latitude, result.Data.Longitude, false); apply the
same change to the other occurrence that checks Latitude/Longitude (the one
around setPickerLocation and the check at the later block referencing
result.Data.Latitude/Longitude).

var success = await _indoorMapService.DeleteFloorAsync(id, cancellationToken);
if (!success)
{
_logger.LogWarning("DeleteFloor failed for floor {FloorId} in department {DepartmentId}.", id, DepartmentId);

Check failure

Code scanning / CodeQL

Log entries created from user input High

This log entry depends on a
user-provided value
.

Copilot Autofix

AI 10 days ago

In general, to fix log-forging issues, any user-controlled data passed to log methods should be sanitized to remove or neutralize characters that can affect the structure of the logs, especially newline and carriage return characters. In .NET, this can be done with string.Replace (for example, removing \r and \n) or by otherwise constraining the logged representation of the user input.

For this specific case, the simplest and least intrusive fix is to create a sanitized version of id just before logging and use that sanitized value in the log call. For example, we can create var safeId = id?.Replace("\r", "").Replace("\n", ""); and then pass safeId into _logger.LogWarning instead of id. This changes only the logged representation (removing control characters) and does not affect any other logic. The change should be made in Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs, within the DeleteFloor method, around line 94–99, where the logging occurs. No new imports or external methods are needed; we only use string.Replace, which is already available.

Suggested changeset 1
Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs b/Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs
--- a/Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs
+++ b/Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs
@@ -94,7 +94,8 @@
 			var success = await _indoorMapService.DeleteFloorAsync(id, cancellationToken);
 			if (!success)
 			{
-				_logger.LogWarning("DeleteFloor failed for floor {FloorId} in department {DepartmentId}.", id, DepartmentId);
+				var safeId = id?.Replace("\r", string.Empty).Replace("\n", string.Empty);
+				_logger.LogWarning("DeleteFloor failed for floor {FloorId} in department {DepartmentId}.", safeId, DepartmentId);
 				TempData["ErrorMessage"] = "Failed to delete the floor. Please try again.";
 			}
 
EOF
@@ -94,7 +94,8 @@
var success = await _indoorMapService.DeleteFloorAsync(id, cancellationToken);
if (!success)
{
_logger.LogWarning("DeleteFloor failed for floor {FloorId} in department {DepartmentId}.", id, DepartmentId);
var safeId = id?.Replace("\r", string.Empty).Replace("\n", string.Empty);
_logger.LogWarning("DeleteFloor failed for floor {FloorId} in department {DepartmentId}.", safeId, DepartmentId);
TempData["ErrorMessage"] = "Failed to delete the floor. Please try again.";
}

Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Web/Resgrid.Web/Areas/User/Views/Mapping/EditLayer.cshtml (2)

195-227: ⚠️ Potential issue | 🟡 Minor

Remove e.preventDefault() and this.submit() to allow normal form submission.

With e.preventDefault() followed by this.submit(), the form bypasses any client-side validation hooks. Since the GeoJson serialization is synchronous, populate #GeoJson and let the form submit normally so that validation can run.

🛠️ Suggested change
 $('#newLayerForm').on('submit', function (e) {
-	e.preventDefault();
 	let layers = map.pm.getGeomanLayers();
@@
 	$('#GeoJson').val(JSON.stringify(featureCollection));
-
-	this.submit();
 });

Additionally, consider including <partial name="_ValidationScriptsPartial" /> in the Scripts section to enable client-side validation, as other views in this area do.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/Mapping/EditLayer.cshtml` around lines 195 -
227, In the submit handler for '#newLayerForm' remove the call to
e.preventDefault() and the explicit this.submit() so the handler only serializes
the map into '#GeoJson' and then returns, allowing normal form submission and
client-side validation to run; keep the layers -> features mapping and the
$('#GeoJson').val(JSON.stringify(featureCollection)) logic intact inside the
handler. Also add the view-level Scripts partial inclusion <partial
name="_ValidationScriptsPartial" /> (in the Scripts section of this view) to
enable client-side validation scripts.

202-203: ⚠️ Potential issue | 🔴 Critical

Remove user-controlled strings from the GeoJSON payload rendered with @Html.Raw.

At lines 202–203 and 212, color, name, and text are read directly from user input and stored in GeoJSON properties. This view later renders the stored GeoJSON with @Html.Raw(Model.GeoJson) at line 181 inside a script block. A user can inject </script> to break out of the script context and execute arbitrary code when the layer is reopened.

Either sanitize these fields before persistence, store them outside the GeoJSON payload, or use a safe JSON deserialization approach on the client side instead of @Html.Raw.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/Mapping/EditLayer.cshtml` around lines 202 -
203, The view is injecting user-controlled strings (geo.properties.color,
geo.properties.name, geo.properties.text) into Model.GeoJson and rendering it
with `@Html.Raw`(Model.GeoJson), allowing a script injection; fix by ensuring
these fields are validated/sanitized on the server before they are included in
the GeoJSON (e.g., enforce a strict color whitelist/hex regex for color and
strip/encode HTML/JS from name/text or store them outside the GeoJSON), and stop
rendering raw user strings client-side by emitting the GeoJSON via a safe JSON
serializer (server-side JSON encode/serialize) instead of
`@Html.Raw`(Model.GeoJson).
🧹 Nitpick comments (4)
Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.editcall.js (1)

354-364: Consider extracting shared reverse geocoding logic.

The fetch logic in geocodeCoordinates (lines 355-362) is nearly identical to findLocation (lines 321-328). You could extract a helper to reduce duplication:

♻️ Proposed refactor to extract shared helper
+            function reverseGeocodeAndUpdateAddress(lat, lng) {
+                fetch(resgrid.absoluteApiBaseUrl + '/api/v4/Geocoding/ReverseGeocode?lat=' + lat + '&lon=' + lng, { headers: { 'Authorization': 'Bearer ' + localStorage.getItem('RgWebApp.auth-tokens') } })
+                    .then(function(r) { return r.json(); })
+                    .then(function(result) {
+                        if (result && result.Data && result.Data.Address) {
+                            $("#Call_Address").val(result.Data.Address);
+                        }
+                    })
+                    .catch(function(err) { console.error("Reverse geocode error:", err); });
+            }
+            editcall.reverseGeocodeAndUpdateAddress = reverseGeocodeAndUpdateAddress;
+
             function findLocation(pos) {
-                fetch(resgrid.absoluteApiBaseUrl + '/api/v4/Geocoding/ReverseGeocode?lat=' + pos.lat + '&lon=' + pos.lng, { headers: { 'Authorization': 'Bearer ' + localStorage.getItem('RgWebApp.auth-tokens') } })
-                    .then(function(r) { return r.json(); })
-                    .then(function(result) {
-                        if (result && result.Data && result.Data.Address) {
-                            $("#Call_Address").val(result.Data.Address);
-                        }
-                    })
-                    .catch(function(err) { console.error("Reverse geocode error:", err); });
+                reverseGeocodeAndUpdateAddress(pos.lat, pos.lng);
                 $("#Latitude").val(pos.lat.toString());
                 $("#Longitude").val(pos.lng.toString());
             }

Then update geocodeCoordinates to call reverseGeocodeAndUpdateAddress(lat, lng) as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.editcall.js`
around lines 354 - 364, Extract the duplicated fetch/reverse-geocoding logic
used in findLocation and geocodeCoordinates into a single helper (e.g.,
reverseGeocodeAndUpdateAddress) that accepts lat/lng, performs the fetch to
'/api/v4/Geocoding/ReverseGeocode', handles the Authorization header, parses
JSON, checks result.Data.Address, and updates $("#Call_Address").val(...); then
replace the bodies of findLocation and geocodeCoordinates so they simply call
reverseGeocodeAndUpdateAddress(lat, lng) and keep editcall.geocodeCoordinates
assignment unchanged.
Web/Resgrid.Web/Views/Account/LogOn.cshtml (1)

104-132: Consider centralizing locale-to-flag metadata to prevent drift.

The locale list and flagMap are now repeated in multiple views/scripts. A shared source (server-provided JSON or one shared JS module/partial) would reduce future mismatch risk.

Also applies to: 175-185

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Views/Account/LogOn.cshtml` around lines 104 - 132, Duplicate
locale-to-flag mappings are hard-coded in this view (the <ul
class="dropdown-menu"> entries) and a repeated flagMap elsewhere; extract the
locale/flag metadata into a single shared source (e.g., a server-provided JSON
endpoint or a shared JS module/partial such as a _LocaleFlags partial or
locales.json) and replace the hard-coded <a class="langDropdownSelection"
data-locale="..."> items with a loop that renders entries from that shared data;
update the other occurrence noted (lines 175-185 / the other flagMap usage) to
read from the same shared module/endpoint so all views/scripts use one canonical
locale-to-flag mapping.
Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs (1)

97-97: Localize the user-facing failure message.

Line 97 introduces hardcoded English text, which will bypass localization in localized UI flows.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs` at line 97,
Replace the hardcoded TempData["ErrorMessage"] = "Failed to delete the floor.
Please try again."; with a localized string lookup so the UI uses resource
translations; locate the assignment in IndoorMapsController (the DeleteFloor /
floor deletion action) and change it to set TempData["ErrorMessage"] =
_localizer["FailedToDeleteFloor"] (or Resource.Messages.FailedToDeleteFloor)
using the controller's IStringLocalizer or existing resource class, adding the
"FailedToDeleteFloor" key to the resource files for each supported culture.
Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js (1)

365-372: Same missing HTTP status check pattern.

This fetch call has the same issue as the forward geocoding call. Consider extracting a helper function for authenticated API calls that handles status checks consistently:

Example helper function
function fetchApi(url) {
    return fetch(url, { headers: { 'Authorization': 'Bearer ' + localStorage.getItem('RgWebApp.auth-tokens') } })
        .then(function(r) {
            if (!r.ok) throw new Error('HTTP ' + r.status);
            return r.json();
        });
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js`
around lines 365 - 372, The reverse-geocode fetch lacks an HTTP status check;
extract a helper like fetchApi that wraps fetch with the Authorization header,
checks r.ok and throws on non-2xx (e.g., throw new Error('HTTP ' + r.status)),
then returns r.json(); replace the inline fetch call in
resgrid.dispatch.newcall.js with
fetchApi('/api/v4/Geocoding/ReverseGeocode?lat=' + lat + '&lon=' + lng) and
handle .then(result) and .catch(err) the same way so non-OK responses are
surfaced as errors before JSON processing (refer to the existing Authorization
header, userSuppliedAddress check, and $("#Call_Address").val usage to locate
where to change).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs`:
- Around line 27-34: The Select on zones can NRE if SearchZonesAsync returns
null; in IndoorMapsController (the action where zones is assigned from
_indoorMapService.SearchZonesAsync), guard zones before projecting by replacing
the null with an empty enumerable or returning an appropriate empty result.
Locate the variable zones and the projection into results (zones.Select(...))
and change the code to use a null-coalescing fallback (e.g., zones ??
Enumerable.Empty<YourZoneType>()) or check for null and set results to an empty
sequence so results.Select never runs on null.

In `@Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Floors.cshtml`:
- Around line 78-81: Add an anti-forgery token to the delete form used for the
DeleteFloor action and ensure the confirm dialog string is safely escaped:
inside the form that posts to IndoorMapsController.DeleteFloor (the form that
posts the hidden id floor.IndoorMapFloorId), call Html.AntiForgeryToken() so
[ValidateAntiForgeryToken] won’t 400; for the onclick confirm use a
JSON-encoded/JS-escaped string (e.g., serialize localizer["DeleteFloorConfirm"]
with Json.Serialize or output via Html.Raw(Json.Serialize(...))) or move the
confirmation to a data-confirm attribute and handle with JavaScript so
apostrophes in localizer["DeleteFloorConfirm"] won’t break the JS string.

In
`@Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js`:
- Around line 114-128: The fetch call that starts with
fetch(resgrid.absoluteApiBaseUrl + '/api/v4/Geocoding/ForwardGeocode?address=' +
...) does not check the HTTP response status before calling r.json(), so 4xx/5xx
responses (e.g. 401) will be parsed as JSON and treated as successful; update
the promise chain to first check the Response object (use response.ok or
response.status) and if not ok throw or handle a clear error (include
response.status/statusText), only call r.json() when response.ok, and in the
catch/logging include the HTTP status and message; keep existing success
handling that uses result.Data and
resgrid.dispatch.newcall.setMarkerLocation(lat, lng).

---

Outside diff comments:
In `@Web/Resgrid.Web/Areas/User/Views/Mapping/EditLayer.cshtml`:
- Around line 195-227: In the submit handler for '#newLayerForm' remove the call
to e.preventDefault() and the explicit this.submit() so the handler only
serializes the map into '#GeoJson' and then returns, allowing normal form
submission and client-side validation to run; keep the layers -> features
mapping and the $('#GeoJson').val(JSON.stringify(featureCollection)) logic
intact inside the handler. Also add the view-level Scripts partial inclusion
<partial name="_ValidationScriptsPartial" /> (in the Scripts section of this
view) to enable client-side validation scripts.
- Around line 202-203: The view is injecting user-controlled strings
(geo.properties.color, geo.properties.name, geo.properties.text) into
Model.GeoJson and rendering it with `@Html.Raw`(Model.GeoJson), allowing a script
injection; fix by ensuring these fields are validated/sanitized on the server
before they are included in the GeoJSON (e.g., enforce a strict color
whitelist/hex regex for color and strip/encode HTML/JS from name/text or store
them outside the GeoJSON), and stop rendering raw user strings client-side by
emitting the GeoJSON via a safe JSON serializer (server-side JSON
encode/serialize) instead of `@Html.Raw`(Model.GeoJson).

---

Nitpick comments:
In `@Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs`:
- Line 97: Replace the hardcoded TempData["ErrorMessage"] = "Failed to delete
the floor. Please try again."; with a localized string lookup so the UI uses
resource translations; locate the assignment in IndoorMapsController (the
DeleteFloor / floor deletion action) and change it to set
TempData["ErrorMessage"] = _localizer["FailedToDeleteFloor"] (or
Resource.Messages.FailedToDeleteFloor) using the controller's IStringLocalizer
or existing resource class, adding the "FailedToDeleteFloor" key to the resource
files for each supported culture.

In `@Web/Resgrid.Web/Views/Account/LogOn.cshtml`:
- Around line 104-132: Duplicate locale-to-flag mappings are hard-coded in this
view (the <ul class="dropdown-menu"> entries) and a repeated flagMap elsewhere;
extract the locale/flag metadata into a single shared source (e.g., a
server-provided JSON endpoint or a shared JS module/partial such as a
_LocaleFlags partial or locales.json) and replace the hard-coded <a
class="langDropdownSelection" data-locale="..."> items with a loop that renders
entries from that shared data; update the other occurrence noted (lines 175-185
/ the other flagMap usage) to read from the same shared module/endpoint so all
views/scripts use one canonical locale-to-flag mapping.

In
`@Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.editcall.js`:
- Around line 354-364: Extract the duplicated fetch/reverse-geocoding logic used
in findLocation and geocodeCoordinates into a single helper (e.g.,
reverseGeocodeAndUpdateAddress) that accepts lat/lng, performs the fetch to
'/api/v4/Geocoding/ReverseGeocode', handles the Authorization header, parses
JSON, checks result.Data.Address, and updates $("#Call_Address").val(...); then
replace the bodies of findLocation and geocodeCoordinates so they simply call
reverseGeocodeAndUpdateAddress(lat, lng) and keep editcall.geocodeCoordinates
assignment unchanged.

In
`@Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js`:
- Around line 365-372: The reverse-geocode fetch lacks an HTTP status check;
extract a helper like fetchApi that wraps fetch with the Authorization header,
checks r.ok and throws on non-2xx (e.g., throw new Error('HTTP ' + r.status)),
then returns r.json(); replace the inline fetch call in
resgrid.dispatch.newcall.js with
fetchApi('/api/v4/Geocoding/ReverseGeocode?lat=' + lat + '&lon=' + lng) and
handle .then(result) and .catch(err) the same way so non-OK responses are
surfaced as errors before JSON processing (refer to the existing Authorization
header, userSuppliedAddress check, and $("#Call_Address").val usage to locate
where to change).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dcaf0a09-228d-4339-b068-e23140bef684

📥 Commits

Reviewing files that changed from the base of the PR and between f1e7368 and 1046e3c.

⛔ Files ignored due to path filters (34)
  • Core/Resgrid.Localization/Account/Login.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Account/DeleteAccount.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Account/ForcePasswordChange.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Calendar/Calendar.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Contacts/Contacts.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/CustomStatuses/CustomStatuses.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Department/Department.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Department/DepartmentTypes.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Dispatch/Call.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Dispatch/Dashboard.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Documents/Documents.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Home/EditProfile.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Home/HomeDashboard.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/IndoorMaps/IndoorMaps.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Inventory/Inventory.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Logs/Logs.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Mapping/Mapping.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Messages/Messages.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Notes/Note.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Personnel/Person.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Profile/Profile.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Reports/FlaggedReport.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Routes/Routes.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Security/Security.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Shifts/Shifts.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Subscription/Subscription.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Templates/Templates.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/TwoFactor/TwoFactor.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Units/Units.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/UserDefinedFields/UserDefinedFields.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Voice/Voice.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Workflows/Workflows.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.ar.resx is excluded by !**/*.resx
📒 Files selected for processing (10)
  • Core/Resgrid.Localization/SupportedLocales.cs
  • Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs
  • Web/Resgrid.Web/Areas/User/Views/CustomMaps/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Floors.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Mapping/EditLayer.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Shared/_TopNavbar.cshtml
  • Web/Resgrid.Web/Views/Account/LogOn.cshtml
  • Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.editcall.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/resgrid.user.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • Web/Resgrid.Web/Areas/User/Views/CustomMaps/Index.cshtml

Comment on lines +27 to +34
var zones = await _indoorMapService.SearchZonesAsync(DepartmentId, term);

var results = zones.Select(z => new
{
id = z.IndoorMapZoneId,
text = z.Name,
floorId = z.IndoorMapFloorId
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard zones before Select to avoid a possible null-reference.

If SearchZonesAsync returns null, Line 29 will throw and return a 500 from this endpoint.

🛠️ Proposed fix
 var zones = await _indoorMapService.SearchZonesAsync(DepartmentId, term);
+if (zones == null)
+	return Json(new { results = System.Array.Empty<object>() });

 var results = zones.Select(z => new
 {
 	id = z.IndoorMapZoneId,
 	text = z.Name,
 	floorId = z.IndoorMapFloorId
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var zones = await _indoorMapService.SearchZonesAsync(DepartmentId, term);
var results = zones.Select(z => new
{
id = z.IndoorMapZoneId,
text = z.Name,
floorId = z.IndoorMapFloorId
});
var zones = await _indoorMapService.SearchZonesAsync(DepartmentId, term);
if (zones == null)
return Json(new { results = System.Array.Empty<object>() });
var results = zones.Select(z => new
{
id = z.IndoorMapZoneId,
text = z.Name,
floorId = z.IndoorMapFloorId
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs` around lines
27 - 34, The Select on zones can NRE if SearchZonesAsync returns null; in
IndoorMapsController (the action where zones is assigned from
_indoorMapService.SearchZonesAsync), guard zones before projecting by replacing
the null with an empty enumerable or returning an appropriate empty result.
Locate the variable zones and the projection into results (zones.Select(...))
and change the code to use a null-coalescing fallback (e.g., zones ??
Enumerable.Empty<YourZoneType>()) or check for null and set results to an empty
sequence so results.Select never runs on null.

Comment on lines +78 to +81
<form asp-controller="IndoorMaps" asp-action="DeleteFloor" asp-route-area="User" method="post" style="display:inline;">
<input type="hidden" name="id" value="@floor.IndoorMapFloorId" />
<button type="submit" class="btn btn-danger btn-xs" onclick="return confirm('@localizer["DeleteFloorConfirm"]');"><i class="fa fa-trash"></i> @localizer["Delete"]</button>
</form>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing anti-forgery token in delete form.

The delete form is missing @Html.AntiForgeryToken(), unlike the NewFloor form on line 27. If the controller action has [ValidateAntiForgeryToken], this will cause a 400 error. If not, this is a CSRF vulnerability.

Additionally, the confirm() dialog on line 80 could break if the localized string contains apostrophes (e.g., "Are you sure you'd like to delete this floor?").

🛡️ Proposed fix
 <form asp-controller="IndoorMaps" asp-action="DeleteFloor" asp-route-area="User" method="post" style="display:inline;">
+    `@Html.AntiForgeryToken`()
     <input type="hidden" name="id" value="@floor.IndoorMapFloorId" />
-    <button type="submit" class="btn btn-danger btn-xs" onclick="return confirm('@localizer["DeleteFloorConfirm"]');"><i class="fa fa-trash"></i> `@localizer`["Delete"]</button>
+    <button type="submit" class="btn btn-danger btn-xs" onclick="return confirm('@Html.Raw(System.Text.Encodings.Web.JavaScriptEncoder.Default.Encode(localizer["DeleteFloorConfirm"].Value))');"><i class="fa fa-trash"></i> `@localizer`["Delete"]</button>
 </form>

Or use @Json.Serialize() for proper JavaScript escaping:

-    <button type="submit" class="btn btn-danger btn-xs" onclick="return confirm('@localizer["DeleteFloorConfirm"]');"><i class="fa fa-trash"></i> `@localizer`["Delete"]</button>
+    <button type="submit" class="btn btn-danger btn-xs" onclick="return confirm(`@Json.Serialize`(localizer["DeleteFloorConfirm"].Value));"><i class="fa fa-trash"></i> `@localizer`["Delete"]</button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<form asp-controller="IndoorMaps" asp-action="DeleteFloor" asp-route-area="User" method="post" style="display:inline;">
<input type="hidden" name="id" value="@floor.IndoorMapFloorId" />
<button type="submit" class="btn btn-danger btn-xs" onclick="return confirm('@localizer["DeleteFloorConfirm"]');"><i class="fa fa-trash"></i> @localizer["Delete"]</button>
</form>
<form asp-controller="IndoorMaps" asp-action="DeleteFloor" asp-route-area="User" method="post" style="display:inline;">
`@Html.AntiForgeryToken`()
<input type="hidden" name="id" value="@floor.IndoorMapFloorId" />
<button type="submit" class="btn btn-danger btn-xs" onclick="return confirm('@Html.Raw(System.Text.Encodings.Web.JavaScriptEncoder.Default.Encode(localizer["DeleteFloorConfirm"].Value))');"><i class="fa fa-trash"></i> `@localizer`["Delete"]</button>
</form>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Floors.cshtml` around lines 78 -
81, Add an anti-forgery token to the delete form used for the DeleteFloor action
and ensure the confirm dialog string is safely escaped: inside the form that
posts to IndoorMapsController.DeleteFloor (the form that posts the hidden id
floor.IndoorMapFloorId), call Html.AntiForgeryToken() so
[ValidateAntiForgeryToken] won’t 400; for the onclick confirm use a
JSON-encoded/JS-escaped string (e.g., serialize localizer["DeleteFloorConfirm"]
with Json.Serialize or output via Html.Raw(Json.Serialize(...))) or move the
confirmation to a data-confirm attribute and handle with JavaScript so
apostrophes in localizer["DeleteFloorConfirm"] won’t break the JS string.

Comment on lines +114 to +128
fetch(resgrid.absoluteApiBaseUrl + '/api/v4/Geocoding/ForwardGeocode?address=' + encodeURIComponent(where), { headers: { 'Authorization': 'Bearer ' + localStorage.getItem('RgWebApp.auth-tokens') } })
.then(function(r) { return r.json(); })
.then(function(result) {
if (result && result.Data && result.Data.Latitude != null && result.Data.Longitude != null) {
var lat = result.Data.Latitude;
var lng = result.Data.Longitude;
map.panTo(new L.LatLng(lat, lng));
$("#Latitude").val(lat.toString());
$("#Longitude").val(lng.toString());
resgrid.dispatch.newcall.setMarkerLocation(lat, lng);
} else {
console.log("Geocode returned no results for: " + where);
}
})
.catch(function(err) { console.error("Geocode error:", err); });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing HTTP response status check before parsing JSON.

fetch() only rejects on network errors, not HTTP 4xx/5xx responses. If the server returns 401 (expired/invalid token) or 400, the code attempts to parse the error response as JSON and proceeds with potentially malformed data.

Proposed fix to check response status
                     fetch(resgrid.absoluteApiBaseUrl + '/api/v4/Geocoding/ForwardGeocode?address=' + encodeURIComponent(where), { headers: { 'Authorization': 'Bearer ' + localStorage.getItem('RgWebApp.auth-tokens') } })
-                        .then(function(r) { return r.json(); })
+                        .then(function(r) {
+                            if (!r.ok) throw new Error('HTTP ' + r.status);
+                            return r.json();
+                        })
                         .then(function(result) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js`
around lines 114 - 128, The fetch call that starts with
fetch(resgrid.absoluteApiBaseUrl + '/api/v4/Geocoding/ForwardGeocode?address=' +
...) does not check the HTTP response status before calling r.json(), so 4xx/5xx
responses (e.g. 401) will be parsed as JSON and treated as successful; update
the promise chain to first check the Response object (use response.ok or
response.status) and if not ok throw or handle a clear error (include
response.status/statusText), only call r.json() when response.ok, and in the
catch/logging include the HTTP status and message; keep existing success
handling that uses result.Data and
resgrid.dispatch.newcall.setMarkerLocation(lat, lng).

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs (1)

28-35: ⚠️ Potential issue | 🟡 Minor

Guard against null return from SearchZonesAsync.

If SearchZonesAsync returns null, the Select on line 30 will throw a NullReferenceException, resulting in a 500 response.

🛡️ Proposed fix
 			var zones = await _indoorMapService.SearchZonesAsync(DepartmentId, term);
+			if (zones == null)
+				return Json(new { results = System.Array.Empty<object>() });

 			var results = zones.Select(z => new
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs` around lines
28 - 35, SearchZonesAsync may return null so the LINQ Select on zones can throw;
in IndoorMapsController where you call
_indoorMapService.SearchZonesAsync(DepartmentId, term) check for null before
projecting (e.g., treat null as an empty collection) and build results from an
empty sequence when zones is null; update the code that defines zones and
results to null-guard the projection (using a conditional, null-coalescing to
Enumerable.Empty<...>(), or an early return) so Select is never invoked on a
null reference.
🧹 Nitpick comments (1)
Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs (1)

13-14: Redundant fully-qualified namespace on ILogger.

Line 5 already imports Microsoft.Extensions.Logging, so the field declaration can simply use ILogger<IndoorMapsController>.

♻️ Proposed simplification
 		private readonly IIndoorMapService _indoorMapService;
-		private readonly Microsoft.Extensions.Logging.ILogger<IndoorMapsController> _logger;
+		private readonly ILogger<IndoorMapsController> _logger;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs` around lines
13 - 14, The field declaration for the logger in IndoorMapsController uses a
redundant fully-qualified name; update the private field declaration named
_logger to use the imported ILogger generic (ILogger<IndoorMapsController>)
instead of Microsoft.Extensions.Logging.ILogger<IndoorMapsController>; verify
the class still compiles and that other references to _logger and the
IIndoorMapService field remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs`:
- Around line 28-35: SearchZonesAsync may return null so the LINQ Select on
zones can throw; in IndoorMapsController where you call
_indoorMapService.SearchZonesAsync(DepartmentId, term) check for null before
projecting (e.g., treat null as an empty collection) and build results from an
empty sequence when zones is null; update the code that defines zones and
results to null-guard the projection (using a conditional, null-coalescing to
Enumerable.Empty<...>(), or an early return) so Select is never invoked on a
null reference.

---

Nitpick comments:
In `@Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs`:
- Around line 13-14: The field declaration for the logger in
IndoorMapsController uses a redundant fully-qualified name; update the private
field declaration named _logger to use the imported ILogger generic
(ILogger<IndoorMapsController>) instead of
Microsoft.Extensions.Logging.ILogger<IndoorMapsController>; verify the class
still compiles and that other references to _logger and the IIndoorMapService
field remain unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e50aa1d0-4918-4560-bb5d-e11b1e9414c7

📥 Commits

Reviewing files that changed from the base of the PR and between 1046e3c and ded7476.

📒 Files selected for processing (2)
  • Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs
  • Web/Resgrid.Web/Areas/User/Views/Dispatch/Dashboard.cshtml

@ucswift
Copy link
Copy Markdown
Member Author

ucswift commented Mar 18, 2026

Approve

Copy link
Copy Markdown

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is approved.

@ucswift ucswift merged commit 1a0db64 into master Mar 18, 2026
16 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant