• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

samsmithnz / SatisfactoryTree / 17962523436

24 Sep 2025 12:22AM UTC coverage: 64.18% (-4.0%) from 68.215%
17962523436

Pull #314

github

web-flow
Merge c10995f91 into 9b5cf3f1d
Pull Request #314: Add "Add exported Part" button functionality to factory exported parts section

427 of 772 branches covered (55.31%)

Branch coverage included in aggregate %.

30 of 196 new or added lines in 8 files covered. (15.31%)

8 existing lines in 3 files now uncovered.

1173 of 1721 relevant lines covered (68.16%)

1085.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/SatisfactoryTree.Web/Components/FactoryItems.razor
1
@using SatisfactoryTree.Logic.Models
2
@using SatisfactoryTree.Web.Services
3
@inject IFactoryItemDisplayService DisplayService
4
@inject PlanService PlanService
5
@implements IDisposable
6
@using System.Linq
7

8
@code {
9
    [Parameter]
10
    public Factory? Factory { get; set; }
×
11
    [Parameter]
NEW
12
    public FactoryCatalog FactoryCatalog { get; set; } = new();
×
13

NEW
14
    private bool isAddingExportedPart = false;
×
NEW
15
    private LookupItem? selectedPartToExport = null; // only for the add-export UI
×
NEW
16
    private double exportedQuantity = 1.0;
×
17

18
    protected override void OnInitialized()
NEW
19
    {
×
NEW
20
        PlanService.PlanChanged += OnPlanChanged;
×
NEW
21
    }
×
22

23
    protected override void OnParametersSet()
NEW
24
    {
×
25
        // Fallback: if PartsLookup is empty (or null) but Parts dictionary is populated, build it
NEW
26
        if ((FactoryCatalog.PartsLookup == null || FactoryCatalog.PartsLookup.Count == 0) && FactoryCatalog.Parts != null && FactoryCatalog.Parts.Count > 0)
×
NEW
27
        {
×
28
            try
NEW
29
            {
×
NEW
30
                FactoryCatalog.PartsLookup = FactoryCatalog.Parts
×
NEW
31
                    .Select(kvp => new LookupItem(kvp.Key, kvp.Value?.Name ?? kvp.Key))
×
NEW
32
                    .OrderBy(l => l.Name)
×
NEW
33
                    .ToList();
×
NEW
34
            }
×
NEW
35
            catch(Exception ex)
×
NEW
36
            {
×
NEW
37
                Console.WriteLine($"FactoryItems: Failed to build PartsLookup fallback: {ex.Message}");
×
NEW
38
            }
×
NEW
39
        }
×
NEW
40
    }
×
41

NEW
42
    private void OnPlanChanged() => InvokeAsync(StateHasChanged);
×
43

NEW
44
    public void Dispose() => PlanService.PlanChanged -= OnPlanChanged;
×
45

46
    private LookupItem? GetCurrentLookup(Item item)
NEW
47
    {
×
NEW
48
        return FactoryCatalog.PartsLookup?.FirstOrDefault(p => p.Id == item.Name);
×
NEW
49
    }
×
50

51
    private void StartAddingExportedPart()
NEW
52
    {
×
NEW
53
        isAddingExportedPart = true;
×
NEW
54
        selectedPartToExport = null;
×
NEW
55
        exportedQuantity = 1.0;
×
NEW
56
    }
×
57

58
    private void CancelAddingExportedPart()
NEW
59
    {
×
NEW
60
        isAddingExportedPart = false;
×
NEW
61
        selectedPartToExport = null;
×
NEW
62
    }
×
63

64
    private void OnExportPartSelected(LookupItem selectedPart)
65
    {
×
NEW
66
        selectedPartToExport = selectedPart;
×
67
    }
×
68

69
    private async Task AddExportedPart()
70
    {
×
NEW
71
        if (Factory != null && selectedPartToExport != null)
×
NEW
72
        {
×
73
            try
NEW
74
            {
×
NEW
75
                PlanService.AddExportedPartToFactory(Factory.Id, selectedPartToExport.Id, exportedQuantity);
×
NEW
76
                isAddingExportedPart = false;
×
NEW
77
                selectedPartToExport = null;
×
NEW
78
                await InvokeAsync(StateHasChanged);
×
NEW
79
            }
×
NEW
80
            catch (Exception ex)
×
NEW
81
            {
×
NEW
82
                Console.WriteLine($"Error adding exported part: {ex.Message}");
×
NEW
83
            }
×
NEW
84
        }
×
NEW
85
    }
×
86

87
    private async Task RemoveExportedPart(ExportedItem exportedItem)
NEW
88
    {
×
NEW
89
        if (Factory == null) return;
×
90
        try
NEW
91
        {
×
NEW
92
            PlanService.RemoveExportedPartFromFactory(Factory.Id, exportedItem.Item.Name);
×
NEW
93
            await InvokeAsync(StateHasChanged);
×
NEW
94
        }
×
NEW
95
        catch (Exception ex)
×
NEW
96
        {
×
NEW
97
            Console.WriteLine($"Error removing exported part: {ex.Message}");
×
NEW
98
        }
×
UNCOV
99
    }
×
100
}
101

102
@if (Factory != null)
×
103
{
×
104
    <div class="factory-container">
105
        <h3><input type="text" value="@Factory.Name" class="form-control" /></h3>
106
        
107
        <p><b>Exported parts:</b></p>
108
        @if (Factory.ExportedParts != null && Factory.ExportedParts.Any())
×
109
        {
×
110
            <ul>
UNCOV
111
                @foreach (ExportedItem item in Factory.ExportedParts)
×
112
                {
×
113
                    <li class="exported-part-item">
114
                        <div class="exported-part-info">
115
                            <img src="@DisplayService.GetPartImagePath(item.Item.Name)" alt="@item.Item.Name" class="part-image" />
NEW
116
                            <strong>@item.Item.Name</strong> - @item.Item.Quantity per/min (@item.PartQuantityExported per/min exported)
×
117
                        </div>
NEW
118
                        <button class="btn btn-sm btn-danger" @onclick="() => RemoveExportedPart(item)">Remove</button>
×
119
                    </li>
120
                }
×
121
            </ul>
NEW
122
        }
×
123
        else
NEW
124
        {
×
125
            <p class="text-muted">No exported parts defined.</p>
NEW
126
        }
×
127

NEW
128
        @if (!isAddingExportedPart)
×
NEW
129
        {
×
130
            <button class="btn btn-primary me-2" @onclick="StartAddingExportedPart">Add Exported Part</button>
NEW
131
        }
×
132
        else
NEW
133
        {
×
134
            <div class="add-exported-part-form card p-3 mb-3">
135
                <h5>Add New Exported Part</h5>
136
                <div class="mb-2">
137
                    <label>Select Part:</label>
138
                    <TypeaheadDropdown TItem="LookupItem"
139
                                       Items="@FactoryCatalog.PartsLookup"
140
                                       DisplayNameSelector="@(p => p.Name)"
141
                                       IdSelector="@(p => p.Id)"
142
                                       SelectedItem="@selectedPartToExport"
143
                                       SelectedItemChanged="@OnExportPartSelected"
144
                                       Placeholder="Search for parts to export..."
145
                                       CssClass="me-2" />
146
                </div>
147
                <div class="mb-2">
148
                    <label>Quantity per/min:</label>
149
                    <input type="number" class="form-control" @bind="exportedQuantity" min="0" step="0.1" />
150
                </div>
151
                <div>
152
                    <button class="btn btn-success me-2" @onclick="AddExportedPart" disabled="@(selectedPartToExport == null)">Add</button>
153
                    <button class="btn btn-secondary" @onclick="CancelAddingExportedPart">Cancel</button>
154
                </div>
155
            </div>
NEW
156
        }
×
157

158
        <p><b>Component parts:</b></p>
159
        <ul>
NEW
160
            @if (Factory.ComponentParts != null && Factory.ComponentParts.Any())
×
NEW
161
            {
×
NEW
162
                @foreach (Item item in Factory.ComponentParts)
×
163
                {
×
164
                    <li>
165
                        <div class="component-part-row">
166
                            <img src="@DisplayService.GetPartImagePath(item.Name)" alt="@item.Name" class="part-image" />
167
                            <TypeaheadDropdown TItem="LookupItem"
168
                                               Items="@FactoryCatalog.PartsLookup"
169
                                               DisplayNameSelector="@(p => p.Name)"
170
                                               IdSelector="@(p => p.Id)"
171
                                               SelectedItem="@GetCurrentLookup(item)"
NEW
172
                                               SelectedItemChanged="@(lookup => { if (lookup != null) item.Name = lookup.Id; })"
×
173
                                               Placeholder="Search for parts..."
174
                                               CssClass="me-2" />
175
                            <span class="quantity-label">Quantity:</span>
176
                            <input type="text" value="@item.Quantity.ToString()" class="form-control quantity-input" />
177
                        </div>
178
                        
179
                        <div class="component-badges">
NEW
180
                            @if (item.Ingredients != null)
×
NEW
181
                            {
×
NEW
182
                                @foreach (Item ingredient in item.Ingredients)
×
183
                                {
×
184
                                    <span class="badge badge-blue">
185
                                        <img src="@DisplayService.GetPartImagePath(ingredient.Name)" alt="@ingredient.Name" class="ingredient-image" />
NEW
186
                                        @ingredient.Name @Math.Round(ingredient.Quantity, 1) per/min
×
187
                                    </span>
UNCOV
188
                                }
×
NEW
189
                            }
×
190
                            <span class="badge badge-yellow"> 
NEW
191
                                @if (!string.IsNullOrEmpty(item.Building) && DisplayService.HasBuildingImage(item.Building))
×
NEW
192
                                {
×
193
                                    <img src="@DisplayService.GetBuildingImagePath(item.Building)" alt="@item.Building" class="building-image" />
NEW
194
                                }
×
NEW
195
                                else if (!string.IsNullOrEmpty(item.Building))
×
NEW
196
                                {
×
197
                                    <i class="fas fa-building building-icon" title="@item.Building"></i>
NEW
198
                                }
×
NEW
199
                                @item.Building x @Math.Round(item.BuildingQuantity, 2)
×
200
                            </span>
201
                            <span class="badge badge-grey">
NEW
202
                                <i class="fas fa-bolt power-icon" title="Power Usage"></i> @Math.Round(item.BuildingPowerUsage, 1) MW
×
203
                            </span>
204
                        </div>
205
                    </li>
206
                    <hr>
UNCOV
207
                }
×
NEW
208
            }
×
209
            else
NEW
210
            {
×
211
                <li>No component parts calculated yet.</li>
NEW
212
            }
×
213
        </ul>
214

215
        <p><b>Imported parts:</b></p>
216
        <ul>
NEW
217
            @if (Factory.ImportedParts != null && Factory.ImportedParts.Any())
×
NEW
218
            {
×
NEW
219
                @foreach (KeyValuePair<int, ImportedItem> item in Factory.ImportedParts)
×
UNCOV
220
                {
×
221
                    <li>
222
                        <img src="@DisplayService.GetPartImagePath(item.Value.Item.Name)" alt="@item.Value.Item.Name" class="part-image" />
NEW
223
                        <strong>@item.Value.Item.Name</strong> - Quantity: @Math.Round(item.Value.Item.Quantity, 1) - from factory: @item.Value.FactoryName (@Math.Round(item.Value.PartQuantityImported, 1) per/min imported)
×
224
                    </li>
UNCOV
225
                }
×
NEW
226
            }
×
227
            else
NEW
228
            {
×
229
                <li>No imported parts.</li>
NEW
230
            }
×
231
        </ul>
232

233
        <p><b>Raw resources:</b></p>
234
        <span class="badge badge-blue">
235
            <img src='@DisplayService.GetPartImagePath("OreIron")' alt="OreIron" class="ingredient-image" />
236
            <span>Iron Ore 60 per/min</span>
237
        </span>
238
    </div>
239
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc