diff --git a/StandManager.Domain/Entita/InvitoEvento.cs b/StandManager.Domain/Entita/InvitoEvento.cs new file mode 100644 index 0000000..e4fe7d2 --- /dev/null +++ b/StandManager.Domain/Entita/InvitoEvento.cs @@ -0,0 +1,18 @@ +using StandManager.Domain.Entita.Base; +using System.ComponentModel.DataAnnotations.Schema; + +namespace StandManager.Domain.Entita; + +public class InvitoEvento : EntitaBase +{ + [ForeignKey(nameof(Evento))] + public Guid? EventoId { get; set; } + public Evento Evento{ get; set; } + + [ForeignKey(nameof(Cliente))] + public Guid? ClienteId { get; set; } + public Cliente Cliente { get; set; } + + [InverseProperty(nameof(IscrizioneEvento.InvitoEvento))] + public List IscrizioniEvento { get; set; } +} diff --git a/StandManager.Domain/Entita/IscrizioneEvento.cs b/StandManager.Domain/Entita/IscrizioneEvento.cs index 6fc3d18..2577b57 100644 --- a/StandManager.Domain/Entita/IscrizioneEvento.cs +++ b/StandManager.Domain/Entita/IscrizioneEvento.cs @@ -3,20 +3,6 @@ using System.ComponentModel.DataAnnotations.Schema; namespace StandManager.Domain.Entita; -public class InvitoEvento : EntitaBase -{ - [ForeignKey(nameof(Evento))] - public Guid? EventoId { get; set; } - public Evento Evento{ get; set; } - - [ForeignKey(nameof(Cliente))] - public Guid? ClienteId { get; set; } - public Cliente Cliente { get; set; } - - [InverseProperty(nameof(IscrizioneEvento.InvitoEvento))] - public List IscrizioniEvento { get; set; } -} - public class IscrizioneEvento : EntitaBase { [ForeignKey(nameof(Evento))] diff --git a/StandManager.Service/Interfaces/IIscrizioneEventoService.cs b/StandManager.Service/Interfaces/IIscrizioneEventoService.cs index 1e4c70a..0eb37d2 100644 --- a/StandManager.Service/Interfaces/IIscrizioneEventoService.cs +++ b/StandManager.Service/Interfaces/IIscrizioneEventoService.cs @@ -3,7 +3,6 @@ using StandManager.Domain.Entita; namespace StandManager.Service.Interfaces; -public interface IIscrizioneEventoService : ITService -{ +public interface IIscrizioneEventoService : ITService{ Task Salva(IscrizioneEvento model); } diff --git a/StandManager/Components/Layout/MainLayout.razor b/StandManager/Components/Layout/MainLayout.razor index a089893..1bb02ec 100644 --- a/StandManager/Components/Layout/MainLayout.razor +++ b/StandManager/Components/Layout/MainLayout.razor @@ -6,14 +6,14 @@
-
- + @@ -109,7 +109,7 @@ if (currentClean.Contains("?")) currentClean = currentClean.Substring(0, currentClean.IndexOf("?")); - var isActive = (currentClean.Contains(hrefClean, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(hrefClean) + var isActive = (currentClean.Contains(hrefClean, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(hrefClean) || (string.IsNullOrEmpty(hrefClean) && string.IsNullOrEmpty(currentClean))); return isActive ? "active" : ""; diff --git a/StandManager/Components/Layout/PublicLayout.razor b/StandManager/Components/Layout/PublicLayout.razor index afc0be9..ea596d5 100644 --- a/StandManager/Components/Layout/PublicLayout.razor +++ b/StandManager/Components/Layout/PublicLayout.razor @@ -1,6 +1,7 @@ @inherits LayoutComponentBase @using StandManager.Components.Pages.Management - +@Body -@Body \ No newline at end of file + + \ No newline at end of file diff --git a/StandManager/Components/Pages/Home.razor b/StandManager/Components/Pages/Home.razor index f6d246a..25a8a26 100644 --- a/StandManager/Components/Pages/Home.razor +++ b/StandManager/Components/Pages/Home.razor @@ -1,4 +1,5 @@ @page "/" +@page "/Home" @page "/{invitationId:guid}" @layout PublicLayout @@ -12,6 +13,40 @@ Iscrizioni Evento + + + + + + + + + +
@if (invitationId.HasValue && invito != null) @@ -128,13 +163,12 @@ protected override async Task OnInitializedAsync() { base.OnInitializedAsync(); - invito = invitationId.GetValueOrDefault() != Guid.Empty ? await _managerService.InvitoEventoService.RicercaPer(x => x.Id == invitationId && x.Eliminato == false, includi: x => x.Include(y => y.Evento).Include(y => y.Cliente).ThenInclude(y => y.Destinazioni).Include(y => y.IscrizioniEvento)) : new(); - if(invito == null) + if (invito == null) invalidCode = "Il codice inserito non risulta corretto"; else { @@ -177,7 +211,6 @@ await _managerService.IscrizioneEventoService.Salva(model); _navManager.NavigateTo($"/"); - } } \ No newline at end of file diff --git a/StandManager/Components/Pages/Management/Cliente_Destinazione.razor b/StandManager/Components/Pages/Management/Cliente_Destinazione.razor index eb41c79..9707047 100644 --- a/StandManager/Components/Pages/Management/Cliente_Destinazione.razor +++ b/StandManager/Components/Pages/Management/Cliente_Destinazione.razor @@ -106,13 +106,13 @@ private async Task onDestinazioneSave() { var idClaim = await MembershipUtils.GetUserId(_auth); - + var model = await _managerService.DestinazioneService.RicercaPer(x => x.Id == destinazione.Id, solaLettura: false) ?? new Destinazione() { ClienteId = clienteId }; model = destinazione.Map(model); if (destinazione.AgenteId.GetValueOrDefault() != Guid.Empty) - model.AgenteId = destinazione.AgenteId; + model.Agente = await _managerService.UtenteService.RicercaPer(x => x.Id == destinazione.AgenteId); await _managerService.DestinazioneService.Salva(model, idClaim); _dialogService.Close(true); diff --git a/StandManager/Components/Pages/Management/Eventi.razor b/StandManager/Components/Pages/Management/Eventi.razor index 73a6423..1cfb7ef 100644 --- a/StandManager/Components/Pages/Management/Eventi.razor +++ b/StandManager/Components/Pages/Management/Eventi.razor @@ -1,9 +1,12 @@ @attribute [Authorize] @page "/management/Eventi" +@using Microsoft.EntityFrameworkCore @using StandManager.Model @rendermode InteractiveServer +@inject TooltipService tooltipService + Eventi
@@ -37,8 +40,9 @@ @@ -77,4 +81,18 @@ eventi = (await _managerService.EventoService.RicercaQueryable(x => x.Eliminato == false)).Select(x => (EventoViewModel)x).ToList(); } } + + private async Task SendInvitation(EventoViewModel evento) + { + var ok = await _dialogService.Confirm($"Vuoi davvero invitare i clienti all'evento {evento.Titolo}?", "Conferma invito", new ConfirmOptions { OkButtonText = "Sì", CancelButtonText = "No", Width = "400px" }); + + if (ok == true) + { + _dialogService.Close(); + //Invito + await _dialogService.OpenAsync("Inviti", new Dictionary() { { "eventoId", evento.Id } }); + } + } + + void ShowTooltip(ElementReference elementReference, TooltipOptions options = null) => tooltipService.Open(elementReference, options.Text, options); } \ No newline at end of file diff --git a/StandManager/Components/Pages/Management/Eventi_Inviti.razor b/StandManager/Components/Pages/Management/Eventi_Inviti.razor new file mode 100644 index 0000000..a364fba --- /dev/null +++ b/StandManager/Components/Pages/Management/Eventi_Inviti.razor @@ -0,0 +1,46 @@ +@using Microsoft.EntityFrameworkCore + + + + + +@code { + [CascadingParameter] private Dialog _dialog { get; set; } + [Parameter] public Guid eventoId { get; set; } = Guid.Empty; + + private int clientiTotali { get; set; } = 0; + private int counter { get; set; } = 0; + private string counterLabel{ get; set; } = string.Empty; + + protected override async Task OnInitializedAsync() + { + base.OnInitializedAsync(); + + var evento = await _managerService.EventoService.RicercaPer(x => x.Eliminato == false && x.Id == eventoId); + var clienti = await _managerService.ClienteService.RicercaQueryable(x => x.Eliminato == false); + var invitati = await _managerService.InvitoEventoService.RicercaQueryable(x => x.Eliminato == false && x.EventoId == eventoId, includi: y => y.Include(z => z.Cliente)); + + var clientiInvitatiIds = invitati.Select(x => x.ClienteId).ToList(); + var userId = await MembershipUtils.GetUserId(_auth); + + counter = 0; + clientiTotali = clienti.Count(c => !clientiInvitatiIds.Contains(c.Id)); + + foreach (var c in clienti.Where(c => !clientiInvitatiIds.Contains(c.Id))) + { + var invito = new InvitoEvento + { + ClienteId = c.Id, + EventoId = evento.Id, + Eliminato = false + }; + await _managerService.InvitoEventoService.Salva(invito, userId); + counter += 1; + counterLabel = " di " + clientiTotali; + StateHasChanged(); + await Task.Delay(10); // Simula un ritardo per l'invio dell'email + } + + _dialogService.Close(); + } +} diff --git a/StandManager/Components/Pages/Management/Utenti.razor b/StandManager/Components/Pages/Management/Utenti.razor index b8e6ede..2757ba2 100644 --- a/StandManager/Components/Pages/Management/Utenti.razor +++ b/StandManager/Components/Pages/Management/Utenti.razor @@ -27,13 +27,13 @@
- - - + + diff --git a/StandManager/Components/Pages/Scan.razor b/StandManager/Components/Pages/Scan.razor new file mode 100644 index 0000000..ea2f585 --- /dev/null +++ b/StandManager/Components/Pages/Scan.razor @@ -0,0 +1,110 @@ +@page "/scan" +@using StandManager.Components.Layout +@layout PublicLayout + +@inject IJSRuntime JS +@inject BodyClassService BodyClass + +@rendermode InteractiveServer + +Scan + +
+
+
+ +
+ + + + + +
+
+ + +@if (!string.IsNullOrWhiteSpace(lastResult)) +{ +
+ QR letto: @lastResult +
+} + +@if (!string.IsNullOrWhiteSpace(errorMessage)) +{ +
+ Errore: @errorMessage +
+} + +@code { + private ElementReference videoRef; + private DotNetObjectReference? objRef; + private bool isScanning; + private string? lastResult; + private string? errorMessage; + + protected override void OnInitialized() + { + objRef = DotNetObjectReference.Create(this); + } + + private async Task StartScan() + { + errorMessage = null; + lastResult = null; + + if (isScanning) + return; + + isScanning = true; + await JS.InvokeVoidAsync("qrScanner.start", videoRef, objRef); + } + + private async Task StopScan() + { + if (!isScanning) + return; + + isScanning = false; + await JS.InvokeVoidAsync("qrScanner.stop", videoRef); + } + + [JSInvokable] + public Task OnQrDecoded(string text) + { + lastResult = text; + isScanning = false; + StateHasChanged(); + return Task.CompletedTask; + } + + [JSInvokable] + public Task OnQrError(string message) + { + errorMessage = message; + isScanning = false; + StateHasChanged(); + return Task.CompletedTask; + } + + public async ValueTask DisposeAsync() + { + if (isScanning) + { + await StopScan(); + } + + objRef?.Dispose(); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + await BodyClass.SetBodyClass("body-marketing body-gradient"); + } +} \ No newline at end of file diff --git a/StandManager/Components/Routes.razor b/StandManager/Components/Routes.razor index 26da4c1..37b83eb 100644 --- a/StandManager/Components/Routes.razor +++ b/StandManager/Components/Routes.razor @@ -1,12 +1,21 @@ @using Microsoft.AspNetCore.Components.Authorization - - - - - - - - - - + + + + + + + + + + + + + + +

Pagina non trovata.

+
+
+
+
\ No newline at end of file diff --git a/StandManager/wwwroot/js/qrScanner.js b/StandManager/wwwroot/js/qrScanner.js new file mode 100644 index 0000000..d0d01d9 --- /dev/null +++ b/StandManager/wwwroot/js/qrScanner.js @@ -0,0 +1,78 @@ +window.qrScanner = (function () { + let animationFrameId = null; + let currentStream = null; + + async function start(videoElement, dotNetRef) { + try { + // chiede accesso alla camera (di solito quella posteriore su mobile) + const constraints = { + video: { + facingMode: "environment" + } + }; + + currentStream = await navigator.mediaDevices.getUserMedia(constraints); + videoElement.srcObject = currentStream; + await videoElement.play(); + + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + + const tick = async () => { + if (!videoElement || videoElement.readyState !== videoElement.HAVE_ENOUGH_DATA) { + animationFrameId = requestAnimationFrame(tick); + return; + } + + canvas.width = videoElement.videoWidth; + canvas.height = videoElement.videoHeight; + + ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height); + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + + const code = jsQR(imageData.data, canvas.width, canvas.height, { + inversionAttempts: "dontInvert" + }); + + if (code) { + console.log("QR trovato:", code.data); + // Chiama il metodo C# marcato [JSInvokable] + await dotNetRef.invokeMethodAsync("OnQrDecoded", code.data); + + stop(videoElement); // ferma appena trovato + return; + } + + animationFrameId = requestAnimationFrame(tick); + }; + + tick(); + } catch (err) { + console.error("Errore avvio camera:", err); + if (dotNetRef) { + await dotNetRef.invokeMethodAsync("OnQrError", err.message ?? "Errore sconosciuto"); + } + } + } + + function stop(videoElement) { + if (animationFrameId) { + cancelAnimationFrame(animationFrameId); + animationFrameId = null; + } + + if (currentStream) { + currentStream.getTracks().forEach(t => t.stop()); + currentStream = null; + } + + if (videoElement) { + videoElement.srcObject = null; + } + } + + return { + start: start, + stop: stop + }; +})(); \ No newline at end of file