From a669aad3c463f098435fd9caa15374c8dd26366f Mon Sep 17 00:00:00 2001 From: Matayas Durr Date: Fri, 17 Apr 2026 16:48:41 -0500 Subject: [PATCH 1/3] Update my tasks recipe navigation and stock actions --- .../Components/Custom/PrepTasksPanel.razor | 7 + .../Recipe/Pages/RecipeView.razor | 124 ++++++++++++++++-- .../Recipe/Pages/RecipeView.razor.css | 40 ++++++ 3 files changed, 157 insertions(+), 14 deletions(-) diff --git a/CulinaryCommandApp/Components/Custom/PrepTasksPanel.razor b/CulinaryCommandApp/Components/Custom/PrepTasksPanel.razor index 6216723..97681c5 100644 --- a/CulinaryCommandApp/Components/Custom/PrepTasksPanel.razor +++ b/CulinaryCommandApp/Components/Custom/PrepTasksPanel.razor @@ -2,6 +2,7 @@ @using CulinaryCommand.Data.Entities @using CulinaryCommand.Data.Enums @using Microsoft.AspNetCore.Components +@inject NavigationManager Nav
@@ -208,6 +209,12 @@ private void OpenRecipe(Tasks t) { + if (t.RecipeId.HasValue) + { + Nav.NavigateTo($"/recipes/view/{t.RecipeId.Value}?from=mytasks"); + return; + } + selectedTask = t; showRecipe = true; } diff --git a/CulinaryCommandApp/Recipe/Pages/RecipeView.razor b/CulinaryCommandApp/Recipe/Pages/RecipeView.razor index cca1fbc..55d1248 100644 --- a/CulinaryCommandApp/Recipe/Pages/RecipeView.razor +++ b/CulinaryCommandApp/Recipe/Pages/RecipeView.razor @@ -1,10 +1,13 @@ @page "/recipes/view/{id:int}" @rendermode InteractiveServer +@using CulinaryCommandApp.Inventory.DTOs +@using CulinaryCommandApp.Inventory.Services.Interfaces @using CulinaryCommand.Services.UserContextSpace @using CulinaryCommandApp.Recipe.Entities @inject IRecipeService RecipeService +@inject IInventoryManagementService InventoryService @inject IUserContextService UserCtx @inject NavigationManager Nav @@ -32,7 +35,7 @@ @* ── Back button ── *@ @@ -189,6 +192,30 @@ else { @(line.Ingredient?.Name ?? "—") + @if (HasInventoryIngredient(line)) + { +
+ @if (IsOutOfStock(line)) + { + Out of Stock + + } + else + { + + } +
+ } } @@ -198,7 +225,7 @@ @(line.Ingredient?.Vendor?.Name ?? "—") - @if (line.IngredientId.HasValue) + @if (HasInventoryIngredient(line)) { @@ -272,6 +299,7 @@ @code { [Parameter] public int Id { get; set; } + [SupplyParameterFromQuery(Name = "from")] public string? From { get; set; } private Recipe? _model; private bool _ready; @@ -279,6 +307,12 @@ private bool _priv; private string? _sourceRestaurant; private decimal _scaleServings = 1; + private int[] _allowedLocationIds = []; + private readonly HashSet _updatingIngredientIds = new(); + private string BackHref => + string.Equals(From, "mytasks", StringComparison.OrdinalIgnoreCase) + ? "/tasks" + : "/recipes"; protected override async Task OnInitializedAsync() { @@ -299,18 +333,8 @@ _priv = string.Equals(ctx.User.Role, "Admin", StringComparison.OrdinalIgnoreCase) || string.Equals(ctx.User.Role, "Manager", StringComparison.OrdinalIgnoreCase); - var allowedLocationIds = ctx.AccessibleLocations.Select(l => l.Id); - _model = await RecipeService.GetByIdAsync(Id, allowedLocationIds); - _notFound = _model is null; - - if (_model is not null) - { - _sourceRestaurant = ctx.AccessibleLocations - .FirstOrDefault(l => l.Id == _model.LocationId)?.Name; - - // Default scale = original yield - _scaleServings = _model.YieldAmount ?? 1; - } + _allowedLocationIds = ctx.AccessibleLocations.Select(l => l.Id).ToArray(); + await LoadRecipeAsync(ctx); _ready = true; } @@ -338,4 +362,76 @@ var scaled = originalQty / baseYield.Value * _scaleServings; return scaled.ToString("G29"); } + + private bool HasInventoryIngredient(RecipeIngredient line) => + !line.SubRecipeId.HasValue && + line.IngredientId.HasValue && + line.Ingredient is not null; + + private bool IsUpdatingIngredient(RecipeIngredient line) => + HasInventoryIngredient(line) && + _updatingIngredientIds.Contains(line.Ingredient!.Id); + + private bool IsOutOfStock(RecipeIngredient line) => + HasInventoryIngredient(line) && + line.Ingredient!.StockQuantity <= 0; + + private async Task MarkOutOfStockAsync(RecipeIngredient line) + => await SetIngredientStockAsync(line, 0); + + private async Task MarkInStockAsync(RecipeIngredient line) + => await SetIngredientStockAsync(line, 1); + + private async Task SetIngredientStockAsync(RecipeIngredient line, decimal quantity) + { + if (!HasInventoryIngredient(line)) + return; + + var ingredient = line.Ingredient!; + if (!_updatingIngredientIds.Add(ingredient.Id)) + return; + + try + { + var currentScale = _scaleServings; + + await InventoryService.UpdateItemAsync(new InventoryItemDTO + { + Id = ingredient.Id, + Name = ingredient.Name, + SKU = ingredient.Sku ?? string.Empty, + Category = ingredient.Category, + CurrentQuantity = quantity, + UnitId = ingredient.UnitId, + Price = ingredient.Price ?? 0m, + ReorderLevel = ingredient.ReorderLevel, + Notes = ingredient.Notes, + VendorId = ingredient.VendorId, + StorageLocationId = ingredient.StorageLocationId + }); + + ingredient.StockQuantity = quantity; + await LoadRecipeAsync(); + _scaleServings = currentScale; + } + finally + { + _updatingIngredientIds.Remove(ingredient.Id); + } + } + + private async Task LoadRecipeAsync(UserContext? ctx = null) + { + _model = await RecipeService.GetByIdAsync(Id, _allowedLocationIds); + _notFound = _model is null; + + if (_model is null) + return; + + ctx ??= await UserCtx.GetAsync(); + _sourceRestaurant = ctx.AccessibleLocations + .FirstOrDefault(l => l.Id == _model.LocationId)?.Name; + + _scaleServings = _model.YieldAmount ?? 1; + } } diff --git a/CulinaryCommandApp/Recipe/Pages/RecipeView.razor.css b/CulinaryCommandApp/Recipe/Pages/RecipeView.razor.css index 41c600a..6c555d4 100644 --- a/CulinaryCommandApp/Recipe/Pages/RecipeView.razor.css +++ b/CulinaryCommandApp/Recipe/Pages/RecipeView.razor.css @@ -368,6 +368,46 @@ color: #1f2a37; } +.rv-stock-meta { + display: flex; + align-items: center; + gap: 10px; + margin-top: 4px; + flex-wrap: wrap; +} + +.rv-stock-state { + font-size: 0.77rem; + font-weight: 700; + color: #b42318; + text-transform: uppercase; + letter-spacing: 0.03em; +} + +.rv-stock-action { + padding: 0; + border: 0; + background: transparent; + color: #0a8f3c; + font: inherit; + font-size: 0.8rem; + font-weight: 600; + line-height: 1.2; + text-decoration: underline; + text-underline-offset: 0.14em; + cursor: pointer; +} + +.rv-stock-action:hover:not(:disabled) { + color: #087136; +} + +.rv-stock-action:disabled { + color: #9ca3af; + cursor: default; + text-decoration: none; +} + .rv-subrecipe-pill { display: inline-flex; align-items: center; From 2a75c867c10d7bb8fab982e63368605af5e14bb8 Mon Sep 17 00:00:00 2001 From: Matayas Durr Date: Fri, 17 Apr 2026 16:59:18 -0500 Subject: [PATCH 2/3] Add start-all action for pending tasks --- .../Pages/Assignments/AdminAssignTask.razor | 15 ++++++++++++++- .../Pages/Assignments/TaskBoardPanel.razor | 13 +++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CulinaryCommandApp/Components/Pages/Assignments/AdminAssignTask.razor b/CulinaryCommandApp/Components/Pages/Assignments/AdminAssignTask.razor index f9f3f67..a21de41 100644 --- a/CulinaryCommandApp/Components/Pages/Assignments/AdminAssignTask.razor +++ b/CulinaryCommandApp/Components/Pages/Assignments/AdminAssignTask.razor @@ -147,7 +147,8 @@ else OnMarkInProgress="MarkInProgress" OnMarkComplete="MarkComplete" OnBumpTask="BumpTask" - OnDeleteTask="DeleteTask" /> + OnDeleteTask="DeleteTask" + OnStartAllPending="StartAllPending" />
@@ -789,6 +790,18 @@ else private Task MarkInProgress(int id) => MoveToStatus(id, WorkTaskStatus.InProgress); + private async Task StartAllPending() + { + var pendingTaskIds = TasksByStatus(WorkTaskStatus.Pending) + .Select(t => t.Id) + .ToList(); + + foreach (var taskId in pendingTaskIds) + { + await MarkInProgress(taskId); + } + } + private async Task BumpTask(int id) { await TaskService.BumpDueDateAsync(id, 1); diff --git a/CulinaryCommandApp/Components/Pages/Assignments/TaskBoardPanel.razor b/CulinaryCommandApp/Components/Pages/Assignments/TaskBoardPanel.razor index 1345623..7844055 100644 --- a/CulinaryCommandApp/Components/Pages/Assignments/TaskBoardPanel.razor +++ b/CulinaryCommandApp/Components/Pages/Assignments/TaskBoardPanel.razor @@ -35,6 +35,16 @@ + @if (status == WorkTaskStatus.Pending && tasks.Any()) + { +
+ +
+ } +
@if (!tasks.Any()) { @@ -125,6 +135,7 @@ [Parameter] public EventCallback OnMarkComplete { get; set; } [Parameter] public EventCallback OnBumpTask { get; set; } [Parameter] public EventCallback OnDeleteTask { get; set; } + [Parameter] public EventCallback OnStartAllPending { get; set; } private string TaskBoardSearchValue { @@ -139,4 +150,6 @@ private Task HandleBumpTask(int taskId) => OnBumpTask.InvokeAsync(taskId); private Task HandleDeleteTask(int taskId) => OnDeleteTask.InvokeAsync(taskId); + + private Task HandleStartAllPending() => OnStartAllPending.InvokeAsync(); } From d9ea0537eb6f43f1b88e3475ed7e355098f25a6c Mon Sep 17 00:00:00 2001 From: Matayas Durr Date: Fri, 17 Apr 2026 17:11:03 -0500 Subject: [PATCH 3/3] Add header accent line to management pages --- .../Components/Pages/FeedbackPage.razor | 23 +++++++++++++-- .../Components/Pages/Users/UserList.razor | 2 +- .../Pages/Inventory/InventoryCatalog.razor | 29 ++++++++++++++++--- .../Pages/PurchaseOrderList.razor | 2 +- .../Recipe/Pages/RecipeList.razor | 2 +- 5 files changed, 49 insertions(+), 9 deletions(-) diff --git a/CulinaryCommandApp/Components/Pages/FeedbackPage.razor b/CulinaryCommandApp/Components/Pages/FeedbackPage.razor index a0a1ebb..7713820 100644 --- a/CulinaryCommandApp/Components/Pages/FeedbackPage.razor +++ b/CulinaryCommandApp/Components/Pages/FeedbackPage.razor @@ -4,7 +4,26 @@ @inject IFeedbackService FeedbackSvc