This commit is contained in:
1billy17 2025-04-30 04:13:44 +03:00
parent f06104e789
commit 8c0ff9bbb8
65 changed files with 1666 additions and 219 deletions

13
.idea/.idea.presence/.idea/.gitignore vendored Normal file
View File

@ -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

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AvaloniaProject">
<option name="projectPerEditor">
<map>
<entry key="Desktop.UI/App.axaml" value="Desktop.UI/Desktop.UI.csproj" />
<entry key="Desktop.UI/GroupWindow.axaml" value="Desktop.UI/Desktop.UI.csproj" />
<entry key="Desktop.UI/Views/GroupWindow.axaml" value="Desktop.UI/Desktop.UI.csproj" />
<entry key="Desktop.UI/Views/MainWindow.axaml" value="Desktop.UI/Desktop.UI.csproj" />
<entry key="Presence.Desktop/App.axaml" value="Presence.Desktop/Presence.Desktop.csproj" />
<entry key="Presence.Desktop/Views/GroupView.axaml" value="Presence.Desktop/Presence.Desktop.csproj" />
<entry key="Presence.Desktop/Views/MainWindow.axaml" value="Presence.Desktop/Presence.Desktop.csproj" />
<entry key="Presence.Desktop/Views/PresenceView.axaml" value="Presence.Desktop/Presence.Desktop.csproj" />
</map>
</option>
</component>
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

9
Desktop.UI/App.axaml Normal file
View File

@ -0,0 +1,9 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Desktop.UI.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>

39
Desktop.UI/App.axaml.cs Normal file
View File

@ -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();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View File

@ -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<GroupApiClient>()
.AddSingleton<UserApiClient>()
.AddSingleton<PresenceApiClient>()
.AddTransient<GroupWindowViewModel>();
}
}

View File

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
<Folder Include="Models\"/>
<AvaloniaResource Include="Assets\**"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.2.1"/>
<PackageReference Include="Avalonia.Desktop" Version="11.2.1"/>
<PackageReference Include="Avalonia.ReactiveUI" Version="11.2.1" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.1"/>
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.1"/>
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.2.1">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1"/>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\presence_client\presence_client.csproj" />
</ItemGroup>
</Project>

21
Desktop.UI/Program.cs Normal file
View File

@ -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<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}

17
Desktop.UI/ViewLocator.cs Normal file
View File

@ -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>(T? viewModel, string? contract = null) => viewModel switch
{
GroupWindowViewModel groupWindowViewModel => new GroupWindow{DataContext = groupWindowViewModel},
_ => throw new ArgumentOutOfRangeException(nameof(viewModel))
};
}

View File

@ -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<Group> Groups { get; } = new();
public ObservableCollection<User> FilteredAndSortedStudents { get; } = new();
public ObservableCollection<User> SelectedStudents { get; } = new();
public ReactiveCommand<Unit, Unit> DeleteAllStudentsCommand { get; }
public ReactiveCommand<Unit, Unit> DeleteSelectedStudentsCommand { get; }
public ReactiveCommand<User, Unit> DeleteStudentCommand { get; }
public ReactiveCommand<User, Unit> EditStudentCommand { get; }
public List<string> 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<User>(DeleteStudent);
EditStudentCommand = ReactiveCommand.Create<User>(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)
{
// открытие окна редактирования
}
}

View File

@ -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<GroupWindowViewModel>();
Router.Navigate.Execute(groupViewModel);
}
}

View File

@ -0,0 +1,8 @@
using CommunityToolkit.Mvvm.ComponentModel;
using ReactiveUI;
namespace Desktop.UI.ViewModels;
public class ViewModelBase : ReactiveObject
{
}

View File

@ -0,0 +1,63 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:Desktop.UI.ViewModels"
xmlns:reactive="http://reactiveui.net"
x:Class="Desktop.UI.Views.GroupWindow"
x:DataType="vm:GroupWindowViewModel"
Title="Группы и студенты"
Width="800" Height="600">
<Window.Resources>
<!-- Примеры простых конвертеров -->
</Window.Resources>
<DockPanel Margin="10">
<!-- Верхняя панель -->
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="0 0 0 10" Spacing="10">
<ComboBox ItemsSource="{Binding Groups}"
SelectedItem="{Binding SelectedGroup}"
Width="200">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Content="Удалить всех студентов"
Command="{Binding DeleteAllStudentsCommand}"
IsEnabled="{Binding SelectedGroup, Converter={StaticResource NullToBoolConverter}}"/>
<ComboBox ItemsSource="{Binding SortOptions}"
SelectedItem="{Binding SelectedSortOption}"
Width="200"/>
</StackPanel>
<!-- Список студентов -->
<ListBox ItemsSource="{Binding FilteredAndSortedStudents}"
ListBox.SelectedItems="{Binding SelectedStudents}"
SelectionMode="Multiple">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding FIO}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Изменить"
Command="{Binding EditStudentCommand}"
CommandParameter="{Binding Path=SelectedStudents[0]}"
IsVisible="{Binding SelectedStudents.Count, Converter={StaticResource EqualsToOneConverter}, ConverterParameter=true}"/>
<MenuItem Header="Удалить"
Command="{Binding DeleteStudentCommand}"
CommandParameter="{Binding Path=SelectedStudents[0]}"
IsVisible="{Binding SelectedStudents.Count, Converter={StaticResource EqualsToOneConverter}, ConverterParameter=true}"/>
<MenuItem Header="Удалить выбранные"
Command="{Binding DeleteSelectedStudentsCommand}"
IsVisible="{Binding SelectedStudents.Count, Converter={StaticResource GreaterThanOneConverter}, ConverterParameter=true}"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
</DockPanel>
</Window>

View File

@ -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<GroupWindowViewModel>
{
public GroupWindow()
{
InitializeComponent();
}
}

View File

@ -0,0 +1,22 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:Desktop.UI.ViewModels"
xmlns:app="clr-namespace:Desktop.UI"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveUi="http://reactiveui.net"
xmlns:reactiveUi1="clr-namespace:ReactiveUI;assembly=ReactiveUI"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Desktop.UI.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="Desktop.UI">
<DockPanel>
<reactiveUi:RoutedViewHost Router="{Binding Router}" DockPanel.Dock="Right" Background="Black">
<reactiveUi:RoutedViewHost.ViewLocator>
<app:ViewLocator/>
</reactiveUi:RoutedViewHost.ViewLocator>
</reactiveUi:RoutedViewHost>
</DockPanel>
</Window>

View File

@ -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<MainWindowViewModel>
{
public MainWindow()
{
this.WhenActivated(disposables => { });
AvaloniaXamlLoader.Load(this);
}
}

18
Desktop.UI/app.manifest Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="Desktop.UI.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

View File

@ -2,8 +2,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Presence.Desktop.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
<FluentTheme />
</Application.Styles>
</Application>

View File

@ -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<GroupViewModel>();
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),
};
}

View File

@ -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<RemoteDatabaseContext>()
.AddSingleton<IGroupRepository, SQLGroupRepositoryImpl>()
.AddSingleton<IUserRepository, SQLUserRepositoryImpl>()
.AddTransient<IGroupUseCase, GroupUseCase>()
.AddTransient<IUserUseCase, UserUseCase>()
.AddTransient<GroupViewModel>();
.AddHttpClient()
.AddLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
})
.AddScoped<IGroupApiClient, GroupApiClient>()
.AddScoped<IUserApiClient, UserApiClient>()
.AddScoped<IPresenceApiClient, PresenceApiClient>()
.AddTransient<StartViewModel>()
.AddTransient<PresenceViewModel>()
.AddTransient<GroupViewModel>();
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"));
});
}
}
}

View File

@ -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; }

View File

@ -0,0 +1,23 @@
using System.Collections.Generic;
using ReactiveUI;
namespace Presence.Desktop.Models
{
public class PresencePresenterUI : ReactiveObject
{
private string _groupName;
private List<UserPresencePresenter> _users;
public string GroupName
{
get => _groupName;
set => this.RaiseAndSetIfChanged(ref _groupName, value);
}
public List<UserPresencePresenter> Users
{
get => _users;
set => this.RaiseAndSetIfChanged(ref _users, value);
}
}
}

View File

@ -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);
}
}
}

View File

@ -23,11 +23,14 @@
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\data\data.csproj" />
<ProjectReference Include="..\domain\domain.csproj" />
<ProjectReference Include="..\presence_client\presence_client.csproj" />
</ItemGroup>
</Project>

View File

@ -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();
}
}
}

View File

@ -11,9 +11,10 @@ namespace Presence.Desktop
{
public IViewFor? ResolveView<T>(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))
};
}
}
}

View File

@ -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<GroupPresenter> _groupPresentersDataSource = new List<GroupPresenter>();
private readonly IGroupApiClient _groupApiClient;
private readonly IUserApiClient _userApiClient;
private readonly List<GroupPresenter> _groupPresentersDataSource = new();
private ObservableCollection<GroupPresenter> _groups;
public ObservableCollection<GroupPresenter> Groups => _groups;
private ObservableCollection<UserPresenter> _users;
private string? _newFIO;
private GroupPresenter? _selectedGroupItem;
private UserPresenter? _selectedUser;
private string? _selectedSecondItem;
public GroupPresenter? SelectedGroupItem {
get => _selectedGroupItem;
public ObservableCollection<GroupPresenter> Groups => _groups;
public ObservableCollection<UserPresenter> Users => _users;
public ObservableCollection<string> SecondComboBoxItems { get; } =
new ObservableCollection<string> { "no sort", "name sort", "name sort rev" };
public GroupPresenter? SelectedGroupItem
{
get => _selectedGroupItem;
set => this.RaiseAndSetIfChanged(ref _selectedGroupItem, value);
}
private GroupPresenter? _selectedGroupItem;
public ObservableCollection<UserPresenter> Users { get => _users;}
public ObservableCollection<UserPresenter> _users;
private IGroupUseCase _groupUseCase;
private IUserUseCase _userUseCase;
private ObservableCollection<string> _secondComboBoxItems = new ObservableCollection<string> { "no sort", "name sort", "name sort rev" };
public ObservableCollection<string> SecondComboBoxItems => _secondComboBoxItems;
private string? _selectedSecondItem;
public ReactiveCommand<Unit, Unit> DeleteUsersByGroupCommand { get; }
public ReactiveCommand<Unit, Unit> 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<Unit, Unit> DeleteUsersByGroupCommand { get; }
public ReactiveCommand<Unit, Unit> ImportStudentsCommand { get; }
public ReactiveCommand<UserPresenter, Unit> DeleteUserCommand { get; }
public ReactiveCommand<UserPresenter, Unit> UpdateUserFIOCommand { get; }
public ReactiveCommand<Unit, Unit> GoBackCommand { get; }
public GroupViewModel(IGroupUseCase groupUseCase, IUserUseCase userUseCase)
public GroupViewModel(IGroupApiClient groupApiClient, IUserApiClient userApiClient, IScreen hostScreen)
{
_groupUseCase = groupUseCase;
_userUseCase = userUseCase;
_groups = new ObservableCollection<GroupPresenter>(_groupPresentersDataSource);
_groupApiClient = groupApiClient;
_userApiClient = userApiClient;
_groups = new ObservableCollection<GroupPresenter>();
_users = new ObservableCollection<UserPresenter>();
RefreshGroups();
DeleteUsersByGroupCommand = ReactiveCommand.CreateFromTask(DeleteUsersByGroupAsync);
ImportStudentsCommand = ReactiveCommand.CreateFromTask(ImportStudentsAsync);
DeleteUserCommand = ReactiveCommand.CreateFromTask<UserPresenter>(DeleteUserAsync);
UpdateUserFIOCommand = ReactiveCommand.CreateFromTask<UserPresenter>(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<UserPresenter>(DeleteUser);
UpdateUserFIOCommand = ReactiveCommand.Create<UserPresenter>(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<UserPresenter>()
};
_groupPresentersDataSource.Add(groupPresenter);
}
_groups = new ObservableCollection<GroupPresenter>(_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<FileDialogFilter>
{
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<FileDialogFilter>
{
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<ImportStudents>();
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();
}
}

View File

@ -10,7 +10,7 @@ public class MainWindowViewModel: ViewModelBase, IScreen
public MainWindowViewModel(IServiceProvider serviceProvider)
{
var groupViewModel = serviceProvider.GetRequiredService<GroupViewModel>();
Router.Navigate.Execute(groupViewModel);
var startViewModel = new StartViewModel(this);
Router.Navigate.Execute(startViewModel);
}
}

View File

@ -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<Group> Groups { get; } = new();
public ObservableCollection<UserPresencePresenter> 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<UserPresencePresenter> _selectedAttendanceRecords = new();
public List<UserPresencePresenter> SelectedAttendanceRecords
{
get => _selectedAttendanceRecords;
set => this.RaiseAndSetIfChanged(ref _selectedAttendanceRecords, value);
}
public ReactiveCommand<Unit, Unit> GoBackCommand { get; }
public ReactiveCommand<Unit, Unit> LoadAttendanceCommand { get; }
public ReactiveCommand<List<UserPresencePresenter>, 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<List<UserPresencePresenter>>(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<Group>())
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<UserPresencePresenter>())
{
AttendanceRecords.Add(record);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
AttendanceRecords.Clear();
}
}
private async Task DeleteAttendanceRecords(List<UserPresencePresenter> 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; }
}
}

