feat: wire aspects panel and shipping cost in SingleItemView code-behind
Implements LoadAspectsAsync/RebuildAspectFields, real PostageBox and AiAspects handlers, ShippingCostBox wiring, required-aspects validation, and aspects reset on new listing. Also registers EbayAspectsService in MainWindow (Task 6). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ public partial class MainWindow : MetroWindow
|
||||
private readonly EbayListingService _listingService;
|
||||
private readonly EbayCategoryService _categoryService;
|
||||
private readonly AiAssistantService _aiService;
|
||||
private readonly EbayAspectsService _aspectsService;
|
||||
private readonly BulkImportService _bulkService;
|
||||
private readonly SavedListingsService _savedService;
|
||||
private readonly EbayPriceResearchService _priceService;
|
||||
@@ -26,6 +27,7 @@ public partial class MainWindow : MetroWindow
|
||||
_categoryService = new EbayCategoryService(_auth);
|
||||
_listingService = new EbayListingService(_auth, _categoryService);
|
||||
_aiService = new AiAssistantService(config);
|
||||
_aspectsService = new EbayAspectsService(_auth);
|
||||
_bulkService = new BulkImportService();
|
||||
_savedService = new SavedListingsService();
|
||||
_priceService = new EbayPriceResearchService(_auth);
|
||||
@@ -39,7 +41,7 @@ public partial class MainWindow : MetroWindow
|
||||
SavedView.Initialise(_savedService, _priceLookupService);
|
||||
|
||||
// New Listing + Bulk tabs
|
||||
SingleView.Initialise(_listingService, _categoryService, _aiService, _auth);
|
||||
SingleView.Initialise(_listingService, _categoryService, _aiService, _auth, _aspectsService);
|
||||
BulkView.Initialise(_listingService, _categoryService, _aiService, _bulkService, _auth);
|
||||
|
||||
// Try to restore saved eBay session
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Windows.Media.Imaging;
|
||||
using EbayListingTool.Models;
|
||||
using EbayListingTool.Services;
|
||||
using Microsoft.Win32;
|
||||
using System.Linq;
|
||||
|
||||
namespace EbayListingTool.Views;
|
||||
|
||||
@@ -15,6 +16,8 @@ public partial class SingleItemView : UserControl
|
||||
private EbayCategoryService? _categoryService;
|
||||
private AiAssistantService? _aiService;
|
||||
private EbayAuthService? _auth;
|
||||
private EbayAspectsService? _aspectsService;
|
||||
private List<CategoryAspect> _currentAspects = new();
|
||||
|
||||
private ListingDraft _draft = new();
|
||||
private System.Threading.CancellationTokenSource? _categoryCts;
|
||||
@@ -29,6 +32,8 @@ public partial class SingleItemView : UserControl
|
||||
{
|
||||
InitializeComponent();
|
||||
PostcodeBox.TextChanged += (s, e) => _draft.Postcode = PostcodeBox.Text;
|
||||
ShippingCostBox.ValueChanged += (s, e) =>
|
||||
_draft.ShippingCost = (decimal)(ShippingCostBox.Value ?? 0);
|
||||
}
|
||||
|
||||
private void UserControl_Loaded(object sender, RoutedEventArgs e)
|
||||
@@ -40,12 +45,13 @@ public partial class SingleItemView : UserControl
|
||||
}
|
||||
|
||||
public void Initialise(EbayListingService listingService, EbayCategoryService categoryService,
|
||||
AiAssistantService aiService, EbayAuthService auth)
|
||||
AiAssistantService aiService, EbayAuthService auth, EbayAspectsService aspectsService)
|
||||
{
|
||||
_listingService = listingService;
|
||||
_categoryService = categoryService;
|
||||
_aiService = aiService;
|
||||
_auth = auth;
|
||||
_aspectsService = aspectsService;
|
||||
|
||||
PostcodeBox.Text = App.Configuration["Ebay:DefaultPostcode"] ?? "";
|
||||
}
|
||||
@@ -56,6 +62,8 @@ public partial class SingleItemView : UserControl
|
||||
// Q6: reset form directly — calling NewListing_Click shows a confirmation dialog which
|
||||
// is unexpected when arriving here automatically from the Photo Analysis tab.
|
||||
_draft = new ListingDraft { Postcode = PostcodeBox.Text };
|
||||
_currentAspects = new();
|
||||
AspectsPanel.Visibility = Visibility.Collapsed;
|
||||
TitleBox.Text = "";
|
||||
DescriptionBox.Text = "";
|
||||
CategoryBox.Text = "";
|
||||
@@ -215,6 +223,7 @@ public partial class SingleItemView : UserControl
|
||||
CategoryBox.Text = cat.CategoryName;
|
||||
CategoryIdLabel.Text = $"ID: {cat.CategoryId}";
|
||||
CategorySuggestionsList.Visibility = Visibility.Collapsed;
|
||||
_ = LoadAspectsAsync(_draft.CategoryId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,6 +248,7 @@ public partial class SingleItemView : UserControl
|
||||
_draft.CategoryName = top.CategoryName;
|
||||
CategoryBox.Text = top.CategoryName;
|
||||
CategoryIdLabel.Text = $"ID: {top.CategoryId}";
|
||||
_ = LoadAspectsAsync(_draft.CategoryId);
|
||||
}
|
||||
finally { _suppressCategoryLookup = false; }
|
||||
|
||||
@@ -628,6 +638,8 @@ public partial class SingleItemView : UserControl
|
||||
}
|
||||
|
||||
_draft = new ListingDraft { Postcode = PostcodeBox.Text };
|
||||
_currentAspects = new();
|
||||
AspectsPanel.Visibility = Visibility.Collapsed;
|
||||
TitleBox.Text = "";
|
||||
DescriptionBox.Text = "";
|
||||
CategoryBox.Text = "";
|
||||
@@ -654,6 +666,18 @@ public partial class SingleItemView : UserControl
|
||||
{ ShowError("Validation", "Please select a category."); return false; }
|
||||
if ((PriceBox.Value ?? 0) <= 0)
|
||||
{ ShowError("Validation", "Please enter a price greater than zero."); return false; }
|
||||
|
||||
var missingRequired = _currentAspects
|
||||
.Where(a => a.IsRequired && !_draft.Aspects.ContainsKey(a.Name))
|
||||
.Select(a => a.Name)
|
||||
.ToList();
|
||||
if (missingRequired.Count > 0)
|
||||
{
|
||||
ShowError("Validation",
|
||||
$"Please fill in required item specifics:\n• {string.Join("\n• ", missingRequired)}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -694,12 +718,141 @@ public partial class SingleItemView : UserControl
|
||||
|
||||
private void PostageBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
|
||||
{
|
||||
// TODO: Task 5 — update ShippingCostBox default based on selected postage option
|
||||
var idx = PostageBox.SelectedIndex;
|
||||
if (ShippingCostBox != null)
|
||||
ShippingCostBox.IsEnabled = idx != 4 && idx != 5; // disable for Free/Collection
|
||||
|
||||
_draft.Postage = idx switch
|
||||
{
|
||||
0 => PostageOption.RoyalMailFirstClass,
|
||||
1 => PostageOption.RoyalMailSecondClass,
|
||||
2 => PostageOption.RoyalMailTracked24,
|
||||
3 => PostageOption.RoyalMailTracked48,
|
||||
4 => PostageOption.FreePostage,
|
||||
5 => PostageOption.CollectionOnly,
|
||||
_ => PostageOption.RoyalMailSecondClass
|
||||
};
|
||||
}
|
||||
|
||||
private async void AiAspects_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// TODO: Task 5 — call EbayAspectsService.SuggestAspectsAsync and populate AspectsItemsControl
|
||||
await Task.CompletedTask;
|
||||
if (_aiService == null || _currentAspects.Count == 0) return;
|
||||
|
||||
AspectsAiSpinner.Visibility = Visibility.Visible;
|
||||
AspectsAiIcon.Visibility = Visibility.Collapsed;
|
||||
AiAspectsBtn.IsEnabled = false;
|
||||
|
||||
try
|
||||
{
|
||||
var suggestions = await _aiService.SuggestAspectsAsync(
|
||||
_draft.Title, _draft.Description, _currentAspects);
|
||||
|
||||
foreach (var (name, value) in suggestions)
|
||||
_draft.Aspects[name] = value;
|
||||
|
||||
RebuildAspectFields();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowError("AI Aspects", ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
AspectsAiSpinner.Visibility = Visibility.Collapsed;
|
||||
AspectsAiIcon.Visibility = Visibility.Visible;
|
||||
AiAspectsBtn.IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadAspectsAsync(string categoryId)
|
||||
{
|
||||
if (_aspectsService == null || string.IsNullOrEmpty(categoryId)) return;
|
||||
|
||||
AspectsSpinner.Visibility = Visibility.Visible;
|
||||
AspectsPanel.Visibility = Visibility.Visible;
|
||||
AspectsItemsControl.ItemsSource = null;
|
||||
|
||||
try
|
||||
{
|
||||
_currentAspects = await _aspectsService.GetAspectsAsync(categoryId);
|
||||
_draft.Aspects.Clear();
|
||||
|
||||
AspectsRequiredNote.Visibility = _currentAspects.Any(a => a.IsRequired)
|
||||
? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
RebuildAspectFields();
|
||||
}
|
||||
finally
|
||||
{
|
||||
AspectsSpinner.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void RebuildAspectFields()
|
||||
{
|
||||
var panels = new List<UIElement>();
|
||||
|
||||
foreach (var aspect in _currentAspects)
|
||||
{
|
||||
var labelPanel = new StackPanel { Orientation = Orientation.Horizontal };
|
||||
labelPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = aspect.Name,
|
||||
Style = (Style)FindResource("FieldLabel"),
|
||||
Margin = new Thickness(0, 0, 0, 2)
|
||||
});
|
||||
if (aspect.IsRequired)
|
||||
labelPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = " *",
|
||||
Foreground = System.Windows.Media.Brushes.OrangeRed,
|
||||
FontWeight = FontWeights.Bold,
|
||||
VerticalAlignment = VerticalAlignment.Bottom
|
||||
});
|
||||
|
||||
UIElement input;
|
||||
_draft.Aspects.TryGetValue(aspect.Name, out var existing);
|
||||
|
||||
if (!aspect.IsFreeText && aspect.AllowedValues.Count > 0)
|
||||
{
|
||||
var cb = new ComboBox { Width = 130, Margin = new Thickness(0, 0, 0, 8) };
|
||||
cb.Items.Add(new ComboBoxItem { Content = "", Tag = "" });
|
||||
foreach (var v in aspect.AllowedValues)
|
||||
cb.Items.Add(new ComboBoxItem { Content = v, Tag = v });
|
||||
if (!string.IsNullOrEmpty(existing))
|
||||
{
|
||||
var match = cb.Items.Cast<ComboBoxItem>()
|
||||
.FirstOrDefault(i => i.Tag?.ToString() == existing);
|
||||
if (match != null) cb.SelectedItem = match;
|
||||
}
|
||||
var aspectName = aspect.Name;
|
||||
cb.SelectionChanged += (s, e) =>
|
||||
{
|
||||
var val = (cb.SelectedItem as ComboBoxItem)?.Tag?.ToString() ?? "";
|
||||
if (string.IsNullOrEmpty(val)) _draft.Aspects.Remove(aspectName);
|
||||
else _draft.Aspects[aspectName] = val;
|
||||
};
|
||||
input = cb;
|
||||
}
|
||||
else
|
||||
{
|
||||
var tb = new TextBox { Width = 130, Margin = new Thickness(0, 0, 0, 8), Text = existing ?? "" };
|
||||
var aspectName = aspect.Name;
|
||||
tb.TextChanged += (s, e) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(tb.Text)) _draft.Aspects.Remove(aspectName);
|
||||
else _draft.Aspects[aspectName] = tb.Text;
|
||||
};
|
||||
input = tb;
|
||||
}
|
||||
|
||||
var cell = new StackPanel { Margin = new Thickness(0, 0, 12, 0) };
|
||||
cell.Children.Add(labelPanel);
|
||||
cell.Children.Add(input);
|
||||
panels.Add(cell);
|
||||
}
|
||||
|
||||
AspectsItemsControl.ItemsSource = panels;
|
||||
AspectsPanel.Visibility = _currentAspects.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user