commit b6357c9b8a150660df9b286fd70c4042545a1f79 Author: Muavin Kovaci Date: Thu Mar 12 14:28:52 2026 +0100 CineBook Projekt hinzugefügt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..46177a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vs/ +bin/ +obj/ +*.user +*.suo \ No newline at end of file diff --git a/App.xaml b/App.xaml new file mode 100644 index 0000000..8b324d6 --- /dev/null +++ b/App.xaml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/App.xaml.cs b/App.xaml.cs new file mode 100644 index 0000000..5a4de0d --- /dev/null +++ b/App.xaml.cs @@ -0,0 +1,5 @@ +using System.Windows; + +namespace CineBook; + +public partial class App : Application { } diff --git a/CineBook.csproj b/CineBook.csproj new file mode 100644 index 0000000..d591568 --- /dev/null +++ b/CineBook.csproj @@ -0,0 +1,17 @@ + + + WinExe + net8.0-windows + enable + enable + true + CineBook + CineBook + false + + + + + + + diff --git a/Data/Db.cs b/Data/Db.cs new file mode 100644 index 0000000..a66a874 --- /dev/null +++ b/Data/Db.cs @@ -0,0 +1,122 @@ +using MySqlConnector; +using System; +using System.Collections.Generic; +using System.Data; + +namespace CineBook.Data; + +// Quelle: https://mysqlconnector.net/ (abgerufen: 2026-01-29) +// Zweck: MySQL Zugriff mit parameterisierten Queries (Schutz vor SQL-Injection) + Fehlerbehandlung +public class Db +{ + private readonly string _cs; + public Db(string connectionString) => _cs = connectionString; + + // Quelle/Hilfe: https://mysqlconnector.net/transactions/ (abgerufen: 2026-03-05) + // Zweck: Mehrere DB-Statements atomar ausführen (z.B. Buchung + Seat-Update + Counter-Update) + public T InTransaction(Func work) + { + using var con = new MySqlConnection(_cs); + con.Open(); + using var tx = con.BeginTransaction(); + try + { + var result = work(con, tx); + tx.Commit(); + return result; + } + catch + { + try { tx.Rollback(); } catch { /* ignore rollback errors */ } + throw; + } + } + + public int Execute(MySqlConnection con, MySqlTransaction tx, string sql, Dictionary p) + { + using var cmd = new MySqlCommand(sql, con, tx); + foreach (var kv in p) + cmd.Parameters.AddWithValue(kv.Key, kv.Value ?? DBNull.Value); + return cmd.ExecuteNonQuery(); + } + + public List Query(MySqlConnection con, MySqlTransaction tx, string sql, Dictionary p, Func map) + { + using var cmd = new MySqlCommand(sql, con, tx); + foreach (var kv in p) + cmd.Parameters.AddWithValue(kv.Key, kv.Value ?? DBNull.Value); + using var r = cmd.ExecuteReader(); + var list = new List(); + while (r.Read()) list.Add(map(r)); + return list; + } + + public List Query(string sql, Dictionary p, Func map) + { + try + { + using var con = new MySqlConnection(_cs); + con.Open(); + using var cmd = new MySqlCommand(sql, con); + foreach (var kv in p) + cmd.Parameters.AddWithValue(kv.Key, kv.Value ?? DBNull.Value); + using var r = cmd.ExecuteReader(); + var list = new List(); + while (r.Read()) list.Add(map(r)); + return list; + } + catch (MySqlException ex) + { + throw new Exception("DB-Fehler: " + ex.Message, ex); + } + } + + public List Query(string sql, Func map) + => Query(sql, new Dictionary(), map); + + public int Execute(string sql, Dictionary p) + { + try + { + using var con = new MySqlConnection(_cs); + con.Open(); + using var cmd = new MySqlCommand(sql, con); + foreach (var kv in p) + cmd.Parameters.AddWithValue(kv.Key, kv.Value ?? DBNull.Value); + return cmd.ExecuteNonQuery(); + } + catch (MySqlException ex) + { + throw new Exception("DB-Fehler: " + ex.Message, ex); + } + } + + public long InsertAndGetId(string sql, Dictionary p) + { + try + { + using var con = new MySqlConnection(_cs); + con.Open(); + using var cmd = new MySqlCommand(sql, con); + foreach (var kv in p) + cmd.Parameters.AddWithValue(kv.Key, kv.Value ?? DBNull.Value); + cmd.ExecuteNonQuery(); + return cmd.LastInsertedId; + } + catch (MySqlException ex) + { + throw new Exception("DB-Fehler: " + ex.Message, ex); + } + } + + public bool TestConnection() + { + try + { + using var con = new MySqlConnection(_cs); + con.Open(); + return con.State == ConnectionState.Open; + } + catch { return false; } + } +} diff --git a/Data/DbConfig.cs b/Data/DbConfig.cs new file mode 100644 index 0000000..10f99fa --- /dev/null +++ b/Data/DbConfig.cs @@ -0,0 +1,11 @@ + +namespace CineBook.Data +{ + public static class DbConfig + { + public static string GetConnectionString() + { + return "Server=mysql.pb.bib.de;Port=3306;Database=cinebook;Uid=pbt3h24akv;Pwd=8DwwBCvrMUtJ;"; + } + } +} \ No newline at end of file diff --git a/Data/Repository.cs b/Data/Repository.cs new file mode 100644 index 0000000..e3585fc --- /dev/null +++ b/Data/Repository.cs @@ -0,0 +1,408 @@ +using CineBook.Models; +using MySqlConnector; +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace CineBook.Data; + +// Quelle: Eigene Implementierung basierend auf Unterrichtsmaterial + MySqlConnector Dokumentation +public class Repository +{ + private readonly Db _db; + public Repository(Db db) => _db = db; + + // ==================== USERS ==================== + public User? GetUserByCredentials(string username, string password) + { + var hash = HashPassword(password); + var users = _db.Query( + "SELECT * FROM Users WHERE Username = @u AND PasswordHash = @p LIMIT 1", + new Dictionary { ["@u"] = username, ["@p"] = hash }, + r => new User + { + UserID = r.GetInt32("UserID"), + Username = r.GetString("Username"), + PasswordHash = r.GetString("PasswordHash"), + Role = r.GetString("Role"), + Email = r.IsDBNull(r.GetOrdinal("Email")) ? "" : r.GetString("Email"), + CreatedAt = r.GetDateTime("CreatedAt") + }); + return users.Count > 0 ? users[0] : null; + } + + public List GetAllUsers() => _db.Query( + "SELECT * FROM Users ORDER BY Username", + r => new User + { + UserID = r.GetInt32("UserID"), + Username = r.GetString("Username"), + PasswordHash = r.GetString("PasswordHash"), + Role = r.GetString("Role"), + Email = r.IsDBNull(r.GetOrdinal("Email")) ? "" : r.GetString("Email"), + CreatedAt = r.GetDateTime("CreatedAt") + }); + + public bool CreateUser(string username, string password, string role, string email) + { + if (string.IsNullOrWhiteSpace(username) || username.Length < 3) return false; + if (string.IsNullOrWhiteSpace(password) || password.Length < 6) return false; + try + { + _db.Execute( + "INSERT INTO Users (Username, PasswordHash, Role, Email) VALUES (@u, @p, @r, @e)", + new Dictionary { ["@u"] = username, ["@p"] = HashPassword(password), ["@r"] = role, ["@e"] = email }); + return true; + } + catch { return false; } + } + + public bool UpdateUser(int id, string username, string role, string email) + { + try + { + _db.Execute("UPDATE Users SET Username=@u, Role=@r, Email=@e WHERE UserID=@id", + new Dictionary { ["@u"] = username, ["@r"] = role, ["@e"] = email, ["@id"] = id }); + return true; + } + catch { return false; } + } + + public bool DeleteUser(int id) + { + try { _db.Execute("DELETE FROM Users WHERE UserID=@id", new Dictionary { ["@id"] = id }); return true; } + catch { return false; } + } + + // ==================== MOVIES ==================== + public List GetMovies(bool onlyActive = false) + { + var sql = onlyActive + ? "SELECT * FROM Movies WHERE IsActive=1 ORDER BY Title" + : "SELECT * FROM Movies ORDER BY Title"; + return _db.Query(sql, r => MapMovie(r)); + } + + public List SearchMovies(string term) => _db.Query( + "SELECT * FROM Movies WHERE Title LIKE @t OR Genre LIKE @t ORDER BY Title", + new Dictionary { ["@t"] = $"%{term}%" }, + r => MapMovie(r)); + + public bool CreateMovie(Movie m) + { + if (string.IsNullOrWhiteSpace(m.Title)) return false; + try + { + _db.Execute( + "INSERT INTO Movies (Title, Genre, DurationMinutes, Description, Rating, IsActive) VALUES (@t,@g,@d,@desc,@r,1)", + new Dictionary { ["@t"] = m.Title, ["@g"] = m.Genre, ["@d"] = m.DurationMinutes, ["@desc"] = m.Description, ["@r"] = m.Rating }); + return true; + } + catch { return false; } + } + + public bool UpdateMovie(Movie m) + { + try + { + _db.Execute("UPDATE Movies SET Title=@t, Genre=@g, DurationMinutes=@d, Description=@desc, Rating=@r, IsActive=@a WHERE MovieID=@id", + new Dictionary { ["@t"] = m.Title, ["@g"] = m.Genre, ["@d"] = m.DurationMinutes, ["@desc"] = m.Description, ["@r"] = m.Rating, ["@a"] = m.IsActive ? 1 : 0, ["@id"] = m.MovieID }); + return true; + } + catch { return false; } + } + + public bool DeleteMovie(int id) + { + try { _db.Execute("DELETE FROM Movies WHERE MovieID=@id", new Dictionary { ["@id"] = id }); return true; } + catch { return false; } + } + + private Movie MapMovie(MySqlDataReader r) => new Movie + { + MovieID = r.GetInt32("MovieID"), + Title = r.GetString("Title"), + Genre = r.IsDBNull(r.GetOrdinal("Genre")) ? "" : r.GetString("Genre"), + DurationMinutes = r.GetInt32("DurationMinutes"), + Description = r.IsDBNull(r.GetOrdinal("Description")) ? "" : r.GetString("Description"), + Rating = r.IsDBNull(r.GetOrdinal("Rating")) ? 0 : r.GetDecimal("Rating"), + IsActive = r.GetBoolean("IsActive"), + CreatedAt = r.GetDateTime("CreatedAt") + }; + + // ==================== SCREENINGS ==================== + public List GetScreenings() => _db.Query( + @"SELECT s.*, m.Title as MovieTitle FROM Screenings s + JOIN Movies m ON s.MovieID=m.MovieID + ORDER BY s.ScreeningDate, s.StartTime", + r => MapScreening(r)); + + public List GetScreeningsByMovie(int movieId) => _db.Query( + @"SELECT s.*, m.Title as MovieTitle FROM Screenings s + JOIN Movies m ON s.MovieID=m.MovieID + WHERE s.MovieID=@id ORDER BY s.ScreeningDate, s.StartTime", + new Dictionary { ["@id"] = movieId }, + r => MapScreening(r)); + + public bool CreateScreening(Screening s) + { + if (s.MovieID <= 0) return false; + if (s.ScreeningDate == default) return false; + if (s.TotalSeats <= 0) return false; + if (s.PricePerSeat <= 0) return false; + + try + { + // Quelle/Hilfe: https://mysqlconnector.net/transactions/ (abgerufen: 2026-03-05) + // Zweck: Screening + Seats atomar anlegen, damit Ticketbuchung immer Seat-Daten vorfindet. + return _db.InTransaction((con, tx) => + { + // Screening anlegen + using var cmd = new MySqlCommand( + "INSERT INTO Screenings (MovieID, ScreeningDate, StartTime, Hall, TotalSeats, AvailableSeats, PricePerSeat) " + + "VALUES (@m,@d,@t,@h,@ts,@as,@p);", + con, tx); + cmd.Parameters.AddWithValue("@m", s.MovieID); + cmd.Parameters.AddWithValue("@d", s.ScreeningDate.Date); + cmd.Parameters.AddWithValue("@t", s.StartTime.ToString(@"hh\:mm\:ss")); + cmd.Parameters.AddWithValue("@h", s.Hall); + cmd.Parameters.AddWithValue("@ts", s.TotalSeats); + cmd.Parameters.AddWithValue("@as", s.TotalSeats); + cmd.Parameters.AddWithValue("@p", s.PricePerSeat); + cmd.ExecuteNonQuery(); + var screeningId = (int)cmd.LastInsertedId; + + // Seats generieren (einfaches Kino-Layout: Reihen A.., Sitznummer 1..10) + GenerateSeats(con, tx, screeningId, s.TotalSeats); + return true; + }); + } + catch { return false; } + } + + public bool UpdateScreening(Screening s) + { + if (s.ScreeningID <= 0) return false; + if (s.MovieID <= 0) return false; + if (s.ScreeningDate == default) return false; + if (s.PricePerSeat <= 0) return false; + + try + { + // AvailableSeats wird NICHT blind überschrieben, damit bestehende Buchungen nicht zerstört werden. + _db.Execute( + "UPDATE Screenings SET MovieID=@m, ScreeningDate=@d, StartTime=@t, Hall=@h, PricePerSeat=@p WHERE ScreeningID=@id", + new Dictionary + { + ["@m"] = s.MovieID, + ["@d"] = s.ScreeningDate.Date, + ["@t"] = s.StartTime.ToString(@"hh\:mm\:ss"), + ["@h"] = s.Hall, + ["@p"] = s.PricePerSeat, + ["@id"] = s.ScreeningID + }); + return true; + } + catch { return false; } + } + + public bool DeleteScreening(int id) + { + try { _db.Execute("DELETE FROM Screenings WHERE ScreeningID=@id", new Dictionary { ["@id"] = id }); return true; } + catch { return false; } + } + + private Screening MapScreening(MySqlDataReader r) => new Screening + { + ScreeningID = r.GetInt32("ScreeningID"), + MovieID = r.GetInt32("MovieID"), + MovieTitle = r.IsDBNull(r.GetOrdinal("MovieTitle")) ? "" : r.GetString("MovieTitle"), + ScreeningDate = r.GetDateTime("ScreeningDate"), + StartTime = r.GetTimeSpan("StartTime"), + Hall = r.GetString("Hall"), + TotalSeats = r.GetInt32("TotalSeats"), + AvailableSeats = r.GetInt32("AvailableSeats"), + PricePerSeat = r.GetDecimal("PricePerSeat") + }; + + // ==================== SEATS ==================== + public List GetSeatsByScreening(int screeningId) => _db.Query( + "SELECT * FROM Seats WHERE ScreeningID=@id ORDER BY Row, SeatNumber", + new Dictionary { ["@id"] = screeningId }, + r => new Seat + { + SeatID = r.GetInt32("SeatID"), + ScreeningID = r.GetInt32("ScreeningID"), + Row = r.GetString("Row"), + SeatNumber = r.GetInt32("SeatNumber"), + IsBooked = r.GetBoolean("IsBooked"), + Category = r.GetString("Category") + }); + + public bool EnsureSeatsForScreening(int screeningId, int totalSeats) + { + if (screeningId <= 0 || totalSeats <= 0) return false; + try + { + return _db.InTransaction((con, tx) => + { + var existing = _db.Query(con, tx, + "SELECT COUNT(*) FROM Seats WHERE ScreeningID=@id", + new Dictionary { ["@id"] = screeningId }, + r => r.GetInt32(0)); + if (existing.Count > 0 && existing[0] > 0) return true; + GenerateSeats(con, tx, screeningId, totalSeats); + return true; + }); + } + catch { return false; } + } + + // ==================== BOOKINGS ==================== + public List GetAllBookings() => _db.Query( + @"SELECT b.*, u.Username, m.Title as MovieTitle, s.ScreeningDate, s.StartTime, + CONCAT(se.Row, se.SeatNumber) as SeatDisplay + FROM Bookings b + JOIN Users u ON b.UserID=u.UserID + JOIN Screenings s ON b.ScreeningID=s.ScreeningID + JOIN Movies m ON s.MovieID=m.MovieID + JOIN Seats se ON b.SeatID=se.SeatID + ORDER BY b.BookingDate DESC", + r => MapBooking(r)); + + public List GetBookingsByUser(int userId) => _db.Query( + @"SELECT b.*, u.Username, m.Title as MovieTitle, s.ScreeningDate, s.StartTime, + CONCAT(se.Row, se.SeatNumber) as SeatDisplay + FROM Bookings b + JOIN Users u ON b.UserID=u.UserID + JOIN Screenings s ON b.ScreeningID=s.ScreeningID + JOIN Movies m ON s.MovieID=m.MovieID + JOIN Seats se ON b.SeatID=se.SeatID + WHERE b.UserID=@uid ORDER BY b.BookingDate DESC", + new Dictionary { ["@uid"] = userId }, + r => MapBooking(r)); + + public bool CreateBooking(int userId, int screeningId, int seatId, decimal price) + { + if (userId <= 0 || screeningId <= 0 || seatId <= 0) return false; + if (price <= 0) return false; + + try + { + // Quelle/Hilfe: https://mysqlconnector.net/transactions/ (abgerufen: 2026-03-05) + // Zweck: Seat sperren + AvailableSeats updaten + Booking anlegen atomar. + return _db.InTransaction((con, tx) => + { + var code = "CB-" + DateTime.Now.ToString("yyyyMMddHHmmss"); + + // Seat nur buchen, wenn er noch frei ist + var seatUpdated = _db.Execute(con, tx, + "UPDATE Seats SET IsBooked=1 WHERE SeatID=@sid AND ScreeningID=@sc AND IsBooked=0", + new Dictionary { ["@sid"] = seatId, ["@sc"] = screeningId }); + if (seatUpdated != 1) return false; + + // AvailableSeats nur verringern, wenn noch > 0 + var seatsCounterUpdated = _db.Execute(con, tx, + "UPDATE Screenings SET AvailableSeats=AvailableSeats-1 WHERE ScreeningID=@id AND AvailableSeats > 0", + new Dictionary { ["@id"] = screeningId }); + if (seatsCounterUpdated != 1) return false; + + _db.Execute(con, tx, + "INSERT INTO Bookings (UserID, ScreeningID, SeatID, TotalPrice, Status, BookingCode) VALUES (@u,@s,@seat,@p,'Confirmed',@c)", + new Dictionary { ["@u"] = userId, ["@s"] = screeningId, ["@seat"] = seatId, ["@p"] = price, ["@c"] = code }); + return true; + }); + } + catch { return false; } + } + + public bool CancelBooking(int bookingId, int seatId, int screeningId) + { + if (bookingId <= 0 || seatId <= 0 || screeningId <= 0) return false; + try + { + return _db.InTransaction((con, tx) => + { + var b = _db.Execute(con, tx, + "UPDATE Bookings SET Status='Cancelled' WHERE BookingID=@id AND Status='Confirmed'", + new Dictionary { ["@id"] = bookingId }); + if (b != 1) return false; + + _db.Execute(con, tx, + "UPDATE Seats SET IsBooked=0 WHERE SeatID=@sid AND ScreeningID=@sc", + new Dictionary { ["@sid"] = seatId, ["@sc"] = screeningId }); + + _db.Execute(con, tx, + "UPDATE Screenings SET AvailableSeats=AvailableSeats+1 WHERE ScreeningID=@id", + new Dictionary { ["@id"] = screeningId }); + return true; + }); + } + catch { return false; } + } + + private static void GenerateSeats(MySqlConnection con, MySqlTransaction tx, int screeningId, int totalSeats) + { + // Quelle/Hilfe: Eigene Implementierung (kein Unterrichtsthema), Layout-Logik dokumentiert. + // Layout: + // - Standardmäßig 10 Sitze pro Reihe. + // - Reihen A, B, C, ... + // - VIP: Reihen C und D (falls vorhanden) + var seatsPerRow = 10; + var rowsNeeded = (int)Math.Ceiling(totalSeats / (double)seatsPerRow); + var created = 0; + for (var i = 0; i < rowsNeeded; i++) + { + var rowChar = (char)('A' + i); + for (var seat = 1; seat <= seatsPerRow && created < totalSeats; seat++) + { + var category = (rowChar == 'C' || rowChar == 'D') ? "VIP" : "Standard"; + using var cmd = new MySqlCommand( + "INSERT INTO Seats (ScreeningID, Row, SeatNumber, IsBooked, Category) VALUES (@sid,@r,@n,0,@c)", + con, tx); + cmd.Parameters.AddWithValue("@sid", screeningId); + cmd.Parameters.AddWithValue("@r", rowChar.ToString()); + cmd.Parameters.AddWithValue("@n", seat); + cmd.Parameters.AddWithValue("@c", category); + cmd.ExecuteNonQuery(); + created++; + } + } + } + + private Booking MapBooking(MySqlDataReader r) => new Booking + { + BookingID = r.GetInt32("BookingID"), + UserID = r.GetInt32("UserID"), + Username = r.GetString("Username"), + ScreeningID = r.GetInt32("ScreeningID"), + MovieTitle = r.GetString("MovieTitle"), + ScreeningDate = r.GetDateTime("ScreeningDate"), + StartTime = r.GetTimeSpan("StartTime"), + SeatID = r.GetInt32("SeatID"), + SeatDisplay = r.GetString("SeatDisplay"), + BookingDate = r.GetDateTime("BookingDate"), + TotalPrice = r.GetDecimal("TotalPrice"), + Status = r.GetString("Status"), + BookingCode = r.IsDBNull(r.GetOrdinal("BookingCode")) ? "" : r.GetString("BookingCode") + }; + + // ==================== STATS ==================== + public (int movies, int screenings, int bookings, int users) GetStats() + { + var m = _db.Query("SELECT COUNT(*) FROM Movies WHERE IsActive=1", r => r.GetInt32(0)); + var s = _db.Query("SELECT COUNT(*) FROM Screenings WHERE ScreeningDate >= CURDATE()", r => r.GetInt32(0)); + var b = _db.Query("SELECT COUNT(*) FROM Bookings WHERE Status='Confirmed'", r => r.GetInt32(0)); + var u = _db.Query("SELECT COUNT(*) FROM Users", r => r.GetInt32(0)); + return (m[0], s[0], b[0], u[0]); + } + + public static string HashPassword(string password) + { + // Quelle: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.sha256 (abgerufen: 2026-02-01) + using var sha = SHA256.Create(); + var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(password)); + return Convert.ToHexString(bytes).ToLower(); + } +} diff --git a/Helpers/Session.cs b/Helpers/Session.cs new file mode 100644 index 0000000..126843e --- /dev/null +++ b/Helpers/Session.cs @@ -0,0 +1,11 @@ +using CineBook.Models; + +namespace CineBook.Helpers; + +public static class Session +{ + public static User? CurrentUser { get; set; } + public static bool IsLoggedIn => CurrentUser != null; + public static bool IsAdmin => CurrentUser?.IsAdmin == true; + public static void Logout() => CurrentUser = null; +} diff --git a/Models/Models.cs b/Models/Models.cs new file mode 100644 index 0000000..c84bd84 --- /dev/null +++ b/Models/Models.cs @@ -0,0 +1,76 @@ +using System; + +namespace CineBook.Models; + +public class User +{ + public int UserID { get; set; } + public string Username { get; set; } = ""; + public string PasswordHash { get; set; } = ""; + public string Role { get; set; } = "User"; + public string Email { get; set; } = ""; + public DateTime CreatedAt { get; set; } + public bool IsAdmin => Role == "Admin"; +} + +public class Movie +{ + public int MovieID { get; set; } + public string Title { get; set; } = ""; + public string Genre { get; set; } = ""; + public int DurationMinutes { get; set; } + public string Description { get; set; } = ""; + public decimal Rating { get; set; } + public string PosterUrl { get; set; } = ""; + public bool IsActive { get; set; } = true; + public DateTime CreatedAt { get; set; } + public string DurationDisplay => $"{DurationMinutes} Min."; + public string RatingDisplay => $"★ {Rating:F1}"; +} + +public class Screening +{ + public int ScreeningID { get; set; } + public int MovieID { get; set; } + public string MovieTitle { get; set; } = ""; + public DateTime ScreeningDate { get; set; } + public TimeSpan StartTime { get; set; } + public string Hall { get; set; } = ""; + public int TotalSeats { get; set; } + public int AvailableSeats { get; set; } + public decimal PricePerSeat { get; set; } + public string DateTimeDisplay => $"{ScreeningDate:dd.MM.yyyy} {StartTime:hh\\:mm}"; + public string SeatsDisplay => $"{AvailableSeats}/{TotalSeats} frei"; + public string PriceDisplay => $"{PricePerSeat:F2} €"; +} + +public class Seat +{ + public int SeatID { get; set; } + public int ScreeningID { get; set; } + public string Row { get; set; } = ""; + public int SeatNumber { get; set; } + public bool IsBooked { get; set; } + public string Category { get; set; } = "Standard"; + public string Display => $"{Row}{SeatNumber}"; +} + +public class Booking +{ + public int BookingID { get; set; } + public int UserID { get; set; } + public string Username { get; set; } = ""; + public int ScreeningID { get; set; } + public string MovieTitle { get; set; } = ""; + public DateTime ScreeningDate { get; set; } + public TimeSpan StartTime { get; set; } + public int SeatID { get; set; } + public string SeatDisplay { get; set; } = ""; + public DateTime BookingDate { get; set; } + public decimal TotalPrice { get; set; } + public string Status { get; set; } = "Confirmed"; + public string BookingCode { get; set; } = ""; + public string PriceDisplay => $"{TotalPrice:F2} €"; + public string DateDisplay => BookingDate.ToString("dd.MM.yyyy HH:mm"); + public string ScreeningDisplay => $"{ScreeningDate:dd.MM.yyyy} {StartTime:hh\\:mm}"; +} diff --git a/Views/AdminPanelView.xaml b/Views/AdminPanelView.xaml new file mode 100644 index 0000000..346a960 --- /dev/null +++ b/Views/AdminPanelView.xaml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + +