diff --git a/CulinaryCommandApp/Components/App.razor b/CulinaryCommandApp/Components/App.razor index 427815d..88e8715 100644 --- a/CulinaryCommandApp/Components/App.razor +++ b/CulinaryCommandApp/Components/App.razor @@ -8,9 +8,6 @@ - - - @@ -34,16 +31,6 @@ crossorigin="anonymous"> - - - - - - - - - - diff --git a/CulinaryCommandApp/Components/Pages/Dashboard.razor b/CulinaryCommandApp/Components/Pages/Dashboard.razor index 9e8e0f0..a50c2ca 100644 --- a/CulinaryCommandApp/Components/Pages/Dashboard.razor +++ b/CulinaryCommandApp/Components/Pages/Dashboard.razor @@ -6,20 +6,10 @@ @using CulinaryCommandApp.AIDashboard.Services.Reporting @using CulinaryCommandApp.AIDashboard.Services.DTOs @using CulinaryCommand.Services -@using CulinaryCommandApp.Inventory.Services.Interfaces -@using CulinaryCommandApp.Inventory.Services -@using CulinaryCommandApp.Inventory.DTOs -@using CulinaryCommand.Data -@using CulinaryCommand.PurchaseOrder.Entities -@using Microsoft.EntityFrameworkCore @inject Microsoft.AspNetCore.Hosting.IWebHostEnvironment Env -@inject AppDbContext Db @inject NavigationManager Nav @inject AIReportingService ReportingService @inject IUserContextService UserCtx -@inject IInventoryManagementService InventorySvc -@inject ITaskAssignmentService TaskSvc -@inject LocationState LocationState @if (!_ready) { @@ -28,7 +18,7 @@ else if (!_isSignedIn) {
- You're not signed in. + You’re not signed in.
} @@ -50,148 +40,6 @@ else
- -
- - -
-
-
Low Stock Items (@_lowStockItems.Count)
-
- @if (_lowStockItems.Any()) - { -
- - - - - - - - - - - - - @foreach (var item in _lowStockItems) - { - - - - - - - - - } - -
IngredientCategoryIn StockReorder LevelUnitVendor
@item.Name@item.Category@item.CurrentQuantity.ToString("0.##")@item.ReorderLevel.ToString("0.##")@item.Unit@(item.VendorName ?? "—")
-
- } - else - { -

All inventory items are sufficiently stocked.

- } -
-
-
- - -
-
-
Task Status Breakdown
-
- @if (_taskData.Any()) - { - - } - else - { -

No tasks for this location.

- } -
-
-
-
- - -
-
-
-
Tasks Completed per Employee
-
- @if (_employeeTaskCounts.Any()) - { - - - - - - - - - @foreach (var e in _employeeTaskCounts) - { - - - - - } - -
EmployeeCompleted
@e.Name - @e.Count -
- } - else - { -

No completed tasks yet.

- } -
-
-
-
- - - -
-
- Recent Activity - Last 7 days -
-
- @if (!_activityFeed.Any()) - { -

No recent activity.

- } - else - { - - } -
-
- -
- -
Weekly Report Analysis
@@ -292,55 +140,21 @@ else } @code { - // ── Auth ───────────────────────────────────────────────────────── private bool _ready; private bool _isSignedIn; private string? _role; - private int? _locationId; - // ── AI ─────────────────────────────────────────────────────────── private bool _aiLoading; private string? _aiAnalysis; private AIAnalysisResultDTO? _aiAnalysisObj; private bool _aiLoadedOnce; - // ── Inventory ──────────────────────────────────────────────────── - private List _inventoryData = new(); - private List _lowStockItems = new(); - - // ── Activity feed ──────────────────────────────────────────────── - private record ActivityItem(string Icon, string BadgeClass, string Text, string? Who, DateTime When); - private List _activityFeed = new(); - - // ── Task chart ─────────────────────────────────────────────────── - private List _taskData = new(); - private IList _taskChartData = new List(); - private Layout _taskLayout = new(); - - // ── Employee task completion ────────────────────────────────────── - private record EmployeeCount(string Name, int Count); - private List _employeeTaskCounts = new(); - - // Shared config - private Config _chartConfig = new() { Responsive = true }; - - // ── Lifecycle ──────────────────────────────────────────────────── protected override async Task OnInitializedAsync() { var ctx = await UserCtx.GetAsync(); _isSignedIn = ctx.User?.Id != null; _role = ctx.User?.Role; - await LocationState.SetLocationsAsync(ctx.AccessibleLocations); - _locationId = LocationState.CurrentLocation?.Id; - - if (_isSignedIn && _locationId.HasValue) - { - await LoadInventoryChartAsync(_locationId.Value); - await LoadTaskChartAsync(_locationId.Value); - await LoadActivityFeedAsync(_locationId.Value); - } - _ready = true; } @@ -354,165 +168,37 @@ else _aiLoading = true; StateHasChanged(); - try - { - if (!string.IsNullOrWhiteSpace(_aiAnalysis)) + try{ + if (!string.IsNullOrWhiteSpace(_aiAnalysis)) { var csvPath = Path.Combine(Env.ContentRootPath, "AIDashboard", "Services", "Reporting", "test_data.csv"); _aiAnalysis = await ReportingService.AnalyzeCsvAsync(csvPath); - + + // Try to deserialize the returned JSON into the DTO so we can render structured cards UI. if (!string.IsNullOrWhiteSpace(_aiAnalysis)) { var options = new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true }; _aiAnalysisObj = System.Text.Json.JsonSerializer.Deserialize(_aiAnalysis, options); } - } + } } - catch (Exception e) + catch(Exception e) { Console.WriteLine($"AI Analysis failed: {e.Message}"); _aiAnalysisObj = null; _aiAnalysis = null; + } - finally - { + finally { _aiLoading = false; StateHasChanged(); } + _aiLoading = false; + //_aiAnalysisObj = null; // Uncoomment above and delete curent like to activate AI + StateHasChanged(); } } - // ── Data loaders ───────────────────────────────────────────────── - private async Task LoadInventoryChartAsync(int locationId) - { - _inventoryData = await InventorySvc.GetItemsByLocationAsync(locationId); - _lowStockItems = _inventoryData - .Where(i => i.IsLowStock) - .OrderBy(i => i.CurrentQuantity) - .ToList(); - } - - private async Task LoadTaskChartAsync(int locationId) - { - _taskData = await TaskSvc.GetByLocationAsync(locationId); - - var groups = _taskData - .GroupBy(t => string.IsNullOrWhiteSpace(t.Status) ? "Unknown" : t.Status) - .ToDictionary(g => g.Key, g => g.Count()); - - _taskChartData = new List - { - new Pie - { - Labels = groups.Keys.Select(k => (object)k).ToList(), - Values = groups.Values.Select(v => (object)v).ToList(), - Hole = (decimal?)0.4 - } - }; - - _taskLayout = new Layout - { - PaperBgColor = "transparent", - PlotBgColor = "transparent", - Margin = new Plotly.Blazor.LayoutLib.Margin { T = 10, B = 10, L = 10, R = 10 }, - ShowLegend = true - }; - - _employeeTaskCounts = _taskData - .Where(t => string.Equals(t.Status, "Completed", StringComparison.OrdinalIgnoreCase) - && t.User != null) - .GroupBy(t => t.User!.Name ?? "Unknown") - .Select(g => new EmployeeCount(g.Key, g.Count())) - .OrderByDescending(e => e.Count) - .ToList(); - - } - - private async Task LoadActivityFeedAsync(int locationId) - { - var cutoff = DateTime.UtcNow.AddDays(-7); - var items = new List(); - - // Completed tasks from already-loaded task data - var completedTasks = _taskData - .Where(t => string.Equals(t.Status, "Completed", StringComparison.OrdinalIgnoreCase) - && t.UpdatedAt >= cutoff) - .OrderByDescending(t => t.UpdatedAt) - .Take(10); - - foreach (var t in completedTasks) - { - var who = t.User?.Name ?? "Someone"; - items.Add(new ActivityItem( - Icon: "bi-check-circle-fill", - BadgeClass: "text-bg-success", - Text: $"Task \"{t.Name}\" marked complete", - Who: who, - When: t.UpdatedAt - )); - } - - // Tasks created in the last 7 days (non-completed) - var newTasks = _taskData - .Where(t => !string.Equals(t.Status, "Completed", StringComparison.OrdinalIgnoreCase) - && t.CreatedAt >= cutoff) - .OrderByDescending(t => t.CreatedAt) - .Take(5); - - foreach (var t in newTasks) - { - items.Add(new ActivityItem( - Icon: "bi-clipboard-plus", - BadgeClass: "text-bg-primary", - Text: $"New task assigned: \"{t.Name}\"", - Who: t.User?.Name, - When: t.CreatedAt - )); - } - - // Recent purchase orders - var recentOrders = await Db.PurchaseOrders - .Where(po => po.LocationId == locationId && po.CreatedAt >= cutoff) - .OrderByDescending(po => po.CreatedAt) - .Take(5) - .ToListAsync(); - - foreach (var po in recentOrders) - { - var statusLabel = po.Status switch - { - PurchaseOrderStatus.Draft => "created (Draft)", - PurchaseOrderStatus.Submitted => "submitted", - PurchaseOrderStatus.PartiallyReceived => "partially received", - PurchaseOrderStatus.Received => "received", - PurchaseOrderStatus.Cancelled => "cancelled", - _ => po.Status.ToString() - }; - - items.Add(new ActivityItem( - Icon: "bi-cart", - BadgeClass: "text-bg-warning", - Text: $"Purchase order {po.OrderNumber} {statusLabel} — {po.VendorName}", - Who: null, - When: po.CreatedAt - )); - } - - _activityFeed = items - .OrderByDescending(i => i.When) - .Take(15) - .ToList(); - } - - private static string TimeAgo(DateTime utc) - { - var diff = DateTime.UtcNow - utc; - if (diff.TotalMinutes < 1) return "just now"; - if (diff.TotalMinutes < 60) return $"{(int)diff.TotalMinutes}m ago"; - if (diff.TotalHours < 24) return $"{(int)diff.TotalHours}h ago"; - return $"{(int)diff.TotalDays}d ago"; - } - private void NavigateToSignIn() { Nav.NavigateTo("/login", forceLoad: true); diff --git a/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/ConfigureLocation.razor b/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/ConfigureLocation.razor index 52ec2c0..7cea837 100644 --- a/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/ConfigureLocation.razor +++ b/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/ConfigureLocation.razor @@ -6,7 +6,6 @@ @using CulinaryCommand.Services @using CulinaryCommand.Services.UserContextSpace @using CulinaryCommandApp.Inventory.Entities -@using Unit = CulinaryCommandApp.Inventory.Entities.Unit @using CulinaryCommandApp.Inventory.Services @using CulinaryCommandApp.Inventory.Services.Interfaces @using Microsoft.AspNetCore.Authorization diff --git a/CulinaryCommandApp/Components/Pages/UserSettings/Settings.css b/CulinaryCommandApp/Components/Pages/UserSettings/Settings.css index a35fe3f..1e23a8e 100644 --- a/CulinaryCommandApp/Components/Pages/UserSettings/Settings.css +++ b/CulinaryCommandApp/Components/Pages/UserSettings/Settings.css @@ -13,7 +13,7 @@ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); padding: 40px; width: 100%; - max-width: 960px; + max-width: 700px; } .app-title { diff --git a/CulinaryCommandApp/Components/Pages/UserSettings/Settings.razor b/CulinaryCommandApp/Components/Pages/UserSettings/Settings.razor index 12794b3..46cdc20 100644 --- a/CulinaryCommandApp/Components/Pages/UserSettings/Settings.razor +++ b/CulinaryCommandApp/Components/Pages/UserSettings/Settings.razor @@ -57,12 +57,6 @@ Company - @@ -93,10 +87,6 @@ break; - case "inventory-config": - - break; - default: break; diff --git a/CulinaryCommandApp/Components/Pages/UserSettings/SettingsInventoryConfigurations.razor b/CulinaryCommandApp/Components/Pages/UserSettings/SettingsInventoryConfigurations.razor deleted file mode 100644 index bb0d966..0000000 --- a/CulinaryCommandApp/Components/Pages/UserSettings/SettingsInventoryConfigurations.razor +++ /dev/null @@ -1,314 +0,0 @@ -@rendermode InteractiveServer - -@using CulinaryCommand.Services.UserContextSpace -@using CulinaryCommandApp.Inventory.Entities -@using Unit = CulinaryCommandApp.Inventory.Entities.Unit -@using CulinaryCommandApp.Inventory.Services.Interfaces -@using System.ComponentModel.DataAnnotations - -@inject IUnitService UnitService -@inject IUserContextService UserCtx -@inject NavigationManager Nav - -
-

- Inventory Configurations -

- - @if (!_ready) - { -
-
- Loading... -
-
- } - else - { - -
-
- Unit of Measurement -
-

Define standard units for ingredients and recipes in your kitchen

- - - - -
-
- - -
Full name of the measurement unit
- -
-
- - -
Short form for display
- -
- @*
- - -
Multiplier relative to base unit (e.g. 1000 for kg if gram is base)
-
*@ -
- - @if (!string.IsNullOrEmpty(_saveError)) - { -
- @_saveError -
- } - -
- @if (_editingId.HasValue) - { - - - } - else - { - - - } -
-
-
- - -
-
- Existing Units -
-

Units that have been set and are usable across ingredients and recipes

- - @if (!_units.Any()) - { -
- - No units configured yet. Add one above to get started. -
- } - else - { -
- - - - - - - - - - - @foreach (var unit in _units) - { - - - - - - - } - -
Unit NameAbbreviationConv. FactorActions
@unit.Name@unit.Abbreviation@unit.ConversionFactor - - -
-
-
- @_units.Count unit(s) configured -
- } -
- } -
- - -@if (_unitToDelete is not null) -{ - -} - -@code { - private UserContext? _ctx; - private bool _ready; - private bool _allowed; - private bool _saving; - private bool _deleting; - private int? _editingId; - private string? _saveError; - private Unit? _unitToDelete; - private List _units = new(); - - private UnitFormModel _form = new(); - - protected override async Task OnInitializedAsync() - { - _ctx = await UserCtx.GetAsync(); - - if (_ctx.IsAuthenticated != true) - { - Nav.NavigateTo("/login", true); - return; - } - - if (_ctx.User is not null) - { - _allowed = true; - _units = await UnitService.GetAllAsync(); - } - - _ready = true; - } - - private async Task HandleSave() - { - if (!_allowed || _saving) return; - - _saving = true; - _saveError = null; - - try - { - if (_editingId.HasValue) - { - var unit = new Unit - { - Id = _editingId.Value, - Name = _form.Name!, - Abbreviation = _form.Abbreviation!, - ConversionFactor = _form.ConversionFactor - }; - await UnitService.UpdateAsync(unit); - } - else - { - var unit = new Unit - { - Name = _form.Name!, - Abbreviation = _form.Abbreviation!, - ConversionFactor = _form.ConversionFactor - }; - await UnitService.CreateAsync(unit); - } - - _units = await UnitService.GetAllAsync(); - ResetForm(); - } - catch - { - _saveError = "An error occurred while saving. Please try again."; - } - finally - { - _saving = false; - } - } - - private void StartEdit(Unit unit) - { - _editingId = unit.Id; - _saveError = null; - _form = new UnitFormModel - { - Name = unit.Name, - Abbreviation = unit.Abbreviation, - ConversionFactor = unit.ConversionFactor - }; - } - - private void CancelEdit() => ResetForm(); - - private void ResetForm() - { - _form = new UnitFormModel(); - _editingId = null; - _saveError = null; - } - - private void ConfirmDelete(Unit unit) - { - _unitToDelete = unit; - } - - private void CancelDelete() - { - _unitToDelete = null; - } - - private async Task DeleteUnit() - { - if (_unitToDelete is null || _deleting) return; - - _deleting = true; - try - { - await UnitService.DeleteAsync(_unitToDelete.Id); - _units = await UnitService.GetAllAsync(); - - if (_editingId == _unitToDelete.Id) - ResetForm(); - - _unitToDelete = null; - } - finally - { - _deleting = false; - } - } - - private class UnitFormModel - { - [Required(ErrorMessage = "Unit name is required.")] - [StringLength(100, ErrorMessage = "Name must be 100 characters or fewer.")] - public string? Name { get; set; } - - [Required(ErrorMessage = "Abbreviation is required.")] - [StringLength(20, ErrorMessage = "Abbreviation must be 20 characters or fewer.")] - public string? Abbreviation { get; set; } - - public decimal ConversionFactor { get; set; } = 1; - } -} diff --git a/CulinaryCommandApp/Components/_Imports.razor b/CulinaryCommandApp/Components/_Imports.razor index ecacefb..8306129 100644 --- a/CulinaryCommandApp/Components/_Imports.razor +++ b/CulinaryCommandApp/Components/_Imports.razor @@ -12,11 +12,4 @@ @using CulinaryCommand.Services @using CulinaryCommand.Components.Layout @using CulinaryCommand.Components.Custom -@using CulinaryCommand.Components.Pages -@using BlazorBootstrap; -@using Plotly.Blazor -@using Plotly.Blazor.LayoutLib -@using Plotly.Blazor.Traces -@using Plotly.Blazor.Traces.ScatterLib -@using Plotly.Blazor.Traces.BarLib -@using Plotly.Blazor.Traces.PieLib \ No newline at end of file +@using CulinaryCommand.Components.Pages \ No newline at end of file diff --git a/CulinaryCommandApp/CulinaryCommand.csproj b/CulinaryCommandApp/CulinaryCommand.csproj index 9e5665e..2989c24 100644 --- a/CulinaryCommandApp/CulinaryCommand.csproj +++ b/CulinaryCommandApp/CulinaryCommand.csproj @@ -15,7 +15,6 @@ - @@ -29,7 +28,6 @@ all - PreserveNewest diff --git a/CulinaryCommandApp/Program.cs b/CulinaryCommandApp/Program.cs index 3d18971..3578ae5 100644 --- a/CulinaryCommandApp/Program.cs +++ b/CulinaryCommandApp/Program.cs @@ -29,14 +29,13 @@ var builder = WebApplication.CreateBuilder(args); -builder.Services.AddBlazorBootstrap(); - // // ===================== // UI // ===================== builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); + // // ===================== // Cognito Authentication (MUST be before Build) @@ -51,11 +50,6 @@ .AddCookie() .AddOpenIdConnect(options => { - options.CorrelationCookie.SameSite = SameSiteMode.None; - options.CorrelationCookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; - options.NonceCookie.SameSite = SameSiteMode.None; - options.NonceCookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; - options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; // ---- Read Cognito config (env/appsettings) ---- @@ -123,12 +117,8 @@ // ===================== // AI Services // ===================== -builder.Services.AddSingleton(sp => -{ - var apiKey = builder.Configuration["Google:ApiKey"] - ?? throw new InvalidOperationException("Missing config: Google:ApiKey"); - return new Client(apiKey: apiKey); -});builder.Services.AddScoped(); +builder.Services.AddSingleton(_ => new Client()); +builder.Services.AddScoped(); // // ===================== diff --git a/CulinaryCommandApp/Recipe/Services/RecipeService.cs b/CulinaryCommandApp/Recipe/Services/RecipeService.cs index 5f84827..0e063bc 100644 --- a/CulinaryCommandApp/Recipe/Services/RecipeService.cs +++ b/CulinaryCommandApp/Recipe/Services/RecipeService.cs @@ -57,6 +57,9 @@ public RecipeService(AppDbContext db) public async Task CreateAsync(Rec.Recipe recipe) { + if (string.IsNullOrWhiteSpace(recipe.Category)) + throw new Exception("Category is required."); + _db.Recipes.Add(recipe); await _db.SaveChangesAsync(); }