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
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 ()
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