View File

@ -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<Unit, Unit> OpenGroupCommand { get; }
public ReactiveCommand<Unit, Unit> OpenPresenceCommand { get; }
public ReactiveCommand<Unit, Unit> GoBackCommand { get; }
public StartViewModel(IScreen hostScreen)
{
HostScreen = hostScreen;
OpenGroupCommand = ReactiveCommand.Create(() =>
{
HostScreen.Router.Navigate.Execute(new GroupViewModel(
App.ServiceProvider.GetRequiredService<IGroupApiClient>(),
App.ServiceProvider.GetRequiredService<IUserApiClient>(),
HostScreen
));
});
OpenPresenceCommand = ReactiveCommand.Create(() =>
{
HostScreen.Router.Navigate.Execute(new PresenceViewModel(
App.ServiceProvider.GetRequiredService<IPresenceApiClient>(),
App.ServiceProvider.GetRequiredService<IGroupApiClient>(),
HostScreen
));
});
GoBackCommand = ReactiveCommand.Create(() =>
{
HostScreen.Router.Navigate.Execute(new StartViewModel(HostScreen));
});
}
}

View File

@ -10,16 +10,18 @@
<Design.DataContext>
<vm:GroupViewModel/>
</Design.DataContext>
<DockPanel Background="Black">
<StackPanel DockPanel.Dock="Bottom">
<TextBlock Text="List ↑" HorizontalAlignment="Center"/>
</StackPanel>
<StackPanel
Spacing="10"
HorizontalAlignment="Center"
DockPanel.Dock="Top"
Orientation="Horizontal">
<StackPanel Spacing="10" HorizontalAlignment="Center" DockPanel.Dock="Top" Orientation="Horizontal">
<Button Content="Назад" Command="{Binding GoBackCommand}"/>
<TextBlock Text="Combobox ->"/>
<ComboBox ItemsSource="{Binding Groups}" SelectedValue="{Binding SelectedGroupItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
@ -27,6 +29,7 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox ItemsSource="{Binding SecondComboBoxItems}" SelectedValue="{Binding SelectedSecondItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
@ -34,10 +37,12 @@
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Content="Delete Users"
Command="{Binding DeleteUsersByGroupCommand}"/>
<Button Content="Import Students" Command="{Binding ImportStudentsCommand}"/>
</StackPanel>
<Border>
<ListBox Background="Black" ItemsSource="{Binding Users}" SelectedItem="{Binding SelectedUser}">
<ListBox.ItemTemplate>
@ -47,12 +52,14 @@
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete User"
Command="{Binding DeleteUserCommand}"
CommandParameter="{Binding SelectedUser}" />
<MenuItem Header="Update FIO">
<StackPanel>
<TextBox Text="{Binding NewFIO, Mode=TwoWay}" Width="150" />
<Button Content="Update"

View File

@ -2,7 +2,67 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Presence.Desktop.Views.PresenceView">
Welcome to Avalonia!
xmlns:vm="clr-namespace:Presence.Desktop.ViewModels"
mc:Ignorable="d"
x:Class="Presence.Desktop.Views.PresenceView"
x:DataType="vm:PresenceViewModel"
d:DesignWidth="800" d:DesignHeight="600">
<Design.DataContext>
<vm:PresenceViewModel/>
</Design.DataContext>
<DockPanel Margin="10">
<StackPanel DockPanel.Dock="Right" Orientation="Vertical" Spacing="10" VerticalAlignment="Center" HorizontalAlignment="Center">
<Calendar SelectedDate="{Binding SelectedDate, Mode=TwoWay}"
DisplayDate="{Binding SelectedDate}"
Width="350"
Background="Transparent" />
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<ComboBox ItemsSource="{Binding Groups}"
SelectedItem="{Binding SelectedGroup}"
Width="200">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Content="Назад"
Command="{Binding GoBackCommand}"
Width="100"/>
</StackPanel>
</StackPanel>
<DataGrid ItemsSource="{Binding AttendanceRecords}"
AutoGenerateColumns="False"
GridLinesVisibility="All"
HeadersVisibility="All"
Background="Transparent"
Foreground="White"
SelectionMode="Extended"
Name="AttendanceGrid"
SelectionChanged="OnAttendanceSelectionChanged">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Удалить"
Command="{Binding DeleteAttendanceRecordsCommand}"
CommandParameter="{Binding SelectedAttendanceRecords}" />
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTextColumn Header="ФИО" Binding="{Binding FIO}" Width="*"/>
<DataGridTextColumn Header="Дата" Binding="{Binding Date, StringFormat='dd.MM.yyyy'}" Width="120"/>
<DataGridTextColumn Header="Номер урока" Binding="{Binding LessonNumber}" Width="100"/>
<DataGridCheckBoxColumn Header="Посещение" Binding="{Binding IsAttendance}" Width="100"/>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</UserControl>

