Skip to content

Commit e89b657

Browse files
authored
Merge pull request #78 from SensitTechnologies/No-Work-Instruction-Nodes-development
No work instruction nodes development
2 parents c732eb3 + 6670d59 commit e89b657

27 files changed

+1159
-247
lines changed

MESS/MESS.Blazor/Components/Layout/MainLayout.razor

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
@inject IJSRuntime JS
22
@inherits LayoutComponentBase
33
@implements IDisposable
4+
@using System.ComponentModel
45
@using MESS.Blazor.Components.Navigator
6+
@using MESS.Services.DarkMode
57
@using Microsoft.AspNetCore.Components.Authorization
68
@inject NavigationManager Navigation
79
@inject AuthenticationStateProvider AuthProvider
10+
@inject DarkModeInstance DarkModeInstance
811

912
<HeadContent>
1013
<script src="darkmode.js"></script>
@@ -36,9 +39,8 @@
3639
{
3740
<button class="btn btn-outline-secondary"
3841
@onclick="ToggleDarkMode"
39-
title="Toggle Dark Mode"
40-
style="width: 36px; height: 32px; font-size: 1.3rem; display: flex; align-items: center; justify-content: center; border-radius: 0.25rem;">
41-
@(isDarkMode ? "\u2600\ufe0f" : "\ud83c\udf19")
42+
style="width: 32px; height: 32px; padding: 0; font-size: 1.1rem; display: flex; align-items: center; justify-content: center; border-radius: 0.25rem;">
43+
<i class="bi @(DarkModeInstance.IsDarkMode ? "bi-moon-stars" : "bi-sun")"></i>
4244
</button>
4345
}
4446
</div>
@@ -92,6 +94,23 @@
9294
// Also hide dark mode toggle on WorkInstructionManager page
9395
ShowDarkModeToggle = ShowSidebarToggle;
9496
}
97+
98+
/// <summary>
99+
/// Called when the component is initialized. Registers this component with the DarkModeInstance
100+
/// to receive updates when the dark mode state changes.
101+
/// </summary>
102+
protected override void OnInitialized()
103+
{
104+
DarkModeInstance.PropertyChanged += OnDarkModeChanged;
105+
}
106+
107+
private void OnDarkModeChanged(object? sender, PropertyChangedEventArgs e)
108+
{
109+
if (e.PropertyName == nameof(DarkModeInstance.IsDarkMode))
110+
{
111+
InvokeAsync(StateHasChanged);
112+
}
113+
}
95114

96115
/// <summary>
97116
/// Toggles the sidebar open/closed state.
@@ -112,6 +131,7 @@
112131
/// </summary>
113132
private async Task ToggleDarkMode()
114133
{
134+
DarkModeInstance.Toggle();
115135
isDarkMode = !isDarkMode;
116136
await JS.InvokeVoidAsync("toggleDarkMode", isDarkMode);
117137
}
@@ -121,7 +141,7 @@
121141
/// </summary>
122142
public void Dispose()
123143
{
124-
// Cleanup if needed
144+
DarkModeInstance.PropertyChanged -= OnDarkModeChanged;
125145
}
126146

127147
/// <summary>

MESS/MESS.Blazor/Components/Pages/Phoebe/MenuBar/MenuBarPhoebe.razor

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
@namespace MESS.Blazor.Components.Pages.Phoebe.MenuBar
2+
@using System.ComponentModel
23
@using Microsoft.AspNetCore.Components.Authorization
34
@using MESS.Data.Models
45
@using MESS.Blazor.Components.Pages.Phoebe.WorkInstruction
56
@using MESS.Services.WorkInstruction
67
@using Serilog
78
@using MESS.Blazor.Components.Pages.Phoebe.WorkInstruction.Import
89
@using MESS.Blazor.Components.Pages.Phoebe.WorkInstruction.Export
10+
@using MESS.Services.DarkMode
911
@inject AuthenticationStateProvider AuthProvider
1012
@inject ILogger<MenuBarPhoebe> DemoLogger
1113
@inject IJSRuntime JS
1214
@inject IWorkInstructionEditorService EditorService
1315
@inject IWorkInstructionService WorkInstructionService
16+
@inject DarkModeInstance DarkModeInstance
1417

