diff --git a/CHANGELOG.md b/CHANGELOG.md index e5f285ab..1a2efb4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,17 +4,10 @@ ### Added -* [PostIt] Une page d'historique des commandes billing permet maintenant d'ouvrir une commande existante. -* [PostIt] Une vue "Demandes en cours" en lecture seule est disponible pour le performer, filtrée sur les statuts actifs (Inserted, Accepted, InProgress). - ### Changed -* [PostIt] La page détail billing se préremplit depuis une commande existante (Rdv, Brush, MBrush) et passe en mode mise à jour. - ### Fixed -* [PostIt] Le flux historique n'est plus limité à une simple liste: l'action d'ouverture charge la commande cible puis navigue vers la page détail. - ## [1.0.8-rc9] - unstable ### Added diff --git a/ROADMAP.md b/ROADMAP.md index 364b98bd..4c00e629 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -68,7 +68,7 @@ Trois principes non négociables traversent tous les jalons : > > Chaque jalon a un **critère de sortie** vérifiable. -### Jalon 0 — Fondations techniques +### Jalon 0 — Fondations techniques *(en cours)* > Cible : pouvoir parler du domaine sans se battre avec le runtime. @@ -81,7 +81,7 @@ Trois principes non négociables traversent tous les jalons : --- -### Jalon 1 — Prestation signée de bout en bout *(en cours)* +### Jalon 1 — Prestation signée de bout en bout > Cible : un projet client/fournisseur aboutit à un **devis signé par les deux parties**, traçable, avec notifications. diff --git a/src/PostIt/PostIt.Tests/ActivitiesPageViewModelTests.cs b/src/PostIt/PostIt.Tests/ActivitiesPageViewModelTests.cs index c2fab4c3..da166fa6 100644 --- a/src/PostIt/PostIt.Tests/ActivitiesPageViewModelTests.cs +++ b/src/PostIt/PostIt.Tests/ActivitiesPageViewModelTests.cs @@ -12,17 +12,12 @@ public class ActivitiesPageViewModelTests { var api = new StubActivityApi(); var client = new ActivityApiClient(api, "https://business.example/api/v1/"); - var billingClient = new BillingApiClient(api, "https://business.example/api/v1/"); await client.GetCatalogAsync("brush", TestContext.Current.CancellationToken); await client.GetUsersAsync("brush-pro", TestContext.Current.CancellationToken); - await billingClient.CreateAsync("Rdv", new { Foo = "Bar" }, TestContext.Current.CancellationToken); - await billingClient.GetQuerySummariesAsync("Rdv", 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/users", api.Paths[1]); - Assert.Equal("https://business.example/api/v1/billing/Rdv", api.Paths[2]); - Assert.Equal("https://business.example/api/v1/billing/Rdv", api.Paths[3]); } [Fact] @@ -30,8 +25,7 @@ public class ActivitiesPageViewModelTests { var api = new StubActivityApi(); var client = new ActivityApiClient(api, "https://business.example/api/v1/"); - var billingClient = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = new ActivitiesPageViewModel(client, billingClient); + var vm = new ActivitiesPageViewModel(client); await vm.RefreshAsync(); @@ -82,10 +76,6 @@ public class ActivitiesPageViewModelTests Name = "Brush", Description = "Coiffure à domicile", PerformerCount = 1, - Forms = new List - { - new() { Id = 1, ActionName = "Rdv", Title = "Rendez-vous" } - }, Children = new List { new() @@ -95,10 +85,6 @@ public class ActivitiesPageViewModelTests Description = "Spécialisation premium", ParentCode = "brush", PerformerCount = 1, - Forms = new List - { - new() { Id = 2, ActionName = "Rdv", Title = "Rendez-vous premium" } - } } } } diff --git a/src/PostIt/PostIt.Tests/BillingCommandPageViewModelTests.cs b/src/PostIt/PostIt.Tests/BillingCommandPageViewModelTests.cs deleted file mode 100644 index eef3fe21..00000000 --- a/src/PostIt/PostIt.Tests/BillingCommandPageViewModelTests.cs +++ /dev/null @@ -1,219 +0,0 @@ -using System.Net.Http; -using System.Text.Json; -using PostIt.ViewModels; -using Yavsc; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; -using Yavsc.Models.Haircut; - -namespace PostIt.Tests; - -public class BillingCommandPageViewModelTests -{ - [Fact] - public async Task SubmitAsync_posts_rdv_payload_to_selected_billing_route() - { - var api = new RecordingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = new BillingCommandPageViewModel( - new ActivityBrowseItemDto { Code = "dev", Name = "Développement" }, - new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - new CommandFormSummaryDto { Id = 12, ActionName = "Rdv", Title = "Rendez-vous" }, - client) - { - EventDateText = "2026-09-02 14:30", - Reason = "Point de cadrage", - Address = "1 rue du Test", - LatitudeText = "48.8566", - LongitudeText = "2.3522", - Consent = true, - }; - - await vm.SubmitCommand.ExecuteAsync(null); - - Assert.Equal("https://business.example/api/v1/billing/Rdv", api.LastPath); - Assert.NotNull(api.LastBody); - - using var json = JsonDocument.Parse(JsonSerializer.Serialize(api.LastBody)); - Assert.Equal("dev", json.RootElement.GetProperty("ActivityCode").GetString()); - Assert.Equal("perf-1", json.RootElement.GetProperty("PerformerId").GetString()); - Assert.Equal("Point de cadrage", json.RootElement.GetProperty("Reason").GetString()); - Assert.Equal((int)QueryStatus.Inserted, json.RootElement.GetProperty("Status").GetInt32()); - } - - [Fact] - public async Task SubmitAsync_refuses_unsupported_billing_code() - { - var api = new RecordingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = new BillingCommandPageViewModel( - new ActivityBrowseItemDto { Code = "book", Name = "Book" }, - new ActivityUserDisplayItem { PerformerId = "perf-2", UserName = "Bob" }, - new CommandFormSummaryDto { Id = 13, ActionName = "Book", Title = "Réservation" }, - client); - - await vm.SubmitCommand.ExecuteAsync(null); - - Assert.Null(api.LastPath); - Assert.Contains("n'est pas encore pris en charge", vm.StatusMessage, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public async Task InitializeAsync_loads_prestations_for_brush_and_submit_posts_selected_prestation() - { - var api = new RecordingApi - { - HairPrestations = new List - { - new() { Id = 10, Title = "Femme · Cheveux mi-longs", Details = "Coupe · Brushing" }, - new() { Id = 11, Title = "Homme · Cheveux courts", Details = "Coupe · Coiffage" }, - } - }; - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = new BillingCommandPageViewModel( - new ActivityBrowseItemDto { Code = "brush", Name = "Brush" }, - new ActivityUserDisplayItem { PerformerId = "perf-2", UserName = "Bob" }, - new CommandFormSummaryDto { Id = 13, ActionName = "Brush", Title = "Coupe" }, - client) - { - EventDateText = "2026-09-02 14:30", - Address = "1 rue du Test", - LatitudeText = "48.8566", - LongitudeText = "2.3522", - Consent = true, - AdditionalInfo = "Prévoir shampoing", - }; - - await vm.InitializeAsync(); - vm.SelectedPrestation = vm.AvailablePrestations[1]; - await vm.SubmitCommand.ExecuteAsync(null); - - Assert.Equal("https://business.example/api/v1/billing/Brush", api.LastPath); - using var json = JsonDocument.Parse(JsonSerializer.Serialize(api.LastBody)); - Assert.Equal(11, json.RootElement.GetProperty("PrestationId").GetInt32()); - Assert.Equal("Prévoir shampoing", json.RootElement.GetProperty("AdditionalInfo").GetString()); - } - - [Fact] - public async Task InitializeAsync_loads_prestations_for_mbrush_and_submit_posts_selected_prestations() - { - var api = new RecordingApi - { - HairPrestations = new List - { - new() { Id = 21, Title = "Femme · Cheveux longs", Details = "Coupe · Couleur" }, - new() { Id = 22, Title = "Enfant · Cheveux courts", Details = "Coupe · Sans technique" }, - } - }; - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = new BillingCommandPageViewModel( - new ActivityBrowseItemDto { Code = "mbrush", Name = "MBrush" }, - new ActivityUserDisplayItem { PerformerId = "perf-3", UserName = "Cara" }, - new CommandFormSummaryDto { Id = 14, ActionName = "MBrush", Title = "Coupe groupée" }, - client) - { - EventDateText = "2026-09-03 10:00", - Address = "2 rue du Test", - LatitudeText = "48.8567", - LongitudeText = "2.3523", - Consent = true, - }; - - await vm.InitializeAsync(); - vm.MultiPrestations[0].IsSelected = true; - vm.MultiPrestations[1].IsSelected = true; - await vm.SubmitCommand.ExecuteAsync(null); - - Assert.Equal("https://business.example/api/v1/billing/MBrush", api.LastPath); - using var json = JsonDocument.Parse(JsonSerializer.Serialize(api.LastBody)); - var prestations = json.RootElement.GetProperty("Prestations"); - Assert.Equal(2, prestations.GetArrayLength()); - Assert.Equal(21, prestations[0].GetProperty("PrestationId").GetInt32()); - Assert.Equal(22, prestations[1].GetProperty("PrestationId").GetInt32()); - } - - [Fact] - public async Task InitializeAsync_with_existing_brush_query_prefills_and_submit_updates_query() - { - var api = new RecordingApi - { - HairPrestations = new List - { - new() { Id = 30, Title = "Femme · Cheveux longs", Details = "Coupe · Brushing" }, - new() { Id = 31, Title = "Homme · Cheveux courts", Details = "Coupe" }, - } - }; - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = new BillingCommandPageViewModel( - new ActivityBrowseItemDto { Code = "brush", Name = "Brush" }, - new ActivityUserDisplayItem { PerformerId = "perf-2", UserName = "Bob" }, - new CommandFormSummaryDto { Id = 13, ActionName = "Brush", Title = "Coupe" }, - client); - - await vm.InitializeAsync(new BillingQueryDetailsDto - { - Id = 77, - BillingCode = "Brush", - ActivityCode = "brush", - PerformerId = "perf-2", - ClientId = "cli-1", - EventDate = new DateTime(2026, 9, 2, 14, 30, 0, DateTimeKind.Utc), - Consent = true, - Status = QueryStatus.Accepted, - PrestationId = 30, - AdditionalInfo = "Ancienne note", - Location = new BillingLocationDto - { - Address = "1 rue du Test", - Latitude = 48.8566, - Longitude = 2.3522, - } - }); - - vm.SelectedPrestation = vm.AvailablePrestations[1]; - vm.AdditionalInfo = "Note mise à jour"; - await vm.SubmitCommand.ExecuteAsync(null); - - Assert.Equal(HttpMethod.Put, api.LastMethod); - Assert.Equal("https://business.example/api/v1/billing/Brush/77", api.LastPath); - Assert.True(vm.IsEditingExisting); - Assert.Equal("Mettre à jour la commande", vm.SubmitLabel); - - using var json = JsonDocument.Parse(JsonSerializer.Serialize(api.LastBody)); - Assert.Equal(77, json.RootElement.GetProperty("Id").GetInt32()); - Assert.Equal(31, json.RootElement.GetProperty("PrestationId").GetInt32()); - Assert.Equal("Note mise à jour", json.RootElement.GetProperty("AdditionalInfo").GetString()); - Assert.Equal((int)QueryStatus.Accepted, json.RootElement.GetProperty("Status").GetInt32()); - } - - private sealed class RecordingApi : IYavscApiClient - { - public HttpClient Http { get; } = new(); - public HttpMethod? LastMethod { get; private set; } - public string? LastPath { get; private set; } - public object? LastBody { get; private set; } - public List? HairPrestations { get; init; } - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - LastMethod = method; - LastPath = path; - LastBody = body; - if (typeof(T) == typeof(List)) - { - return Task.FromResult((T)(object)(HairPrestations ?? new List())); - } - return Task.FromResult(default(T)!); - } - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - LastMethod = method; - LastPath = path; - LastBody = body; - return Task.CompletedTask; - } - - public ValueTask DisposeAsync() => ValueTask.CompletedTask; - } -} \ No newline at end of file diff --git a/src/PostIt/PostIt.Tests/BillingQueriesPageViewModelTests.cs b/src/PostIt/PostIt.Tests/BillingQueriesPageViewModelTests.cs deleted file mode 100644 index a5d37532..00000000 --- a/src/PostIt/PostIt.Tests/BillingQueriesPageViewModelTests.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System.Net.Http; -using PostIt.ViewModels; -using Yavsc; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; - -namespace PostIt.Tests; - -public class BillingQueriesPageViewModelTests -{ - [Fact] - public async Task RefreshAsync_filters_queries_by_selected_activity_and_performer() - { - var api = new StubBillingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = new BillingQueriesPageViewModel( - new ActivityBrowseItemDto { Code = "dev", Name = "Développement" }, - new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - new CommandFormSummaryDto { Id = 1, ActionName = "Rdv", Title = "Rendez-vous" }, - client); - - await vm.InitializeAsync(); - - Assert.Equal("https://business.example/api/v1/billing/Rdv", api.Paths.Single()); - Assert.Equal(3, vm.Queries.Count); - Assert.Contains(vm.Queries, q => q.Description == "Rendez-vous #1"); - Assert.Contains("3 commande", vm.StatusMessage, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public async Task RefreshAsync_in_readonly_ongoing_mode_keeps_only_ongoing_statuses_and_disables_open() - { - var api = new StubBillingApi(); - var client = new BillingApiClient(api, "https://business.example/api/v1/"); - var vm = new BillingQueriesPageViewModel( - new ActivityBrowseItemDto { Code = "dev", Name = "Développement" }, - new ActivityUserDisplayItem { PerformerId = "perf-1", UserName = "Alice" }, - new CommandFormSummaryDto { Id = 1, ActionName = "Rdv", Title = "Rendez-vous" }, - client, - isReadOnly: true, - ongoingOnly: true); - - await vm.InitializeAsync(); - - Assert.Equal(2, vm.Queries.Count); - Assert.All(vm.Queries, q => Assert.DoesNotContain("Rejected", q.StatusLabel, StringComparison.OrdinalIgnoreCase)); - Assert.Contains("lecture seule", vm.StatusMessage, StringComparison.OrdinalIgnoreCase); - Assert.False(vm.CanOpenDetails); - - vm.SelectedQuery = vm.Queries[0]; - Assert.False(vm.OpenSelectedQueryCommand.CanExecute(null)); - } - - private sealed class StubBillingApi : IYavscApiClient - { - public HttpClient Http { get; } = new(); - public List Paths { get; } = new(); - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - Paths.Add(path); - - if (typeof(T) == typeof(List)) - { - var data = new List - { - new() - { - Id = 11, - ActivityCode = "dev", - PerformerId = "perf-1", - ClientId = "cli-1", - Status = QueryStatus.Inserted, - Description = "Rendez-vous #1", - Reason = "Point de cadrage", - EventDate = new DateTime(2026, 9, 1, 10, 0, 0, DateTimeKind.Utc), - }, - new() - { - Id = 12, - ActivityCode = "other", - PerformerId = "perf-1", - ClientId = "cli-1", - Status = QueryStatus.Accepted, - Description = "Autre activité", - EventDate = new DateTime(2026, 9, 2, 10, 0, 0, DateTimeKind.Utc), - }, - new() - { - Id = 13, - ActivityCode = "dev", - PerformerId = "perf-2", - ClientId = "cli-1", - Status = QueryStatus.Accepted, - Description = "Autre performer", - EventDate = new DateTime(2026, 9, 3, 10, 0, 0, DateTimeKind.Utc), - }, - new() - { - Id = 14, - ActivityCode = "dev", - PerformerId = "perf-1", - ClientId = "cli-1", - Status = QueryStatus.InProgress, - Description = "En cours", - EventDate = new DateTime(2026, 9, 4, 10, 0, 0, DateTimeKind.Utc), - }, - new() - { - Id = 15, - ActivityCode = "dev", - PerformerId = "perf-1", - ClientId = "cli-1", - Status = QueryStatus.Rejected, - Description = "Rejetée", - EventDate = new DateTime(2026, 9, 5, 10, 0, 0, DateTimeKind.Utc), - } - }; - - return Task.FromResult((T)(object)data); - } - - return Task.FromResult(default(T)!); - } - - public Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - Paths.Add(path); - return Task.CompletedTask; - } - - public ValueTask DisposeAsync() => ValueTask.CompletedTask; - } -} \ No newline at end of file diff --git a/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs b/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs index e86d1c4c..98b6efb3 100644 --- a/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs +++ b/src/PostIt/PostIt/Helpers/ServiceCollectionHelpers.cs @@ -24,7 +24,6 @@ public static class ServiceCollectionHelpers var blogAclClient = new BlogAclApiClient(api, settings.BlogsApiUrl); var userSearchClient = new UserSearchClient(api, settings.BlogsApiUrl); var activityClient = new ActivityApiClient(api, settings.BusinessApiUrl); - var billingClient = new BillingApiClient(api, settings.BusinessApiUrl); var userDirectory = new UserDirectory(userSearchClient); // Vues @@ -48,9 +47,6 @@ public static class ServiceCollectionHelpers services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddTransient(); - services.AddTransient(); - services.AddTransient(); // ViewModels services.AddSingleton(settings); services.AddSingleton(api); @@ -59,13 +55,11 @@ public static class ServiceCollectionHelpers services.AddSingleton(blogAclClient); services.AddSingleton(userSearchClient); services.AddSingleton(activityClient); - services.AddSingleton(billingClient); services.AddSingleton(userDirectory); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddTransient(); // Dialogs (modal-light pages): the ViewLocator resolves // them when a caller pushes a PostAclDialogViewModel or diff --git a/src/PostIt/PostIt/ViewLocator.cs b/src/PostIt/PostIt/ViewLocator.cs index 2fd51633..a81bf6ce 100644 --- a/src/PostIt/PostIt/ViewLocator.cs +++ b/src/PostIt/PostIt/ViewLocator.cs @@ -40,9 +40,6 @@ public class ViewLocator : IDataTemplate Settings => services.GetRequiredService(), HomePageViewModel => services.GetRequiredService(), ActivitiesPageViewModel => services.GetRequiredService(), - CommandFormsPageViewModel => services.GetRequiredService(), - BillingCommandPageViewModel => services.GetRequiredService(), - BillingQueriesPageViewModel => services.GetRequiredService(), SignaturePageViewModel => services.GetRequiredService(), AddCircleMemberDialogViewModel => services.GetRequiredService(), CirclesPageViewModel => services.GetRequiredService(), diff --git a/src/PostIt/PostIt/ViewModels/ActivitiesPageViewModel.cs b/src/PostIt/PostIt/ViewModels/ActivitiesPageViewModel.cs index 5268d07a..e39a2c18 100644 --- a/src/PostIt/PostIt/ViewModels/ActivitiesPageViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/ActivitiesPageViewModel.cs @@ -4,10 +4,8 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; -using Avalonia; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; -using PostIt.Helpers; using Yavsc.Abstract.Workflow; using Yavsc.Api.Client; @@ -16,7 +14,6 @@ namespace PostIt.ViewModels; public partial class ActivitiesPageViewModel : ViewModelBase { private readonly ActivityApiClient _client; - private readonly BillingApiClient _billingClient; private bool _syncingSelection; [ObservableProperty] @@ -34,9 +31,6 @@ public partial class ActivitiesPageViewModel : ViewModelBase [ObservableProperty] public partial ObservableCollection Performers { get; set; } = new(); - [ObservableProperty, NotifyCanExecuteChangedFor(nameof(OpenCommandFormsCommand))] - public partial ActivityUserDisplayItem? SelectedPerformer { get; set; } - [ObservableProperty] public partial bool IsBusy { get; set; } @@ -46,7 +40,6 @@ public partial class ActivitiesPageViewModel : ViewModelBase public ActivityBrowseItemDto? CurrentActivity => SelectedSpecialization ?? SelectedActivity; public string SelectedActivityLabel => SelectedActivity?.Name ?? "(aucune activité)"; public string CurrentActivityLabel => CurrentActivity?.Name ?? "(aucune)"; - public int CurrentFormCount => CurrentActivity?.Forms?.Count ?? 0; public override bool CanNavigateNext { @@ -60,10 +53,9 @@ public partial class ActivitiesPageViewModel : ViewModelBase protected set { _ = value; } } - public ActivitiesPageViewModel(ActivityApiClient client, BillingApiClient billingClient) + public ActivitiesPageViewModel(ActivityApiClient client) { _client = client ?? throw new ArgumentNullException(nameof(client)); - _billingClient = billingClient ?? throw new ArgumentNullException(nameof(billingClient)); } partial void OnSelectedActivityChanged(ActivityBrowseItemDto? value) @@ -162,13 +154,11 @@ public partial class ActivitiesPageViewModel : ViewModelBase OnPropertyChanged(nameof(CurrentActivity)); OnPropertyChanged(nameof(SelectedActivityLabel)); OnPropertyChanged(nameof(CurrentActivityLabel)); - OnPropertyChanged(nameof(CurrentFormCount)); Specializations = new ObservableCollection(activity?.Children ?? new()); if (activity is null) { Performers = new ObservableCollection(); - SelectedPerformer = null; return; } @@ -189,7 +179,6 @@ public partial class ActivitiesPageViewModel : ViewModelBase OnPropertyChanged(nameof(CurrentActivity)); OnPropertyChanged(nameof(CurrentActivityLabel)); - OnPropertyChanged(nameof(CurrentFormCount)); if (specialization is null) { @@ -211,47 +200,21 @@ public partial class ActivitiesPageViewModel : ViewModelBase var list = await _client.GetUsersAsync(activity.Code); Performers = new ObservableCollection((list ?? new()) .Select(ActivityUserDisplayItem.FromDto)); - SelectedPerformer = null; StatusMessage = $"{activity.Name} · {Performers.Count} utilisateur(s)"; } catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) { Performers = new ObservableCollection(); - SelectedPerformer = null; StatusMessage = "Accès refusé pour les activités (scope 'api'). Déconnectez puis reconnectez-vous."; } catch (Exception ex) { Performers = new ObservableCollection(); - SelectedPerformer = null; StatusMessage = $"Erreur: {ex.Message}"; } finally { IsBusy = false; - OpenCommandFormsCommand.NotifyCanExecuteChanged(); } } - - private bool CanOpenCommandForms() - => SelectedPerformer is not null && CurrentActivity?.Forms?.Count > 0; - - [RelayCommand(CanExecute = nameof(CanOpenCommandForms))] - private async Task OpenCommandFormsAsync() - { - if (SelectedPerformer is null || CurrentActivity is null) - { - StatusMessage = "Sélectionnez un utilisateur et une activité avec formulaire."; - return; - } - - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - var vm = new CommandFormsPageViewModel(CurrentActivity, SelectedPerformer, _billingClient); - await app.PushPageAsync(vm); - } } diff --git a/src/PostIt/PostIt/ViewModels/BillingCommandPageViewModel.cs b/src/PostIt/PostIt/ViewModels/BillingCommandPageViewModel.cs deleted file mode 100644 index c9050cbf..00000000 --- a/src/PostIt/PostIt/ViewModels/BillingCommandPageViewModel.cs +++ /dev/null @@ -1,399 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Globalization; -using System.Net; -using System.Net.Http; -using System.Linq; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using Yavsc; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; -using Yavsc.Models.Billing; -using Yavsc.Models.Haircut; -using Yavsc.Models.Relationship; - -namespace PostIt.ViewModels; - -public partial class BillingCommandPageViewModel : ViewModelBase -{ - private readonly BillingApiClient _billingClient; - - public ActivityBrowseItemDto Activity { get; } - public ActivityUserDisplayItem Performer { get; } - public CommandFormSummaryDto Form { get; } - - [ObservableProperty] - public partial bool IsBusy { get; set; } - - [ObservableProperty] - public partial string StatusMessage { get; set; } - - [ObservableProperty] - public partial string EventDateText { get; set; } - - [ObservableProperty] - public partial string Reason { get; set; } = string.Empty; - - [ObservableProperty] - public partial string Address { get; set; } = string.Empty; - - [ObservableProperty] - public partial string LatitudeText { get; set; } = string.Empty; - - [ObservableProperty] - public partial string LongitudeText { get; set; } = string.Empty; - - [ObservableProperty] - public partial bool Consent { get; set; } = true; - - [ObservableProperty] - public partial ObservableCollection AvailablePrestations { get; set; } = new(); - - [ObservableProperty] - public partial HairPrestationDto? SelectedPrestation { get; set; } - - [ObservableProperty] - public partial ObservableCollection MultiPrestations { get; set; } = new(); - - [ObservableProperty] - public partial string AdditionalInfo { get; set; } = string.Empty; - - [ObservableProperty] - public partial long? ExistingQueryId { get; set; } - - [ObservableProperty] - public partial QueryStatus CommandStatus { get; set; } = QueryStatus.Inserted; - - public string Title => Form.Title; - public string PerformerLabel => Performer.UserName; - public string ActivityLabel => Activity.Name; - public bool IsSupported => IsRdv || IsBrush || IsMultiBrush; - public bool IsRdv => string.Equals(Form.ActionName, BillingCodes.Rdv, StringComparison.Ordinal); - public bool IsBrush => string.Equals(Form.ActionName, BillingCodes.Brush, StringComparison.Ordinal); - public bool IsMultiBrush => string.Equals(Form.ActionName, BillingCodes.MBrush, StringComparison.Ordinal); - public bool ShowsReason => IsRdv; - public bool ShowsAdditionalInfo => IsBrush; - public bool ShowsSinglePrestation => IsBrush; - public bool ShowsMultiplePrestations => IsMultiBrush; - public string BillingRoute => $"/billing/{Form.ActionName}"; - public bool IsEditingExisting => ExistingQueryId.HasValue; - public string SubmitLabel => IsEditingExisting ? "Mettre à jour la commande" : "Poster la commande"; - public string SupportMessage => IsSupported - ? IsRdv - ? "Complétez les informations du rendez-vous puis postez la commande." - : IsBrush - ? "Choisissez une prestation coiffure puis postez la commande." - : "Choisissez une ou plusieurs prestations coiffure puis postez la commande." - : $"Le formulaire {Form.ActionName} n'est pas encore pris en charge dans PostIt."; - - public override bool CanNavigateNext - { - get => false; - protected set { _ = value; } - } - - public override bool CanNavigatePrevious - { - get => true; - protected set { _ = value; } - } - - public BillingCommandPageViewModel( - ActivityBrowseItemDto activity, - ActivityUserDisplayItem performer, - CommandFormSummaryDto form, - BillingApiClient billingClient) - { - Activity = activity ?? throw new ArgumentNullException(nameof(activity)); - Performer = performer ?? throw new ArgumentNullException(nameof(performer)); - Form = form ?? throw new ArgumentNullException(nameof(form)); - _billingClient = billingClient ?? throw new ArgumentNullException(nameof(billingClient)); - - EventDateText = DateTime.Now.AddDays(1).ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture); - StatusMessage = SupportMessage; - } - - partial void OnExistingQueryIdChanged(long? value) - { - OnPropertyChanged(nameof(IsEditingExisting)); - OnPropertyChanged(nameof(SubmitLabel)); - } - - public async Task InitializeAsync(BillingQueryDetailsDto? existingQuery = null) - { - if (!IsBrush && !IsMultiBrush) - { - if (existingQuery is not null) - { - ApplyExistingQuery(existingQuery); - } - - return; - } - - IsBusy = true; - try - { - var prestations = await _billingClient.GetHairPrestationsAsync(Form.ActionName).ConfigureAwait(true); - AvailablePrestations = new ObservableCollection(prestations ?? new List()); - SelectedPrestation = AvailablePrestations.FirstOrDefault(); - MultiPrestations = new ObservableCollection(AvailablePrestations.Select(SelectableHairPrestationItem.FromDto)); - - StatusMessage = AvailablePrestations.Count == 0 - ? "Aucune prestation coiffure disponible." - : SupportMessage; - } - catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) - { - StatusMessage = "Accès refusé au catalogue de prestations (scope 'api'). Déconnectez puis reconnectez-vous."; - } - catch (Exception ex) - { - StatusMessage = $"Erreur lors du chargement des prestations: {ex.Message}"; - } - finally - { - IsBusy = false; - } - - if (existingQuery is not null) - { - ApplyExistingQuery(existingQuery); - } - } - - [RelayCommand] - private async Task SubmitAsync() - { - if (!IsSupported) - { - StatusMessage = SupportMessage; - return; - } - - if (!Consent) - { - StatusMessage = "Le consentement est requis pour poster la commande."; - return; - } - - if (!TryParseEventDate(out var eventDate)) - { - StatusMessage = "La date doit être saisie au format yyyy-MM-dd HH:mm."; - return; - } - - if (IsRdv && string.IsNullOrWhiteSpace(Reason)) - { - StatusMessage = "Le motif du rendez-vous est requis."; - return; - } - - if (string.IsNullOrWhiteSpace(Address)) - { - StatusMessage = "L'adresse du rendez-vous est requise."; - return; - } - - if (!TryParseCoordinate(LatitudeText, out var latitude)) - { - StatusMessage = "Latitude invalide."; - return; - } - - if (!TryParseCoordinate(LongitudeText, out var longitude)) - { - StatusMessage = "Longitude invalide."; - return; - } - - IsBusy = true; - try - { - var location = new Location - { - Address = Address.Trim(), - Latitude = latitude, - Longitude = longitude, - }; - - var payload = new BillingQueryDetailsDto - { - Id = ExistingQueryId ?? 0, - BillingCode = Form.ActionName, - ActivityCode = Activity.Code, - PerformerId = Performer.PerformerId, - Consent = Consent, - EventDate = eventDate, - Status = CommandStatus, - Reason = Reason.Trim(), - AdditionalInfo = string.IsNullOrWhiteSpace(AdditionalInfo) ? string.Empty : AdditionalInfo.Trim(), - Location = new BillingLocationDto - { - Address = location.Address, - Latitude = location.Latitude, - Longitude = location.Longitude, - } - }; - - if (IsRdv) - { - if (IsEditingExisting) - { - await _billingClient.UpdateAsync(Form.ActionName, ExistingQueryId!.Value, payload).ConfigureAwait(true); - } - else - { - await _billingClient.CreateAsync(Form.ActionName, new - { - ActivityCode = Activity.Code, - PerformerId = Performer.PerformerId, - Consent, - EventDate = eventDate, - Location = location, - Reason = payload.Reason, - Status = payload.Status, - }).ConfigureAwait(true); - } - } - else if (IsBrush) - { - if (SelectedPrestation is null) - { - StatusMessage = "Sélectionnez une prestation coiffure."; - return; - } - - payload.PrestationId = SelectedPrestation.Id; - - if (IsEditingExisting) - { - await _billingClient.UpdateAsync(Form.ActionName, ExistingQueryId!.Value, payload).ConfigureAwait(true); - } - else - { - await _billingClient.CreateAsync(Form.ActionName, new - { - ActivityCode = Activity.Code, - PerformerId = Performer.PerformerId, - Consent, - EventDate = (DateTime?)eventDate, - Location = location, - PrestationId = SelectedPrestation.Id, - AdditionalInfo = string.IsNullOrWhiteSpace(AdditionalInfo) ? null : AdditionalInfo.Trim(), - Status = payload.Status, - }).ConfigureAwait(true); - } - } - else if (IsMultiBrush) - { - var selectedPrestations = MultiPrestations.Where(x => x.IsSelected).ToList(); - if (selectedPrestations.Count == 0) - { - StatusMessage = "Sélectionnez au moins une prestation coiffure."; - return; - } - - payload.PrestationIds = selectedPrestations.Select(x => x.Id).ToList(); - - if (IsEditingExisting) - { - await _billingClient.UpdateAsync(Form.ActionName, ExistingQueryId!.Value, payload).ConfigureAwait(true); - } - else - { - await _billingClient.CreateAsync(Form.ActionName, new - { - ActivityCode = Activity.Code, - PerformerId = Performer.PerformerId, - Consent, - EventDate = eventDate, - Location = location, - Prestations = selectedPrestations.Select(x => new { PrestationId = x.Id }).ToList(), - Status = payload.Status, - }).ConfigureAwait(true); - } - } - - StatusMessage = IsEditingExisting - ? $"Commande #{ExistingQueryId} mise à jour sur {BillingRoute} pour {Performer.UserName}." - : $"Commande transmise sur {BillingRoute} pour {Performer.UserName}."; - } - catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) - { - StatusMessage = "Accès refusé au billing (scope 'api'). Déconnectez puis reconnectez-vous."; - } - catch (Exception ex) - { - StatusMessage = $"Erreur lors de l'envoi: {ex.Message}"; - } - finally - { - IsBusy = false; - } - } - - private void ApplyExistingQuery(BillingQueryDetailsDto existingQuery) - { - ExistingQueryId = existingQuery.Id; - CommandStatus = existingQuery.Status; - Consent = existingQuery.Consent; - Reason = existingQuery.Reason ?? string.Empty; - AdditionalInfo = existingQuery.AdditionalInfo ?? string.Empty; - - if (existingQuery.EventDate is not null) - { - EventDateText = existingQuery.EventDate.Value - .ToLocalTime() - .ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture); - } - - if (existingQuery.Location is not null) - { - Address = existingQuery.Location.Address ?? string.Empty; - LatitudeText = existingQuery.Location.Latitude.ToString(CultureInfo.InvariantCulture); - LongitudeText = existingQuery.Location.Longitude.ToString(CultureInfo.InvariantCulture); - } - - if (IsBrush && existingQuery.PrestationId is not null) - { - SelectedPrestation = AvailablePrestations.FirstOrDefault(x => x.Id == existingQuery.PrestationId.Value); - } - - if (IsMultiBrush) - { - var selectedIds = existingQuery.PrestationIds is null - ? new HashSet() - : new HashSet(existingQuery.PrestationIds); - foreach (var item in MultiPrestations) - { - item.IsSelected = selectedIds.Contains(item.Id); - } - } - - StatusMessage = $"Commande #{existingQuery.Id} chargée."; - } - - private bool TryParseEventDate(out DateTime eventDate) - { - return DateTime.TryParse( - EventDateText, - CultureInfo.CurrentCulture, - DateTimeStyles.AssumeLocal, - out eventDate) - || DateTime.TryParse( - EventDateText, - CultureInfo.InvariantCulture, - DateTimeStyles.AssumeLocal, - out eventDate); - } - - private static bool TryParseCoordinate(string text, out double value) - { - return double.TryParse(text, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.CurrentCulture, out value) - || double.TryParse(text, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out value); - } -} \ No newline at end of file diff --git a/src/PostIt/PostIt/ViewModels/BillingQueriesPageViewModel.cs b/src/PostIt/PostIt/ViewModels/BillingQueriesPageViewModel.cs deleted file mode 100644 index d680ba74..00000000 --- a/src/PostIt/PostIt/ViewModels/BillingQueriesPageViewModel.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Avalonia; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using PostIt.Helpers; -using Yavsc; -using Yavsc.Api.Client; -using Yavsc.Abstract.Workflow; - -namespace PostIt.ViewModels; - -public partial class BillingQueriesPageViewModel : ViewModelBase -{ - private readonly BillingApiClient _billingClient; - - public ActivityBrowseItemDto Activity { get; } - public ActivityUserDisplayItem Performer { get; } - public CommandFormSummaryDto Form { get; } - public bool IsReadOnly { get; } - public bool OngoingOnly { get; } - - [ObservableProperty] - public partial ObservableCollection Queries { get; set; } = new(); - - [ObservableProperty, NotifyCanExecuteChangedFor(nameof(OpenSelectedQueryCommand))] - public partial BillingQueryDisplayItem? SelectedQuery { get; set; } - - [ObservableProperty] - public partial bool IsBusy { get; set; } - - [ObservableProperty] - public partial string StatusMessage { get; set; } = "Chargement des commandes..."; - - public string Title => IsReadOnly - ? $"Demandes en cours ({Form.Title})" - : $"Commandes {Form.Title}"; - public string ContextLabel => $"{Performer.UserName} · {Activity.Name}"; - public bool CanOpenDetails => !IsReadOnly; - - public override bool CanNavigateNext - { - get => false; - protected set { _ = value; } - } - - public override bool CanNavigatePrevious - { - get => true; - protected set { _ = value; } - } - - public BillingQueriesPageViewModel( - ActivityBrowseItemDto activity, - ActivityUserDisplayItem performer, - CommandFormSummaryDto form, - BillingApiClient billingClient, - bool isReadOnly = false, - bool ongoingOnly = false) - { - Activity = activity ?? throw new ArgumentNullException(nameof(activity)); - Performer = performer ?? throw new ArgumentNullException(nameof(performer)); - Form = form ?? throw new ArgumentNullException(nameof(form)); - _billingClient = billingClient ?? throw new ArgumentNullException(nameof(billingClient)); - IsReadOnly = isReadOnly; - OngoingOnly = ongoingOnly; - } - - public Task InitializeAsync() => RefreshAsync(); - - private bool CanOpenSelectedQuery() => !IsReadOnly && SelectedQuery is not null; - - [RelayCommand] - public async Task RefreshAsync() - { - IsBusy = true; - try - { - var list = await _billingClient.GetQuerySummariesAsync(Form.ActionName).ConfigureAwait(true); - var filtered = (list ?? new()) - .Where(q => q.ActivityCode == Activity.Code && q.PerformerId == Performer.PerformerId) - .Where(q => !OngoingOnly || IsOngoingStatus(q.Status)) - .OrderByDescending(q => q.EventDate ?? DateTime.MinValue) - .ThenByDescending(q => q.Id) - .Select(BillingQueryDisplayItem.FromDto) - .ToList(); - - Queries = new ObservableCollection(filtered); - StatusMessage = BuildLoadedStatusMessage(filtered.Count); - } - catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) - { - Queries = new ObservableCollection(); - StatusMessage = "Accès refusé au billing (scope 'api'). Déconnectez puis reconnectez-vous."; - } - catch (Exception ex) - { - Queries = new ObservableCollection(); - StatusMessage = $"Erreur: {ex.Message}"; - } - finally - { - IsBusy = false; - } - } - - [RelayCommand(CanExecute = nameof(CanOpenSelectedQuery))] - public async Task OpenSelectedQueryAsync() - { - if (IsReadOnly) - { - StatusMessage = "Mode lecture seule: l'ouverture en modification est désactivée."; - return; - } - - if (SelectedQuery is null) - { - StatusMessage = "Sélectionnez une commande."; - return; - } - - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - IsBusy = true; - try - { - var details = await _billingClient.GetQueryAsync(Form.ActionName, SelectedQuery.Id).ConfigureAwait(true); - var vm = new BillingCommandPageViewModel(Activity, Performer, Form, _billingClient); - await vm.InitializeAsync(details).ConfigureAwait(true); - await app.PushPageAsync(vm).ConfigureAwait(true); - } - catch (HttpRequestException ex) when (ex.StatusCode is HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden) - { - StatusMessage = "Accès refusé au billing (scope 'api'). Déconnectez puis reconnectez-vous."; - } - catch (Exception ex) - { - StatusMessage = $"Erreur lors de l'ouverture: {ex.Message}"; - } - finally - { - IsBusy = false; - } - } - - private string BuildLoadedStatusMessage(int count) - { - if (count == 0) - { - return OngoingOnly - ? "Aucune demande en cours pour ce formulaire." - : "Aucune commande trouvée pour ce formulaire."; - } - - if (OngoingOnly) - { - return $"{count} demande(s) en cours chargée(s) (lecture seule)."; - } - - return $"{count} commande(s) chargée(s)."; - } - - private static bool IsOngoingStatus(QueryStatus status) - => status is QueryStatus.Inserted or QueryStatus.Accepted or QueryStatus.InProgress; -} \ No newline at end of file diff --git a/src/PostIt/PostIt/ViewModels/BillingQueryDisplayItem.cs b/src/PostIt/PostIt/ViewModels/BillingQueryDisplayItem.cs deleted file mode 100644 index bc437337..00000000 --- a/src/PostIt/PostIt/ViewModels/BillingQueryDisplayItem.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using Yavsc.Api.Client; - -namespace PostIt.ViewModels; - -public sealed class BillingQueryDisplayItem -{ - public long Id { get; init; } - public string Description { get; init; } = string.Empty; - public string Summary { get; init; } = string.Empty; - public string StatusLabel { get; init; } = string.Empty; - public string EventDateLabel { get; init; } = string.Empty; - public string BillingCode { get; init; } = string.Empty; - - public static BillingQueryDisplayItem FromDto(BillingQuerySummaryDto dto) - { - var summary = !string.IsNullOrWhiteSpace(dto.Reason) - ? dto.Reason - : !string.IsNullOrWhiteSpace(dto.AdditionalInfo) - ? dto.AdditionalInfo - : dto.Description; - - return new BillingQueryDisplayItem - { - Id = dto.Id, - Description = string.IsNullOrWhiteSpace(dto.Description) - ? $"Commande #{dto.Id}" - : dto.Description, - Summary = summary, - StatusLabel = dto.Status.ToString(), - EventDateLabel = dto.EventDate?.ToLocalTime().ToString("g") ?? "Date non précisée", - BillingCode = dto.BillingCode, - }; - } -} \ No newline at end of file diff --git a/src/PostIt/PostIt/ViewModels/CommandFormsPageViewModel.cs b/src/PostIt/PostIt/ViewModels/CommandFormsPageViewModel.cs deleted file mode 100644 index e1453c11..00000000 --- a/src/PostIt/PostIt/ViewModels/CommandFormsPageViewModel.cs +++ /dev/null @@ -1,134 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.Linq; -using System.Threading.Tasks; -using Avalonia; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using PostIt.Helpers; -using Yavsc.Abstract.Workflow; -using Yavsc.Api.Client; - -namespace PostIt.ViewModels; - -public partial class CommandFormsPageViewModel : ViewModelBase -{ - private readonly BillingApiClient _billingClient; - - public ActivityBrowseItemDto Activity { get; } - public ActivityUserDisplayItem Performer { get; } - - [ObservableProperty] - public partial ObservableCollection Forms { get; set; } - - [ObservableProperty, NotifyCanExecuteChangedFor(nameof(OpenSelectedFormCommand)), NotifyCanExecuteChangedFor(nameof(OpenQueriesCommand)), NotifyCanExecuteChangedFor(nameof(OpenOngoingQueriesCommand))] - public partial CommandFormSummaryDto? SelectedForm { get; set; } - - [ObservableProperty] - public partial string StatusMessage { get; set; } - - public string Title => $"Formulaires pour {Performer.UserName}"; - public string ContextLabel => $"{Activity.Name} · {Forms.Count} formulaire(s)"; - - public override bool CanNavigateNext - { - get => false; - protected set { _ = value; } - } - - public override bool CanNavigatePrevious - { - get => true; - protected set { _ = value; } - } - - public CommandFormsPageViewModel( - ActivityBrowseItemDto activity, - ActivityUserDisplayItem performer, - BillingApiClient billingClient) - { - Activity = activity ?? throw new ArgumentNullException(nameof(activity)); - Performer = performer ?? throw new ArgumentNullException(nameof(performer)); - _billingClient = billingClient ?? throw new ArgumentNullException(nameof(billingClient)); - - Forms = new ObservableCollection((activity.Forms ?? new()) - .OrderBy(f => f.Title) - .ThenBy(f => f.ActionName)); - SelectedForm = Forms.FirstOrDefault(); - StatusMessage = Forms.Count == 0 - ? "Aucun formulaire n'est disponible pour cette activité." - : "Choisissez le formulaire à utiliser."; - } - - private bool CanOpenSelectedForm() => SelectedForm is not null; - - private bool CanOpenQueries() => SelectedForm is not null; - - private bool CanOpenOngoingQueries() => SelectedForm is not null; - - [RelayCommand(CanExecute = nameof(CanOpenSelectedForm))] - private async Task OpenSelectedFormAsync() - { - if (SelectedForm is null) - { - StatusMessage = "Sélectionnez un formulaire."; - return; - } - - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - var vm = new BillingCommandPageViewModel(Activity, Performer, SelectedForm, _billingClient); - await vm.InitializeAsync(); - await app.PushPageAsync(vm); - } - - [RelayCommand(CanExecute = nameof(CanOpenQueries))] - private async Task OpenQueriesAsync() - { - if (SelectedForm is null) - { - StatusMessage = "Sélectionnez un formulaire."; - return; - } - - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - var vm = new BillingQueriesPageViewModel(Activity, Performer, SelectedForm, _billingClient); - await vm.InitializeAsync(); - await app.PushPageAsync(vm); - } - - [RelayCommand(CanExecute = nameof(CanOpenOngoingQueries))] - private async Task OpenOngoingQueriesAsync() - { - if (SelectedForm is null) - { - StatusMessage = "Sélectionnez un formulaire."; - return; - } - - var app = (App?)Application.Current; - if (app is null) - { - throw new InvalidOperationException("Application PostIt indisponible."); - } - - var vm = new BillingQueriesPageViewModel( - Activity, - Performer, - SelectedForm, - _billingClient, - isReadOnly: true, - ongoingOnly: true); - await vm.InitializeAsync(); - await app.PushPageAsync(vm); - } -} \ No newline at end of file diff --git a/src/PostIt/PostIt/ViewModels/SelectableHairPrestationItem.cs b/src/PostIt/PostIt/ViewModels/SelectableHairPrestationItem.cs deleted file mode 100644 index ee07bb97..00000000 --- a/src/PostIt/PostIt/ViewModels/SelectableHairPrestationItem.cs +++ /dev/null @@ -1,22 +0,0 @@ -using CommunityToolkit.Mvvm.ComponentModel; -using Yavsc.Models.Haircut; - -namespace PostIt.ViewModels; - -public partial class SelectableHairPrestationItem : ObservableObject -{ - public long Id { get; init; } - public string Title { get; init; } = string.Empty; - public string Details { get; init; } = string.Empty; - - [ObservableProperty] - public partial bool IsSelected { get; set; } - - public static SelectableHairPrestationItem FromDto(HairPrestationDto dto) - => new() - { - Id = dto.Id, - Title = dto.Title, - Details = dto.Details, - }; -} \ No newline at end of file diff --git a/src/PostIt/PostIt/Views/ActivitiesPage.axaml b/src/PostIt/PostIt/Views/ActivitiesPage.axaml index 31d7be5f..8dd88c6f 100644 --- a/src/PostIt/PostIt/Views/ActivitiesPage.axaml +++ b/src/PostIt/PostIt/Views/ActivitiesPage.axaml @@ -65,14 +65,12 @@ - + - + @@ -106,10 +104,6 @@ -