diff --git a/CourseProject1125/App.axaml b/CourseProject1125/App.axaml index 0ef9d74..39203a1 100644 --- a/CourseProject1125/App.axaml +++ b/CourseProject1125/App.axaml @@ -2,8 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="CourseProject1125.App" xmlns:local="using:CourseProject1125" - RequestedThemeVariant="Default"> - + RequestedThemeVariant="Light"> diff --git a/CourseProject1125/App.axaml.cs b/CourseProject1125/App.axaml.cs index 3478f46..6bd3ccb 100644 --- a/CourseProject1125/App.axaml.cs +++ b/CourseProject1125/App.axaml.cs @@ -33,10 +33,7 @@ public partial class App : Application // Avoid duplicate validations from both Avalonia and the CommunityToolkit. // More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins DisableAvaloniaDataAnnotationValidation(); - desktop.MainWindow = new MainWindow - { - DataContext = new MainWindowViewModel(), - }; + desktop.MainWindow = new MainWindow(); } base.OnFrameworkInitializationCompleted(); diff --git a/CourseProject1125/DB/Connection.cs b/CourseProject1125/DB/Connection.cs index db99814..3c05dbd 100644 --- a/CourseProject1125/DB/Connection.cs +++ b/CourseProject1125/DB/Connection.cs @@ -3,5 +3,5 @@ namespace CourseProject1125.DB; public class Connection { public static string ConnectionString = - @"server=127.0.0.1; userid=root; password=; database=courseproject1125; port=3306; characterset=utf8"; + @"server=192.168.200.13; userid=student; password=student; database=1125_CourseSolop; port=3306; characterset=utf8; Convert Zero Datetime=True"; } \ No newline at end of file diff --git a/CourseProject1125/DB/DatabaseService.cs b/CourseProject1125/DB/DatabaseService.cs index 5310341..01e365d 100644 --- a/CourseProject1125/DB/DatabaseService.cs +++ b/CourseProject1125/DB/DatabaseService.cs @@ -517,8 +517,8 @@ public static class DatabaseService FROM loans l JOIN book_copies c ON l.copy_id = c.id JOIN books b ON c.book_id = b.id - JOIN users r ON l.reader_id = r.id - JOIN users lib ON l.librarian_id = lib.id + LEFT JOIN users r ON l.reader_id = r.id + LEFT JOIN users lib ON l.librarian_id = lib.id ORDER BY l.issue_date DESC"; using var cmd = new MySqlCommand(sql, conn); using var reader = cmd.ExecuteReader(); @@ -542,19 +542,19 @@ public static class DatabaseService var readerUser = new User { - Id = Convert.ToInt32(reader["reader_id"]), - Full_name = reader["reader_name"].ToString() ?? "", - Email = reader["reader_email"].ToString() ?? "" + Id = reader["reader_id"] != DBNull.Value ? Convert.ToInt32(reader["reader_id"]) : 0, + Full_name = reader["reader_name"]?.ToString() ?? "", + Email = reader["reader_email"]?.ToString() ?? "" }; var librarianUser = new User { - Id = Convert.ToInt32(reader["librarian_id"]), - Full_name = reader["librarian_name"].ToString() ?? "" + Id = reader["librarian_id"] != DBNull.Value ? Convert.ToInt32(reader["librarian_id"]) : 0, + Full_name = reader["librarian_name"]?.ToString() ?? "" }; var returnDateVal = reader["return_date"]; - DateTime retDate = DateTime.MinValue; + DateTime? retDate = null; if (returnDateVal != DBNull.Value) { // Check if it's '0000-00-00 00:00:00' or default MySQL empty timestamp @@ -612,6 +612,18 @@ public static class DatabaseService cmdUpdate.Parameters.AddWithValue("@copyId", copyId); cmdUpdate.ExecuteNonQuery(); + // 3. Deactivate reservation for this reader for the issued book if it exists + string sqlDeactivateReservation = @" + UPDATE reservations + SET is_active = 0 + WHERE reader_id = @readerId + AND is_active = 1 + AND book_id = (SELECT book_id FROM book_copies WHERE id = @copyId LIMIT 1)"; + using var cmdDeactivateRes = new MySqlCommand(sqlDeactivateReservation, conn, tr); + cmdDeactivateRes.Parameters.AddWithValue("@readerId", readerId); + cmdDeactivateRes.Parameters.AddWithValue("@copyId", copyId); + cmdDeactivateRes.ExecuteNonQuery(); + tr.Commit(); return true; } @@ -623,6 +635,24 @@ public static class DatabaseService } } + public static bool RequestBookReturn(int loanId) + { + try + { + using var conn = GetConnection(); + conn.Open(); + string sql = "UPDATE loans SET status = 'pending_return' WHERE id = @loanId"; + using var cmd = new MySqlCommand(sql, conn); + cmd.Parameters.AddWithValue("@loanId", loanId); + return cmd.ExecuteNonQuery() > 0; + } + catch (Exception ex) + { + Console.WriteLine("Error in RequestBookReturn: " + ex.Message); + return false; + } + } + public static decimal ReturnLoan(int loanId, DateTime returnDate) { MySqlTransaction? tr = null; @@ -712,12 +742,13 @@ public static class DatabaseService string sql = @" SELECT f.id, f.loan_id, f.amount, f.is_paid, f.created_at, b.title AS book_title, r.full_name AS reader_name, - l.issue_date, l.due_date, l.return_date, l.status AS loan_status + l.issue_date, l.due_date, l.return_date, l.status AS loan_status, + l.reader_id FROM fines f - JOIN loans l ON f.loan_id = l.id - JOIN book_copies c ON l.copy_id = c.id - JOIN books b ON c.book_id = b.id - JOIN users r ON l.reader_id = r.id + LEFT JOIN loans l ON f.loan_id = l.id + LEFT JOIN book_copies c ON l.copy_id = c.id + LEFT JOIN books b ON c.book_id = b.id + LEFT JOIN users r ON l.reader_id = r.id ORDER BY f.created_at DESC"; using var cmd = new MySqlCommand(sql, conn); using var reader = cmd.ExecuteReader(); @@ -725,7 +756,7 @@ public static class DatabaseService { var book = new Book { - Title = reader["book_title"].ToString() ?? "" + Title = reader["book_title"]?.ToString() ?? "" }; var copy = new Book_copies @@ -735,11 +766,12 @@ public static class DatabaseService var readerUser = new User { - Full_name = reader["reader_name"].ToString() ?? "" + Id = reader["reader_id"] != DBNull.Value ? Convert.ToInt32(reader["reader_id"]) : 0, + Full_name = reader["reader_name"]?.ToString() ?? "" }; var returnDateVal = reader["return_date"]; - DateTime retDate = DateTime.MinValue; + DateTime? retDate = null; if (returnDateVal != DBNull.Value) { string strVal = returnDateVal.ToString() ?? ""; @@ -751,10 +783,10 @@ public static class DatabaseService var loan = new Loan { - Id = Convert.ToInt32(reader["loan_id"]), - Status = reader["loan_status"].ToString() ?? "", - Issue_date = Convert.ToDateTime(reader["issue_date"]), - Due_date = Convert.ToDateTime(reader["due_date"]), + Id = reader["loan_id"] != DBNull.Value ? Convert.ToInt32(reader["loan_id"]) : 0, + Status = reader["loan_status"]?.ToString() ?? "", + Issue_date = reader["issue_date"] != DBNull.Value ? Convert.ToDateTime(reader["issue_date"]) : DateTime.MinValue, + Due_date = reader["due_date"] != DBNull.Value ? Convert.ToDateTime(reader["due_date"]) : DateTime.MinValue, Return_date = retDate, CopyId = copy, ReaderId = readerUser diff --git a/CourseProject1125/Models/Book_copies.cs b/CourseProject1125/Models/Book_copies.cs index 75300d2..752f2d6 100644 --- a/CourseProject1125/Models/Book_copies.cs +++ b/CourseProject1125/Models/Book_copies.cs @@ -7,5 +7,11 @@ public class Book_copies public string Status { get; set; } public string Shelf_location { get; set; } public Book BookId { get; set; } - + public string StatusText => Status switch + { + "available" => "Доступна", + "issued" => "Выдана", + "repair" => "В ремонте", + _ => Status + }; } \ No newline at end of file diff --git a/CourseProject1125/Models/Fine.cs b/CourseProject1125/Models/Fine.cs index 5c96707..9010fe7 100644 --- a/CourseProject1125/Models/Fine.cs +++ b/CourseProject1125/Models/Fine.cs @@ -9,4 +9,5 @@ public class Fine public int Is_paid { get; set; } public DateTime Created_at { get; set; } public Loan LoanId { get; set; } + public string StatusText => Is_paid == 1 ? "Оплачен" : "Не оплачен"; } \ No newline at end of file diff --git a/CourseProject1125/Models/Loan.cs b/CourseProject1125/Models/Loan.cs index c3e3bfb..1ff1997 100644 --- a/CourseProject1125/Models/Loan.cs +++ b/CourseProject1125/Models/Loan.cs @@ -8,9 +8,15 @@ public class Loan public string Status { get; set; } public DateTime Issue_date { get; set; } public DateTime Due_date { get; set; } - public DateTime Return_date { get; set; } + public DateTime? Return_date { get; set; } public Book_copies CopyId { get; set; } public User ReaderId { get; set; } public User LibrarianId { get; set; } - + public string StatusText => Status switch + { + "active" => "На руках", + "pending_return" => "Ожидает возврата", + "returned" => "Возвращена", + _ => Status + }; } \ No newline at end of file diff --git a/CourseProject1125/Program.cs b/CourseProject1125/Program.cs index 6165eac..19a4212 100644 --- a/CourseProject1125/Program.cs +++ b/CourseProject1125/Program.cs @@ -10,20 +10,15 @@ namespace CourseProject1125; 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) { - var host = Host.CreateDefaultBuilder(args) // Добавили args для порядка - // --- ДОБАВЬ ЭТОТ БЛОК --- + var host = Host.CreateDefaultBuilder(args) .ConfigureLogging(logging => { - logging.ClearProviders(); // Удаляет EventLog, который не работает на Linux - logging.AddConsole(); // Оставляет вывод в консоль + logging.ClearProviders(); + logging.AddConsole(); }) - // ------------------------ .ConfigureAppConfiguration((context, config) => { config.SetBasePath(AppContext.BaseDirectory) @@ -39,11 +34,46 @@ sealed class Program } }).Build(); + if (args.Length > 0 && args[0] == "--diagnose") + { + RunDiagnostics(); + return; + } + BuildAvaloniaApp(host.Services) .StartWithClassicDesktopLifetime(args); } - // Avalonia configuration, don't remove; also used by visual designer. + private static void RunDiagnostics() + { + try + { + Console.WriteLine("=== DIAGNOSTICS START ==="); + using var conn = new MySqlConnector.MySqlConnection(Connection.ConnectionString); + conn.Open(); + Console.WriteLine("Connected to database successfully!"); + + Console.WriteLine("Printing all users..."); + var users = DatabaseService.GetUsers(); + foreach (var u in users) + { + Console.WriteLine($"- User: {u.Login}, Pass: {u.Password_hash}, RoleId: {u.RoleId?.id} ({u.RoleId?.Name})"); + } + + Console.WriteLine("Testing DatabaseService.GetLoans()..."); + var loans = DatabaseService.GetLoans(); + Console.WriteLine($"GetLoans returned {loans.Count} loans."); + foreach (var l in loans) + { + Console.WriteLine($"- Loan ID: {l.Id}, Book: {l.CopyId?.BookId?.Title}, Reader: {l.ReaderId?.Full_name} (ID: {l.ReaderId?.Id}), Status: {l.Status}"); + } + } + catch (Exception ex) + { + Console.WriteLine("Diagnostics error: " + ex); + } + } + public static AppBuilder BuildAvaloniaApp(IServiceProvider hostServices) => AppBuilder.Configure(() => new App(hostServices)) .UsePlatformDetect() diff --git a/CourseProject1125/ViewModels/AdminWindowViewModel.cs b/CourseProject1125/ViewModels/AdminWindowViewModel.cs new file mode 100644 index 0000000..2249bc4 --- /dev/null +++ b/CourseProject1125/ViewModels/AdminWindowViewModel.cs @@ -0,0 +1,708 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using CourseProject1125.DB; +using CourseProject1125.Models; + +namespace CourseProject1125.ViewModels +{ + public class PopularBookItem + { + public string Title { get; set; } = string.Empty; + public int BorrowCount { get; set; } + } + + public class OverdueLoanItem + { + public string Title { get; set; } = string.Empty; + public string Reader { get; set; } = string.Empty; + public string IssueDate { get; set; } = string.Empty; + public string DueDate { get; set; } = string.Empty; + public int OverdueDays { get; set; } + } + + public partial class AdminWindowViewModel : ViewModelBase + { + private List _allAuthors = new(); + private List _allBooks = new(); + private List _allRoles = new(); + + public Action? OnLogoutRequested { get; set; } + + public string AdminName => UserSession.UserName; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(BooksTabBackground), nameof(BooksTabForeground), nameof(IsBooksVisible))] + [NotifyPropertyChangedFor(nameof(AuthorsTabBackground), nameof(AuthorsTabForeground), nameof(IsAuthorsVisible))] + [NotifyPropertyChangedFor(nameof(CopiesTabBackground), nameof(CopiesTabForeground), nameof(IsCopiesVisible))] + [NotifyPropertyChangedFor(nameof(UsersTabBackground), nameof(UsersTabForeground), nameof(IsUsersVisible))] + [NotifyPropertyChangedFor(nameof(ReportsTabBackground), nameof(ReportsTabForeground), nameof(IsReportsVisible))] + private string _currentTab = "Books"; + + [ObservableProperty] private string _activeTabHeader = "Управление книгами"; + + public string BooksTabBackground => CurrentTab == "Books" ? "LightGray" : "Transparent"; + public string BooksTabForeground => CurrentTab == "Books" ? "Black" : "Gray"; + public bool IsBooksVisible => CurrentTab == "Books"; + + public string AuthorsTabBackground => CurrentTab == "Authors" ? "LightGray" : "Transparent"; + public string AuthorsTabForeground => CurrentTab == "Authors" ? "Black" : "Gray"; + public bool IsAuthorsVisible => CurrentTab == "Authors"; + + public string CopiesTabBackground => CurrentTab == "Copies" ? "LightGray" : "Transparent"; + public string CopiesTabForeground => CurrentTab == "Copies" ? "Black" : "Gray"; + public bool IsCopiesVisible => CurrentTab == "Copies"; + + public string UsersTabBackground => CurrentTab == "Users" ? "LightGray" : "Transparent"; + public string UsersTabForeground => CurrentTab == "Users" ? "Black" : "Gray"; + public bool IsUsersVisible => CurrentTab == "Users"; + + public string ReportsTabBackground => CurrentTab == "Reports" ? "LightGray" : "Transparent"; + public string ReportsTabForeground => CurrentTab == "Reports" ? "Black" : "Gray"; + public bool IsReportsVisible => CurrentTab == "Reports"; + + [ObservableProperty] private List _authorsList = new(); + [ObservableProperty] private Author? _selectedAuthor; + [ObservableProperty] private string _authorName = string.Empty; + [ObservableProperty] private string _authorStatus = string.Empty; + [ObservableProperty] private string _authorStatusColor = "Black"; + + [ObservableProperty] private List _booksList = new(); + [ObservableProperty] private Book? _selectedBook; + [ObservableProperty] private string _bookTitle = string.Empty; + [ObservableProperty] private string _searchBookAuthorText = string.Empty; + [ObservableProperty] private List _filteredBookAuthors = new(); + [ObservableProperty] private Author? _selectedBookAuthor; + [ObservableProperty] private string _bookIsbn = string.Empty; + [ObservableProperty] private string _bookYear = string.Empty; + [ObservableProperty] private string _bookDesc = string.Empty; + [ObservableProperty] private string _bookStatus = string.Empty; + [ObservableProperty] private string _bookStatusColor = "Black"; + + [ObservableProperty] private List _copiesList = new(); + [ObservableProperty] private Book_copies? _selectedCopy; + [ObservableProperty] private string _searchCopyBookText = string.Empty; + [ObservableProperty] private List _filteredCopyBooks = new(); + [ObservableProperty] private Book? _selectedCopyBook; + [ObservableProperty] private string _copyInv = string.Empty; + [ObservableProperty] private string _copyStatusField = string.Empty; + [ObservableProperty] private string _copyShelf = string.Empty; + [ObservableProperty] private string _copyStatus = string.Empty; + [ObservableProperty] private string _copyStatusColor = "Black"; + + [ObservableProperty] private List _usersList = new(); + [ObservableProperty] private User? _selectedUser; + [ObservableProperty] private string _userFullName = string.Empty; + [ObservableProperty] private string _searchUserRoleText = string.Empty; + [ObservableProperty] private List _filteredUserRoles = new(); + [ObservableProperty] private Role? _selectedUserRole; + [ObservableProperty] private string _userLogin = string.Empty; + [ObservableProperty] private string _userPass = string.Empty; + [ObservableProperty] private string _userEmail = string.Empty; + [ObservableProperty] private string _userPhone = string.Empty; + [ObservableProperty] private string _userStatus = string.Empty; + [ObservableProperty] private string _userStatusColor = "Black"; + + [ObservableProperty] private string _finesCollected = "0.00 руб."; + [ObservableProperty] private string _finesUnpaid = "0.00 руб."; + [ObservableProperty] private List _popularBooksList = new(); + [ObservableProperty] private List _overdueLoansList = new(); + + public AdminWindowViewModel() + { + LoadAllData(); + ShowTab("Books"); + } + + private void LoadAllData() + { + RefreshAuthors(); + RefreshBooks(); + RefreshCopies(); + RefreshUsers(); + RefreshReports(); + } + + [RelayCommand] + private void ShowTab(string tabName) + { + CurrentTab = tabName; + + switch (tabName) + { + case "Books": + ActiveTabHeader = "Управление книгами"; + RefreshBooks(); + break; + case "Authors": + ActiveTabHeader = "Управление авторами"; + RefreshAuthors(); + break; + case "Copies": + ActiveTabHeader = "Управление экземплярами"; + RefreshCopies(); + break; + case "Users": + ActiveTabHeader = "Управление пользователями"; + RefreshUsers(); + break; + case "Reports": + ActiveTabHeader = "Аналитические отчеты"; + RefreshReports(); + break; + } + } + + [RelayCommand] + private void Logout() + { + OnLogoutRequested?.Invoke(); + } + + private void RefreshAuthors() + { + _allAuthors = DatabaseService.GetAuthors(); + AuthorsList = _allAuthors; + FilterBookAuthors(); + } + + partial void OnSelectedAuthorChanged(Author? value) + { + if (value != null) + { + AuthorName = value.Name; + } + } + + [RelayCommand] + private void AddAuthor() + { + string name = AuthorName?.Trim() ?? string.Empty; + if (string.IsNullOrEmpty(name)) + { + AuthorStatusColor = "Red"; + AuthorStatus = "Имя автора не может быть пустым."; + return; + } + + if (DatabaseService.AddAuthor(name)) + { + RefreshAuthors(); + ClearAuthor(); + AuthorStatusColor = "Green"; + AuthorStatus = "Автор добавлен!"; + } + else + { + AuthorStatusColor = "Red"; + AuthorStatus = "Ошибка добавления автора."; + } + } + + [RelayCommand] + private void EditAuthor() + { + if (SelectedAuthor == null) + { + AuthorStatusColor = "Red"; + AuthorStatus = "Выберите автора для редактирования."; + return; + } + string name = AuthorName?.Trim() ?? string.Empty; + if (string.IsNullOrEmpty(name)) return; + + if (DatabaseService.UpdateAuthor(SelectedAuthor.Id, name)) + { + RefreshAuthors(); + ClearAuthor(); + AuthorStatusColor = "Green"; + AuthorStatus = "Автор обновлен!"; + } + else + { + AuthorStatusColor = "Red"; + AuthorStatus = "Ошибка обновления автора."; + } + } + + [RelayCommand] + private void DeleteAuthor() + { + if (SelectedAuthor == null) return; + if (DatabaseService.DeleteAuthor(SelectedAuthor.Id)) + { + RefreshAuthors(); + ClearAuthor(); + AuthorStatusColor = "Green"; + AuthorStatus = "Автор удален!"; + } + else + { + AuthorStatusColor = "Red"; + AuthorStatus = "Ошибка удаления автора (возможно, он связан с книгами)."; + } + } + + [RelayCommand] + private void ClearAuthor() + { + SelectedAuthor = null; + AuthorName = string.Empty; + AuthorStatus = string.Empty; + } + + private void RefreshBooks() + { + _allBooks = DatabaseService.GetBooks(); + BooksList = _allBooks; + FilterCopyBooks(); + } + + partial void OnSearchBookAuthorTextChanged(string value) + { + FilterBookAuthors(); + } + + private void FilterBookAuthors() + { + string filter = SearchBookAuthorText?.Trim() ?? string.Empty; + if (string.IsNullOrEmpty(filter)) + { + FilteredBookAuthors = _allAuthors.Take(20).ToList(); + } + else + { + FilteredBookAuthors = _allAuthors.Where(a => + a.Name.Contains(filter, StringComparison.OrdinalIgnoreCase) + ).ToList(); + } + } + + partial void OnSelectedBookChanged(Book? value) + { + if (value != null) + { + BookTitle = value.Title; + BookIsbn = value.Isbn; + BookYear = value.Year_published.ToString(); + BookDesc = value.Description; + + SearchBookAuthorText = value.AuthorId?.Name ?? string.Empty; + SelectedBookAuthor = _allAuthors.FirstOrDefault(a => a.Id == value.AuthorId?.Id); + } + } + + [RelayCommand] + private void AddBook() + { + string title = BookTitle?.Trim() ?? string.Empty; + string isbn = BookIsbn?.Trim() ?? string.Empty; + string desc = BookDesc?.Trim() ?? string.Empty; + int.TryParse(BookYear?.Trim(), out int year); + var author = SelectedBookAuthor; + + if (string.IsNullOrEmpty(title)) + { + BookStatusColor = "Red"; + BookStatus = "Название книги не может быть пустым."; + return; + } + if (author == null) + { + BookStatusColor = "Red"; + BookStatus = "Выберите автора из списка (введите имя для поиска)."; + return; + } + + if (DatabaseService.AddBook(title, isbn, desc, year, author.Id)) + { + RefreshBooks(); + ClearBook(); + BookStatusColor = "Green"; + BookStatus = "Книга добавлена!"; + } + else + { + BookStatusColor = "Red"; + BookStatus = "Ошибка добавления (проверьте ISBN на уникальность)."; + } + } + + [RelayCommand] + private void EditBook() + { + if (SelectedBook == null) + { + BookStatusColor = "Red"; + BookStatus = "Выберите книгу для редактирования."; + return; + } + string title = BookTitle?.Trim() ?? string.Empty; + string isbn = BookIsbn?.Trim() ?? string.Empty; + string desc = BookDesc?.Trim() ?? string.Empty; + int.TryParse(BookYear?.Trim(), out int year); + var author = SelectedBookAuthor; + + if (string.IsNullOrEmpty(title) || author == null) + { + BookStatusColor = "Red"; + BookStatus = "Заполните название и выберите автора."; + return; + } + + if (DatabaseService.UpdateBook(SelectedBook.Id, title, isbn, desc, year, author.Id)) + { + RefreshBooks(); + ClearBook(); + BookStatusColor = "Green"; + BookStatus = "Книга обновлена!"; + } + else + { + BookStatusColor = "Red"; + BookStatus = "Ошибка обновления книги."; + } + } + + [RelayCommand] + private void DeleteBook() + { + if (SelectedBook == null) return; + if (DatabaseService.DeleteBook(SelectedBook.Id)) + { + RefreshBooks(); + ClearBook(); + BookStatusColor = "Green"; + BookStatus = "Книга удалена!"; + } + else + { + BookStatusColor = "Red"; + BookStatus = "Ошибка удаления книги (возможно, есть выданные экземпляры)."; + } + } + + [RelayCommand] + private void ClearBook() + { + SelectedBook = null; + BookTitle = string.Empty; + BookIsbn = string.Empty; + BookYear = string.Empty; + BookDesc = string.Empty; + SearchBookAuthorText = string.Empty; + SelectedBookAuthor = null; + BookStatus = string.Empty; + } + + private void RefreshCopies() + { + CopiesList = DatabaseService.GetBookCopies(); + } + + partial void OnSearchCopyBookTextChanged(string value) + { + FilterCopyBooks(); + } + + private void FilterCopyBooks() + { + string filter = SearchCopyBookText?.Trim() ?? string.Empty; + if (string.IsNullOrEmpty(filter)) + { + FilteredCopyBooks = _allBooks.Take(20).ToList(); + } + else + { + FilteredCopyBooks = _allBooks.Where(b => + b.Title.Contains(filter, StringComparison.OrdinalIgnoreCase) || + (b.AuthorId?.Name != null && b.AuthorId.Name.Contains(filter, StringComparison.OrdinalIgnoreCase)) + ).ToList(); + } + } + + partial void OnSelectedCopyChanged(Book_copies? value) + { + if (value != null) + { + CopyInv = value.Inventory_number ?? string.Empty; + CopyShelf = value.Shelf_location ?? string.Empty; + CopyStatusField = value.Status ?? string.Empty; + + SearchCopyBookText = value.BookId?.Title ?? string.Empty; + SelectedCopyBook = _allBooks.FirstOrDefault(b => b.Id == value.BookId?.Id); + } + } + + [RelayCommand] + private void AddCopy() + { + var book = SelectedCopyBook; + string inv = CopyInv?.Trim() ?? string.Empty; + string shelf = CopyShelf?.Trim() ?? string.Empty; + string status = CopyStatusField?.Trim() ?? "available"; + if (string.IsNullOrEmpty(status)) status = "available"; + + if (book == null) + { + CopyStatusColor = "Red"; + CopyStatus = "Выберите книгу из списка (введите название для поиска)."; + return; + } + if (string.IsNullOrEmpty(inv)) + { + CopyStatusColor = "Red"; + CopyStatus = "Инвентарный номер обязателен."; + return; + } + + if (DatabaseService.AddBookCopy(book.Id, inv, status, shelf)) + { + RefreshCopies(); + ClearCopy(); + CopyStatusColor = "Green"; + CopyStatus = "Экземпляр добавлен!"; + } + else + { + CopyStatusColor = "Red"; + CopyStatus = "Ошибка добавления (проверьте уникальность инв. номера)."; + } + } + + [RelayCommand] + private void EditCopy() + { + if (SelectedCopy == null) + { + CopyStatusColor = "Red"; + CopyStatus = "Выберите экземпляр для редактирования."; + return; + } + var book = SelectedCopyBook; + string inv = CopyInv?.Trim() ?? string.Empty; + string shelf = CopyShelf?.Trim() ?? string.Empty; + string status = CopyStatusField?.Trim() ?? "available"; + if (string.IsNullOrEmpty(status)) status = "available"; + + if (book == null || string.IsNullOrEmpty(inv)) + { + CopyStatusColor = "Red"; + CopyStatus = "Выберите книгу и заполните инв. номер."; + return; + } + + if (DatabaseService.UpdateBookCopy(SelectedCopy.Id, book.Id, inv, status, shelf)) + { + RefreshCopies(); + ClearCopy(); + CopyStatusColor = "Green"; + CopyStatus = "Экземпляр обновлен!"; + } + else + { + CopyStatusColor = "Red"; + CopyStatus = "Ошибка обновления."; + } + } + + [RelayCommand] + private void DeleteCopy() + { + if (SelectedCopy == null) return; + if (DatabaseService.DeleteBookCopy(SelectedCopy.Id)) + { + RefreshCopies(); + ClearCopy(); + CopyStatusColor = "Green"; + CopyStatus = "Экземпляр удален!"; + } + else + { + CopyStatusColor = "Red"; + CopyStatus = "Ошибка удаления экземпляра."; + } + } + + [RelayCommand] + private void ClearCopy() + { + SelectedCopy = null; + CopyInv = string.Empty; + CopyShelf = string.Empty; + SearchCopyBookText = string.Empty; + SelectedCopyBook = null; + CopyStatusField = string.Empty; + CopyStatus = string.Empty; + } + + private void RefreshUsers() + { + UsersList = DatabaseService.GetUsers(); + _allRoles = DatabaseService.GetRoles(); + FilterUserRoles(); + } + + partial void OnSearchUserRoleTextChanged(string value) + { + FilterUserRoles(); + } + + private void FilterUserRoles() + { + string filter = SearchUserRoleText?.Trim() ?? string.Empty; + if (string.IsNullOrEmpty(filter)) + { + FilteredUserRoles = _allRoles.ToList(); + } + else + { + FilteredUserRoles = _allRoles.Where(r => + r.Name.Contains(filter, StringComparison.OrdinalIgnoreCase) + ).ToList(); + } + } + + partial void OnSelectedUserChanged(User? value) + { + if (value != null) + { + UserFullName = value.Full_name ?? string.Empty; + UserLogin = value.Login ?? string.Empty; + UserPass = value.Password_hash ?? string.Empty; + UserEmail = value.Email ?? string.Empty; + UserPhone = value.Phone ?? string.Empty; + + SearchUserRoleText = value.RoleId?.Name ?? string.Empty; + SelectedUserRole = _allRoles.FirstOrDefault(r => r.id == value.RoleId?.id); + } + } + + [RelayCommand] + private void AddUser() + { + string name = UserFullName?.Trim() ?? string.Empty; + string login = UserLogin?.Trim() ?? string.Empty; + string pass = UserPass?.Trim() ?? string.Empty; + string email = UserEmail?.Trim() ?? string.Empty; + string phone = UserPhone?.Trim() ?? string.Empty; + var role = SelectedUserRole; + + if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(login) || string.IsNullOrEmpty(pass) || role == null) + { + UserStatusColor = "Red"; + UserStatus = "Заполните обязательные поля (ФИО, Логин, Пароль, Роль)."; + return; + } + + if (DatabaseService.AddUser(name, email, phone, login, pass, role.id)) + { + RefreshUsers(); + ClearUser(); + UserStatusColor = "Green"; + UserStatus = "Пользователь добавлен!"; + } + else + { + UserStatusColor = "Red"; + UserStatus = "Ошибка добавления (проверьте уникальность Логина/Email)."; + } + } + + [RelayCommand] + private void EditUser() + { + if (SelectedUser == null) + { + UserStatusColor = "Red"; + UserStatus = "Выберите пользователя для редактирования."; + return; + } + string name = UserFullName?.Trim() ?? string.Empty; + string login = UserLogin?.Trim() ?? string.Empty; + string pass = UserPass?.Trim() ?? string.Empty; + string email = UserEmail?.Trim() ?? string.Empty; + string phone = UserPhone?.Trim() ?? string.Empty; + var role = SelectedUserRole; + + if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(login) || string.IsNullOrEmpty(pass) || role == null) + { + UserStatusColor = "Red"; + UserStatus = "Заполните все обязательные поля и выберите роль."; + return; + } + + if (DatabaseService.UpdateUser(SelectedUser.Id, name, email, phone, login, pass, role.id)) + { + RefreshUsers(); + ClearUser(); + UserStatusColor = "Green"; + UserStatus = "Пользователь обновлен!"; + } + else + { + UserStatusColor = "Red"; + UserStatus = "Ошибка обновления пользователя."; + } + } + + [RelayCommand] + private void DeleteUser() + { + if (SelectedUser == null) return; + if (DatabaseService.DeleteUser(SelectedUser.Id)) + { + RefreshUsers(); + ClearUser(); + UserStatusColor = "Green"; + UserStatus = "Пользователь удален!"; + } + else + { + UserStatusColor = "Red"; + UserStatus = "Ошибка удаления пользователя."; + } + } + + [RelayCommand] + private void ClearUser() + { + SelectedUser = null; + UserFullName = string.Empty; + UserLogin = string.Empty; + UserPass = string.Empty; + UserEmail = string.Empty; + UserPhone = string.Empty; + SearchUserRoleText = string.Empty; + SelectedUserRole = null; + UserStatus = string.Empty; + } + + [RelayCommand] + private void RefreshReports() + { + decimal collected = DatabaseService.GetTotalFinesCollected(); + decimal unpaid = DatabaseService.GetTotalFinesUnpaid(); + + FinesCollected = $"{collected:N2} руб."; + FinesUnpaid = $"{unpaid:N2} руб."; + + PopularBooksList = DatabaseService.GetPopularBooks().Rows.Cast().Select(r => new PopularBookItem + { + Title = r["Title"]?.ToString() ?? string.Empty, + BorrowCount = r["BorrowCount"] != DBNull.Value ? Convert.ToInt32(r["BorrowCount"]) : 0 + }).ToList(); + + OverdueLoansList = DatabaseService.GetOverdueLoans().Rows.Cast().Select(r => new OverdueLoanItem + { + Title = r["Title"]?.ToString() ?? string.Empty, + Reader = r["Reader"]?.ToString() ?? string.Empty, + IssueDate = r["IssueDate"] != DBNull.Value ? Convert.ToDateTime(r["IssueDate"]).ToString("dd.MM.yyyy") : string.Empty, + DueDate = r["DueDate"] != DBNull.Value ? Convert.ToDateTime(r["DueDate"]).ToString("dd.MM.yyyy") : string.Empty, + OverdueDays = r["OverdueDays"] != DBNull.Value ? Convert.ToInt32(r["OverdueDays"]) : 0 + }).ToList(); + } + } +} diff --git a/CourseProject1125/ViewModels/LibrarianWindowViewModel.cs b/CourseProject1125/ViewModels/LibrarianWindowViewModel.cs new file mode 100644 index 0000000..985783b --- /dev/null +++ b/CourseProject1125/ViewModels/LibrarianWindowViewModel.cs @@ -0,0 +1,413 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using CourseProject1125.DB; +using CourseProject1125.Models; + +namespace CourseProject1125.ViewModels +{ + public partial class LibrarianWindowViewModel : ViewModelBase + { + private List _availableCopies = new(); + private List _readers = new(); + + public Action? OnLogoutRequested { get; set; } + + public string LibrarianName => UserSession.UserName; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(IssueTabBackground), nameof(IssueTabForeground), nameof(IsIssueVisible))] + [NotifyPropertyChangedFor(nameof(ReturnsTabBackground), nameof(ReturnsTabForeground), nameof(IsReturnsVisible))] + [NotifyPropertyChangedFor(nameof(RegisterTabBackground), nameof(RegisterTabForeground), nameof(IsRegisterVisible))] + [NotifyPropertyChangedFor(nameof(FinesTabBackground), nameof(FinesTabForeground), nameof(IsFinesVisible))] + [NotifyPropertyChangedFor(nameof(ReservationsTabBackground), nameof(ReservationsTabForeground), nameof(IsReservationsVisible))] + private string _currentTab = "Issue"; + + [ObservableProperty] private string _activeTabHeader = "📖 Оформление выдачи книг"; + + public string IssueTabBackground => CurrentTab == "Issue" ? "LightGray" : "Transparent"; + public string IssueTabForeground => CurrentTab == "Issue" ? "Black" : "Gray"; + public bool IsIssueVisible => CurrentTab == "Issue"; + + public string ReturnsTabBackground => CurrentTab == "Returns" ? "LightGray" : "Transparent"; + public string ReturnsTabForeground => CurrentTab == "Returns" ? "Black" : "Gray"; + public bool IsReturnsVisible => CurrentTab == "Returns"; + + public string RegisterTabBackground => CurrentTab == "Register" ? "LightGray" : "Transparent"; + public string RegisterTabForeground => CurrentTab == "Register" ? "Black" : "Gray"; + public bool IsRegisterVisible => CurrentTab == "Register"; + + public string FinesTabBackground => CurrentTab == "Fines" ? "LightGray" : "Transparent"; + public string FinesTabForeground => CurrentTab == "Fines" ? "Black" : "Gray"; + public bool IsFinesVisible => CurrentTab == "Fines"; + + public string ReservationsTabBackground => CurrentTab == "Reservations" ? "LightGray" : "Transparent"; + public string ReservationsTabForeground => CurrentTab == "Reservations" ? "Black" : "Gray"; + public bool IsReservationsVisible => CurrentTab == "Reservations"; + + [ObservableProperty] private string _searchCopyText = string.Empty; + [ObservableProperty] private List _filteredCopies = new(); + [ObservableProperty] private Book_copies? _selectedAvailableCopy; + [ObservableProperty] private string _searchReaderText = string.Empty; + [ObservableProperty] private List _filteredReaders = new(); + [ObservableProperty] private User? _selectedReader; + [ObservableProperty] private DateTime? _issueDueDate = DateTime.Now.AddDays(14); + [ObservableProperty] private string _issueStatus = string.Empty; + [ObservableProperty] private string _issueStatusColor = "Black"; + + [ObservableProperty] private List _activeLoansList = new(); + [ObservableProperty] private Loan? _selectedActiveLoan; + [ObservableProperty] private string _returnDetails = "Выберите выдачу из списка слева."; + [ObservableProperty] private string _returnStatus = string.Empty; + [ObservableProperty] private string _returnStatusColor = "Black"; + [ObservableProperty] private bool _isReturnEnabled; + + [ObservableProperty] private string _regFullName = string.Empty; + [ObservableProperty] private string _regEmail = string.Empty; + [ObservableProperty] private string _regPhone = string.Empty; + [ObservableProperty] private string _regLogin = string.Empty; + [ObservableProperty] private string _regPass = string.Empty; + [ObservableProperty] private string _regStatus = string.Empty; + [ObservableProperty] private string _regStatusColor = "Black"; + + [ObservableProperty] private List _finesList = new(); + [ObservableProperty] private Fine? _selectedFine; + [ObservableProperty] private string _fineDetails = "Выберите штраф из списка."; + [ObservableProperty] private string _fineStatus = string.Empty; + [ObservableProperty] private string _fineStatusColor = "Black"; + [ObservableProperty] private bool _isPayFineEnabled; + + [ObservableProperty] private List _reservationsList = new(); + [ObservableProperty] private Reservation? _selectedReservation; + [ObservableProperty] private string _reservationDetails = "Выберите бронь из списка."; + [ObservableProperty] private string _reservationStatus = string.Empty; + [ObservableProperty] private string _reservationStatusColor = "Black"; + [ObservableProperty] private bool _isCancelReservationEnabled; + + public LibrarianWindowViewModel() + { + LoadAllData(); + ShowTab("Issue"); + } + + private void LoadAllData() + { + RefreshIssueCombos(); + RefreshActiveLoans(); + RefreshFines(); + RefreshReservations(); + } + + [RelayCommand] + private void ShowTab(string tabName) + { + CurrentTab = tabName; + + switch (tabName) + { + case "Issue": + ActiveTabHeader = "Оформление выдачи книг"; + RefreshIssueCombos(); + break; + case "Returns": + ActiveTabHeader = "Приём возврата книг"; + RefreshActiveLoans(); + break; + case "Register": + ActiveTabHeader = "Регистрация читателей"; + break; + case "Fines": + ActiveTabHeader = "Управление штрафами"; + RefreshFines(); + break; + case "Reservations": + ActiveTabHeader = "Бронирования читателей"; + RefreshReservations(); + break; + } + } + + [RelayCommand] + private void Logout() + { + OnLogoutRequested?.Invoke(); + } + + private void RefreshIssueCombos() + { + var allCopies = DatabaseService.GetBookCopies(); + _availableCopies = allCopies.Where(c => string.Equals(c.Status, "available", StringComparison.OrdinalIgnoreCase)).ToList(); + + var allUsers = DatabaseService.GetUsers(); + _readers = allUsers.Where(u => u.RoleId?.id == 3 || string.Equals(u.RoleId?.Name, "Читатель", StringComparison.OrdinalIgnoreCase)).ToList(); + + FilterIssueCopies(); + FilterIssueReaders(); + + IssueStatus = string.Empty; + } + + partial void OnSearchCopyTextChanged(string value) + { + FilterIssueCopies(); + } + + partial void OnSearchReaderTextChanged(string value) + { + FilterIssueReaders(); + } + + private void FilterIssueCopies() + { + var filter = SearchCopyText?.Trim() ?? string.Empty; + if (string.IsNullOrEmpty(filter)) + { + FilteredCopies = _availableCopies.Take(15).ToList(); + } + else + { + FilteredCopies = _availableCopies.Where(c => + (c.BookId?.Title != null && c.BookId.Title.Contains(filter, StringComparison.OrdinalIgnoreCase)) || + (c.Inventory_number != null && c.Inventory_number.Contains(filter, StringComparison.OrdinalIgnoreCase)) || + (c.BookId?.AuthorId?.Name != null && c.BookId.AuthorId.Name.Contains(filter, StringComparison.OrdinalIgnoreCase)) + ).ToList(); + } + } + + private void FilterIssueReaders() + { + var filter = SearchReaderText?.Trim() ?? string.Empty; + if (string.IsNullOrEmpty(filter)) + { + FilteredReaders = _readers.Take(15).ToList(); + } + else + { + FilteredReaders = _readers.Where(r => + (r.Full_name != null && r.Full_name.Contains(filter, StringComparison.OrdinalIgnoreCase)) || + (r.Login != null && r.Login.Contains(filter, StringComparison.OrdinalIgnoreCase)) + ).ToList(); + } + } + + [RelayCommand] + private void IssueBook() + { + if (SelectedAvailableCopy == null || SelectedReader == null || IssueDueDate == null) + { + IssueStatusColor = "Red"; + IssueStatus = "Ошибка: Выберите книгу, читателя и укажите дату сдачи."; + return; + } + + var dueDate = IssueDueDate.Value; + + if (dueDate <= DateTime.Now) + { + IssueStatusColor = "Red"; + IssueStatus = "Ошибка: Срок возврата должен быть в будущем."; + return; + } + + int librarianId = UserSession.UserId; + + if (DatabaseService.IssueLoan(SelectedAvailableCopy.Id, SelectedReader.Id, librarianId, dueDate)) + { + IssueStatusColor = "Green"; + IssueStatus = $"Успешно! Книга '{SelectedAvailableCopy.BookId?.Title}' выдана читателю '{SelectedReader.Full_name}'."; + SearchCopyText = string.Empty; + SearchReaderText = string.Empty; + RefreshIssueCombos(); + } + else + { + IssueStatusColor = "Red"; + IssueStatus = "Ошибка при оформлении выдачи в базе данных."; + } + } + + private void RefreshActiveLoans() + { + var allLoans = DatabaseService.GetLoans(); + ActiveLoansList = allLoans.Where(l => + string.Equals(l.Status, "active", StringComparison.OrdinalIgnoreCase) || + string.Equals(l.Status, "pending_return", StringComparison.OrdinalIgnoreCase) + ).ToList(); + + SelectedActiveLoan = null; + } + + partial void OnSelectedActiveLoanChanged(Loan? value) + { + if (value != null) + { + ReturnDetails = $"Книга: {value.CopyId?.BookId?.Title}\n" + + $"Инвентарный номер: {value.CopyId?.Inventory_number}\n" + + $"Читатель: {value.ReaderId?.Full_name}\n" + + $"Дата выдачи: {value.Issue_date:dd.MM.yyyy}\n" + + $"Срок сдачи: {value.Due_date:dd.MM.yyyy}\n" + + $"Статус: {value.StatusText}"; + IsReturnEnabled = true; + } + else + { + ReturnDetails = "Выберите выдачу из списка слева."; + IsReturnEnabled = false; + } + ReturnStatus = string.Empty; + } + + [RelayCommand] + private void ReturnBook() + { + if (SelectedActiveLoan == null) return; + + DateTime returnDate = DateTime.Now; + decimal fine = DatabaseService.ReturnLoan(SelectedActiveLoan.Id, returnDate); + + if (fine >= 0) + { + RefreshActiveLoans(); + if (fine > 0) + { + ReturnStatusColor = "Red"; + ReturnStatus = $"Книга возвращена! Зафиксирована просрочка. Выписан штраф: {fine:N2} руб."; + } + else + { + ReturnStatusColor = "Green"; + ReturnStatus = "Книга возвращена успешно! Просрочек нет."; + } + } + else + { + ReturnStatusColor = "Red"; + ReturnStatus = "Ошибка выполнения возврата книги."; + } + } + + [RelayCommand] + private void RegisterReader() + { + string name = RegFullName?.Trim() ?? string.Empty; + string email = RegEmail?.Trim() ?? string.Empty; + string phone = RegPhone?.Trim() ?? string.Empty; + string login = RegLogin?.Trim() ?? string.Empty; + string pass = RegPass?.Trim() ?? string.Empty; + + if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(login) || string.IsNullOrEmpty(pass)) + { + RegStatusColor = "Red"; + RegStatus = "Пожалуйста, заполните ФИО, логин и пароль."; + return; + } + + if (DatabaseService.AddUser(name, email, phone, login, pass, 3)) + { + RegStatusColor = "Green"; + RegStatus = $"Успешно! Читатель '{name}' зарегистрирован."; + + RegFullName = string.Empty; + RegEmail = string.Empty; + RegPhone = string.Empty; + RegLogin = string.Empty; + RegPass = string.Empty; + } + else + { + RegStatusColor = "Red"; + RegStatus = "Ошибка: Пользователь с таким логином или Email уже существует."; + } + } + + private void RefreshFines() + { + FinesList = DatabaseService.GetFines().Where(f => f.Is_paid == 0).ToList(); + SelectedFine = null; + } + + partial void OnSelectedFineChanged(Fine? value) + { + if (value != null) + { + string paidStr = value.Is_paid == 1 ? "Оплачен" : "НЕ оплачен"; + FineDetails = $"Читатель: {value.LoanId?.ReaderId?.Full_name}\n" + + $"Книга: {value.LoanId?.CopyId?.BookId?.Title}\n" + + $"Сумма штрафа: {value.Amount:N2} руб.\n" + + $"Статус: {paidStr}\n" + + $"Дата начисления: {value.Created_at:dd.MM.yyyy}"; + IsPayFineEnabled = value.Is_paid == 0; + } + else + { + FineDetails = "Выберите штраф из списка."; + IsPayFineEnabled = false; + } + FineStatus = string.Empty; + } + + [RelayCommand] + private void PayFine() + { + if (SelectedFine == null) return; + + if (DatabaseService.PayFine(SelectedFine.Id)) + { + RefreshFines(); + FineStatusColor = "Green"; + FineStatus = "Штраф успешно отмечен как оплаченный!"; + } + else + { + FineStatusColor = "Red"; + FineStatus = "Ошибка при оплате штрафа."; + } + } + + private void RefreshReservations() + { + ReservationsList = DatabaseService.GetReservations().Where(r => r.is_active == 1 && r.Expiry_date >= DateTime.Now).ToList(); + SelectedReservation = null; + } + + partial void OnSelectedReservationChanged(Reservation? value) + { + if (value != null) + { + string activeStr = value.is_active == 1 ? "Активна" : "Неактивна/Отменена"; + ReservationDetails = $"Книга: {value.BookId?.Title}\n" + + $"Читатель: {value.ReaderId?.Full_name}\n" + + $"Дата бронирования: {value.Reservation_date:dd.MM.yyyy}\n" + + $"Истекает: {value.Expiry_date:dd.MM.yyyy}\n" + + $"Статус: {activeStr}"; + IsCancelReservationEnabled = value.is_active == 1; + } + else + { + ReservationDetails = "Выберите бронь из списка."; + IsCancelReservationEnabled = false; + } + ReservationStatus = string.Empty; + } + + [RelayCommand] + private void CancelReservation() + { + if (SelectedReservation == null) return; + + if (DatabaseService.CancelReservation(SelectedReservation.Id)) + { + RefreshReservations(); + ReservationStatusColor = "Green"; + ReservationStatus = "Бронирование успешно отменено!"; + } + else + { + ReservationStatusColor = "Red"; + ReservationStatus = "Ошибка при отмене бронирования."; + } + } + } +} diff --git a/CourseProject1125/ViewModels/MainWindowViewModel.cs b/CourseProject1125/ViewModels/MainWindowViewModel.cs index 622bf07..87ffe00 100644 --- a/CourseProject1125/ViewModels/MainWindowViewModel.cs +++ b/CourseProject1125/ViewModels/MainWindowViewModel.cs @@ -1,20 +1,55 @@ -using System; -using System.Data; -using Microsoft.Data.SqlClient; -using System.ComponentModel; -using System.Runtime.CompilerServices; -using Avalonia.Controls; -using Avalonia.Interactivity; +using System; +using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using CourseProject1125.DB; -using Tmds.DBus.Protocol; -using Microsoft.Extensions.DependencyInjection; -using Connection = CourseProject1125.DB.Connection; namespace CourseProject1125.ViewModels { - public partial class MainWindowViewModel + public partial class MainWindowViewModel : ViewModelBase { + [ObservableProperty] + private string _loginInput = string.Empty; + + [ObservableProperty] + private string _passwordInput = string.Empty; + + [ObservableProperty] + private string _errorMessage = string.Empty; + + public Action? OnLoginSuccess { get; set; } + + [RelayCommand] + private void Login() + { + ErrorMessage = string.Empty; + + var login = LoginInput?.Trim() ?? string.Empty; + var password = PasswordInput?.Trim() ?? string.Empty; + + if (string.IsNullOrEmpty(login) || string.IsNullOrEmpty(password)) + { + ErrorMessage = "Пожалуйста, введите логин и пароль."; + return; + } + + try + { + var user = DatabaseService.AuthenticateUser(login, password); + if (user != null) + { + OnLoginSuccess?.Invoke(user.RoleId.id); + } + else + { + ErrorMessage = "Неверный логин или пароль."; + } + } + catch (Exception ex) + { + ErrorMessage = $"Ошибка: {ex.Message}"; + } + } + [RelayCommand] private void FullExit() { diff --git a/CourseProject1125/ViewModels/ReaderWindowViewModel.cs b/CourseProject1125/ViewModels/ReaderWindowViewModel.cs new file mode 100644 index 0000000..0623f45 --- /dev/null +++ b/CourseProject1125/ViewModels/ReaderWindowViewModel.cs @@ -0,0 +1,357 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using CourseProject1125.DB; +using CourseProject1125.Models; + +namespace CourseProject1125.ViewModels +{ + public partial class ReaderWindowViewModel : ViewModelBase + { + private List _allBooks = new(); + private List _allCopies = new(); + + public Action? OnLogoutRequested { get; set; } + + public string ReaderName => UserSession.UserName; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(CatalogTabBackground), nameof(CatalogTabForeground), nameof(IsCatalogVisible))] + [NotifyPropertyChangedFor(nameof(MyLoansTabBackground), nameof(MyLoansTabForeground), nameof(IsMyLoansVisible))] + [NotifyPropertyChangedFor(nameof(ReturnBookTabBackground), nameof(ReturnBookTabForeground), nameof(IsReturnBookVisible))] + [NotifyPropertyChangedFor(nameof(MyFinesTabBackground), nameof(MyFinesTabForeground), nameof(IsMyFinesVisible))] + [NotifyPropertyChangedFor(nameof(MyReservationsTabBackground), nameof(MyReservationsTabForeground), nameof(IsMyReservationsVisible))] + private string _currentTab = "Catalog"; + + [ObservableProperty] private string _activeTabHeader = "Каталог и бронирование книг"; + + public string CatalogTabBackground => CurrentTab == "Catalog" ? "LightGray" : "Transparent"; + public string CatalogTabForeground => CurrentTab == "Catalog" ? "Black" : "Gray"; + public bool IsCatalogVisible => CurrentTab == "Catalog"; + + public string MyLoansTabBackground => CurrentTab == "MyLoans" ? "LightGray" : "Transparent"; + public string MyLoansTabForeground => CurrentTab == "MyLoans" ? "Black" : "Gray"; + public bool IsMyLoansVisible => CurrentTab == "MyLoans"; + + public string ReturnBookTabBackground => CurrentTab == "ReturnBook" ? "LightGray" : "Transparent"; + public string ReturnBookTabForeground => CurrentTab == "ReturnBook" ? "Black" : "Gray"; + public bool IsReturnBookVisible => CurrentTab == "ReturnBook"; + + public string MyFinesTabBackground => CurrentTab == "MyFines" ? "LightGray" : "Transparent"; + public string MyFinesTabForeground => CurrentTab == "MyFines" ? "Black" : "Gray"; + public bool IsMyFinesVisible => CurrentTab == "MyFines"; + + public string MyReservationsTabBackground => CurrentTab == "MyReservations" ? "LightGray" : "Transparent"; + public string MyReservationsTabForeground => CurrentTab == "MyReservations" ? "Black" : "Gray"; + public bool IsMyReservationsVisible => CurrentTab == "MyReservations"; + + [ObservableProperty] private string _searchQuery = string.Empty; + [ObservableProperty] private List _booksList = new(); + [ObservableProperty] private Book? _selectedBook; + [ObservableProperty] private string _catalogBookTitle = "Выберите книгу из списка."; + [ObservableProperty] private string _catalogBookAuthor = string.Empty; + [ObservableProperty] private string _catalogBookDesc = string.Empty; + [ObservableProperty] private string _catalogAvailability = "Доступно копий: -"; + [ObservableProperty] private string _catalogStatus = string.Empty; + [ObservableProperty] private string _catalogStatusColor = "Black"; + [ObservableProperty] private bool _isReserveEnabled; + + [ObservableProperty] private List _myLoansList = new(); + + [ObservableProperty] private List _activeLoansList = new(); + [ObservableProperty] private Loan? _selectedReturnLoan; + [ObservableProperty] private string _returnBookDetails = "Выберите выданную книгу из списка слева."; + [ObservableProperty] private string _returnBookStatus = string.Empty; + [ObservableProperty] private string _returnBookStatusColor = "Black"; + [ObservableProperty] private bool _isSubmitReturnEnabled; + + [ObservableProperty] private List _myFinesList = new(); + [ObservableProperty] private Fine? _selectedFine; + [ObservableProperty] private string _myFineDetails = "Выберите штраф из списка слева."; + [ObservableProperty] private string _myFineStatus = string.Empty; + [ObservableProperty] private string _myFineStatusColor = "Black"; + [ObservableProperty] private bool _isPayMyFineEnabled; + + [ObservableProperty] private List _myReservationsList = new(); + [ObservableProperty] private Reservation? _selectedReservation; + [ObservableProperty] private string _myReservationDetails = "Выберите бронь из списка."; + [ObservableProperty] private string _myReservationStatus = string.Empty; + [ObservableProperty] private string _myReservationStatusColor = "Black"; + [ObservableProperty] private bool _isCancelMyReservationEnabled; + + public ReaderWindowViewModel() + { + LoadAllData(); + ShowTab("Catalog"); + } + + private void LoadAllData() + { + _allBooks = DatabaseService.GetBooks(); + _allCopies = DatabaseService.GetBookCopies(); + + RefreshCatalog(); + RefreshMyLoans(); + RefreshMyFines(); + RefreshMyReservations(); + } + + [RelayCommand] + private void ShowTab(string tabName) + { + CurrentTab = tabName; + + switch (tabName) + { + case "Catalog": + ActiveTabHeader = "Каталог и бронирование книг"; + LoadAllData(); + break; + case "MyLoans": + ActiveTabHeader = "Мои выданные книги"; + RefreshMyLoans(); + break; + case "ReturnBook": + ActiveTabHeader = "Сдать книгу"; + RefreshReturnLoans(); + break; + case "MyFines": + ActiveTabHeader = "Мои штрафы"; + RefreshMyFines(); + break; + case "MyReservations": + ActiveTabHeader = "Мои бронирования"; + RefreshMyReservations(); + break; + } + } + + [RelayCommand] + private void Logout() + { + OnLogoutRequested?.Invoke(); + } + + private void RefreshCatalog() + { + BooksList = _allBooks; + SelectedBook = null; + } + + [RelayCommand] + private void Search() + { + var query = SearchQuery?.Trim() ?? string.Empty; + if (string.IsNullOrEmpty(query)) + { + BooksList = _allBooks; + } + else + { + BooksList = _allBooks.Where(b => + b.Title.Contains(query, StringComparison.OrdinalIgnoreCase) || + (b.AuthorId?.Name != null && b.AuthorId.Name.Contains(query, StringComparison.OrdinalIgnoreCase)) || + (b.Isbn != null && b.Isbn.Contains(query, StringComparison.OrdinalIgnoreCase)) + ).ToList(); + } + } + + partial void OnSelectedBookChanged(Book? value) + { + if (value != null) + { + CatalogBookTitle = value.Title; + CatalogBookAuthor = $"Автор: {value.AuthorId?.Name} | ISBN: {value.Isbn} | Год: {value.Year_published}"; + CatalogBookDesc = string.IsNullOrEmpty(value.Description) ? "Описание отсутствует." : value.Description; + + int totalCopies = _allCopies.Count(c => c.BookId?.Id == value.Id); + int availableCopies = _allCopies.Count(c => c.BookId?.Id == value.Id && string.Equals(c.Status, "available", StringComparison.OrdinalIgnoreCase)); + + CatalogAvailability = $"Всего экземпляров: {totalCopies} | Доступно для выдачи: {availableCopies}"; + IsReserveEnabled = availableCopies > 0; + CatalogStatus = string.Empty; + } + else + { + CatalogBookTitle = "Выберите книгу из списка."; + CatalogBookAuthor = string.Empty; + CatalogBookDesc = string.Empty; + CatalogAvailability = "Доступно копий: -"; + IsReserveEnabled = false; + CatalogStatus = string.Empty; + } + } + + [RelayCommand] + private void ReserveBook() + { + if (SelectedBook == null) return; + + int readerId = UserSession.UserId; + DateTime expiryDate = DateTime.Now.AddDays(3); + + if (DatabaseService.CreateReservation(SelectedBook.Id, readerId, expiryDate)) + { + _allCopies = DatabaseService.GetBookCopies(); + + var currentBook = SelectedBook; + SelectedBook = null; + SelectedBook = currentBook; + + CatalogStatusColor = "Green"; + CatalogStatus = "Успешно! Книга забронирована на 3 дня. Заберите её на стойке выдачи."; + } + else + { + CatalogStatusColor = "Red"; + CatalogStatus = "Ошибка создания бронирования."; + } + } + + private void RefreshMyLoans() + { + int readerId = UserSession.UserId; + var allLoans = DatabaseService.GetLoans(); + MyLoansList = allLoans.Where(l => l.ReaderId?.Id == readerId).ToList(); + } + + private void RefreshReturnLoans() + { + int readerId = UserSession.UserId; + var allLoans = DatabaseService.GetLoans(); + ActiveLoansList = allLoans.Where(l => l.ReaderId?.Id == readerId && string.Equals(l.Status, "active", StringComparison.OrdinalIgnoreCase)).ToList(); + + SelectedReturnLoan = null; + } + + partial void OnSelectedReturnLoanChanged(Loan? value) + { + if (value != null) + { + ReturnBookDetails = $"Книга: {value.CopyId?.BookId?.Title}\n" + + $"Инвентарный номер: {value.CopyId?.Inventory_number}\n" + + $"Дата выдачи: {value.Issue_date:dd.MM.yyyy}\n" + + $"Срок сдачи: {value.Due_date:dd.MM.yyyy}"; + IsSubmitReturnEnabled = true; + } + else + { + ReturnBookDetails = "Выберите выданную книгу из списка слева."; + IsSubmitReturnEnabled = false; + } + ReturnBookStatus = string.Empty; + } + + [RelayCommand] + private void SubmitReturn() + { + if (SelectedReturnLoan == null) return; + + if (DatabaseService.RequestBookReturn(SelectedReturnLoan.Id)) + { + RefreshReturnLoans(); + RefreshMyLoans(); + ReturnBookStatusColor = "Green"; + ReturnBookStatus = "Запрос на возврат успешно отправлен. Пожалуйста, передайте книгу библиотекарю для подтверждения."; + } + else + { + ReturnBookStatusColor = "Red"; + ReturnBookStatus = "Ошибка при отправке запроса на возврат."; + } + } + + private void RefreshMyFines() + { + int readerId = UserSession.UserId; + var allFines = DatabaseService.GetFines(); + MyFinesList = allFines.Where(f => f.LoanId?.ReaderId?.Id == readerId && f.Is_paid == 0).ToList(); + + SelectedFine = null; + } + + partial void OnSelectedFineChanged(Fine? value) + { + if (value != null) + { + MyFineDetails = $"Книга: {value.LoanId?.CopyId?.BookId?.Title}\n" + + $"Инвентарный номер: {value.LoanId?.CopyId?.Inventory_number}\n" + + $"Сумма штрафа: {value.Amount:N2} руб.\n" + + $"Дата начисления: {value.Created_at:dd.MM.yyyy}"; + IsPayMyFineEnabled = true; + } + else + { + MyFineDetails = "Выберите штраф из списка слева."; + IsPayMyFineEnabled = false; + } + MyFineStatus = string.Empty; + } + + [RelayCommand] + private void PayMyFine() + { + if (SelectedFine == null) return; + + if (DatabaseService.PayFine(SelectedFine.Id)) + { + RefreshMyFines(); + MyFineStatusColor = "Green"; + MyFineStatus = "Штраф успешно оплачен!"; + } + else + { + MyFineStatusColor = "Red"; + MyFineStatus = "Ошибка при оплате штрафа."; + } + } + + private void RefreshMyReservations() + { + int readerId = UserSession.UserId; + var allRes = DatabaseService.GetReservations(); + MyReservationsList = allRes.Where(r => r.ReaderId?.Id == readerId && r.is_active == 1 && r.Expiry_date >= DateTime.Now).ToList(); + + SelectedReservation = null; + } + + partial void OnSelectedReservationChanged(Reservation? value) + { + if (value != null) + { + string activeStr = value.is_active == 1 ? "Активна" : "Неактивна/Отменена"; + MyReservationDetails = $"Книга: {value.BookId?.Title}\n" + + $"Дата брони: {value.Reservation_date:dd.MM.yyyy}\n" + + $"Истекает: {value.Expiry_date:dd.MM.yyyy}\n" + + $"Статус: {activeStr}"; + IsCancelMyReservationEnabled = value.is_active == 1; + } + else + { + MyReservationDetails = "Выберите бронь из списка."; + IsCancelMyReservationEnabled = false; + } + MyReservationStatus = string.Empty; + } + + [RelayCommand] + private void CancelMyReservation() + { + if (SelectedReservation == null) return; + + if (DatabaseService.CancelReservation(SelectedReservation.Id)) + { + RefreshMyReservations(); + MyReservationStatusColor = "Green"; + MyReservationStatus = "Бронирование успешно отменено!"; + } + else + { + MyReservationStatusColor = "Red"; + MyReservationStatus = "Ошибка при отмене бронирования."; + } + } + } +} diff --git a/CourseProject1125/Views/AdminWindow.axaml b/CourseProject1125/Views/AdminWindow.axaml index 59c8c81..afb7d41 100644 --- a/CourseProject1125/Views/AdminWindow.axaml +++ b/CourseProject1125/Views/AdminWindow.axaml @@ -1,50 +1,50 @@ + Background="White"> - + - - - + + + - -