From 2a8a4fc4a47d1975bb79a5a032349fc3d15d4f82 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 31 Aug 2026 00:25:53 +0100 Subject: [PATCH 1/2] List the activities --- src/PostIt/PostIt/Views/ActivitiesPage.axaml | 11 +++- .../Business/ActivityApiController.cs | 63 ++++++++++--------- src/Yavsc.Api/Program.cs | 5 +- 3 files changed, 48 insertions(+), 31 deletions(-) diff --git a/src/PostIt/PostIt/Views/ActivitiesPage.axaml b/src/PostIt/PostIt/Views/ActivitiesPage.axaml index 03248a0e..d3cddb30 100644 --- a/src/PostIt/PostIt/Views/ActivitiesPage.axaml +++ b/src/PostIt/PostIt/Views/ActivitiesPage.axaml @@ -72,7 +72,16 @@ - + diff --git a/src/Yavsc.Api/Controllers/Business/ActivityApiController.cs b/src/Yavsc.Api/Controllers/Business/ActivityApiController.cs index abd5f28c..d2e3dcea 100644 --- a/src/Yavsc.Api/Controllers/Business/ActivityApiController.cs +++ b/src/Yavsc.Api/Controllers/Business/ActivityApiController.cs @@ -44,19 +44,23 @@ namespace Yavsc.Controllers var codes = activities .Select(a => a.Code) - .Concat(activities.SelectMany(a => a.Children.Where(c => !c.Hidden).Select(c => c.Code))) + .Concat(activities.SelectMany(a => (a.Children ?? new List()) + .Where(c => !c.Hidden) + .Select(c => c.Code))) + .Where(c => !string.IsNullOrWhiteSpace(c)) .Distinct() .ToArray(); - var performerCounts = await _context.Performers - .AsNoTracking() - .Where(p => p.Active) - .SelectMany( - p => p.Activity - .Where(a => codes.Contains(a.DoesCode)) - .Select(a => new { a.DoesCode, p.PerformerId })) - .GroupBy(x => x.DoesCode) - .Select(g => new { Code = g.Key, Count = g.Select(x => x.PerformerId).Distinct().Count() }) + var performerCounts = await ( + from ua in _context.UserActivities.AsNoTracking() + join p in _context.Performers.AsNoTracking() on ua.UserId equals p.PerformerId + where p.Active && !string.IsNullOrWhiteSpace(ua.DoesCode) && codes.Contains(ua.DoesCode) + group ua by ua.DoesCode into g + select new + { + Code = g.Key, + Count = g.Select(x => x.UserId).Distinct().Count() + }) .ToDictionaryAsync(x => x.Code, x => x.Count, cancellationToken); return Ok(activities.Select(a => ToBrowseItem(a, performerCounts)).ToList()); @@ -80,29 +84,32 @@ namespace Yavsc.Controllers return NotFound(); } - var performers = await _context.Performers - .AsNoTracking() - .Include(p => p.Performer) - .Include(p => p.Activity) - .ThenInclude(a => a.Does) - .Where(p => p.Active && p.Activity.Any(a => a.DoesCode == id)) - .OrderBy(p => p.Rate) - .Select(p => new ActivityPerformerDto + var performers = await ( + from p in _context.Performers.AsNoTracking() + join ua in _context.UserActivities.AsNoTracking() on p.PerformerId equals ua.UserId + join u in _context.ApplicationUser.AsNoTracking() on p.PerformerId equals u.Id into users + from user in users.DefaultIfEmpty() + where p.Active && ua.DoesCode == id + orderby p.Rate + select new ActivityPerformerDto { PerformerId = p.PerformerId, - UserName = p.Performer.UserName, + UserName = user != null ? (user.UserName ?? string.Empty) : string.Empty, Active = p.Active, AcceptNotifications = p.AcceptNotifications, AcceptPublicContact = p.AcceptPublicContact, - WebSite = p.WebSite, + WebSite = p.WebSite ?? string.Empty, ActivityCode = id, ActivityName = activity.Name, - SettingsClassName = p.Activity - .Where(a => a.DoesCode == id) - .Select(a => a.Does.SettingsClassName) - .FirstOrDefault(), - ExtraActivityCount = p.Activity.Count(a => a.DoesCode != id) + SettingsClassName = _context.Activities + .Where(a => a.Code == id) + .Select(a => a.SettingsClassName) + .FirstOrDefault() ?? string.Empty, + ExtraActivityCount = _context.UserActivities + .Where(x => x.UserId == p.PerformerId && x.DoesCode != id) + .Count() }) + .Distinct() .ToListAsync(cancellationToken); return Ok(performers); @@ -240,7 +247,7 @@ namespace Yavsc.Controllers Photo = activity.Photo, Rate = activity.Rate, PerformerCount = performerCounts.TryGetValue(activity.Code, out var count) ? count : 0, - Forms = activity.Forms + Forms = (activity.Forms ?? Enumerable.Empty()) .Select(f => new CommandFormSummaryDto { Id = f.Id, @@ -248,7 +255,7 @@ namespace Yavsc.Controllers Title = f.Title, }) .ToList(), - Children = activity.Children + Children = (activity.Children ?? Enumerable.Empty()) .Where(c => !c.Hidden) .OrderByDescending(c => c.Rate) .Select(c => new ActivityBrowseItemDto @@ -260,7 +267,7 @@ namespace Yavsc.Controllers Photo = c.Photo, Rate = c.Rate, PerformerCount = performerCounts.TryGetValue(c.Code, out var childCount) ? childCount : 0, - Forms = c.Forms + Forms = (c.Forms ?? Enumerable.Empty()) .Select(f => new CommandFormSummaryDto { Id = f.Id, diff --git a/src/Yavsc.Api/Program.cs b/src/Yavsc.Api/Program.cs index 3ed20e8d..8db29e53 100644 --- a/src/Yavsc.Api/Program.cs +++ b/src/Yavsc.Api/Program.cs @@ -72,9 +72,10 @@ internal class Program services.AddAuthentication("Bearer") .AddYavscJwtBearer(builder.Configuration); + // DbContextBuilder services.AddDbContext(options => - - options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); + options.UseNpgsql(builder.Configuration.GetConnectionString( + Yavsc.Constants.YavscConnectionStringName))); services.AddLocalization(options => { From 7b9274515344bb149da640486ba2fdd28026f1e6 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Mon, 31 Aug 2026 02:14:04 +0100 Subject: [PATCH 2/2] =?UTF-8?q?Exploration=20des=20activit=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 8 + .../ActivitiesPageViewModelTests.cs | 18 +- src/PostIt/PostIt/App.axaml.cs | 5 - .../ViewModels/ActivitiesPageViewModel.cs | 19 +- .../ViewModels/ActivityUserDisplayItem.cs | 39 ++++ src/PostIt/PostIt/Views/ActivitiesPage.axaml | 48 +++- .../Workflow/ActivityPerformerDto.cs | 1 + src/Yavsc.Api.Client/ActivityApiClient.cs | 9 +- .../ActivityApiControllerTests.cs | 161 ++++++++++++++ src/Yavsc.Api.Test/ApiCollection.cs | 6 + src/Yavsc.Api.Test/Directory.Packages.props | 7 + .../Fixtures/ApiWebServerFixture.cs | 210 ++++++++++++++++++ src/Yavsc.Api.Test/HomeControllerTests.cs | 69 ++++++ src/Yavsc.Api.Test/Yavsc.Api.Test.csproj | 33 +++ .../Business/ActivityApiController.cs | 71 ++++-- .../Contracting/SIRENExceptionsController.cs | 34 ++- src/Yavsc.Org/Controllers/HomeController.cs | 28 ++- src/Yavsc.Org/Views/Manage/SetActivity.cshtml | 2 +- .../Views/SIRENExceptions/Create.cshtml | 21 +- yavsc.sln | 15 ++ 20 files changed, 743 insertions(+), 61 deletions(-) create mode 100644 src/PostIt/PostIt/ViewModels/ActivityUserDisplayItem.cs create mode 100644 src/Yavsc.Api.Test/ActivityApiControllerTests.cs create mode 100644 src/Yavsc.Api.Test/ApiCollection.cs create mode 100644 src/Yavsc.Api.Test/Directory.Packages.props create mode 100644 src/Yavsc.Api.Test/Fixtures/ApiWebServerFixture.cs create mode 100644 src/Yavsc.Api.Test/HomeControllerTests.cs create mode 100644 src/Yavsc.Api.Test/Yavsc.Api.Test.csproj diff --git a/CHANGELOG.md b/CHANGELOG.md index ec42b81b..1a2efb4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [1.0.8-rc10] - unstable + +### Added + +### Changed + +### Fixed + ## [1.0.8-rc9] - unstable ### Added diff --git a/src/PostIt/PostIt.Tests/ActivitiesPageViewModelTests.cs b/src/PostIt/PostIt.Tests/ActivitiesPageViewModelTests.cs index 124777e0..da166fa6 100644 --- a/src/PostIt/PostIt.Tests/ActivitiesPageViewModelTests.cs +++ b/src/PostIt/PostIt.Tests/ActivitiesPageViewModelTests.cs @@ -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)) { - var performers = path.EndsWith("brush-pro/performers", StringComparison.Ordinal) + var performers = path.EndsWith("brush-pro/users", StringComparison.Ordinal) ? new List { - 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 { - 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); diff --git a/src/PostIt/PostIt/App.axaml.cs b/src/PostIt/PostIt/App.axaml.cs index f43aa1d9..d3fbf651 100644 --- a/src/PostIt/PostIt/App.axaml.cs +++ b/src/PostIt/PostIt/App.axaml.cs @@ -86,11 +86,6 @@ private void ConfigureRootView(MainView rootView) rootView.NavRoot.PopToRootAsync(); }; - sessionStatus.LoginSucceeded += async () => - { - await PushMainPageAsync(); - }; - rootView.SessionBanner.DataContext = sessionStatus; } diff --git a/src/PostIt/PostIt/ViewModels/ActivitiesPageViewModel.cs b/src/PostIt/PostIt/ViewModels/ActivitiesPageViewModel.cs index 57e387c9..e39a2c18 100644 --- a/src/PostIt/PostIt/ViewModels/ActivitiesPageViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/ActivitiesPageViewModel.cs @@ -29,7 +29,7 @@ public partial class ActivitiesPageViewModel : ViewModelBase public partial ActivityBrowseItemDto? SelectedSpecialization { get; set; } [ObservableProperty] - public partial ObservableCollection Performers { get; set; } = new(); + public partial ObservableCollection Performers { get; set; } = new(); [ObservableProperty] public partial bool IsBusy { get; set; } @@ -122,14 +122,14 @@ public partial class ActivitiesPageViewModel : ViewModelBase { Activities = new ObservableCollection(); Specializations = new ObservableCollection(); - Performers = new ObservableCollection(); + Performers = new ObservableCollection(); StatusMessage = "Accès refusé pour les activités (scope 'api'). Déconnectez puis reconnectez-vous."; } catch (Exception ex) { Activities = new ObservableCollection(); Specializations = new ObservableCollection(); - Performers = new ObservableCollection(); + Performers = new ObservableCollection(); StatusMessage = $"Erreur: {ex.Message}"; } finally @@ -158,7 +158,7 @@ public partial class ActivitiesPageViewModel : ViewModelBase if (activity is null) { - Performers = new ObservableCollection(); + Performers = new ObservableCollection(); return; } @@ -197,18 +197,19 @@ public partial class ActivitiesPageViewModel : ViewModelBase IsBusy = true; try { - var list = await _client.GetPerformersAsync(activity.Code); - Performers = new ObservableCollection(list ?? new()); - StatusMessage = $"{activity.Name} · {Performers.Count} prestataire(s)"; + var list = await _client.GetUsersAsync(activity.Code); + Performers = new ObservableCollection((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(); + Performers = new ObservableCollection(); StatusMessage = "Accès refusé pour les activités (scope 'api'). Déconnectez puis reconnectez-vous."; } catch (Exception ex) { - Performers = new ObservableCollection(); + Performers = new ObservableCollection(); StatusMessage = $"Erreur: {ex.Message}"; } finally diff --git a/src/PostIt/PostIt/ViewModels/ActivityUserDisplayItem.cs b/src/PostIt/PostIt/ViewModels/ActivityUserDisplayItem.cs new file mode 100644 index 00000000..ee06b883 --- /dev/null +++ b/src/PostIt/PostIt/ViewModels/ActivityUserDisplayItem.cs @@ -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}" + }; + } +} diff --git a/src/PostIt/PostIt/Views/ActivitiesPage.axaml b/src/PostIt/PostIt/Views/ActivitiesPage.axaml index d3cddb30..8dd88c6f 100644 --- a/src/PostIt/PostIt/Views/ActivitiesPage.axaml +++ b/src/PostIt/PostIt/Views/ActivitiesPage.axaml @@ -6,6 +6,20 @@ x:DataType="vm:ActivitiesPageViewModel" Header="Activités"> + + + + +