diff --git a/CulinaryCommandApp/Components/Custom/UserLocationTagPicker.razor b/CulinaryCommandApp/Components/Custom/UserLocationTagPicker.razor index ed240c3..aaefd25 100644 --- a/CulinaryCommandApp/Components/Custom/UserLocationTagPicker.razor +++ b/CulinaryCommandApp/Components/Custom/UserLocationTagPicker.razor @@ -1,5 +1,6 @@ @using CulinaryCommand.Data.Entities @inject ILocationService LocationService +@inject AuthService Auth @rendermode InteractiveServer
@@ -52,7 +53,7 @@ protected override async Task OnInitializedAsync() { - AllLocations = await LocationService.GetAllLocationsAsync(); + AllLocations = await LocationService.GetLocationsByCompanyAsync(Auth.CurrentUser?.CompanyId); RefreshLists(); } diff --git a/CulinaryCommandApp/Components/Layout/MainLayout.razor b/CulinaryCommandApp/Components/Layout/MainLayout.razor index 1c00367..d507af9 100644 --- a/CulinaryCommandApp/Components/Layout/MainLayout.razor +++ b/CulinaryCommandApp/Components/Layout/MainLayout.razor @@ -3,6 +3,7 @@ @using CulinaryCommand.Services @inject CulinaryCommand.Services.AuthService Auth @inject NavigationManager Nav +@inject LocationState LocationState @using System.Threading.Tasks @rendermode InteractiveServer @@ -30,6 +31,8 @@ if (!firstRender || _hydrated) return; + await LocationState.HydrateAsync(); + var path = Nav.Uri.ToLowerInvariant(); if (!path.Contains("/signin") && !path.Contains("/signup") && !path.Contains("/adminsignup")) { diff --git a/CulinaryCommandApp/Components/Layout/NavMenu.razor b/CulinaryCommandApp/Components/Layout/NavMenu.razor index fc56718..49be8e9 100644 --- a/CulinaryCommandApp/Components/Layout/NavMenu.razor +++ b/CulinaryCommandApp/Components/Layout/NavMenu.razor @@ -27,62 +27,20 @@
- @* *@ - - - @* *@ - - + @if (Auth.UserRole == "Employee") + { - - - - @if (Auth.UserRole == "Admin") - { - - + } + else { @* admin or manager *@ - - - } - else if (Auth.UserRole == "Manager") - { + + + } + @if (Auth.UserRole == "Admin") + { } diff --git a/CulinaryCommandApp/Components/Pages/AdminView.razor b/CulinaryCommandApp/Components/Pages/AdminView.razor index d40d810..307be3f 100644 --- a/CulinaryCommandApp/Components/Pages/AdminView.razor +++ b/CulinaryCommandApp/Components/Pages/AdminView.razor @@ -16,7 +16,7 @@ + Link="/inventory-management" /> diff --git a/CulinaryCommandApp/Components/Pages/Assignments/MyTasks.razor b/CulinaryCommandApp/Components/Pages/Assignments/MyTasks.razor index b74a754..31604c8 100644 --- a/CulinaryCommandApp/Components/Pages/Assignments/MyTasks.razor +++ b/CulinaryCommandApp/Components/Pages/Assignments/MyTasks.razor @@ -79,6 +79,7 @@ else selectedLocationId = LocationState.CurrentLocation?.Id; + LocationState.OnChange -= HandleLocationStateChanged; LocationState.OnChange += HandleLocationStateChanged; await LoadTasksAsync(); @@ -104,10 +105,11 @@ else allTasks = await TaskService.GetForUserAsync(Auth.UserId.Value, selectedLocationId); prepTasks = allTasks - .Where(t => t.Kind == WorkTaskKind.PrepFromRecipe) + @* .Where(t => t.Kind == WorkTaskKind.PrepFromRecipe) *@ .Where(t => !string.Equals(t.Status, "Completed", StringComparison.OrdinalIgnoreCase)) .OrderBy(t => t.DueDate) .ToList(); + } finally { diff --git a/CulinaryCommandApp/Components/Pages/Dashboard.razor b/CulinaryCommandApp/Components/Pages/Dashboard.razor index 9ec92df..c2b1390 100644 --- a/CulinaryCommandApp/Components/Pages/Dashboard.razor +++ b/CulinaryCommandApp/Components/Pages/Dashboard.razor @@ -149,12 +149,12 @@ else protected override async Task OnAfterRenderAsync(bool firstRender) { - + if (!firstRender) return; await Auth.EnsureHydratedAsync(); // safe here (interactive) _ready = true; StateHasChanged(); - if (!_aiLoadedOnce) + if (!_aiLoadedOnce && Auth.IsSignedIn) { _aiLoadedOnce = true; _aiLoading = true; @@ -162,25 +162,29 @@ else // Get analysis: - 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)) + try { - try + 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 - { - _aiAnalysisObj = null; - } } + catch(Exception e) + { + Console.WriteLine($"AI Analysis failed: {e.Message}"); + _aiAnalysisObj = null; + _aiAnalysis = null; - _aiLoading = false; - StateHasChanged(); + } + finally { + _aiLoading = false; + StateHasChanged(); + } } } diff --git a/CulinaryCommandApp/Components/Pages/EmployeeView.razor b/CulinaryCommandApp/Components/Pages/EmployeeView.razor index 0f988e2..b066f23 100644 --- a/CulinaryCommandApp/Components/Pages/EmployeeView.razor +++ b/CulinaryCommandApp/Components/Pages/EmployeeView.razor @@ -1,17 +1,36 @@ @rendermode InteractiveServer +@implements IDisposable

Employee Dashboard

-

Welcome, @Auth.CurrentUser.Name!

+

Welcome, @(Auth.CurrentUser?.Name ?? "User")!

  • View your active tasks and prep items
  • -
  • Check your assigned location: @LocationState.CurrentLocation
  • +
  • Check your assigned location: @(LocationState.CurrentLocation?.Name ?? "Not assigned")
  • Submit updates to your manager
@code { [Inject] private CulinaryCommand.Services.AuthService Auth { get; set; } = default!; - [Inject] private CulinaryCommand.Services.LocationState LocationState { get; set; } = default!; + [Inject] private CulinaryCommand.Services.LocationState LocationState { get; set; } = default!; + private async void HandleLocationStateChanged() + { + await InvokeAsync(async () => + { + StateHasChanged(); + }); + } + + protected override void OnInitialized() + { + // Subscribe to the change event + LocationState.OnChange += HandleLocationStateChanged; + } + + public void Dispose() + { + LocationState.OnChange -= HandleLocationStateChanged; + } } diff --git a/CulinaryCommandApp/Components/Pages/ManagerView.razor b/CulinaryCommandApp/Components/Pages/ManagerView.razor index ee7c476..b9f6768 100644 --- a/CulinaryCommandApp/Components/Pages/ManagerView.razor +++ b/CulinaryCommandApp/Components/Pages/ManagerView.razor @@ -34,7 +34,7 @@ + Link="/inventory-management" /> diff --git a/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/LocationEmployeeView.razor b/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/LocationEmployeeView.razor index 76ebd4a..9538df1 100644 --- a/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/LocationEmployeeView.razor +++ b/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/LocationEmployeeView.razor @@ -1,6 +1,20 @@ @using CulinaryCommand.Data.Entities @inject AuthService Auth -
+ +@foreach (var loc in Locations) +{ +
+
+
+
@loc.Name
+

@loc.Address

+ @loc.City, @loc.State @loc.ZipCode +
+
+
+} + +@*

@Auth.Company

@@ -15,8 +29,8 @@

@Location.City, @Location.State @Location.ZipCode

} -
+
*@ @code { - [Parameter] public Location? Location { get; set; } + [Parameter] public List Locations { get; set; } = new List(); } \ No newline at end of file diff --git a/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/LocationUserAdminView.razor b/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/LocationUserAdminView.razor index c5ae9ff..aa71e77 100644 --- a/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/LocationUserAdminView.razor +++ b/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/LocationUserAdminView.razor @@ -75,25 +75,29 @@ @user.Role - @if (user.Role == "Manager") - { - - } - else - { - - } - - +
+ @if (user.Role == "Manager") + { + + } + else if (user.Role == "Employee") + { + + } + @if (!(user.Role == "Admin" && user.Id == Auth.CurrentUser?.Id)) { + @* Admins shouldn't be able to remove themselves *@ + + } +
} @@ -175,7 +179,17 @@ if (!int.TryParse(selectedUserId, out var userId)) return; - await LocationService.AddUserToLocationAsync(LocationId, userId); + var UserToAdd = AvailableUsers.FirstOrDefault(u => u.Id == userId); + + if (UserToAdd != null) { + // add to general user mapping + await LocationService.AddUserToLocationAsync(LocationId, userId); + + // add to manager list if user is manager + if (UserToAdd.Role == "Manager") { + await LocationService.AddManagerToLocationAsync(LocationId, userId); + } + } await ReloadUsers(); HideAddExistingForm(); diff --git a/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/SettingsLocations.razor b/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/SettingsLocations.razor index d5d5df9..176253e 100644 --- a/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/SettingsLocations.razor +++ b/CulinaryCommandApp/Components/Pages/UserSettings/LocationSettings/SettingsLocations.razor @@ -26,9 +26,9 @@ Locations="locations" OnEdit="Update" /> } - else + else // Auth.UserRole == "Employee" { - + } } @@ -70,8 +70,8 @@ } else { - employeeLocation = (await LocationService.GetLocationsByEmployeeAsync(Auth.UserId!.Value)) - .FirstOrDefault(); + locations = await LocationService.GetLocationsByEmployeeAsync(Auth.UserId!.Value); + } } diff --git a/CulinaryCommandApp/Components/Pages/Users/Edit.razor b/CulinaryCommandApp/Components/Pages/Users/Edit.razor index 089e12f..6ea6a24 100644 --- a/CulinaryCommandApp/Components/Pages/Users/Edit.razor +++ b/CulinaryCommandApp/Components/Pages/Users/Edit.razor @@ -39,6 +39,7 @@ else { await UserService.UpdateUserAsync(u); await UserService.AssignLocationsAsync(u.Id, u.UserLocations.Select(x => x.LocationId).ToList()); + Nav.NavigateTo("/users"); } } diff --git a/CulinaryCommandApp/Components/Pages/Users/UserForm.razor b/CulinaryCommandApp/Components/Pages/Users/UserForm.razor index 4e339e9..1f2ff0f 100644 --- a/CulinaryCommandApp/Components/Pages/Users/UserForm.razor +++ b/CulinaryCommandApp/Components/Pages/Users/UserForm.razor @@ -58,7 +58,14 @@ async Task Save() { - await OnValidSubmit.InvokeAsync(Model); + // force the sync from the list of IDs into the model's collection + Model.UserLocations = AssignedLocationIds.Select(id => new UserLocation + { + UserId = Model.Id, + LocationId = id + }).ToList(); + + await OnValidSubmit.InvokeAsync(Model); } void Cancel() => Nav.NavigateTo("/users"); diff --git a/CulinaryCommandApp/Components/Pages/Users/UserList.razor b/CulinaryCommandApp/Components/Pages/Users/UserList.razor index 556c31d..dc66da7 100644 --- a/CulinaryCommandApp/Components/Pages/Users/UserList.razor +++ b/CulinaryCommandApp/Components/Pages/Users/UserList.razor @@ -57,7 +57,8 @@ else } // Now safe to load data - _users = await Users.GetAllUsersAsync(); + @* _users = await Users.GetAllUsersAsync(); *@ + _users = await Users.GetUsersByCompanyAsync(Auth.CurrentUser?.CompanyId ?? 0); StateHasChanged(); } diff --git a/CulinaryCommandApp/Services/LocationState.cs b/CulinaryCommandApp/Services/LocationState.cs index b6221f1..d3e3ede 100644 --- a/CulinaryCommandApp/Services/LocationState.cs +++ b/CulinaryCommandApp/Services/LocationState.cs @@ -15,7 +15,6 @@ namespace CulinaryCommand.Services public class LocationState { private readonly IJSRuntime _js; - public List ManagedLocations { get; private set; } = new(); public Location? CurrentLocation { get; private set; } @@ -26,6 +25,22 @@ public LocationState(IJSRuntime js) _js = js; } + private void NotifyStateChanged() => OnChange?.Invoke(); + + public async Task HydrateAsync() + { + if (CurrentLocation != null) return; // Already have it + + var savedId = await _js.InvokeAsync("localStorage.getItem", "cc_selected_location_id"); + + if (!string.IsNullOrEmpty(savedId) && int.TryParse(savedId, out int id)) + { + // Assuming you've loaded your ManagedLocations list already + CurrentLocation = ManagedLocations.FirstOrDefault(l => l.Id == id); + NotifyStateChanged(); + } + } + public async Task SetLocationsAsync(List locations) { ManagedLocations = locations ?? new List(); @@ -51,7 +66,8 @@ public async Task SetCurrentLocationAsync(Location loc) // Persist to localStorage await _js.InvokeVoidAsync("localStorage.setItem", "cc_activeLocationId", loc.Id); - OnChange?.Invoke(); + // OnChange?.Invoke(); + NotifyStateChanged(); } } diff --git a/CulinaryCommandApp/Services/UserService.cs b/CulinaryCommandApp/Services/UserService.cs index a39dc5e..8ccfcec 100644 --- a/CulinaryCommandApp/Services/UserService.cs +++ b/CulinaryCommandApp/Services/UserService.cs @@ -86,7 +86,10 @@ public async Task> GetUsersByCompanyAsync(int companyId) public async Task UpdateUserAsync(User user) { - var existing = await _context.Users.FindAsync(user.Id); + var existing = await _context.Users + .Include(u => u.UserLocations) + .Include(u => u.ManagerLocations) + .FirstOrDefaultAsync(u => u.Id == user.Id); if (existing == null) return; existing.Name = user.Name; @@ -95,6 +98,43 @@ public async Task UpdateUserAsync(User user) existing.Phone = user.Phone; existing.UpdatedAt = DateTime.UtcNow; + // get the list of IDs from the incoming model + var newUserLocationIds = user.UserLocations.Select(ul => ul.LocationId).ToList(); + + // sync user-locations (general access) + var toRemoveUL = existing.UserLocations.Where(ul => !newUserLocationIds.Contains(ul.LocationId)).ToList(); + foreach (var ul in toRemoveUL) existing.UserLocations.Remove(ul); + + foreach (var locId in newUserLocationIds) + { + if (!existing.UserLocations.Any(ul => ul.LocationId == locId)) + { + existing.UserLocations.Add(new UserLocation { UserId = user.Id, LocationId = locId }); + } + } + + // sync manager-locations (manager access) + if (string.Equals(existing.Role, "Manager", StringComparison.OrdinalIgnoreCase)) + { + // remove manager records for locations no longer assigned + var toRemoveML = existing.ManagerLocations.Where(ml => !newUserLocationIds.Contains(ml.LocationId)).ToList(); + foreach (var ml in toRemoveML) existing.ManagerLocations.Remove(ml); + + // add manager records for new locations + foreach (var locId in newUserLocationIds) + { + if (!existing.ManagerLocations.Any(ml => ml.LocationId == locId)) + { + existing.ManagerLocations.Add(new ManagerLocation { UserId = user.Id, LocationId = locId }); + } + } + } + else + { + // if role was changed from Manager to something else, wipe manager entries + existing.ManagerLocations.Clear(); + } + await _context.SaveChangesAsync(); }