diff --git a/CulinaryCommandApp/Data/Enums/Category.cs b/CulinaryCommandApp/Data/Enums/Category.cs index 4fd595a..3c67d50 100644 --- a/CulinaryCommandApp/Data/Enums/Category.cs +++ b/CulinaryCommandApp/Data/Enums/Category.cs @@ -9,11 +9,21 @@ namespace CulinaryCommandApp.Data.Enums public class Category { public const string Produce = "Produce"; - public const string Dairy = "Dairy"; - public const string Meat = "Meat"; + public const string DairyEggs = "Dairy & Eggs"; + public const string Cheese = "Cheese"; + public const string MeatPoultry = "Meat & Poultry"; + public const string Seafood = "Seafood"; + public const string Bakery = "Bakery"; + public const string PastaGrains = "Pasta & Grains"; public const string DryGoods = "Dry Goods"; - public const string Beverages = "Beverages"; - public const string Condiments = "Condiments"; public const string Spices = "Spices"; + public const string Condiments = "Condiments"; + public const string SyrupsMixes = "Syrups & Mixes"; + public const string Desserts = "Desserts"; + public const string Beer = "Beer"; + public const string Wine = "Wine"; + public const string Spirits = "Spirits"; + public const string Beverages = "Beverages"; + public const string Prepared = "Prepared"; } } \ No newline at end of file diff --git a/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryCatalog.razor b/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryCatalog.razor index 6e02cab..6f6a444 100644 --- a/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryCatalog.razor +++ b/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryCatalog.razor @@ -15,6 +15,7 @@ @inject EnumService EnumService @inject IUserContextService UserCtx @inject NavigationManager Nav +@inject IJSRuntime JS
@if (!_ready) @@ -32,7 +33,7 @@

Inventory Catalog

-

@(LocationState.CurrentLocation is not null ? $"{LocationState.CurrentLocation.Name} — ingredient catalog" : "Manage your ingredient catalog")

+

@(LocationState.CurrentLocation is not null ? $"{LocationState.CurrentLocation.Name}" : "Manage your ingredient catalog")

@@ -85,18 +86,17 @@ Item Name Category - Unit - Cost/Unit + Unit Supplier Notes - Actions + @if (isLoading) { - +
Loading...
@@ -106,7 +106,7 @@ else if (!pagedItems.Any()) { - +

No items found

@@ -115,7 +115,7 @@ { @foreach (var item in pagedItems) { - + @@ -128,19 +128,13 @@ @item.Category - @item.Unit - $@item.Price.ToString("F2") + @item.Unit @(item.VendorName ?? "—") @(string.IsNullOrWhiteSpace(item.Notes) ? "-" : item.Notes) - -
- - -
+ + } @@ -171,6 +165,24 @@
+ @if (_openMenuId is not null) + { +
+
+ @{ var _menuItem = pagedItems.FirstOrDefault(i => i.Id == _openMenuId); } + @if (_menuItem is not null) + { +
@_menuItem.Name
+ + + } +
+ } + } @* end @if (LocationState.CurrentLocation is not null) *@ } @* end else (_allowed) *@ @@ -264,6 +276,29 @@ private HashSet selectedIds = new(); private List categories = new(); + private int? _openMenuId = null; + private double _menuX = 0; + private double _menuY = 0; + + private async Task ToggleMenu(int id, MouseEventArgs e) + { + if (_openMenuId == id) + { + _openMenuId = null; + return; + } + _openMenuId = id; + // Clamp to viewport so the dialog never clips off-screen + var dialogWidth = 190; + var dialogHeight = 110; + var viewportWidth = await JS.InvokeAsync("eval", "window.innerWidth"); + var viewportHeight = await JS.InvokeAsync("eval", "window.innerHeight"); + _menuX = Math.Min(e.ClientX - dialogWidth, viewportWidth - dialogWidth - 8); + _menuY = Math.Min(e.ClientY, viewportHeight - dialogHeight - 8); + } + + private void CloseMenu() => _openMenuId = null; + private bool showModal = false; private bool _saving = false; private bool _ready = false; @@ -399,12 +434,24 @@ private string GetCategoryClass(string? category) => category switch { - "Grains & Pasta" => "category-grains", - "Dairy" => "category-dairy", - "Oils & Sauces" => "category-oils", - "Vegetables" => "category-vegetables", + "Produce" => "category-produce", + "Dairy & Eggs" => "category-dairy-eggs", + "Cheese" => "category-cheese", "Meat & Poultry" => "category-meat", - _ => "category-default" + "Seafood" => "category-seafood", + "Bakery" => "category-bakery", + "Pasta & Grains" => "category-pasta-grains", + "Dry Goods" => "category-dry-goods", + "Spices" => "category-spices", + "Condiments" => "category-condiments", + "Syrups & Mixes" => "category-syrups", + "Desserts" => "category-desserts", + "Beer" => "category-beer", + "Wine" => "category-wine", + "Spirits" => "category-spirits", + "Beverages" => "category-beverages", + "Prepared" => "category-prepared", + _ => "category-default" }; private bool IsPageSelected => pagedItems.Count > 0 && pagedItems.All(i => selectedIds.Contains(i.Id)); diff --git a/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryCatalog.razor.css b/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryCatalog.razor.css index 88288a0..c676ba5 100644 --- a/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryCatalog.razor.css +++ b/CulinaryCommandApp/Inventory/Pages/Inventory/InventoryCatalog.razor.css @@ -28,24 +28,24 @@ } .inventory-header { - background: var(--card); - border-radius: 16px; - padding: 22px 26px; - box-shadow: var(--shadow); - border: 1px solid var(--border); - margin-bottom: 18px; + padding: 8px 0 18px 16px; + border-left: 4px solid #2ca259; + margin-bottom: 20px; } .header-text h1 { font-family: "Space Grotesk", "Source Sans 3", sans-serif; - font-size: 1.6rem; + font-size: 1.5rem; + font-weight: 700; margin: 0 0 4px; + color: #1f2a37; + letter-spacing: -0.01em; } .header-text p { margin: 0; - color: var(--muted); - font-size: 0.95rem; + color: #6b7280; + font-size: 0.88rem; } .inventory-toolbar { @@ -192,7 +192,6 @@ } .inventory-table thead { - background: var(--green); color: var(--ink); } @@ -204,6 +203,7 @@ letter-spacing: 0.02em; text-transform: uppercase; cursor: pointer; + background-color: #2ca259; } .inventory-table thead th i { @@ -230,8 +230,9 @@ text-align: center; } -.actions-col { - width: 100px; +.ellipsis-col { + width: 40px; + text-align: center; } .name-cell .item-name { @@ -245,85 +246,136 @@ margin-top: 2px; } +.unit-col, +.inventory-table thead th.unit-col { + text-align: center; +} + +.unit-badge { + display: inline-block; + padding: 3px 9px; + border-radius: 6px; + border: 1px solid #d1d5db; + font-size: 0.78rem; + font-family: "Source Sans 3", "Segoe UI", sans-serif; + font-weight: 500; + color: #4b5563; + letter-spacing: 0.03em; + background: transparent; +} + .category-pill { display: inline-flex; align-items: center; - padding: 6px 12px; + padding: 4px 10px; border-radius: 999px; font-size: 0.75rem; - font-weight: 700; - background: var(--pill); - color: #1f2a37; -} - -.category-grains { - background: #fbbf24; - color: #78350f; + font-weight: 600; + color: #374151; +} + +.category-produce { background: #dcfce7; } +.category-dairy-eggs { background: #fef9c3; } +.category-cheese { background: #fef3c7; } +.category-meat { background: #ffe4e6; } +.category-seafood { background: #dbeafe; } +.category-bakery { background: #fce7f3; } +.category-pasta-grains { background: #fef08a; } +.category-dry-goods { background: #e5e7eb; } +.category-spices { background: #fde8d8; } +.category-condiments { background: #d1fae5; } +.category-syrups { background: #ede9fe; } +.category-desserts { background: #fce7f3; } +.category-beer { background: #fef3c7; } +.category-wine { background: #f3e8ff; } +.category-spirits { background: #e0e7ff; } +.category-beverages { background: #cffafe; } +.category-prepared { background: #f1f5f9; } +.category-default { background: #e5e7eb; } + +.ellipsis-btn { + width: 28px; + height: 28px; + border-radius: 6px; + border: none; + background: transparent; + color: #9ca3af; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 1rem; + opacity: 0; + transition: opacity 0.15s ease, background 0.15s ease; + cursor: pointer; } -.category-dairy { - background: #a855f7; - color: #fafafa; +.ellipsis-btn:hover { + background: transparent; + color: #1f2a37; } -.category-oils { - background: #eab308; - color: #713f12; +.data-row:hover .ellipsis-btn { + opacity: 1; } -.category-vegetables { - background: #22c55e; - color: #fafafa; +.row-menu-backdrop { + position: fixed; + inset: 0; + z-index: 200; } -.category-meat { - background: #ef4444; - color: #fafafa; +.row-menu-dialog { + position: fixed; + z-index: 201; + background: #ffffff; + border: 1px solid #e5e7eb; + border-radius: 14px; + box-shadow: 0 20px 50px rgba(31, 42, 55, 0.15); + padding: 8px; + min-width: 180px; + display: flex; + flex-direction: column; + gap: 2px; } -.category-default { - background: #6b7280; - color: #ffffff; +.row-menu-label { + font-size: 0.78rem; + font-weight: 600; + color: #9ca3af; + padding: 6px 12px 8px; + letter-spacing: 0.04em; + text-transform: uppercase; + border-bottom: 1px solid #f3f4f6; + margin-bottom: 4px; } -.action-buttons { +.row-menu-action { display: flex; - gap: 8px; -} - -.btn-action { - width: 32px; - height: 32px; - border-radius: 8px; - border: 1px solid #d1d5db; - background: #f3f4f6; - display: inline-flex; align-items: center; - justify-content: center; - color: var(--ink); - transition: all 0.2s ease; - font-weight: 600; + gap: 10px; + padding: 9px 12px; + border: none; + background: transparent; + border-radius: 8px; + font-size: 0.9rem; + color: #1f2a37; + font-family: "Source Sans 3", "Segoe UI", sans-serif; + cursor: pointer; + text-align: left; + transition: background 0.15s ease; + width: 100%; } -.btn-action:hover { - color: var(--ink); - border-color: #9ca3af; - background: #e5e7eb; - transform: translateY(-1px); - box-shadow: 0 6px 12px rgba(107, 114, 128, 0.2); +.row-menu-action:hover { + background: #f9fafb; } -.btn-action.delete { - background: #ef4444; - border-color: #ef4444; - color: var(--ink); +.row-menu-action.delete { + color: #dc2626; } -.btn-action.delete:hover { - color: var(--ink); - border-color: #dc2626; - background: #dc2626; - box-shadow: 0 6px 12px rgba(239, 68, 68, 0.3); +.row-menu-action.delete:hover { + background: #fef2f2; } .table-footer { @@ -355,7 +407,7 @@ display: inline-flex; align-items: center; justify-content: center; - color: var(--green); + color: #2ca259; font-size: 0.85rem; font-weight: 600; transition: all 0.2s ease; @@ -363,15 +415,15 @@ .page-btn:hover, .page-nav:hover { - background: var(--green); + background: #2ca259; color: #ffffff; - border-color: var(--green); + border-color: #2ca259; box-shadow: 0 6px 12px rgba(10, 143, 60, 0.25); } .page-btn.active { - background: var(--green); - border-color: var(--green); + background: #2ca259; + border-color: #2ca259; color: #ffffff; box-shadow: 0 6px 12px rgba(10, 143, 60, 0.25); } diff --git a/CulinaryCommandApp/Services/EnumService.cs b/CulinaryCommandApp/Services/EnumService.cs index f20f6f0..2596db8 100644 --- a/CulinaryCommandApp/Services/EnumService.cs +++ b/CulinaryCommandApp/Services/EnumService.cs @@ -9,12 +9,22 @@ public List GetCategories() return new List { Category.Produce, - Category.Dairy, - Category.Meat, + Category.DairyEggs, + Category.Cheese, + Category.MeatPoultry, + Category.Seafood, + Category.Bakery, + Category.PastaGrains, Category.DryGoods, - Category.Beverages, + Category.Spices, Category.Condiments, - Category.Spices + Category.SyrupsMixes, + Category.Desserts, + Category.Beer, + Category.Wine, + Category.Spirits, + Category.Beverages, + Category.Prepared }; }