From 697367dd54f24b99d6dfcb891fea9053d06aaa25 Mon Sep 17 00:00:00 2001 From: Marc Wieland Date: Tue, 4 Nov 2025 23:29:21 +0100 Subject: [PATCH] Erstes Formular + QR Integration --- FilterCair.Client/App.razor | 7 +- FilterCair.Client/Pages/Authentication.razor | 9 ++ FilterCair.Client/Pages/FilterForm.razor | 91 ++++++++++++++++++++ FilterCair.Client/Pages/Home.razor | 3 +- FilterCair.Client/Pages/QRScanner.razor | 53 ++++++++++++ FilterCair.Client/Program.cs | 9 +- FilterCair.Client/wwwroot/appsettings.json | 15 +++- FilterCair.Client/wwwroot/index.html | 1 + FilterCair.Client/wwwroot/js/filtercair.js | 68 ++++++++++++++- FilterCair.Shared/Class1.cs | 6 -- FilterCair.Shared/Models/FilterModel.cs | 22 +++++ FilterCair.sln | 3 + 12 files changed, 266 insertions(+), 21 deletions(-) create mode 100644 FilterCair.Client/Pages/Authentication.razor create mode 100644 FilterCair.Client/Pages/FilterForm.razor create mode 100644 FilterCair.Client/Pages/QRScanner.razor delete mode 100644 FilterCair.Shared/Class1.cs create mode 100644 FilterCair.Shared/Models/FilterModel.cs diff --git a/FilterCair.Client/App.razor b/FilterCair.Client/App.razor index 3e79996..93d3013 100644 --- a/FilterCair.Client/App.razor +++ b/FilterCair.Client/App.razor @@ -4,14 +4,13 @@ - + - Not found -

Sorry, there's nothing at this address.

+

Seite nicht gefunden.

-
\ No newline at end of file + diff --git a/FilterCair.Client/Pages/Authentication.razor b/FilterCair.Client/Pages/Authentication.razor new file mode 100644 index 0000000..8e91b09 --- /dev/null +++ b/FilterCair.Client/Pages/Authentication.razor @@ -0,0 +1,9 @@ +@page "/authentication/{action}" +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication + + + +@code { + [Parameter] + public string? Action { get; set; } +} diff --git a/FilterCair.Client/Pages/FilterForm.razor b/FilterCair.Client/Pages/FilterForm.razor new file mode 100644 index 0000000..6e7e024 --- /dev/null +++ b/FilterCair.Client/Pages/FilterForm.razor @@ -0,0 +1,91 @@ +@page "/filterform" +@inject IJSRuntime JS + +@using FilterCair.Shared.Models + +Filterdaten erfassen + +
+

Filterdaten erfassen

