Files
EbayListingTool/EbayListingTool/Views/BulkImportView.xaml
Peter Foster 9fad0f2ac0 Initial commit: EbayListingTool WPF application
C# WPF desktop app for creating eBay UK listings with AI-powered
photo analysis. Features: multi-photo vision analysis via OpenRouter
(Claude), local listing save/export, saved listings browser,
single item listing form, bulk import from CSV/Excel, and eBay
OAuth authentication.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 17:33:27 +01:00

485 lines
27 KiB
XML

<UserControl x:Class="EbayListingTool.Views.BulkImportView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks">
<UserControl.Resources>
<!-- Shared value converters via inline DataTriggers -->
<!-- Status text colour: applied to DataGrid cells via a DataTemplate -->
<Style x:Key="StatusTextStyle" TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Foreground" Value="{DynamicResource MahApps.Brushes.Gray3}"/>
</Style>
<!-- DataGrid row style: alternating shading + status-driven foreground -->
<Style x:Key="BulkRowStyle" TargetType="DataGridRow"
BasedOn="{StaticResource MahApps.Styles.DataGridRow}">
<!-- Even rows get a very slight alternate tint -->
<Style.Triggers>
<Trigger Property="AlternationIndex" Value="1">
<Setter Property="Background" Value="{DynamicResource MahApps.Brushes.Gray10}"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{DynamicResource MahApps.Brushes.Highlight}"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- AI toolbar button: indigo gradient matching SingleItemView -->
<Style x:Key="AiToolbarButton" TargetType="Button"
BasedOn="{StaticResource MahApps.Styles.Button.Square}">
<Setter Property="Height" Value="32"/>
<Setter Property="Padding" Value="12,0"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#7C3AED" Offset="0"/>
<GradientStop Color="#4F46E5" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#8B5CF6" Offset="0"/>
<GradientStop Color="#6366F1" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.45"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- Small AI row button -->
<Style x:Key="AiRowButton" TargetType="Button"
BasedOn="{StaticResource MahApps.Styles.Button.Square}">
<Setter Property="Width" Value="28"/>
<Setter Property="Height" Value="24"/>
<Setter Property="FontSize" Value="11"/>
<Setter Property="Margin" Value="2,0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#7C3AED" Offset="0"/>
<GradientStop Color="#4F46E5" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#8B5CF6" Offset="0"/>
<GradientStop Color="#6366F1" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid Margin="16,12,16,12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- ================================================================
TOOLBAR
================================================================ -->
<Border Grid.Row="0"
BorderBrush="{DynamicResource MahApps.Brushes.Gray8}"
BorderThickness="1" CornerRadius="4"
Background="{DynamicResource MahApps.Brushes.Gray10}"
Padding="10,8" Margin="0,0,0,10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Open file -->
<Button Grid.Column="0" Click="OpenFile_Click"
Style="{DynamicResource MahApps.Styles.Button.Square.Accent}"
Height="32" Padding="12,0" FontSize="13">
<StackPanel Orientation="Horizontal">
<iconPacks:PackIconMaterial Kind="FolderOpenOutline" Width="14" Height="14"
Margin="0,0,6,0" VerticalAlignment="Center"/>
<TextBlock Text="Open CSV / Excel" VerticalAlignment="Center"/>
</StackPanel>
</Button>
<!-- Download template -->
<Button Grid.Column="1" Click="DownloadTemplate_Click" Margin="8,0,0,0"
Style="{DynamicResource MahApps.Styles.Button.Square}"
Height="32" Padding="12,0" FontSize="13">
<StackPanel Orientation="Horizontal">
<iconPacks:PackIconMaterial Kind="Download" Width="14" Height="14"
Margin="0,0,6,0" VerticalAlignment="Center"/>
<TextBlock Text="Template" VerticalAlignment="Center"/>
</StackPanel>
</Button>
<!-- AI Enhance All — gradient AI button with spinner -->
<Button Grid.Column="2" x:Name="EnhanceAllBtn" Margin="8,0,0,0"
Click="AiEnhanceAll_Click"
Style="{StaticResource AiToolbarButton}"
IsEnabled="False">
<StackPanel Orientation="Horizontal">
<mah:ProgressRing x:Name="EnhanceSpinner"
Width="14" Height="14" Margin="0,0,6,0"
Foreground="White" Visibility="Collapsed"/>
<iconPacks:PackIconMaterial x:Name="EnhanceIcon"
Kind="AutoFix" Width="14" Height="14"
Margin="0,0,6,0" VerticalAlignment="Center"/>
<TextBlock Text="AI Enhance All" VerticalAlignment="Center"/>
</StackPanel>
</Button>
<!-- Post All -->
<Button Grid.Column="3" x:Name="PostAllBtn" Margin="8,0,0,0"
Click="PostAll_Click"
Style="{DynamicResource MahApps.Styles.Button.Square.Accent}"
Height="32" Padding="12,0" FontSize="13" FontWeight="SemiBold"
IsEnabled="False">
<StackPanel Orientation="Horizontal">
<mah:ProgressRing x:Name="PostAllSpinner"
Width="14" Height="14" Margin="0,0,6,0"
Foreground="White" Visibility="Collapsed"/>
<iconPacks:PackIconMaterial x:Name="PostAllIcon"
Kind="Send" Width="14" Height="14"
Margin="0,0,6,0" VerticalAlignment="Center"/>
<TextBlock Text="Post All" VerticalAlignment="Center"/>
</StackPanel>
</Button>
<!-- Clear -->
<Button Grid.Column="4" x:Name="ClearBtn" Margin="8,0,0,0"
Click="ClearAll_Click"
Style="{DynamicResource MahApps.Styles.Button.Square}"
Height="32" Padding="12,0" FontSize="13"
IsEnabled="False">
<StackPanel Orientation="Horizontal">
<iconPacks:PackIconMaterial Kind="TrashCanOutline" Width="14" Height="14"
Margin="0,0,6,0" VerticalAlignment="Center"/>
<TextBlock Text="Clear" VerticalAlignment="Center"/>
</StackPanel>
</Button>
<!-- Row count — right-aligned -->
<Border Grid.Column="6" CornerRadius="10" Padding="10,3"
Background="{DynamicResource MahApps.Brushes.Gray8}"
VerticalAlignment="Center">
<StackPanel Orientation="Horizontal">
<iconPacks:PackIconMaterial Kind="TableRowsPlusAfter" Width="12" Height="12"
Margin="0,0,5,0" VerticalAlignment="Center"
Foreground="{DynamicResource MahApps.Brushes.Gray4}"/>
<TextBlock x:Name="RowCountLabel" VerticalAlignment="Center"
FontSize="12"
Foreground="{DynamicResource MahApps.Brushes.Gray3}"
Text="No file loaded"/>
</StackPanel>
</Border>
</Grid>
</Border>
<!-- ================================================================
PROGRESS BAR
================================================================ -->
<Grid Grid.Row="1">
<mah:MetroProgressBar x:Name="ProgressBar" Height="5"
Margin="0,0,0,10" Visibility="Collapsed"
Minimum="0" Maximum="100"/>
</Grid>
<!-- ================================================================
SUMMARY BAR (shown after posting)
================================================================ -->
<Border Grid.Row="2" x:Name="SummaryBar" Visibility="Collapsed"
CornerRadius="4" Padding="12,6" Margin="0,0,0,8"
Background="{DynamicResource MahApps.Brushes.Gray10}"
BorderBrush="{DynamicResource MahApps.Brushes.Gray8}" BorderThickness="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Summary text set from code-behind via SummaryLabel.Text -->
<TextBlock x:Name="SummaryLabel" Grid.Column="0"
FontSize="12" VerticalAlignment="Center"
Foreground="{DynamicResource MahApps.Brushes.Gray3}"/>
<!-- Colour-coded stat pills -->
<Border Grid.Column="1" CornerRadius="10" Padding="8,2" Margin="8,0,0,0"
Background="#1A4CAF50" BorderBrush="#4CAF50" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<Ellipse Width="6" Height="6" Fill="#4CAF50" Margin="0,0,4,0"
VerticalAlignment="Center"/>
<TextBlock x:Name="SummaryPostedPill" Text="0 posted" FontSize="11"
Foreground="#4CAF50" FontWeight="SemiBold"/>
</StackPanel>
</Border>
<Border Grid.Column="2" CornerRadius="10" Padding="8,2" Margin="6,0,0,0"
Background="#1AE53935" BorderBrush="#E53935" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<Ellipse Width="6" Height="6" Fill="#E53935" Margin="0,0,4,0"
VerticalAlignment="Center"/>
<TextBlock x:Name="SummaryFailedPill" Text="0 failed" FontSize="11"
Foreground="#E53935" FontWeight="SemiBold"/>
</StackPanel>
</Border>
<Border Grid.Column="3" CornerRadius="10" Padding="8,2" Margin="6,0,0,0"
Background="#1AFFA726" BorderBrush="#FFA726" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<Ellipse Width="6" Height="6" Fill="#FFA726" Margin="0,0,4,0"
VerticalAlignment="Center"/>
<TextBlock x:Name="SummaryReadyPill" Text="0 ready" FontSize="11"
Foreground="#FFA726" FontWeight="SemiBold"/>
</StackPanel>
</Border>
</Grid>
</Border>
<!-- ================================================================
DATA GRID
================================================================ -->
<DataGrid Grid.Row="3" x:Name="ItemsGrid"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
SelectionMode="Single"
HeadersVisibility="Column"
GridLinesVisibility="Horizontal"
AlternationCount="2"
RowStyle="{StaticResource BulkRowStyle}"
RowHeight="36"
FontSize="12"
Visibility="Collapsed"
Style="{DynamicResource MahApps.Styles.DataGrid}">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader"
BasedOn="{StaticResource MahApps.Styles.DataGridColumnHeader}">
<Setter Property="FontSize" Value="11"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Foreground" Value="{DynamicResource MahApps.Brushes.Gray3}"/>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.Columns>
<!-- Status icon column -->
<DataGridTextColumn Header="" Width="30"
Binding="{Binding StatusIcon}"
IsReadOnly="True"/>
<!-- Title — flexible fill -->
<DataGridTextColumn Header="TITLE" Width="*"
Binding="{Binding Title}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<!-- Condition -->
<DataGridTextColumn Header="CONDITION" Width="90"
Binding="{Binding Condition}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<!-- Category -->
<DataGridTextColumn Header="CATEGORY" Width="140"
Binding="{Binding CategoryKeyword}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<!-- Price -->
<DataGridTextColumn Header="PRICE £" Width="70"
Binding="{Binding Price}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<!-- Qty -->
<DataGridTextColumn Header="QTY" Width="45"
Binding="{Binding Quantity}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
<!-- Status — colour-coded badge -->
<DataGridTemplateColumn Header="STATUS" Width="140" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<!-- Badge whose colour is driven by Status enum via DataTriggers -->
<Border CornerRadius="3" Padding="6,2" Margin="2,4"
HorizontalAlignment="Left" VerticalAlignment="Center">
<Border.Style>
<Style TargetType="Border">
<!-- Default: neutral grey -->
<Setter Property="Background" Value="#22888888"/>
<Setter Property="BorderBrush" Value="#55888888"/>
<Setter Property="BorderThickness" Value="1"/>
<Style.Triggers>
<!-- Posted → green -->
<DataTrigger Binding="{Binding StatusBadge}" Value="Posted">
<Setter Property="Background" Value="#1A4CAF50"/>
<Setter Property="BorderBrush" Value="#4CAF50"/>
</DataTrigger>
<!-- Failed → red -->
<DataTrigger Binding="{Binding StatusBadge}" Value="Failed">
<Setter Property="Background" Value="#1AE53935"/>
<Setter Property="BorderBrush" Value="#E53935"/>
</DataTrigger>
<!-- Enhancing → amber -->
<DataTrigger Binding="{Binding StatusBadge}" Value="Enhancing">
<Setter Property="Background" Value="#1AFFA726"/>
<Setter Property="BorderBrush" Value="#FFA726"/>
</DataTrigger>
<!-- Posting → blue -->
<DataTrigger Binding="{Binding StatusBadge}" Value="Posting">
<Setter Property="Background" Value="#1A2196F3"/>
<Setter Property="BorderBrush" Value="#2196F3"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{Binding StatusMessage}"
FontSize="11" FontWeight="SemiBold">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource MahApps.Brushes.Gray3}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding StatusBadge}" Value="Posted">
<Setter Property="Foreground" Value="#4CAF50"/>
</DataTrigger>
<DataTrigger Binding="{Binding StatusBadge}" Value="Failed">
<Setter Property="Foreground" Value="#E53935"/>
</DataTrigger>
<DataTrigger Binding="{Binding StatusBadge}" Value="Enhancing">
<Setter Property="Foreground" Value="#FFA726"/>
</DataTrigger>
<DataTrigger Binding="{Binding StatusBadge}" Value="Posting">
<Setter Property="Foreground" Value="#2196F3"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- Actions: AI enhance + post row -->
<DataGridTemplateColumn Header="ACTIONS" Width="80" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<!-- AI Enhance row -->
<Button Style="{StaticResource AiRowButton}"
ToolTip="AI Enhance this row"
Click="AiEnhanceRow_Click" Tag="{Binding}">
<iconPacks:PackIconMaterial Kind="AutoFix"
Width="11" Height="11"/>
</Button>
<!-- Post row -->
<Button Width="28" Height="24" Margin="2,0"
ToolTip="Post this item"
Click="PostRow_Click" Tag="{Binding}"
Style="{DynamicResource MahApps.Styles.Button.Square.Accent}">
<iconPacks:PackIconMaterial Kind="Send"
Width="11" Height="11"/>
</Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<!-- ================================================================
EMPTY STATE (shown before any file is loaded)
================================================================ -->
<Border Grid.Row="3" x:Name="EmptyState"
BorderBrush="{DynamicResource MahApps.Brushes.Gray8}"
BorderThickness="1" CornerRadius="4"
Background="{DynamicResource MahApps.Brushes.Gray10}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<iconPacks:PackIconMaterial Kind="FileTableOutline"
Width="56" Height="56"
HorizontalAlignment="Center" Margin="0,0,0,12"
Foreground="{DynamicResource MahApps.Brushes.Gray7}"/>
<TextBlock Text="Open a CSV or Excel file to bulk import listings"
FontSize="14" FontWeight="SemiBold"
HorizontalAlignment="Center"
Foreground="{DynamicResource MahApps.Brushes.Gray4}"/>
<TextBlock Text="Columns: Title, Description, Price, Condition, CategoryKeyword, Quantity, PhotoPaths"
FontSize="11" HorizontalAlignment="Center" Margin="0,6,0,20"
Foreground="{DynamicResource MahApps.Brushes.Gray6}"
TextWrapping="Wrap" MaxWidth="480" TextAlignment="Center"/>
<Button HorizontalAlignment="Center"
Click="DownloadTemplate_Click"
Style="{DynamicResource MahApps.Styles.Button.Square.Accent}"
Height="34" Padding="16,0" FontSize="13">
<StackPanel Orientation="Horizontal">
<iconPacks:PackIconMaterial Kind="Download" Width="14" Height="14"
Margin="0,0,6,0" VerticalAlignment="Center"/>
<TextBlock Text="Download CSV Template" VerticalAlignment="Center"/>
</StackPanel>
</Button>
</StackPanel>
</Border>
</Grid>
</UserControl>