View File

@ -1,7 +1,9 @@
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Presence.Desktop.Models;
using Presence.Desktop.ViewModels;
using ReactiveUI;
@ -12,5 +14,16 @@ public partial class PresenceView : ReactiveUserControl<PresenceViewModel>
public PresenceView()
{
this.WhenActivated(disposables => { });
AvaloniaXamlLoader.Load(this); }
AvaloniaXamlLoader.Load(this);
}
private void OnAttendanceSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (DataContext is PresenceViewModel vm && sender is DataGrid grid)
{
vm.SelectedAttendanceRecords = grid.SelectedItems
.OfType<UserPresencePresenter>()
.ToList();
}
}
}

View File

@ -0,0 +1,13 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:Presence.Desktop.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Presence.Desktop.Views.StartView"
x:DataType="vm:StartViewModel">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="10">
<Button Content="Открыть Группы" Command="{Binding OpenGroupCommand}" Foreground="Black" />
<Button Content="Открыть Посещаемость" Command="{Binding OpenPresenceCommand}" Foreground="Black" />
</StackPanel>
</UserControl>

View File

@ -0,0 +1,15 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Presence.Desktop.ViewModels;
namespace Presence.Desktop.Views;
public partial class StartView : ReactiveUserControl<StartViewModel>
{
public StartView()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -11,5 +11,6 @@ namespace Demo.Data.Repository
bool DeletePresenceByUserGuid(Guid UserGuid);
bool DeletePresenceByGroup(int groupID);
bool DeletePresenceByDateRange(DateOnly startDate, DateOnly endDate);
bool DeletePresenceByDateUserLessonNumber(DateOnly date, int lessonNumber, Guid userGuid);
}
}

View File

@ -11,5 +11,6 @@ namespace Demo.Data.Repository
bool RemoveUsersByGroupID(int GroupID);
public UserLocalEntity? CreateUser(string FIO, string GroupName);
UserLocalEntity? UpdateUser(UserLocalEntity updatedUser);
bool UpdateFioUser(Guid guid, string fio);
}
}

View File

@ -11,9 +11,7 @@ namespace Demo.Data.Repository
public SQLPresenceRepositoryImpl(RemoteDatabaseContext remoteDatabaseContext){
_remoteDatabaseContext = remoteDatabaseContext;
GetAllPresence = _remoteDatabaseContext.PresenceDaos.Select(x => new PresenceLocalEntity{UserGuid = x.UserGuid, Date = x.Date, LessonNumber = x.LessonNumber, IsAttedance = x.IsAttedance}).ToList();
}
public List<PresenceLocalEntity> GetAllPresence = new List<PresenceLocalEntity>{};
public List<PresenceLocalEntity> GetAllPresences(){
return _remoteDatabaseContext.PresenceDaos.Select(x => new PresenceLocalEntity{UserGuid = x.UserGuid, Date = x.Date, LessonNumber = x.LessonNumber, IsAttedance = x.IsAttedance}).ToList();
@ -91,6 +89,23 @@ namespace Demo.Data.Repository
return true;
}
public bool DeletePresenceByDateUserLessonNumber(DateOnly date, int lessonNumber, Guid userGuid)
{
var delRecords = _remoteDatabaseContext
.PresenceDaos.Where(y => y.Date == date &&
y.LessonNumber == lessonNumber &&
y.UserGuid == userGuid).ToList();
_remoteDatabaseContext.PresenceDaos.RemoveRange(delRecords);
_remoteDatabaseContext.SaveChanges();
if (delRecords.Count == 0) {
return false;
}
return true;
}
public void IsAttedance(int firstLesson, int lastLesson, DateOnly date, Guid UserGuid){
var presencesToUpdate = _remoteDatabaseContext.PresenceDaos
.Where(x => x.LessonNumber >= firstLesson

View File

@ -80,5 +80,17 @@ namespace Demo.Data.Repository
_remoteDatabaseContext.SaveChanges();
return new UserLocalEntity{FIO = user.FIO, Guid = user.Guid, GroupID = user.GroupID};
}
public bool UpdateFioUser(Guid guid, string fio)
{
var user = _remoteDatabaseContext.Users.FirstOrDefault(x => x.Guid == guid);
if (user == null){
return false;
}
user.FIO = fio;
_remoteDatabaseContext.SaveChanges();
return true;
}
}
}

View File

@ -27,4 +27,10 @@ namespace Demo.Domain.Models
{
public List<int> GroupIDs { get; set; }
}
public class ImportStudents
{
public string fio { get; set; }
public string groupName { get; set; }
}
}

View File

@ -49,9 +49,22 @@ namespace Demo.Domain.Models
public class UserPresenceInfo
{
public Guid Guid { get; set; }
public string FIO { get; set; }
public int LessonNumber { get; set; }
public DateOnly Date { get; set; }
public bool IsAttendance { get; set; }
}
public class UpdateFioRequest
{
public string Fio { get; set; }
}
public class CreateUserRequest
{
public string Fio { get; set; }
public string GroupName { get; set; }
}
}

View File

@ -16,6 +16,7 @@ namespace Demo.Domain.UseCase
bool DeletePresenceByUser(Guid userGuid);
bool DeletePresenceByGroup(int groupID);
bool DeletePresenceByDateRange(DateOnly startDate, DateOnly endDate);
bool DeletePresenceByDateUserLessonNumber(DateOnly date, int lessonNumber, Guid userGuid);
PresenceResponse GetPresencebyAll(int GroupID, DateOnly? start = null, DateOnly? end = null, Guid userGuid = default);
}
}

View File

@ -7,9 +7,10 @@ namespace Demo.Domain.UseCase
List<User> GetAllUsers();
bool RemoveUserByGuid(Guid userGuid);
bool RemoveUsersByGroupID(int GroupID);
bool CreateUser(User user);
bool CreateUser(string fio, string groupName);
User UpdateUser(User user);
User GetUserByGuid(Guid userGuid);
List<User> GetUsersByGroupID(int groupID);
bool UpdateFioUser(Guid guid, string fio);
}
}

View File

@ -130,45 +130,54 @@ namespace Demo.Domain.UseCase
return presenceByGroup;
}
public PresenceResponse GetPresencebyAll(int GroupID, DateOnly? start = null, DateOnly? end = null, Guid userGuid = default){
IEnumerable<UserLocalEntity>? users = null;
if (userGuid == default)
public PresenceResponse GetPresencebyAll(int GroupID, DateOnly? start = null, DateOnly? end = null, Guid userGuid = default)
{
var usersQuery = _repositoryUserImpl.GetAllUser().Where(x => x.GroupID == GroupID);
if (userGuid != default)
{
users = _repositoryUserImpl.GetAllUser().Where(x => x.GroupID == GroupID);
} else {
users = _repositoryUserImpl.GetAllUser().Where(x => x.GroupID == GroupID && x.Guid == userGuid);
usersQuery = usersQuery.Where(x => x.Guid == userGuid);
}
var users = usersQuery.ToList();
var presencesQuery = _repositoryPresenceImpl.GetAllPresences()
.Where(p => users.Select(u => u.Guid).Contains(p.UserGuid));
if (start.HasValue && end.HasValue)
{
presencesQuery = presencesQuery.Where(p => p.Date >= start && p.Date <= end);
}
var presences = presencesQuery.ToList();
var usersFor = new List<UserPresenceInfo>();
foreach(var user in users){
IEnumerable<PresenceLocalEntity> presences;
if (userGuid != default){
presences = _repositoryPresenceImpl.GetAllPresences().Where(x => x.UserGuid == userGuid).ToList();
}else{
presences = _repositoryPresenceImpl.GetAllPresences().ToList();
}
foreach(var presence in presences)
foreach (var user in users)
{
var userPresences = presences.Where(p => p.UserGuid == user.Guid);
foreach (var presence in userPresences)
{
if (start.HasValue && end.HasValue)
usersFor.Add(new UserPresenceInfo
{
if (presence.Date >= start && presence.Date <= end)
{
usersFor.Add(new UserPresenceInfo{
FIO = user.FIO, LessonNumber = presence.LessonNumber, Date = presence.Date, IsAttendance = presence.IsAttedance
});
}
} else
{
usersFor.Add(new UserPresenceInfo{
FIO = user.FIO, LessonNumber = presence.LessonNumber, Date = presence.Date, IsAttendance = presence.IsAttedance
});
}
Guid = user.Guid,
FIO = user.FIO,
LessonNumber = presence.LessonNumber,
Date = presence.Date,
IsAttendance = presence.IsAttedance
});
}
}
var presenceGet = new PresenceResponse{
GroupName = _repositoryGroupImpl.GetAllGroup().FirstOrDefault(x => x.ID == GroupID).Name,
var groupName = _repositoryGroupImpl.GetAllGroup()
.FirstOrDefault(x => x.ID == GroupID)?.Name ?? string.Empty;
return new PresenceResponse
{
GroupName = groupName,
Users = usersFor
};
return presenceGet;
}
public bool IsAttedance(int firstLesson, int lastLesson, DateOnly date, Guid UserGuid){
@ -235,6 +244,11 @@ namespace Demo.Domain.UseCase
return _repositoryPresenceImpl.DeletePresenceByDateRange(startDate, endDate);
}
public bool DeletePresenceByDateUserLessonNumber(DateOnly date, int lessonNumber, Guid userGuid)
{
return _repositoryPresenceImpl.DeletePresenceByDateUserLessonNumber(date, lessonNumber, userGuid);
}
public bool GeneratePresenceWeek(int firstLesson, int lastLesson, int groupID, DateOnly date){
for (int i = 0; i < 8; i++){
GeneratePresence(firstLesson, lastLesson, groupID, date.AddDays(i));

View File

@ -61,8 +61,8 @@ namespace Demo.Domain.UseCase
return users;
}
public bool CreateUser(User user){
_repositoryUserImpl.CreateUser(user.FIO, user.Group.Name);
public bool CreateUser(string fio, string groupName){
_repositoryUserImpl.CreateUser(fio, groupName);
return true;
}
@ -99,5 +99,10 @@ namespace Demo.Domain.UseCase
Group = group
};
}
public bool UpdateFioUser(Guid guid, string fio)
{
return _repositoryUserImpl.UpdateFioUser(guid, fio);
}
}
}

6
http_client/Class1.cs Normal file
View File

@ -0,0 +1,6 @@
namespace http_client;
public class Class1
{
}

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "presence_api", "presence_ap
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Presence.Desktop", "Presence.Desktop\Presence.Desktop.csproj", "{7B4467D9-1176-4C11-8940-7D0559ED6593}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "presence_client", "presence_client\presence_client.csproj", "{B0E9656D-3791-481D-AFEE-C6686E05899F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -48,5 +50,9 @@ Global
{7B4467D9-1176-4C11-8940-7D0559ED6593}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7B4467D9-1176-4C11-8940-7D0559ED6593}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7B4467D9-1176-4C11-8940-7D0559ED6593}.Release|Any CPU.Build.0 = Release|Any CPU
{B0E9656D-3791-481D-AFEE-C6686E05899F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B0E9656D-3791-481D-AFEE-C6686E05899F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B0E9656D-3791-481D-AFEE-C6686E05899F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B0E9656D-3791-481D-AFEE-C6686E05899F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@ -20,6 +20,7 @@ public class AdminController: ControllerBase{
}
//post
// +
[HttpPost]
public ActionResult<bool> CreateGroup([FromBody] GroupWithUsers request)
{
@ -30,12 +31,21 @@ public class AdminController: ControllerBase{
}
bool isCreated = _groupUseCase.CreateGroup(request.GroupName);
return Ok(isCreated);
}
foreach(var user in request.Users){
var usert = new User{FIO = user.FIO, Guid = Guid.NewGuid(), Group = user.Group};
_userUseCase.CreateUser(usert);
[HttpPost("usercreate")]
public ActionResult<bool> CreateUser([FromBody] CreateUserRequest request)
{
if (request == null || string.IsNullOrEmpty(request.GroupName))
{
_logger.LogWarning("CreateUser: Invalid request");
return BadRequest("Invalid request");
}
bool isCreated = _userUseCase.CreateUser(request.Fio, request.GroupName);
return Ok(isCreated);
}
@ -71,6 +81,19 @@ public class AdminController: ControllerBase{
return Ok(true);
}
[HttpDelete("usersbygroupid/{groupId}")]
public ActionResult<bool> DeleteUsersByGroupId(int groupId)
{
bool isDeleted = _userUseCase.RemoveUsersByGroupID(groupId);
if (!isDeleted)
{
_logger.LogWarning($"Group {groupId} not found or has no users");
return NotFound("Group not found or has no users");
}
return Ok(true);
}
// +
[HttpDelete("group")]
public ActionResult<bool> DeleteGroup(int GroupID){
bool isDeleted = _groupUseCase.RemoveGroupByID(GroupID);
@ -81,6 +104,7 @@ public class AdminController: ControllerBase{
return Ok(true);
}
// +
[HttpDelete("groups")]
public ActionResult<bool> DeleteGroups([FromBody] DeleteGroupsRequest request){
if (request == null){
@ -97,19 +121,22 @@ public class AdminController: ControllerBase{
}
return Ok(true);
}
// +
[HttpDelete("presence")]
public ActionResult<bool> DeletePresence(){
return Ok(_presenceUseCase.DeletePresence());
}
//get
// +
[HttpGet]
public ActionResult<IEnumerable<GroupU>> getGroupsWithUsers()
{
return Ok(_groupUseCase.GetAllGroupsWithUsers());
}
// +
[HttpGet("user/{userGuid}")]
public ActionResult<User> GetUserByGuid(Guid userGuid)
{
@ -121,4 +148,23 @@ public class AdminController: ControllerBase{
return NotFound("User not found");
}
}
//Patch
[HttpPatch("updatefio/user/{userGuid}")]
public ActionResult<bool> UpdateUserFio(Guid userGuid, [FromBody] UpdateFioRequest request)
{
if (string.IsNullOrWhiteSpace(request?.Fio))
{
return BadRequest("FIO cannot be empty");
}
var isUpdated = _userUseCase.UpdateFioUser(userGuid, request.Fio);
if (!isUpdated)
{
_logger.LogWarning("User not found");
return NotFound("User not found");
}
return Ok(true);
}
}

View File

@ -7,10 +7,10 @@ namespace presence_api.Controllers;
[Route("api/[controller]")]
public class GroupController: ControllerBase{
private readonly GroupUseCase _groupUseCase;
public GroupController(GroupUseCase groupUseCase){
_groupUseCase = groupUseCase;
}
private readonly GroupUseCase _groupUseCase;
public GroupController(GroupUseCase groupUseCase){
_groupUseCase = groupUseCase;
}
[HttpGet]
public ActionResult<IEnumerable<Group>> getGroups(){

View File

@ -17,7 +17,8 @@ public class PresenceController: ControllerBase{
_logger = logger;
}
// +
[HttpPost]
public ActionResult<bool> GeneratePresence(int firstLesson, int lastLesson, int groupID, string date) {
@ -31,6 +32,7 @@ public class PresenceController: ControllerBase{
return Ok(true);
}
// +
[HttpGet]
public ActionResult<PresenceResponse> GetPresence(int groupID, string start = null, string end = null, Guid userGuid = default)
{
@ -92,6 +94,26 @@ public class PresenceController: ControllerBase{
return Ok(true);
}
[HttpDelete("records")]
public ActionResult<bool> DeletePresenceByRecords(string date, int lessonNumber, Guid userGuid)
{
if (!DateOnly.TryParse(date, out var parsedDate))
{
_logger.LogWarning("DeletePresenceByRecords: Invalid date format");
return BadRequest("Invalid date format");
}
var isDeleted = _presenceUseCase.DeletePresenceByDateUserLessonNumber(parsedDate, lessonNumber, userGuid);
if (isDeleted == false)
{
_logger.LogWarning("DeletePresenceByRecords: User not found");
return NotFound("User not found");
}
return Ok(true);
}
// +
[HttpPatch]
public ActionResult<bool> UpdatePresence(int firstLesson, int lastLesson, string date, Guid UserGuid)
{

View File

@ -0,0 +1,109 @@
using System.Net.Http.Json;
using System.Net;
using System.Text.Json;
using Microsoft.Extensions.Logging;
namespace presence_client.ApiClients;
public abstract class BaseApiClient
{
protected readonly HttpClient _httpClient;
protected readonly ILogger _logger;
protected readonly JsonSerializerOptions _jsonOptions;
protected BaseApiClient(IHttpClientFactory httpClientFactory, ILogger logger)
{
_httpClient = httpClientFactory.CreateClient("PresenceApi");
_logger = logger;
_jsonOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
WriteIndented = true
};
}
protected async Task<T> GetAsync<T>(string endpoint)
{
try
{
var fullEndpoint = endpoint.StartsWith("api/") ? endpoint : $"api/{endpoint}";
_logger.LogDebug("Sending GET request to {Endpoint}", fullEndpoint);
var response = await _httpClient.GetAsync(fullEndpoint);
if (response.StatusCode == HttpStatusCode.NotFound)
{
_logger.LogWarning("Endpoint {Endpoint} returned 404 Not Found", fullEndpoint);
return default;
}
return await HandleResponse<T>(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in GET request to {Endpoint}", endpoint);
return default;
}
}
protected async Task<TResponse> PostAsync<TRequest, TResponse>(string endpoint, TRequest data)
{
try
{
_logger.LogDebug("Sending POST request to {Endpoint} with data {@Data}", endpoint, data);
var response = await _httpClient.PostAsJsonAsync(endpoint, data, _jsonOptions);
return await HandleResponse<TResponse>(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in POST request to {Endpoint}", endpoint);
return default;
}
}
protected async Task<bool> DeleteAsync(string endpoint)
{
try
{
_logger.LogDebug("Sending DELETE request to {Endpoint}", endpoint);
var response = await _httpClient.DeleteAsync(endpoint);
if (!response.IsSuccessStatusCode)
{
_logger.LogWarning("DELETE request to {Endpoint} failed with status {StatusCode}",
endpoint, response.StatusCode);
}
return response.IsSuccessStatusCode;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in DELETE request to {Endpoint}", endpoint);
return false;
}
}
private async Task<T> HandleResponse<T>(HttpResponseMessage response)
{
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
_logger.LogError("Request failed with status {StatusCode}. Response: {Response}",
response.StatusCode, content);
return default;
}
try
{
var result = await response.Content.ReadFromJsonAsync<T>(_jsonOptions);
_logger.LogDebug("Request succeeded with response {@Response}", result);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deserializing response to type {Type}", typeof(T).Name);
return default;
}
}
}

View File

@ -0,0 +1,26 @@
using System.Net.Http.Json;
using Demo.Domain.Models;
using Microsoft.Extensions.Logging;
using presence_client.ApiClients.Interfaces;
namespace presence_client.ApiClients;
public class GroupApiClient : BaseApiClient, IGroupApiClient
{
private const string BasePath = "Group";
public GroupApiClient(IHttpClientFactory httpClientFactory, ILogger<GroupApiClient> logger)
: base(httpClientFactory, logger)
{
}
public async Task<List<Group>> GetGroupsAsync()
{
return await GetAsync<List<Group>>(BasePath) ?? new List<Group>();
}
public async Task<List<GroupU>> GetGroupsWithUsersAsync()
{
return await GetAsync<List<GroupU>>("Admin") ?? new List<GroupU>();
}
}

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Demo.Domain.Models;
namespace presence_client.ApiClients.Interfaces;
public interface IGroupApiClient
{
Task<List<Group>> GetGroupsAsync();
Task<List<GroupU>> GetGroupsWithUsersAsync();
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Demo.Domain.Models;
using presence_client.DTO;
namespace presence_client.ApiClients.Interfaces;
public interface IPresenceApiClient
{
Task<PresenceResponse?> GetPresenceAsync(int groupId, string startDate, string endDate);
Task<bool> DeletePresenceRecords(string date, int lessonNumder, Guid userGuid);
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Demo.Domain.Models;
using presence_client.DTO;
namespace presence_client.ApiClients.Interfaces;
public interface IUserApiClient
{
Task<bool> DeleteUserAsync(Guid userGuid);
Task<bool> UpdateUserFioAsync(Guid userGuid, string fio);
Task<bool> DeleteUsersByGroupIdAsync(int groupId);
Task<bool> CreateUser(string fio, string groupName);
}

View File

@ -0,0 +1,39 @@
using System.Net.Http.Json;
using Demo.Domain.Models;
using presence_client.DTO;
using Microsoft.Extensions.Logging;
using presence_client.ApiClients.Interfaces;
namespace presence_client.ApiClients;
public class PresenceApiClient : BaseApiClient, IPresenceApiClient
{
private const string BasePath = "api/Presence";
public PresenceApiClient(IHttpClientFactory httpClientFactory, ILogger<PresenceApiClient> logger)
: base(httpClientFactory, logger)
{
}
public async Task<PresenceResponse?> GetPresenceAsync(int groupId, string startDate, string endDate)
{
try
{
var url = $"{BasePath}?groupID={groupId}&start={startDate}&end={endDate}";
_logger.LogInformation($"Sending request to: {url}");
return await _httpClient.GetFromJsonAsync<PresenceResponse>(url);
}
catch (Exception ex)
{
_logger.LogError(ex, "GetPresenceAsync error");
throw;
}
}
public async Task<bool> DeletePresenceRecords(string date, int lessonNumder, Guid userGuid)
{
return await DeleteAsync($"{BasePath}/records/?date={date}&lessonNumber={lessonNumder}&userGuid={userGuid}");
}
}

View File

@ -0,0 +1,81 @@
using System.Net;
using System.Net.Http.Json;
using Demo.Domain.Models;
using presence_client.DTO;
using Microsoft.Extensions.Logging;
using presence_client.ApiClients.Interfaces;
namespace presence_client.ApiClients;
public class UserApiClient : BaseApiClient, IUserApiClient
{
private const string BasePath = "api/Admin";
public UserApiClient(IHttpClientFactory httpClientFactory, ILogger<GroupApiClient> logger)
: base(httpClientFactory, logger)
{
}
public async Task<bool> DeleteUserAsync(Guid userGuid)
{
try
{
var responce = await _httpClient.DeleteAsync($"{BasePath}/user?userGuid={userGuid}");
return responce.IsSuccessStatusCode;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error gg");
return false;
}
}
public async Task<bool> UpdateUserFioAsync(Guid userGuid, string fio)
{
try
{
var request = new UpdateFio { Fio = fio };
var response = await _httpClient.PatchAsJsonAsync(
$"{BasePath}/updatefio/user/{userGuid}",
request,
_jsonOptions);
return response.IsSuccessStatusCode;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating user FIO");
return false;
}
}
public async Task<bool> DeleteUsersByGroupIdAsync(int groupId)
{
try
{
var responce = await _httpClient.DeleteAsync($"{BasePath}/usersbygroupid/{groupId}");
return responce.IsSuccessStatusCode;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error deleting users for group {groupId}");
return false;
}
}
public async Task<bool> CreateUser(string fio, string groupName)
{
try
{
var request = new CreateUserRequest { Fio = fio, GroupName = groupName };
var response = await _httpClient.PostAsJsonAsync($"{BasePath}/usercreate", request, _jsonOptions);
return response.IsSuccessStatusCode;
}
catch (Exception e)
{
_logger.LogError(e, "Error creating user");
return false;
}
}
}

View File

@ -0,0 +1,28 @@
namespace presence_client.DTO;
public class PresenceQuery
{
public string GroupName { get; set; }
}
public class PresencePost
{
public string GroupName { get; set; }
public List<PresenceUserPost> Users { get; set; }
}
public class PresenceUserPost
{
public string UserGuid { get; set; }
public int LessonNumber { get; set; }
public DateTime Date { get; set; }
public bool IsAttendance { get; set; }
}
public class PresenceUpdate
{
public string UserGuid { get; set; }
public int LessonNumber { get; set; }
public DateTime Date { get; set; }
public bool IsAttendance { get; set; }
}

View File

@ -0,0 +1,16 @@
namespace presence_client.DTO;
public class ClientDeleteUsersRequest
{
public List<Guid> UsersGuid { get; set; } = new();
}
public class UpdateFio
{
public string Fio { get; set; }
}
public class ClientDeleteUsersByGroupId
{
public int GroupId { get; set; }
}

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType> <!-- Сделали библиотеку -->
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
<PackageReference Include="System.Net.Http.Json" Version="10.0.0-preview.3.25171.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\domain\domain.csproj" />
</ItemGroup>
</Project>

View File

@ -57,7 +57,7 @@ namespace Demo.UI
}
private void DisplayMenu() {
Console.WriteLine("0) Вывести всё о группе\n1) Вывести все группы\n2) Cоздать новую группу\n3) Обновить имя группы\n4) Вывести группу по ID\n5) Удалить группу по ID\n6) Вывести всех юзеров\n7) Вывести юзера по GUID\n8) Обновить юзера по GUID\n9) Удалить юзера по GUID\n10) Тебе этого не надо\n11) Вывести посещаемость по группе\n12) Вывести посещаемость по группе и времени\n13) Генерация посещаемости\n14) Генерация посещаемости на неделю\n15) Смена посещаемости юзера");
Console.WriteLine("0) Вывести всё о группе\n1) Вывести все группы\n2) Cоздать новую группу\n3) Обновить имя группы\n4) Вывести группу по ID\n5) Удалить группу по ID\n6) Вывести всех юзеров\n7) Вывести юзера по GUID\n8) Обновить юзера по GUID\n9) Удалить юзера по GUID\n10) Тебе этого не надо\n11) Вывести посещаемость по группе\n12) Вывести посещаемость по группе и времени\n13) Генерация посещаемости\n14) Генерация посещаемости на неделю\n15) Смена посещаемости юзера\n16)Создать пользователя");
while (true)
{
switch (Console.ReadLine())
@ -95,6 +95,9 @@ namespace Demo.UI
_presenceConsoleUI.GeneratePresenceWeek(Convert.ToInt32(Console.ReadLine()), Convert.ToInt32(Console.ReadLine()), Convert.ToInt32(Console.ReadLine()), DateOnly.FromDateTime(Convert.ToDateTime(Console.ReadLine()))); break;
case "15": Console.WriteLine("Введите первый и последний урок, дату и Guid юзера");
_presenceConsoleUI.IsAttedance(Convert.ToInt32(Console.ReadLine()), Convert.ToInt32(Console.ReadLine()), DateOnly.FromDateTime(Convert.ToDateTime(Console.ReadLine())), Guid.Parse(Console.ReadLine())); break;
// case "16": Console.WriteLine("Введите фио и название группы пользователя");
// _userConsoleUI.CreateUser(Console.ReadLine(), Convert.ToInt32(Console.ReadLine()), Console.ReadLine()); break;
default: DisplayMenu();
break;

View File

@ -49,5 +49,24 @@ namespace Demo.UI
userOutput.AppendLine($"{output.Guid}\t{output.FIO}\t{output.Group.Name}");
Console.WriteLine(userOutput);
}
// public void CreateUser(string fio, int id, string groupName)
// {
// Group resgr = new Group
// {
// ID = id,
// Name = groupName,
// };
//
// User res = new User
// {
// FIO = fio,
// Group = resgr
// };
// Boolean output = _userUseCase.CreateUser(res);
// StringBuilder userOutput = new StringBuilder();
// userOutput.AppendLine(output.ToString());
// Console.WriteLine(userOutput);
// }
}
}