From fd59af0e4ca8d9b10dbf9d37431a9d185556c9a1 Mon Sep 17 00:00:00 2001 From: Wyatt Hunter Date: Sat, 7 Feb 2026 10:24:35 -0600 Subject: [PATCH 1/7] FoundationCog --- CulinaryCommandApp/CulinaryCommand.csproj | 3 + CulinaryCommandApp/Program.cs | 193 +++++++++++++--------- 2 files changed, 117 insertions(+), 79 deletions(-) diff --git a/CulinaryCommandApp/CulinaryCommand.csproj b/CulinaryCommandApp/CulinaryCommand.csproj index 4e4019a..d8759c1 100644 --- a/CulinaryCommandApp/CulinaryCommand.csproj +++ b/CulinaryCommandApp/CulinaryCommand.csproj @@ -13,6 +13,8 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -22,6 +24,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + PreserveNewest diff --git a/CulinaryCommandApp/Program.cs b/CulinaryCommandApp/Program.cs index 0834f07..8fda080 100644 --- a/CulinaryCommandApp/Program.cs +++ b/CulinaryCommandApp/Program.cs @@ -1,86 +1,110 @@ using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; + using CulinaryCommand.Data; using CulinaryCommand.Services; using CulinaryCommand.Inventory.Services; using CulinaryCommand.PurchaseOrder.Services; using CulinaryCommand.Inventory; using CulinaryCommand.Inventory.Services.Interfaces; -using System; // for Version, TimeSpan -using System.Linq; -using CulinaryCommand.Components; // for args.Any -using Google.GenAI; +using CulinaryCommand.Components; using CulinaryCommandApp.AIDashboard.Services.Reporting; - +using Google.GenAI; +using System; var builder = WebApplication.CreateBuilder(args); - -// FORCE EF to load your config when running "dotnet ef" -// builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); -// builder.Configuration.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true); - -// Add services to the container. +// +// ===================== +// UI +// ===================== builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); -// Register Google GenAI client and AIReportingService so they can be injected. -// The Client will pick up the GOOGLE_API_KEY from environment variables (set in deploy.yml). +// +// ===================== +// Cognito Authentication (MUST be before Build) +// ===================== +// ===== Cognito Auth (OIDC) ===== +builder.Services + .AddAuthentication(options => + { + options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; + }) + .AddCookie() + .AddOpenIdConnect(options => + { + var userPoolId = "us-east-2_SULe0c9vr"; + var region = "us-east-2"; + + options.Authority = $"https://cognito-idp.{region}.amazonaws.com/{userPoolId}"; + options.MetadataAddress = $"{options.Authority}/.well-known/openid-configuration"; + + options.ClientId = "55joip0viah9qtj7dndhvma2gt"; + options.ClientSecret = Environment.GetEnvironmentVariable("COGNITO_CLIENT_SECRET"); // don’t hardcode + + options.ResponseType = OpenIdConnectResponseType.Code; + options.SaveTokens = true; + + options.CallbackPath = "/signin-oidc"; + options.SignedOutCallbackPath = "/signout-callback-oidc"; + + options.RequireHttpsMetadata = true; // keep true + + options.Scope.Clear(); + options.Scope.Add("openid"); + options.Scope.Add("email"); + options.Scope.Add("profile"); + + options.TokenValidationParameters.NameClaimType = "cognito:username"; + options.TokenValidationParameters.RoleClaimType = "cognito:groups"; + }); + +builder.Services.AddAuthorization(); + + +// +// ===================== +// AI Services +// ===================== builder.Services.AddSingleton(_ => new Client()); builder.Services.AddScoped(); -// DB hookup -// var conn = builder.Configuration.GetConnectionString("DefaultConnection"); -// if (string.IsNullOrWhiteSpace(conn)) -// { -// throw new InvalidOperationException("Missing connection string 'Default'. Set ConnectionStrings__Default via environment or config."); -// } -// Console.WriteLine("CONNECTION STRING FROM CONFIG:"); -// Console.WriteLine(conn); - - -// builder.Services.AddDbContext(opt => -// opt.UseMySql(conn, new MySqlServerVersion(new Version(8, 0, 36)), -// mySqlOptions => mySqlOptions.EnableRetryOnFailure() -// ) -// ); - -// DB hookup +// +// ===================== +// Database +// ===================== var conn = builder.Configuration.GetConnectionString("DefaultConnection"); if (string.IsNullOrWhiteSpace(conn)) { - throw new InvalidOperationException("Missing connection string 'DefaultConnection'. Set ConnectionStrings__DefaultConnection via environment or config."); + throw new InvalidOperationException( + "Missing connection string 'DefaultConnection'"); } -// Mask password for logs (primarily for debugging in the Lightsail instance) -string MaskPwd(string s) -{ - if (string.IsNullOrEmpty(s)) return s; - var parts = s.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - for (int i = 0; i < parts.Length; i++) - { - if (parts[i].StartsWith("Pwd=", StringComparison.OrdinalIgnoreCase)) - parts[i] = "Pwd=****"; - } - return string.Join(';', parts); -} -Console.WriteLine($"[Startup] Using MySQL connection: {MaskPwd(conn)}"); - builder.Services.AddDbContext(opt => - opt.UseMySql(conn, new MySqlServerVersion(new Version(8, 0, 36)), mySqlOpts => - { - // Enable transient retry for RDS connectivity hiccups - mySqlOpts.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(10), errorNumbersToAdd: null); - })); - -// registers services with Scoped lifetime. + opt.UseMySql( + conn, + new MySqlServerVersion(new Version(8, 0, 36)), + mySqlOpts => mySqlOpts.EnableRetryOnFailure() + ) +); + +// +// ===================== +// Application Services +// ===================== builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -90,50 +114,61 @@ string MaskPwd(string s) builder.Services.AddScoped(); builder.Services.AddSingleton(); +// +// ===================== +// Build App +// ===================== var app = builder.Build(); -// Determine whether the app should only run migrations and exit -var migrateOnly = (Environment.GetEnvironmentVariable("MIGRATE_ONLY")?.Equals("true", StringComparison.OrdinalIgnoreCase) == true) - || (args != null && args.Any(a => a.Equals("--migrate-only", StringComparison.OrdinalIgnoreCase))); - -// Apply pending EF Core migrations at startup +// +// ===================== +// Apply EF Migrations +// ===================== using (var scope = app.Services.CreateScope()) { try { - var database = scope.ServiceProvider.GetRequiredService(); - database.Database.Migrate(); + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.Migrate(); } catch (Exception ex) { - Console.WriteLine($"[Startup] Migration failed: {ex.GetType().Name} - {ex.Message}"); - // Optionally: keep running without schema update; remove this catch to fail hard instead + Console.WriteLine($"[Startup] Migration warning: {ex.Message}"); } } -if (migrateOnly) -{ - Console.WriteLine("[Startup] MIGRATE_ONLY set; exiting after applying migrations."); - return; -} - -// Configure the HTTP request pipeline. -if (!app.Environment.IsDevelopment()) -{ - app.UseExceptionHandler("/Error", createScopeForErrors: true); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); -} - -// Temporarily disable HTTPS redirect for development -//app.UseHttpsRedirection(); +// +// ===================== +// Middleware +// ===================== app.UseStaticFiles(); app.UseAntiforgery(); +app.UseAuthentication(); +app.UseAuthorization(); + +// +// ===================== +// Routes +// ===================== app.MapRazorComponents() .AddInteractiveServerRenderMode(); -// Simple health endpoint for load balancers/CI checks app.MapGet("/health", () => "OK"); +app.MapGet("/login", async (HttpContext ctx) => +{ + await ctx.ChallengeAsync( + OpenIdConnectDefaults.AuthenticationScheme, + new AuthenticationProperties { RedirectUri = "/" } + ); +}); + +app.MapGet("/logout", async (HttpContext ctx) => +{ + await ctx.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + await ctx.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, + new AuthenticationProperties { RedirectUri = "/" }); +}); + app.Run(); From 0583fa22267f19c08d5498036d39da3dd689b816 Mon Sep 17 00:00:00 2001 From: Anthony Phan <131195703+antphan12@users.noreply.github.com> Date: Mon, 16 Feb 2026 23:47:04 -0600 Subject: [PATCH 2/7] Added new IMS design Still need to update the design for buttons --- .../Components/Layout/NavMenu.razor | 5 - .../Pages/Inventory/InventoryManagement.razor | 577 ++++++++------ .../Inventory/InventoryManagement.razor.css | 738 ++++++++---------- CulinaryCommandApp/wwwroot/css/app.css | 3 + 4 files changed, 653 insertions(+), 670 deletions(-) diff --git a/CulinaryCommandApp/Components/Layout/NavMenu.razor b/CulinaryCommandApp/Components/Layout/NavMenu.razor index fc56718..3393468 100644 --- a/CulinaryCommandApp/Components/Layout/NavMenu.razor +++ b/CulinaryCommandApp/Components/Layout/NavMenu.razor @@ -103,11 +103,6 @@ Purchase Orders - } diff --git a/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryManagement.razor b/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryManagement.razor index 73fbf28..920e939 100644 --- a/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryManagement.razor +++ b/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryManagement.razor @@ -3,175 +3,139 @@ @using CulinaryCommand.Inventory.DTOs @using Microsoft.JSInterop -@using CulinaryCommand.Inventory.Services.Interfaces @using System.Threading.Tasks
-

