Skip to content

Commit daa2ef9

Browse files
committed
RE1-T105 PR#305 fixes
1 parent de853d6 commit daa2ef9

5 files changed

Lines changed: 98 additions & 30 deletions

File tree

Core/Resgrid.Localization/Areas/User/Routes/Routes.en.resx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,34 @@
252252
<data name="CreateRoute" xml:space="preserve">
253253
<value>Create Route</value>
254254
</data>
255+
<!-- StartRoute -->
256+
<data name="StartRoutePageTitle" xml:space="preserve">
257+
<value>Start Route</value>
258+
</data>
259+
<data name="StartRouteHeader" xml:space="preserve">
260+
<value>Start Route</value>
261+
</data>
262+
<data name="StartRouteBreadcrumb" xml:space="preserve">
263+
<value>Start Route</value>
264+
</data>
265+
<data name="AssignUnit" xml:space="preserve">
266+
<value>Assign Unit</value>
267+
</data>
268+
<data name="SelectUnitPlaceholder" xml:space="preserve">
269+
<value>-- Select a Unit --</value>
270+
</data>
271+
<data name="AssignUnitHelp" xml:space="preserve">
272+
<value>Select the unit that will execute this route.</value>
273+
</data>
274+
<data name="RouteInfoLabel" xml:space="preserve">
275+
<value>Route Info</value>
276+
</data>
277+
<data name="MinEstimated" xml:space="preserve">
278+
<value>min estimated</value>
279+
</data>
280+
<data name="StartRouteNow" xml:space="preserve">
281+
<value>Start Route Now</value>
282+
</data>
255283
<!-- Edit -->
256284
<data name="EditRoutePageTitle" xml:space="preserve">
257285
<value>Edit Route</value>

Web/Resgrid.Web/Areas/User/Controllers/RoutesController.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -381,8 +381,24 @@ public async Task<IActionResult> StartRoute(RouteStartView model, CancellationTo
381381
if (plan.RouteStatus != (int)RouteStatus.Active)
382382
return RedirectToAction("View", new { id = model.RoutePlanId });
383383

384-
var instance = await _routeService.StartRouteAsync(model.RoutePlanId, model.SelectedUnitId, UserId, cancellationToken);
385-
return RedirectToAction("InstanceDetail", new { instanceId = instance.RouteInstanceId });
384+
try
385+
{
386+
var instance = await _routeService.StartRouteAsync(model.RoutePlanId, model.SelectedUnitId, UserId, cancellationToken);
387+
return RedirectToAction("InstanceDetail", new { instanceId = instance.RouteInstanceId });
388+
}
389+
catch (InvalidOperationException ex)
390+
{
391+
// Unit already has an active route — let the user pick a different unit.
392+
ModelState.AddModelError(nameof(model.SelectedUnitId), ex.Message);
393+
model.Plan = plan;
394+
model.Units = (await _unitsService.GetUnitsForDepartmentAsync(DepartmentId)).ToList();
395+
return View(model);
396+
}
397+
catch (ArgumentException)
398+
{
399+
// Route plan disappeared between validation and start — redirect to Index.
400+
return RedirectToAction("Index");
401+
}
386402
}
387403

388404
[HttpGet]

Web/Resgrid.Web/Areas/User/Views/Routes/StartRoute.cshtml

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
@model Resgrid.Web.Areas.User.Models.Routes.RouteStartView
22
@inject IStringLocalizer<Resgrid.Localization.Areas.User.Routes.Routes> localizer
33
@{
4-
ViewBag.Title = "Resgrid | Start Route";
4+
ViewBag.Title = "Resgrid | " + localizer["StartRoutePageTitle"];
55
}
66

77
<div class="row wrapper border-bottom white-bg page-heading">
88
<div class="col-sm-8">
9-
<h2>Start Route &mdash; @Model.Plan.Name</h2>
9+
<h2>@localizer["StartRouteHeader"] &mdash; @Model.Plan.Name</h2>
1010
<ol class="breadcrumb">
1111
<li><a asp-controller="Home" asp-action="Dashboard" asp-route-area="User">@commonLocalizer["HomeModule"]</a></li>
1212
<li><a asp-controller="Routes" asp-action="Index" asp-route-area="User">@localizer["RoutesHeader"]</a></li>
1313
<li><a asp-controller="Routes" asp-action="View" asp-route-id="@Model.Plan.RoutePlanId" asp-route-area="User">@Model.Plan.Name</a></li>
14-
<li class="active"><strong>Start Route</strong></li>
14+
<li class="active"><strong>@localizer["StartRouteBreadcrumb"]</strong></li>
1515
</ol>
1616
</div>
1717
</div>
@@ -21,31 +21,39 @@
2121
<div class="col-md-6 col-md-offset-3">
2222
<div class="ibox float-e-margins">
2323
<div class="ibox-title">
24-
<h5><i class="fa fa-play"></i> Start Route: @Model.Plan.Name</h5>
24+
<h5><i class="fa fa-play"></i> @localizer["StartRouteHeader"]: @Model.Plan.Name</h5>
2525
</div>
2626
<div class="ibox-content">
2727
<form asp-controller="Routes" asp-action="StartRoute" asp-route-area="User" method="post" class="form-horizontal">
2828
@Html.AntiForgeryToken()
2929
<input type="hidden" name="RoutePlanId" value="@Model.Plan.RoutePlanId" />
3030

31-
<div class="form-group">
32-
<label class="col-sm-3 control-label">Assign Unit</label>
31+
@if (!ViewData.ModelState.IsValid)
32+
{
33+
<div class="alert alert-danger">
34+
<i class="fa fa-exclamation-circle"></i>
35+
@Html.ValidationSummary(excludePropertyErrors: false, message: "", htmlAttributes: new { @class = "mb-0" })
36+
</div>
37+
}
38+
39+
<div class="form-group @(ViewData.ModelState["SelectedUnitId"]?.Errors.Count > 0 ? "has-error" : "")">
40+
<label class="col-sm-3 control-label">@localizer["AssignUnit"]</label>
3341
<div class="col-sm-9">
3442
<select name="SelectedUnitId" class="form-control" required>
35-
<option value="">-- Select a Unit --</option>
43+
<option value="">@localizer["SelectUnitPlaceholder"]</option>
3644
@foreach (var unit in Model.Units)
3745
{
3846
<option value="@unit.UnitId">@unit.Name</option>
3947
}
4048
</select>
41-
<span class="help-block">Select the unit that will execute this route.</span>
49+
<span class="help-block">@localizer["AssignUnitHelp"]</span>
4250
</div>
4351
</div>
4452

4553
@if (Model.Plan.EstimatedDistanceMeters.HasValue || Model.Plan.EstimatedDurationSeconds.HasValue)
4654
{
4755
<div class="form-group">
48-
<label class="col-sm-3 control-label">Route Info</label>
56+
<label class="col-sm-3 control-label">@localizer["RouteInfoLabel"]</label>
4957
<div class="col-sm-9">
5058
@if (Model.Plan.EstimatedDistanceMeters.HasValue)
5159
{
@@ -59,7 +67,7 @@
5967
{
6068
<span class="label label-info">
6169
<i class="fa fa-clock-o"></i>
62-
@(Math.Round(Model.Plan.EstimatedDurationSeconds.Value / 60, 0)) min estimated
70+
@(Math.Round(Model.Plan.EstimatedDurationSeconds.Value / 60, 0)) @localizer["MinEstimated"]
6371
</span>
6472
}
6573
</div>
@@ -69,10 +77,10 @@
6977
<div class="form-group">
7078
<div class="col-sm-offset-3 col-sm-9">
7179
<button type="submit" class="btn btn-success">
72-
<i class="fa fa-play"></i> Start Route Now
80+
<i class="fa fa-play"></i> @localizer["StartRouteNow"]
7381
</button>
7482
<a asp-controller="Routes" asp-action="View" asp-route-id="@Model.Plan.RoutePlanId" asp-route-area="User" class="btn btn-default">
75-
Cancel
83+
@localizer["Cancel"]
7684
</a>
7785
</div>
7886
</div>

Web/Resgrid.Web/wwwroot/js/app/internal/routes/resgrid.routes.edit.js

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ $(document).ready(function () {
111111
.addLayer(tiles);
112112
startPickerMap.on('click', function (e) { setStartLocation(e.latlng.lat, e.latlng.lng); });
113113
startPickerInitialized = true;
114-
if (initialLat && initialLng) { setStartLocation(initialLat, initialLng); }
114+
if (initialLat != null && initialLng != null) { setStartLocation(initialLat, initialLng); }
115115
}
116116

117117
function setStartLocation(lat, lng) {
@@ -144,7 +144,7 @@ $(document).ready(function () {
144144
.addLayer(tiles);
145145
endPickerMap.on('click', function (e) { setEndLocation(e.latlng.lat, e.latlng.lng); });
146146
endPickerInitialized = true;
147-
if (initialLat && initialLng) { setEndLocation(initialLat, initialLng); }
147+
if (initialLat != null && initialLng != null) { setEndLocation(initialLat, initialLng); }
148148
}
149149

150150
function setEndLocation(lat, lng) {
@@ -169,21 +169,29 @@ $(document).ready(function () {
169169
function updateLocationPickerVisibility() {
170170
if (!$('#chkUseStationAsStart').prop('checked')) {
171171
$('#startLocationGroup').show();
172+
$('#startLat').prop('disabled', false);
173+
$('#startLng').prop('disabled', false);
172174
initStartPickerMap(
173-
(typeof existingStartLat !== 'undefined' && existingStartLat) ? existingStartLat : null,
174-
(typeof existingStartLng !== 'undefined' && existingStartLng) ? existingStartLng : null
175+
(typeof existingStartLat !== 'undefined' && existingStartLat != null) ? existingStartLat : null,
176+
(typeof existingStartLng !== 'undefined' && existingStartLng != null) ? existingStartLng : null
175177
);
176178
} else {
177179
$('#startLocationGroup').hide();
180+
$('#startLat').val('').prop('disabled', true);
181+
$('#startLng').val('').prop('disabled', true);
178182
}
179183
if (!$('#chkUseStationAsEnd').prop('checked')) {
180184
$('#endLocationGroup').show();
185+
$('#endLat').prop('disabled', false);
186+
$('#endLng').prop('disabled', false);
181187
initEndPickerMap(
182-
(typeof existingEndLat !== 'undefined' && existingEndLat) ? existingEndLat : null,
183-
(typeof existingEndLng !== 'undefined' && existingEndLng) ? existingEndLng : null
188+
(typeof existingEndLat !== 'undefined' && existingEndLat != null) ? existingEndLat : null,
189+
(typeof existingEndLng !== 'undefined' && existingEndLng != null) ? existingEndLng : null
184190
);
185191
} else {
186192
$('#endLocationGroup').hide();
193+
$('#endLat').val('').prop('disabled', true);
194+
$('#endLng').val('').prop('disabled', true);
187195
}
188196
}
189197

@@ -199,7 +207,7 @@ $(document).ready(function () {
199207
fetch(resgrid.absoluteApiBaseUrl + '/api/v4/Geocoding/ForwardGeocode?address=' + encodeURIComponent(where), { headers: { 'Authorization': 'Bearer ' + getAuthToken() } })
200208
.then(function (r) { return r.json(); })
201209
.then(function (result) {
202-
if (result && result.Data && result.Data.Latitude && result.Data.Longitude) {
210+
if (result && result.Data && result.Data.Latitude != null && result.Data.Longitude != null) {
203211
setStartLocation(result.Data.Latitude, result.Data.Longitude);
204212
} else { alert('Address not found.'); }
205213
})
@@ -214,7 +222,7 @@ $(document).ready(function () {
214222
if (!word) return;
215223
$.ajax({ url: resgrid.absoluteBaseUrl + '/User/Dispatch/GetCoordinatesFromW3W?words=' + encodeURIComponent(word), type: 'GET' })
216224
.done(function (data) {
217-
if (data && data.Latitude && data.Longitude) { setStartLocation(data.Latitude, data.Longitude); }
225+
if (data && data.Latitude != null && data.Longitude != null) { setStartLocation(data.Latitude, data.Longitude); }
218226
else { alert('What3Words was unable to find a location for those words.'); }
219227
});
220228
});
@@ -236,7 +244,7 @@ $(document).ready(function () {
236244
fetch(resgrid.absoluteApiBaseUrl + '/api/v4/Geocoding/ForwardGeocode?address=' + encodeURIComponent(where), { headers: { 'Authorization': 'Bearer ' + getAuthToken() } })
237245
.then(function (r) { return r.json(); })
238246
.then(function (result) {
239-
if (result && result.Data && result.Data.Latitude && result.Data.Longitude) {
247+
if (result && result.Data && result.Data.Latitude != null && result.Data.Longitude != null) {
240248
setEndLocation(result.Data.Latitude, result.Data.Longitude);
241249
} else { alert('Address not found.'); }
242250
})
@@ -251,7 +259,7 @@ $(document).ready(function () {
251259
if (!word) return;
252260
$.ajax({ url: resgrid.absoluteBaseUrl + '/User/Dispatch/GetCoordinatesFromW3W?words=' + encodeURIComponent(word), type: 'GET' })
253261
.done(function (data) {
254-
if (data && data.Latitude && data.Longitude) { setEndLocation(data.Latitude, data.Longitude); }
262+
if (data && data.Latitude != null && data.Longitude != null) { setEndLocation(data.Latitude, data.Longitude); }
255263
else { alert('What3Words was unable to find a location for those words.'); }
256264
});
257265
});

Web/Resgrid.Web/wwwroot/js/app/internal/routes/resgrid.routes.new.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ $(document).ready(function () {
105105
.addLayer(tiles);
106106
startPickerMap.on('click', function (e) { setStartLocation(e.latlng.lat, e.latlng.lng); });
107107
startPickerInitialized = true;
108-
if (initialLat && initialLng) { setStartLocation(initialLat, initialLng); }
108+
if (Number.isFinite(Number(initialLat)) && Number.isFinite(Number(initialLng))) { setStartLocation(initialLat, initialLng); }
109109
}
110110

111111
function setStartLocation(lat, lng) {
@@ -138,7 +138,7 @@ $(document).ready(function () {
138138
.addLayer(tiles);
139139
endPickerMap.on('click', function (e) { setEndLocation(e.latlng.lat, e.latlng.lng); });
140140
endPickerInitialized = true;
141-
if (initialLat && initialLng) { setEndLocation(initialLat, initialLng); }
141+
if (Number.isFinite(Number(initialLat)) && Number.isFinite(Number(initialLng))) { setEndLocation(initialLat, initialLng); }
142142
}
143143

144144
function setEndLocation(lat, lng) {
@@ -163,15 +163,23 @@ $(document).ready(function () {
163163
function updateLocationPickerVisibility() {
164164
if (!$('#chkUseStationAsStart').prop('checked')) {
165165
$('#startLocationGroup').show();
166+
$('#startLat').prop('disabled', false);
167+
$('#startLng').prop('disabled', false);
166168
initStartPickerMap();
167169
} else {
168170
$('#startLocationGroup').hide();
171+
$('#startLat').val('').prop('disabled', true);
172+
$('#startLng').val('').prop('disabled', true);
169173
}
170174
if (!$('#chkUseStationAsEnd').prop('checked')) {
171175
$('#endLocationGroup').show();
176+
$('#endLat').prop('disabled', false);
177+
$('#endLng').prop('disabled', false);
172178
initEndPickerMap();
173179
} else {
174180
$('#endLocationGroup').hide();
181+
$('#endLat').val('').prop('disabled', true);
182+
$('#endLng').val('').prop('disabled', true);
175183
}
176184
}
177185

@@ -187,7 +195,7 @@ $(document).ready(function () {
187195
fetch(resgrid.absoluteApiBaseUrl + '/api/v4/Geocoding/ForwardGeocode?address=' + encodeURIComponent(where), { headers: { 'Authorization': 'Bearer ' + getAuthToken() } })
188196
.then(function (r) { return r.json(); })
189197
.then(function (result) {
190-
if (result && result.Data && result.Data.Latitude && result.Data.Longitude) {
198+
if (result && result.Data && result.Data.Latitude != null && result.Data.Longitude != null) {
191199
setStartLocation(result.Data.Latitude, result.Data.Longitude);
192200
} else { alert('Address not found.'); }
193201
})
@@ -202,7 +210,7 @@ $(document).ready(function () {
202210
if (!word) return;
203211
$.ajax({ url: resgrid.absoluteBaseUrl + '/User/Dispatch/GetCoordinatesFromW3W?words=' + encodeURIComponent(word), type: 'GET' })
204212
.done(function (data) {
205-
if (data && data.Latitude && data.Longitude) { setStartLocation(data.Latitude, data.Longitude); }
213+
if (data && data.Latitude != null && data.Longitude != null) { setStartLocation(data.Latitude, data.Longitude); }
206214
else { alert('What3Words was unable to find a location for those words.'); }
207215
});
208216
});
@@ -224,7 +232,7 @@ $(document).ready(function () {
224232
fetch(resgrid.absoluteApiBaseUrl + '/api/v4/Geocoding/ForwardGeocode?address=' + encodeURIComponent(where), { headers: { 'Authorization': 'Bearer ' + getAuthToken() } })
225233
.then(function (r) { return r.json(); })
226234
.then(function (result) {
227-
if (result && result.Data && result.Data.Latitude && result.Data.Longitude) {
235+
if (result && result.Data && result.Data.Latitude != null && result.Data.Longitude != null) {
228236
setEndLocation(result.Data.Latitude, result.Data.Longitude);
229237
} else { alert('Address not found.'); }
230238
})
@@ -239,7 +247,7 @@ $(document).ready(function () {
239247
if (!word) return;
240248
$.ajax({ url: resgrid.absoluteBaseUrl + '/User/Dispatch/GetCoordinatesFromW3W?words=' + encodeURIComponent(word), type: 'GET' })
241249
.done(function (data) {
242-
if (data && data.Latitude && data.Longitude) { setEndLocation(data.Latitude, data.Longitude); }
250+
if (data && data.Latitude != null && data.Longitude != null) { setEndLocation(data.Latitude, data.Longitude); }
243251
else { alert('What3Words was unable to find a location for those words.'); }
244252
});
245253
});

0 commit comments

Comments
 (0)