Allineamento da prod
This commit is contained in:
@ -6,14 +6,14 @@
|
||||
|
||||
<div class="page">
|
||||
<!-- NAV MENU MANAGEMENT -->
|
||||
<header class="navbar navbar-expand-md d-print-none" >
|
||||
<header class="navbar navbar-expand-md d-print-none">
|
||||
<div class="container-xl">
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar-menu" aria-controls="navbar-menu" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
|
||||
<a href="/">
|
||||
<img src="/Logo_dac.png" width="80" class="">
|
||||
<img src="/Logo_dac.png" width="80" class="">
|
||||
</a>
|
||||
</h1>
|
||||
<AuthorizeView>
|
||||
@ -32,7 +32,7 @@
|
||||
<a href="/account/logout" class="dropdown-item"> Logout </a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
</div>
|
||||
@ -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" : "";
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
@inherits LayoutComponentBase
|
||||
@using StandManager.Components.Pages.Management
|
||||
|
||||
<PublicHeader />
|
||||
@Body
|
||||
|
||||
@Body
|
||||
<script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.js"></script>
|
||||
<script src="/js/qrScanner.js"></script>
|
||||
@ -1,4 +1,5 @@
|
||||
@page "/"
|
||||
@page "/Home"
|
||||
@page "/{invitationId:guid}"
|
||||
@layout PublicLayout
|
||||
|
||||
@ -12,6 +13,40 @@
|
||||
|
||||
<PageTitle>Iscrizioni Evento</PageTitle>
|
||||
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<header class="navbar navbar-expand-lg navbar-transparent py-3">
|
||||
<div class="container">
|
||||
<div class="collapse navbar-collapse">
|
||||
<nav class="navbar-nav ms-auto">
|
||||
<div class="nav-item">
|
||||
<a class="nav-link" href="/management"><span class="nav-link-title">Management</span></a>
|
||||
</div>
|
||||
|
||||
<div class="nav-item">
|
||||
<a class="nav-link" href="/scan"><span class="nav-link-title">Scansione</span></a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</Authorized>
|
||||
|
||||
<NotAuthorized>
|
||||
<header class="navbar navbar-expand-lg navbar-transparent py-3">
|
||||
<div class="container">
|
||||
<div class="collapse navbar-collapse">
|
||||
<nav class="navbar-nav ms-auto">
|
||||
<div class="nav-item">
|
||||
<a class="nav-link" href="/management"><span class="nav-link-title">Management</span></a>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</NotAuthorized>
|
||||
</AuthorizeView>
|
||||
|
||||
<header class="hero pb-0">
|
||||
<div class="container">
|
||||
@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($"/");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
@attribute [Authorize]
|
||||
@page "/management/Eventi"
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using StandManager.Model
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject TooltipService tooltipService
|
||||
|
||||
<PageTitle>Eventi</PageTitle>
|
||||
|
||||
<div class="page-wrapper">
|
||||
@ -37,8 +40,9 @@
|
||||
|
||||
<RadzenDataGridColumn Context="evento" Filterable="false" Sortable="false" TextAlign="TextAlign.Right" Width="250px">
|
||||
<Template Context="evento">
|
||||
<RadzenButton Icon="edit" ButtonStyle="ButtonStyle.Light" Variant="Variant.Flat" Size="ButtonSize.Medium" class="rz-my-1 rz-ms-1" Click="@(args => EditRow(evento))" @onclick:stopPropagation="true" />
|
||||
<RadzenButton Icon="delete" ButtonStyle="ButtonStyle.Danger" Variant="Variant.Flat" Size="ButtonSize.Medium" Shade="Shade.Lighter" class="rz-my-1 rz-ms-1" Click="@(args => DeleteRow(evento))" @onclick:stopPropagation="true" />
|
||||
<RadzenButton Icon="calendar_clock" ButtonStyle="ButtonStyle.Light" Variant="Variant.Flat" Size="ButtonSize.Medium" class="rz-my-1 rz-ms-1" Click="@(args => SendInvitation(evento))" @onclick:stopPropagation="true" MouseEnter="@(args => ShowTooltip(args, new TooltipOptions() { Text = "Invia invito" }))" />
|
||||
<RadzenButton Icon="edit" ButtonStyle="ButtonStyle.Light" Variant="Variant.Flat" Size="ButtonSize.Medium" class="rz-my-1 rz-ms-1" Click="@(args => EditRow(evento))" @onclick:stopPropagation="true" MouseEnter="@(args => ShowTooltip(args, new TooltipOptions() { Text = "Modifica evento" }))" />
|
||||
<RadzenButton Icon="delete" ButtonStyle="ButtonStyle.Danger" Variant="Variant.Flat" Size="ButtonSize.Medium" Shade="Shade.Lighter" class="rz-my-1 rz-ms-1" Click="@(args => DeleteRow(evento))" @onclick:stopPropagation="true" MouseEnter="@(args => ShowTooltip(args, new TooltipOptions() { Text = "Rimuovi evento" }))" />
|
||||
</Template>
|
||||
</RadzenDataGridColumn>
|
||||
</Columns>
|
||||
@ -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<Eventi_Inviti>("Inviti", new Dictionary<string, object>() { { "eventoId", evento.Id } });
|
||||
}
|
||||
}
|
||||
|
||||
void ShowTooltip(ElementReference elementReference, TooltipOptions options = null) => tooltipService.Open(elementReference, options.Text, options);
|
||||
}
|
||||
46
StandManager/Components/Pages/Management/Eventi_Inviti.razor
Normal file
46
StandManager/Components/Pages/Management/Eventi_Inviti.razor
Normal file
@ -0,0 +1,46 @@
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
<RadzenStack Gap="1rem" class="rz-m-12">
|
||||
<RadzenProgressBar Value="@counter" Max="@clientiTotali" Unit="@counterLabel" AriaLabel="" />
|
||||
</RadzenStack>
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
@ -27,13 +27,13 @@
|
||||
<div class="col-lg-12">
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<RadzenDataGrid @ref="userGrid" AllowFiltering="true" AllowColumnResize="true" AllowAlternatingRows="false" FilterMode="FilterMode.CheckBoxList" AllowSorting="true" PageSize="5"
|
||||
<RadzenDataGrid @ref="userGrid" AllowFiltering="true" AllowColumnResize="true" AllowAlternatingRows="false" FilterMode="FilterMode.CheckBoxList" AllowSorting="true" PageSize="15"
|
||||
AllowPaging="true" PagerHorizontalAlign="HorizontalAlign.Left" ShowPagingSummary="true"
|
||||
Data="@utenti" ColumnWidth="300px" LogicalFilterOperator="LogicalFilterOperator.Or" SelectionMode="DataGridSelectionMode.Single">
|
||||
<Columns>
|
||||
<RadzenDataGridColumn Property="@nameof(Utente.Id)" Filterable="false" Title="ID" Width="80px" TextAlign="TextAlign.Center" />
|
||||
<RadzenDataGridColumn Property="@nameof(Utente.Nome)" Title="First Name" Width="160px" />
|
||||
<RadzenDataGridColumn Property="@nameof(Utente.Cognome)" Title="Last Name" Width="160px" />
|
||||
<RadzenDataGridColumn Property="@nameof(Utente.Nome)" Title="Nome" Width="160px" />
|
||||
<RadzenDataGridColumn Property="@nameof(Utente.Cognome)" Title="Cognome" Width="160px" />
|
||||
<RadzenDataGridColumn Property="@nameof(Utente.Email)" Title="Mail" Width="200px" />
|
||||
|
||||
<RadzenDataGridColumn Context="order" Filterable="false" Sortable="false" TextAlign="TextAlign.Right">
|
||||
|
||||
110
StandManager/Components/Pages/Scan.razor
Normal file
110
StandManager/Components/Pages/Scan.razor
Normal file
@ -0,0 +1,110 @@
|
||||
@page "/scan"
|
||||
@using StandManager.Components.Layout
|
||||
@layout PublicLayout
|
||||
|
||||
@inject IJSRuntime JS
|
||||
@inject BodyClassService BodyClass
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Scan</PageTitle>
|
||||
|
||||
<header class="hero pb-0">
|
||||
<div class="container">
|
||||
<div class="mb-3">
|
||||
<video @ref="videoRef" autoplay playsinline style="width:100%;max-width:400px;border:1px solid #ccc;border-radius:8px;"></video>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" @onclick="StartScan" disabled="@isScanning">
|
||||
Avvia scansione
|
||||
</button>
|
||||
|
||||
<button class="btn btn-secondary" @onclick="StopScan" disabled="@(!isScanning)">
|
||||
Ferma
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(lastResult))
|
||||
{
|
||||
<div class="alert alert-success">
|
||||
<strong>QR letto:</strong> @lastResult
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(errorMessage))
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
<strong>Errore:</strong> @errorMessage
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private ElementReference videoRef;
|
||||
private DotNetObjectReference<Scan>? 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");
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,21 @@
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
|
||||
<Router AppAssembly="@typeof(Program).Assembly">
|
||||
<Found Context="routeData">
|
||||
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)">
|
||||
<NotAuthorized>
|
||||
<RedirectToLogin />
|
||||
</NotAuthorized>
|
||||
</AuthorizeRouteView>
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||
</Found>
|
||||
</Router>
|
||||
<CascadingAuthenticationState>
|
||||
<Router AppAssembly="@typeof(Program).Assembly">
|
||||
<Found Context="routeData">
|
||||
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)">
|
||||
<NotAuthorized>
|
||||
<RedirectToLogin />
|
||||
</NotAuthorized>
|
||||
</AuthorizeRouteView>
|
||||
|
||||
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
|
||||
</Found>
|
||||
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(Layout.MainLayout)">
|
||||
<p>Pagina non trovata.</p>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
</CascadingAuthenticationState>
|
||||
78
StandManager/wwwroot/js/qrScanner.js
Normal file
78
StandManager/wwwroot/js/qrScanner.js
Normal file
@ -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
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user