| @@ -10,6 +10,7 @@ | |||
| @inject PageHistoryManager PageHistoryManager | |||
| @inject ReportDataProvider ReportDataProvider | |||
| @inject Toaster Toaster | |||
| @inject InputCursorHandler InputCursorHandler; | |||
| <div class="row px-3 h-100"> | |||
| @@ -24,33 +25,40 @@ | |||
| </div> | |||
| <div class="row no-gutters w-100"> | |||
| <div class="col-6" style="padding-right:0.5em"> | |||
| <MatStringField Class="w-100" Label="@I18n["Firstname"]" Outlined="true" type="text" @bind-Value="@Account.Firstname" Required="true"></MatStringField> | |||
| <MatStringField Class="w-100" Label="@I18n["Firstname"]" Outlined="true" type="text" @bind-Value="@Account.Firstname" Required="true" | |||
| OnKeyDown="@InputCursorHandler.OnKeyPressHandlerAsync"></MatStringField> | |||
| </div> | |||
| <div class="col-6" style="padding-left:0.5em"> | |||
| <MatStringField Class="w-100" Label="@I18n["Lastname"]" Outlined="true" type="text" @bind-Value="@Account.Lastname" Required="true"></MatStringField> | |||
| <MatStringField Class="w-100" Label="@I18n["Lastname"]" Outlined="true" type="text" @bind-Value="@Account.Lastname" Required="true" | |||
| OnKeyDown="@InputCursorHandler.OnKeyPressHandlerAsync"></MatStringField> | |||
| </div> | |||
| </div> | |||
| <div class="row no-gutters align-items-center w-100"> | |||
| <div class="col-12"> | |||
| <MatStringField Class="w-100" Label="@I18n["Address"]" Outlined="true" type="text" @bind-Value="@Account.Address" ></MatStringField> | |||
| <MatStringField Class="w-100" Label="@I18n["Address"]" Outlined="true" type="text" @bind-Value="@Account.Address" | |||
| OnKeyDown="@InputCursorHandler.OnKeyPressHandlerAsync"></MatStringField> | |||
| </div> | |||
| </div> | |||
| <div class="row no-gutters align-items-center w-100"> | |||
| <div class="col-4" style="padding-right:0.5em"> | |||
| <MatStringField Class="w-100" Label="@I18n["Zip"]" Outlined="true" type="text" @bind-Value="@Account.Zip"></MatStringField> | |||
| <MatStringField Class="w-100" Label="@I18n["Zip"]" Outlined="true" type="text" @bind-Value="@Account.Zip" | |||
| OnKeyDown="@InputCursorHandler.OnKeyPressHandlerAsync"></MatStringField> | |||
| </div> | |||
| <div class="col-8" style="padding-left:0.5em"> | |||
| <MatStringField Class="w-100" Label="@I18n["City"]" Outlined="true" type="text" @bind-Value="@Account.City"></MatStringField> | |||
| <MatStringField Class="w-100" Label="@I18n["City"]" Outlined="true" type="text" @bind-Value="@Account.City" | |||
| OnKeyDown="@InputCursorHandler.OnKeyPressHandlerAsync"></MatStringField> | |||
| </div> | |||
| </div> | |||
| <div class="row no-gutters align-items-center w-100"> | |||
| <div class="col-12"> | |||
| <MatStringField Class="w-100" Label="@I18n["Phone"]" Outlined="true" type="text" @bind-Value="@Account.Phone" Required="true"></MatStringField> | |||
| <MatStringField Class="w-100" Label="@I18n["Phone"]" Outlined="true" type="text" @bind-Value="@Account.Phone" Required="true" | |||
| OnKeyDown="@InputCursorHandler.OnKeyPressHandlerAsync"></MatStringField> | |||
| </div> | |||
| </div> | |||
| <div class="row no-gutters align-items-center w-100"> | |||
| <div class="col-12"> | |||
| <MatStringField Class="w-100" Label="@I18n["Mail"]" Outlined="true" type="text" @bind-Value="@Account.Email"></MatStringField> | |||
| <MatStringField Class="w-100" Label="@I18n["Mail"]" Outlined="true" type="text" @bind-Value="@Account.Email" | |||
| OnKeyDown="@InputCursorHandler.OnKeyPressHandlerAsync"></MatStringField> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -136,7 +144,7 @@ | |||
| Validator.ValidateContact(ReportDataProvider.Report); | |||
| NavigationManager.NavigateTo("fundvelo/conclusion_missing"); | |||
| } | |||
| } catch (ArgumentException ex) { | |||
| } catch (ArgumentException) { | |||
| Toaster.ShowWarning(I18n.GetString("Warning.MandatoryFields.Title"), I18n.GetString("Warning.MandatoryFields.Msg")); | |||
| } | |||
| } | |||
| @@ -107,7 +107,7 @@ | |||
| try { | |||
| Validator.ValidateAlternatePickContact(ReportDataProvider.GetFoundReport()); | |||
| NavigationManager.NavigateTo("fundvelo/account/Found"); | |||
| } catch (ArgumentException ex) { | |||
| } catch (ArgumentException) { | |||
| Toaster.ShowWarning(I18n.GetString("Warning.MandatoryFields.Title"), I18n.GetString("Warning.MandatoryFields.Msg")); | |||
| } | |||
| } | |||
| @@ -229,7 +229,8 @@ | |||
| MouseEvent mouseEvent = new MouseEvent(); | |||
| LatLng coordinates; | |||
| LatLng devicePosition = await InitializeDeviceMapPosition(); | |||
| if (report != null && report.GeographicInfo.Latitude != 0 && report.GeographicInfo.Longitude != 0) { | |||
| if (report != null && ((report.GeographicInfo.Latitude != 0 && report.GeographicInfo.Longitude != 0) || | |||
| (ReportDataProvider.ReportRepositoryItem != null && ReportDataProvider.ReportRepositoryItem.ID != 0))) { | |||
| bicycleGeoPosition.DisplayCity = await GetFormattedAddressZipAndTown(ReportDataProvider); | |||
| coordinates = new LatLng(report.GeographicInfo.Latitude, report.GeographicInfo.Longitude); | |||
| mouseEvent.LatLng = coordinates; | |||
| @@ -282,7 +283,7 @@ | |||
| } else { | |||
| NavigationManager.NavigateTo("fundvelo/account/Found"); | |||
| } | |||
| } catch (ArgumentException ex) { | |||
| } catch (ArgumentException) { | |||
| Toaster.ShowWarning(I18n.GetString("Warning.MandatoryFields.Title"), I18n.GetString("Warning.MandatoryFields.Msg")); | |||
| } | |||
| } | |||
| @@ -36,6 +36,9 @@ namespace cwebplusApp.Pages { | |||
| [Inject] | |||
| private IStringLocalizer<Resources> I18n { get; init; } | |||
| [Inject] | |||
| private OnlineStatusProvider OnlineStatusProvider { get; init; } | |||
| private NominatimService NominatimService { get; set; } | |||
| @@ -80,7 +83,9 @@ namespace cwebplusApp.Pages { | |||
| if (this.addressDto == null) { | |||
| this.bicycleGeoPosition.Latitude = ReportDataProvider.GetFoundReport().GeographicInfo.Latitude; | |||
| this.bicycleGeoPosition.Longitude = ReportDataProvider.GetFoundReport().GeographicInfo.Longitude; | |||
| this.addressDto = await NominatimService.GetAddressForCoordinates(this.bicycleGeoPosition.Latitude, this.bicycleGeoPosition.Longitude); | |||
| if (OnlineStatusProvider.Online && this.bicycleGeoPosition.Latitude != 0 && this.bicycleGeoPosition.Longitude != 0) { | |||
| this.addressDto = await NominatimService.GetAddressForCoordinates(this.bicycleGeoPosition.Latitude, this.bicycleGeoPosition.Longitude); | |||
| } | |||
| if (this.addressDto == null) { | |||
| this.addressDto = new(); | |||
| this.addressDto.address = new(); | |||
| @@ -92,10 +97,12 @@ namespace cwebplusApp.Pages { | |||
| } | |||
| protected async Task AddBicycleMarkerOnClickPosition(MouseEvent mouseEvent) { | |||
| if (this.bicyclePositionMarker != null) { | |||
| await bicyclePositionMarker.Remove(); | |||
| if (mouseEvent.LatLng.Lat != 0 && mouseEvent.LatLng.Lng != 0) { | |||
| if (this.bicyclePositionMarker != null) { | |||
| await bicyclePositionMarker.Remove(); | |||
| } | |||
| this.bicyclePositionMarker = await this.MarkerFactory.CreateAndAddToMap(mouseEvent.LatLng, this.mapRef, this.bicycleMarkerOptions); | |||
| } | |||
| this.bicyclePositionMarker = await this.MarkerFactory.CreateAndAddToMap(mouseEvent.LatLng, this.mapRef, this.bicycleMarkerOptions); | |||
| } | |||
| protected async Task<LatLng> GetDeviceGeoLocation() { | |||
| @@ -139,9 +146,19 @@ namespace cwebplusApp.Pages { | |||
| private async Task ShowBicycleGeoLocation() { | |||
| if (this.bicycleGeoPosition.Latitude != 0 && this.bicycleGeoPosition.Longitude != 0) { | |||
| LatLng geoPosition = new(this.bicycleGeoPosition.Latitude, this.bicycleGeoPosition.Longitude); | |||
| await this.mapRef.SetZoom(16); | |||
| await this.mapRef.SetView(geoPosition); | |||
| } else if (!String.IsNullOrEmpty(this.bicycleGeoPosition.Address) && | |||
| !String.IsNullOrEmpty(this.bicycleGeoPosition.Zip) && | |||
| !String.IsNullOrEmpty(this.bicycleGeoPosition.City)) { | |||
| LatLng geoPosition = await NominatimService.GetCoordinatesForAddress(bicycleGeoPosition.Address + ", " + bicycleGeoPosition.Zip + " " + bicycleGeoPosition.City); | |||
| this.bicycleGeoPosition.Latitude = geoPosition.Lat; | |||
| this.bicycleGeoPosition.Longitude = geoPosition.Lng; | |||
| await this.mapRef.SetZoom(16); | |||
| await this.mapRef.SetView(geoPosition); | |||
| MouseEvent mouseEvent = new MouseEvent(); | |||
| mouseEvent.LatLng = geoPosition; | |||
| await AddBicycleMarkerOnClickPosition(mouseEvent); | |||
| } | |||
| } | |||
| @@ -130,7 +130,7 @@ | |||
| transmittedReports.Remove(item); | |||
| StateHasChanged(); | |||
| Toaster.ShowSuccess(I18n.GetString("Success.DeleteReport.Title"), I18n.GetString("Success.DeleteReport.Msg", item.ID)); | |||
| } catch (Exception ex) { | |||
| } catch (Exception) { | |||
| Toaster.ShowWarning(I18n.GetString("Error.DeleteReport.Title"), I18n.GetString("Error.DeleteReport.Msg", item.ID)); | |||
| } | |||
| } | |||
| @@ -231,7 +231,7 @@ | |||
| try { | |||
| Validator.ValidateMissingReportKeyData(ReportDataProvider.GetMissingReport()); | |||
| NavigationManager.NavigateTo("fundvelo/account/Missing"); | |||
| } catch (ArgumentException ex) { | |||
| } catch (ArgumentException) { | |||
| Toaster.ShowWarning(I18n.GetString("Warning.MandatoryFields.Title"), I18n.GetString("Warning.MandatoryFields.Msg")); | |||
| } | |||
| } | |||
| @@ -115,7 +115,7 @@ | |||
| pendingReports.Remove(item); | |||
| StateHasChanged(); | |||
| Toaster.ShowSuccess(I18n.GetString("Success.DeleteReport.Title"), I18n.GetString("Success.DeleteReport.Msg", item.ID)); | |||
| } catch (Exception ex) { | |||
| } catch (Exception) { | |||
| Toaster.ShowWarning(I18n.GetString("Error.DeleteReport.Title"), I18n.GetString("Error.DeleteReport.Msg", item.ID)); | |||
| } | |||
| } | |||
| @@ -26,6 +26,7 @@ namespace CaritasPWA { | |||
| builder.Services.AddSingleton<ReportDataProvider>(); | |||
| builder.Services.AddSingleton<OnlineStatusProvider>(); | |||
| builder.Services.AddSingleton<ScrollLockRemover>(); | |||
| builder.Services.AddSingleton<InputCursorHandler>(); | |||
| builder.Services.AddScoped<ReportRepositoryService>(); | |||
| builder.Services.AddScoped<Toaster>(); | |||
| builder.Services.AddScoped<UserDataProvider>(); | |||
| @@ -29,6 +29,7 @@ namespace cwebplusApp.Shared.Models { | |||
| private void decodeDisplayCity(string displayCity) { | |||
| if (!String.IsNullOrEmpty(displayCity)) { | |||
| displayCity = displayCity.Trim(); | |||
| char[] delimiterChars = { '-', ' ' }; | |||
| string[] tokens = displayCity.Split(delimiterChars); | |||
| if (tokens.Length == 3) { | |||
| @@ -1,7 +1,8 @@ | |||
| using cwebplusApp.Shared.Models; | |||
| using FisSst.BlazorMaps; | |||
| using Newtonsoft.Json; | |||
| //using Json.Net; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Globalization; | |||
| using System.Net.Http; | |||
| using System.Threading.Tasks; | |||
| @@ -28,5 +29,30 @@ namespace cwebplusApp.Shared.Services { | |||
| return null; | |||
| } | |||
| } | |||
| public static async ValueTask<LatLng> GetCoordinatesForAddress(string addressZipCity) { | |||
| HttpClient httpClient = new() { BaseAddress = new Uri("https://nominatim.openstreetmap.org/") }; | |||
| try { | |||
| HttpResponseMessage httpResult = await httpClient.GetAsync(string.Format("search?q={0}&format=json", addressZipCity)); | |||
| if (httpResult.StatusCode == System.Net.HttpStatusCode.OK) { | |||
| string contentStr = await httpResult.Content.ReadAsStringAsync(); | |||
| var settings = new JsonSerializerSettings(); | |||
| settings.NullValueHandling = NullValueHandling.Ignore; | |||
| List<NominatimCoordinates> coordinatesDtos = JsonConvert.DeserializeObject<List<NominatimCoordinates>>(contentStr, settings); | |||
| return new LatLng(coordinatesDtos[0].lat, coordinatesDtos[0].lon); | |||
| } | |||
| return null; | |||
| } catch (Exception) { | |||
| return null; | |||
| } | |||
| } | |||
| } | |||
| public class NominatimCoordinates { | |||
| public double lat; | |||
| public double lon; | |||
| } | |||
| } | |||
| @@ -0,0 +1,39 @@ | |||
| using Microsoft.AspNetCore.Components.Web; | |||
| using Microsoft.JSInterop; | |||
| using System; | |||
| using System.Threading.Tasks; | |||
| namespace cwebplusApp.Shared.Services { | |||
| public class InputCursorHandler { | |||
| private readonly IJSRuntime jsRuntime; | |||
| public InputCursorHandler(IJSRuntime _jsRuntime) { | |||
| this.jsRuntime = _jsRuntime; | |||
| } | |||
| public async Task OnKeyPressHandlerAsync(KeyboardEventArgs e) { | |||
| Console.WriteLine("Key pressed: " + e.Key); | |||
| var reference = DotNetObjectReference.Create(this); | |||
| if (e.Key.Equals("Enter") || e.Key.Equals("ArrowRight")) { | |||
| await jsRuntime.InvokeVoidAsync("MoveCursorToNextInput", reference, e.Key); | |||
| //await MoveInputCursor(true); | |||
| } else if (e.Key.Equals("ArrowLeft")) { | |||
| await jsRuntime.InvokeVoidAsync("MoveCursorToPreviousInput", reference); | |||
| //await MoveInputCursor(false); | |||
| } | |||
| } | |||
| //public async Task MoveInputCursor(bool forward) { | |||
| // var reference = DotNetObjectReference.Create(this); | |||
| // if (forward) { | |||
| // await jsRuntime.InvokeVoidAsync("MoveCursorToNextInput", reference); | |||
| // } else { | |||
| // await jsRuntime.InvokeVoidAsync("MoveCursorToPreviousInput", reference); | |||
| // } | |||
| //} | |||
| } | |||
| } | |||
| @@ -11,7 +11,7 @@ namespace cwebplusApp.Shared.Services { | |||
| private readonly IJSRuntime jsRuntime; | |||
| public ScrollLockRemover(IJSRuntime _jsRuntime) { | |||
| jsRuntime = _jsRuntime; | |||
| this.jsRuntime = _jsRuntime; | |||
| } | |||
| public async Task removeScrollLockAsync() { | |||
| @@ -68,6 +68,32 @@ | |||
| function RemoveScrollLock(dotNetObjRef) { | |||
| document.querySelector("body.mdc-dialog-scroll-lock")?.classList.remove("mdc-dialog-scroll-lock"); | |||
| } | |||
| function MoveCursorToNextInput(dotNetObjRef, key) { | |||
| if (key == "Enter" || key == "ArrowRight") { | |||
| var activeInput = document.activeElement; | |||
| var inputs = document.getElementsByTagName("input"); | |||
| var arr = Array.prototype.slice.call(inputs) | |||
| var index = arr.indexOf(activeInput); | |||
| if (index + 1 < arr.length && (key == "Enter" || activeInput.selectionStart == activeInput.value.length)) { | |||
| arr[index + 1].focus(); | |||
| setTimeout(function () { arr[index + 1].select(); }, 50); | |||
| } | |||
| } | |||
| } | |||
| function MoveCursorToPreviousInput(dotNetObjRef) { | |||
| var activeInput = document.activeElement; | |||
| var inputs = document.getElementsByTagName("input"); | |||
| var arr = Array.prototype.slice.call(inputs) | |||
| var index = arr.indexOf(activeInput); | |||
| if (index - 1 >= 0 && activeInput.selectionStart == 0) { | |||
| arr[index - 1].focus(); | |||
| setTimeout(function () { arr[index - 1].select(); }, 50); | |||
| } | |||
| } | |||
| </script> | |||
| <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" | |||