diff --git a/Presence.API/Controllers/PresenceController.cs b/Presence.API/Controllers/PresenceController.cs index aa37e4b..79c6e22 100644 --- a/Presence.API/Controllers/PresenceController.cs +++ b/Presence.API/Controllers/PresenceController.cs @@ -1,6 +1,7 @@ using domain.UseCase; using Microsoft.AspNetCore.Mvc; using Presence.API.Response; +using domain.Request; using System; using System.Linq; @@ -18,10 +19,10 @@ namespace Presence.API.Controllers } [HttpGet("/presence/{groupId}")] - public ActionResult GetPresence( + public ActionResult> GetPresence( int groupId, [FromQuery] int? subject, - [FromQuery] DateTime? date, + [FromQuery] DateOnly? date, [FromQuery] int? student) { var presences = _presenceUseCase.GetPresence(groupId, subject, date, student) @@ -32,11 +33,69 @@ namespace Presence.API.Controllers StudentName = $"{p.Student.LastName} {p.Student.FirstName} {p.Student.Patronymic}", SubjectId = p.SubjectId, SubjectName = p.Subject.Name, + TrafficId = p.TrafficId, + TrafficName = p.Traffic.Name, + LessonNumber = p.LessonNumber, Date = p.Date }) .ToList(); return Ok(presences); } + + [HttpPost("/presence")] + public IActionResult AddPresenceRecords([FromBody] AddPresenceRequest request) + { + if (!ModelState.IsValid) + return BadRequest(ModelState); + + try + { + _presenceUseCase.AddPresenceRecords(request); + return Ok(); + } + catch (ArgumentException ex) + { + return BadRequest(ex.Message); + } + } + + [HttpPut("/presence")] + public IActionResult UpdatePresenceRecords([FromBody] UpdatePresenceRequest request) + { + if (!ModelState.IsValid) + return BadRequest(ModelState); + + try + { + _presenceUseCase.UpdatePresenceRecords(request); + return Ok(); + } + catch (ArgumentException ex) + { + return BadRequest(ex.Message); + } + } + + [HttpDelete("/presence")] + public IActionResult DeleteAllPresence() + { + _presenceUseCase.DeleteAllPresence(); + return NoContent(); + } + + [HttpDelete("/presence/group")] + public IActionResult DeleteGroupPresence([FromQuery] int? group) + { + if (!group.HasValue) + { + _presenceUseCase.DeleteAllPresence(); + } + else + { + _presenceUseCase.DeleteGroupPresence(group.Value); + } + return NoContent(); + } } } diff --git a/Presence.API/Response/PresenceResponse.cs b/Presence.API/Response/PresenceResponse.cs index c3da8f6..d9865c3 100644 --- a/Presence.API/Response/PresenceResponse.cs +++ b/Presence.API/Response/PresenceResponse.cs @@ -9,6 +9,9 @@ namespace Presence.API.Response public string StudentName { get; set; } public int SubjectId { get; set; } public string SubjectName { get; set; } + public int TrafficId { get; set; } + public string TrafficName { get; set; } + public int LessonNumber { get; set; } public DateOnly Date { get; set; } } } diff --git a/Presence.Desktop/App.axaml b/Presence.Desktop/App.axaml new file mode 100644 index 0000000..eea6afa --- /dev/null +++ b/Presence.Desktop/App.axaml @@ -0,0 +1,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Presence.Desktop/App.axaml.cs b/Presence.Desktop/App.axaml.cs new file mode 100644 index 0000000..4960713 --- /dev/null +++ b/Presence.Desktop/App.axaml.cs @@ -0,0 +1,36 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using Microsoft.Extensions.DependencyInjection; +using Presence.Desktop.DI; +using Presence.Desktop.ViewModels; +using Presence.Desktop.Views; + +namespace Presence.Desktop +{ + public partial class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddComonoServices(); + var services = serviceCollection.BuildServiceProvider(); + MainWindowViewModel mainViewModel = services.GetRequiredService(); + + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow + { + DataContext = mainViewModel, + }; + } + + base.OnFrameworkInitializationCompleted(); + } + } +} \ No newline at end of file diff --git a/Presence.Desktop/Assets/avalonia-logo.ico b/Presence.Desktop/Assets/avalonia-logo.ico new file mode 100644 index 0000000..da8d49f Binary files /dev/null and b/Presence.Desktop/Assets/avalonia-logo.ico differ diff --git a/Presence.Desktop/DI/ServiceCollectionExtension.cs b/Presence.Desktop/DI/ServiceCollectionExtension.cs new file mode 100644 index 0000000..acbbc27 --- /dev/null +++ b/Presence.Desktop/DI/ServiceCollectionExtension.cs @@ -0,0 +1,21 @@ +using data; +using data.Repository; +using domain.UseCase; +using domain.Service; +using Microsoft.Extensions.DependencyInjection; +using Presence.Desktop.ViewModels; + +namespace Presence.Desktop.DI +{ + public static class ServiceCollectionExtension + { + public static void AddComonoServices(this IServiceCollection collection) + { + collection + .AddDbContext() + .AddSingleton() + .AddTransient() + .AddTransient(); + } + } +} diff --git a/Presence.Desktop/Models/GroupPresenter.cs b/Presence.Desktop/Models/GroupPresenter.cs new file mode 100644 index 0000000..aad4c03 --- /dev/null +++ b/Presence.Desktop/Models/GroupPresenter.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Presence.Desktop.Models +{ + public class GroupPresenter + { + public int Id { get; set; } + public string Name { get; set; } + public IEnumerable? students = null; + } +} diff --git a/Presence.Desktop/Models/StudentPresenter.cs b/Presence.Desktop/Models/StudentPresenter.cs new file mode 100644 index 0000000..62f3c09 --- /dev/null +++ b/Presence.Desktop/Models/StudentPresenter.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Presence.Desktop.Models +{ + public class StudentPresenter + { + public int Id { get; set; } + + public string LastName { get; set; } + + public string FirstName { get; set; } + + public string Patronymic { get; set; } + + public GroupPresenter Group { get; set; } + } +} diff --git a/Presence.Desktop/Presence.Desktop.csproj b/Presence.Desktop/Presence.Desktop.csproj new file mode 100644 index 0000000..a7928e1 --- /dev/null +++ b/Presence.Desktop/Presence.Desktop.csproj @@ -0,0 +1,33 @@ + + + WinExe + net8.0 + enable + true + app.manifest + true + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Presence.Desktop/Program.cs b/Presence.Desktop/Program.cs new file mode 100644 index 0000000..9d4a474 --- /dev/null +++ b/Presence.Desktop/Program.cs @@ -0,0 +1,24 @@ +using Avalonia; +using Avalonia.ReactiveUI; +using System; + +namespace Presence.Desktop +{ + internal 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() + .UseReactiveUI(); + } +} diff --git a/Presence.Desktop/ViewLocator.cs b/Presence.Desktop/ViewLocator.cs new file mode 100644 index 0000000..1e7c521 --- /dev/null +++ b/Presence.Desktop/ViewLocator.cs @@ -0,0 +1,34 @@ +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using Presence.Desktop.ViewModels; +using System; + +namespace Presence.Desktop +{ + public class ViewLocator : IDataTemplate + { + + public Control? Build(object? data) + { + if (data is null) + return null; + + var name = data.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); + var type = Type.GetType(name); + + if (type != null) + { + var control = (Control)Activator.CreateInstance(type)!; + control.DataContext = data; + return control; + } + + return new TextBlock { Text = "Not Found: " + name }; + } + + public bool Match(object? data) + { + return data is ViewModelBase; + } + } +} diff --git a/Presence.Desktop/ViewModels/MainWindowViewModel.cs b/Presence.Desktop/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000..f024e49 --- /dev/null +++ b/Presence.Desktop/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,89 @@ +using domain.UseCase; +using Presence.Desktop.Models; +using System; +using System.Linq; +using ReactiveUI; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Presence.Desktop.ViewModels +{ + public class MainWindowViewModel : ViewModelBase + { + private readonly IGroupUseCase _groupService; + private List groupPresenters = new List(); + private ObservableCollection _groups; + public ObservableCollection Groups + { + get => _groups; + set => this.RaiseAndSetIfChanged(ref _groups, value); + } + + private GroupPresenter? _selectedGroupItem; + public GroupPresenter? SelectedGroupItem + { + get => _selectedGroupItem; + set => this.RaiseAndSetIfChanged(ref _selectedGroupItem, value); + } + + private ObservableCollection _students; + public ObservableCollection Students + { + get => _students; + set => this.RaiseAndSetIfChanged(ref _students, value); + } + + public MainWindowViewModel() + { + _groupService = null; + _groups = new(); + _students = new(); + } + + public MainWindowViewModel(IGroupUseCase gService, IGroupUseCase groupService, ObservableCollection groups, ObservableCollection students) + { + _groupService = gService; + + foreach (var item in _groupService.GetGroupsWithStudents()) + { + GroupPresenter groupPresenter = new GroupPresenter + { + Id = item.Id, + Name = item.Name, + students = item.Users?.Select(u => new StudentPresenter + { + Id = u.Id, + FirstName = u.FirstName, + LastName = u.LastName, + Patronymic = u.Patronymic, + Group = new GroupPresenter + { + Id = item.Id, + Name = item.Name + } + }) + }; + groupPresenters.Add(groupPresenter); + } + + _groups = new(groupPresenters); + + _students = new(); + + this.WhenAnyValue(vm => vm.SelectedGroupItem) + .Subscribe(_ => SetStudents()); + } + + private void SetStudents() + { + if (SelectedGroupItem == null) return; + if (SelectedGroupItem.students == null) return; + + Students.Clear(); + + foreach (var student in SelectedGroupItem.students) + Students.Add(student); + } + + } +} diff --git a/Presence.Desktop/ViewModels/ViewModelBase.cs b/Presence.Desktop/ViewModels/ViewModelBase.cs new file mode 100644 index 0000000..66c82c5 --- /dev/null +++ b/Presence.Desktop/ViewModels/ViewModelBase.cs @@ -0,0 +1,8 @@ +using ReactiveUI; + +namespace Presence.Desktop.ViewModels +{ + public class ViewModelBase : ReactiveObject + { + } +} diff --git a/Presence.Desktop/Views/MainWindow.axaml b/Presence.Desktop/Views/MainWindow.axaml new file mode 100644 index 0000000..655327b --- /dev/null +++ b/Presence.Desktop/Views/MainWindow.axaml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Presence.Desktop/Views/MainWindow.axaml.cs b/Presence.Desktop/Views/MainWindow.axaml.cs new file mode 100644 index 0000000..2619e70 --- /dev/null +++ b/Presence.Desktop/Views/MainWindow.axaml.cs @@ -0,0 +1,12 @@ +using Avalonia.Controls; + +namespace Presence.Desktop.Views +{ + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/Presence.Desktop/app.manifest b/Presence.Desktop/app.manifest new file mode 100644 index 0000000..9c877cb --- /dev/null +++ b/Presence.Desktop/app.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/data/DAO/Diary.cs b/data/DAO/Diary.cs index d2269ba..fac764e 100644 --- a/data/DAO/Diary.cs +++ b/data/DAO/Diary.cs @@ -12,12 +12,14 @@ namespace data.DAO [Key] public int Id { get; set; } - public string DiaryText { get; set; } + public string? DiaryText { get; set; } public int TrafficId { get; set; } public int StudentGroupSubjectId { get; set; } + public int StudentId { get; set; } + public virtual Traffic Traffic { get; set; } public virtual StudentGroupSubject StudentGroupSubject { get; set; } diff --git a/data/Repository/IPresenceRepository.cs b/data/Repository/IPresenceRepository.cs index a0ca0a7..61c8599 100644 --- a/data/Repository/IPresenceRepository.cs +++ b/data/Repository/IPresenceRepository.cs @@ -9,7 +9,15 @@ namespace data.Repository IEnumerable GetPresence( int groupId, int? subjectId = null, - DateTime? date = null, + DateOnly? date = null, int? studentId = null); + + public void DeleteAllPresence(); + + public void DeleteGroupPresence(int groupId); + + public void AddPresenceRecords(List items); + + public void UpdatePresenceRecords(List items); } } diff --git a/data/Repository/SQLPresenceRepository.cs b/data/Repository/SQLPresenceRepository.cs index 211d1d0..ca7e4b1 100644 --- a/data/Repository/SQLPresenceRepository.cs +++ b/data/Repository/SQLPresenceRepository.cs @@ -1,7 +1,7 @@ using data.DAO; using Microsoft.EntityFrameworkCore; using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; namespace data.Repository @@ -18,25 +18,66 @@ namespace data.Repository public IEnumerable GetPresence( int groupId, int? subjectId = null, - DateTime? date = null, + DateOnly? date = null, int? studentId = null) { var query = _context.Diaries .Include(p => p.Student) .Include(p => p.StudentGroupSubject) .ThenInclude(p => p.Subject) + .Include(p => p.Traffic) .Where(p => p.Student.GroupId == groupId); if (subjectId.HasValue) query = query.Where(p => p.StudentGroupSubject.Subject.Id == subjectId.Value); if (date.HasValue) - query = query.Where(p => p.Date == DateOnly.FromDateTime(date.Value)); + query = query.Where(p => p.Date == date.Value); if (studentId.HasValue) query = query.Where(p => p.Student.Id == studentId.Value); return query.ToList(); } + + public void DeleteAllPresence() + { + _context.Diaries.RemoveRange(_context.Diaries); + _context.SaveChanges(); + } + + public void DeleteGroupPresence(int groupId) + { + var presencesToDelete = _context.Diaries + .Include(p => p.Student) + .Where(p => p.Student.GroupId == groupId); + + _context.Diaries.RemoveRange(presencesToDelete); + _context.SaveChanges(); + } + + public void AddPresenceRecords(List items) + { + _context.Diaries.AddRange(items); + _context.SaveChanges(); + } + + public void UpdatePresenceRecords(List items) + { + foreach (var item in items) + { + var existingRecord = _context.Diaries + .FirstOrDefault(p => + p.Date == item.Date && + p.NumberSubject == item.NumberSubject && + p.StudentId == item.StudentId); + + if (existingRecord != null) + { + existingRecord.TrafficId = item.TrafficId; + } + } + _context.SaveChanges(); + } } } diff --git a/domain/Entity/PresenceEntity.cs b/domain/Entity/PresenceEntity.cs index 3a15756..1b8f3d6 100644 --- a/domain/Entity/PresenceEntity.cs +++ b/domain/Entity/PresenceEntity.cs @@ -9,6 +9,9 @@ namespace domain.Entity public UserEntity Student { get; set; } public int SubjectId { get; set; } public SubjectEntity Subject { get; set; } + public int TrafficId { get; set; } + public TrafficEntity Traffic { get; set; } + public int LessonNumber { get; set; } public DateOnly Date { get; set; } } } diff --git a/domain/Entity/TrafficEntity.cs b/domain/Entity/TrafficEntity.cs new file mode 100644 index 0000000..ac2a8f3 --- /dev/null +++ b/domain/Entity/TrafficEntity.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace domain.Entity +{ + public class TrafficEntity + { + public int Id { get; set; } + public string Name { get; set; } + } +} diff --git a/domain/Request/AddPresenceRequest.cs b/domain/Request/AddPresenceRequest.cs new file mode 100644 index 0000000..57bb1cb --- /dev/null +++ b/domain/Request/AddPresenceRequest.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace domain.Request +{ + public class AddPresenceRequest + { + public List Items { get; set; } = new List(); + } + + public class PresenceItem + { + public DateOnly Date { get; set; } + public int StudentGroupSubjectId { get; set; } + public int LessonNumber { get; set; } + public int StudentId { get; set; } + public int TrafficId { get; set; } + } +} diff --git a/domain/Request/UpdatePresenceRequest.cs b/domain/Request/UpdatePresenceRequest.cs new file mode 100644 index 0000000..4afe183 --- /dev/null +++ b/domain/Request/UpdatePresenceRequest.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace domain.Request +{ + public class UpdatePresenceRequest + { + public List Items { get; set; } = new List(); + } + + public class UpdatePresenceItem + { + public DateOnly Date { get; set; } + public int LessonNumber { get; set; } + public int StudentId { get; set; } + public int TrafficId { get; set; } + } +} diff --git a/domain/Service/PresenceService.cs b/domain/Service/PresenceService.cs index 0f9347a..aa91511 100644 --- a/domain/Service/PresenceService.cs +++ b/domain/Service/PresenceService.cs @@ -1,5 +1,7 @@ +using data.DAO; using data.Repository; using domain.Entity; +using domain.Request; using domain.UseCase; using System; using System.Collections.Generic; @@ -18,7 +20,7 @@ namespace domain.Service public IEnumerable GetPresence( int groupId, int? subjectId = null, - DateTime? date = null, + DateOnly? date = null, int? studentId = null) { return _presenceRepository.GetPresence(groupId, subjectId, date, studentId).Select(p => new PresenceEntity @@ -39,8 +41,69 @@ namespace domain.Service Id = p.StudentGroupSubject.Subject.Id, Name = p.StudentGroupSubject.Subject.Name }, + TrafficId = p.TrafficId, + Traffic = new TrafficEntity + { + Id = p.TrafficId, + Name = p.Traffic.Name + }, + LessonNumber = p.NumberSubject, Date = p.Date }); } + + public void DeleteAllPresence() + { + _presenceRepository.DeleteAllPresence(); + } + + public void DeleteGroupPresence(int groupId) + { + _presenceRepository.DeleteGroupPresence(groupId); + } + + public void AddPresenceRecords(AddPresenceRequest request) + { + if (request?.Items == null || request.Items.Count == 0) + throw new ArgumentException("No records"); + + List diaries = new List(); + + foreach (var item in request.Items) + { + diaries.Add(new Diary + { + Date = item.Date, + DiaryText = "", + NumberSubject = item.LessonNumber, + StudentId = item.StudentId, + TrafficId = item.TrafficId, + StudentGroupSubjectId = item.StudentGroupSubjectId + }); + } + + _presenceRepository.AddPresenceRecords(diaries); + } + + public void UpdatePresenceRecords(UpdatePresenceRequest request) + { + if (request?.Items == null || request.Items.Count == 0) + throw new ArgumentException("No records"); + + List diaries = new List(); + + foreach (var item in request.Items) + { + diaries.Add(new Diary + { + Date = item.Date, + NumberSubject = item.LessonNumber, + StudentId = item.StudentId, + TrafficId = item.TrafficId, + }); + } + + _presenceRepository.UpdatePresenceRecords(diaries); + } } } diff --git a/domain/UseCase/IPresenceUseCase.cs b/domain/UseCase/IPresenceUseCase.cs index b4cd83b..a8437db 100644 --- a/domain/UseCase/IPresenceUseCase.cs +++ b/domain/UseCase/IPresenceUseCase.cs @@ -1,4 +1,5 @@ using domain.Entity; +using domain.Request; using System; using System.Collections.Generic; @@ -9,7 +10,12 @@ namespace domain.UseCase IEnumerable GetPresence( int groupId, int? subjectId = null, - DateTime? date = null, + DateOnly? date = null, int? studentId = null); + + void DeleteAllPresence(); + void DeleteGroupPresence(int groupId); + void AddPresenceRecords(AddPresenceRequest request); + void UpdatePresenceRecords(UpdatePresenceRequest request); } } diff --git a/domain/domain.csproj b/domain/domain.csproj index f2296ed..98549a9 100644 --- a/domain/domain.csproj +++ b/domain/domain.csproj @@ -7,7 +7,9 @@ - + + + diff --git a/presence.sln b/presence.sln index 10e1507..26befdc 100644 --- a/presence.sln +++ b/presence.sln @@ -9,7 +9,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "console_ui", "console_ui\co EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "domain", "domain\domain.csproj", "{4D39120E-C021-4D38-BB4D-2F58BDE5FE17}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Presence.API", "Presence.API\Presence.API.csproj", "{88AE15FA-7E2D-4D7C-A75B-7DAC9742B32A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Presence.API", "Presence.API\Presence.API.csproj", "{88AE15FA-7E2D-4D7C-A75B-7DAC9742B32A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Presence.Desktop", "Presence.Desktop\Presence.Desktop.csproj", "{03BA523C-7C5B-4FE1-BAC1-461E4B4F7FCD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -33,6 +35,10 @@ Global {88AE15FA-7E2D-4D7C-A75B-7DAC9742B32A}.Debug|Any CPU.Build.0 = Debug|Any CPU {88AE15FA-7E2D-4D7C-A75B-7DAC9742B32A}.Release|Any CPU.ActiveCfg = Release|Any CPU {88AE15FA-7E2D-4D7C-A75B-7DAC9742B32A}.Release|Any CPU.Build.0 = Release|Any CPU + {03BA523C-7C5B-4FE1-BAC1-461E4B4F7FCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03BA523C-7C5B-4FE1-BAC1-461E4B4F7FCD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03BA523C-7C5B-4FE1-BAC1-461E4B4F7FCD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03BA523C-7C5B-4FE1-BAC1-461E4B4F7FCD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE