Exploration des activités

This commit is contained in:
Paul Schneider 2026-08-31 02:14:04 +01:00
commit 7b92745153
Signed by: notazof
GPG key ID: 1DD5D838E5343B06
20 changed files with 743 additions and 61 deletions

View file

@ -14,10 +14,10 @@ public class ActivitiesPageViewModelTests
var client = new ActivityApiClient(api, "https://business.example/api/v1/");
await client.GetCatalogAsync("brush", TestContext.Current.CancellationToken);
await client.GetPerformersAsync("brush-pro", TestContext.Current.CancellationToken);
await client.GetUsersAsync("brush-pro", TestContext.Current.CancellationToken);
Assert.Equal("https://business.example/api/v1/activity/catalog?parentCode=brush", api.Paths[0]);
Assert.Equal("https://business.example/api/v1/activity/brush-pro/performers", api.Paths[1]);
Assert.Equal("https://business.example/api/v1/activity/brush-pro/users", api.Paths[1]);
}
[Fact]
@ -34,12 +34,20 @@ public class ActivitiesPageViewModelTests
Assert.Equal("brush", vm.CurrentActivity?.Code);
Assert.Single(vm.Performers);
Assert.Equal("Alice", vm.Performers[0].UserName);
Assert.True(vm.Performers[0].HasPerformerProfile);
Assert.True(vm.Performers[0].IsPerformerActive);
Assert.Equal("Actif", vm.Performers[0].PerformerStatusBadgeLabel);
Assert.Equal("Pas d'autre activité", vm.Performers[0].ExtraActivityLabel);
await vm.ShowSpecializationAsync(vm.Specializations[0]);
Assert.Equal("brush-pro", vm.CurrentActivity?.Code);
Assert.Single(vm.Performers);
Assert.Equal("Bob", vm.Performers[0].UserName);
Assert.True(vm.Performers[0].HasPerformerProfile);
Assert.False(vm.Performers[0].IsPerformerActive);
Assert.Equal("Inactif", vm.Performers[0].PerformerStatusBadgeLabel);
Assert.Equal("Autres spécialisations: 2", vm.Performers[0].ExtraActivityLabel);
Assert.Contains("brush pro", vm.StatusMessage, StringComparison.OrdinalIgnoreCase);
await vm.ShowSpecializationAsync(null);
@ -86,14 +94,14 @@ public class ActivitiesPageViewModelTests
if (typeof(T) == typeof(List<ActivityPerformerDto>))
{
var performers = path.EndsWith("brush-pro/performers", StringComparison.Ordinal)
var performers = path.EndsWith("brush-pro/users", StringComparison.Ordinal)
? new List<ActivityPerformerDto>
{
new() { PerformerId = "pro-2", UserName = "Bob", ActivityCode = "brush-pro", ActivityName = "Brush Pro" }
new() { PerformerId = "pro-2", HasPerformerProfile = true, Active = false, UserName = "Bob", ActivityCode = "brush-pro", ActivityName = "Brush Pro", ExtraActivityCount = 2 }
}
: new List<ActivityPerformerDto>
{
new() { PerformerId = "pro-1", UserName = "Alice", ActivityCode = "brush", ActivityName = "Brush" }
new() { PerformerId = "pro-1", HasPerformerProfile = true, Active = true, UserName = "Alice", ActivityCode = "brush", ActivityName = "Brush", ExtraActivityCount = 0 }
};
return Task.FromResult((T)(object)performers);

View file

@ -86,11 +86,6 @@ private void ConfigureRootView(MainView rootView)
rootView.NavRoot.PopToRootAsync();
};
sessionStatus.LoginSucceeded += async () =>
{
await PushMainPageAsync();
};
rootView.SessionBanner.DataContext = sessionStatus;
}

View file

@ -29,7 +29,7 @@ public partial class ActivitiesPageViewModel : ViewModelBase
public partial ActivityBrowseItemDto? SelectedSpecialization { get; set; }
[ObservableProperty]
public partial ObservableCollection<ActivityPerformerDto> Performers { get; set; } = new();
public partial ObservableCollection<ActivityUserDisplayItem> Performers { get; set; } = new();
[ObservableProperty]
public partial bool IsBusy { get; set; }
@ -122,14 +122,14 @@ public partial class ActivitiesPageViewModel : ViewModelBase
{
Activities = new ObservableCollection<ActivityBrowseItemDto>();
Specializations = new ObservableCollection<ActivityBrowseItemDto>();
Performers = new ObservableCollection<ActivityPerformerDto>();
Performers = new ObservableCollection<ActivityUserDisplayItem>();
StatusMessage = "Accès refusé pour les activités (scope 'api'). Déconnectez puis reconnectez-vous.";
}
catch (Exception ex)
{
Activities = new ObservableCollection<ActivityBrowseItemDto>();
Specializations = new ObservableCollection<ActivityBrowseItemDto>();
Performers = new ObservableCollection<ActivityPerformerDto>();
Performers = new ObservableCollection<ActivityUserDisplayItem>();
StatusMessage = $"Erreur: {ex.Message}";
}
finally
@ -158,7 +158,7 @@ public partial class ActivitiesPageViewModel : ViewModelBase
if (activity is null)
{
Performers = new ObservableCollection<ActivityPerformerDto>();
Performers = new ObservableCollection<ActivityUserDisplayItem>();
return;
}
@ -197,18 +197,19 @@ public partial class ActivitiesPageViewModel : ViewModelBase
IsBusy = true;
try
{
var list = await _client.GetPerformersAsync(activity.Code);
Performers = new ObservableCollection<ActivityPerformerDto>(list ?? new());
StatusMessage = $"{activity.Name} · {Performers.Count} prestataire(s)";
var list = await _client.GetUsersAsync(activity.Code);
Performers = new ObservableCollection<ActivityUserDisplayItem>((list ?? new())
.Select(ActivityUserDisplayItem.FromDto));
StatusMessage = $"{activity.Name} · {Performers.Count} utilisateur(s)";
}
catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden)
{
Performers = new ObservableCollection<ActivityPerformerDto>();
Performers = new ObservableCollection<ActivityUserDisplayItem>();
StatusMessage = "Accès refusé pour les activités (scope 'api'). Déconnectez puis reconnectez-vous.";
}
catch (Exception ex)
{
Performers = new ObservableCollection<ActivityPerformerDto>();
Performers = new ObservableCollection<ActivityUserDisplayItem>();
StatusMessage = $"Erreur: {ex.Message}";
}
finally

View file

@ -0,0 +1,39 @@
using Yavsc.Abstract.Workflow;
namespace PostIt.ViewModels;
public sealed class ActivityUserDisplayItem
{
public string PerformerId { get; init; } = string.Empty;
public bool HasPerformerProfile { get; init; }
public string PerformerBadgeLabel { get; init; } = "Profil pro";
public bool IsPerformerActive { get; init; }
public string PerformerStatusBadgeLabel { get; init; } = "Inactif";
public string PerformerStatusBadgeBackground { get; init; } = "#FDECEA";
public string PerformerStatusBadgeBorder { get; init; } = "#C62828";
public string PerformerStatusBadgeForeground { get; init; } = "#8E0000";
public string UserName { get; init; } = string.Empty;
public string WebSite { get; init; } = string.Empty;
public int ExtraActivityCount { get; init; }
public string ExtraActivityLabel { get; init; } = "Pas d'autre activité";
public static ActivityUserDisplayItem FromDto(ActivityPerformerDto dto)
{
return new ActivityUserDisplayItem
{
PerformerId = dto.PerformerId,
HasPerformerProfile = dto.HasPerformerProfile,
UserName = dto.UserName,
WebSite = dto.WebSite,
IsPerformerActive = dto.Active,
PerformerStatusBadgeLabel = dto.Active ? "Actif" : "Inactif",
PerformerStatusBadgeBackground = dto.Active ? "#E6F7EC" : "#FDECEA",
PerformerStatusBadgeBorder = dto.Active ? "#2E7D32" : "#C62828",
PerformerStatusBadgeForeground = dto.Active ? "#1B5E20" : "#8E0000",
ExtraActivityCount = dto.ExtraActivityCount,
ExtraActivityLabel = dto.ExtraActivityCount == 0
? "Pas d'autre activité"
: $"Autres spécialisations: {dto.ExtraActivityCount}"
};
}
}

View file

@ -6,6 +6,20 @@
x:DataType="vm:ActivitiesPageViewModel"
Header="Activités">
<Grid RowDefinitions="Auto,*,Auto" Margin="12">
<Grid.Styles>
<Style Selector="Border.user-badge">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="6" />
<Setter Property="Padding" Value="5,0" />
<Setter Property="MinHeight" Value="16" />
</Style>
<Style Selector="TextBlock.user-badge-text">
<Setter Property="FontSize" Value="9" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</Grid.Styles>
<StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="8">
<Button Content="Rafraîchir" Command="{Binding RefreshCommand}" />
<TextBlock Text="Catalogue des activités" FontWeight="Bold" VerticalAlignment="Center" />
@ -22,7 +36,7 @@
<StackPanel Spacing="2" Margin="0,0,0,8">
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" FontSize="11" Opacity="0.7" />
<TextBlock Text="{Binding PerformerCount, StringFormat='Prestataires: {0}'}"
<TextBlock Text="{Binding PerformerCount, StringFormat='Utilisateurs: {0}'}"
FontSize="11" Opacity="0.6" />
</StackPanel>
</DataTemplate>
@ -43,7 +57,7 @@
<StackPanel Spacing="2" Margin="0,0,0,8">
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" FontSize="11" Opacity="0.7" />
<TextBlock Text="{Binding PerformerCount, StringFormat='Prestataires: {0}'}"
<TextBlock Text="{Binding PerformerCount, StringFormat='Utilisateurs: {0}'}"
FontSize="11" Opacity="0.6" />
</StackPanel>
</DataTemplate>
@ -52,17 +66,39 @@
</Grid>
<Grid Grid.Column="4" RowDefinitions="Auto,Auto,*">
<TextBlock Grid.Row="0" Text="Prestataires" FontWeight="Bold" Margin="0,0,0,8" />
<TextBlock Grid.Row="0" Text="Utilisateurs" FontWeight="Bold" Margin="0,0,0,8" />
<TextBlock Grid.Row="1"
Text="{Binding CurrentActivityLabel, StringFormat='Activité affichée : {0}'}"
FontSize="11" Opacity="0.7" Margin="0,0,0,8" />
<ListBox Grid.Row="2" ItemsSource="{Binding Performers}">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="wf:ActivityPerformerDto">
<DataTemplate x:DataType="vm:ActivityUserDisplayItem">
<StackPanel Spacing="2" Margin="0,0,0,8">
<TextBlock Text="{Binding UserName}" FontWeight="Bold" />
<StackPanel Orientation="Horizontal" Spacing="4">
<TextBlock Text="{Binding UserName}" FontWeight="Bold" />
<Border IsVisible="{Binding HasPerformerProfile}"
Classes="user-badge"
Background="#D9F5E5"
BorderBrush="#2E7D32"
>
<TextBlock Text="{Binding PerformerBadgeLabel}"
Classes="user-badge-text"
Foreground="#1B5E20"
/>
</Border>
<Border IsVisible="{Binding HasPerformerProfile}"
Classes="user-badge"
Background="{Binding PerformerStatusBadgeBackground}"
BorderBrush="{Binding PerformerStatusBadgeBorder}"
>
<TextBlock Text="{Binding PerformerStatusBadgeLabel}"
Classes="user-badge-text"
Foreground="{Binding PerformerStatusBadgeForeground}"
/>
</Border>
</StackPanel>
<TextBlock Text="{Binding WebSite}" FontSize="11" Opacity="0.7" />
<TextBlock Text="{Binding ExtraActivityCount, StringFormat='Autres spécialisations: {0}'}"
<TextBlock Text="{Binding ExtraActivityLabel}"
FontSize="11" Opacity="0.6" />
</StackPanel>
</DataTemplate>