From f1e7368f4ab426a9612a0f940ce1cdcfb9bc4b97 Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Tue, 17 Mar 2026 14:34:31 -0700 Subject: [PATCH 1/3] RE1-T105 More custom maps work --- Core/Resgrid.Config/MappingConfig.cs | 6 + .../Areas/User/CustomMaps/CustomMaps.cs | 6 + .../Areas/User/CustomMaps/CustomMaps.en.resx | 368 ++++++++++++++++++ .../Areas/User/IndoorMaps/IndoorMaps.cs | 6 + .../Areas/User/IndoorMaps/IndoorMaps.en.resx | 259 ++++++++++++ .../Areas/User/Mapping/Mapping.cs | 6 + .../Areas/User/Mapping/Mapping.en.resx | 287 ++++++++++++++ .../Areas/User/Routes/Routes.cs | 6 + .../Areas/User/Routes/Routes.en.resx | 342 ++++++++++++++++ Core/Resgrid.Model/PermissionTypes.cs | 3 +- .../Resgrid.Providers.Claims/ClaimsLogic.cs | 7 +- .../GeoLocationProvider.cs | 2 +- .../Controllers/v4/GeocodingController.cs | 99 +++++ .../Models/v4/Geocoding/GeocodingResults.cs | 45 +++ .../Resgrid.Web.Services.xml | 47 +++ Web/Resgrid.Web.Services/appsettings.json | 10 + .../User/Controllers/CustomMapsController.cs | 12 +- .../User/Controllers/DispatchController.cs | 5 + .../User/Controllers/IndoorMapsController.cs | 27 ++ .../User/Controllers/RoutesController.cs | 91 ++++- .../Areas/User/Views/Calendar/Edit.cshtml | 2 +- .../Areas/User/Views/Calendar/New.cshtml | 2 +- .../Areas/User/Views/CustomMaps/Edit.cshtml | 49 +-- .../Areas/User/Views/CustomMaps/Import.cshtml | 33 +- .../Areas/User/Views/CustomMaps/Index.cshtml | 37 +- .../Areas/User/Views/CustomMaps/Layers.cshtml | 55 +-- .../Areas/User/Views/CustomMaps/New.cshtml | 49 +-- .../User/Views/CustomMaps/RegionEditor.cshtml | 87 +++-- .../Views/Dispatch/AddArchivedCall.cshtml | 4 +- .../User/Views/Dispatch/CallExport.cshtml | 6 +- .../User/Views/Dispatch/CallExportEx.cshtml | 112 +++--- .../User/Views/Dispatch/Dashboard.cshtml | 12 +- .../Areas/User/Views/Dispatch/NewCall.cshtml | 3 +- .../User/Views/Dispatch/UpdateCall.cshtml | 3 +- .../Areas/User/Views/Dispatch/ViewCall.cshtml | 8 +- .../Areas/User/Views/Groups/Geofence.cshtml | 40 +- .../Areas/User/Views/IndoorMaps/Edit.cshtml | 43 +- .../Areas/User/Views/IndoorMaps/Floors.cshtml | 37 +- .../Areas/User/Views/IndoorMaps/Index.cshtml | 31 +- .../Areas/User/Views/IndoorMaps/New.cshtml | 43 +- .../User/Views/IndoorMaps/ZoneEditor.cshtml | 61 +-- .../Areas/User/Views/Logs/LogExport.cshtml | 6 +- .../Areas/User/Views/Mapping/AddPOI.cshtml | 27 +- .../User/Views/Mapping/AddPOIType.cshtml | 31 +- .../Areas/User/Views/Mapping/EditLayer.cshtml | 57 +-- .../User/Views/Mapping/ImportPOIs.cshtml | 21 +- .../Areas/User/Views/Mapping/Index.cshtml | 35 +- .../Areas/User/Views/Mapping/Layers.cshtml | 25 +- .../User/Views/Mapping/LiveRouting.cshtml | 111 +++--- .../Areas/User/Views/Mapping/NewLayer.cshtml | 57 +-- .../Areas/User/Views/Mapping/POIs.cshtml | 31 +- .../User/Views/Mapping/StationRouting.cshtml | 113 +++--- .../Areas/User/Views/Mapping/ViewType.cshtml | 46 +-- .../User/Views/Reports/ActionLogs.cshtml | 6 +- .../Reports/ActiveCallsResourcesReport.cshtml | 6 +- .../Views/Reports/CallSummaryReport.cshtml | 6 +- .../Views/Reports/CertificationsReport.cshtml | 6 +- .../Reports/DepartmentActivityReport.cshtml | 6 +- .../Reports/FlaggedCallNotesReport.cshtml | 6 +- .../Areas/User/Views/Reports/LogReport.cshtml | 6 +- .../Reports/PersonnelEventsReport.cshtml | 6 +- .../Reports/PersonnelHoursDetailReport.cshtml | 6 +- .../Views/Reports/PersonnelHoursReport.cshtml | 6 +- .../User/Views/Reports/PersonnelReport.cshtml | 6 +- .../PersonnelStaffingHistoryReport.cshtml | 6 +- .../User/Views/Reports/StaffingReport.cshtml | 6 +- .../Views/Reports/UnitEventsReport.cshtml | 6 +- .../Reports/UnitStateHistoryReport.cshtml | 6 +- .../UpcomingShiftReadinessReport.cshtml | 6 +- .../User/Views/Routes/ActiveRoutes.cshtml | 23 +- .../Areas/User/Views/Routes/Edit.cshtml | 200 ++++++++-- .../Areas/User/Views/Routes/Index.cshtml | 33 +- .../User/Views/Routes/InstanceDetail.cshtml | 39 +- .../Areas/User/Views/Routes/Instances.cshtml | 25 +- .../Areas/User/Views/Routes/New.cshtml | 55 +-- .../Areas/User/Views/Routes/View.cshtml | 29 +- .../User/Views/Shared/_ChatWidget.cshtml | 4 +- .../Views/Subscription/ViewInvoice.cshtml | 6 +- .../Views/Account/AffiliateRegister.cshtml | 2 +- .../Views/Account/CompletedInvite.cshtml | 6 +- .../Views/Account/ForgotPassword.cshtml | 2 +- Web/Resgrid.Web/Views/Account/Lockout.cshtml | 6 +- .../Views/Account/MissingCode.cshtml | 8 +- .../Views/Account/MissingInvite.cshtml | 6 +- Web/Resgrid.Web/Views/Account/Register.cshtml | 2 +- Web/Resgrid.Web/Views/Shared/Error.cshtml | 6 +- .../Views/Shared/Unauthorized.cshtml | 6 +- .../Views/Shared/_ScriptsPartial.cshtml | 4 +- .../Views/Shared/_StylePartial.cshtml | 2 +- Web/Resgrid.Web/libman.json | 2 +- .../custommaps/resgrid.custommaps.editor.js | 98 ++--- .../resgrid.custommaps.regioneditor.js | 8 +- .../resgrid.dispatch.addArchivedCall.js | 83 ++-- .../dispatch/resgrid.dispatch.editcall.js | 87 ++--- .../dispatch/resgrid.dispatch.newcall.js | 90 ++--- .../groups/resgrid.groups.geofence.js | 134 ++++--- .../indoormaps/resgrid.indoormaps.editor.js | 4 +- .../resgrid.indoormaps.zoneeditor.js | 4 +- .../mapping/resgrid.mapping.viewType.js | 146 ++----- .../internal/routes/resgrid.routes.edit.js | 267 ++++++++++++- .../routes/resgrid.routes.instancedetail.js | 2 +- .../internal/routes/resgrid.routes.view.js | 13 +- 102 files changed, 3115 insertions(+), 1294 deletions(-) create mode 100644 Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.cs create mode 100644 Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.en.resx create mode 100644 Core/Resgrid.Localization/Areas/User/IndoorMaps/IndoorMaps.cs create mode 100644 Core/Resgrid.Localization/Areas/User/IndoorMaps/IndoorMaps.en.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Mapping/Mapping.cs create mode 100644 Core/Resgrid.Localization/Areas/User/Mapping/Mapping.en.resx create mode 100644 Core/Resgrid.Localization/Areas/User/Routes/Routes.cs create mode 100644 Core/Resgrid.Localization/Areas/User/Routes/Routes.en.resx create mode 100644 Web/Resgrid.Web.Services/Controllers/v4/GeocodingController.cs create mode 100644 Web/Resgrid.Web.Services/Models/v4/Geocoding/GeocodingResults.cs diff --git a/Core/Resgrid.Config/MappingConfig.cs b/Core/Resgrid.Config/MappingConfig.cs index f127a2ce1..0622b9e28 100644 --- a/Core/Resgrid.Config/MappingConfig.cs +++ b/Core/Resgrid.Config/MappingConfig.cs @@ -59,6 +59,12 @@ public static class MappingConfig public static string LeafletAttribution = "© OpenStreetMap contributors CC-BY-SA"; + /*********************************** + * Geocoding and Routing Service URLs + ***********************************/ + public static string NominatimUrl = "https://nominatim.openstreetmap.org"; + public static string OsrmUrl = "https://router.project-osrm.org"; + public static string GetWebsiteOSMUrl() { if (!string.IsNullOrWhiteSpace(WebsiteOSMKey)) diff --git a/Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.cs b/Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.cs new file mode 100644 index 000000000..4aec77040 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.cs @@ -0,0 +1,6 @@ +namespace Resgrid.Localization.Areas.User.CustomMaps +{ + public class CustomMaps + { + } +} diff --git a/Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.en.resx b/Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.en.resx new file mode 100644 index 000000000..d9fdbed56 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/CustomMaps/CustomMaps.en.resx @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Cancel + + + Save + + + Name + + + Description + + + Edit + + + Delete + + + Import + + + Type + + + Created + + + Order + + + Yes + + + No + + + + Custom Maps + + + Custom Maps + + + New Custom Map + + + All + + + Indoor + + + Outdoor + + + Event + + + Custom + + + Layers + + + Are you sure you want to delete this map? + + + + New Custom Map + + + New Custom Map + + + New + + + Map Type + + + Indoor + + + Outdoor + + + Event + + + Custom + + + e.g. Main Hospital, Concert Venue + + + Optional description + + + Draw Bounds + + + Draw a rectangle on the map below to define the bounds. Click the map to set the center point. + + + Draw a rectangle on the map below to update the bounds. Click the map to set the center point. + + + + Edit Custom Map + + + Edit Custom Map + + + Edit + + + + Layers + + + Layers + + + Layers + + + Add Layer + + + Layer Name + + + e.g. Floor 1, Base Map, Infrastructure + + + Order + + + Type + + + Floor Plan + + + Overlay + + + Data Layer + + + Infrastructure + + + Layer Image + + + Images larger than 2048px will be automatically tiled. + + + Add Layer + + + Has Image + + + Tiled + + + Region Editor + + + Delete this layer? + + + + Region Editor + + + Region Editor + + + Regions + + + Region Properties + + + Name + + + Type + + + Room + + + Wing + + + Corridor + + + Stairwell + + + Elevator + + + Hazard Zone + + + Assembly Point + + + Staging Area + + + Access Point + + + Utility + + + Search Grid + + + Stage + + + Gate + + + Entrance + + + Parking Area + + + Vendor Area + + + Emergency Lane + + + District + + + Custom + + + Color + + + Description + + + Searchable in dispatch + + + Dispatchable + + + Save Region + + + Dispatchable + + + + Import + + + Import + + + Import + + + Import Geospatial Data + + + Target Layer + + + File + + + Supported formats: GeoJSON (.geojson, .json), KML (.kml), KMZ (.kmz) + + + Import + + + Import History + + + Status + + + Date + + + Error + + diff --git a/Core/Resgrid.Localization/Areas/User/IndoorMaps/IndoorMaps.cs b/Core/Resgrid.Localization/Areas/User/IndoorMaps/IndoorMaps.cs new file mode 100644 index 000000000..fc51574b7 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/IndoorMaps/IndoorMaps.cs @@ -0,0 +1,6 @@ +namespace Resgrid.Localization.Areas.User.IndoorMaps +{ + public class IndoorMaps + { + } +} diff --git a/Core/Resgrid.Localization/Areas/User/IndoorMaps/IndoorMaps.en.resx b/Core/Resgrid.Localization/Areas/User/IndoorMaps/IndoorMaps.en.resx new file mode 100644 index 000000000..f03fe729f --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/IndoorMaps/IndoorMaps.en.resx @@ -0,0 +1,259 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Cancel + + + Save + + + Name + + + Description + + + Edit + + + Delete + + + Created + + + Yes + + + No + + + + Indoor Maps + + + Indoor Maps + + + New Indoor Map + + + Floors + + + Are you sure you want to delete this indoor map? + + + + New Indoor Map + + + New Indoor Map + + + New + + + e.g. Main Hospital + + + Optional description + + + Center Latitude + + + Center Longitude + + + e.g. 39.7392 + + + e.g. -104.9903 + + + NE Bound Lat + + + NE Bound Lon + + + SW Bound Lat + + + SW Bound Lon + + + + Edit Indoor Map + + + Edit Indoor Map + + + Edit + + + + Floors + + + Floors + + + Add Floor + + + Floor Name + + + e.g. Floor 1, Basement, Roof + + + Order + + + Floor Plan Image + + + Add Floor + + + Floors + + + Has Image + + + Zone Editor + + + Delete this floor? + + + + Zone Editor + + + Zone Editor + + + Zones + + + Zone Properties + + + Name + + + Type + + + Room + + + Wing + + + Corridor + + + Stairwell + + + Elevator + + + Hazard Zone + + + Assembly Point + + + Staging Area + + + Access Point + + + Utility + + + Search Grid + + + Custom + + + Color + + + Description + + + Searchable in dispatch + + + Save Zone + + diff --git a/Core/Resgrid.Localization/Areas/User/Mapping/Mapping.cs b/Core/Resgrid.Localization/Areas/User/Mapping/Mapping.cs new file mode 100644 index 000000000..585880b0c --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Mapping/Mapping.cs @@ -0,0 +1,6 @@ +namespace Resgrid.Localization.Areas.User.Mapping +{ + public class Mapping + { + } +} diff --git a/Core/Resgrid.Localization/Areas/User/Mapping/Mapping.en.resx b/Core/Resgrid.Localization/Areas/User/Mapping/Mapping.en.resx new file mode 100644 index 000000000..2a0cb5dc6 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Mapping/Mapping.en.resx @@ -0,0 +1,287 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Mapping + + + Manage Layers + + + Custom Maps + + + Map Options + + + Show Calls + + + Show Personnel + + + Show Units + + + Show Stations + + + Show Geofences + + + Show POIs + + + Close + + + Save + + + Cancel + + + + Add POI + + + Add POI + + + Latitude + + + Latitude (Decimal Notation: i.e. 39.1517) + + + Longitude + + + Longitude (Decimal Notation: i.e. -119.4571) + + + Note + + + + Add POI Type + + + Add POI Type + + + Name + + + Is Destination + + + To denote a potential destination for a unit/person. For example an office, rally point, hospital, access point, etc. + + + Color + + + Marker + + + Icon + + + None + + + + Edit Layer + + + Edit Layer + + + New Layer + + + New Layer + + + Is Visible By Default + + + Do you want this layer to be visible every time a map loads? Note: Having too many layers visible by default will complicate the map. Users can turn layers on if they want to see them in the map control. + + + Is Destination + + + This will allow address searching to use coordinates from this layer in the search. Note, if you don't have searchable points (text fields) don't mark as searchable as it can slow down dispatching. + + + Layer Elements + + + Edit Layer + + + Add Layer + + + + Import POIs + + + Import POIs + + + Upload + + + Import POIs + + + + Layers + + + Layers + + + New Layer + + + Item Count + + + Edit + + + Delete + + + + Live Navigation + + + Live Routing + + + + POIs + + + POIs + + + Add POI Type + + + Point Count + + + View + + + Add + + + Import + + + WARNING: This will permanently delete this POI Type and all it's positions. Are you sure you want to delete the + + + POI Type? + + + + Station Navigation + + + Station Routing + + + Distance: + + + Duration: + + + + View Type + + + View Type + + + Map + + + Information + + + List + + + True + + + False + + diff --git a/Core/Resgrid.Localization/Areas/User/Routes/Routes.cs b/Core/Resgrid.Localization/Areas/User/Routes/Routes.cs new file mode 100644 index 000000000..85f715dba --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Routes/Routes.cs @@ -0,0 +1,6 @@ +namespace Resgrid.Localization.Areas.User.Routes +{ + public class Routes + { + } +} diff --git a/Core/Resgrid.Localization/Areas/User/Routes/Routes.en.resx b/Core/Resgrid.Localization/Areas/User/Routes/Routes.en.resx new file mode 100644 index 000000000..5643d1668 --- /dev/null +++ b/Core/Resgrid.Localization/Areas/User/Routes/Routes.en.resx @@ -0,0 +1,342 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Cancel + + + Save + + + Name + + + Description + + + Status + + + Actions + + + + Routes + + + Routes + + + New Route + + + Active Routes + + + Stops + + + Profile + + + Created + + + View + + + Edit + + + History + + + Delete this route plan? + + + + Route History + + + Route History + + + History + + + Unit + + + Started + + + Ended + + + Detail + + + + Active Routes + + + Active Routes + + + Active + + + No Active Routes + + + There are no route instances currently in progress. + + + Unit + + + Stops + + + Started + + + View Details + + + + New Route + + + New Route Plan + + + New + + + Route Details + + + Unit + + + -- Assign at start time -- + + + Draft + + + Active + + + Paused + + + Archived + + + Route Color + + + Route Profile + + + Driving + + + Walking + + + Cycling + + + Driving (Traffic) + + + Geofence Radius (m) + + + Use Station as Start + + + Use Station as End + + + Optimize Stop Order + + + Map & Stops + + + Stops can be managed after creating the route via the API or edit form. + + + Create Route + + + + Edit Route + + + Edit Route Plan + + + Edit + + + # + + + Type + + + Address + + + Priority + + + Save Changes + + + + Route Detail + + + View + + + Route Map + + + Details + + + Status + + + Profile + + + Geofence + + + Distance + + + Duration + + + + Route Instance Detail + + + Route Instance + + + Instance Detail + + + Instance Info + + + Started + + + Ended + + + Progress + + + Stop Timeline + + + Pending + + + Checked In + + + Completed + + + Skipped + + + Unknown + + + In: + + + Out: + + + Dwell: + + + Skip: + + + Stop + + diff --git a/Core/Resgrid.Model/PermissionTypes.cs b/Core/Resgrid.Model/PermissionTypes.cs index bdc7d23fd..37aa46a90 100644 --- a/Core/Resgrid.Model/PermissionTypes.cs +++ b/Core/Resgrid.Model/PermissionTypes.cs @@ -28,8 +28,7 @@ public enum PermissionTypes ManageWorkflowCredentials = 23, ViewWorkflowRuns = 24, ViewUdfFields = 25, - CreateRoute = 26, - ManageRoutes = 27 + ManageRoutes = 26 } } diff --git a/Providers/Resgrid.Providers.Claims/ClaimsLogic.cs b/Providers/Resgrid.Providers.Claims/ClaimsLogic.cs index feb0f716d..81c730f35 100644 --- a/Providers/Resgrid.Providers.Claims/ClaimsLogic.cs +++ b/Providers/Resgrid.Providers.Claims/ClaimsLogic.cs @@ -1539,9 +1539,10 @@ public static void AddUdfClaims(ClaimsIdentity identity, bool isAdmin, List /// Adds Route claims based on department role. /// Department admins always receive full access (View + Update + Create + Delete). - /// Group admins receive View + Update when the permission is DepartmentAndGroupAdmins or Everyone. - /// Regular users receive View only when the permission is Everyone. - /// Default (no permission record): Everyone — all users can view routes. + /// DepartmentAdminsOnly: non-admins receive no claims. + /// DepartmentAndGroupAdmins: group admins receive View + Update + Create; regular users receive no claims. + /// Everyone: all users receive View + Update + Create. + /// Default (no permission record): all users receive View; group admins additionally receive Update + Create. /// public static void AddRouteClaims(ClaimsIdentity identity, bool isAdmin, List permissions, bool isGroupAdmin, List roles) { diff --git a/Providers/Resgrid.Providers.Geo/GeoLocationProvider.cs b/Providers/Resgrid.Providers.Geo/GeoLocationProvider.cs index 9b355782d..f33a4f90f 100644 --- a/Providers/Resgrid.Providers.Geo/GeoLocationProvider.cs +++ b/Providers/Resgrid.Providers.Geo/GeoLocationProvider.cs @@ -107,7 +107,7 @@ async Task getCordsFromAddress() coordinates = string.Format("{0},{1}", firstAddress.Coordinates.Latitude, firstAddress.Coordinates.Longitude); } } - catch { /* Don't report on GeoLocation failures */ } + catch (Exception ex) { var test = ex; /* Don't report on GeoLocation failures */ } if (string.IsNullOrWhiteSpace(coordinates)) { diff --git a/Web/Resgrid.Web.Services/Controllers/v4/GeocodingController.cs b/Web/Resgrid.Web.Services/Controllers/v4/GeocodingController.cs new file mode 100644 index 000000000..fd82207e7 --- /dev/null +++ b/Web/Resgrid.Web.Services/Controllers/v4/GeocodingController.cs @@ -0,0 +1,99 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Resgrid.Model.Providers; +using Resgrid.Providers.Claims; +using Resgrid.Web.Services.Helpers; +using Resgrid.Web.Services.Models.v4.Geocoding; + +namespace Resgrid.Web.Services.Controllers.v4 +{ + /// + /// Forward and reverse geocoding operations. Requests are proxied through the + /// server-side geocoding provider so that external API keys are never exposed + /// to the client, and so that all calls pass through the configured rate limiter. + /// + [Route("api/v{VersionId:apiVersion}/[controller]")] + [ApiVersion("4.0")] + [ApiExplorerSettings(GroupName = "v4")] + public class GeocodingController : V4AuthenticatedApiControllerbase + { + private readonly IGeoLocationProvider _geoLocationProvider; + + public GeocodingController(IGeoLocationProvider geoLocationProvider) + { + _geoLocationProvider = geoLocationProvider; + } + + /// + /// Converts a human-readable address string into geographic coordinates. + /// + /// Address string to geocode. + /// ForwardGeocodeResult with Latitude/Longitude, or nulls if not found. + [HttpGet("ForwardGeocode")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [Authorize(Policy = ResgridResources.Call_View)] + public async Task> ForwardGeocode([FromQuery] string address) + { + if (string.IsNullOrWhiteSpace(address)) + return BadRequest(); + + var result = new ForwardGeocodeResult(); + + try + { + var coordinates = await _geoLocationProvider.GetLatLonFromAddress(address); + + if (!string.IsNullOrEmpty(coordinates)) + { + var parts = coordinates.Split(','); + if (parts.Length == 2 && + double.TryParse(parts[0], NumberStyles.Any, CultureInfo.InvariantCulture, out var lat) && + double.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var lng)) + { + result.Data.Latitude = lat; + result.Data.Longitude = lng; + } + } + } + catch { /* provider errors are non-fatal */ } + + result.PageSize = 1; + result.Status = ResponseHelper.Success; + ResponseHelper.PopulateV4ResponseData(result); + + return Ok(result); + } + + /// + /// Converts geographic coordinates into a human-readable address string. + /// + /// Latitude of the location to reverse-geocode. + /// Longitude of the location to reverse-geocode. + /// ReverseGeocodeResult with Address, or empty string if not found. + [HttpGet("ReverseGeocode")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Authorize(Policy = ResgridResources.Call_View)] + public async Task> ReverseGeocode([FromQuery] double lat, [FromQuery] double lon) + { + var result = new ReverseGeocodeResult(); + + try + { + var address = await _geoLocationProvider.GetAddressFromLatLong(lat, lon); + result.Data.Address = address ?? string.Empty; + } + catch { /* provider errors are non-fatal */ } + + result.PageSize = 1; + result.Status = ResponseHelper.Success; + ResponseHelper.PopulateV4ResponseData(result); + + return Ok(result); + } + } +} diff --git a/Web/Resgrid.Web.Services/Models/v4/Geocoding/GeocodingResults.cs b/Web/Resgrid.Web.Services/Models/v4/Geocoding/GeocodingResults.cs new file mode 100644 index 000000000..54f8db09e --- /dev/null +++ b/Web/Resgrid.Web.Services/Models/v4/Geocoding/GeocodingResults.cs @@ -0,0 +1,45 @@ +namespace Resgrid.Web.Services.Models.v4.Geocoding +{ + /// + /// Result of a forward geocode (address → coordinates) request. + /// + public class ForwardGeocodeResult : StandardApiResponseV4Base + { + /// Response data + public ForwardGeocodeData Data { get; set; } + + public ForwardGeocodeResult() + { + Data = new ForwardGeocodeData(); + } + } + + public class ForwardGeocodeData + { + /// Latitude of the geocoded location, or null if not found. + public double? Latitude { get; set; } + + /// Longitude of the geocoded location, or null if not found. + public double? Longitude { get; set; } + } + + /// + /// Result of a reverse geocode (coordinates → address) request. + /// + public class ReverseGeocodeResult : StandardApiResponseV4Base + { + /// Response data + public ReverseGeocodeData Data { get; set; } + + public ReverseGeocodeResult() + { + Data = new ReverseGeocodeData(); + } + } + + public class ReverseGeocodeData + { + /// Human-readable address for the supplied coordinates, or empty if not found. + public string Address { get; set; } + } +} diff --git a/Web/Resgrid.Web.Services/Resgrid.Web.Services.xml b/Web/Resgrid.Web.Services/Resgrid.Web.Services.xml index 2a28e0d3e..5bfeea138 100644 --- a/Web/Resgrid.Web.Services/Resgrid.Web.Services.xml +++ b/Web/Resgrid.Web.Services/Resgrid.Web.Services.xml @@ -574,6 +574,28 @@ The form identifier A FormResultData for the requested form + + + Forward and reverse geocoding operations. Requests are proxied through the + server-side geocoding provider so that external API keys are never exposed + to the client, and so that all calls pass through the configured rate limiter. + + + + + Converts a human-readable address string into geographic coordinates. + + Address string to geocode. + ForwardGeocodeResult with Latitude/Longitude, or nulls if not found. + + + + Converts geographic coordinates into a human-readable address string. + + Latitude of the location to reverse-geocode. + Longitude of the location to reverse-geocode. + ReverseGeocodeResult with Address, or empty string if not found. + User generated forms that are dispayed to get custom information for New Calls, Unit Checks, etc @@ -5831,6 +5853,31 @@ Default constructor + + + Result of a forward geocode (address → coordinates) request. + + + + Response data + + + Latitude of the geocoded location, or null if not found. + + + Longitude of the geocoded location, or null if not found. + + + + Result of a reverse geocode (coordinates → address) request. + + + + Response data + + + Human-readable address for the supplied coordinates, or empty if not found. + A group in the Resgrid system diff --git a/Web/Resgrid.Web.Services/appsettings.json b/Web/Resgrid.Web.Services/appsettings.json index 1ea7fa4a9..6a3d8e5f1 100644 --- a/Web/Resgrid.Web.Services/appsettings.json +++ b/Web/Resgrid.Web.Services/appsettings.json @@ -52,6 +52,16 @@ "Period": "60s", "Limit": 15 }, + { + "Endpoint": "*:/api/v4/Geocoding/ForwardGeocode", + "Period": "60s", + "Limit": 30 + }, + { + "Endpoint": "*:/api/v4/Geocoding/ReverseGeocode", + "Period": "60s", + "Limit": 30 + }, { "Endpoint": "*:/api/v3/Auth/Validate", "Period": "60s", diff --git a/Web/Resgrid.Web/Areas/User/Controllers/CustomMapsController.cs b/Web/Resgrid.Web/Areas/User/Controllers/CustomMapsController.cs index 8c74b38aa..1b93ba535 100644 --- a/Web/Resgrid.Web/Areas/User/Controllers/CustomMapsController.cs +++ b/Web/Resgrid.Web/Areas/User/Controllers/CustomMapsController.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; @@ -9,7 +10,6 @@ using Resgrid.Model.Services; using Resgrid.Web.Areas.User.Models.CustomMaps; using Resgrid.Web.Helpers; -using System.Collections.Generic; namespace Resgrid.Web.Areas.User.Controllers { @@ -209,6 +209,7 @@ public async Task RegionEditor(string id) } [HttpPost] + [ValidateAntiForgeryToken] public async Task SaveRegion([FromBody] IndoorMapZone region, CancellationToken cancellationToken) { var layer = await _customMapService.GetLayerByIdAsync(region.IndoorMapFloorId); @@ -231,6 +232,7 @@ public async Task SaveRegion([FromBody] IndoorMapZone region, Can } [HttpPost] + [ValidateAntiForgeryToken] public async Task DeleteRegion([FromBody] DeleteRegionRequest request, CancellationToken cancellationToken) { var region = await _customMapService.GetRegionByIdAsync(request.RegionId); @@ -336,6 +338,14 @@ public async Task GetLayerImage(string id) [HttpGet] public async Task GetLayerTile(string id, int z, int x, int y) { + var layer = await _customMapService.GetLayerByIdAsync(id); + if (layer == null) + return NotFound(); + + var map = await _customMapService.GetCustomMapByIdAsync(layer.IndoorMapId); + if (map == null || map.DepartmentId != DepartmentId) + return NotFound(); + var tile = await _customMapService.GetTileAsync(id, z, x, y); if (tile == null) return NotFound(); diff --git a/Web/Resgrid.Web/Areas/User/Controllers/DispatchController.cs b/Web/Resgrid.Web/Areas/User/Controllers/DispatchController.cs index 381012d43..516de53b1 100644 --- a/Web/Resgrid.Web/Areas/User/Controllers/DispatchController.cs +++ b/Web/Resgrid.Web/Areas/User/Controllers/DispatchController.cs @@ -625,6 +625,11 @@ public async Task UpdateCall(UpdateCallView model, IFormCollectio call.IndoorMapZoneId = indoorMapZoneId; call.IndoorMapFloorId = indoorMapFloorId; } + else + { + call.IndoorMapZoneId = null; + call.IndoorMapFloorId = null; + } List existingDispatches = new List(call.Dispatches); diff --git a/Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs b/Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs index 3dfc50bb0..cbd45b942 100644 --- a/Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs +++ b/Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs @@ -1,12 +1,39 @@ +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Resgrid.Model.Services; namespace Resgrid.Web.Areas.User.Controllers { [Area("User")] public class IndoorMapsController : SecureBaseController { + private readonly IIndoorMapService _indoorMapService; + + public IndoorMapsController(IIndoorMapService indoorMapService) + { + _indoorMapService = indoorMapService; + } + + [HttpGet] + public async Task SearchZones(string term) + { + if (string.IsNullOrWhiteSpace(term)) + return Json(new { results = System.Array.Empty() }); + + var zones = await _indoorMapService.SearchZonesAsync(DepartmentId, term); + + var results = zones.Select(z => new + { + id = z.IndoorMapZoneId, + text = z.Name, + floorId = z.IndoorMapFloorId + }); + + return Json(new { results }); + } + [HttpGet] public IActionResult Index() { diff --git a/Web/Resgrid.Web/Areas/User/Controllers/RoutesController.cs b/Web/Resgrid.Web/Areas/User/Controllers/RoutesController.cs index 7ce2f0960..a7fc1c11a 100644 --- a/Web/Resgrid.Web/Areas/User/Controllers/RoutesController.cs +++ b/Web/Resgrid.Web/Areas/User/Controllers/RoutesController.cs @@ -2,9 +2,11 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Resgrid.Model; using Resgrid.Model.Services; +using Resgrid.Providers.Claims; using Resgrid.Web.Areas.User.Models.Routes; namespace Resgrid.Web.Areas.User.Controllers @@ -14,14 +16,17 @@ public class RoutesController : SecureBaseController { private readonly IRouteService _routeService; private readonly IUnitsService _unitsService; + private readonly ICallsService _callsService; - public RoutesController(IRouteService routeService, IUnitsService unitsService) + public RoutesController(IRouteService routeService, IUnitsService unitsService, ICallsService callsService) { _routeService = routeService; _unitsService = unitsService; + _callsService = callsService; } [HttpGet] + [Authorize(Policy = ResgridResources.Route_View)] public async Task Index() { var model = new RouteIndexView(); @@ -30,6 +35,7 @@ public async Task Index() } [HttpGet] + [Authorize(Policy = ResgridResources.Route_Create)] public async Task New() { var model = new RouteNewView(); @@ -39,6 +45,7 @@ public async Task New() [HttpPost] [ValidateAntiForgeryToken] + [Authorize(Policy = ResgridResources.Route_Create)] public async Task New(RouteNewView model, CancellationToken cancellationToken) { if (ModelState.IsValid) @@ -58,6 +65,7 @@ public async Task New(RouteNewView model, CancellationToken cance } [HttpGet] + [Authorize(Policy = ResgridResources.Route_Update)] public async Task Edit(string id) { var plan = await _routeService.GetRoutePlanByIdAsync(id); @@ -74,6 +82,7 @@ public async Task Edit(string id) [HttpPost] [ValidateAntiForgeryToken] + [Authorize(Policy = ResgridResources.Route_Update)] public async Task Edit(RouteEditView model, CancellationToken cancellationToken) { if (ModelState.IsValid) @@ -99,6 +108,7 @@ public async Task Edit(RouteEditView model, CancellationToken can [HttpPost] [ValidateAntiForgeryToken] + [Authorize(Policy = ResgridResources.Route_Delete)] public async Task Delete(string id, CancellationToken cancellationToken) { var plan = await _routeService.GetRoutePlanByIdAsync(id); @@ -111,6 +121,7 @@ public async Task Delete(string id, CancellationToken cancellatio } [HttpGet] + [Authorize(Policy = ResgridResources.Route_View)] public async Task View(string id) { var plan = await _routeService.GetRoutePlanByIdAsync(id); @@ -124,6 +135,7 @@ public async Task View(string id) } [HttpGet] + [Authorize(Policy = ResgridResources.Route_View)] public async Task Instances(string routePlanId) { var plan = await _routeService.GetRoutePlanByIdAsync(routePlanId); @@ -140,6 +152,7 @@ public async Task Instances(string routePlanId) } [HttpGet] + [Authorize(Policy = ResgridResources.Route_View)] public async Task ActiveRoutes() { var instances = await _routeService.GetInstancesForDepartmentAsync(DepartmentId); @@ -153,6 +166,82 @@ public async Task ActiveRoutes() } [HttpGet] + [Authorize(Policy = ResgridResources.Route_View)] + public async Task GetCallsForLinking() + { + var calls = await _callsService.GetAllNonDispatchedScheduledCallsByDepartmentIdAsync(DepartmentId); + var active = await _callsService.GetActiveCallsByDepartmentAsync(DepartmentId); + var all = calls.Union(active).Distinct().OrderBy(c => c.Name).Select(c => new { id = c.CallId, name = c.Name, address = c.Address }).ToList(); + return Json(all); + } + + [HttpPost] + [ValidateAntiForgeryToken] + [Authorize(Policy = ResgridResources.Route_Update)] + public async Task AddStop(string routePlanId, string name, string description, int stopType, int priority, + decimal latitude, decimal longitude, string address, int? callId, int? geofenceRadius, + string plannedArrival, string plannedDeparture, int? dwellMinutes, string contactName, string contactNumber, string notes, + CancellationToken cancellationToken) + { + var plan = await _routeService.GetRoutePlanByIdAsync(routePlanId); + if (plan == null || plan.DepartmentId != DepartmentId) + return Json(new { success = false, message = "Not found" }); + + var existingStops = await _routeService.GetRouteStopsForPlanAsync(routePlanId); + var stop = new RouteStop + { + RoutePlanId = routePlanId, + Name = name, + Description = description, + StopType = stopType, + Priority = priority, + Latitude = latitude, + Longitude = longitude, + Address = address, + CallId = callId, + GeofenceRadiusMeters = geofenceRadius, + EstimatedDwellMinutes = dwellMinutes, + ContactName = contactName, + ContactNumber = contactNumber, + Notes = notes, + StopOrder = existingStops.Count + 1, + AddedOn = DateTime.UtcNow, + IsDeleted = false + }; + + if (!string.IsNullOrWhiteSpace(plannedArrival) && DateTime.TryParse(plannedArrival, out var arrivalDt)) + stop.PlannedArrivalTime = arrivalDt.ToUniversalTime(); + if (!string.IsNullOrWhiteSpace(plannedDeparture) && DateTime.TryParse(plannedDeparture, out var departureDt)) + stop.PlannedDepartureTime = departureDt.ToUniversalTime(); + + await _routeService.SaveRouteStopAsync(stop, cancellationToken); + return Json(new { success = true }); + } + + [HttpPost] + [ValidateAntiForgeryToken] + [Authorize(Policy = ResgridResources.Route_Delete)] + public async Task DeleteStop(string stopId, CancellationToken cancellationToken) + { + // Verify ownership by loading the stop via the plan + // We load all stops for the department's plans to validate ownership + var deleted = false; + var plans = await _routeService.GetRoutePlansForDepartmentAsync(DepartmentId); + foreach (var p in plans) + { + var stops = await _routeService.GetRouteStopsForPlanAsync(p.RoutePlanId); + if (stops.Any(s => s.RouteStopId == stopId)) + { + deleted = await _routeService.DeleteRouteStopAsync(stopId, cancellationToken); + break; + } + } + + return Json(new { success = deleted }); + } + + [HttpGet] + [Authorize(Policy = ResgridResources.Route_View)] public async Task InstanceDetail(string instanceId) { var instance = await _routeService.GetInstanceByIdAsync(instanceId); diff --git a/Web/Resgrid.Web/Areas/User/Views/Calendar/Edit.cshtml b/Web/Resgrid.Web/Areas/User/Views/Calendar/Edit.cshtml index ac52874a0..f1b80059f 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Calendar/Edit.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Calendar/Edit.cshtml @@ -365,7 +365,7 @@
- +
diff --git a/Web/Resgrid.Web/Areas/User/Views/Calendar/New.cshtml b/Web/Resgrid.Web/Areas/User/Views/Calendar/New.cshtml index d59b70691..ec6f04622 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Calendar/New.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Calendar/New.cshtml @@ -322,7 +322,7 @@
- +
diff --git a/Web/Resgrid.Web/Areas/User/Views/CustomMaps/Edit.cshtml b/Web/Resgrid.Web/Areas/User/Views/CustomMaps/Edit.cshtml index 3d65cd661..44fac0277 100644 --- a/Web/Resgrid.Web/Areas/User/Views/CustomMaps/Edit.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/CustomMaps/Edit.cshtml @@ -1,21 +1,21 @@ @model Resgrid.Web.Areas.User.Models.CustomMaps.CustomMapNewView @using Resgrid.Model +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | Edit Custom Map"; + ViewBag.Title = "Resgrid | " + localizer["EditCustomMapPageTitle"]; } @section Styles { - - + }
-

Edit Custom Map

+

@localizer["EditCustomMapHeader"]

@@ -33,34 +33,34 @@ @Html.AntiForgeryToken()
- +
- +
- +
- +
- +
- +
-

Draw a rectangle on the map below to update the bounds. Click the map to set the center point.

+

@localizer["DrawBoundsEditHelp"]

@@ -77,8 +77,8 @@
- Cancel - + @localizer["Cancel"] +
@@ -89,11 +89,12 @@ @section Scripts { - - + } diff --git a/Web/Resgrid.Web/Areas/User/Views/CustomMaps/Import.cshtml b/Web/Resgrid.Web/Areas/User/Views/CustomMaps/Import.cshtml index e07bc1bac..cecdcb094 100644 --- a/Web/Resgrid.Web/Areas/User/Views/CustomMaps/Import.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/CustomMaps/Import.cshtml @@ -1,16 +1,17 @@ @model Resgrid.Web.Areas.User.Models.CustomMaps.CustomMapImportView @using Resgrid.Model +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | Import - " + Model.Map.Name; + ViewBag.Title = "Resgrid | " + localizer["ImportPageTitlePrefix"] + " - " + Model.Map.Name; }
-

Import - @Model.Map.Name

+

@localizer["ImportHeaderPrefix"] - @Model.Map.Name

@@ -20,7 +21,7 @@
-
Import Geospatial Data
+
@localizer["ImportGeospatialTitle"]
@if (!string.IsNullOrWhiteSpace(Model.Message)) @@ -31,7 +32,7 @@ @Html.AntiForgeryToken()
- +
- Supported formats: GeoJSON (.geojson, .json), KML (.kml), KMZ (.kmz) + @localizer["ImportFileHelp"]
- +
@@ -60,18 +61,18 @@
-
Import History
+
@localizer["ImportHistoryTitle"]
- - - - - + + + + + diff --git a/Web/Resgrid.Web/Areas/User/Views/CustomMaps/Index.cshtml b/Web/Resgrid.Web/Areas/User/Views/CustomMaps/Index.cshtml index a47ecc411..7fc71710b 100644 --- a/Web/Resgrid.Web/Areas/User/Views/CustomMaps/Index.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/CustomMaps/Index.cshtml @@ -1,24 +1,25 @@ @model Resgrid.Web.Areas.User.Models.CustomMaps.CustomMapIndexView @using Resgrid.Model +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | Custom Maps"; + ViewBag.Title = "Resgrid | " + localizer["CustomMapsPageTitle"]; }
-

Custom Maps

+

@localizer["CustomMapsHeader"]

@@ -30,29 +31,29 @@
FileTypeStatusDateError@localizer["File"]@localizer["Type"]@localizer["Status"]@localizer["Date"]@localizer["Error"]
- - - - + + + + @@ -67,10 +68,10 @@ } diff --git a/Web/Resgrid.Web/Areas/User/Views/CustomMaps/Layers.cshtml b/Web/Resgrid.Web/Areas/User/Views/CustomMaps/Layers.cshtml index 681e0ba6a..32222e388 100644 --- a/Web/Resgrid.Web/Areas/User/Views/CustomMaps/Layers.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/CustomMaps/Layers.cshtml @@ -1,16 +1,17 @@ @model Resgrid.Web.Areas.User.Models.CustomMaps.CustomMapLayersView @using Resgrid.Model +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | Layers - " + Model.Map.Name; + ViewBag.Title = "Resgrid | " + localizer["LayersPageTitlePrefix"] + " - " + Model.Map.Name; }
-

Layers - @Model.Map.Name

+

@localizer["LayersHeaderPrefix"] - @Model.Map.Name

@@ -20,39 +21,39 @@
-
Add Layer
+
@localizer["AddLayerTitle"]
@Html.AntiForgeryToken()
- +
- +
- +
- +
- +
- Images larger than 2048px will be automatically tiled. + @localizer["LayerImageHelp"]
- +
@@ -61,18 +62,18 @@
-
Layers
+
@localizer["LayersBreadcrumb"]
NameTypeDescriptionCreated@localizer["Name"]@localizer["Type"]@localizer["Description"]@localizer["Created"]
@map.Description @map.AddedOn.ToString("g") - Layers - Import - Edit - Delete + @localizer["Layers"] + @localizer["Import"] + @localizer["Edit"] + @localizer["Delete"]
- - - - - + + + + + @@ -85,7 +86,7 @@ - + } diff --git a/Web/Resgrid.Web/Areas/User/Views/CustomMaps/New.cshtml b/Web/Resgrid.Web/Areas/User/Views/CustomMaps/New.cshtml index 788e9edc6..fa8de45c3 100644 --- a/Web/Resgrid.Web/Areas/User/Views/CustomMaps/New.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/CustomMaps/New.cshtml @@ -1,21 +1,21 @@ @model Resgrid.Web.Areas.User.Models.CustomMaps.CustomMapNewView @using Resgrid.Model +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | New Custom Map"; + ViewBag.Title = "Resgrid | " + localizer["NewCustomMapPageTitle"]; } @section Styles { - - + }
-

New Custom Map

+

@localizer["NewCustomMapHeader"]

@@ -32,34 +32,34 @@
@Html.AntiForgeryToken()
- +
- +
- +
- +
- +
- +
-

Draw a rectangle on the map below to define the bounds. Click the map to set the center point.

+

@localizer["DrawBoundsHelp"]

@@ -76,8 +76,8 @@
- Cancel - + @localizer["Cancel"] +
@@ -88,11 +88,12 @@ @section Scripts { - - + } diff --git a/Web/Resgrid.Web/Areas/User/Views/CustomMaps/RegionEditor.cshtml b/Web/Resgrid.Web/Areas/User/Views/CustomMaps/RegionEditor.cshtml index 819577037..3523230c4 100644 --- a/Web/Resgrid.Web/Areas/User/Views/CustomMaps/RegionEditor.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/CustomMaps/RegionEditor.cshtml @@ -1,12 +1,12 @@ @model Resgrid.Web.Areas.User.Models.CustomMaps.CustomMapRegionEditorView @using Resgrid.Model +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | Region Editor - " + Model.Layer.Name; + ViewBag.Title = "Resgrid | " + localizer["RegionEditorPageTitlePrefix"] + " - " + Model.Layer.Name; } @section Styles { - - + } - -

@localizer["AddArchivedCallHeader"]

@@ -400,6 +398,8 @@ var newCallFormData = '@Html.Raw(Model.NewCallFormData)'; var osmTileUrl = '@Resgrid.Config.MappingConfig.GetWebsiteOSMUrl()'; var osmTileAttribution = '@Resgrid.Config.MappingConfig.LeafletAttribution'; + var nominatimUrl = '@Resgrid.Config.MappingConfig.NominatimUrl'; + var osrmUrl = '@Resgrid.Config.MappingConfig.OsrmUrl'; @if (Model.CenterCoordinates != null && Model.CenterCoordinates.Latitude.HasValue && Model.CenterCoordinates.Longitude.HasValue) diff --git a/Web/Resgrid.Web/Areas/User/Views/Dispatch/CallExport.cshtml b/Web/Resgrid.Web/Areas/User/Views/Dispatch/CallExport.cshtml index 2e7b8a4a3..f7456a354 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Dispatch/CallExport.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Dispatch/CallExport.cshtml @@ -17,7 +17,7 @@ -
- + - + diff --git a/Web/Resgrid.Web/Areas/User/Views/Dispatch/CallExportEx.cshtml b/Web/Resgrid.Web/Areas/User/Views/Dispatch/CallExportEx.cshtml index 3265421a1..4e5d92b7e 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Dispatch/CallExportEx.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Dispatch/CallExportEx.cshtml @@ -17,7 +17,7 @@ -
- + - + @@ -555,67 +555,63 @@ - + + @if (Model.Station != null) { } diff --git a/Web/Resgrid.Web/Areas/User/Views/Dispatch/Dashboard.cshtml b/Web/Resgrid.Web/Areas/User/Views/Dispatch/Dashboard.cshtml index 641cf8b25..c70d7217c 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Dispatch/Dashboard.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Dispatch/Dashboard.cshtml @@ -25,8 +25,6 @@ } - -

@commonLocalizer["CallsModule"]

@@ -42,15 +40,15 @@
- @localizer["ArchivedCalls"] - @if (ClaimsAuthorizationHelper.CanCreateCall()) - { - @localizer["NewCall"] - } @if (ClaimsAuthorizationHelper.CanViewRoutes()) { Routes } + @localizer["ArchivedCalls"] + @if (ClaimsAuthorizationHelper.CanCreateCall()) + { + @localizer["NewCall"] + }
diff --git a/Web/Resgrid.Web/Areas/User/Views/Dispatch/NewCall.cshtml b/Web/Resgrid.Web/Areas/User/Views/Dispatch/NewCall.cshtml index d99e37abe..e90346549 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Dispatch/NewCall.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Dispatch/NewCall.cshtml @@ -528,13 +528,14 @@ @section Scripts { - @if (Model.CenterCoordinates != null && Model.CenterCoordinates.Latitude.HasValue && Model.CenterCoordinates.Longitude.HasValue) diff --git a/Web/Resgrid.Web/Areas/User/Views/Dispatch/UpdateCall.cshtml b/Web/Resgrid.Web/Areas/User/Views/Dispatch/UpdateCall.cshtml index 7058a2bf3..5a8c98809 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Dispatch/UpdateCall.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Dispatch/UpdateCall.cshtml @@ -402,7 +402,6 @@ @section Scripts { - diff --git a/Web/Resgrid.Web/Areas/User/Views/Dispatch/ViewCall.cshtml b/Web/Resgrid.Web/Areas/User/Views/Dispatch/ViewCall.cshtml index 4ffd43f56..d1fd9dbb3 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Dispatch/ViewCall.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Dispatch/ViewCall.cshtml @@ -11,8 +11,8 @@ @section Styles { - - + + } @Html.HiddenFor(m => m.Latitude) @@ -801,8 +801,8 @@ - - + + - @if (Model.Group == null || String.IsNullOrWhiteSpace(Model.Group.Geofence)) - { - - } - else - { - - } + + @if (Model.Group == null || String.IsNullOrWhiteSpace(Model.Group.Geofence)) + { + + } + else + { + + } - + - + } diff --git a/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Edit.cshtml b/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Edit.cshtml index f812b6e39..c7d96aa1f 100644 --- a/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Edit.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Edit.cshtml @@ -1,15 +1,16 @@ @model Resgrid.Web.Areas.User.Models.IndoorMaps.IndoorMapNewView +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | Edit Indoor Map"; + ViewBag.Title = "Resgrid | " + localizer["EditIndoorMapPageTitle"]; }
-

Edit Indoor Map

+

@localizer["EditIndoorMapHeader"]

@@ -27,43 +28,43 @@ @Html.AntiForgeryToken()
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
@@ -71,8 +72,8 @@
- Cancel - + @localizer["Cancel"] +
@@ -84,5 +85,9 @@
@section Scripts { + } diff --git a/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Floors.cshtml b/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Floors.cshtml index a82165f82..54ef0433d 100644 --- a/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Floors.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Floors.cshtml @@ -1,15 +1,16 @@ @model Resgrid.Web.Areas.User.Models.IndoorMaps.IndoorMapFloorsView +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | Floors - " + Model.IndoorMap.Name; + ViewBag.Title = "Resgrid | " + localizer["FloorsPageTitlePrefix"] + " - " + Model.IndoorMap.Name; }
-

Floors - @Model.IndoorMap.Name

+

@localizer["FloorsPageTitlePrefix"] - @Model.IndoorMap.Name

@@ -19,29 +20,29 @@
-
Add Floor
+
@localizer["AddFloorTitle"]
@Html.AntiForgeryToken()
- +
- +
- +
- +
- +
@@ -50,16 +51,16 @@
-
Floors
+
@localizer["FloorsTableTitle"]
OrderNameTypeHas ImageTiled@localizer["Order"]@localizer["Name"]@localizer["Type"]@localizer["HasImage"]@localizer["Tiled"]
@layer.FloorOrder @layer.Name @((CustomMapLayerType)layer.LayerType)@(layer.ImageData != null || layer.IsTiled ? "Yes" : "No")@(layer.ImageData != null || layer.IsTiled ? localizer["Yes"].Value : localizer["No"].Value) @if (layer.IsTiled) { @@ -93,12 +94,12 @@ } else { - No + @localizer["No"] } - Region Editor - Delete + @localizer["RegionEditor"] + @localizer["Delete"]
- - - + + + @@ -71,10 +72,10 @@ - + } diff --git a/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Index.cshtml b/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Index.cshtml index 30a2457ac..83b47b0d5 100644 --- a/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Index.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/Index.cshtml @@ -1,23 +1,24 @@ @model Resgrid.Web.Areas.User.Models.IndoorMaps.IndoorMapIndexView +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | Indoor Maps"; + ViewBag.Title = "Resgrid | " + localizer["IndoorMapsPageTitle"]; }
-

Indoor Maps

+

@localizer["IndoorMapsHeader"]

@@ -31,9 +32,9 @@
OrderNameHas Image@localizer["Order"]@localizer["Name"]@localizer["HasImage"]
@floor.FloorOrder @floor.Name@(floor.ImageData != null ? "Yes" : "No")@(floor.ImageData != null ? localizer["Yes"].Value : localizer["No"].Value) - Zone Editor - Delete + @localizer["ZoneEditor"] + @localizer["Delete"]
- - - + + + @@ -47,9 +48,9 @@ } @@ -64,11 +65,5 @@ @section Scripts { - + } diff --git a/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/New.cshtml b/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/New.cshtml index 31336db96..157757fdd 100644 --- a/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/New.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/New.cshtml @@ -1,15 +1,16 @@ @model Resgrid.Web.Areas.User.Models.IndoorMaps.IndoorMapNewView +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | New Indoor Map"; + ViewBag.Title = "Resgrid | " + localizer["NewIndoorMapPageTitle"]; }
-

New Indoor Map

+

@localizer["NewIndoorMapHeader"]

@@ -26,43 +27,43 @@
@Html.AntiForgeryToken()
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
@@ -70,8 +71,8 @@
- Cancel - + @localizer["Cancel"] +
@@ -83,5 +84,9 @@
@section Scripts { + } diff --git a/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/ZoneEditor.cshtml b/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/ZoneEditor.cshtml index 361594c6d..0ea94ace6 100644 --- a/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/ZoneEditor.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/IndoorMaps/ZoneEditor.cshtml @@ -1,11 +1,11 @@ @model Resgrid.Web.Areas.User.Models.IndoorMaps.IndoorMapZoneEditorView +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | Zone Editor - " + Model.Floor.Name; + ViewBag.Title = "Resgrid | " + localizer["ZoneEditorPageTitlePrefix"] + " - " + Model.Floor.Name; } @section Styles { - - + } - -
-

Mapping

+

@localizer["MappingHeader"]

@@ -41,10 +40,10 @@ { } @@ -68,17 +67,17 @@
diff --git a/Web/Resgrid.Web/Areas/User/Views/Mapping/Layers.cshtml b/Web/Resgrid.Web/Areas/User/Views/Mapping/Layers.cshtml index 04dcb2ed9..67a9d3679 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Mapping/Layers.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Mapping/Layers.cshtml @@ -1,21 +1,22 @@ -@using Resgrid.Web.Helpers +@using Resgrid.Web.Helpers @model Resgrid.Web.Areas.User.Models.Mapping.LayersView +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | Layers"; + ViewBag.Title = "Resgrid | " + localizer["LayersPageTitle"]; }
-

Layers

+

@localizer["LayersHeader"]

@@ -23,7 +24,7 @@ { } @@ -39,13 +40,13 @@
@@ -65,8 +66,8 @@ @layer.Data.Features.Count } diff --git a/Web/Resgrid.Web/Areas/User/Views/Mapping/LiveRouting.cshtml b/Web/Resgrid.Web/Areas/User/Views/Mapping/LiveRouting.cshtml index 80d182abf..6d3b618de 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Mapping/LiveRouting.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Mapping/LiveRouting.cshtml @@ -1,6 +1,7 @@ -@model Resgrid.WebCore.Areas.User.Models.Mapping.StationRoutingView +@model Resgrid.WebCore.Areas.User.Models.Mapping.StationRoutingView +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | Live Navigation"; + ViewBag.Title = "Resgrid | " + localizer["LiveRoutingPageTitle"]; } @section Styles @@ -14,16 +15,16 @@
-

Live Routing

+

@localizer["LiveRoutingHeader"]

@@ -57,71 +58,67 @@ @section Scripts { - diff --git a/Web/Resgrid.Web/Areas/User/Views/Mapping/NewLayer.cshtml b/Web/Resgrid.Web/Areas/User/Views/Mapping/NewLayer.cshtml index f0f5b4b92..52c0bee93 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Mapping/NewLayer.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Mapping/NewLayer.cshtml @@ -1,7 +1,8 @@ - + @model Resgrid.Web.Areas.User.Models.Mapping.NewLayerView +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | New Layer"; + ViewBag.Title = "Resgrid | " + localizer["NewLayerPageTitle"]; } @section Styles @@ -39,16 +40,16 @@
-

New Layer

+

@localizer["NewLayerHeader"]

@@ -78,7 +79,7 @@
@@ -88,29 +89,29 @@
@Html.CheckBoxFor(x => x.IsOnByDefault) - Do you want this layer to be visible every time a map loads? Note: Having too many layers visible by default will complicate the map. Users can turn layers on if they want to see them in the map control. + @localizer["IsVisibleByDefaultHelp"]
@Html.CheckBoxFor(x => x.IsSearchable) - This will allow address searching to use coordinates from this layer in the search. Note, if you don't have searchable points (text fields) don't mark as searchable as it can slow down dispatching.' + @localizer["IsSearchableHelp"]
@@ -120,7 +121,7 @@
@@ -128,8 +129,8 @@
@@ -207,7 +208,7 @@ break; default: } - + return geo; }); @@ -216,30 +217,8 @@ features, }; - //for (let i = 0; i < layers.length; i++) { - - // const geo = layers[i].toGeoJSON(); - // geo.properties.shape = layers[i].pm.getShape(); - // geo.properties.color = $('#Color').val(); - // geo.properties.name = $('#Name').val(); - // geo.properties.category = "default"; - - // switch (geo.properties.shape) { - // case "Text": - // geo.properties.text = layer.pm.getText().trim(); - // break; - // default: - // } - - // //layers[i].setStyle({ color: $('#Color').val() }); - // fg.addLayer(layers[i]); - //} - - //const geojson = fg.toGeoJSON(); - //const json = JSON.stringify(geojson); const json = JSON.stringify(featureCollection); - - //const json = JSON.stringify(map.pm.getGeomanLayers(true).toGeoJSON()); + $('#GeoJson').val(json); $("#newLayerForm").submit(); diff --git a/Web/Resgrid.Web/Areas/User/Views/Mapping/POIs.cshtml b/Web/Resgrid.Web/Areas/User/Views/Mapping/POIs.cshtml index fb659e7eb..6fbd943c5 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Mapping/POIs.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Mapping/POIs.cshtml @@ -1,22 +1,23 @@ -@using Resgrid.Web.Helpers +@using Resgrid.Web.Helpers @model Resgrid.Web.Areas.User.Models.Mapping.POIsView +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | POIs"; + ViewBag.Title = "Resgrid | " + localizer["POIsPageTitle"]; }
-

POIs

+

@localizer["POIsHeader"]

@@ -24,7 +25,7 @@ { } @@ -40,13 +41,13 @@
@@ -66,13 +67,13 @@ @u.Pois.Count @@ -100,4 +101,4 @@ }); //]]> -} \ No newline at end of file +} diff --git a/Web/Resgrid.Web/Areas/User/Views/Mapping/StationRouting.cshtml b/Web/Resgrid.Web/Areas/User/Views/Mapping/StationRouting.cshtml index 410e78ebe..d3a158e15 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Mapping/StationRouting.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Mapping/StationRouting.cshtml @@ -1,6 +1,7 @@ -@model Resgrid.WebCore.Areas.User.Models.Mapping.StationRoutingView +@model Resgrid.WebCore.Areas.User.Models.Mapping.StationRoutingView +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | Station Navigation"; + ViewBag.Title = "Resgrid | " + localizer["StationRoutingPageTitle"]; } @section Styles @@ -12,20 +13,19 @@ } -
-

Station Routing

+

@localizer["StationRoutingHeader"]

@@ -52,62 +52,57 @@ @section Scripts { } diff --git a/Web/Resgrid.Web/Areas/User/Views/Mapping/ViewType.cshtml b/Web/Resgrid.Web/Areas/User/Views/Mapping/ViewType.cshtml index df48d17ca..a0e861ba0 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Mapping/ViewType.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Mapping/ViewType.cshtml @@ -1,8 +1,9 @@ -@using Resgrid.Model +@using Resgrid.Model @using Resgrid.Web.Helpers @model Resgrid.Web.Areas.User.Models.Mapping.ViewTypeView +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | View Type"; + ViewBag.Title = "Resgrid | " + localizer["ViewTypePageTitle"]; Layout = "~/Areas/User/Views/Shared/_UserLayout.cshtml"; } @@ -27,19 +28,19 @@
-

View Type

+

@localizer["ViewTypeHeader"]

@@ -49,9 +50,9 @@
@@ -65,7 +66,7 @@
@@ -75,24 +76,24 @@
@if (Model.Type.IsDestination) { - True + @localizer["True"] } else { - False + @localizer["False"] }
@@ -116,11 +117,12 @@ @section Scripts { - - - + + } diff --git a/Web/Resgrid.Web/Areas/User/Views/Reports/ActionLogs.cshtml b/Web/Resgrid.Web/Areas/User/Views/Reports/ActionLogs.cshtml index 0dfe9c377..90f800fb8 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Reports/ActionLogs.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Reports/ActionLogs.cshtml @@ -15,7 +15,7 @@ - - + - + diff --git a/Web/Resgrid.Web/Areas/User/Views/Reports/ActiveCallsResourcesReport.cshtml b/Web/Resgrid.Web/Areas/User/Views/Reports/ActiveCallsResourcesReport.cshtml index 01b3f2ff5..d908fa10d 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Reports/ActiveCallsResourcesReport.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Reports/ActiveCallsResourcesReport.cshtml @@ -15,7 +15,7 @@ -
- - + + diff --git a/Web/Resgrid.Web/Areas/User/Views/Reports/CallSummaryReport.cshtml b/Web/Resgrid.Web/Areas/User/Views/Reports/CallSummaryReport.cshtml index 6b21e4381..58e6c3247 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Reports/CallSummaryReport.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Reports/CallSummaryReport.cshtml @@ -15,7 +15,7 @@ -
- - + + + - + diff --git a/Web/Resgrid.Web/Areas/User/Views/Reports/DepartmentActivityReport.cshtml b/Web/Resgrid.Web/Areas/User/Views/Reports/DepartmentActivityReport.cshtml index 3433263d3..ceed41bb4 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Reports/DepartmentActivityReport.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Reports/DepartmentActivityReport.cshtml @@ -15,7 +15,7 @@ -
- - + + - + + diff --git a/Web/Resgrid.Web/Areas/User/Views/Reports/LogReport.cshtml b/Web/Resgrid.Web/Areas/User/Views/Reports/LogReport.cshtml index ecaff9bcd..bbd1fd367 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Reports/LogReport.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Reports/LogReport.cshtml @@ -16,7 +16,7 @@ - - + - + diff --git a/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelEventsReport.cshtml b/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelEventsReport.cshtml index 274801745..d764b2d59 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelEventsReport.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelEventsReport.cshtml @@ -15,7 +15,7 @@ -
- - + + diff --git a/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelHoursDetailReport.cshtml b/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelHoursDetailReport.cshtml index ffc8b25c2..05fd8dea8 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelHoursDetailReport.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelHoursDetailReport.cshtml @@ -15,7 +15,7 @@ -
- + - + diff --git a/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelHoursReport.cshtml b/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelHoursReport.cshtml index c71305132..4c4525974 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelHoursReport.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelHoursReport.cshtml @@ -15,7 +15,7 @@ -
- + - + diff --git a/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelReport.cshtml b/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelReport.cshtml index 1817daa17..8a1b0c3f8 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelReport.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelReport.cshtml @@ -14,7 +14,7 @@ -
- + - + diff --git a/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelStaffingHistoryReport.cshtml b/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelStaffingHistoryReport.cshtml index c9deb056a..f569e73c4 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelStaffingHistoryReport.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Reports/PersonnelStaffingHistoryReport.cshtml @@ -15,7 +15,7 @@ - - + - + diff --git a/Web/Resgrid.Web/Areas/User/Views/Reports/StaffingReport.cshtml b/Web/Resgrid.Web/Areas/User/Views/Reports/StaffingReport.cshtml index 90af2095d..3cc399a11 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Reports/StaffingReport.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Reports/StaffingReport.cshtml @@ -15,7 +15,7 @@ -
- + - + diff --git a/Web/Resgrid.Web/Areas/User/Views/Reports/UnitEventsReport.cshtml b/Web/Resgrid.Web/Areas/User/Views/Reports/UnitEventsReport.cshtml index c517ce498..7c66f7166 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Reports/UnitEventsReport.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Reports/UnitEventsReport.cshtml @@ -15,7 +15,7 @@ -
- + - + diff --git a/Web/Resgrid.Web/Areas/User/Views/Reports/UnitStateHistoryReport.cshtml b/Web/Resgrid.Web/Areas/User/Views/Reports/UnitStateHistoryReport.cshtml index 8df8d95c7..3cc341643 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Reports/UnitStateHistoryReport.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Reports/UnitStateHistoryReport.cshtml @@ -15,7 +15,7 @@ - - + - + diff --git a/Web/Resgrid.Web/Areas/User/Views/Reports/UpcomingShiftReadinessReport.cshtml b/Web/Resgrid.Web/Areas/User/Views/Reports/UpcomingShiftReadinessReport.cshtml index bd4c93a9c..5b1f88ac2 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Reports/UpcomingShiftReadinessReport.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Reports/UpcomingShiftReadinessReport.cshtml @@ -12,7 +12,7 @@ -
- + - + diff --git a/Web/Resgrid.Web/Areas/User/Views/Routes/ActiveRoutes.cshtml b/Web/Resgrid.Web/Areas/User/Views/Routes/ActiveRoutes.cshtml index 9866f83bf..c33b1a4b6 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Routes/ActiveRoutes.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Routes/ActiveRoutes.cshtml @@ -1,15 +1,16 @@ @model Resgrid.Web.Areas.User.Models.Routes.ActiveRoutesView +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | Active Routes"; + ViewBag.Title = "Resgrid | " + localizer["ActiveRoutesPageTitle"]; }
-