Inventory

-
-
+ +
+ +
+ + + +
+ +
+
+ +
+ - @* *@
- -
+
- - - -
- - @if (showFilterPanel) - { -
-
-
- - -
-
- - -
-
- - -
-
- -
-
-
- } - -
- - - - - - - + + + + + + + + + @if (isLoading) { - } - else if (!filteredItems.Any()) + else if (!pagedItems.Any()) { - } else { - @foreach (var item in filteredItems) + @foreach (var item in pagedItems) { - - + - + + - + +
- Name - - SKU - - Current Qty - - Out of Stock - - Last Order + + - Price - ActionsName CategoryUnitCurrent Stock Expires Cost/Unit LocationLast Updated Actions
+
Loading...
+

No items found

-
- @item.Name - @if (item.IsLowStock) - { - Low - } -
+
+ @item.SKU -
- - @item.CurrentQuantity @item.Unit - +
+
@item.Name
+
SKU: @item.SKU
- @if (item.OutOfStockDate.HasValue) - { - - @item.OutOfStockDate.Value.ToString("MM/dd/yy") - @if (IsDateCritical(item.OutOfStockDate.Value)) - { - - } - - } - else - { - - - } + @item.Category @item.Unit - @if (item.LastOrderDate.HasValue) - { - @item.LastOrderDate.Value.ToString("MM/dd/yy") - } - else - { - - - } +
+ @item.CurrentQuantity + @if (item.CurrentQuantity <= 0) + { + + } + else if (item.IsLowStock) + { + + } +
@(item.OutOfStockDate.HasValue ? item.OutOfStockDate.Value.ToString("M/d/yy") : "-") $@item.Price.ToString("F2") + @GetLocation(item.Id)@(item.LastOrderDate.HasValue ? item.LastOrderDate.Value.ToString("M/d/yy") : "-")
-
@@ -189,25 +153,27 @@ }
-
- - -
-
-
Total Items
-
@inventoryItems.Count
-
-
-
Low Stock
-
@inventoryItems.Count(i => i.IsLowStock)
-
-
-
Out of Stock
-
@inventoryItems.Count(i => i.CurrentQuantity <= 0)
-
-
-
Total Value
-
$@inventoryItems.Sum(i => i.CurrentQuantity * i.Price).ToString("F2")
+
@@ -292,16 +258,22 @@ @code { private List inventoryItems = new(); private List filteredItems = new(); + private List pagedItems = new(); private bool isLoading = true; private string activeTab = "all"; - private bool showFilterPanel = false; private string searchTerm = ""; private string selectedCategory = ""; private string stockStatus = ""; private string sortColumn = ""; private bool sortAscending = true; + private int currentPage = 1; + private int pageSize = 10; + private int totalPages = 1; + private List pageButtons = new(); + private HashSet selectedIds = new(); - private List categories = new() { "Produce", "Dairy", "Meat", "Dry Goods", "Beverages", "Condiments", "Spices" }; + private readonly Dictionary itemLocations = new(); + private List categories = new() { "Vegetables", "Dairy", "Meat & Poultry", "Grains & Pasta", "Oils & Sauces" }; private bool showModal = false; private InventoryItemDTO modalItem = new(); @@ -316,19 +288,40 @@ { isLoading = true; - // Load real data from the inventory service instead of static mock data - try + inventoryItems = new List { - inventoryItems = await InventorySvc.GetAllItemsAsync(); - } - catch (Exception ex) + new InventoryItemDTO { Id = 1, Name = "Pasta", SKU = "123", Category = "Grains & Pasta", Unit = "lbs", CurrentQuantity = 10, Price = 5, ReorderLevel = 15, OutOfStockDate = new DateTime(2026, 1, 2), LastOrderDate = new DateTime(2026, 2, 11) }, + new InventoryItemDTO { Id = 2, Name = "Milk", SKU = "321", Category = "Dairy", Unit = "mL", CurrentQuantity = 20, Price = 8, ReorderLevel = 25, OutOfStockDate = new DateTime(2026, 1, 1), LastOrderDate = new DateTime(2026, 2, 10) }, + new InventoryItemDTO { Id = 3, Name = "Heavy Cream", SKU = "456", Category = "Dairy", Unit = "L", CurrentQuantity = 35, Price = 10, ReorderLevel = 20, OutOfStockDate = new DateTime(2026, 2, 5), LastOrderDate = new DateTime(2026, 2, 8) }, + new InventoryItemDTO { Id = 4, Name = "Olive Oil", SKU = "654", Category = "Oils & Sauces", Unit = "L", CurrentQuantity = 45, Price = 8, ReorderLevel = 30, OutOfStockDate = new DateTime(2026, 6, 22), LastOrderDate = new DateTime(2026, 2, 1) }, + new InventoryItemDTO { Id = 5, Name = "Potatoes", SKU = "789", Category = "Vegetables", Unit = "lbs", CurrentQuantity = 150, Price = 9, ReorderLevel = 40, OutOfStockDate = new DateTime(2026, 1, 5), LastOrderDate = new DateTime(2026, 2, 11) }, + new InventoryItemDTO { Id = 6, Name = "Basil", SKU = "987", Category = "Vegetables", Unit = "bunch", CurrentQuantity = 25, Price = 3, ReorderLevel = 20, OutOfStockDate = new DateTime(2026, 1, 2), LastOrderDate = new DateTime(2026, 2, 11) }, + new InventoryItemDTO { Id = 7, Name = "Beef", SKU = "012", Category = "Meat & Poultry", Unit = "lbs", CurrentQuantity = 150, Price = 25, ReorderLevel = 60, OutOfStockDate = new DateTime(2026, 2, 26), LastOrderDate = new DateTime(2026, 1, 21) }, + new InventoryItemDTO { Id = 8, Name = "Mozzarella", SKU = "677", Category = "Dairy", Unit = "lbs", CurrentQuantity = 75, Price = 25, ReorderLevel = 80, OutOfStockDate = new DateTime(2026, 2, 26), LastOrderDate = new DateTime(2026, 2, 11) }, + new InventoryItemDTO { Id = 9, Name = "Jasmine Rice", SKU = "962", Category = "Grains & Pasta", Unit = "lbs", CurrentQuantity = 150, Price = 25, ReorderLevel = 90, OutOfStockDate = new DateTime(2026, 1, 2), LastOrderDate = new DateTime(2026, 2, 11) }, + new InventoryItemDTO { Id = 10, Name = "Green Peppers", SKU = "832", Category = "Vegetables", Unit = "lbs", CurrentQuantity = 0, Price = 25, ReorderLevel = 30, OutOfStockDate = new DateTime(2026, 1, 1), LastOrderDate = new DateTime(2026, 2, 11) } + }; + + itemLocations.Clear(); + itemLocations[1] = "Dry Storage"; + itemLocations[2] = "Walk-in"; + itemLocations[3] = "Walk-in"; + itemLocations[4] = "Dry Storage"; + itemLocations[5] = "Dry Storage"; + itemLocations[6] = "Walk-in"; + itemLocations[7] = "Walk-in Freezer"; + itemLocations[8] = "Walk-in"; + itemLocations[9] = "Dry Storage"; + itemLocations[10] = "Walk-in"; + + foreach (var item in inventoryItems) { - Console.Error.WriteLine($"Failed to load inventory items: {ex}"); - inventoryItems = new List(); + item.IsLowStock = item.CurrentQuantity <= item.ReorderLevel && item.CurrentQuantity > 0; } FilterItems(); isLoading = false; + await Task.CompletedTask; } private void SetActiveTab(string tab) @@ -337,21 +330,16 @@ FilterItems(); } - private void ToggleFilterPanel() - { - showFilterPanel = !showFilterPanel; - } - private void FilterItems() { + currentPage = 1; filteredItems = inventoryItems.Where(item => { // Tab filter bool tabMatch = activeTab switch { - "dry" => item.Category == "Dry Goods", - "arriving" => item.OutOfStockDate.HasValue && item.OutOfStockDate.Value <= DateTime.Now.AddDays(7) && item.OutOfStockDate.Value >= DateTime.Now, - "ordered" => item.LastOrderDate.HasValue && item.LastOrderDate.Value >= DateTime.Now.AddDays(-7), + "low" => item.IsLowStock, + "out" => item.CurrentQuantity <= 0, _ => true }; @@ -377,14 +365,7 @@ }).ToList(); ApplySorting(); - } - - private void ClearFilters() - { - searchTerm = ""; - selectedCategory = ""; - stockStatus = ""; - FilterItems(); + UpdatePagination(); } private void SortBy(string column) @@ -399,6 +380,7 @@ sortAscending = true; } ApplySorting(); + UpdatePagination(); } private void ApplySorting() @@ -406,46 +388,166 @@ filteredItems = sortColumn switch { "name" => sortAscending ? filteredItems.OrderBy(i => i.Name).ToList() : filteredItems.OrderByDescending(i => i.Name).ToList(), - "sku" => sortAscending ? filteredItems.OrderBy(i => i.SKU).ToList() : filteredItems.OrderByDescending(i => i.SKU).ToList(), "currentQty" => sortAscending ? filteredItems.OrderBy(i => i.CurrentQuantity).ToList() : filteredItems.OrderByDescending(i => i.CurrentQuantity).ToList(), - "outOfStock" => sortAscending ? filteredItems.OrderBy(i => i.OutOfStockDate ?? DateTime.MaxValue).ToList() : filteredItems.OrderByDescending(i => i.OutOfStockDate ?? DateTime.MinValue).ToList(), - "lastOrder" => sortAscending ? filteredItems.OrderBy(i => i.LastOrderDate ?? DateTime.MaxValue).ToList() : filteredItems.OrderByDescending(i => i.LastOrderDate ?? DateTime.MinValue).ToList(), + "expires" => sortAscending ? filteredItems.OrderBy(i => i.OutOfStockDate ?? DateTime.MaxValue).ToList() : filteredItems.OrderByDescending(i => i.OutOfStockDate ?? DateTime.MinValue).ToList(), + "lastUpdated" => sortAscending ? filteredItems.OrderBy(i => i.LastOrderDate ?? DateTime.MaxValue).ToList() : filteredItems.OrderByDescending(i => i.LastOrderDate ?? DateTime.MinValue).ToList(), "price" => sortAscending ? filteredItems.OrderBy(i => i.Price).ToList() : filteredItems.OrderByDescending(i => i.Price).ToList(), _ => filteredItems }; } - private string GetRowClass(InventoryItemDTO item) + private string GetCategoryClass(string category) { - if (item.CurrentQuantity <= 0) return "row-out-of-stock"; - if (item.IsLowStock) return "row-low-stock"; - return ""; + return category switch + { + "Grains & Pasta" => "category-grains", + "Dairy" => "category-dairy", + "Oils & Sauces" => "category-oils", + "Vegetables" => "category-vegetables", + "Meat & Poultry" => "category-meat", + _ => "category-default" + }; } - private bool IsDateCritical(DateTime date) + private string GetLocation(int id) { - return date <= DateTime.Now.AddDays(3); + return itemLocations.TryGetValue(id, out var location) ? location : "-"; } - private async Task IncrementQuantity(InventoryItemDTO item) + private bool IsPageSelected => pagedItems.Count > 0 && pagedItems.All(i => selectedIds.Contains(i.Id)); + + private void ToggleSelection(int id, ChangeEventArgs args) { - item.CurrentQuantity++; - item.IsLowStock = item.CurrentQuantity <= item.ReorderLevel; - // TODO - //Call service to update quantity - StateHasChanged(); + var isChecked = args.Value is bool boolValue && boolValue; + if (!isChecked && args.Value is string textValue) + { + bool.TryParse(textValue, out isChecked); + } + + if (isChecked) + { + selectedIds.Add(id); + } + else + { + selectedIds.Remove(id); + } } - private async Task DecrementQuantity(InventoryItemDTO item) + private void ToggleSelectAll(ChangeEventArgs args) { - if (item.CurrentQuantity > 0) + var isChecked = args.Value is bool boolValue && boolValue; + if (!isChecked && args.Value is string textValue) { - item.CurrentQuantity--; - item.IsLowStock = item.CurrentQuantity <= item.ReorderLevel; - // TODO - // Call service to update quantity - StateHasChanged(); + bool.TryParse(textValue, out isChecked); } + + if (isChecked) + { + foreach (var item in pagedItems) + { + selectedIds.Add(item.Id); + } + } + else + { + foreach (var item in pagedItems) + { + selectedIds.Remove(item.Id); + } + } + } + + private void UpdatePagination() + { + totalPages = Math.Max(1, (int)Math.Ceiling(filteredItems.Count / (double)pageSize)); + currentPage = Math.Min(currentPage, totalPages); + pagedItems = filteredItems + .Skip((currentPage - 1) * pageSize) + .Take(pageSize) + .ToList(); + + pageButtons = BuildPageButtons(); + } + + private List BuildPageButtons() + { + var buttons = new List(); + if (totalPages <= 1) + { + buttons.Add(1); + return buttons; + } + + if (totalPages <= 5) + { + for (var i = 1; i <= totalPages; i++) + { + buttons.Add(i); + } + return buttons; + } + + buttons.Add(1); + + if (currentPage > 3) + { + buttons.Add(-1); + } + + var start = Math.Max(2, currentPage - 1); + var end = Math.Min(totalPages - 1, currentPage + 1); + for (var i = start; i <= end; i++) + { + buttons.Add(i); + } + + if (currentPage < totalPages - 2) + { + buttons.Add(-1); + } + + buttons.Add(totalPages); + return buttons; + } + + private void GoToPage(int page) + { + currentPage = page; + UpdatePagination(); + } + + private void PreviousPage() + { + if (currentPage > 1) + { + currentPage--; + UpdatePagination(); + } + } + + private void NextPage() + { + if (currentPage < totalPages) + { + currentPage++; + UpdatePagination(); + } + } + + private int GetStartIndex() + { + if (filteredItems.Count == 0) + { + return 0; + } + + return ((currentPage - 1) * pageSize) + 1; + } + + private int GetEndIndex() + { + return Math.Min(currentPage * pageSize, filteredItems.Count); } private void ShowAddItemModal() @@ -476,44 +578,24 @@ { try { + modalItem.IsLowStock = modalItem.CurrentQuantity <= modalItem.ReorderLevel && modalItem.CurrentQuantity > 0; if (editingItem != null) { - // Persist changes via service - var updated = await InventorySvc.UpdateItemAsync(modalItem); - if (updated != null) + var idx = inventoryItems.FindIndex(i => i.Id == modalItem.Id); + if (idx >= 0) { - var idx = inventoryItems.FindIndex(i => i.Id == updated.Id); - if (idx >= 0) - inventoryItems[idx] = updated; - else - inventoryItems.Add(updated); - - editingItem = inventoryItems.First(i => i.Id == updated.Id); + inventoryItems[idx] = modalItem; } else { - await JSRuntime.InvokeVoidAsync("alert", "Failed to update item."); + inventoryItems.Add(modalItem); } } else { - // Create new ingredient via service - var dto = new CreateIngredientDTO - { - Name = modalItem.Name, - SKU = string.IsNullOrWhiteSpace(modalItem.SKU) ? null : modalItem.SKU, - CurrentQuantity = modalItem.CurrentQuantity, - Price = modalItem.Price, - Category = modalItem.Category, - ReorderLevel = modalItem.ReorderLevel, - UnitId = 1 // TODO: wire a real unit selector; use sensible default for now - }; - - var created = await InventorySvc.AddItemAsync(dto); - if (created != null) - { - inventoryItems.Add(created); - } + modalItem.Id = inventoryItems.Any() ? inventoryItems.Max(i => i.Id) + 1 : 1; + inventoryItems.Add(modalItem); + itemLocations[modalItem.Id] = "Dry Storage"; } CloseModal(); @@ -534,9 +616,7 @@ private async Task OrderItem(InventoryItemDTO item) { - //TODO - // Implement some type of order functionality - Console.WriteLine($"Ordering {item.Name}"); + await JSRuntime.InvokeVoidAsync("alert", $"Ordering {item.Name} coming soon."); } private async Task DeleteItem(InventoryItemDTO item) @@ -545,33 +625,58 @@ { return; } - try { - var success = await InventorySvc.DeleteItemAsync(item.Id); + inventoryItems.Remove(item); + FilterItems(); + } - if (success) { - inventoryItems.Remove(item); - FilterItems(); - } - else { - await JSRuntime.InvokeVoidAsync("alert", $"Failed to delete {item.Name}."); - } + private async Task ExportInventory() + { + await JSRuntime.InvokeVoidAsync("alert", "Export coming soon."); + } + + private async Task RefreshInventory() + { + await LoadInventoryData(); + await JSRuntime.InvokeVoidAsync("console.log", "Inventory refreshed"); + } + + private void ToggleQuickFilter() + { + stockStatus = stockStatus == "low" ? "" : "low"; + FilterItems(); + } + + private string SearchTerm + { + get => searchTerm; + set + { + searchTerm = value; + FilterItems(); } - catch (Exception ex) { - Console.Error.WriteLine($"Failed to delete item: {ex}"); - await JSRuntime.InvokeVoidAsync("alert", "An error occurred while deleting the item."); + } + + private string SelectedCategory + { + get => selectedCategory; + set + { + selectedCategory = value; + FilterItems(); } } -/* - // If needed immplement CSV/Excel export - private void ExportInventory() + + private string StockStatus { - Console.WriteLine("Exporting inventory..."); + get => stockStatus; + set + { + stockStatus = value; + FilterItems(); + } } -*/ [Inject] private IJSRuntime JSRuntime { get; set; } = default!; - [Inject] - private IInventoryManagementService InventorySvc { get; set; } = default!; } diff --git a/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryManagement.razor.css b/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryManagement.razor.css index b586867..0292664 100644 --- a/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryManagement.razor.css +++ b/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryManagement.razor.css @@ -1,451 +1,386 @@ +:root { + --ink: #1f2a37; + --muted: #6b7280; + --border: #e5e7eb; + --bg: #f6f7f9; + --card: #ffffff; + --green: #0a8f3c; + --green-dark: #087136; + --pill: #f0f2f5; + --shadow: 0 10px 30px rgba(31, 42, 55, 0.08); +} + .inventory-container { - max-width: 1400px; + max-width: 1280px; margin: 0 auto; - padding: 20px; - background: #f8f9fa; + padding: 24px 24px 40px; + background: var(--bg); min-height: 100vh; + font-family: "Source Sans 3", "Segoe UI", sans-serif; + color: var(--ink); } -.inventory-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 30px; - background: white; - padding: 30px; - border-radius: 12px; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); - position: relative; - overflow: hidden; +.inventory-container button, +.inventory-container .btn { + font-family: "Source Sans 3", "Segoe UI", sans-serif; + letter-spacing: 0.01em; + transition: all 0.2s ease; } -.inventory-header::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 4px; - background: #198754; +.inventory-header { + background: var(--card); + border-radius: 16px; + padding: 22px 26px; + box-shadow: var(--shadow); + border: 1px solid var(--border); + margin-bottom: 18px; } -.inventory-header h1 { - font-size: 2.5rem; - font-weight: 700; - color: #2c3e50; - margin: 0; - display: flex; - align-items: center; - gap: 12px; +.header-text h1 { + font-family: "Space Grotesk", "Source Sans 3", sans-serif; + font-size: 1.6rem; + margin: 0 0 4px; } -.inventory-header h1::before { - content: '📦'; - font-size: 2.2rem; +.header-text p { + margin: 0; + color: var(--muted); + font-size: 0.95rem; } -.header-actions { +.inventory-toolbar { display: flex; - gap: 12px; + gap: 16px; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + background: var(--card); + border-radius: 16px; + padding: 16px 18px; + border: 1px solid var(--border); + box-shadow: var(--shadow); + margin-bottom: 14px; } -.header-actions .btn { +.search-bar { display: flex; align-items: center; - gap: 8px; - padding: 12px 24px; - font-weight: 600; + gap: 10px; + flex: 1 1 320px; + background: #f9fafb; + border: 1px solid var(--border); border-radius: 12px; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - border: none; - position: relative; - overflow: hidden; -} - -.header-actions .btn::before { - content: ''; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent); - transition: left 0.5s; -} - -.header-actions .btn:hover::before { - left: 100%; -} - -.header-actions .btn:hover { - transform: translateY(-3px); - box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2); -} - -.header-actions .btn-success { - background: #198754; - border-color: #198754; + padding: 10px 14px; + min-width: 260px; } -.header-actions .btn-success:hover { - background: #157347; - border-color: #146c43; +.search-bar i { + color: var(--muted); } -.header-actions .btn-primary { - background: #198754; - border-color: #198754; -} - -.header-actions .btn-primary:hover { - background: #157347; - border-color: #146c43; +.search-bar input { + border: none; + outline: none; + background: transparent; + width: 100%; + font-size: 0.95rem; } -/* Filter Tabs */ -.filter-tabs { +.toolbar-actions { display: flex; - gap: 8px; - margin-bottom: 20px; - padding: 16px; - background: white; - border-radius: 12px; - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); + align-items: center; + gap: 10px; flex-wrap: wrap; } -.tab-btn { - padding: 12px 28px; - background: transparent; - border: 2px solid transparent; - color: #666; - font-weight: 600; - cursor: pointer; +.icon-btn { + width: 36px; + height: 36px; border-radius: 10px; - transition: all 0.3s ease; - position: relative; + border: 1px solid #d1fae5; + background: #ecfdf3; + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--green); + transition: all 0.2s ease; } -.tab-btn:hover { - color: #198754; - background: rgba(25, 135, 84, 0.1); +.icon-btn:hover { + color: #ffffff; + border-color: var(--green); + background: var(--green); } -.tab-btn.active { - background: #198754; - color: white; - box-shadow: 0 4px 12px rgba(25, 135, 84, 0.3); +.select-wrap { + position: relative; +} + +.select-wrap .form-select { + border-radius: 10px; + border: 1px solid var(--border); + padding: 8px 30px 8px 12px; + font-size: 0.9rem; + background-color: #ffffff; } -.btn-filter-list { - margin-left: auto; - padding: 12px 24px; - background: white; - border: 2px solid #dee2e6; +.btn-add { + background: var(--green); + color: #ffffff; + border: none; + padding: 9px 14px; border-radius: 10px; - color: #495057; font-weight: 600; - cursor: pointer; - display: flex; + display: inline-flex; align-items: center; gap: 8px; - transition: all 0.3s ease; -} - -.btn-filter-list:hover { - background: #198754; - color: white; - border-color: #198754; - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(25, 135, 84, 0.3); -} - -/* Filter Panel */ -.filter-panel { - background: white; - padding: 24px; - border-radius: 12px; - margin-bottom: 20px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); - border: 2px solid #f0f0f0; - animation: slideDown 0.3s ease; + transition: background 0.2s ease; + box-shadow: 0 8px 16px rgba(10, 143, 60, 0.18); } -@keyframes slideDown { - from { - opacity: 0; - transform: translateY(-10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.filter-row { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 16px; - align-items: end; +.btn-add:hover { + background: var(--green-dark); + transform: translateY(-1px); } -.filter-group { +.tab-row { display: flex; - flex-direction: column; + gap: 10px; + margin-bottom: 12px; + flex-wrap: wrap; } -.filter-group label { +.tab-btn { + border: 1px solid #d1fae5; + background: #f0fdf4; + padding: 8px 14px; + border-radius: 999px; font-weight: 600; - color: #495057; - margin-bottom: 6px; font-size: 0.9rem; + color: var(--green); + transition: all 0.2s ease; } -.filter-group .form-control { - padding: 8px 12px; - border: 1px solid #ced4da; - border-radius: 6px; - transition: border-color 0.3s ease; +.tab-btn:hover { + background: var(--green); + color: #ffffff; + border-color: var(--green); } -.filter-group .form-control:focus { - border-color: #198754; - box-shadow: 0 0 0 0.2rem rgba(25, 135, 84, 0.25); +.tab-btn.active { + background: var(--green); + color: #ffffff; + border-color: var(--green); } -/* Inventory Table */ .inventory-table-container { - background: white; + background: var(--card); border-radius: 16px; - box-shadow: 0 6px 24px rgba(0, 0, 0, 0.1); + border: 1px solid var(--border); + box-shadow: var(--shadow); overflow: hidden; - margin-bottom: 30px; - border: 1px solid #f0f0f0; } .inventory-table { width: 100%; border-collapse: collapse; + font-size: 0.92rem; } .inventory-table thead { - background: #198754; - color: white; - position: relative; -} - -.inventory-table thead::after { - content: ''; - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 2px; - background: #146c43; + background: var(--green); + color: #ffffff; } .inventory-table thead th { - padding: 18px 16px; + padding: 14px 12px; text-align: left; - font-weight: 700; - font-size: 0.85rem; + font-weight: 600; + font-size: 0.82rem; + letter-spacing: 0.02em; text-transform: uppercase; - letter-spacing: 1px; cursor: pointer; - user-select: none; - transition: all 0.3s ease; -} - -.inventory-table thead th:hover { - background: rgba(255, 255, 255, 0.15); - transform: scale(1.02); } .inventory-table thead th i { margin-left: 6px; - font-size: 0.75rem; opacity: 0.8; - transition: transform 0.3s ease; -} - -.inventory-table thead th:hover i { - transform: translateY(-2px); } .inventory-table tbody tr { - border-bottom: 1px solid #f0f0f0; - transition: all 0.3s ease; + border-bottom: 1px solid var(--border); + background: #ffffff; } .inventory-table tbody tr:hover { - background: rgba(25, 135, 84, 0.05); - transform: scale(1.01); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + background: #f9fafb; } -.inventory-table tbody tr.row-low-stock { - background: linear-gradient(90deg, #fff3cd 0%, #ffe9a6 100%); - border-left: 4px solid #ffc107; +.inventory-table tbody td { + padding: 14px 12px; + vertical-align: middle; } -.inventory-table tbody tr.row-low-stock:hover { - background: linear-gradient(90deg, #ffe9a6 0%, #ffd966 100%); +.checkbox-col { + width: 40px; + text-align: center; } -.inventory-table tbody tr.row-out-of-stock { - background: linear-gradient(90deg, #f8d7da 0%, #f5c2c7 100%); - border-left: 4px solid #dc3545; +.actions-col { + width: 120px; } -.inventory-table tbody tr.row-out-of-stock:hover { - background: linear-gradient(90deg, #f5c2c7 0%, #f1aeb5 100%); +.name-cell .item-name { + font-weight: 600; + color: var(--ink); } -.inventory-table tbody td { - padding: 18px 16px; - color: #333; - font-weight: 500; +.name-cell .item-sku { + font-size: 0.78rem; + color: var(--muted); + margin-top: 2px; } -.item-name { - display: flex; +.category-pill { + display: inline-flex; align-items: center; - font-weight: 500; + padding: 4px 10px; + border-radius: 999px; + font-size: 0.75rem; + font-weight: 600; + background: var(--pill); + color: var(--ink); } -.item-name .badge { - font-size: 0.7rem; - padding: 3px 8px; - border-radius: 4px; +.category-grains { + background: #f6c97a; } -/* Quantity Control */ -.qty-control { - display: flex; - align-items: center; - gap: 10px; - background: #f8f9fa; - padding: 6px 12px; - border-radius: 12px; - width: fit-content; +.category-dairy { + background: #c8a6ff; } -.qty-btn { - width: 36px; - height: 36px; - border: 2px solid #dee2e6; - background: white; - border-radius: 10px; +.category-oils { + background: #f1d06f; +} + +.category-vegetables { + background: #78c58a; +} + +.category-meat { + background: #f28a8a; +} + +.category-default { + background: #d5d7dc; +} + +.stock-cell { display: flex; align-items: center; - justify-content: center; - cursor: pointer; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - color: #495057; - font-weight: bold; + gap: 8px; } -.qty-btn:hover { - background: #198754; - color: white; - border-color: #198754; - transform: scale(1.15); - box-shadow: 0 4px 12px rgba(25, 135, 84, 0.3); +.stock-value { + font-weight: 600; } -.qty-btn:active { - transform: scale(0.9); +.stock-indicator { + font-size: 0.85rem; } -.qty-value { - min-width: 90px; - text-align: center; - font-weight: 700; - color: #333; - font-size: 1rem; +.stock-indicator.low { + color: #f59e0b; +} + +.stock-indicator.out { + color: #ef4444; } -/* Action Buttons */ .action-buttons { display: flex; gap: 8px; } .btn-action { - width: 36px; - height: 36px; - border: 1px solid #dee2e6; - background: white; - border-radius: 6px; - display: flex; + width: 32px; + height: 32px; + border-radius: 8px; + border: 1px solid #e0f2fe; + background: #eff6ff; + display: inline-flex; align-items: center; justify-content: center; - cursor: pointer; - transition: all 0.3s ease; - color: #495057; + color: #2563eb; + transition: all 0.2s ease; } .btn-action:hover { - background: #007bff; - color: white; - border-color: #007bff; - transform: translateY(-2px); + color: #ffffff; + border-color: #2563eb; + background: #2563eb; + transform: translateY(-1px); + box-shadow: 0 6px 12px rgba(37, 99, 235, 0.2); } -.btn-action.text-danger:hover { - background: #dc3545; - border-color: #dc3545; +.btn-action.delete:hover { + color: #ffffff; + border-color: #ef4444; + background: #ef4444; + box-shadow: 0 6px 12px rgba(239, 68, 68, 0.2); } -/* Inventory Stats */ -.inventory-stats { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 24px; - margin-top: 30px; +.table-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 16px; + background: #ffffff; } -.stat-card { - background: white; - padding: 28px; - border-radius: 16px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08); - text-align: center; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); - border: 2px solid transparent; - position: relative; - overflow: hidden; +.footer-count { + color: var(--muted); + font-size: 0.85rem; } -.stat-card::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 4px; - background: #198754; +.pagination { + display: flex; + align-items: center; + gap: 6px; } -.stat-card:hover { - transform: translateY(-8px) scale(1.02); - box-shadow: 0 12px 28px rgba(25, 135, 84, 0.25); - border-color: #198754; +.page-btn, +.page-nav { + width: 32px; + height: 32px; + border-radius: 8px; + border: 1px solid #d1fae5; + background: #f0fdf4; + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--green); + font-size: 0.85rem; } -.stat-label { - font-size: 0.85rem; - color: #666; - font-weight: 600; - margin-bottom: 12px; - text-transform: uppercase; - letter-spacing: 1px; +.page-btn:hover, +.page-nav:hover { + background: var(--green); + color: #ffffff; + border-color: var(--green); + box-shadow: 0 6px 12px rgba(10, 143, 60, 0.18); +} + +.page-btn.active { + background: var(--green); + border-color: var(--green); + color: #ffffff; } -.stat-value { - font-size: 2.5rem; - font-weight: 800; - color: #198754; +.page-ellipsis { + padding: 0 6px; + color: var(--muted); } /* Modal */ @@ -455,10 +390,9 @@ left: 0; width: 100%; height: 100%; - background: rgba(0, 0, 0, 0.6); - backdrop-filter: blur(5px); + background: rgba(15, 23, 42, 0.6); + backdrop-filter: blur(4px); z-index: 1040; - animation: fadeIn 0.3s ease; } .modal-dialog-custom { @@ -467,157 +401,92 @@ left: 50%; transform: translate(-50%, -50%); z-index: 1050; - width: 90%; - max-width: 600px; - animation: slideInModal 0.4s cubic-bezier(0.4, 0, 0.2, 1); -} - -@keyframes slideInModal { - from { - opacity: 0; - transform: translate(-50%, -40%); - } - to { - opacity: 1; - transform: translate(-50%, -50%); - } + width: 92%; + max-width: 560px; } .modal-content-custom { - background: white; - border-radius: 20px; - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + background: #ffffff; + border-radius: 18px; + border: 1px solid var(--border); overflow: hidden; - border: 2px solid #f0f0f0; + box-shadow: var(--shadow); } .modal-header-custom { - padding: 28px 32px; - background: #198754; - color: white; + padding: 18px 22px; + background: var(--green); + color: #ffffff; display: flex; justify-content: space-between; align-items: center; - position: relative; -} - -.modal-header-custom::after { - content: ''; - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 3px; - background: #146c43; } .modal-header-custom h3 { margin: 0; - font-size: 1.75rem; - font-weight: 700; - text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + font-size: 1.2rem; } .btn-close-custom { + border: none; background: rgba(255, 255, 255, 0.2); - border: 2px solid rgba(255, 255, 255, 0.3); - color: white; - font-size: 1.5rem; - cursor: pointer; - padding: 0; - width: 40px; - height: 40px; - display: flex; + color: #ffffff; + width: 32px; + height: 32px; + border-radius: 10px; + display: inline-flex; align-items: center; justify-content: center; - border-radius: 10px; - transition: all 0.3s ease; -} - -.btn-close-custom:hover { - background: rgba(255, 255, 255, 0.3); - transform: rotate(90deg) scale(1.1); } .modal-body-custom { - padding: 32px; - max-height: 60vh; - overflow-y: auto; + padding: 22px; } .modal-footer-custom { - padding: 24px 32px; - background: #f8f9fa; + padding: 14px 22px; + background: #f9fafb; display: flex; justify-content: flex-end; - gap: 12px; - border-top: 2px solid #f0f0f0; + gap: 10px; + border-top: 1px solid var(--border); } -/* Animations */ -@keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } +.modal-footer-custom .btn { + border-radius: 10px; + font-weight: 600; + box-shadow: 0 6px 12px rgba(31, 41, 55, 0.1); } -@keyframes slideIn { - from { - transform: translate(-50%, -60%); - opacity: 0; - } - to { - transform: translate(-50%, -50%); - opacity: 1; - } +.modal-footer-custom .btn-secondary { + background: #e5e7eb; + border-color: #e5e7eb; + color: #111827; } -@keyframes pulse { - 0%, 100% { - transform: scale(1); - } - 50% { - transform: scale(1.05); - } +.modal-footer-custom .btn-secondary:hover { + background: #d1d5db; + border-color: #d1d5db; } -/* Responsive */ -@media (max-width: 768px) { - .inventory-header { - flex-direction: column; - align-items: flex-start; - gap: 16px; - } - - .filter-tabs { - overflow-x: auto; - -webkit-overflow-scrolling: touch; - } - - .inventory-table-container { - overflow-x: auto; - } - - .inventory-table { - min-width: 800px; - } +.modal-footer-custom .btn-success { + background: var(--green); + border-color: var(--green); +} - .inventory-stats { - grid-template-columns: repeat(2, 1fr); - } +.modal-footer-custom .btn-success:hover { + background: var(--green-dark); + border-color: var(--green-dark); } /* Loading Spinner */ .spinner-border { - width: 4rem; - height: 4rem; - border-width: 0.4rem; - border-color: #198754; + width: 3rem; + height: 3rem; + border-width: 0.3rem; + border-color: var(--green); border-right-color: transparent; - animation: spin 0.75s linear infinite; + animation: spin 0.8s linear infinite; } @keyframes spin { @@ -626,10 +495,21 @@ } } -.item-name .badge { - font-size: 0.7rem; - padding: 4px 10px; - border-radius: 6px; - font-weight: 700; - animation: pulse 2s infinite; +@media (max-width: 900px) { + .inventory-toolbar { + flex-direction: column; + align-items: stretch; + } + + .toolbar-actions { + justify-content: flex-start; + } + + .inventory-table-container { + overflow-x: auto; + } + + .inventory-table { + min-width: 980px; + } } diff --git a/CulinaryCommandApp/wwwroot/css/app.css b/CulinaryCommandApp/wwwroot/css/app.css index 95a4aba..308ec90 100644 --- a/CulinaryCommandApp/wwwroot/css/app.css +++ b/CulinaryCommandApp/wwwroot/css/app.css @@ -1,3 +1,6 @@ +@import url("https://fonts.googleapis.com/css2?family=Spline+Sans:wght@400;500;600;700&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Source+Sans+3:wght@400;600;700&family=Space+Grotesk:wght@500;600&display=swap"); + html, body { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; From 479fcd287733352d653dac98ee953e15561b9847 Mon Sep 17 00:00:00 2001 From: Anthony Phan <131195703+antphan12@users.noreply.github.com> Date: Tue, 17 Feb 2026 00:18:22 -0600 Subject: [PATCH 3/7] Added color and designs to buttons --- .../Pages/Inventory/InventoryManagement.razor | 33 +++- .../Inventory/InventoryManagement.razor.css | 154 +++++++++++++----- 2 files changed, 144 insertions(+), 43 deletions(-) diff --git a/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryManagement.razor b/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryManagement.razor index 920e939..a99416a 100644 --- a/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryManagement.razor +++ b/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryManagement.razor @@ -3,6 +3,7 @@ @using CulinaryCommand.Inventory.DTOs @using Microsoft.JSInterop +@using Microsoft.AspNetCore.Components.Forms @using System.Threading.Tasks
@@ -244,6 +245,24 @@
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +