feat(save-attachments): show case#, court#, status in candidate picker
Replace the single-line ListBox with a DataGrid (case number, name,
court case number, status) so users disambiguate same-named cases
(e.g. two "שלומוב זויה" cases). Hydrates each search hit via
GET /Case/{id}?select=id,name,number,status,cCourtCaseNumber in
parallel so columns populate without an extra click. CourtCaseNumber
is mapped to the EspoCRM custom field cCourtCaseNumber.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,13 @@ namespace MarcusLaw.OutlookAddin.Core.Models
|
||||
[JsonPropertyName("status")]
|
||||
public string? Status { get; set; }
|
||||
|
||||
// Marcus-Law custom field on Case — court file/docket number.
|
||||
// Field name guessed from EspoCRM "c-prefixed PascalCase" convention;
|
||||
// adjust if the admin used a different name.
|
||||
[JsonPropertyName("cCourtCaseNumber")]
|
||||
[JsonConverter(typeof(FlexibleStringConverter))]
|
||||
public string? CourtCaseNumber { get; set; }
|
||||
|
||||
[JsonPropertyName("accountId")]
|
||||
public string? AccountId { get; set; }
|
||||
|
||||
|
||||
@@ -80,32 +80,28 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CaseItem" TargetType="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<Border x:Name="Bd" Background="{TemplateBinding Background}" CornerRadius="4"
|
||||
Margin="2,1" Padding="8,6">
|
||||
<ContentPresenter />
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{StaticResource HoverBg}" />
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="{StaticResource SelectedBg}" />
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid Margin="16">
|
||||
<Grid Margin="16" x:Name="RootGrid">
|
||||
<Grid.Resources>
|
||||
<Style x:Key="CaseGrid" TargetType="DataGrid">
|
||||
<Setter Property="AutoGenerateColumns" Value="False" />
|
||||
<Setter Property="HeadersVisibility" Value="Column" />
|
||||
<Setter Property="GridLinesVisibility" Value="Horizontal" />
|
||||
<Setter Property="HorizontalGridLinesBrush" Value="#E2E8F0" />
|
||||
<Setter Property="RowBackground" Value="White" />
|
||||
<Setter Property="AlternatingRowBackground" Value="#F8FAFC" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="CanUserAddRows" Value="False" />
|
||||
<Setter Property="CanUserDeleteRows" Value="False" />
|
||||
<Setter Property="CanUserResizeRows" Value="False" />
|
||||
<Setter Property="CanUserReorderColumns" Value="False" />
|
||||
<Setter Property="SelectionMode" Value="Single" />
|
||||
<Setter Property="SelectionUnit" Value="FullRow" />
|
||||
<Setter Property="IsReadOnly" Value="True" />
|
||||
<Setter Property="RowHeight" Value="30" />
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
@@ -130,23 +126,16 @@
|
||||
|
||||
<!-- Cases -->
|
||||
<Border Grid.Row="1" Style="{StaticResource Card}">
|
||||
<ListBox ItemsSource="{Binding Cases}"
|
||||
<DataGrid ItemsSource="{Binding Cases}"
|
||||
SelectedItem="{Binding SelectedCase}"
|
||||
BorderThickness="0"
|
||||
Background="Transparent"
|
||||
ItemContainerStyle="{StaticResource CaseItem}"
|
||||
Padding="4">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
<TextBlock FontSize="13">
|
||||
<Run Text="[" /><Run Text="{Binding Number, Mode=OneWay}" /><Run Text="] " />
|
||||
<Run Text="{Binding Name, Mode=OneWay}" FontWeight="SemiBold" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
Style="{StaticResource CaseGrid}">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="מס׳ תיק" Binding="{Binding Number}" Width="80" />
|
||||
<DataGridTextColumn Header="שם" Binding="{Binding Name}" Width="*" />
|
||||
<DataGridTextColumn Header="מס׳ בבית משפט" Binding="{Binding CourtCaseNumber}" Width="140" />
|
||||
<DataGridTextColumn Header="סטטוס" Binding="{Binding Status}" Width="100" />
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</Border>
|
||||
|
||||
<!-- Folder -->
|
||||
|
||||
@@ -237,6 +237,27 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
|
||||
return name.Trim(' ', '-');
|
||||
}
|
||||
|
||||
private async Task<CaseEntity?> LoadCaseRowAsync(EspoEntityRef hit, CancellationToken ct)
|
||||
{
|
||||
var row = new CaseEntity { Id = hit.Id, Name = hit.Name };
|
||||
if (string.IsNullOrEmpty(hit.Id)) return row;
|
||||
try
|
||||
{
|
||||
var detail = await _client.GetCaseAsync(
|
||||
hit.Id, "id,name,number,status,cCourtCaseNumber", ct).ConfigureAwait(false);
|
||||
row.Number = detail.Number;
|
||||
row.Status = detail.Status;
|
||||
row.CourtCaseNumber = detail.CourtCaseNumber;
|
||||
if (!string.IsNullOrEmpty(detail.Name)) row.Name = detail.Name;
|
||||
}
|
||||
catch (OperationCanceledException) { throw; }
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning(ex, "Hydrating case {CaseId} for picker failed", hit.Id);
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
private async Task SearchAsync(string query, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
@@ -255,20 +276,32 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
Cases.Clear();
|
||||
int kept = 0;
|
||||
var caseHits = new List<EspoEntityRef>();
|
||||
foreach (var hit in search.List)
|
||||
{
|
||||
if (!string.Equals(hit.EntityType, "Case", StringComparison.OrdinalIgnoreCase)) continue;
|
||||
Cases.Add(new CaseEntity
|
||||
{
|
||||
Id = hit.Id,
|
||||
Name = hit.Name
|
||||
});
|
||||
kept++;
|
||||
if (string.Equals(hit.EntityType, "Case", StringComparison.OrdinalIgnoreCase))
|
||||
caseHits.Add(hit);
|
||||
}
|
||||
StatusMessage = kept == 0
|
||||
? "אין תוצאות מסוג תיק (Case)"
|
||||
: $"נמצאו {kept} תיקים";
|
||||
|
||||
if (caseHits.Count == 0)
|
||||
{
|
||||
StatusMessage = "אין תוצאות מסוג תיק (Case)";
|
||||
return;
|
||||
}
|
||||
|
||||
var detailTasks = new List<Task<CaseEntity?>>(caseHits.Count);
|
||||
foreach (var hit in caseHits)
|
||||
{
|
||||
detailTasks.Add(LoadCaseRowAsync(hit, ct));
|
||||
}
|
||||
var details = await Task.WhenAll(detailTasks).ConfigureAwait(true);
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
foreach (var row in details)
|
||||
{
|
||||
if (row != null) Cases.Add(row);
|
||||
}
|
||||
StatusMessage = $"נמצאו {Cases.Count} תיקים";
|
||||
}
|
||||
catch (OperationCanceledException) { /* superseded */ }
|
||||
catch (EspoCrmAuthorizationException)
|
||||
|
||||
Reference in New Issue
Block a user