Active Routes

+

@localizer["ActiveRoutesHeader"]

@@ -21,8 +22,8 @@
-

No Active Routes

-

There are no route instances currently in progress.

+

@localizer["NoActiveRoutes"]

+

@localizer["NoActiveRoutesMessage"]

@@ -40,11 +41,11 @@
- Unit + @localizer["UnitLabel"]

@instance.UnitId

- Stops + @localizer["StopsLabel"]

@instance.StopsCompleted / @instance.StopsTotal

@@ -52,10 +53,10 @@
- Started: @(instance.ActualStartOn?.ToString("HH:mm") ?? "-") + @localizer["StartedLabel"]: @(instance.ActualStartOn?.ToString("HH:mm") ?? "-")
- View Details + @localizer["ViewDetails"]
diff --git a/Web/Resgrid.Web/Areas/User/Views/Routes/Edit.cshtml b/Web/Resgrid.Web/Areas/User/Views/Routes/Edit.cshtml index 8266d80da..c0e64316f 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Routes/Edit.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Routes/Edit.cshtml @@ -1,15 +1,16 @@ @model Resgrid.Web.Areas.User.Models.Routes.RouteEditView +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | Edit Route"; + ViewBag.Title = "Resgrid | " + localizer["EditRoutePageTitle"]; }
-

Edit Route Plan

+

@localizer["EditRoutePlanHeader"]

@@ -18,31 +19,31 @@
-
Route Details
+
@localizer["RouteDetailsTitle"]
@Html.AntiForgeryToken()
- +
- +
- +
- - - - + + + +
- +
- +
- +
@@ -93,40 +94,51 @@
-

Stops (@Model.Stops.Count)

+
+

@localizer["Stops"] (@Model.Stops.Count)

+ +
NameDescriptionCreated@localizer["Name"]@localizer["Description"]@localizer["Created"]
@map.Description @map.AddedOn.ToString("g") - Floors - Edit - Delete + @localizer["Floors"] + @localizer["Edit"] + @localizer["Delete"]
- Name + @localizer["Name"] - Color + @localizer["Color"] - Item Count + @localizer["ItemCount"] - Edit - Delete + @localizer["Edit"] + @localizer["Delete"]
- Name + @localizer["Name"] - Color + @localizer["Color"] - Point Count + @localizer["PointCount"] - View + @localizer["View"] @if (ClaimsAuthorizationHelper.IsUserDepartmentAdmin()) { - Add - Import - Delete + @localizer["Add"] + @localizer["Import"] + @localizer["Delete"] }
- - - - - + + + + + + - + @foreach (var stop in Model.Stops) { - + + } @@ -139,8 +151,8 @@
- Cancel - + @localizer["Cancel"] +
@@ -150,11 +162,127 @@ + + + @section Scripts { } diff --git a/Web/Resgrid.Web/Areas/User/Views/Routes/Index.cshtml b/Web/Resgrid.Web/Areas/User/Views/Routes/Index.cshtml index 3d27dd6c1..7ac3cdf53 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Routes/Index.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Routes/Index.cshtml @@ -1,25 +1,26 @@ @model Resgrid.Web.Areas.User.Models.Routes.RouteIndexView @using Resgrid.Model +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | Routes"; + ViewBag.Title = "Resgrid | " + localizer["RoutesPageTitle"]; }
-

Routes

+

@localizer["RoutesHeader"]

@@ -33,12 +34,12 @@
#NameTypeAddressPriority@localizer["StopsTableHash"]@localizer["Name"]@localizer["StopType"]@localizer["Address"]@localizer["Priority"]
@stop.StopOrder @stop.Name @((Resgrid.Model.RouteStopType)stop.StopType) @stop.Address @((Resgrid.Model.RouteStopPriority)stop.Priority) + +
- - - - - - + + + + + + @@ -52,18 +53,18 @@
NameStatusStopsProfileCreatedActions@localizer["Name"]@localizer["Status"]@localizer["Stops"]@localizer["Profile"]@localizer["Created"]@localizer["Actions"]
@plan.AddedOn.ToString("yyyy-MM-dd") - View + @localizer["View"] - Edit + @localizer["Edit"] - History + @localizer["History"]
@Html.AntiForgeryToken() -
diff --git a/Web/Resgrid.Web/Areas/User/Views/Routes/InstanceDetail.cshtml b/Web/Resgrid.Web/Areas/User/Views/Routes/InstanceDetail.cshtml index 72333d6c9..dd6480d5a 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Routes/InstanceDetail.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Routes/InstanceDetail.cshtml @@ -1,15 +1,16 @@ @model Resgrid.Web.Areas.User.Models.Routes.RouteInstanceDetailView +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | Route Instance Detail"; + ViewBag.Title = "Resgrid | " + localizer["RouteInstanceDetailPageTitle"]; }
-

Route Instance - @Model.Plan.Name

+

@localizer["RouteInstanceHeaderPrefix"] - @Model.Plan.Name

