Skip to content

Commit a86a19f

Browse files
feat: Add title prefix functionality and description editing for map rotations import
1 parent b76f081 commit a86a19f

3 files changed

Lines changed: 95 additions & 14 deletions

File tree

src/XtremeIdiots.Portal.Web/Controllers/MapRotationsController.cs

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,6 +1111,7 @@ public async Task<IActionResult> Import(ImportMapRotationsViewModel model, Cance
11111111
{
11121112
Index = i,
11131113
Title = rotation.Title,
1114+
Description = rotation.RawComment ?? "",
11141115
GameMode = rotation.GameMode,
11151116
MapCount = rotation.MapNames.Count,
11161117
MapNames = rotation.MapNames,
@@ -1186,13 +1187,16 @@ public async Task<IActionResult> ImportConfirm(ImportMapRotationsConfirmViewMode
11861187
return authResult;
11871188

11881189
var selectedIndices = new HashSet<int>(model.SelectedIndices);
1189-
var selectedRotations = draft.Rotations
1190+
var editLookup = model.Edits
1191+
.Where(e => e != null)
1192+
.ToDictionary(e => e.Index, e => e);
1193+
var prefix = model.ImportPrefix?.Trim() ?? "";
1194+
var selectedRotationsWithIndex = draft.Rotations
11901195
.Select((r, i) => (Rotation: r, Index: i))
11911196
.Where(x => selectedIndices.Contains(x.Index))
1192-
.Select(x => x.Rotation)
11931197
.ToList();
11941198

1195-
if (selectedRotations.Count == 0)
1199+
if (selectedRotationsWithIndex.Count == 0)
11961200
{
11971201
this.AddAlertWarning("No rotations selected for import.");
11981202
return RedirectToAction(nameof(Import));
@@ -1219,7 +1223,7 @@ public async Task<IActionResult> ImportConfirm(ImportMapRotationsConfirmViewMode
12191223
}
12201224

12211225
// Step 2: Resolve ALL map names to GUIDs (always re-resolve after creation attempt)
1222-
var allMapNames = selectedRotations.SelectMany(r => r.MapNames).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
1226+
var allMapNames = selectedRotationsWithIndex.SelectMany(x => x.Rotation.MapNames).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
12231227
var mapLookup = new Dictionary<string, Guid>(StringComparer.OrdinalIgnoreCase);
12241228

12251229
for (var skip = 0; skip < allMapNames.Length; skip += 100)
@@ -1241,7 +1245,7 @@ public async Task<IActionResult> ImportConfirm(ImportMapRotationsConfirmViewMode
12411245
// Step 3: Create rotations
12421246
var results = new List<ImportResultItem>();
12431247

1244-
foreach (var rotation in selectedRotations)
1248+
foreach (var (rotation, rotationIndex) in selectedRotationsWithIndex)
12451249
{
12461250
try
12471251
{
@@ -1262,9 +1266,24 @@ public async Task<IActionResult> ImportConfirm(ImportMapRotationsConfirmViewMode
12621266
continue;
12631267
}
12641268

1265-
var createDto = new CreateMapRotationDto(draft.GameType, rotation.Title, rotation.GameMode)
1269+
// Apply user edits (title/description) and prefix
1270+
var title = rotation.Title;
1271+
var description = rotation.RawComment;
1272+
if (editLookup.TryGetValue(rotationIndex, out var edit))
12661273
{
1267-
Description = rotation.RawComment,
1274+
if (!string.IsNullOrWhiteSpace(edit.Title))
1275+
title = edit.Title.Trim();
1276+
description = edit.Description?.Trim();
1277+
}
1278+
1279+
if (!string.IsNullOrEmpty(prefix) && !title.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
1280+
{
1281+
title = $"{prefix}{title}";
1282+
}
1283+
1284+
var createDto = new CreateMapRotationDto(draft.GameType, title, rotation.GameMode)
1285+
{
1286+
Description = description,
12681287
MapIds = mapIds
12691288
};
12701289

@@ -1275,7 +1294,7 @@ public async Task<IActionResult> ImportConfirm(ImportMapRotationsConfirmViewMode
12751294
{
12761295
results.Add(new ImportResultItem
12771296
{
1278-
Title = rotation.Title,
1297+
Title = title,
12791298
Status = "Imported",
12801299
MapRotationId = createResult.Result.Data.MapRotationId
12811300
});
@@ -1284,7 +1303,7 @@ public async Task<IActionResult> ImportConfirm(ImportMapRotationsConfirmViewMode
12841303
{
12851304
results.Add(new ImportResultItem
12861305
{
1287-
Title = rotation.Title,
1306+
Title = title,
12881307
Status = "Failed",
12891308
Error = "API returned failure"
12901309
});
@@ -1305,7 +1324,7 @@ public async Task<IActionResult> ImportConfirm(ImportMapRotationsConfirmViewMode
13051324
{
13061325
GameType = draft.GameType,
13071326
ImportedCount = results.Count(r => r.Status == "Imported"),
1308-
SkippedCount = selectedRotations.Count - results.Count,
1327+
SkippedCount = selectedRotationsWithIndex.Count - results.Count,
13091328
FailedCount = results.Count(r => r.Status == "Failed"),
13101329
MapsCreatedCount = mapsCreated,
13111330
Results = results

src/XtremeIdiots.Portal.Web/ViewModels/ImportMapRotationsViewModel.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public class ImportRotationPreviewItem
3030
{
3131
public int Index { get; set; }
3232
public string Title { get; set; } = "";
33+
public string Description { get; set; } = "";
3334
public string GameMode { get; set; } = "";
3435
public int MapCount { get; set; }
3536
public List<string> MapNames { get; set; } = [];
@@ -45,7 +46,16 @@ public class ImportRotationPreviewItem
4546
public class ImportMapRotationsConfirmViewModel
4647
{
4748
public string DraftId { get; set; } = "";
49+
public string ImportPrefix { get; set; } = "";
4850
public List<int> SelectedIndices { get; set; } = [];
51+
public List<ImportRotationEditItem> Edits { get; set; } = [];
52+
}
53+
54+
public class ImportRotationEditItem
55+
{
56+
public int Index { get; set; }
57+
public string Title { get; set; } = "";
58+
public string Description { get; set; } = "";
4959
}
5060

5161
public class ImportMapRotationsResultViewModel

src/XtremeIdiots.Portal.Web/Views/MapRotations/ImportPreview.cshtml

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,31 @@
2626
</div>
2727
}
2828

29+
<div class="ibox">
30+
<div class="ibox-title">
31+
<h5>Import Prefix</h5>
32+
</div>
33+
<div class="ibox-content">
34+
<div class="row align-items-end">
35+
<div class="col-md-6">
36+
<label for="ImportPrefix" class="form-label">
37+
Title Prefix <small class="text-muted">(optional — prepended to all titles on import)</small>
38+
</label>
39+
<input type="text" class="form-control" id="ImportPrefix" name="ImportPrefix"
40+
placeholder="e.g. COD4 - Knife Maps" />
41+
</div>
42+
<div class="col-md-6 mt-2 mt-md-0">
43+
<button type="button" class="btn btn-sm btn-outline-primary" id="applyPrefix">
44+
<i class="fa-solid fa-fw fa-pen"></i> Preview Prefix
45+
</button>
46+
<button type="button" class="btn btn-sm btn-outline-secondary" id="clearPrefix">
47+
<i class="fa-solid fa-fw fa-eraser"></i> Clear Prefix
48+
</button>
49+
</div>
50+
</div>
51+
</div>
52+
</div>
53+
2954
<div class="ibox">
3055
<div class="ibox-title d-flex justify-content-between align-items-center">
3156
<h5>@ViewData["Title"]</h5>
@@ -40,7 +65,7 @@
4065
<thead>
4166
<tr>
4267
<th style="width: 40px;"><i class="fa-solid fa-fw fa-check"></i></th>
43-
<th>Title</th>
68+
<th>Title / Description</th>
4469
<th>Mode</th>
4570
<th>Maps</th>
4671
<th>Variable</th>
@@ -49,19 +74,27 @@
4974
</tr>
5075
</thead>
5176
<tbody>
52-
@foreach (var item in Model.Rotations)
77+
@for (var i = 0; i < Model.Rotations.Count; i++)
5378
{
79+
var item = Model.Rotations[i];
5480
<tr class="@(item.IsDuplicate ? "table-warning" : "")">
5581
<td>
5682
<input type="checkbox" name="SelectedIndices" value="@item.Index"
5783
class="form-check-input rotation-checkbox"
5884
@(item.Selected ? "checked" : "") />
5985
</td>
6086
<td>
61-
<strong>@item.Title</strong>
87+
<input type="hidden" name="Edits[@i].Index" value="@item.Index" />
88+
<input type="text" name="Edits[@i].Title" value="@item.Title"
89+
class="form-control form-control-sm mb-1 rotation-title"
90+
data-original-title="@item.Title"
91+
placeholder="Rotation title" />
92+
<input type="text" name="Edits[@i].Description" value="@item.Description"
93+
class="form-control form-control-sm rotation-description"
94+
placeholder="Description (optional)" />
6295
@if (item.DateText != null)
6396
{
64-
<br /><small class="text-muted">@item.DateText</small>
97+
<small class="text-muted mt-1 d-inline-block">@item.DateText</small>
6598
}
6699
@if (item.IsDuplicate)
67100
{
@@ -126,5 +159,24 @@
126159
document.getElementById('selectNone')?.addEventListener('click', function () {
127160
document.querySelectorAll('.rotation-checkbox').forEach(cb => cb.checked = false);
128161
});
162+
163+
document.getElementById('applyPrefix')?.addEventListener('click', function () {
164+
var prefix = document.getElementById('ImportPrefix').value.trim();
165+
if (!prefix) return;
166+
var separator = '';
167+
document.querySelectorAll('.rotation-title').forEach(function (input) {
168+
var original = input.getAttribute('data-original-title');
169+
if (!input.value.startsWith(prefix + separator)) {
170+
input.value = prefix + separator + original;
171+
}
172+
});
173+
});
174+
175+
document.getElementById('clearPrefix')?.addEventListener('click', function () {
176+
document.getElementById('ImportPrefix').value = '';
177+
document.querySelectorAll('.rotation-title').forEach(function (input) {
178+
input.value = input.getAttribute('data-original-title');
179+
});
180+
});
129181
</script>
130182
}

0 commit comments

Comments
 (0)