Created
January 5, 2025 21:21
-
-
Save tjdaley/fef9cc275235e62578fbb288221fe2f7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" | |
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit" | |
xmlns:viewmodels="clr-namespace:TrakerManager.ViewModels" | |
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | |
xmlns:converters="clr-namespace:TrakerManager.Converters" | |
x:Class="TrakerManager.Views.MainPage" | |
Title="Traker Manager"> | |
<ContentPage.Resources> | |
<converters:IntToBoolConverter x:Key="IntToBoolConverter"/> | |
<converters:InvertedBoolConverter x:Key="InvertedBoolConverter"/> | |
<converters:SortIndicatorConverter x:Key="SortIndicatorConverter"/> | |
<!-- Style for client items in left panel --> | |
<Style x:Key="ClientItemStyle" TargetType="Grid"> | |
<Setter Property="Background" Value="Transparent"/> | |
<Setter Property="VisualStateManager.VisualStateGroups"> | |
<VisualStateGroupList> | |
<VisualStateGroup Name="CommonStates"> | |
<VisualState Name="Normal"> | |
<VisualState.Setters> | |
<Setter Property="Background" Value="Transparent"/> | |
</VisualState.Setters> | |
</VisualState> | |
<VisualState Name="Selected"> | |
<VisualState.Setters> | |
<Setter Property="Background" Value="LightYellow"/> | |
</VisualState.Setters> | |
</VisualState> | |
<VisualState Name="PointerOver"> | |
<VisualState.Setters> | |
<Setter Property="Background" Value="#e3f2fd"/> | |
</VisualState.Setters> | |
</VisualState> | |
</VisualStateGroup> | |
</VisualStateGroupList> | |
</Setter> | |
</Style> | |
<!-- Style for folder items in right panel --> | |
<Style x:Key="FolderItemStyle" TargetType="Grid"> | |
<Setter Property="Background" Value="Transparent"/> | |
<Setter Property="VisualStateManager.VisualStateGroups"> | |
<VisualStateGroupList> | |
<VisualStateGroup Name="CommonStates"> | |
<VisualState Name="Normal"> | |
<VisualState.Setters> | |
<Setter Property="Background" Value="Transparent"/> | |
</VisualState.Setters> | |
</VisualState> | |
<VisualState Name="Selected"> | |
<VisualState.Setters> | |
<Setter Property="Background" Value="#e3f2fd"/> | |
</VisualState.Setters> | |
</VisualState> | |
<VisualState Name="PointerOver"> | |
<VisualState.Setters> | |
<Setter Property="Background" Value="#f5f5f5"/> | |
</VisualState.Setters> | |
</VisualState> | |
</VisualStateGroup> | |
</VisualStateGroupList> | |
</Setter> | |
</Style> | |
<Style x:Key="ClassificationFolderItemStyle" TargetType="Grid"> | |
<Setter Property="Background" Value="Orange"/> | |
<Setter Property="VisualStateManager.VisualStateGroups"> | |
<VisualStateGroupList> | |
<VisualStateGroup Name="CommonStates"> | |
<VisualState Name="Normal"> | |
<VisualState.Setters> | |
<Setter Property="Background" Value="Orange"/> | |
</VisualState.Setters> | |
</VisualState> | |
<VisualState Name="Selected"> | |
<VisualState.Setters> | |
<Setter Property="Background" Value="Pink"/> | |
</VisualState.Setters> | |
</VisualState> | |
<VisualState Name="PointerOver"> | |
<VisualState.Setters> | |
<Setter Property="Background" Value="IndianRed"/> | |
</VisualState.Setters> | |
</VisualState> | |
</VisualStateGroup> | |
</VisualStateGroupList> | |
</Setter> | |
</Style> | |
<Style x:Key="DocumentRowStyle" TargetType="Grid"> | |
<Setter Property="VisualStateManager.VisualStateGroups"> | |
<VisualStateGroupList> | |
<VisualStateGroup Name="CommonStates"> | |
<VisualState Name="Normal"> | |
<VisualState.Setters> | |
<Setter Property="BackgroundColor" Value="Transparent"/> | |
</VisualState.Setters> | |
</VisualState> | |
<VisualState Name="Selected"> | |
<VisualState.Setters> | |
<Setter Property="BackgroundColor" Value="#2A2A2A"/> | |
</VisualState.Setters> | |
</VisualState> | |
<VisualState Name="PointerOver"> | |
<VisualState.Setters> | |
<Setter Property="BackgroundColor" Value="#232323"/> | |
</VisualState.Setters> | |
</VisualState> | |
</VisualStateGroup> | |
</VisualStateGroupList> | |
</Setter> | |
</Style> | |
<Style x:Key="DocumentGridLabelStyle" TargetType="Label"> | |
<Setter Property="TextColor" Value="black"/> | |
<Style.Triggers> | |
<DataTrigger TargetType="Label" | |
Binding="{Binding Source={RelativeSource AncestorType={x:Type Grid}}, Path=Background.Color}" | |
Value="#2A2A2A"> | |
<Setter Property="TextColor" Value="#00FFFF"/> | |
</DataTrigger> | |
<DataTrigger TargetType="Label" | |
Binding="{Binding Source={RelativeSource AncestorType={x:Type Grid}}, Path=Background.Color}" | |
Value="#232323"> | |
<Setter Property="TextColor" Value="#E0E0E0"/> | |
</DataTrigger> | |
</Style.Triggers> | |
</Style> | |
<Style x:Key="SortHeaderButtonStyle" TargetType="Button"> | |
<Setter Property="BackgroundColor" Value="Transparent"/> | |
<Setter Property="FontAttributes" Value="Bold"/> | |
<Setter Property="TextColor" Value="Black"/> | |
<Setter Property="Padding" Value="1,1"/> | |
<Setter Property="HorizontalOptions" Value="Start"/> | |
</Style> | |
<Style x:Key="DetailLabelStyle" TargetType="Label"> | |
<Setter Property="FontSize" Value="11"/> | |
<Setter Property="TextColor" Value="#00FFFF"/> | |
<Setter Property="HorizontalOptions" Value="End"/> | |
<Setter Property="VerticalOptions" Value="Center"/> | |
<Setter Property="Margin" Value="0"/> | |
<Setter Property="Padding" Value="0"/> | |
</Style> | |
<!-- When the value is shown in an entry field --> | |
<Style x:Key="DetailValueStyle" TargetType="Entry"> | |
<Setter Property="FontSize" Value="11"/> | |
<Setter Property="TextColor" Value="White"/> | |
<Setter Property="BackgroundColor" Value="#2A2A2A"/> | |
<Setter Property="Margin" Value="2"/> | |
<Setter Property="HeightRequest" Value="35"/> | |
<Setter Property="VerticalTextAlignment" Value="Center"/> | |
</Style> | |
<!-- When the Value is shown in a label --> | |
<Style x:Key="DetailValueLabelStyle" TargetType="Label"> | |
<Setter Property="FontSize" Value="11"/> | |
<Setter Property="TextColor" Value="White"/> | |
<Setter Property="VerticalOptions" Value="Center"/> | |
<Setter Property="Margin" Value="0"/> | |
<Setter Property="Padding" Value="0"/> | |
</Style> | |
<!-- For the Copy Icons --> | |
<Style x:Key="CopyButtonStyle" TargetType="Button"> | |
<Setter Property="HeightRequest" Value="20"/> | |
<Setter Property="WidthRequest" Value="20"/> | |
<Setter Property="Padding" Value="2"/> | |
<Setter Property="BackgroundColor" Value="Transparent"/> | |
<Setter Property="TextColor" Value="#666666"/> | |
<Setter Property="Text" Value="📋"/> | |
<Setter Property="FontSize" Value="12"/> | |
<Setter Property="VerticalOptions" Value="Center"/> | |
<Setter Property="HorizontalOptions" Value="Start"/> | |
<Setter Property="ToolTipProperties.Text" Value="Click to copy field value."/> | |
</Style> | |
<!-- For the Copy All and Edit Buttons --> | |
<Style x:Key="ActionButtonStyle" TargetType="Button"> | |
<Setter Property="HeightRequest" Value="0"/> | |
<Setter Property="Margin" Value="0,0,0,0"/> | |
<Setter Property="Padding" Value="2,2,2,2"/> | |
<Setter Property="FontSize" Value="11"/> | |
</Style> | |
</ContentPage.Resources> | |
<Grid ColumnDefinitions="300, *" | |
BackgroundColor="LightGray" | |
Padding="10"> | |
<!-- Left Panel with Clients --> | |
<Border Grid.Column="0" | |
BackgroundColor="White" | |
Stroke="Gray" | |
StrokeThickness="1" | |
Padding="5" | |
HorizontalOptions="Fill" | |
VerticalOptions="Fill"> | |
<CollectionView ItemsSource="{Binding TreeItems}" | |
SelectionMode="Single" | |
SelectedItem="{Binding SelectedTreeItem}"> | |
<CollectionView.ItemTemplate> | |
<DataTemplate> | |
<Grid Style="{StaticResource ClientItemStyle}" | |
Padding="10,5"> | |
<Grid.GestureRecognizers> | |
<PointerGestureRecognizer PointerEntered="OnPointerEntered" | |
PointerExited="OnPointerExited"/> | |
</Grid.GestureRecognizers> | |
<Grid.ColumnDefinitions> | |
<ColumnDefinition Width="*"/> | |
<ColumnDefinition Width="Auto"/> | |
</Grid.ColumnDefinitions> | |
<Label Grid.Column="0" | |
Text="{Binding Name}" | |
TextColor="Black" | |
FontSize="14"> | |
<Label.Style> | |
<Style TargetType="Label"> | |
<Setter Property="TextColor" Value="#0066cc"/> | |
<Setter Property="FontSize" Value="14"/> | |
<Style.Triggers> | |
<!-- Make the selected client bold and change its color --> | |
<DataTrigger TargetType="Label" Binding="{Binding IsSelected}" Value="True"> | |
<Setter Property="BackgroundColor" Value="#0066cc"/> | |
<Setter Property="TextColor" Value="White"/> | |
<Setter Property="FontAttributes" Value="Bold"/> | |
</DataTrigger> | |
</Style.Triggers> | |
</Style> | |
</Label.Style> | |
</Label> | |
<!--Label Grid.Column="1" | |
Text="{Binding SubItems.Count, StringFormat='({0})'}" | |
TextColor="Gray" | |
FontSize="12"/--> | |
</Grid> | |
</DataTemplate> | |
</CollectionView.ItemTemplate> | |
</CollectionView> | |
</Border> | |
<!-- Right Panel --> | |
<Grid Grid.Column="1"> | |
<Grid.RowDefinitions> | |
<RowDefinition Height="Auto"/> | |
<RowDefinition Height="*"/> | |
</Grid.RowDefinitions> | |
<!-- Breadcrumb Navigation --> | |
<Border Grid.Row="0" | |
Margin="0,0,0,10" | |
Background="Pink" | |
Stroke="LightGray" | |
StrokeThickness="1" | |
Padding="10,5"> | |
<CollectionView ItemsSource="{Binding Breadcrumbs}" | |
HorizontalOptions="Start"> | |
<CollectionView.ItemsLayout> | |
<LinearItemsLayout Orientation="Horizontal" | |
ItemSpacing="5"/> | |
</CollectionView.ItemsLayout> | |
<CollectionView.ItemTemplate> | |
<DataTemplate> | |
<StackLayout Orientation="Horizontal"> | |
<Label > | |
<Label.GestureRecognizers> | |
<TapGestureRecognizer | |
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:MainViewModel}}, Path=BreadcrumbSelectedCommand}" | |
CommandParameter="{Binding .}" /> | |
</Label.GestureRecognizers> | |
<Label.Style> | |
<Style TargetType="Label"> | |
<Setter Property="TextColor" Value="#0066cc"/> | |
<Setter Property="FontSize" Value="14"/> | |
<Style.Triggers> | |
<!-- Make the last breadcrumb bold and change its color --> | |
<DataTrigger TargetType="Label" Binding="{Binding IsLast}" Value="True"> | |
<Setter Property="TextColor" Value="#333333"/> | |
<Setter Property="FontAttributes" Value="Bold"/> | |
</DataTrigger> | |
</Style.Triggers> | |
</Style> | |
</Label.Style> | |
<Label.Text> | |
<MultiBinding StringFormat="{}{0} {1}"> | |
<!-- The actual breadcrumb text --> | |
<Binding Path="Text"/> | |
<!-- The separator (>) --> | |
<Binding Path="IsLast" Converter="{StaticResource InvertedBoolConverter}"> | |
<Binding.ConverterParameter>></Binding.ConverterParameter> | |
</Binding> | |
</MultiBinding> | |
</Label.Text> | |
</Label> | |
</StackLayout> | |
</DataTemplate> | |
</CollectionView.ItemTemplate> | |
</CollectionView> | |
</Border> | |
<!-- Dynamic Content Area --> | |
<Grid Grid.Row="1"> | |
<!-- Tracker View --> | |
<CollectionView ItemsSource="{Binding SelectedClientTrackers}" | |
IsVisible="{Binding ShowTrackers}"> | |
<CollectionView.ItemsLayout> | |
<GridItemsLayout Orientation="Vertical" | |
Span="4"/> | |
</CollectionView.ItemsLayout> | |
<CollectionView.ItemTemplate> | |
<DataTemplate> | |
<Grid Style="{StaticResource FolderItemStyle}" | |
Padding="10" | |
WidthRequest="120"> | |
<Grid.GestureRecognizers> | |
<TapGestureRecognizer | |
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:MainViewModel}}, Path=TrackerSelectedCommand}" | |
CommandParameter="{Binding .}"/> | |
<PointerGestureRecognizer PointerEntered="OnPointerEntered" | |
PointerExited="OnPointerExited"/> | |
</Grid.GestureRecognizers> | |
<Grid.RowDefinitions> | |
<RowDefinition Height="64"/> | |
<RowDefinition Height="Auto"/> | |
</Grid.RowDefinitions> | |
<Image Source="folder.png" | |
Grid.Row="0" | |
HeightRequest="64" | |
WidthRequest="64" | |
HorizontalOptions="Center"/> | |
<Label Text="{Binding Name}" | |
Grid.Row="1" | |
TextColor="Black" | |
HorizontalOptions="Center" | |
LineBreakMode="WordWrap"/> | |
</Grid> | |
</DataTemplate> | |
</CollectionView.ItemTemplate> | |
</CollectionView> | |
<!-- Classifications View --> | |
<CollectionView ItemsSource="{Binding SelectedTrackerClassifications}" | |
IsVisible="{Binding ShowClassifications}"> | |
<CollectionView.ItemsLayout> | |
<GridItemsLayout Orientation="Vertical" Span="4"/> | |
</CollectionView.ItemsLayout> | |
<CollectionView.ItemTemplate> | |
<DataTemplate> | |
<Grid Style="{StaticResource FolderItemStyle}" | |
Padding="10" | |
WidthRequest="120"> | |
<Grid.GestureRecognizers> | |
<TapGestureRecognizer | |
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:MainViewModel}}, Path=ClassificationSelectedCommand}" | |
CommandParameter="{Binding .}" /> | |
<TapGestureRecognizer | |
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:MainViewModel}}, Path=ShowContextMenuCommand}" | |
CommandParameter="{Binding .}" | |
NumberOfTapsRequired="2" /> | |
<PointerGestureRecognizer PointerEntered="OnPointerEntered" | |
PointerExited="OnPointerExited" /> | |
</Grid.GestureRecognizers> | |
<Grid.RowDefinitions> | |
<RowDefinition Height="64" /> | |
<RowDefinition Height="Auto" /> | |
<RowDefinition Height="Auto" /> | |
</Grid.RowDefinitions> | |
<Image Source="folder.png" | |
Grid.Row="0" | |
HeightRequest="64" | |
WidthRequest="64" | |
HorizontalOptions="Center" /> | |
<Label Text="{Binding Name}" | |
Grid.Row="1" | |
TextColor="Black" | |
HorizontalOptions="Center" | |
LineBreakMode="WordWrap" /> | |
<Label Text="{Binding DocumentCount, StringFormat='({0} documents)'}" | |
Grid.Row="2" | |
TextColor="Gray" | |
FontSize="12" | |
HorizontalOptions="Center" /> | |
</Grid> | |
</DataTemplate> | |
</CollectionView.ItemTemplate> | |
</CollectionView> | |
<!-- Sub-Classifications View --> | |
<CollectionView ItemsSource="{Binding SubClassificationFolders}" | |
IsVisible="{Binding ShowSubClassifications}"> | |
<CollectionView.ItemsLayout> | |
<GridItemsLayout Orientation="Vertical" | |
Span="4"/> | |
</CollectionView.ItemsLayout> | |
<CollectionView.ItemTemplate> | |
<DataTemplate> | |
<Grid Style="{StaticResource FolderItemStyle}" | |
Padding="10" | |
WidthRequest="120"> | |
<Grid.GestureRecognizers> | |
<TapGestureRecognizer | |
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodels:MainViewModel}}, Path=SubClassificationSelectedCommand}" | |
CommandParameter="{Binding .}"/> | |
<PointerGestureRecognizer PointerEntered="OnPointerEntered" | |
PointerExited="OnPointerExited"/> | |
</Grid.GestureRecognizers> | |
<Grid.RowDefinitions> | |
<RowDefinition Height="64"/> | |
<RowDefinition Height="Auto"/> | |
<RowDefinition Height="Auto"/> | |
</Grid.RowDefinitions> | |
<Image Source="folder.png" | |
Grid.Row="0" | |
HeightRequest="64" | |
WidthRequest="64" | |
HorizontalOptions="Center"/> | |
<Label Text="{Binding DisplayName}" | |
Grid.Row="1" | |
TextColor="Black" | |
HorizontalOptions="Center" | |
LineBreakMode="WordWrap"/> | |
<Label Text="{Binding Documents.Count, StringFormat='({0} documents)'}" | |
Grid.Row="2" | |
TextColor="Gray" | |
FontSize="12" | |
HorizontalOptions="Center"/> | |
</Grid> | |
</DataTemplate> | |
</CollectionView.ItemTemplate> | |
</CollectionView> | |
<!-- Documents Grid View --> | |
<Grid IsVisible="{Binding ShowDocuments}" RowDefinitions="*, Auto"> | |
<!-- Documents Grid --> | |
<CollectionView Grid.Row="0" | |
SelectionMode="Single" | |
ItemsSource="{Binding DocumentsForSelectedClassification}" | |
SelectedItem="{Binding SelectedDocument}"> | |
<CollectionView.Header> | |
<Grid ColumnDefinitions="1*, 1*, 2*, 1*, 1*, 1*, 1*, 1*" | |
Padding="10,5"> | |
<Button Grid.Column="0" | |
Command="{Binding SortCommand}" | |
CommandParameter="DocumentDate" | |
Style="{StaticResource SortHeaderButtonStyle}"> | |
<Button.Text> | |
<MultiBinding Converter="{StaticResource SortIndicatorConverter}" | |
ConverterParameter="DocumentDate"> | |
<Binding Source="Document Date"/> | |
<Binding Path="CurrentSortField"/> | |
<Binding Path="IsAscending"/> | |
</MultiBinding> | |
</Button.Text> | |
</Button> | |
<Button Grid.Column="1" | |
Command="{Binding SortCommand}" | |
CommandParameter="Classification" | |
Style="{StaticResource SortHeaderButtonStyle}"> | |
<Button.Text> | |
<MultiBinding Converter="{StaticResource SortIndicatorConverter}" | |
ConverterParameter="Classification"> | |
<Binding Source="Classification"/> | |
<Binding Path="CurrentSortField"/> | |
<Binding Path="IsAscending"/> | |
</MultiBinding> | |
</Button.Text> | |
</Button> | |
<Button Grid.Column="2" | |
Command="{Binding SortCommand}" | |
CommandParameter="Title" | |
Style="{StaticResource SortHeaderButtonStyle}"> | |
<Button.Text> | |
<MultiBinding Converter="{StaticResource SortIndicatorConverter}" | |
ConverterParameter="Title"> | |
<Binding Source="Title"/> | |
<Binding Path="CurrentSortField"/> | |
<Binding Path="IsAscending"/> | |
</MultiBinding> | |
</Button.Text> | |
</Button> | |
<Button Grid.Column="3" | |
Command="{Binding SortCommand}" | |
CommandParameter="BeginningBates" | |
Style="{StaticResource SortHeaderButtonStyle}"> | |
<Button.Text> | |
<MultiBinding Converter="{StaticResource SortIndicatorConverter}" | |
ConverterParameter="BeginningBates"> | |
<Binding Source="Beg Bates"/> | |
<Binding Path="CurrentSortField"/> | |
<Binding Path="IsAscending"/> | |
</MultiBinding> | |
</Button.Text> | |
</Button> | |
<Button Grid.Column="4" | |
Command="{Binding SortCommand}" | |
CommandParameter="EndingBates" | |
Style="{StaticResource SortHeaderButtonStyle}"> | |
<Button.Text> | |
<MultiBinding Converter="{StaticResource SortIndicatorConverter}" | |
ConverterParameter="EndingBates"> | |
<Binding Source="End Bates"/> | |
<Binding Path="CurrentSortField"/> | |
<Binding Path="IsAscending"/> | |
</MultiBinding> | |
</Button.Text> | |
</Button> | |
<Button Grid.Column="5" | |
Command="{Binding SortCommand}" | |
CommandParameter="Institution" | |
Style="{StaticResource SortHeaderButtonStyle}"> | |
<Button.Text> | |
<MultiBinding Converter="{StaticResource SortIndicatorConverter}" | |
ConverterParameter="Institution"> | |
<Binding Source="Institution"/> | |
<Binding Path="CurrentSortField"/> | |
<Binding Path="IsAscending"/> | |
</MultiBinding> | |
</Button.Text> | |
</Button> | |
<Button Grid.Column="6" | |
Command="{Binding SortCommand}" | |
CommandParameter="AccountNumber" | |
Style="{StaticResource SortHeaderButtonStyle}"> | |
<Button.Text> | |
<MultiBinding Converter="{StaticResource SortIndicatorConverter}" | |
ConverterParameter="AccountNumber"> | |
<Binding Source="Account"/> | |
<Binding Path="CurrentSortField"/> | |
<Binding Path="IsAscending"/> | |
</MultiBinding> | |
</Button.Text> | |
</Button> | |
<Button Grid.Column="7" | |
Command="{Binding SortCommand}" | |
CommandParameter="Version" | |
Style="{StaticResource SortHeaderButtonStyle}"> | |
<Button.Text> | |
<MultiBinding Converter="{StaticResource SortIndicatorConverter}" | |
ConverterParameter="Version"> | |
<Binding Source="Version"/> | |
<Binding Path="CurrentSortField"/> | |
<Binding Path="IsAscending"/> | |
</MultiBinding> | |
</Button.Text> | |
</Button> | |
</Grid> | |
</CollectionView.Header> | |
<CollectionView.ItemTemplate> | |
<DataTemplate> | |
<Grid ColumnDefinitions="1*, 1*, 2*, 1*, 1*, 1*, 1*, 1*" Padding="10,5"> | |
<VisualStateManager.VisualStateGroups> | |
<VisualStateGroupList> | |
<VisualStateGroup Name="CommonStates"> | |
<VisualState Name="Normal"> | |
<VisualState.Setters> | |
<Setter Property="BackgroundColor" Value="Transparent"/> | |
</VisualState.Setters> | |
</VisualState> | |
<VisualState Name="Selected"> | |
<VisualState.Setters> | |
<Setter Property="BackgroundColor" Value="#2A2A2A"/> | |
</VisualState.Setters> | |
</VisualState> | |
<VisualState Name="PointerOver"> | |
<VisualState.Setters> | |
<Setter Property="BackgroundColor" Value="#232323"/> | |
</VisualState.Setters> | |
</VisualState> | |
</VisualStateGroup> | |
</VisualStateGroupList> | |
</VisualStateManager.VisualStateGroups> | |
<Grid.GestureRecognizers> | |
<PointerGestureRecognizer PointerEntered="OnPointerEntered" | |
PointerExited="OnPointerExited"/> | |
</Grid.GestureRecognizers> | |
<Label Text="{Binding DocumentDate}" | |
Grid.Column="0" | |
Style="{StaticResource DocumentGridLabelStyle}"/> | |
<Label Text="{Binding Classification}" | |
Grid.Column="1" | |
Style="{StaticResource DocumentGridLabelStyle}"/> | |
<Label Text="{Binding Title}" | |
Grid.Column="2" | |
Style="{StaticResource DocumentGridLabelStyle}"/> | |
<Label Text="{Binding BeginningBates}" | |
Grid.Column="3" | |
Style="{StaticResource DocumentGridLabelStyle}"/> | |
<Label Text="{Binding EndingBates}" | |
Grid.Column="4" | |
Style="{StaticResource DocumentGridLabelStyle}"/> | |
<Label Text="{Binding SubClassification[financial institution]}" | |
Grid.Column="5" | |
Style="{StaticResource DocumentGridLabelStyle}"/> | |
<Label Text="{Binding SubClassification[account number]}" | |
Grid.Column="6" | |
Style="{StaticResource DocumentGridLabelStyle}"/> | |
<Label Text="{Binding Version}" | |
Grid.Column="7" | |
Style="{StaticResource DocumentGridLabelStyle}"/> | |
</Grid> | |
</DataTemplate> | |
</CollectionView.ItemTemplate> | |
</CollectionView> | |
<!-- Document Details Panel --> | |
<Border Grid.Row="1" | |
IsVisible="{Binding ShowDocumentDetails}" | |
Stroke="Gray" | |
Background="#1a1a1a" | |
StrokeThickness="1" | |
Margin="0,5,0,0" | |
Padding="5"> | |
<Grid RowDefinitions="Auto,*" ColumnDefinitions="*,Auto"> | |
<Label Text="Document Details" | |
Grid.Row="0" | |
Grid.Column="0" | |
FontSize="12" | |
FontAttributes="Bold" | |
TextColor="White" | |
Margin="0,0,0,5"/> | |
<Button Text="Copy All" | |
Grid.Row="0" | |
Grid.Column="1" | |
Style="{StaticResource ActionButtonStyle}" | |
Command="{Binding CopyDocumentDetailsCommand}"/> | |
<ScrollView Grid.Row="1" Grid.ColumnSpan="2"> | |
<Grid RowDefinitions="1*,1*,1*,1*,1*,1*,1*,1*,1*" | |
ColumnDefinitions="Auto,*, *, *, *, *" | |
RowSpacing="3" | |
ColumnSpacing="5"> | |
<!-- ROW 0--> | |
<!-- Document ID --> | |
<Grid Grid.Row="0" Grid.Column="0" ColumnDefinitions="Auto,Auto" ColumnSpacing="2"> | |
<Button Grid.Column="0" | |
Style="{StaticResource CopyButtonStyle}" | |
Command="{Binding CopyFieldCommand}" | |
CommandParameter="{Binding SelectedDocument.Id}"/> | |
<Label Text="Document ID:" VerticalOptions="Center" Style="{StaticResource DetailLabelStyle}" Grid.Column="1"/> | |
</Grid> | |
<Label Text="{Binding SelectedDocument.Id}" | |
Grid.Row="0" Grid.Column="1" | |
Style="{StaticResource DetailValueLabelStyle}"/> | |
<!-- Client Reference --> | |
<Label Text="Client Reference:" Grid.Row="0" Grid.Column="2" Style="{StaticResource DetailLabelStyle}"/> | |
<Entry Text="{Binding SelectedDocument.ClientReference, Mode=TwoWay}" | |
Grid.Row="0" Grid.Column="3" Style="{StaticResource DetailValueStyle}" | |
TextChanged="OnDocumentFieldChanged"/> | |
<!-- Classification --> | |
<Label Text="Classification:" Grid.Row="0" Grid.Column="4" Style="{StaticResource DetailLabelStyle}"/> | |
<Entry Text="{Binding SelectedDocument.Classification, Mode=TwoWay}" | |
Grid.Row="0" Grid.Column="5" Style="{StaticResource DetailValueStyle}" | |
TextChanged="OnDocumentFieldChanged"/> | |
<!-- ROW 1 --> | |
<!-- Sub Classification (Read-only for now) --> | |
<Label Text="Sub Classification:" Grid.Row="1" Grid.Column="0" Style="{StaticResource DetailLabelStyle}"/> | |
<Grid Grid.Row="1" Grid.Column="1" ColumnDefinitions="*,Auto" Grid.ColumnSpan="5"> | |
<Entry Text="{Binding SubClassificationDisplay, Mode=TwoWay}" | |
Style="{StaticResource DetailValueStyle}" | |
TextColor="Gray" | |
IsReadOnly="True"/> | |
<Button Text="Edit" | |
Style="{StaticResource ActionButtonStyle}" | |
Grid.Column="2" | |
Command="{Binding EditSubClassificationCommand}"/> | |
</Grid> | |
<!-- ROW 2 --> | |
<!-- Title --> | |
<Label Text="Title:" | |
Grid.Row="2" Grid.Column="0" | |
Style="{StaticResource DetailLabelStyle}"/> | |
<Entry Text="{Binding SelectedDocument.Title, Mode=TwoWay}" | |
Grid.Row="2" Grid.Column="1" | |
Style="{StaticResource DetailValueStyle}" | |
TextChanged="OnDocumentFieldChanged"/> | |
<!-- Document Date --> | |
<Label Text="Document Date:" Grid.Row="2" Grid.Column="2" Style="{StaticResource DetailLabelStyle}"/> | |
<Entry Text="{Binding SelectedDocument.DocumentDate, Mode=TwoWay}" | |
Grid.Row="2" Grid.Column="3" Style="{StaticResource DetailValueStyle}" | |
TextChanged="OnDocumentFieldChanged"/> | |
<!-- ROW 3 --> | |
<!-- Produced Date --> | |
<Label Text="Produced Date:" Grid.Row="3" Grid.Column="0" Style="{StaticResource DetailLabelStyle}"/> | |
<Entry Text="{Binding SelectedDocument.ProducedDate, Mode=TwoWay}" | |
Grid.Row="3" Grid.Column="1" Style="{StaticResource DetailValueStyle}" | |
TextChanged="OnDocumentFieldChanged"/> | |
<!-- Beginning Bates --> | |
<Label Text="Beginning Bates:" Grid.Row="3" Grid.Column="2" Style="{StaticResource DetailLabelStyle}"/> | |
<Entry Text="{Binding SelectedDocument.BeginningBates, Mode=TwoWay}" | |
Grid.Row="3" Grid.Column="3" Style="{StaticResource DetailValueStyle}" | |
TextChanged="OnDocumentFieldChanged"/> | |
<!-- Ending Bates --> | |
<Label Text="Ending Bates:" Grid.Row="3" Grid.Column="4" Style="{StaticResource DetailLabelStyle}"/> | |
<Entry Text="{Binding SelectedDocument.EndingBates, Mode=TwoWay}" | |
Grid.Row="3" Grid.Column="5" Style="{StaticResource DetailValueStyle}" | |
TextChanged="OnDocumentFieldChanged"/> | |
<!-- ROW 4 --> | |
<!-- Page Max --> | |
<!-- Page Count --> | |
<Label Text="Page Count:" Grid.Row="4" Grid.Column="0" Style="{StaticResource DetailLabelStyle}"/> | |
<Label Text="{Binding SelectedDocument.PageCount}" | |
Grid.Row="4" Grid.Column="1" Style="{StaticResource DetailValueLabelStyle}"/> | |
<!-- Missing Pages --> | |
<Label Text="Missing Pages:" Grid.Row="4" Grid.Column="2" Style="{StaticResource DetailLabelStyle}"/> | |
<Entry Text="{Binding SelectedDocument.MissingPages, Mode=TwoWay}" | |
Grid.Row="4" Grid.Column="3" Style="{StaticResource DetailValueStyle}" | |
TextChanged="OnDocumentFieldChanged"/> | |
<Label Text="Page Max:" Grid.Row="4" Grid.Column="4" Style="{StaticResource DetailLabelStyle}"/> | |
<Label Text="{Binding SelectedDocument.PageMax}" | |
Grid.Row="4" Grid.Column="5" Style="{StaticResource DetailValueLabelStyle}"/> | |
<!-- ROW 5 --> | |
<!-- File Path --> | |
<Grid Grid.Row="5" Grid.Column="0" ColumnDefinitions="Auto,Auto" ColumnSpacing="2"> | |
<Button Grid.Column="0" | |
Style="{StaticResource CopyButtonStyle}" | |
Command="{Binding CopyFieldCommand}" | |
CommandParameter="{Binding SelectedDocument.Path}"/> | |
<Label Text="File Path:" VerticalOptions="Center" Style="{StaticResource DetailLabelStyle}" Grid.Column="1"/> | |
</Grid> | |
<Label Text="{Binding SelectedDocument.Path}" | |
Grid.Row="5" Grid.Column="1" Style="{StaticResource DetailValueLabelStyle}" | |
Grid.ColumnSpan="5"/> | |
<!-- ROW 6 --> | |
<Label Text="File name:" | |
Grid.Row="6" Grid.Column="0" | |
Style="{StaticResource DetailLabelStyle}"/> | |
<Label Text="{Binding SelectedDocument.Filename}" | |
Grid.Row="6" Grid.Column="1" | |
Style="{StaticResource DetailValueLabelStyle}"/> | |
<Label Text="File type:" | |
Grid.Row="6" Grid.Column="2" | |
Style="{StaticResource DetailLabelStyle}"/> | |
<Label Text="{Binding SelectedDocument.FileType}" | |
Grid.Row="6" Grid.Column="3" | |
Style="{StaticResource DetailValueLabelStyle}"/> | |
<!-- File Create Date --> | |
<Label Text="File Create Date:" | |
Grid.Row="6" Grid.Column="4" | |
Style="{StaticResource DetailLabelStyle}"/> | |
<Label Text="{Binding SelectedDocument.FileCreateDate}" | |
Grid.Row="6" Grid.Column="5" | |
Style="{StaticResource DetailValueLabelStyle}"/> | |
<!-- ROW 7 --> | |
<!-- Added By--> | |
<Label Text="Added By:" Grid.Row="7" Grid.Column="0" Style="{StaticResource DetailLabelStyle}"/> | |
<Label Text="{Binding SelectedDocument.AddedBy}" | |
Grid.Row="7" Grid.Column="1" Style="{StaticResource DetailValueLabelStyle}"/> | |
<!-- Added Date--> | |
<Label Text="Added Date:" Grid.Row="7" Grid.Column="2" Style="{StaticResource DetailLabelStyle}"/> | |
<Label Text="{Binding SelectedDocument.AddedDate}" | |
Grid.Row="7" Grid.Column="3" Style="{StaticResource DetailValueLabelStyle}"/> | |
<!-- ROW 8 --> | |
<!-- Updated By--> | |
<Label Text="Updated By:" Grid.Row="8" Grid.Column="0" Style="{StaticResource DetailLabelStyle}"/> | |
<Label Text="{Binding SelectedDocument.UpdatedBy}" | |
Grid.Row="8" Grid.Column="1" Style="{StaticResource DetailValueLabelStyle}"/> | |
<!-- Updated Date--> | |
<Label Text="Updated Date:" Grid.Row="8" Grid.Column="2" Style="{StaticResource DetailLabelStyle}"/> | |
<Label Text="{Binding SelectedDocument.UpdatedDate}" | |
Grid.Row="8" Grid.Column="3" Style="{StaticResource DetailValueLabelStyle}"/> | |
<!-- Version --> | |
<Label Text="Version:" Grid.Row="8" Grid.Column="4" Style="{StaticResource DetailLabelStyle}"/> | |
<Label Text="{Binding SelectedDocument.Version}" | |
Grid.Row="8" Grid.Column="5" Style="{StaticResource DetailValueLabelStyle}"/> | |
</Grid> | |
</ScrollView> | |
</Grid> | |
</Border> | |
</Grid> | |
</Grid> | |
</Grid> | |
</Grid> | |
</ContentPage> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Diagnostics; | |
using CommunityToolkit.Maui.Views; | |
namespace TrakerManager.Views | |
{ | |
public partial class MainPage : ContentPage | |
{ | |
private LoginPopup? _loginPopup; | |
private readonly IServiceProvider _serviceProvider; | |
private Document? _selectedDocument; | |
public MainPage(IServiceProvider serviceProvider, MainViewModel viewModel) | |
{ | |
InitializeComponent(); | |
BindingContext = viewModel; | |
_serviceProvider = serviceProvider; | |
} | |
protected override async void OnAppearing() | |
{ | |
base.OnAppearing(); | |
_loginPopup = _serviceProvider.GetRequiredService<LoginPopup>(); | |
var result = await this.ShowPopupAsync(_loginPopup); | |
if (result is bool success && success) | |
{ | |
if (BindingContext is MainViewModel viewModel) | |
{ | |
await viewModel.LoadClientsCommand.ExecuteAsync(null); | |
} | |
} | |
} | |
public async Task CloseLoginPopup(bool success) | |
{ | |
if (_loginPopup != null) | |
{ | |
await _loginPopup.CloseAsync(success); | |
} | |
} | |
private void OnPointerEntered(object sender, PointerEventArgs e) | |
{ | |
if (sender is Grid grid && !IsSelectedGrid(grid)) | |
{ | |
VisualStateManager.GoToState(grid, "PointerOver"); | |
} | |
} | |
private void OnPointerExited(object sender, PointerEventArgs e) | |
{ | |
if (sender is Grid grid) | |
{ | |
// Only go to Normal state if this isn't the selected grid | |
if (!IsSelectedGrid(grid)) | |
{ | |
VisualStateManager.GoToState(grid, "Normal"); | |
} | |
} | |
} | |
private bool IsSelectedGrid(Grid grid) | |
{ | |
if (grid.BindingContext is Document doc && | |
BindingContext is MainViewModel viewModel) | |
{ | |
return doc == viewModel.SelectedDocument; | |
} | |
return false; | |
} | |
private void OnDocumentFieldChanged(object sender, TextChangedEventArgs e) | |
{ | |
Debug.WriteLine("MainPage.xaml::OnDocumentFieldChanged: Entered method"); | |
// Explicitly call SaveDocument command in MainViewModel | |
if (BindingContext is not MainViewModel viewModel) | |
return; | |
Debug.WriteLine($"MainPage.xaml::OnDocumentFieldChanged: IsPopulatingForm = {viewModel.IsPopulatingForm}"); | |
if (viewModel.IsPopulatingForm) | |
{ | |
Debug.WriteLine("MainPage.xaml::OnDocumentFieldChanged: Skipping update because form is populating"); | |
return; | |
} | |
Debug.WriteLine("MainPage.xaml::OnDocumentFieldChanged: Updating selected document"); | |
viewModel.DebounceSaveDocument(); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.ObjectModel; | |
using System.Diagnostics; | |
using TrakerManager.Models; | |
using TrakerManager.Services; | |
using CommunityToolkit.Mvvm.ComponentModel; | |
using CommunityToolkit.Mvvm.Input; | |
using CommunityToolkit.Maui.Alerts; // Add this using directive | |
using System.ComponentModel.DataAnnotations; | |
using System.Text; | |
using System.Timers; | |
using TrakerManager.Utilities; | |
namespace TrakerManager.ViewModels | |
{ | |
#pragma warning disable IDE0028 // Initialization can be simplified. Suppress because it lures us away from type safety. | |
public partial class MainViewModel : ObservableObject | |
{ | |
private readonly TrakerService _trakerService; | |
private readonly bool _confirmCopy = false; // Set to true to confirm copy actions with a dialog | |
public MainViewModel(TrakerService trakerService) | |
{ | |
_trakerService = trakerService; | |
TreeItems = new ObservableCollection<TreeViewItem>(); | |
SelectedClientTrackers = new ObservableCollection<TreeViewItem>(); | |
SelectedTrackerClassifications = new ObservableCollection<ClassificationItem>(); | |
DocumentsForSelectedClassification = new ObservableCollection<Document>(); | |
// Initialize commands | |
LoadClientsCommand = new AsyncRelayCommand(LoadClients); | |
LoadTrackersCommand = new AsyncRelayCommand<string>(LoadTrackersForClient); | |
LoadDocumentsCommand = new AsyncRelayCommand<string>(LoadDocumentsForTracker); | |
} | |
[ObservableProperty] | |
private ObservableCollection<TreeViewItem> _treeItems; | |
[ObservableProperty] | |
private ObservableCollection<TreeViewItem> _selectedClientTrackers; | |
[ObservableProperty] | |
private ObservableCollection<ClassificationItem> _selectedTrackerClassifications; | |
[ObservableProperty] | |
private ObservableCollection<Document> _documentsForSelectedClassification; | |
[ObservableProperty] | |
private TreeViewItem? _selectedTreeItem; | |
[ObservableProperty] | |
private bool _showTrackers; | |
[ObservableProperty] | |
private bool _showClassifications; | |
[ObservableProperty] | |
private bool _showSubClassifications; | |
[ObservableProperty] | |
private bool _showDocuments; | |
[ObservableProperty] | |
private int _currentClassificationLevel = 0; | |
[ObservableProperty] | |
private ClassificationDefinition? _currentClassificationDefinition; | |
[ObservableProperty] | |
private ObservableCollection<SubClassificationFolder> _subClassificationFolders = new(); | |
[ObservableProperty] | |
private ObservableCollection<BreadcrumbItem> _breadcrumbs = new(); | |
[ObservableProperty] | |
private Document? _selectedDocument; | |
[ObservableProperty] | |
private bool _showDocumentDetails; | |
[ObservableProperty] | |
private string _subClassificationDisplay = string.Empty; | |
[ObservableProperty] | |
private string _currentSortField = string.Empty; | |
[ObservableProperty] | |
private bool _isAscending = true; | |
[ObservableProperty] | |
private bool _isPopulatingForm; // Set to true when loading data into form fields | |
// Add the collection as an ObservableCollection | |
//[ObservableProperty] | |
//private ObservableCollection<Document> _documentsForSelectedClassification = new(); | |
public IAsyncRelayCommand LoadClientsCommand { get; } | |
public IAsyncRelayCommand<string> LoadTrackersCommand { get; } | |
public IAsyncRelayCommand<string> LoadDocumentsCommand { get; } | |
private System.Timers.Timer? _debounceTimer; | |
private async Task LoadClients() | |
{ | |
try | |
{ | |
Debug.WriteLine("Loading clients..."); | |
var clients = await _trakerService.GetClientsAsync(); | |
Debug.WriteLine($"Retrieved {clients.Count} clients from service."); | |
TreeItems.Clear(); | |
Debug.WriteLine("Cleared TreeItems collection."); | |
foreach (var client in clients) | |
{ | |
Debug.WriteLine($"Processing client {client.Name}."); | |
var clientItem = new TreeViewItem | |
{ | |
Id = client.Id, | |
Name = client.Name, | |
ItemType = TreeItemType.Client | |
}; | |
TreeItems.Add(clientItem); | |
Debug.WriteLine($"Added client to tree {client.Name}."); | |
} | |
Debug.WriteLine($"TreeItems count {TreeItems.Count}."); | |
} | |
catch (Exception ex) | |
{ | |
Debug.WriteLine($"Error in LoadClients: {ex.Message}"); | |
if (Application.Current?.Windows?.Count > 0) | |
{ | |
await Application.Current.Windows[0].Page!.DisplayAlert( | |
"Error", | |
"Unable to load clients. Please try again.", | |
"OK"); | |
} | |
} | |
} | |
[RelayCommand] | |
private async Task LoadTrackersForClient(string? clientId) | |
{ | |
if (string.IsNullOrEmpty(clientId)) | |
{ | |
// Handle null or empty clientId here, if applicable. | |
Debug.WriteLine("Client ID is null or empty."); | |
return; | |
} | |
try | |
{ | |
var trackers = await _trakerService.GetClientTrackersAsync(clientId); | |
var clientItem = TreeItems.First(x => x.Id == clientId); | |
clientItem.SubItems.Clear(); | |
Breadcrumbs.Clear(); | |
foreach (var tracker in trackers) | |
{ | |
Debug.WriteLine($"Creating TreeViewItem for tracker: {tracker.Name}"); | |
var trackerItem = new TreeViewItem | |
{ | |
Id = tracker.Id, | |
Name = tracker.Name, | |
ItemType = TreeItemType.Tracker | |
}; | |
clientItem.SubItems.Add(trackerItem); | |
} | |
} | |
catch (Exception ex) | |
{ | |
if (Application.Current?.Windows?.Count > 0) | |
{ | |
Debug.WriteLine(ex.Message); | |
await Application.Current.Windows[0].Page!.DisplayAlert( | |
"Error", | |
"Unable to load trackers. Please try again.", | |
"OK"); | |
} | |
} | |
} | |
[RelayCommand] | |
private async Task LoadDocumentsForTracker(string? trackerId) | |
{ | |
if (string.IsNullOrEmpty(trackerId)) | |
{ | |
// Handle null or empty clientId here, if applicable. | |
Debug.WriteLine("Tracker ID is null or empty."); | |
return; | |
} | |
try | |
{ | |
Debug.WriteLine($"Loading documents for tracker: {trackerId}"); | |
var documents = await _trakerService.GetTrackerDocumentsAsync(trackerId); | |
// Find the tracker item in the tree | |
TreeViewItem? trackerItem = null; | |
foreach (var client in TreeItems) | |
{ | |
trackerItem = client.SubItems.FirstOrDefault(t => t.Id == trackerId); | |
if (trackerItem != null) | |
break; | |
} | |
if (trackerItem == null) | |
{ | |
Debug.WriteLine("Tracker item not found in tree."); | |
return; | |
} | |
Debug.WriteLine($"Found tracker in tree: {trackerItem.Name}"); | |
trackerItem.SubItems.Clear(); | |
// Group by classification first | |
var classificationGroups = documents | |
.Where(d => !string.IsNullOrEmpty(d.Classification)) | |
.GroupBy(d => d.Classification); | |
foreach (var classGroup in classificationGroups) | |
{ | |
Debug.WriteLine($"Processing classification group: {classGroup.Key}"); | |
var classItem = new TreeViewItem | |
{ | |
Id = $"class_{classGroup.Key}", | |
Name = classGroup.Key ?? string.Empty, // Fix for CS8601 | |
ItemType = TreeItemType.Classification | |
}; | |
trackerItem.SubItems.Add(classItem); | |
} | |
} | |
catch (Exception ex) | |
{ | |
if (Application.Current?.Windows?.Count > 0) | |
{ | |
Debug.WriteLine(ex.Message); | |
await Application.Current.Windows[0].Page!.DisplayAlert( | |
"Error", | |
"Unable to load document classifications. Please try again.", | |
"OK"); | |
} | |
} | |
} | |
async partial void OnSelectedTreeItemChanged(TreeViewItem? value) | |
{ | |
Debug.WriteLine($"\nSelection changed to: {value?.Name}, Type: {value?.ItemType}"); | |
if (value != null) | |
{ | |
try | |
{ | |
// Reset IsSelected on each clientItem | |
foreach (var treeItem in TreeItems) | |
{ | |
treeItem.IsSelected = false; | |
} | |
value.IsSelected = true; | |
foreach (var treeItem in TreeItems) | |
{ | |
Debug.WriteLine($"Tree Items: {treeItem.Name} - Selected? {treeItem.IsSelected}"); | |
} | |
// When client selected, show their trackers in right panel | |
var trackers = await _trakerService.GetClientTrackersAsync(value.Id); | |
Debug.WriteLine($"Retrieved {trackers.Count} trackers for client"); | |
SelectedClientTrackers.Clear(); | |
foreach (var tracker in trackers) | |
{ | |
SelectedClientTrackers.Add(new TreeViewItem | |
{ | |
Id = tracker.Id, | |
Name = tracker.Name, | |
ItemType = TreeItemType.Tracker | |
}); | |
} | |
ShowTrackers = true; | |
ShowClassifications = false; | |
ShowDocuments = false; | |
Debug.WriteLine($"Added {SelectedClientTrackers.Count} trackers to right panel"); | |
} | |
catch (Exception ex) | |
{ | |
Debug.WriteLine($"Error loading trackers: {ex.Message}"); | |
if (Application.Current?.Windows?.Count > 0) | |
{ | |
await Application.Current.Windows[0].Page!.DisplayAlert( | |
"Error", | |
"Unable to load trackers. Please try again.", | |
"OK"); | |
} | |
} | |
} | |
} | |
[RelayCommand] | |
private async Task TrackerSelected(TreeViewItem tracker) | |
{ | |
try | |
{ | |
Debug.WriteLine($"Tracker selected: {tracker.Name}"); | |
var documents = await _trakerService.GetTrackerDocumentsAsync(tracker.Id); | |
Debug.WriteLine($"Retrieved {documents.Count} documents"); | |
// Reset navigation state | |
CurrentClassificationLevel = 0; | |
CurrentClassificationDefinition = null; | |
// Add tracker to breadcrumbs | |
Breadcrumbs.Clear(); | |
Breadcrumbs.Add(new BreadcrumbItem | |
{ | |
Text = tracker.Name!, | |
Level = "0", | |
IsLast = true | |
}); | |
// Group documents by classification | |
var classifications = documents | |
.Where(d => !string.IsNullOrEmpty(d.Classification)) | |
.GroupBy(d => d.Classification) | |
.Select(g => new ClassificationItem | |
{ | |
Name = g.Key!, | |
DocumentCount = g.Count(), | |
Documents = [.. g] // same as g.ToList() | |
}) | |
.ToList(); | |
SelectedTrackerClassifications.Clear(); | |
foreach (var classification in classifications) | |
{ | |
SelectedTrackerClassifications.Add(classification); | |
Debug.WriteLine($"Added classification {classification.Name} with {classification.DocumentCount} documents"); | |
} | |
ShowTrackers = false; | |
ShowClassifications = true; | |
ShowDocuments = false; | |
ShowSubClassifications = false; | |
CurrentClassificationLevel++; | |
} | |
catch (Exception ex) | |
{ | |
Debug.WriteLine($"Error in TrackerSelected: {ex.Message}"); | |
if (Application.Current?.Windows?.Count > 0) | |
{ | |
await Application.Current.Windows[0].Page!.DisplayAlert( | |
"Error", | |
"Unable to load classifications. Please try again.", | |
"OK"); | |
} | |
} | |
} | |
[RelayCommand] | |
private async Task ClassificationSelected(ClassificationItem? classification) | |
{ | |
if (classification == null) | |
{ | |
Debug.WriteLine("Selected classification is null."); | |
return; | |
} | |
try | |
{ | |
Debug.WriteLine($"Classification selected: {classification.Name}"); | |
// Add the classification name to breadcrumbs | |
UpdateBreadcrumbs(classification.Name); | |
if (ClassificationConfig.Definitions.TryGetValue(classification.Name, out var definition)) | |
{ | |
CurrentClassificationDefinition = definition; | |
if (definition.HasSubClassifications) | |
{ | |
Debug.WriteLine($"Processing sub-classifications for level {CurrentClassificationLevel}"); | |
var fieldName = definition.GetFieldNameForLevel(CurrentClassificationLevel); | |
var displayName = definition.GetDisplayNameForLevel(CurrentClassificationLevel); | |
var groups = classification.Documents | |
.Where(d => d.SubClassification != null && | |
d.SubClassification.ContainsKey(fieldName) && | |
d.SubClassification[fieldName] != null) | |
.GroupBy(d => d.SubClassification![fieldName]?.ToString()) | |
.Where(g => g.Key != null); | |
SubClassificationFolders.Clear(); | |
foreach (var group in groups) | |
{ | |
SubClassificationFolders.Add(new SubClassificationFolder | |
{ | |
DisplayName = $"{displayName}: {group.Key}", | |
Value = group.Key!, | |
ParentClassification = classification.Name, | |
Level = CurrentClassificationLevel, | |
Documents = [.. group] | |
}); | |
} | |
ShowTrackers = false; | |
ShowClassifications = false; | |
ShowDocuments = false; | |
ShowSubClassifications = true; | |
CurrentClassificationLevel++; | |
} | |
else | |
{ | |
DocumentsForSelectedClassification = new ObservableCollection<Document>(classification.Documents); | |
ShowTrackers = false; | |
ShowClassifications = false; | |
ShowSubClassifications = false; | |
ShowDocuments = true; | |
CurrentClassificationLevel = 0; | |
CurrentClassificationDefinition = null; | |
} | |
} | |
else | |
{ | |
DocumentsForSelectedClassification = new ObservableCollection<Document>(classification.Documents); | |
ShowTrackers = false; | |
ShowClassifications = false; | |
ShowSubClassifications = false; | |
ShowDocuments = true; | |
} | |
} | |
catch (Exception ex) | |
{ | |
Debug.WriteLine($"Error in ClassificationSelected: {ex.Message}"); | |
if (Application.Current?.Windows?.Count > 0) | |
{ | |
await Application.Current.Windows[0].Page!.DisplayAlert( | |
"Error", | |
"Unable to process classification. Please try again.", | |
"OK"); | |
} | |
} | |
} | |
[RelayCommand] | |
private async Task SubClassificationSelected(SubClassificationFolder? folder) | |
{ | |
if (folder == null) | |
{ | |
Debug.WriteLine("MainViewModel::SubClassificationSelected: Selected sub-classification folder is null."); | |
return; | |
} | |
Debug.WriteLine($"MainViewModel::SubClassificationSelected: {folder?.DisplayName}"); | |
try | |
{ | |
Debug.WriteLine($"MainViewModel::SubClassificationSelected: {folder?.DisplayName}"); | |
UpdateBreadcrumbs(folder!.DisplayName); | |
if (CurrentClassificationDefinition != null && | |
CurrentClassificationLevel < CurrentClassificationDefinition.MaxDepth) | |
{ | |
// Get the next level's field name | |
var nextFieldName = CurrentClassificationDefinition.GetFieldNameForLevel(CurrentClassificationLevel); | |
var nextDisplayName = CurrentClassificationDefinition.GetDisplayNameForLevel(CurrentClassificationLevel); | |
// Filter documents by the current selection and group by next level | |
var groups = folder.Documents | |
.Where(d => d.SubClassification != null && | |
d.SubClassification.ContainsKey(nextFieldName) && | |
d.SubClassification[nextFieldName] != null) | |
.GroupBy(d => d.SubClassification![nextFieldName]?.ToString()) | |
.Where(g => g.Key != null); | |
SubClassificationFolders.Clear(); | |
foreach (var group in groups) | |
{ | |
SubClassificationFolders.Add(new SubClassificationFolder | |
{ | |
DisplayName = $"{nextDisplayName}: {group.Key}", | |
Value = group.Key!, | |
ParentClassification = folder.ParentClassification, | |
Level = CurrentClassificationLevel, | |
Documents = [.. group] | |
}); | |
Debug.WriteLine($"MainViewModel::SubClassificationSelected: Added next level folder: {nextDisplayName}: {group.Key} ({group.Count()} documents)"); | |
} | |
CurrentClassificationLevel++; | |
} | |
else | |
{ | |
// At leaf level, show documents | |
DocumentsForSelectedClassification = new ObservableCollection<Document>(folder.Documents); | |
ShowSubClassifications = false; | |
ShowDocuments = true; | |
CurrentClassificationLevel++; | |
} | |
} | |
catch (Exception ex) | |
{ | |
Debug.WriteLine($"MainViewModel::SubClassificationSelected: Error in SubClassificationSelected: {ex.Message}"); | |
await PopupMessage.Show("Unable to process sub-classification. Please try again.", "Error"); | |
} | |
} | |
[RelayCommand] | |
private async Task ChangeClassification(TreeViewItem classificationFolder) | |
{ | |
try | |
{ | |
// Prompt user for new classification value | |
var mainPage = Application.Current?.Windows[0].Page; | |
if (mainPage == null) | |
{ | |
Debug.WriteLine("MainViewModel::ChangeClassification: Main page is null."); | |
return; | |
} | |
string? newClassification = await mainPage.DisplayPromptAsync( | |
"Change Classification", | |
$"Enter the new classification for {classificationFolder.Name}:", | |
placeholder: "New Classification"); | |
if (string.IsNullOrWhiteSpace(newClassification)) | |
{ | |
await PopupMessage.Show("Classification cannot be empty."); | |
return; | |
} | |
// Collect document IDs from the folder | |
var documentIds = classificationFolder.Documents.Select(d => d.Id).ToArray(); | |
if (documentIds.Length == 0) | |
{ | |
await PopupMessage.Show("No documents found in this folder."); | |
return; | |
} | |
// Update classification | |
var failedUpdates = await _trakerService.UpdateDocumentClassificationAsync(documentIds, newClassification); | |
// Provide feedback | |
if (failedUpdates.Count != 0) | |
{ | |
string failedMessage = string.Join("\n", failedUpdates.Select(f => $"ID: {f.Id}, Reason: {f.Reason}")); | |
await PopupMessage.Show($"Some documents failed to update:\n{failedMessage}", "Partial Success"); | |
} | |
else | |
{ | |
await PopupMessage.Show("All documents updated successfully.", "Success"); | |
} | |
} | |
catch (Exception ex) | |
{ | |
Debug.WriteLine($"Error in ChangeClassification: {ex.Message}"); | |
await PopupMessage.Show("An error occurred. Please try again."); | |
} | |
} | |
[RelayCommand] | |
private void DocumentSelected(Document document) | |
{ | |
SelectedDocument = document; | |
ShowDocumentDetails = true; | |
// Format sub-classification for display | |
if (document.SubClassification != null) | |
{ | |
SubClassificationDisplay = string.Join(", ", | |
document.SubClassification.Select(kvp => $"{kvp.Key}: {kvp.Value}")); | |
} | |
else | |
{ | |
SubClassificationDisplay = "No sub-classification data"; | |
} | |
} | |
[RelayCommand] | |
private static async Task EditSubClassification() | |
{ | |
// TODO: Implement sub-classification editing in future phase | |
await PopupMessage.Show("Sub-classification editing will be implemented in a future update.", "Coming Soon!!"); | |
} | |
// Optional: Add property change handlers to save changes | |
partial void OnSelectedDocumentChanged(Document? value) | |
{ | |
if (value == null) return; | |
Debug.WriteLine("MainViewModel::OnSelectedDocumentChanged: Setting IsPopulatingForm to True"); | |
IsPopulatingForm = true; | |
ShowDocumentDetails = true; | |
// Force the current method to complete and allow the binding updates to occur | |
Application.Current?.Dispatcher.Dispatch(() => { | |
// Now set IsPopulatingForm back to false | |
Debug.WriteLine("MainViewModel::OnSelectedDocumentChanged: Setting IsPopulatingForm to False"); | |
IsPopulatingForm = false; | |
}); | |
} | |
private void UpdateDocumentAcrossCollections(Document sourceDocument) | |
{ | |
Debug.WriteLine($"MainViewModel::UpdateDocumentAcrossCollections: Updating document {sourceDocument.Id} across collections"); | |
// Update in DocumentsForSelectedClassification (grid view) | |
var gridDoc = DocumentsForSelectedClassification.FirstOrDefault(d => d.Id == sourceDocument.Id); | |
if (gridDoc != null) | |
{ | |
CopyDocumentProperties(sourceDocument, gridDoc); | |
} | |
// Update in ClassificationItems | |
foreach (var classItem in SelectedTrackerClassifications) | |
{ | |
var doc = classItem.Documents.FirstOrDefault(d => d.Id == sourceDocument.Id); | |
if (doc != null) | |
{ | |
CopyDocumentProperties(sourceDocument, doc); | |
break; | |
} | |
} | |
// Update in SubClassificationFolders | |
foreach (var folder in SubClassificationFolders) | |
{ | |
var doc = folder.Documents.FirstOrDefault(d => d.Id == sourceDocument.Id); | |
if (doc != null) | |
{ | |
CopyDocumentProperties(sourceDocument, doc); | |
break; | |
} | |
} | |
} | |
// Helper method to copy properties from one document to another | |
private void CopyDocumentProperties(Document source, Document target) | |
{ | |
target.Version = source.Version; | |
target.Title = source.Title; | |
target.DocumentDate = source.DocumentDate; | |
target.BeginningBates = source.BeginningBates; | |
target.EndingBates = source.EndingBates; | |
target.Classification = source.Classification; | |
target.SubClassification = source.SubClassification; | |
target.ClientReference = source.ClientReference; | |
target.MissingPages = source.MissingPages; | |
target.ProducedDate = source.ProducedDate; | |
target.UpdatedBy = source.UpdatedBy; | |
target.UpdatedDate = source.UpdatedDate; | |
} | |
[RelayCommand] | |
private void Sort(string fieldName) | |
{ | |
if (CurrentSortField == fieldName) | |
{ | |
// Toggle direction if clicking same field | |
IsAscending = !IsAscending; | |
} | |
else | |
{ | |
// New field, default to ascending | |
CurrentSortField = fieldName; | |
IsAscending = true; | |
} | |
var sorted = IsAscending | |
? DocumentsForSelectedClassification.OrderBy(GetSortValue(fieldName)) | |
: DocumentsForSelectedClassification.OrderByDescending(GetSortValue(fieldName)); | |
var newList = new ObservableCollection<Document>(sorted); | |
DocumentsForSelectedClassification = newList; | |
} | |
[RelayCommand] | |
private Task BreadcrumbSelected(BreadcrumbItem item) | |
{ | |
Debug.WriteLine($"@@@@@ Breadcrumb selected: {item.Text}, Level: {item.Level}"); | |
// Navigate back to this level | |
CurrentClassificationLevel = int.Parse(item.Level); | |
return NavigateToLevel(item.Level); | |
} | |
private static Func<Document, IComparable> GetSortValue(string fieldName) => fieldName switch | |
{ | |
"DocumentDate" => d => d.DocumentDate ?? "", | |
"Classification" => d => d.Classification ?? "", | |
"Title" => d => d.Title ?? "", | |
"BeginningBates" => d => d.BeginningBates ?? "", | |
"EndingBates" => d => d.EndingBates ?? "", | |
"Institution" => d => d.SubClassification?["financial institution"]?.ToString() ?? "", | |
"Account" => d => d.SubClassification?["account number"]?.ToString() ?? "", | |
_ => d => d.DocumentDate ?? "" | |
}; | |
private async Task NavigateToLevel(string level) | |
{ | |
try | |
{ | |
int targetLevel = int.Parse(level) - 1; | |
Debug.WriteLine($"@@@@@ Breadcrumb target level: {level} {targetLevel}"); | |
if (targetLevel >= 0 && targetLevel < Breadcrumbs.Count) | |
{ | |
// Reset to the selected level | |
CurrentClassificationLevel = targetLevel; | |
// Clear any items after this level in breadcrumbs | |
while (Breadcrumbs.Count > targetLevel) // + 1) | |
{ | |
Breadcrumbs.RemoveAt(Breadcrumbs.Count - 1); | |
} | |
// Re-show the appropriate view based on the level | |
if (targetLevel == 0) | |
{ | |
Breadcrumbs.Clear(); | |
ShowTrackers = true; | |
ShowClassifications = false; | |
ShowSubClassifications = false; | |
ShowDocuments = false; | |
} | |
else if (targetLevel == 1) // Classification | |
{ | |
ShowTrackers = false; | |
ShowClassifications = true; | |
ShowSubClassifications = false; | |
ShowDocuments = false; | |
} | |
else | |
{ | |
// Rebuild the sub-classification view for this level | |
ShowTrackers = false; | |
ShowClassifications = false; | |
ShowSubClassifications = true; | |
ShowDocuments = false; | |
// TODO: Rebuild the folder view for this level | |
// This will depend on maintaining a navigation history | |
} | |
} | |
} | |
catch (Exception ex) | |
{ | |
Debug.WriteLine($"Error navigating to level: {ex.Message}"); | |
if (Application.Current?.Windows?.Count > 0) | |
{ | |
await Application.Current.Windows[0].Page!.DisplayAlert( | |
"Error", | |
"Unable to navigate to selected level. Please try again.", | |
"OK"); | |
} | |
} | |
} | |
[RelayCommand] | |
private async Task CopyField(string value) | |
{ | |
if (string.IsNullOrEmpty(value)) return; | |
try | |
{ | |
await Clipboard.SetTextAsync(value); | |
} | |
catch (Exception ex) | |
{ | |
Debug.WriteLine($"Error copying to clipboard: {ex.Message}"); | |
} | |
try | |
{ | |
// Optional: Show brief feedback | |
if (_confirmCopy) // This is a private field set in the constructor | |
{ | |
var page = Application.Current?.Windows[0].Page; | |
page?.DisplayAlert("Copied", "Value copied to clipboard", "OK"); | |
} | |
else | |
{ | |
// Use a toast notification instead of an alert | |
await Toast.Make("Value copied to clipboard").Show(); | |
} | |
} | |
catch (Exception ex) | |
{ | |
Debug.WriteLine($"Error notifying user: {ex.Message}"); | |
} | |
} | |
[RelayCommand] | |
private async Task SaveDocument() | |
{ | |
if (SelectedDocument == null) return; | |
DumpObjectProperties(SelectedDocument); | |
try | |
{ | |
Debug.WriteLine($"MainViewModel::SaveDocument: Saving document: {SelectedDocument.Id}"); | |
var isSuccess = await _trakerService.UpdateDocumentAsync(SelectedDocument); | |
if (isSuccess) | |
{ | |
Debug.WriteLine("MainViewModel::SaveDocument: Document saved successfully."); | |
// Get the latest version | |
var latestVersion = await _trakerService.GetDocumentVersion(SelectedDocument.Id); | |
if (latestVersion != null) | |
{ | |
// Update version in SelectedDocument | |
SelectedDocument.Version = latestVersion.Version; | |
// Update across all collections | |
UpdateDocumentAcrossCollections(SelectedDocument); | |
} | |
} | |
else | |
{ | |
Debug.WriteLine("MainViewModel::SaveDocument: Failed to save document."); | |
} | |
} | |
catch (Exception ex) | |
{ | |
Debug.WriteLine($"MainViewModel::SaveDocument: Error in SaveDocument: {ex.Message}"); | |
await PopupMessage.Show("An error occurred when trying to save the change. Please try again.", "Error"); | |
} | |
} | |
private static void DumpObjectProperties(object obj, string objectName = "Object") | |
{ | |
if (obj == null) | |
{ | |
Debug.WriteLine($"MainViewModel::DumpObjectProperties: {objectName} is null."); | |
return; | |
} | |
Debug.WriteLine($"MainViewModel::DumpObjectProperties: --- Dumping properties for {objectName} ---"); | |
foreach (var prop in obj.GetType().GetProperties()) | |
{ | |
try | |
{ | |
var value = prop.GetValue(obj, null); | |
Debug.WriteLine($"{prop.Name}: {value}"); | |
} | |
catch (Exception ex) | |
{ | |
Debug.WriteLine($"{prop.Name}: Unable to retrieve value ({ex.Message})"); | |
} | |
} | |
Debug.WriteLine($"MainViewModel::DumpObjectProperties: --- End of {objectName} properties ---"); | |
} | |
private void UpdateBreadcrumbs(string newItem) | |
{ | |
Debug.WriteLine($"MainViewModel::UpdateBreadcrumbs: Updating breadcrumbs with: {newItem}"); | |
if (CurrentClassificationLevel == 0) | |
{ | |
Breadcrumbs.Clear(); | |
Debug.WriteLine("MainViewModel::UpdateBreadcrumbs: Cleared breadcrumbs"); | |
} | |
else | |
{ | |
while (Breadcrumbs.Count > CurrentClassificationLevel) | |
{ | |
Breadcrumbs.RemoveAt(Breadcrumbs.Count - 1); | |
} | |
Debug.WriteLine($"MainViewModel::UpdateBreadcrumbs: Trimmed breadcrumbs to level {CurrentClassificationLevel}"); | |
} | |
Breadcrumbs.Add(new BreadcrumbItem | |
{ | |
Text = newItem, | |
Level = CurrentClassificationLevel.ToString(), | |
IsLast = true | |
}); | |
// Update IsLast for all items except the last | |
for (int i = 0; i < Breadcrumbs.Count - 1; i++) | |
{ | |
Breadcrumbs[i].IsLast = false; // This will now notify the UI | |
} | |
Breadcrumbs[^1].IsLast = true; // Set the last item explicitly | |
// Debug output | |
foreach (var breadcrumb in Breadcrumbs) | |
{ | |
Debug.WriteLine($"@@@@@Breadcrumb: ({breadcrumb.Level}) {breadcrumb.Text}, IsLast: {breadcrumb.IsLast}"); | |
} | |
} | |
public void DebounceSaveDocument() | |
{ | |
_debounceTimer?.Stop(); | |
_debounceTimer = new System.Timers.Timer(500); // Delay for 500 ms | |
_debounceTimer.Elapsed += async (s, e) => | |
{ | |
_debounceTimer?.Stop(); | |
await SaveDocument(); // Save the document after debounce delay | |
}; | |
_debounceTimer.Start(); | |
} | |
partial void OnBreadcrumbsChanged(ObservableCollection<BreadcrumbItem>? oldValue, ObservableCollection<BreadcrumbItem> newValue) | |
{ | |
Debug.WriteLine("MainViewModel::OnBreadcrumbsChanged: Breadcrumbs collection updated."); | |
OnPropertyChanged(nameof(Breadcrumbs)); | |
} | |
partial void OnTreeItemsChanged(ObservableCollection<TreeViewItem>? oldValue, ObservableCollection<TreeViewItem> newValue) | |
{ | |
Debug.WriteLine("MainViewModel::OnTreeItemsChanged: TreeItems changed."); | |
OnPropertyChanged(nameof(TreeItems)); | |
} | |
partial void OnSubClassificationFoldersChanged(ObservableCollection<SubClassificationFolder>? oldValue, ObservableCollection<SubClassificationFolder> newValue) | |
{ | |
Debug.WriteLine($"MainViewModel::OnSubClassificationFoldersChanged: SubClassificationFolders updated. Count: {newValue.Count}"); | |
} | |
} | |
public class ClassificationItem | |
{ | |
public string Name { get; set; } | |
public int DocumentCount { get; set; } | |
public List<Document> Documents { get; set; } | |
public Dictionary<string, object> SubClassificationData { get; set; } | |
public ClassificationItem() | |
{ | |
Name = string.Empty; | |
Documents = new List<Document>(); | |
SubClassificationData = new Dictionary<string, object>(); | |
} | |
} | |
public class SubClassificationFolder | |
{ | |
public string DisplayName { get; set; } = string.Empty; | |
public string Value { get; set; } = string.Empty; | |
public string ParentClassification { get; set; } = string.Empty; | |
public int Level { get; set; } | |
public List<Document> Documents { get; set; } = new(); | |
} | |
public partial class BreadcrumbItem : ObservableObject | |
{ | |
[ObservableProperty] | |
private string text = string.Empty; | |
[ObservableProperty] | |
private string level = string.Empty; | |
[ObservableProperty] | |
private bool isLast; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment