This commit is contained in:
42
BimAI.UI.Shared/Components/AuthGuard.razor
Normal file
42
BimAI.UI.Shared/Components/AuthGuard.razor
Normal file
@@ -0,0 +1,42 @@
|
||||
@using BimAI.UI.Shared.Services
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<div class="d-flex justify-center align-center" style="height: 100vh;">
|
||||
<MudProgressCircular Indeterminate="true" Size="Size.Large" />
|
||||
</div>
|
||||
}
|
||||
else if (_isAuthenticated)
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
private bool _isLoading = true;
|
||||
private bool _isAuthenticated = false;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
Console.WriteLine("AuthGuard: Checking authentication...");
|
||||
|
||||
_isAuthenticated = await AuthService.CheckAuthenticationAsync();
|
||||
_isLoading = false;
|
||||
|
||||
Console.WriteLine($"AuthGuard: isAuthenticated={_isAuthenticated}");
|
||||
|
||||
if (!_isAuthenticated)
|
||||
{
|
||||
Console.WriteLine("AuthGuard: Redirecting to /login");
|
||||
Navigation.NavigateTo("/login", replace: true);
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
5
BimAI.UI.Shared/Components/Dashboard.razor
Normal file
5
BimAI.UI.Shared/Components/Dashboard.razor
Normal file
@@ -0,0 +1,5 @@
|
||||
@page "/dashboard"
|
||||
@using MudBlazor
|
||||
|
||||
<MudText Typo="Typo.h4">Dashboard</MudText>
|
||||
<p>Tutaj znajdzie się panel ogólny aplikacji</p>
|
||||
15
BimAI.UI.Shared/Components/Index.razor
Normal file
15
BimAI.UI.Shared/Components/Index.razor
Normal file
@@ -0,0 +1,15 @@
|
||||
@page "/"
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@code
|
||||
{
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
Navigation.NavigateTo("/dashboard");
|
||||
}
|
||||
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
}
|
||||
142
BimAI.UI.Shared/Components/LoginCard.razor
Normal file
142
BimAI.UI.Shared/Components/LoginCard.razor
Normal file
@@ -0,0 +1,142 @@
|
||||
|
||||
@using BimAI.UI.Shared.Services
|
||||
@using Microsoft.Extensions.Configuration
|
||||
@inject IJSRuntime JS
|
||||
@inject IConfiguration Configuration
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<MudCard Class="login-card" Elevation="8">
|
||||
<MudCardContent Class="pa-8 d-flex flex-column align-center">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">Witaj w BimAI</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mb-6 text-center">
|
||||
Zaloguj się używając konta Google
|
||||
</MudText>
|
||||
|
||||
<MudButton
|
||||
Variant="Variant.Filled"
|
||||
StartIcon="@Icons.Custom.Brands.Google"
|
||||
Size="Size.Large"
|
||||
OnClick="HandleGoogleSignIn"
|
||||
Disabled="@_isLoading">
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudProgressCircular Class="mr-3" Size="Size.Small" Indeterminate="true"></MudProgressCircular>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>Zaloguj z Google</span>
|
||||
}
|
||||
</MudButton>
|
||||
|
||||
@if (!string.IsNullOrEmpty(_errorMessage))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Class="mt-4">
|
||||
@_errorMessage
|
||||
</MudAlert>
|
||||
}
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
@code {
|
||||
private bool _isLoading = false;
|
||||
private string _errorMessage = string.Empty;
|
||||
private static LoginCard? _instance;
|
||||
private bool _isInitialized = false;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
_instance = this;
|
||||
await InitializeGoogleSignIn();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InitializeGoogleSignIn()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
|
||||
var clientId = Configuration["GoogleAuth:ClientId"];
|
||||
if (string.IsNullOrEmpty(clientId))
|
||||
{
|
||||
throw new Exception("Google ClientId is not configured.");
|
||||
}
|
||||
|
||||
await JS.InvokeVoidAsync("initGoogleSignIn", clientId);
|
||||
_isInitialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_errorMessage = "Błąd inicjalizacji Google Sign-In.";
|
||||
Console.Error.WriteLine($"Google Sign-In initialization error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleGoogleSignIn()
|
||||
{
|
||||
try
|
||||
{
|
||||
_isLoading = true;
|
||||
_errorMessage = string.Empty;
|
||||
StateHasChanged();
|
||||
|
||||
await JS.InvokeVoidAsync("requestGoogleSignIn");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_errorMessage = "Błąd podczas logowania. Spróbuj ponownie";
|
||||
_isLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public static async Task OnGoogleSignInSuccess(string accessToken, string fullName, string email, string avatarUrl)
|
||||
{
|
||||
Console.WriteLine($"Google Sign-In Success: {email}");
|
||||
|
||||
if (_instance != null)
|
||||
{
|
||||
var userInfo = new UserInfo
|
||||
{
|
||||
FullName = fullName,
|
||||
Email = email,
|
||||
AvatarUrl = avatarUrl
|
||||
};
|
||||
|
||||
await _instance.AuthService.SetAuthenticationAsync(accessToken, userInfo);
|
||||
|
||||
_instance._isLoading = false;
|
||||
_instance._errorMessage = string.Empty;
|
||||
|
||||
_instance.NavigationManager.NavigateTo("/dashboard", replace: true);
|
||||
|
||||
await _instance.InvokeAsync(() => _instance.StateHasChanged());
|
||||
}
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public static async Task OnGoogleSignInError(string error)
|
||||
{
|
||||
Console.WriteLine($"Google SignIn Error: {error}");
|
||||
|
||||
if (_instance != null)
|
||||
{
|
||||
_instance._isLoading = false;
|
||||
_instance._errorMessage = "Błąd logowania Google. Spróbuj ponownie";
|
||||
await _instance.InvokeAsync(() => _instance.StateHasChanged());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<style>
|
||||
.login-card {
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
</style>
|
||||
10
BimAI.UI.Shared/Components/NavMenu.razor
Normal file
10
BimAI.UI.Shared/Components/NavMenu.razor
Normal file
@@ -0,0 +1,10 @@
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using MudBlazor
|
||||
<MudNavMenu>
|
||||
<MudNavLink href="dashboard" Icon="@Icons.Material.Filled.Dashboard" Match="NavLinkMatch.All">
|
||||
Dashboard
|
||||
</MudNavLink>
|
||||
<MudNavLink Href="products" Icon="@Icons.Material.Filled.List" Match="NavLinkMatch.All">
|
||||
Produkty
|
||||
</MudNavLink>
|
||||
</MudNavMenu>
|
||||
124
BimAI.UI.Shared/Components/ProductListComponent.razor
Normal file
124
BimAI.UI.Shared/Components/ProductListComponent.razor
Normal file
@@ -0,0 +1,124 @@
|
||||
@using MudBlazor.Internal
|
||||
<MudText Typo="Typo.h4" Class="mb-4">Lista Produktów</MudText>
|
||||
|
||||
<MudExpansionPanels Class="mb-4">
|
||||
<MudExpansionPanel Icon="@Icons.Material.Filled.FilterList"
|
||||
Text="Filtry"
|
||||
Expanded="true">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudTextField @bind-Value="filterRequest.Search"
|
||||
Label="Szukaj"
|
||||
Placeholder="Nazwa, Kod, EAN..."
|
||||
Immediate="true"
|
||||
DebounceInterval="500"
|
||||
OnDebounceIntervalElapsed="SearchProducts"
|
||||
Clearable="true"/>
|
||||
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudTextField @bind-Value="filterRequest.Name"
|
||||
Label="Nazwa produktu"
|
||||
Immediate="true"
|
||||
DebounceInterval="500"
|
||||
OnDebounceIntervalElapsed="SearchProducts"
|
||||
Clearable="true"/>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudTextField @bind-Value="filterRequest.Code"
|
||||
Label="Kod produktu"
|
||||
Immediate="true"
|
||||
DebounceInterval="500"
|
||||
OnDebounceIntervalElapsed="SearchProducts"
|
||||
Clearable="true"/>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<div style="display: flex; gap: 8px; align-items: flex-end;">
|
||||
<div style="flex: 1;">
|
||||
<MudTextField @bind-Value="filterRequest.Ean"
|
||||
Label="EAN"
|
||||
Immediate="true"
|
||||
DebounceInterval="500"
|
||||
OnDebounceIntervalElapsed="SearchProducts"
|
||||
Clearable="true"/>
|
||||
</div>
|
||||
@if (ScannerService.IsAvailable)
|
||||
{
|
||||
<MudIconButton Icon="@Icons.Material.Filled.CameraAlt"
|
||||
Color="Color.Primary"
|
||||
OnClick="OnScannerClick"
|
||||
Variant="Variant.Filled"
|
||||
Size="Size.Medium"
|
||||
Title="Skanuj kod EAN"/>
|
||||
}
|
||||
</div>
|
||||
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
OnClick="ClearFilters"
|
||||
StartIcon="Icons.Material.Filled.Clear">
|
||||
Wyczyść filtry
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
|
||||
<MudDivider Class="my-4"></MudDivider>
|
||||
|
||||
<MudTable Items="products.Items"
|
||||
Dense="true"
|
||||
Hover="true"
|
||||
Loading="isLoading"
|
||||
LoadingProgressColor="Color.Info">
|
||||
<HeaderContent>
|
||||
<MudTh>Nazwa</MudTh>
|
||||
<MudTh>Kod</MudTh>
|
||||
<MudTh>EAN</MudTh>
|
||||
<MudTh>Akcje</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Nazwa">@context.Name</MudTd>
|
||||
<MudTd DataLabel="Kod">@context.Code.Trim()</MudTd>
|
||||
<MudTd DataLabel="EAN"></MudTd>
|
||||
<MudTd DataLabel="Akcje">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||
Size="Size.Small"
|
||||
OnClick="() => EditProduct(context.Id)"/>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
Size="Size.Small"
|
||||
Color="Color.Error"
|
||||
OnClick="() => DeleteProduct(context.Id)"/>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
<NoRecordsContent>
|
||||
<MudText>Brak produktów do wyświetlenia</MudText>
|
||||
</NoRecordsContent>
|
||||
<LoadingContent>
|
||||
Ładowanie...
|
||||
</LoadingContent>
|
||||
</MudTable>
|
||||
|
||||
@if (products.TotalCount > 0)
|
||||
{
|
||||
<MudGrid Class="mt-4" AlignItems="Center.Center">
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudText Typo="Typo.body2">
|
||||
Wyniki @((products.Page - 1) * products.PageSize + 1) - @Math.Min(products.Page * products.PageSize, products.TotalCount)
|
||||
z @products.TotalCount
|
||||
</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" Class="d-flex justify-end">
|
||||
<MudPagination Count="products.TotalPages"
|
||||
Selected="products.Page"
|
||||
SelectedChanged="OnPageChanged"
|
||||
ShowFirstButton="true"
|
||||
ShowLastButton="true"/>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
}
|
||||
104
BimAI.UI.Shared/Components/ProductListComponent.razor.cs
Normal file
104
BimAI.UI.Shared/Components/ProductListComponent.razor.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using BimAI.UI.Shared.Interfaces;
|
||||
using BimAI.UI.Shared.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using BimAI.Application.DTOModels;
|
||||
using BimAI.Application.DTOModels.Common;
|
||||
using MudBlazor;
|
||||
|
||||
namespace BimAI.UI.Shared.Components;
|
||||
|
||||
public partial class ProductListComponent : ComponentBase
|
||||
{
|
||||
[Inject] private ProductService ProductService { get; set; } = default!;
|
||||
[Inject] private IScannerService ScannerService { get; set; } = default!;
|
||||
[Inject] private ISnackbar Snackbar { get; set; } = default!;
|
||||
|
||||
|
||||
private PagedResult<ProductDto> products = new();
|
||||
private ProductFilterRequest filterRequest = new();
|
||||
private bool isLoading = false;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadProducts();
|
||||
}
|
||||
|
||||
private async Task LoadProducts()
|
||||
{
|
||||
isLoading = true;
|
||||
|
||||
try
|
||||
{
|
||||
products = await ProductService.GetProductsAsync(filterRequest);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Loading products failed: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SearchProducts()
|
||||
{
|
||||
filterRequest.Page = 1;
|
||||
await LoadProducts();
|
||||
}
|
||||
|
||||
private async Task OnPageChanged(int page)
|
||||
{
|
||||
filterRequest.Page = page;
|
||||
await LoadProducts();
|
||||
}
|
||||
|
||||
private async Task ClearFilters()
|
||||
{
|
||||
filterRequest = new ProductFilterRequest();
|
||||
await LoadProducts();
|
||||
}
|
||||
|
||||
private async Task EditProduct(Guid productId)
|
||||
{
|
||||
// TODO
|
||||
Console.WriteLine($"Edytuj produkt: {productId}");
|
||||
}
|
||||
|
||||
private async Task DeleteProduct(Guid productId)
|
||||
{
|
||||
// TODO
|
||||
Console.WriteLine($"Usuń produkt: {productId}");
|
||||
}
|
||||
|
||||
private string GetScannerIcon()
|
||||
{
|
||||
return ScannerService.IsAvailable ? Icons.Material.Filled.CameraAlt : "";
|
||||
}
|
||||
|
||||
private async Task OnScannerClick()
|
||||
{
|
||||
if (!ScannerService.IsAvailable)
|
||||
{
|
||||
Snackbar.Add("Skaner nie jest dostępny na tej platformie", Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var scannedCode = await ScannerService.ScanBarcodeAsync();
|
||||
if (!string.IsNullOrEmpty(scannedCode))
|
||||
{
|
||||
filterRequest.Ean = scannedCode;
|
||||
await SearchProducts();
|
||||
Snackbar.Add($"Zeskanowano kod: {scannedCode}", Severity.Success);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Scanner error: {ex.Message}");
|
||||
Snackbar.Add("Błąd podczas skanowania", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
18
BimAI.UI.Shared/Components/Routes.razor
Normal file
18
BimAI.UI.Shared/Components/Routes.razor
Normal file
@@ -0,0 +1,18 @@
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using MudBlazor
|
||||
|
||||
<Router AppAssembly="@typeof(Routes).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<MudCard Elevation="0">
|
||||
<MudText Typo="Typo.h6">
|
||||
Strona nieznaleziona.
|
||||
</MudText>
|
||||
</MudCard>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
Reference in New Issue
Block a user