diff --git a/.idea/.idea.presence/.idea/.gitignore b/.idea/.idea.presence/.idea/.gitignore
new file mode 100644
index 0000000..6c406d2
--- /dev/null
+++ b/.idea/.idea.presence/.idea/.gitignore
@@ -0,0 +1,13 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Rider ignored files
+/contentModel.xml
+/projectSettingsUpdater.xml
+/.idea.presence.iml
+/modules.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.idea.presence/.idea/avalonia.xml b/.idea/.idea.presence/.idea/avalonia.xml
new file mode 100644
index 0000000..553b849
--- /dev/null
+++ b/.idea/.idea.presence/.idea/avalonia.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.presence/.idea/indexLayout.xml b/.idea/.idea.presence/.idea/indexLayout.xml
new file mode 100644
index 0000000..7b08163
--- /dev/null
+++ b/.idea/.idea.presence/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.presence/.idea/vcs.xml b/.idea/.idea.presence/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/.idea.presence/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Desktop.UI/App.axaml b/Desktop.UI/App.axaml
new file mode 100644
index 0000000..6ccc6b1
--- /dev/null
+++ b/Desktop.UI/App.axaml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Desktop.UI/App.axaml.cs b/Desktop.UI/App.axaml.cs
new file mode 100644
index 0000000..3e3e6eb
--- /dev/null
+++ b/Desktop.UI/App.axaml.cs
@@ -0,0 +1,39 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Data.Core;
+using Avalonia.Data.Core.Plugins;
+using System.Linq;
+using Avalonia.Markup.Xaml;
+using Desktop.UI.DI;
+using Desktop.UI.ViewModels;
+using Desktop.UI.Views;
+using Microsoft.Extensions.DependencyInjection;
+using presence_client.ApiClients;
+
+namespace Desktop.UI;
+
+public partial class App : Application
+{
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+
+ var serviceCollection = new ServiceCollection();
+ serviceCollection.AddCommonService();
+ var services = serviceCollection.BuildServiceProvider();
+
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ desktop.MainWindow = new MainWindow()
+ {
+ DataContext = new MainWindowViewModel(services),
+ };
+ }
+
+ base.OnFrameworkInitializationCompleted();
+ }
+}
\ No newline at end of file
diff --git a/Desktop.UI/Assets/avalonia-logo.ico b/Desktop.UI/Assets/avalonia-logo.ico
new file mode 100644
index 0000000..da8d49f
Binary files /dev/null and b/Desktop.UI/Assets/avalonia-logo.ico differ
diff --git a/Desktop.UI/DI/ServiceColletionExtensions.cs b/Desktop.UI/DI/ServiceColletionExtensions.cs
new file mode 100644
index 0000000..aa4ecd6
--- /dev/null
+++ b/Desktop.UI/DI/ServiceColletionExtensions.cs
@@ -0,0 +1,23 @@
+using Microsoft.Extensions.DependencyInjection;
+using Desktop.UI.ViewModels;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+using presence_client.ApiClients;
+
+namespace Desktop.UI.DI;
+
+public static class ServiceColletionExtensions
+{
+ public static void AddCommonService(this IServiceCollection collection)
+ {
+ collection
+ .AddSingleton()
+ .AddSingleton()
+ .AddSingleton()
+ .AddTransient();
+ }
+}
\ No newline at end of file
diff --git a/Desktop.UI/Desktop.UI.csproj b/Desktop.UI/Desktop.UI.csproj
new file mode 100644
index 0000000..efa93bb
--- /dev/null
+++ b/Desktop.UI/Desktop.UI.csproj
@@ -0,0 +1,34 @@
+
+
+ WinExe
+ net8.0
+ enable
+ true
+ app.manifest
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ None
+ All
+
+
+
+
+
+
+
+
+
diff --git a/Desktop.UI/Program.cs b/Desktop.UI/Program.cs
new file mode 100644
index 0000000..343f1f6
--- /dev/null
+++ b/Desktop.UI/Program.cs
@@ -0,0 +1,21 @@
+using Avalonia;
+using System;
+
+namespace Desktop.UI;
+
+sealed class Program
+{
+ // Initialization code. Don't use any Avalonia, third-party APIs or any
+ // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+ // yet and stuff might break.
+ [STAThread]
+ public static void Main(string[] args) => BuildAvaloniaApp()
+ .StartWithClassicDesktopLifetime(args);
+
+ // Avalonia configuration, don't remove; also used by visual designer.
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UsePlatformDetect()
+ .WithInterFont()
+ .LogToTrace();
+}
\ No newline at end of file
diff --git a/Desktop.UI/ViewLocator.cs b/Desktop.UI/ViewLocator.cs
new file mode 100644
index 0000000..f77ae9e
--- /dev/null
+++ b/Desktop.UI/ViewLocator.cs
@@ -0,0 +1,17 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Controls.Templates;
+using Desktop.UI.ViewModels;
+using Desktop.UI.Views;
+using ReactiveUI;
+
+namespace Desktop.UI;
+
+public class ViewLocator : IViewLocator
+{
+ public IViewFor? ResolveView(T? viewModel, string? contract = null) => viewModel switch
+ {
+ GroupWindowViewModel groupWindowViewModel => new GroupWindow{DataContext = groupWindowViewModel},
+ _ => throw new ArgumentOutOfRangeException(nameof(viewModel))
+ };
+}
\ No newline at end of file
diff --git a/Desktop.UI/ViewModels/GroupWindowViewModel.cs b/Desktop.UI/ViewModels/GroupWindowViewModel.cs
new file mode 100644
index 0000000..8c6611d
--- /dev/null
+++ b/Desktop.UI/ViewModels/GroupWindowViewModel.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reactive;
+using System.Reactive.Linq;
+using System.Threading.Tasks;
+using Demo.Domain.Models;
+using presence_client.ApiClients;
+using presence_client.DTO;
+using ReactiveUI;
+
+namespace Desktop.UI.ViewModels;
+
+public class GroupWindowViewModel : ReactiveObject, IRoutableViewModel
+{
+ private readonly GroupApiClient _groupApi;
+ private readonly UserApiClient _userApi;
+ private readonly PresenceApiClient _presenceApi;
+
+ public string UrlPathSegment => "group";
+ public IScreen HostScreen { get; }
+
+ public ObservableCollection Groups { get; } = new();
+ public ObservableCollection FilteredAndSortedStudents { get; } = new();
+ public ObservableCollection SelectedStudents { get; } = new();
+
+ public ReactiveCommand DeleteAllStudentsCommand { get; }
+ public ReactiveCommand DeleteSelectedStudentsCommand { get; }
+ public ReactiveCommand DeleteStudentCommand { get; }
+ public ReactiveCommand EditStudentCommand { get; }
+
+ public List SortOptions { get; } = new() { "Без сортировки", "По фамилии ↑", "По фамилии ↓" };
+
+ private Group? _selectedGroup;
+ public Group? SelectedGroup
+ {
+ get => _selectedGroup;
+ set => this.RaiseAndSetIfChanged(ref _selectedGroup, value);
+ }
+
+ private string _selectedSortOption = "Без сортировки";
+ public string SelectedSortOption
+ {
+ get => _selectedSortOption;
+ set => this.RaiseAndSetIfChanged(ref _selectedSortOption, value);
+ }
+
+ public GroupWindowViewModel(GroupApiClient groupApi, UserApiClient userApi, PresenceApiClient presenceApi, IScreen screen)
+ {
+ _groupApi = groupApi;
+ _userApi = userApi;
+ _presenceApi = presenceApi;
+ HostScreen = screen;
+
+ DeleteAllStudentsCommand = ReactiveCommand.CreateFromTask(DeleteAllStudents);
+ DeleteSelectedStudentsCommand = ReactiveCommand.CreateFromTask(DeleteSelectedStudents);
+ DeleteStudentCommand = ReactiveCommand.CreateFromTask(DeleteStudent);
+ EditStudentCommand = ReactiveCommand.Create(EditStudent); // пока-заглушка
+
+ this.WhenAnyValue(x => x.SelectedGroup, x => x.SelectedSortOption)
+ .Throttle(TimeSpan.FromMilliseconds(200))
+ .ObserveOn(RxApp.MainThreadScheduler)
+ .Subscribe(async _ => await LoadStudents());
+
+ LoadGroups();
+ }
+
+ private async void LoadGroups()
+ {
+ var groups = await _groupApi.ClientGetGroups();
+ Groups.Clear();
+ foreach (var group in groups)
+ Groups.Add(group);
+ }
+
+ private async Task LoadStudents()
+ {
+ if (SelectedGroup == null)
+ {
+ FilteredAndSortedStudents.Clear();
+ return;
+ }
+
+ var response = await _presenceApi.ClientGetPresence(new PresenceQuery { GroupName = SelectedGroup.Name });
+ var students = response.Select(p => new User {
+ FIO = p.FIO,
+ Guid = Guid.NewGuid(), // или другое значение
+ Group = SelectedGroup! // ← добавьте это
+ }).DistinctBy(u => u.FIO).ToList();
+
+ switch (SelectedSortOption)
+ {
+ case "По фамилии ↑":
+ students = students.OrderBy(s => s.FIO).ToList();
+ break;
+ case "По фамилии ↓":
+ students = students.OrderByDescending(s => s.FIO).ToList();
+ break;
+ }
+
+ FilteredAndSortedStudents.Clear();
+ foreach (var student in students)
+ FilteredAndSortedStudents.Add(student);
+ }
+
+ private async Task DeleteAllStudents()
+ {
+ if (SelectedGroup != null)
+ {
+ await _presenceApi.ClientDeletePresenceByGroup(SelectedGroup.Name);
+ await LoadStudents();
+ }
+ }
+
+ private async Task DeleteSelectedStudents()
+ {
+ var guids = SelectedStudents.Select(u => u.Guid).ToList();
+ await _userApi.ClientDeleteUsers(guids);
+ await LoadStudents();
+ }
+
+ private async Task DeleteStudent(User user)
+ {
+ await _userApi.ClientDeleteUser(user.Guid);
+ await LoadStudents();
+ }
+
+ private void EditStudent(User user)
+ {
+ // открытие окна редактирования
+ }
+}
\ No newline at end of file
diff --git a/Desktop.UI/ViewModels/MainWindowViewModel.cs b/Desktop.UI/ViewModels/MainWindowViewModel.cs
new file mode 100644
index 0000000..0d6f931
--- /dev/null
+++ b/Desktop.UI/ViewModels/MainWindowViewModel.cs
@@ -0,0 +1,17 @@
+using System;
+using Microsoft.Extensions.DependencyInjection;
+using ReactiveUI;
+
+namespace Desktop.UI.ViewModels;
+
+public class MainWindowViewModel : ViewModelBase, IScreen
+{
+
+ public RoutingState Router { get; } = new RoutingState();
+
+ public MainWindowViewModel(IServiceProvider serviceProvider)
+ {
+ var groupViewModel = serviceProvider.GetRequiredService();
+ Router.Navigate.Execute(groupViewModel);
+ }
+}
\ No newline at end of file
diff --git a/Desktop.UI/ViewModels/ViewModelBase.cs b/Desktop.UI/ViewModels/ViewModelBase.cs
new file mode 100644
index 0000000..acbc489
--- /dev/null
+++ b/Desktop.UI/ViewModels/ViewModelBase.cs
@@ -0,0 +1,8 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using ReactiveUI;
+
+namespace Desktop.UI.ViewModels;
+
+public class ViewModelBase : ReactiveObject
+{
+}
\ No newline at end of file
diff --git a/Desktop.UI/Views/GroupWindow.axaml b/Desktop.UI/Views/GroupWindow.axaml
new file mode 100644
index 0000000..6cc6e72
--- /dev/null
+++ b/Desktop.UI/Views/GroupWindow.axaml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Desktop.UI/Views/GroupWindow.axaml.cs b/Desktop.UI/Views/GroupWindow.axaml.cs
new file mode 100644
index 0000000..0b3e741
--- /dev/null
+++ b/Desktop.UI/Views/GroupWindow.axaml.cs
@@ -0,0 +1,15 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
+using Desktop.UI.ViewModels;
+
+namespace Desktop.UI.Views;
+
+public partial class GroupWindow : ReactiveWindow
+{
+ public GroupWindow()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/Desktop.UI/Views/MainWindow.axaml b/Desktop.UI/Views/MainWindow.axaml
new file mode 100644
index 0000000..9f02d90
--- /dev/null
+++ b/Desktop.UI/Views/MainWindow.axaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Desktop.UI/Views/MainWindow.axaml.cs b/Desktop.UI/Views/MainWindow.axaml.cs
new file mode 100644
index 0000000..51b337a
--- /dev/null
+++ b/Desktop.UI/Views/MainWindow.axaml.cs
@@ -0,0 +1,17 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
+using Desktop.UI.ViewModels;
+using ReactiveUI;
+
+namespace Desktop.UI.Views;
+
+public partial class MainWindow : ReactiveWindow
+{
+ public MainWindow()
+ {
+ this.WhenActivated(disposables => { });
+ AvaloniaXamlLoader.Load(this);
+
+ }
+}
\ No newline at end of file
diff --git a/Desktop.UI/app.manifest b/Desktop.UI/app.manifest
new file mode 100644
index 0000000..b922880
--- /dev/null
+++ b/Desktop.UI/app.manifest
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Presence.Desktop/App.axaml b/Presence.Desktop/App.axaml
index 92a16bc..5fa9e5a 100644
--- a/Presence.Desktop/App.axaml
+++ b/Presence.Desktop/App.axaml
@@ -2,8 +2,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Presence.Desktop.App"
RequestedThemeVariant="Default">
-
+
+
\ No newline at end of file
diff --git a/Presence.Desktop/App.axaml.cs b/Presence.Desktop/App.axaml.cs
index 0f4bce1..fb47cdd 100644
--- a/Presence.Desktop/App.axaml.cs
+++ b/Presence.Desktop/App.axaml.cs
@@ -1,7 +1,9 @@
+using System;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
using Presence.Desktop.DI;
using Presence.Desktop.ViewModels;
using Presence.Desktop.Views;
@@ -10,6 +12,7 @@ namespace Presence.Desktop
{
public partial class App : Application
{
+ public static IServiceProvider? ServiceProvider { get; private set; }
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
@@ -19,14 +22,20 @@ namespace Presence.Desktop
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddCommonService();
- var services = serviceCollection.BuildServiceProvider();
- var mainViewModel = services.GetRequiredService();
+
+ serviceCollection.AddLogging(builder =>
+ {
+ builder.AddConsole();
+ builder.SetMinimumLevel(LogLevel.Debug);
+ });
+
+ ServiceProvider = serviceCollection.BuildServiceProvider();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow()
{
- DataContext = new MainWindowViewModel(services),
+ DataContext = new MainWindowViewModel(ServiceProvider),
};
}
diff --git a/Presence.Desktop/DI/ServiceColletionExtensions.cs b/Presence.Desktop/DI/ServiceColletionExtensions.cs
index 5b02bdd..4c357c5 100644
--- a/Presence.Desktop/DI/ServiceColletionExtensions.cs
+++ b/Presence.Desktop/DI/ServiceColletionExtensions.cs
@@ -6,22 +6,42 @@ using Presence.Desktop.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using presence_client.ApiClients;
+using presence_client.ApiClients.Interfaces;
+using ReactiveUI;
namespace Presence.Desktop.DI
{
public static class ServiceColletionExtensions
{
- public static void AddCommonService(this IServiceCollection collection) {
+ public static void AddCommonService(this IServiceCollection collection)
+ {
collection
- .AddDbContext()
- .AddSingleton()
- .AddSingleton()
- .AddTransient()
- .AddTransient()
- .AddTransient();
+ .AddHttpClient()
+ .AddLogging(logging =>
+ {
+ logging.ClearProviders();
+ logging.AddConsole();
+ })
+ .AddScoped()
+ .AddScoped()
+ .AddScoped()
+ .AddTransient()
+ .AddTransient()
+ .AddTransient();
+
+ collection.AddHttpClient("PresenceApi", client =>
+ {
+ client.BaseAddress = new Uri("http://localhost:5192");
+ client.DefaultRequestHeaders.Accept.Clear();
+ client.DefaultRequestHeaders.Accept.Add(
+ new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
+ });
}
}
}
\ No newline at end of file
diff --git a/Presence.Desktop/Models/GroupPresenter.cs b/Presence.Desktop/Models/GroupPresenter.cs
index 18cb72b..dbe2907 100644
--- a/Presence.Desktop/Models/GroupPresenter.cs
+++ b/Presence.Desktop/Models/GroupPresenter.cs
@@ -8,7 +8,7 @@ using System.Threading.Tasks;
namespace Presence.Desktop.Models
{
- public class GroupPresenter
+ public class GroupPresenter : ReactiveObject
{
public int Id { get; set; }
public string Name { get; set; }
diff --git a/Presence.Desktop/Models/PresencePresenter.cs b/Presence.Desktop/Models/PresencePresenter.cs
new file mode 100644
index 0000000..a46d2da
--- /dev/null
+++ b/Presence.Desktop/Models/PresencePresenter.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+using ReactiveUI;
+
+namespace Presence.Desktop.Models
+{
+ public class PresencePresenterUI : ReactiveObject
+ {
+ private string _groupName;
+ private List _users;
+
+ public string GroupName
+ {
+ get => _groupName;
+ set => this.RaiseAndSetIfChanged(ref _groupName, value);
+ }
+
+ public List Users
+ {
+ get => _users;
+ set => this.RaiseAndSetIfChanged(ref _users, value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Presence.Desktop/Models/UserPresenter.cs b/Presence.Desktop/Models/UserPresenter.cs
index 787ad48..b71bc71 100644
--- a/Presence.Desktop/Models/UserPresenter.cs
+++ b/Presence.Desktop/Models/UserPresenter.cs
@@ -3,13 +3,71 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using ReactiveUI;
namespace Presence.Desktop.Models
{
- public class UserPresenter
+ public class UserPresenter : ReactiveObject
{
- public Guid Guid { get; set; }
- public string Name { get; set; }
- public GroupPresenter Group { get; set; }
+ private Guid _guid;
+ private string _name;
+ private GroupPresenter _group;
+
+ public Guid Guid
+ {
+ get => _guid;
+ set => this.RaiseAndSetIfChanged(ref _guid, value);
+ }
+
+ public string Name
+ {
+ get => _name;
+ set => this.RaiseAndSetIfChanged(ref _name, value);
+ }
+
+ public GroupPresenter Group
+ {
+ get => _group;
+ set => this.RaiseAndSetIfChanged(ref _group, value);
+ }
+ }
+
+ public class UserPresencePresenter : ReactiveObject
+ {
+ private Guid _guidUser;
+ private string _fio;
+ private int _lessonNumber;
+ private DateOnly _date;
+ private bool _isAttendance;
+
+ public Guid GuidUser
+ {
+ get => _guidUser;
+ set => this.RaiseAndSetIfChanged(ref _guidUser, value);
+ }
+
+ public string FIO
+ {
+ get => _fio;
+ set => this.RaiseAndSetIfChanged(ref _fio, value);
+ }
+
+ public int LessonNumber
+ {
+ get => _lessonNumber;
+ set => this.RaiseAndSetIfChanged(ref _lessonNumber, value);
+ }
+
+ public DateOnly Date
+ {
+ get => _date;
+ set => this.RaiseAndSetIfChanged(ref _date, value);
+ }
+
+ public bool IsAttendance
+ {
+ get => _isAttendance;
+ set => this.RaiseAndSetIfChanged(ref _isAttendance, value);
+ }
}
}
diff --git a/Presence.Desktop/Presence.Desktop.csproj b/Presence.Desktop/Presence.Desktop.csproj
index 993989b..5f257d0 100644
--- a/Presence.Desktop/Presence.Desktop.csproj
+++ b/Presence.Desktop/Presence.Desktop.csproj
@@ -23,11 +23,14 @@
None
All
-
+
+
+
+
diff --git a/Presence.Desktop/Program.cs b/Presence.Desktop/Program.cs
index d0c4bd7..f6767ab 100644
--- a/Presence.Desktop/Program.cs
+++ b/Presence.Desktop/Program.cs
@@ -1,5 +1,7 @@
using Avalonia;
using Avalonia.ReactiveUI;
+using presence_client;
+using presence_client.ApiClients;
using System;
namespace Presence.Desktop
@@ -21,4 +23,4 @@ namespace Presence.Desktop
.LogToTrace()
.UseReactiveUI();
}
-}
\ No newline at end of file
+}
diff --git a/Presence.Desktop/ViewLocator.cs b/Presence.Desktop/ViewLocator.cs
index 3c91c1b..441bc97 100644
--- a/Presence.Desktop/ViewLocator.cs
+++ b/Presence.Desktop/ViewLocator.cs
@@ -11,9 +11,10 @@ namespace Presence.Desktop
{
public IViewFor? ResolveView(T? viewModel, string? contract = null) => viewModel switch
{
+ StartViewModel startViewModel => new StartView { DataContext = startViewModel },
GroupViewModel groupViewModel => new GroupView{DataContext = groupViewModel},
PresenceViewModel presenceViewModel => new PresenceView{DataContext = presenceViewModel},
_ => throw new ArgumentOutOfRangeException(nameof(viewModel))
};
}
-}
\ No newline at end of file
+}
diff --git a/Presence.Desktop/ViewModels/GroupViewModel.cs b/Presence.Desktop/ViewModels/GroupViewModel.cs
index 6b9d091..0de3897 100644
--- a/Presence.Desktop/ViewModels/GroupViewModel.cs
+++ b/Presence.Desktop/ViewModels/GroupViewModel.cs
@@ -1,4 +1,3 @@
-using Demo.Domain.UseCase;
using Presence.Desktop.Models;
using ReactiveUI;
using System;
@@ -7,39 +6,46 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
+using System.Threading.Tasks;
using Avalonia.Controls;
using System.IO;
+using presence_client.ApiClients.Interfaces;
using Demo.Domain.Models;
+using Microsoft.Extensions.DependencyInjection;
+using Presence.Desktop.DI;
namespace Presence.Desktop.ViewModels
{
public class GroupViewModel : ViewModelBase, IRoutableViewModel
{
- private readonly List _groupPresentersDataSource = new List();
+ private readonly IGroupApiClient _groupApiClient;
+ private readonly IUserApiClient _userApiClient;
+
+ private readonly List _groupPresentersDataSource = new();
private ObservableCollection _groups;
- public ObservableCollection Groups => _groups;
+ private ObservableCollection _users;
+ private string? _newFIO;
+ private GroupPresenter? _selectedGroupItem;
+ private UserPresenter? _selectedUser;
+ private string? _selectedSecondItem;
- public GroupPresenter? SelectedGroupItem {
- get => _selectedGroupItem;
+ public ObservableCollection Groups => _groups;
+ public ObservableCollection Users => _users;
+
+ public ObservableCollection SecondComboBoxItems { get; } =
+ new ObservableCollection { "no sort", "name sort", "name sort rev" };
+
+ public GroupPresenter? SelectedGroupItem
+ {
+ get => _selectedGroupItem;
set => this.RaiseAndSetIfChanged(ref _selectedGroupItem, value);
}
- private GroupPresenter? _selectedGroupItem;
-
- public ObservableCollection Users { get => _users;}
- public ObservableCollection _users;
-
- private IGroupUseCase _groupUseCase;
- private IUserUseCase _userUseCase;
-
- private ObservableCollection _secondComboBoxItems = new ObservableCollection { "no sort", "name sort", "name sort rev" };
- public ObservableCollection SecondComboBoxItems => _secondComboBoxItems;
- private string? _selectedSecondItem;
-
-
- public ReactiveCommand DeleteUsersByGroupCommand { get; }
- public ReactiveCommand ImportStudentsCommand { get; }
-
+ public UserPresenter? SelectedUser
+ {
+ get => _selectedUser;
+ set => this.RaiseAndSetIfChanged(ref _selectedUser, value);
+ }
public string? SelectedSecondItem
{
@@ -47,57 +53,61 @@ namespace Presence.Desktop.ViewModels
set => this.RaiseAndSetIfChanged(ref _selectedSecondItem, value);
}
- private UserPresenter? _selectedUser;
- public UserPresenter? SelectedUser
- {
- get => _selectedUser;
- set => this.RaiseAndSetIfChanged(ref _selectedUser, value);
- }
-
- private string? _newFIO;
public string? NewFIO
{
get => _newFIO;
set => this.RaiseAndSetIfChanged(ref _newFIO, value);
}
+ public ReactiveCommand DeleteUsersByGroupCommand { get; }
+ public ReactiveCommand ImportStudentsCommand { get; }
public ReactiveCommand DeleteUserCommand { get; }
public ReactiveCommand UpdateUserFIOCommand { get; }
+ public ReactiveCommand GoBackCommand { get; }
- public GroupViewModel(IGroupUseCase groupUseCase, IUserUseCase userUseCase)
+ public GroupViewModel(IGroupApiClient groupApiClient, IUserApiClient userApiClient, IScreen hostScreen)
{
- _groupUseCase = groupUseCase;
- _userUseCase = userUseCase;
-
- _groups = new ObservableCollection(_groupPresentersDataSource);
+ _groupApiClient = groupApiClient;
+ _userApiClient = userApiClient;
+
+ _groups = new ObservableCollection();
_users = new ObservableCollection();
-
- RefreshGroups();
-
+
+ DeleteUsersByGroupCommand = ReactiveCommand.CreateFromTask(DeleteUsersByGroupAsync);
+ ImportStudentsCommand = ReactiveCommand.CreateFromTask(ImportStudentsAsync);
+ DeleteUserCommand = ReactiveCommand.CreateFromTask(DeleteUserAsync);
+ UpdateUserFIOCommand = ReactiveCommand.CreateFromTask(UpdateUserFIOAsync);
+ HostScreen = hostScreen;
+
+ GoBackCommand = ReactiveCommand.Create(() =>
+ {
+ HostScreen.Router.Navigate.Execute(new StartViewModel(HostScreen));
+ });
+
+ SelectedSecondItem = SecondComboBoxItems.First();
+
this.WhenAnyValue(vm => vm.SelectedGroupItem)
- .Subscribe(_ =>
- {
- RefreshGroups();
- SetUsers();
- });
-
- this.WhenAnyValue(vm => vm.SelectedSecondItem)
+ .WhereNotNull()
.Subscribe(_ => SetUsers());
-
- SelectedSecondItem = _secondComboBoxItems.First();
-
- DeleteUsersByGroupCommand = ReactiveCommand.Create(() => DeleteUsersByGroupID());
- ImportStudentsCommand = ReactiveCommand.CreateFromTask(async () => await ImportStudents());
-
- DeleteUserCommand = ReactiveCommand.Create(DeleteUser);
- UpdateUserFIOCommand = ReactiveCommand.Create(UpdateUserFIO);
+
+ this.WhenAnyValue(vm => vm.SelectedSecondItem)
+ .WhereNotNull()
+ .Subscribe(_ => SetUsers());
+
+ LoadDataAsync().ConfigureAwait(false);
}
- private void RefreshGroups()
+ private async Task LoadDataAsync()
{
- _groupPresentersDataSource.Clear();
+ await RefreshGroupsAsync();
+ }
- foreach (var group in _groupUseCase.GetAllGroupsWithUsers())
+ private async Task RefreshGroupsAsync()
+ {
+ var groupsFromApi = await _groupApiClient.GetGroupsWithUsersAsync();
+ _groupPresentersDataSource.Clear();
+
+ foreach (var group in groupsFromApi)
{
var groupPresenter = new GroupPresenter
{
@@ -108,80 +118,36 @@ namespace Presence.Desktop.ViewModels
Name = user.FIO,
Guid = user.Guid,
Group = new GroupPresenter { Id = group.ID, Name = group.Name }
- }).ToList()
+ }).ToList() ?? new List()
};
_groupPresentersDataSource.Add(groupPresenter);
}
_groups = new ObservableCollection(_groupPresentersDataSource);
- }
-
- private async System.Threading.Tasks.Task ImportStudents()
- {
- if (SelectedGroupItem == null || SelectedGroupItem.Id == null) return;
-
- var openFileDialog = new OpenFileDialog
+ this.RaisePropertyChanged(nameof(Groups));
+
+ if (_groups.Any())
{
- Filters = new List
- {
- new FileDialogFilter { Name = "CSV Files", Extensions = { "csv" } }
- },
- AllowMultiple = false
- };
-
- var result = await openFileDialog.ShowAsync(new Window());
- if (result?.Length > 0)
- {
- var filePath = result[0];
- var lines = File.ReadAllLines(filePath);
-
- foreach (var line in lines.Skip(1))
- {
- var columns = line.Split(',');
- if (columns.Length < 1) continue;
-
- var fio = columns[0];
- var newUser = new User
- {
- FIO = fio,
- Group = new Group
- {
- Name = SelectedGroupItem.Name,
- ID = SelectedGroupItem.Id
- }
- };
-
- var isCreated = _userUseCase.CreateUser(newUser);
- if (isCreated)
- {
- RefreshGroups();
- SetUsers();
- }
- }
- }
- }
-
- private void DeleteUsersByGroupID() {
-
- if (SelectedGroupItem == null)
- return;
-
- var groupID = SelectedGroupItem.Id;
- bool res = _userUseCase.RemoveUsersByGroupID(groupID);
-
- if (res) {
- Users.Clear();
+ SelectedGroupItem = _groups.First();
}
}
private void SetUsers()
{
- if (SelectedGroupItem == null) return;
-
Users.Clear();
- var group = _groups.FirstOrDefault(it => it.Id == SelectedGroupItem.Id);
- if (group?.users == null) return;
+ if (SelectedGroupItem == null)
+ {
+ this.RaisePropertyChanged(nameof(Users));
+ return;
+ }
+
+ var group = _groupPresentersDataSource.FirstOrDefault(it => it.Id == SelectedGroupItem.Id);
+ if (group?.users == null)
+ {
+ this.RaisePropertyChanged(nameof(Users));
+ return;
+ }
var sortedUsers = SelectedSecondItem switch
{
@@ -194,40 +160,96 @@ namespace Presence.Desktop.ViewModels
{
Users.Add(user);
}
+
+ this.RaisePropertyChanged(nameof(Users));
}
- private void DeleteUser(UserPresenter user) {
+ private async Task ImportStudentsAsync()
+ {
+ if (SelectedGroupItem == null || SelectedGroupItem.Id == null) return;
- if (user == null) return;
+ var openFileDialog = new OpenFileDialog
+ {
+ Filters = new List
+ {
+ new() { Name = "CSV Files", Extensions = { "csv" } }
+ },
+ AllowMultiple = false
+ };
- var isDeleted = _userUseCase.RemoveUserByGuid(user.Guid);
- if (isDeleted) {
- Users.Remove(user);
+ var result = await openFileDialog.ShowAsync(new Window());
+ if (result?.Length > 0)
+ {
+ var filePath = result[0];
+ var lines = File.ReadAllLines(filePath);
+ var createdUsers = new List();
+
+ foreach (var line in lines.Skip(1))
+ {
+ var columns = line.Split(';');
+ if (columns.Length < 2) continue;
+
+ var fio = columns[0];
+ var groupName = columns[1];
+ var newUser = new ImportStudents
+ {
+ fio = fio,
+ groupName = groupName
+ };
+ createdUsers.Add(newUser);
+ }
+
+ foreach (var user in createdUsers)
+ {
+ var success = await _userApiClient.CreateUser(user.fio, user.groupName);
+ if (!success)
+ {
+ return;
+ }
+ }
+
+ await RefreshGroupsAsync();
}
}
- private void UpdateUserFIO(UserPresenter user)
+ private async Task DeleteUsersByGroupAsync()
{
- if (user == null || string.IsNullOrWhiteSpace(NewFIO)) return;
+ if (SelectedGroupItem == null) return;
- user.Name = NewFIO;
- var updatedUser = new User
+ var groupId = SelectedGroupItem.Id;
+ var success = await _userApiClient.DeleteUsersByGroupIdAsync(groupId);
+
+ if (success)
{
- Guid = user.Guid,
- FIO = NewFIO,
- Group = new Group
- {
- ID = SelectedGroupItem.Id,
- Name = SelectedGroupItem.Name
- }
- };
- var isUpdated = _userUseCase.UpdateUser(updatedUser);
+ await RefreshGroupsAsync();
+ }
+ }
- if (isUpdated != null)
+ private async Task DeleteUserAsync(UserPresenter user)
+ {
+ if (user == null) return;
+
+ var success = await _userApiClient.DeleteUserAsync(user.Guid);
+ if (success)
{
+ await RefreshGroupsAsync();
+ }
+ }
+
+ private async Task UpdateUserFIOAsync(UserPresenter user)
+ {
+ if (user == null || string.IsNullOrWhiteSpace(NewFIO))
+ {
+ return;
+ }
+
+ var success = await _userApiClient.UpdateUserFioAsync(user.Guid, NewFIO);
+ if (success)
+ {
+ user.Name = NewFIO;
NewFIO = string.Empty;
- RefreshGroups();
- SetUsers();
+ this.RaisePropertyChanged(nameof(Users));
+ await RefreshGroupsAsync();
}
}
diff --git a/Presence.Desktop/ViewModels/MainWindowViewModel.cs b/Presence.Desktop/ViewModels/MainWindowViewModel.cs
index 0bfed38..a424c83 100644
--- a/Presence.Desktop/ViewModels/MainWindowViewModel.cs
+++ b/Presence.Desktop/ViewModels/MainWindowViewModel.cs
@@ -10,7 +10,7 @@ public class MainWindowViewModel: ViewModelBase, IScreen
public MainWindowViewModel(IServiceProvider serviceProvider)
{
- var groupViewModel = serviceProvider.GetRequiredService();
- Router.Navigate.Execute(groupViewModel);
+ var startViewModel = new StartViewModel(this);
+ Router.Navigate.Execute(startViewModel);
}
}
\ No newline at end of file
diff --git a/Presence.Desktop/ViewModels/PresenceViewModel.cs b/Presence.Desktop/ViewModels/PresenceViewModel.cs
index 290ac6f..71edc7b 100644
--- a/Presence.Desktop/ViewModels/PresenceViewModel.cs
+++ b/Presence.Desktop/ViewModels/PresenceViewModel.cs
@@ -1,9 +1,146 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reactive;
+using System.Reactive.Linq;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Demo.Domain.Models;
+using Presence.Desktop.Models;
+using presence_client.ApiClients.Interfaces;
using ReactiveUI;
-namespace Presence.Desktop.ViewModels;
-
-public class PresenceViewModel: ViewModelBase, IRoutableViewModel
+namespace Presence.Desktop.ViewModels
{
- public string? UrlPathSegment { get; }
- public IScreen HostScreen { get; }
+ public class PresenceViewModel : ViewModelBase, IRoutableViewModel
+ {
+ private readonly IPresenceApiClient _presenceApiClient;
+ private readonly IGroupApiClient _groupApiClient;
+
+ private Group _selectedGroup;
+ private DateTime _selectedDate = DateTime.Today;
+
+ public ObservableCollection Groups { get; } = new();
+ public ObservableCollection AttendanceRecords { get; } = new();
+
+ public Group SelectedGroup
+ {
+ get => _selectedGroup;
+ set => this.RaiseAndSetIfChanged(ref _selectedGroup, value);
+ }
+
+ public DateTime SelectedDate
+ {
+ get => _selectedDate;
+ set => this.RaiseAndSetIfChanged(ref _selectedDate, value);
+ }
+
+ private List _selectedAttendanceRecords = new();
+ public List SelectedAttendanceRecords
+ {
+ get => _selectedAttendanceRecords;
+ set => this.RaiseAndSetIfChanged(ref _selectedAttendanceRecords, value);
+ }
+
+ public ReactiveCommand GoBackCommand { get; }
+ public ReactiveCommand LoadAttendanceCommand { get; }
+ public ReactiveCommand, Unit> DeleteAttendanceRecordsCommand { get; }
+
+ public PresenceViewModel(
+ IPresenceApiClient presenceApiClient,
+ IGroupApiClient groupApiClient,
+ IScreen hostScreen)
+ {
+ _presenceApiClient = presenceApiClient;
+ _groupApiClient = groupApiClient;
+ HostScreen = hostScreen;
+
+ GoBackCommand = ReactiveCommand.Create(() =>
+ {
+ HostScreen.Router.Navigate.Execute(new StartViewModel(HostScreen));
+ });
+ LoadAttendanceCommand = ReactiveCommand.CreateFromTask(LoadAttendanceDataAsync);
+ DeleteAttendanceRecordsCommand = ReactiveCommand.CreateFromTask>(DeleteAttendanceRecords);
+
+ this.WhenAnyValue(x => x.SelectedGroup, x => x.SelectedDate)
+ .Throttle(TimeSpan.FromMilliseconds(200))
+ .ObserveOn(RxApp.MainThreadScheduler)
+ .Select(_ => Unit.Default)
+ .InvokeCommand(LoadAttendanceCommand);
+
+ _ = LoadDataAsync();
+ }
+
+ private async Task LoadDataAsync()
+ {
+ try
+ {
+ var groups = await _groupApiClient.GetGroupsAsync();
+ Groups.Clear();
+ foreach (var group in groups ?? Enumerable.Empty())
+ Groups.Add(group);
+
+ if (Groups.Any())
+ {
+ SelectedGroup = Groups.First();
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ }
+
+ private async Task LoadAttendanceDataAsync()
+ {
+ if (SelectedGroup == null) return;
+
+ try
+ {
+ var response = await _presenceApiClient.GetPresenceAsync(
+ SelectedGroup.ID,
+ SelectedDate.ToString("dd.MM.yyyy"),
+ SelectedDate.ToString("dd.MM.yyyy"));
+
+ AttendanceRecords.Clear();
+ foreach (var record in response?.Users?.Select(u => new UserPresencePresenter
+ {
+ GuidUser = u.Guid,
+ FIO = u.FIO,
+ LessonNumber = u.LessonNumber,
+ Date = u.Date,
+ IsAttendance = u.IsAttendance
+ }) ?? Enumerable.Empty())
+ {
+ AttendanceRecords.Add(record);
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ AttendanceRecords.Clear();
+ }
+ }
+
+ private async Task DeleteAttendanceRecords(List selectedItems)
+ {
+ foreach (var item in selectedItems)
+ {
+ AttendanceRecords.Remove(item);
+ var success = await _presenceApiClient.DeletePresenceRecords(
+ item.Date.ToString(), item.LessonNumber, item.GuidUser);
+
+ if (success)
+ {
+ await LoadDataAsync();
+ }
+ }
+
+ SelectedAttendanceRecords.Clear();
+ }
+
+ public string? UrlPathSegment => "presence";
+ public IScreen HostScreen { get; }
+ }
}
\ No newline at end of file
diff --git a/Presence.Desktop/ViewModels/StartViewModel.cs b/Presence.Desktop/ViewModels/StartViewModel.cs
new file mode 100644
index 0000000..fefd8a7
--- /dev/null
+++ b/Presence.Desktop/ViewModels/StartViewModel.cs
@@ -0,0 +1,46 @@
+using System.Reactive;
+using Demo.Data.Repository;
+using Demo.Domain.UseCase;
+using Microsoft.Extensions.DependencyInjection;
+using presence_client.ApiClients.Interfaces;
+using ReactiveUI;
+
+namespace Presence.Desktop.ViewModels;
+
+public class StartViewModel : ViewModelBase, IRoutableViewModel
+{
+ public IScreen HostScreen { get; }
+ public string? UrlPathSegment => "start";
+
+ public ReactiveCommand OpenGroupCommand { get; }
+ public ReactiveCommand OpenPresenceCommand { get; }
+ public ReactiveCommand GoBackCommand { get; }
+
+ public StartViewModel(IScreen hostScreen)
+ {
+ HostScreen = hostScreen;
+
+ OpenGroupCommand = ReactiveCommand.Create(() =>
+ {
+ HostScreen.Router.Navigate.Execute(new GroupViewModel(
+ App.ServiceProvider.GetRequiredService(),
+ App.ServiceProvider.GetRequiredService(),
+ HostScreen
+ ));
+ });
+
+ OpenPresenceCommand = ReactiveCommand.Create(() =>
+ {
+ HostScreen.Router.Navigate.Execute(new PresenceViewModel(
+ App.ServiceProvider.GetRequiredService(),
+ App.ServiceProvider.GetRequiredService(),
+ HostScreen
+ ));
+ });
+
+ GoBackCommand = ReactiveCommand.Create(() =>
+ {
+ HostScreen.Router.Navigate.Execute(new StartViewModel(HostScreen));
+ });
+ }
+}
diff --git a/Presence.Desktop/Views/GroupView.axaml b/Presence.Desktop/Views/GroupView.axaml
index 7329c2d..449dc20 100644
--- a/Presence.Desktop/Views/GroupView.axaml
+++ b/Presence.Desktop/Views/GroupView.axaml
@@ -10,16 +10,18 @@
+
+
-
+
+
+
+
+
@@ -27,6 +29,7 @@
+
@@ -34,10 +37,12 @@
+
+
@@ -47,12 +52,14 @@
+