+ + + + + +
+ + +
+ +
+ + + + @foreach (var h in halls) + { + + } + +
+ +
+ + + + + + + +
+ +
+ + +
+ +
+ + +
+ + +
+ + @if (saved) + { +
+ Daten gespeichert! +
+ } +
+ +@code { + private FilterModel filter = new(); + private bool saved = false; + private List halls = new() { "Halle A", "Halle B", "Halle C" }; + + private async Task SaveForm() + { + saved = true; + Console.WriteLine($"Gespeichert: {System.Text.Json.JsonSerializer.Serialize(filter)}"); + + // später → JS.InvokeVoidAsync("IndexedDB.saveFilter", filter); + await JS.InvokeVoidAsync("console.log", "Filter gespeichert:", filter); + } + + // optional: QR-Ergebnis vorbelegen + protected override void OnInitialized() + { + var qrParam = NavManager?.ToAbsoluteUri(NavManager.Uri).Query; + if (!string.IsNullOrEmpty(qrParam)) + { + // z. B. ?id=FC-123 + var parts = System.Web.HttpUtility.ParseQueryString(qrParam); + filter.FilterId = parts.Get("id"); + } + } + + [Inject] NavigationManager? NavManager { get; set; } +} diff --git a/FilterCair.Client/Pages/Home.razor b/FilterCair.Client/Pages/Home.razor index 7dfb3dd..7e1cbe0 100644 --- a/FilterCair.Client/Pages/Home.razor +++ b/FilterCair.Client/Pages/Home.razor @@ -110,7 +110,8 @@ private void Login() { - Nav.NavigateTo("authentication/login"); + Nav.NavigateTo("authentication/login", forceLoad: true); + } private void Logout() diff --git a/FilterCair.Client/Pages/QRScanner.razor b/FilterCair.Client/Pages/QRScanner.razor new file mode 100644 index 0000000..ee50adb --- /dev/null +++ b/FilterCair.Client/Pages/QRScanner.razor @@ -0,0 +1,53 @@ +@page "/qrscanner" +@inject IJSRuntime JS + +QR-Scanner + +
+

📷 QR-Code Scanner

+ +
+ +
+ + @if (!string.IsNullOrEmpty(result)) + { +
+ Erkannt: @result +
+ } + +
+ + +
+
+ +@code { + private string? result; + + private async Task StartScan() + { + await JS.InvokeVoidAsync("QRScanner.start"); + } + + private async Task StopScan() + { + await JS.InvokeVoidAsync("QRScanner.stop"); + } + + [JSInvokable] + public void OnQrDetected(string code) + { + result = code; + StateHasChanged(); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + await JS.InvokeVoidAsync("QRScanner.init", DotNetObjectReference.Create(this)); + } + } +} diff --git a/FilterCair.Client/Program.cs b/FilterCair.Client/Program.cs index f6f8633..f1afa9f 100644 --- a/FilterCair.Client/Program.cs +++ b/FilterCair.Client/Program.cs @@ -7,13 +7,12 @@ var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); builder.RootComponents.Add("head::after"); -var apiBase = builder.Configuration["Api:BaseUrl"]; - builder.Services.AddHttpClient("FilterCair.ServerAPI", client => - client.BaseAddress = new Uri(apiBase)) + client.BaseAddress = new Uri(builder.Configuration["Api:BaseUrl"] ?? "https://localhost:7010")) .AddHttpMessageHandler(); -builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(apiBase) }); +builder.Services.AddScoped(sp => + sp.GetRequiredService().CreateClient("FilterCair.ServerAPI")); builder.Services.AddMsalAuthentication(options => { @@ -22,4 +21,4 @@ builder.Services.AddMsalAuthentication(options => "api://54b010c7-4a9a-4f2d-bea3-9faba3f12495/API.Access"); }); -await builder.Build().RunAsync(); \ No newline at end of file +await builder.Build().RunAsync(); diff --git a/FilterCair.Client/wwwroot/appsettings.json b/FilterCair.Client/wwwroot/appsettings.json index c5fae1b..4101c2e 100644 --- a/FilterCair.Client/wwwroot/appsettings.json +++ b/FilterCair.Client/wwwroot/appsettings.json @@ -1,12 +1,19 @@ { - "ApiUrl": "https://filtercair-api.azurewebsites.net", "AzureAd": { "Authority": "https://login.microsoftonline.com/66c8d79d-dbbb-46ca-9b6d-3b942f463abe", "ClientId": "54b010c7-4a9a-4f2d-bea3-9faba3f12495", - "ValidateAuthority": true + "ValidateAuthority": true, + "RedirectUri": "https://filtercair-client-efava4bfgvamhkfu.westeurope-01.azurewebsites.net/authentication/login-callback", + "PostLogoutRedirectUri": "https://filtercair-client-efava4bfgvamhkfu.westeurope-01.azurewebsites.net/", + "CacheLocation": "localStorage", + "Scopes": [ + "openid", + "profile", + "email", + "api://54b010c7-4a9a-4f2d-bea3-9faba3f12495/API.Access" + ] }, "Api": { - "Scopes": [ "api://54b010c7-4a9a-4f2d-bea3-9faba3f12495/API.Access" ], - "BaseUrl": "https://localhost:7010" + "BaseUrl": "https://filtercair-api.azurewebsites.net" } } diff --git a/FilterCair.Client/wwwroot/index.html b/FilterCair.Client/wwwroot/index.html index 1af5f2b..1acde80 100644 --- a/FilterCair.Client/wwwroot/index.html +++ b/FilterCair.Client/wwwroot/index.html @@ -43,6 +43,7 @@ integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"> + diff --git a/FilterCair.Client/wwwroot/js/filtercair.js b/FilterCair.Client/wwwroot/js/filtercair.js index 9d858a9..a09e01c 100644 --- a/FilterCair.Client/wwwroot/js/filtercair.js +++ b/FilterCair.Client/wwwroot/js/filtercair.js @@ -19,4 +19,70 @@ ); }); } -}; \ No newline at end of file +}; + + + +window.QRScanner = { + video: null, + stream: null, + dotnetRef: null, + scanning: false, + + init: function (dotnetRef) { + this.dotnetRef = dotnetRef; + console.log("QRScanner initialized"); + }, + + start: async function () { + if (!this.video) { + this.video = document.getElementById("video"); + } + try { + this.stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } }); + this.video.srcObject = this.stream; + this.scanning = true; + this.scanLoop(); + } catch (err) { + alert("Kamera konnte nicht gestartet werden: " + err); + } + }, + + stop: function () { + if (this.stream) { + this.stream.getTracks().forEach(t => t.stop()); + } + this.scanning = false; + }, + + scanLoop: async function () { + if (!this.video) return; + const canvas = document.createElement("canvas"); + const context = canvas.getContext("2d"); + const jsQRAvailable = typeof jsQR !== 'undefined'; + + if (!jsQRAvailable) { + console.warn("jsQR not loaded yet."); + return; + } + + const tick = () => { + if (!this.scanning) return; + if (this.video.readyState === this.video.HAVE_ENOUGH_DATA) { + canvas.width = this.video.videoWidth; + canvas.height = this.video.videoHeight; + context.drawImage(this.video, 0, 0, canvas.width, canvas.height); + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + const code = jsQR(imageData.data, imageData.width, imageData.height); + if (code) { + console.log("QR erkannt:", code.data); + this.dotnetRef.invokeMethodAsync("OnQrDetected", code.data); + this.stop(); + return; + } + } + requestAnimationFrame(tick); + }; + requestAnimationFrame(tick); + } +}; diff --git a/FilterCair.Shared/Class1.cs b/FilterCair.Shared/Class1.cs deleted file mode 100644 index 1610083..0000000 --- a/FilterCair.Shared/Class1.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace FilterCair.Shared; - -public class Class1 -{ - -} diff --git a/FilterCair.Shared/Models/FilterModel.cs b/FilterCair.Shared/Models/FilterModel.cs new file mode 100644 index 0000000..686a039 --- /dev/null +++ b/FilterCair.Shared/Models/FilterModel.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FilterCair.Shared.Models +{ + public class FilterModel + { + [Required] + public string? FilterId { get; set; } + + [Required] + public string? Halle { get; set; } + + public string? Zustand { get; set; } + public double? Luftdruck { get; set; } + public string? Bemerkung { get; set; } + } +} diff --git a/FilterCair.sln b/FilterCair.sln index 12391ff..66ce82b 100644 --- a/FilterCair.sln +++ b/FilterCair.sln @@ -59,4 +59,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {57097AF9-FD61-420B-B0E9-520CF180D7FC} + EndGlobalSection EndGlobal