@@ -18,7 +19,7 @@
-
Route Map
+
@localizer["RouteMapTitle"]
@@ -26,18 +27,18 @@
-
Instance Info
+
@localizer["InstanceInfoTitle"]
-
Status
+
@localizer["StatusLabel"]
@((Resgrid.Model.RouteInstanceStatus)Model.Instance.Status)
-
Unit
+
@localizer["Unit"]
@Model.Instance.UnitId
-
Started
+
@localizer["StartedDt"]
@(Model.Instance.ActualStartOn?.ToString("yyyy-MM-dd HH:mm") ?? "-")
-
Ended
+
@localizer["EndedDt"]
@(Model.Instance.ActualEndOn?.ToString("yyyy-MM-dd HH:mm") ?? "-")
-
Progress
+
@localizer["Progress"]
@Model.Instance.StopsCompleted / @Model.Instance.StopsTotal
@@ -50,35 +51,35 @@
-
Stop Timeline
+
@localizer["StopTimelineTitle"]
@foreach (var stop in Model.Stops.OrderBy(s => s.StopOrder)) { var statusClass = stop.Status switch { 1 => "lazur-bg", 2 => "navy-bg", 3 => "warning-bg", _ => "default-bg" }; - var statusText = stop.Status switch { 0 => "Pending", 1 => "Checked In", 2 => "Completed", 3 => "Skipped", _ => "Unknown" }; + var statusText = stop.Status switch { 0 => localizer["StopStatusPending"].Value, 1 => localizer["StopStatusCheckedIn"].Value, 2 => localizer["StopStatusCompleted"].Value, 3 => localizer["StopStatusSkipped"].Value, _ => localizer["StopStatusUnknown"].Value };
-

Stop @(stop.StopOrder + 1)

+

@localizer["StopLabel"] @(stop.StopOrder + 1)

@statusText

@if (stop.CheckInOn.HasValue) { -

In: @stop.CheckInOn.Value.ToString("HH:mm:ss")

+

@localizer["StopInPrefix"] @stop.CheckInOn.Value.ToString("HH:mm:ss")

} @if (stop.CheckOutOn.HasValue) { -

Out: @stop.CheckOutOn.Value.ToString("HH:mm:ss")

+

@localizer["StopOutPrefix"] @stop.CheckOutOn.Value.ToString("HH:mm:ss")

} @if (stop.DwellSeconds.HasValue) { -

Dwell: @(stop.DwellSeconds.Value / 60)m @(stop.DwellSeconds.Value % 60)s

+

@localizer["StopDwellPrefix"] @(stop.DwellSeconds.Value / 60)m @(stop.DwellSeconds.Value % 60)s

} @if (!string.IsNullOrEmpty(stop.SkipReason)) { -

Skip: @stop.SkipReason

+

@localizer["StopSkipPrefix"] @stop.SkipReason

}
diff --git a/Web/Resgrid.Web/Areas/User/Views/Routes/Instances.cshtml b/Web/Resgrid.Web/Areas/User/Views/Routes/Instances.cshtml index feb90027f..ef420dbfb 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Routes/Instances.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Routes/Instances.cshtml @@ -1,15 +1,16 @@ @model Resgrid.Web.Areas.User.Models.Routes.RouteInstancesView +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | Route History"; + ViewBag.Title = "Resgrid | " + localizer["RouteHistoryPageTitle"]; }
-

Route History - @Model.Plan.Name

+

@localizer["RouteHistoryHeaderPrefix"] - @Model.Plan.Name

@@ -23,12 +24,12 @@ - - - - - - + + + + + + @@ -42,7 +43,7 @@ diff --git a/Web/Resgrid.Web/Areas/User/Views/Routes/New.cshtml b/Web/Resgrid.Web/Areas/User/Views/Routes/New.cshtml index 09b248b9a..dd42b2f37 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Routes/New.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Routes/New.cshtml @@ -1,15 +1,16 @@ @model Resgrid.Web.Areas.User.Models.Routes.RouteNewView +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | New Route"; + ViewBag.Title = "Resgrid | " + localizer["NewRoutePageTitle"]; }
-

New Route Plan

+

@localizer["NewRoutePlanHeader"]

@@ -19,31 +20,31 @@
-
Route Details
+
@localizer["RouteDetailsTitle"]
@Html.AntiForgeryToken()
- +
- +
- +
- - + +
- +
- +
- +
@@ -92,29 +93,29 @@
-

Map & Stops

-

Stops can be managed after creating the route via the API or edit form.

+

@localizer["MapAndStopsTitle"]

+

@localizer["MapAndStopsHelp"]

- Cancel - + @localizer["Cancel"] +
diff --git a/Web/Resgrid.Web/Areas/User/Views/Routes/View.cshtml b/Web/Resgrid.Web/Areas/User/Views/Routes/View.cshtml index 9050d60e7..14938540a 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Routes/View.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Routes/View.cshtml @@ -1,15 +1,16 @@ @model Resgrid.Web.Areas.User.Models.Routes.RouteDetailView +@inject IStringLocalizer localizer @{ - ViewBag.Title = "Resgrid | Route Detail"; + ViewBag.Title = "Resgrid | " + localizer["RouteDetailPageTitle"]; }

@Model.Plan.Name

@@ -18,7 +19,7 @@
-
Route Map
+
@localizer["RouteMapTitle"]
@@ -26,23 +27,23 @@
-
Details
+
@localizer["DetailsTitle"]
-
Status
+
@localizer["StatusLabel"]
@((Resgrid.Model.RouteStatus)Model.Plan.RouteStatus)
-
Profile
+
@localizer["ProfileLabel"]
@(Model.Plan.MapboxRouteProfile ?? "driving")
-
Geofence
+
@localizer["GeofenceLabel"]
@Model.Plan.GeofenceRadiusMeters m
@if (Model.Plan.EstimatedDistanceMeters.HasValue) { -
Distance
+
@localizer["DistanceLabel"]
@(Math.Round(Model.Plan.EstimatedDistanceMeters.Value / 1000, 1)) km
} @if (Model.Plan.EstimatedDurationSeconds.HasValue) { -
Duration
+
@localizer["DurationLabel"]
@(Math.Round(Model.Plan.EstimatedDurationSeconds.Value / 60, 0)) min
}
@@ -50,7 +51,7 @@
-
Stops (@Model.Stops.Count)
+
@localizer["Stops"] (@Model.Stops.Count)
@foreach (var stop in Model.Stops.OrderBy(s => s.StopOrder)) @@ -77,8 +78,8 @@ var osmTileUrl = '@Resgrid.Config.MappingConfig.GetWebsiteOSMUrl()'; var osmTileAttribution = '@Resgrid.Config.MappingConfig.LeafletAttribution'; var routeStops = @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.Stops.OrderBy(s => s.StopOrder).Select(s => new { s.Name, lat = s.Latitude, lng = s.Longitude }))); - var routeGeometry = '@Html.Raw(Model.Plan.MapboxRouteGeometry ?? "")'; - var routeColor = '@Html.Raw(Model.Plan.RouteColor ?? "#3388ff")'; + var routeGeometry = @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.Plan.MapboxRouteGeometry ?? "")); + var routeColor = @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model.Plan.RouteColor ?? "#3388ff")); } diff --git a/Web/Resgrid.Web/Areas/User/Views/Shared/_ChatWidget.cshtml b/Web/Resgrid.Web/Areas/User/Views/Shared/_ChatWidget.cshtml index ce86b3236..7bf54376d 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Shared/_ChatWidget.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Shared/_ChatWidget.cshtml @@ -1,5 +1,5 @@ - - + +
diff --git a/Web/Resgrid.Web/Areas/User/Views/Subscription/ViewInvoice.cshtml b/Web/Resgrid.Web/Areas/User/Views/Subscription/ViewInvoice.cshtml index c9819a898..202989ee6 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Subscription/ViewInvoice.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Subscription/ViewInvoice.cshtml @@ -15,7 +15,7 @@ -
- + - + diff --git a/Web/Resgrid.Web/Views/Account/AffiliateRegister.cshtml b/Web/Resgrid.Web/Views/Account/AffiliateRegister.cshtml index 07966569c..8fc9584da 100644 --- a/Web/Resgrid.Web/Views/Account/AffiliateRegister.cshtml +++ b/Web/Resgrid.Web/Views/Account/AffiliateRegister.cshtml @@ -83,7 +83,7 @@ }
- + diff --git a/Web/Resgrid.Web/Views/Account/CompletedInvite.cshtml b/Web/Resgrid.Web/Views/Account/CompletedInvite.cshtml index 59d313c7d..79f9785cf 100644 --- a/Web/Resgrid.Web/Views/Account/CompletedInvite.cshtml +++ b/Web/Resgrid.Web/Views/Account/CompletedInvite.cshtml @@ -13,7 +13,7 @@ Resgrid | @localizer["CompletedInviteHeader"] -
- + - + diff --git a/Web/Resgrid.Web/Views/Account/ForgotPassword.cshtml b/Web/Resgrid.Web/Views/Account/ForgotPassword.cshtml index c05c35ae2..dc31f9b03 100644 --- a/Web/Resgrid.Web/Views/Account/ForgotPassword.cshtml +++ b/Web/Resgrid.Web/Views/Account/ForgotPassword.cshtml @@ -65,7 +65,7 @@
- + diff --git a/Web/Resgrid.Web/Views/Account/Lockout.cshtml b/Web/Resgrid.Web/Views/Account/Lockout.cshtml index b771e5b63..722ecb7c8 100644 --- a/Web/Resgrid.Web/Views/Account/Lockout.cshtml +++ b/Web/Resgrid.Web/Views/Account/Lockout.cshtml @@ -12,7 +12,7 @@ Resgrid | @localizer["LockedOutHeader"] -
- + - + diff --git a/Web/Resgrid.Web/Views/Account/MissingCode.cshtml b/Web/Resgrid.Web/Views/Account/MissingCode.cshtml index 9792958dd..0a5e7fdc4 100644 --- a/Web/Resgrid.Web/Views/Account/MissingCode.cshtml +++ b/Web/Resgrid.Web/Views/Account/MissingCode.cshtml @@ -11,7 +11,7 @@ - -
- + - + diff --git a/Web/Resgrid.Web/Views/Account/MissingInvite.cshtml b/Web/Resgrid.Web/Views/Account/MissingInvite.cshtml index 04c5e3f09..dd7163b65 100644 --- a/Web/Resgrid.Web/Views/Account/MissingInvite.cshtml +++ b/Web/Resgrid.Web/Views/Account/MissingInvite.cshtml @@ -13,7 +13,7 @@ Resgrid | @localizer["MissingInviteHeader"] -
- + - + diff --git a/Web/Resgrid.Web/Views/Account/Register.cshtml b/Web/Resgrid.Web/Views/Account/Register.cshtml index 5906d9cd3..74f0b5c46 100644 --- a/Web/Resgrid.Web/Views/Account/Register.cshtml +++ b/Web/Resgrid.Web/Views/Account/Register.cshtml @@ -140,7 +140,7 @@
- + diff --git a/Web/Resgrid.Web/Views/Shared/Error.cshtml b/Web/Resgrid.Web/Views/Shared/Error.cshtml index 33b245984..6b00ab81b 100644 --- a/Web/Resgrid.Web/Views/Shared/Error.cshtml +++ b/Web/Resgrid.Web/Views/Shared/Error.cshtml @@ -10,7 +10,7 @@ Resgrid - Error -
- + - + diff --git a/Web/Resgrid.Web/Views/Shared/Unauthorized.cshtml b/Web/Resgrid.Web/Views/Shared/Unauthorized.cshtml index 8efdc7aac..b68831dd2 100644 --- a/Web/Resgrid.Web/Views/Shared/Unauthorized.cshtml +++ b/Web/Resgrid.Web/Views/Shared/Unauthorized.cshtml @@ -10,7 +10,7 @@ Resgrid - Unauthorized - - + - + diff --git a/Web/Resgrid.Web/Views/Shared/_ScriptsPartial.cshtml b/Web/Resgrid.Web/Views/Shared/_ScriptsPartial.cshtml index aafd1f661..1b2328773 100644 --- a/Web/Resgrid.Web/Views/Shared/_ScriptsPartial.cshtml +++ b/Web/Resgrid.Web/Views/Shared/_ScriptsPartial.cshtml @@ -1,10 +1,10 @@ - - diff --git a/Web/Resgrid.Web/Areas/User/Views/Shared/_TopNavbar.cshtml b/Web/Resgrid.Web/Areas/User/Views/Shared/_TopNavbar.cshtml index e57ded547..011913336 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Shared/_TopNavbar.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Shared/_TopNavbar.cshtml @@ -85,6 +85,9 @@ + diff --git a/Web/Resgrid.Web/Views/Account/LogOn.cshtml b/Web/Resgrid.Web/Views/Account/LogOn.cshtml index a696457fe..c08d520ab 100644 --- a/Web/Resgrid.Web/Views/Account/LogOn.cshtml +++ b/Web/Resgrid.Web/Views/Account/LogOn.cshtml @@ -126,6 +126,9 @@ + @@ -177,7 +180,8 @@ 'fr': '/images/flags/32/France.png', 'it': '/images/flags/32/Italy.png', 'pl': '/images/flags/32/Poland.png', - 'uk': '/images/flags/32/Ukraine.png' + 'uk': '/images/flags/32/Ukraine.png', + 'ar': '/images/flags/32/Saudi-Arabia.png' }; if (langCookieValue) { diff --git a/Web/Resgrid.Web/libman.json b/Web/Resgrid.Web/libman.json index e424aecc8..30257af54 100644 --- a/Web/Resgrid.Web/libman.json +++ b/Web/Resgrid.Web/libman.json @@ -323,6 +323,6 @@ "bundle.min.js", "bundle.min.js.map" ] - }, + } ] } diff --git a/Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.editcall.js b/Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.editcall.js index 4a65551f7..67a33ba65 100644 --- a/Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.editcall.js +++ b/Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.editcall.js @@ -121,7 +121,7 @@ var resgrid; 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 && result.Data.Longitude) { + 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)); diff --git a/Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js b/Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js index eef3bef66..bf6470016 100644 --- a/Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js +++ b/Web/Resgrid.Web/wwwroot/js/app/internal/dispatch/resgrid.dispatch.newcall.js @@ -114,7 +114,7 @@ var resgrid; 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 && result.Data.Longitude) { + 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)); @@ -137,7 +137,7 @@ var resgrid; contentType: 'application/json', type: 'GET' }).done(function (data) { - if (data && data.Latitude && data.Longitude) { + if (data && data.Latitude != null && data.Longitude != null) { map.panTo(new L.LatLng(data.Latitude, data.Longitude)); $("#Latitude").val(data.Latitude); diff --git a/Web/Resgrid.Web/wwwroot/js/app/internal/resgrid.user.js b/Web/Resgrid.Web/wwwroot/js/app/internal/resgrid.user.js index 352ce6ac2..672f764e2 100644 --- a/Web/Resgrid.Web/wwwroot/js/app/internal/resgrid.user.js +++ b/Web/Resgrid.Web/wwwroot/js/app/internal/resgrid.user.js @@ -54,7 +54,8 @@ var resgrid; 'fr': '/images/flags/32/France.png', 'it': '/images/flags/32/Italy.png', 'pl': '/images/flags/32/Poland.png', - 'uk': '/images/flags/32/Ukraine.png' + 'uk': '/images/flags/32/Ukraine.png', + 'ar': '/images/flags/32/Saudi-Arabia.png' }; if (langCookieValue) { From ded747604ea241a85dc4dd61308f4cf9b1b1a963 Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Tue, 17 Mar 2026 16:21:26 -0700 Subject: [PATCH 3/3] RE1-T105 Bug fixes --- Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs | 1 + Web/Resgrid.Web/Areas/User/Views/Dispatch/Dashboard.cshtml | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs b/Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs index 7bf5e3f2f..b25e986d0 100644 --- a/Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs +++ b/Web/Resgrid.Web/Areas/User/Controllers/IndoorMapsController.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using Resgrid.Model.Services; namespace Resgrid.Web.Areas.User.Controllers diff --git a/Web/Resgrid.Web/Areas/User/Views/Dispatch/Dashboard.cshtml b/Web/Resgrid.Web/Areas/User/Views/Dispatch/Dashboard.cshtml index c70d7217c..9740429bc 100644 --- a/Web/Resgrid.Web/Areas/User/Views/Dispatch/Dashboard.cshtml +++ b/Web/Resgrid.Web/Areas/User/Views/Dispatch/Dashboard.cshtml @@ -102,8 +102,6 @@ @section Scripts { - -
StatusUnitStartedEndedStopsActions@localizer["Status"]@localizer["Unit"]@localizer["Started"]@localizer["Ended"]@localizer["Stops"]@localizer["Actions"]
@instance.StopsCompleted / @instance.StopsTotal - Detail + @localizer["Detail"]