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 @@
- @*
-
- Sign In
-
-
*@
-
-
- @*
-
- Sign Up
-
-
*@
-
-
+ @if (Auth.UserRole == "Employee")
+ {
My Tasks
-
-
-
- Inventory
-
-
-
- @if (Auth.UserRole == "Admin")
- {
-
-
- Recipes
-
-
-
-
- Ingredients
-
-
+ }
+ else { @* admin or manager *@
Assign Tasks
-
-
- Purchase Orders
-
-
-
-
- Manage Users
-
-
- }
- else if (Auth.UserRole == "Manager")
- {
Recipes
@@ -94,8 +52,8 @@
-
- Assign Tasks
+
+ Inventory
@@ -103,9 +61,14 @@
Purchase Orders
+
+
+ }
+ @if (Auth.UserRole == "Admin")
+ {
-
- Inventory
+
+ Manage Users
}
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();
}