1518
<div class="menu-bar-card">
1619
<button class="btn-small" title="Toggle Sidebar" @onclick="ToggleSidebar" aria-label="Toggle sidebar">☰</button>
@@ -41,6 +44,9 @@
4144
OnClick="ToggleActive" Disabled="@(!HasInstruction)">Active</FluentMenuItem>
4245
<FluentDivider></FluentDivider>
4346
<FluentMenuItem OnClick="@ShowEditAssociationsDialog" Disabled="@(!HasInstruction)">Edit Product Associations</FluentMenuItem>
47+
<FluentDivider/>
48+
<FluentMenuItem OnClick="@(() => TriggerAndClose(OnNewPart))">New Part</FluentMenuItem>
49+
<FluentMenuItem OnClick="@(() => TriggerAndClose(OnNewStep))">New Step</FluentMenuItem>
4450
</FluentMenu>
4551
</div>
4652

@@ -90,9 +96,7 @@
9096
</div>
9197

9298
<button class="btn-small dark-toggle-btn" title="Dark Mode" @onclick="ToggleDarkMode">
93-
@((darkModeIcon == "bi bi-moon-stars") ?
94-
(MarkupString)"<i class=\"bi bi-moon-stars\"></i>" :
95-
(MarkupString)"<i class=\"bi bi-sun\"></i>")
99+
<i class="bi @(DarkModeInstance.IsDarkMode ? "bi-moon-stars" : "bi-sun")"></i>
96100
</button>
97101

98102
@if (!string.IsNullOrWhiteSpace(ActiveLineOperator))
@@ -181,6 +185,18 @@
181185
[Parameter]
182186
public EventCallback OnDelete { get; set; }
183187

188+
/// <summary>
189+
///
190+
/// </summary>
191+
[Parameter]
192+
public EventCallback OnNewPart { get; set; }
193+
194+
/// <summary>
195+
///
196+
/// </summary>
197+
[Parameter]
198+
public EventCallback OnNewStep { get; set; }
199+
184200
/// <summary>
185201
/// Event callback triggered when the user restores a previous version from the version history sidebar.
186202
/// </summary>
@@ -272,6 +288,15 @@
272288
{
273289
var state = await AuthProvider.GetAuthenticationStateAsync();
274290
ActiveLineOperator = state.User.Identity?.Name;
291+
DarkModeInstance.PropertyChanged += OnDarkModeChanged;
292+
}
293+
294+
private void OnDarkModeChanged(object? sender, PropertyChangedEventArgs e)
295+
{
296+
if (e.PropertyName == nameof(DarkModeInstance.IsDarkMode))
297+
{
298+
InvokeAsync(StateHasChanged);
299+
}
275300
}
276301

277302
/// <inheritdoc />
@@ -317,6 +342,7 @@
317342
await JS.InvokeVoidAsync("document.body.classList.toggle", "dark-mode");
318343
var isDark = await JS.InvokeAsync<bool>("document.body.classList.contains", "dark-mode");
319344
darkModeIcon = isDark ? "bi bi-sun" : "bi bi-moon-stars";
345+
DarkModeInstance.Toggle();
320346
}
321347

322348
private async Task ToggleSidebar()
@@ -513,4 +539,9 @@
513539
DialogSelectedProducts = products;
514540
return Task.CompletedTask;
515541
}
542+
543+
private void Dispose()
544+
{
545+
DarkModeInstance.PropertyChanged -= OnDarkModeChanged;
546+
}
516547
}

MESS/MESS.Blazor/Components/Pages/Phoebe/Product/Index.razor

Lines changed: 159 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
@using MESS.Services.WorkInstruction
88
@using Microsoft.AspNetCore.Authorization
99
@using Serilog
10+
@using MESS.Blazor.Components.Pages.Phoebe.Product.ProductTableRow.WorkInstructionAssociation
11+
@using MESS.Blazor.Components.Pages.Phoebe.Product.ProductTableRow
1012
@inject IProductService ProductService
1113
@inject IWorkInstructionService WorkInstructionService
1214
@inject IToastService ToastService
@@ -23,32 +25,67 @@ else
2325
{
2426
<table class="table table-striped">
2527
<thead>
26-
<tr>
27-
<th>Name</th>
28-
<th>Active</th>
29-
<th>Work Instructions</th>
30-
<th>Actions</th>
31-
</tr>
28+
<tr>
29+
<th>Name</th>
30+
<th>Active</th>
31+
<th>Work Instructions</th>
32+
<th>Actions</th>
33+
</tr>
3234
</thead>
3335
<tbody>
3436
@foreach (var product in Products)
3537
{
36-
<ProductTableRow Product="product" OnSubmit="HandleSubmitProduct" AllInstructions="_allInstructions"/>
38+
<ProductTableRow
39+
Product="product"
40+
Instructions="_associatedInstructionsMap.ContainsKey(product.Id)
41+
? _associatedInstructionsMap[product.Id]
42+
: new List<WorkInstruction>()"
43+
SelectedInstructionIds="GetSelectedIds(product.Id)"
44+
SelectedInstructionIdsChanged="@GetSelectedIdsChangedCallback(product.Id)"
45+
OnAddInstructions="@GetAddInstructionsCallback(product.Id)"
46+
OnRemoveInstructions="@GetRemoveInstructionsCallback(product.Id)"
47+
OnSubmit="@SubmitProductCallback" />
3748
}
38-
<ProductTableRow Product="_newProduct" OnSubmit="HandleSubmitProduct" AllInstructions="_allInstructions" />
49+
50+
<ProductTableRow
51+
Product="_newProduct"
52+
Instructions="[]"
53+
SelectedInstructionIds="[]"
54+
SelectedInstructionIdsChanged="@NoOpSelectedIdsChangedCallback"
55+
OnAddInstructions="@EventCallback.Empty"
56+
OnRemoveInstructions="@NoOpRemoveInstructionsCallback"
57+
OnSubmit="@SubmitProductCallback" />
3958
</tbody>
4059
</table>
60+
61+
<AddWorkInstructionDialog
62+
Visible="@_addDialogVisible"
63+
VisibleChanged="@OnVisibleChanged"
64+
Instructions="@_nonAssociatedInstructions"
65+
OnAdd="@AddInstructionsCallback" />
4166
}
4267

4368
@code {
44-
private List<Product> Products { get; set; } = new List<Product>();
45-
private Product _newProduct = new Product { Name = "", IsActive = true };
69+
private List<Product> Products { get; set; } = new();
70+
private Product _newProduct = new() { Name = "", IsActive = true };
4671
private List<WorkInstruction>? _allInstructions;
47-
/// <inheritdoc />
72+
private Dictionary<int, List<int>> SelectedInstructionMap { get; set; } = new();
73+
private Dictionary<int, EventCallback<List<int>>> _selectedIdsChangedCallbacks = new();
74+
private Dictionary<int, List<WorkInstruction>> _associatedInstructionsMap = new();
75+
76+
private bool _addDialogVisible = false;
77+
private List<WorkInstruction> _nonAssociatedInstructions = [];
78+
private int CurrentProductId { get; set; }
79+
80+
/// <summary>
81+
/// Called by the Blazor framework when the component is initialized.
82+
/// Loads products and associated instructions.
83+
/// </summary>
4884
protected override async Task OnInitializedAsync()
4985
{
5086
await LoadProducts();
51-
_allInstructions = await WorkInstructionService.GetAllAsync();
87+
_allInstructions = await WorkInstructionService.GetAllLatestAsync();
88+
RebuildInstructionMap();
5289
}
5390

5491
private async Task LoadProducts()
@@ -62,24 +99,19 @@ else
6299
{
63100
Log.Information("Submitting product: {ProductName} (ID: {ProductId})", product.Name, product.Id);
64101

65-
if (product.Id == 0)
102+
if (product.Id == 0)
66103
{
67-
await ProductService.CreateAsync(product); // Create new product
104+
await ProductService.CreateAsync(product);
68105
ToastService.ShowSuccess($"Product '{product.Name}' created successfully!");
69106
}
70107
else
71108
{
72-
await ProductService.UpdateProductAsync(product); // Update existing product
109+
await ProductService.UpdateProductAsync(product);
73110
ToastService.ShowSuccess($"Product '{product.Name}' updated successfully!");
74111
}
75112

76113
await LoadProducts();
77-
78-
_newProduct = new Product
79-
{
80-
Name = "",
81-
IsActive = true
82-
};
114+
_newProduct = new() { Name = "", IsActive = true };
83115

84116
Log.Information("Product submitted successfully: {ProductName} (ID: {ProductId})", product.Name, product.Id);
85117
}
@@ -89,5 +121,111 @@ else
89121
ToastService.ShowError($"Error submitting product '{product.Name}'. Please try again.");
90122
}
91123
}
124+
125+
private void RebuildInstructionMap()
126+
{
127+
if (_allInstructions is null || Products is null) return;
128+
129+
_associatedInstructionsMap = Products.ToDictionary(
130+
p => p.Id,
131+
p => _allInstructions
132+
.Where(wi => wi.Products.Any(prod => prod.Id == p.Id))
133+
.ToList()
134+
);
135+
}
136+
137+
private List<int> GetSelectedIds(int productId)
138+
{
139+
if (!SelectedInstructionMap.TryGetValue(productId, out var selected))
140+
selected = [];
141+
142+
return selected;
143+
}
144+
145+
private Task HandleSelectedInstructionIdsChanged(int productId, List<int> selectedIds)
146+
{
147+
SelectedInstructionMap[productId] = selectedIds;
148+
StateHasChanged();
149+
return Task.CompletedTask;
150+
}
151+
152+
private EventCallback<List<int>> GetSelectedIdsChangedCallback(int productId)
153+
{
154+
if (!_selectedIdsChangedCallbacks.TryGetValue(productId, out var callback))
155+
{
156+
callback = EventCallback.Factory.Create<List<int>>(this,
157+
(List<int> selectedIds) => HandleSelectedInstructionIdsChanged(productId, selectedIds));
158+
_selectedIdsChangedCallbacks[productId] = callback;
159+
}
160+
return callback;
161+
}
162+
163+
private EventCallback GetAddInstructionsCallback(int productId)
164+
{
165+
return EventCallback.Factory.Create(this, () => ShowAddDialog(productId));
166+
}
92167

168+
private EventCallback<List<int>> GetRemoveInstructionsCallback(int productId)
169+
{
170+
return EventCallback.Factory.Create<List<int>>(this,
171+
(List<int> ids) => RemoveInstructionsFromProduct(productId, ids));
172+
}
173+
174+
private EventCallback<Product> SubmitProductCallback =>
175+
EventCallback.Factory.Create<Product>(this, HandleSubmitProduct);
176+
177+
private EventCallback<List<int>> NoOpSelectedIdsChangedCallback =>
178+
EventCallback.Factory.Create<List<int>>(this, _ => Task.CompletedTask);
179+
180+
private EventCallback<List<int>> NoOpRemoveInstructionsCallback =>
181+
EventCallback.Factory.Create<List<int>>(this, _ => Task.CompletedTask);
182+
183+
private EventCallback<List<int>> AddInstructionsCallback =>
184+
EventCallback.Factory.Create<List<int>>(this, OnAddInstructions);
185+
186+
private void ShowAddDialog(int productId)
187+
{
188+
if (_allInstructions is null)
189+
{
190+
ToastService.ShowError("Instructions not loaded yet.");
191+
return;
192+
}
193+
194+
_addDialogVisible = true;
195+
_nonAssociatedInstructions = _allInstructions
196+
.Where(wi => wi.Products.All(p => p.Id != productId))
197+
.ToList();
198+
199+
CurrentProductId = productId;
200+
}
201+
202+
private void OnVisibleChanged(bool visible)
203+
{
204+
_addDialogVisible = visible;
205+
}
206+
207+
private Task OnAddInstructions(List<int> instructionIds)
208+
{
209+
return AssociateInstructionsToProduct(CurrentProductId, instructionIds);
210+
}
211+
212+
private async Task AssociateInstructionsToProduct(int productId, List<int> instructionIds)
213+
{
214+
await ProductService.AddWorkInstructionsAsync(productId, instructionIds);
215+
SelectedInstructionMap[productId] = [];
216+
await LoadProducts();
217+
_allInstructions = await WorkInstructionService.GetAllLatestAsync();
218+
RebuildInstructionMap();
219+
ToastService.ShowSuccess("Work instructions added.");
220+
}
221+
222+
private async Task RemoveInstructionsFromProduct(int productId, List<int> instructionIds)
223+
{
224+
await ProductService.RemoveWorkInstructionsAsync(productId, instructionIds);
225+
SelectedInstructionMap[productId] = [];
226+
await LoadProducts();
227+
_allInstructions = await WorkInstructionService.GetAllLatestAsync();
228+
RebuildInstructionMap();
229+
ToastService.ShowSuccess("Selected work instructions removed.");
230+
}
93231
}

0 commit comments

Comments
 (0)