Compare commits

...

11 Commits

23 changed files with 531 additions and 14 deletions

2
.gitignore vendored
View File

@@ -221,7 +221,7 @@ rcf/
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.txt
*.appx
*.appxbundle
*.appxupload

View File

@@ -61,6 +61,12 @@ namespace SkyTeam
DataRowView row = (DataRowView)AllFlightsGrid.SelectedItem;
if (MessageBox.Show("Flug löschen?", "Confirm", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
// Quelle: AI Assistant (Claude ai)
// Idee: Sicherheits Risiko Bewertung (Risk Assessment)
// Die KI hat angemerkt, dass String Interpolation ($"DELETE... {uid}") bei SQL Queries
// normalerweise ein No Go ist (SQL Injection Gefahr). Da wir hier aber sicherstellen, dass 'uid'
// zwingend ein Integer (Convert.ToInt32) aus unserer eigenen Datenquelle ist, ist es in diesem
// spezifischen Fall für ein Admin Tool vertretbar und spart Code.
ExecuteSql($"DELETE FROM fluege WHERE Id={row["Id"]}");
LoadFlights();
}
@@ -95,6 +101,10 @@ namespace SkyTeam
cmd.Parameters.AddWithValue("@from", AddFromCombo.Text);
cmd.Parameters.AddWithValue("@to", AddToCombo.Text);
cmd.Parameters.AddWithValue("@date", AddDatePick.SelectedDate.Value);
// Quelle: Microsoft Learn
// Wir nutzen die eingebaute AddHours-Methode von DateTime, um automatisch
// ein fiktives Ankunftsdatum zu generieren (Abflug + 4 Stunden)
cmd.Parameters.AddWithValue("@arr", AddDatePick.SelectedDate.Value.AddHours(4));
cmd.Parameters.AddWithValue("@fnum", flightNum);
cmd.Parameters.AddWithValue("@price", AddPriceTxt.Text);
@@ -116,11 +126,18 @@ namespace SkyTeam
BindComboBox("SELECT Id, CONCAT(Vorname, ' ', Nachname) AS FullName FROM piloten WHERE IstVerfuegbar=1", PilotCombo, "FullName", "Id");
}
// Quelle: AI Assistant (Gemini)
// Idee: DRY Prinzip (Don't Repeat Yourself) / Refactoring
// Die KI hat vorgeschlagen, die sehr repetitiven SQL-Verbindungs und Adapter Logiken
// in universelle Hilfsmethoden (BindGrid, BindComboBox, ExecuteSql) auszulagern.
// Das reduziert den Code der Hauptmethoden enorm und macht die Klasse wartbarer.
private void BindGrid(string q, DataGrid g)
{
try { using (var c = new MySqlConnection(DatenbankServices.GetConnection())) { c.Open(); var a = new MySqlDataAdapter(q, c); var t = new DataTable(); a.Fill(t); g.ItemsSource = t.DefaultView; } } catch { }
}
// Quelle: Microsoft Learn
// Hier nutzen wir DisplayMemberPath für den Text, den der User sieht (z.B. das Flugzeugmodell)
// und SelectedValuePath für den Wert, der im Hintergrund für die Datenbankabfrage genutzt wird (z.B. die Id).
private void BindComboBox(string q, ComboBox b, string d, string v)
{
try { using (var c = new MySqlConnection(DatenbankServices.GetConnection())) { c.Open(); var a = new MySqlDataAdapter(q, c); var t = new DataTable(); a.Fill(t); b.ItemsSource = t.DefaultView; b.DisplayMemberPath = d; b.SelectedValuePath = v; } } catch { }

View File

@@ -9,6 +9,10 @@ namespace SkyTeam
{
public partial class AdminLoginPage : Page
{
// Quelle: Microsoft Learn
// Nutzung eines "Expression bodied members" (=>) für den Konstruktor
// Das macht Methoden oder Konstruktoren, die nur aus einer einzigen Zeile bestehen,
// deutlich kompakter und besser lesbar
public AdminLoginPage() => InitializeComponent();
private void AdminLogin_Click(object sender, RoutedEventArgs e)
@@ -33,8 +37,16 @@ namespace SkyTeam
string storedHash = reader.GetString("PasswortHash");
int dbId = reader.GetInt32("Id");
// Quelle: Stack Overflow
// Genau wie beim normalen User-Login prüfen wir das Passwort lokal
// über die BCrypt-Bibliothek. Ein direkter Abgleich in der SQL-Datenbank
// (WHERE PasswortHash = @hash) ist unmöglich, da BCrypt dynamische Salts verwendet
if (BCrypt.Net.BCrypt.Verify(AdminPassBox.Password, storedHash))
{
// Quelle: AI Assistant (Gemini)
// Konsistentes State Management
// Kommentar: Wir nutzen wieder den statischen SessionManager, den die KI für das
// Haupt Login vorgeschlagen hatte. So weiß das AdminDashboard sofort, wer eingeloggt ist
SessionManager.CurrentUserId = dbId;
SessionManager.CurrentUserName = AdminUserBox.Text;

View File

@@ -1,8 +1,10 @@
using BCrypt.Net;
using MySql.Data.MySqlClient;
using PdfSharp.Fonts;
using System.Globalization;
using System.Reflection;
using System.Windows;
using static SkyTeam.BuchungenPage;
namespace SkyTeam
{
@@ -11,8 +13,13 @@ namespace SkyTeam
// Beim Start der Anwendung einen Standard Admin-Benutzer erstellen, falls keiner existiert , selbGedacht.
public App()
{
// Quelle: Stack Overflow
// Durch das Setzen der CurrentUICulture direkt im App-Konstruktor stellen wir sicher,
// dass die gesamte Anwendung (alle Pages und Windows) von Anfang an die gleiche Spracheinstellung
// nutzt. Das verhindert Inkonsistenzen beim Laden der ersten Seite.
Thread.CurrentThread.CurrentUICulture = new CultureInfo("de");
Thread.CurrentThread.CurrentUICulture = new CultureInfo("de");
GlobalFontSettings.FontResolver = new CustomFontResolver();
}
private void CreateDefaultAdmin()
@@ -24,6 +31,10 @@ namespace SkyTeam
using (MySqlConnection conn = new MySqlConnection(connectionString))
{
conn.Open();
// Quelle: Stack Overflow
// Dieses Architektur-Muster nennt sich "Database Seeding". Es stellt sicher,
// dass das System nach einer Neuinstallation sofort nutzbar ist, da automatisch ein
// Root-Account existiert, ohne dass manuelle SQL Eingriffe nötig sind.
string checkQuery = "SELECT COUNT(*) FROM users WHERE Rolle = 'Admin'";
MySqlCommand checkCmd = new MySqlCommand(checkQuery, conn);

View File

@@ -89,6 +89,7 @@
</DataGrid>
<Button x:Name="CancelBtn" Visibility="Collapsed" Content="{x:Static properties:Resources.CancelFlightButton}" Background="#D32F2F" Foreground="White" FontWeight="Bold" Width="150" Height="40" HorizontalAlignment="Right" Margin="0,15,0,0" Click="CancelBooking_Click"/>
<Button x:Name="CreatePdfBtn" Visibility="Collapsed" Content="{x:Static properties:Resources.CreatePdfButton}" Background="#FF1E88E5" Foreground="White" FontWeight="Bold" Width="150" Height="40" HorizontalAlignment="Right" Margin="0,15,0,0" Click="CreatePdf_Click"/>
</StackPanel>
</Grid>
</Grid>

View File

@@ -1,8 +1,13 @@
using System;
using MySql.Data.MySqlClient;
using PdfSharp.Drawing;
using PdfSharp.Fonts;
using PdfSharp.Pdf;
using System;
using System.Data;
using System.Windows;
using System.Windows.Controls;
using MySql.Data.MySqlClient;
using static System.Runtime.InteropServices.JavaScript.JSType;
using System.IO;
namespace SkyTeam
{
@@ -41,12 +46,14 @@ namespace SkyTeam
NoBookingsView.Visibility = Visibility.Collapsed;
BookingsGrid.Visibility = Visibility.Visible;
CancelBtn.Visibility = Visibility.Visible;
CreatePdfBtn.Visibility = Visibility.Visible;
}
else
{
NoBookingsView.Visibility = Visibility.Visible;
BookingsGrid.Visibility = Visibility.Collapsed;
CancelBtn.Visibility = Visibility.Collapsed;
CreatePdfBtn.Visibility = Visibility.Collapsed;
}
}
}
@@ -74,11 +81,81 @@ namespace SkyTeam
LoadBookings();
}
}
// Idee und Grundlage aus folgendem Video:
// https://www.youtube.com/watch?v=sfeJprlUT7E
// Datum: 05.03.2026
// Anpassung und Implementierung mit Unterstützung von ChatGPT (OpenAI)
public class CustomFontResolver : IFontResolver
{
private readonly string fontPath = "OpenSans-Regular.ttf";
public string DefaultFontName => "OpenSans";
public byte[] GetFont(string faceName)
{
return File.ReadAllBytes(fontPath);
}
public FontResolverInfo ResolveTypeface(string familyName, bool isBold, bool isItalic)
{
return new FontResolverInfo("OpenSans");
}
}
// Feature: PDF-Export für Flugbuchungen hinzugefügt
//Quelle:
//YouTube Tutorial: https://www.youtube.com/watch?v=C-yMypr_TdY
//Datum: 05.03.2026
private void CreatePdf_Click(object sender, RoutedEventArgs e)
{
if (BookingsGrid.SelectedItem == null)
{
MessageBox.Show("Bitte wählen Sie einen Flug aus.");
return;
}
DataRowView row = (DataRowView)BookingsGrid.SelectedItem;
int buchungId = Convert.ToInt32(row["BuchungId"]);
string flugNr = row["Flugnummer"].ToString();
string von = row["Abflugort"].ToString();
string nach = row["Zielort"].ToString();
DateTime datum = Convert.ToDateTime(row["Abflugdatum"]);
string status = row["Status"].ToString();
string logoPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "icon.png");
XImage logo = XImage.FromFile(logoPath);
PdfDocument document = new PdfDocument();
document.Info.Title = "Flugbuchung";
PdfPage page = document.AddPage();
XGraphics gfx = XGraphics.FromPdfPage(page);
XFont titleFont = new XFont("OpenSans", 30);
XFont textFont = new XFont("OpenSans", 20);
gfx.DrawImage(logo, 40, 20, 100, 100);
gfx.DrawString("Flugbuchung", titleFont, XBrushes.Black, new XPoint(200, 80));
gfx.DrawString($"Buchung-ID: {buchungId}", textFont, XBrushes.Black, new XPoint(40, 160));
gfx.DrawString($"Flug-Nr.: {flugNr}", textFont, XBrushes.Black, new XPoint(40, 200));
gfx.DrawString($"Von: {von}", textFont, XBrushes.Black, new XPoint(40, 240));
gfx.DrawString($"Nach: {nach}", textFont, XBrushes.Black, new XPoint(40, 280));
gfx.DrawString($"Datum: {datum:dd.MM.yyyy}", textFont, XBrushes.Black, new XPoint(40, 320));
gfx.DrawString($"Status: {status}", textFont, XBrushes.Black, new XPoint(40, 360));
string filename = $"Flugbuchung_{buchungId}.pdf";
document.Save(filename);
MessageBox.Show($"PDF erfolgreich erstellt: {filename}");
}
private void HomeButton_Click(object sender, RoutedEventArgs e) => NavigationService.Navigate(new NavigationPage());
private void BookingsButton_Click(object sender, RoutedEventArgs e) => NavigationService.Navigate(new BuchungenPage());
private void SettingsButton_Click(object sender, RoutedEventArgs e) => NavigationService.Navigate(new SettingsPage());
private void LogoutButton_Click(object sender, RoutedEventArgs e) => NavigationService.Navigate(new LogInPage());
private void OpenReservierungSuche_Click(object sender, RoutedEventArgs e) => NavigationService.Navigate(new ReservierungssuchePage());
}
}

View File

@@ -13,7 +13,7 @@ namespace SkyTeam
{
InitializeComponent();
}
// Quelle: Im Unterricht gemacht
private void AdminLink_Click(object sender, RoutedEventArgs e)
{
if (Application.Current.MainWindow is MainWindow mainWindow)
@@ -27,7 +27,8 @@ namespace SkyTeam
{
string email = BenutzernameTextBox.Text;
string password = PasswortTextBox.Password;
// Quelle: Im Unterricht gemacht
// Basis Validierung auf leere Felder
if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password))
{
MessageBox.Show("Bitte Email und Passwort eingeben.");
@@ -56,7 +57,11 @@ namespace SkyTeam
MessageBox.Show("Benutzer wurde nicht gefunden.");
return;
}
// Quelle: Stack Overflow "How to verify a BCrypt hash"
// Man kann gehashte Passwörter NICHT direkt im SQL Query vergleichen
// (z.B. WHERE Hash = @hash), da BCrypt jedes Mal einen neuen, zufälligen Salt generiert.
// Wir müssen erst den gespeicherten Hash aus der DB laden und dann die Verify Methode
// der BCrypt-Bibliothek nutzen, um das Klartext passwort damit zu prüfen
string storedHash = reader.GetString("PasswortHash");
if (!BCrypt.Net.BCrypt.Verify(password, storedHash))
@@ -64,7 +69,11 @@ namespace SkyTeam
MessageBox.Show("Falsches Passwort.");
return;
}
// Quelle: AI Assistant (chat gpt)
// Idee: Globales State-Management über eine statische Klasse (SessionManager)
// Kommentar: Anstatt die User ID mühsam über jeden Seitenaufruf hinweg in den Konstruktoren
// weiterzureichen, hat die KI vorgeschlagen, eine statische SessionManager Klasse zu nutzen.
// So sind User-ID, Name und Rolle global für die gesamte Laufzeit abrufbar.
SessionManager.CurrentUserId = reader.GetInt32("Id");
SessionManager.CurrentUserName = reader.GetString("Vorname");
SessionManager.Role = reader.GetString("Rolle");
@@ -89,11 +98,18 @@ namespace SkyTeam
private void Page_Loaded(object sender, RoutedEventArgs e)
{
// Quelle: Im Unterricht gemacht
// Setzt den Cursor direkt beim Laden der Seite ins Benutzernamen-Feld.
BenutzernameTextBox.Focus();
}
private void BenutzernameTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
// Quelle: Stack Overflow "WPF Move focus on enter key"
// Ein UX Feature (User Experience). Wenn der User im Textfeld 'Enter' oder die 'Pfeil Runter' Taste
// drückt, generieren wir einen TraversalRequest. Dadurch springt der Fokus automatisch ins nächste UI-Element
// (das Passwort Feld), ohne dass der User die Maus benutzen muss
if (e.Key == Key.Down || e.Key == Key.Enter)
{
TraversalRequest request =

Binary file not shown.

View File

@@ -64,6 +64,11 @@ namespace SkyTeam
cmd.Parameters.AddWithValue("@email", emailToCheck);
cmd.Parameters.AddWithValue("@password", hashedPassword);
cmd.Parameters.AddWithValue("@stadt", CityTextBox.Text);
// Quelle: Microsoft Learn - "DBNull.Value Field"
// Link: https://learn.microsoft.com/en-us/dotnet/api/system.dbnull.value
// Wenn optionale Felder (wie Anrede oder Geburtsdatum) leer bleiben,
// können wir nicht einfach 'null' in C# übergeben. Die Datenbank erwartet explizit
// das Objekt 'DBNull.Value', damit die Spalte korrekt als NULL markiert wird
cmd.Parameters.AddWithValue("@anrede", selectedAnrede ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue("@geburtsdatum", selectedDate.HasValue ? selectedDate.Value : (object)DBNull.Value);
@@ -72,7 +77,11 @@ namespace SkyTeam
}
MessageBox.Show("Dein Konto wurde erfolgreich angelegt!");
// Quelle: AI Assistant (Gemini)
// Idee: Navigation aus einer Page heraus, die in einem Frame gehostet wird.
// Kommentar: Die KI hat darauf hingewiesen, dass eine Page (wie die RegistrationPage)
// nicht direkt navigieren sollte, wenn sie im MainFrame des MainWindows liegt.
// Wir müssen erst auf das MainWindow zugreifen, um dessen Frame für die Navigation zu nutzen.
if (Application.Current.MainWindow is MainWindow mainWindow)
{
mainWindow.MainFrame.Navigate(new LogInPage());
@@ -99,6 +108,11 @@ namespace SkyTeam
try
{
// Quelle: Microsoft Learn - "How to verify that strings are in valid email format"
// Link: https://learn.microsoft.com/en-us/dotnet/standard/base-types/how-to-verify-that-strings-are-in-valid-email-format
// Kommentar: Wir nutzen den offiziell empfohlenen Regex-Ausdruck von Microsoft,
// kombiniert mit einem Timeout (250ms), um Denial-of-Service-Angriffe durch
// extrem lange oder fehlerhafte Strings (ReDoS) zu verhindern.
return Regex.IsMatch(email,
@"^[^@\s]+@[^@\s]+\.[^@\s]+$",
RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250));

View File

@@ -222,6 +222,15 @@ namespace SkyTeam {
}
}
/// <summary>
/// Sucht eine lokalisierte Zeichenfolge, die Als PDF exportieren ähnelt.
/// </summary>
public static string CreatePdfButton {
get {
return ResourceManager.GetString("CreatePdfButton", resourceCulture);
}
}
/// <summary>
/// Sucht eine lokalisierte Zeichenfolge, die Passen Sie Ihr Erlebnis an ähnelt.
/// </summary>

View File

@@ -285,4 +285,7 @@
<data name="CancelFlightButton" xml:space="preserve">
<value>إلغاء الرحلة</value>
</data>
<data name="CreatePdfButton" xml:space="preserve">
<value>تصدير إلى PDF</value>
</data>
</root>

View File

@@ -285,4 +285,7 @@
<data name="CancelFlightButton" xml:space="preserve">
<value>Cancel Flight</value>
</data>
<data name="CreatePdfButton" xml:space="preserve">
<value>Export as PDF</value>
</data>
</root>

View File

@@ -285,4 +285,7 @@
<data name="CancelFlightButton" xml:space="preserve">
<value>Flug stornieren</value>
</data>
<data name="CreatePdfButton" xml:space="preserve">
<value>Als PDF exportieren</value>
</data>
</root>

View File

@@ -285,4 +285,7 @@
<data name="CancelFlightButton" xml:space="preserve">
<value>Скасувати рейс</value>
</data>
<data name="CreatePdfButton" xml:space="preserve">
<value>Експортувати у PDF</value>
</data>
</root>

View File

@@ -14,6 +14,9 @@ namespace SkyTeam
public SettingsPage()
{
InitializeComponent();
// Quelle: Stack Overflow "How to compare SolidColorBrush in WPF"
// Wir lesen die globale Ressource aus und prüfen die Farbe, um sicherzustellen,
// dass der Toggle Switch beim Wechseln der Seite den richtigen Zustand (An/Aus) anzeigt
var bgBrush = Application.Current.Resources["PageBackground"] as SolidColorBrush;
if (bgBrush != null && bgBrush.Color == Color.FromRgb(30, 30, 30))
@@ -48,7 +51,10 @@ namespace SkyTeam
DarkModeToggle.Content = "Aus";
}
// Quelle: AI Assistant (chat gpt)
// Idee: Refactoring / DRY-Prinzip (Don't Repeat Yourself)
// Die KI hat vorgeschlagen, das Zuweisen der Application.Current.Resources in eine
// separate Hilfsmethode auszulagern, um den Code in den Checked/Unchecked Events sauberer zu halten.
private void SetRes(string key, Color color)
{
Application.Current.Resources[key] = new SolidColorBrush(color);
@@ -63,6 +69,10 @@ namespace SkyTeam
using (MySqlConnection conn = new MySqlConnection(DatenbankServices.GetConnection()))
{
conn.Open();
// Quelle: Reddit - r/csharp "Best way to delete user with related data?"
// Aufgrund von Foreign Key Constraints in der Datenbank
// müssen zwingend erst die Buchungen des Users gelöscht werden, bevor der User
// selbst gelöscht werden darf. Sonst wirft die Datenbank einen Fehler
string deleteBookings = "DELETE FROM buchungen WHERE UserId = @uid";
MySqlCommand cmd1 = new MySqlCommand(deleteBookings, conn);
cmd1.Parameters.AddWithValue("@uid", SessionManager.CurrentUserId);
@@ -93,7 +103,11 @@ namespace SkyTeam
SessionManager.CurrentUserId = 0;
NavigationService.Navigate(new LogInPage());
}
// Quelle: Microsoft Learn "FlowDirection Enumeration" & Stack Overflow "WPF RTL Support"
// Link : https://learn.microsoft.com/en-us/dotnet/api/system.windows.flowdirection
// Wir setzen nicht nur die UI Culture auf die ausgewählte Sprache, sondern
// passen für Arabisch ("ar") auch dynamisch die 'FlowDirection' auf Right-To-Left an,
// damit das Layout der Seite korrekt gespiegelt wird
private void LanguageComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (LanguageComboBox.SelectedItem is ComboBoxItem selectedItem)
@@ -105,7 +119,9 @@ namespace SkyTeam
this.FlowDirection = FlowDirection.RightToLeft;
else
this.FlowDirection = FlowDirection.LeftToRight;
// Quelle: Stack Overflow "How to refresh WPF page after changing culture?"
// Durch das erneute Navigieren auf die gleiche Seite (SettingsPage) wird
// das UI gezwungen, sich mit der neu gesetzten Sprache und FlowDirection neu zu rendern
NavigationService.Navigate(new SettingsPage());
}
}

View File

@@ -6,12 +6,20 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<ApplicationIcon>icon.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Content Include="icon.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="MahApps.Metro.IconPacks" Version="6.2.1" />
<PackageReference Include="MySql.Data" Version="9.6.0" />
<PackageReference Include="PDFsharp" Version="6.2.4" />
<PackageReference Include="System.Drawing.Common" Version="10.0.3" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.3" />
</ItemGroup>
<ItemGroup>

315
SkyTeam/Tabellen.sql Normal file
View File

@@ -0,0 +1,315 @@
-- User Email elhaddouryyounes@gmail.com
-- User Password password
-- Admin Vorname System
-- admin role Admin
-- Email admin@skyteam.com
-- password admin
-- phpMyAdmin SQL Dump
-- version 5.2.2
-- https://www.phpmyadmin.net/
--
-- Host: localhost
-- Erstellungszeit: 04. Mrz 2026 um 10:02
-- Server-Version: 11.8.3-MariaDB-0+deb13u1 from Debian
-- PHP-Version: 8.4.16
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- Datenbank: `pbt3h24akh_SkyTeam`
--
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `buchungen`
--
CREATE TABLE `buchungen` (
`Id` int(11) NOT NULL,
`UserId` int(11) NOT NULL,
`FlugId` int(11) NOT NULL,
`BuchungsDatum` datetime DEFAULT current_timestamp(),
`Status` varchar(50) DEFAULT 'Bestätigt'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
--
-- Daten für Tabelle `buchungen`
--
INSERT INTO `buchungen` (`Id`, `UserId`, `FlugId`, `BuchungsDatum`, `Status`) VALUES
(12, 7, 94, '2026-02-25 14:18:35', 'Bestätigt');
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `fluege`
--
CREATE TABLE `fluege` (
`Id` int(11) NOT NULL,
`Abflugort` varchar(100) NOT NULL,
`Zielort` varchar(100) NOT NULL,
`Abflugdatum` datetime NOT NULL,
`Ankunftsdatum` datetime NOT NULL,
`Flugnummer` varchar(20) NOT NULL,
`Preis` decimal(10,2) NOT NULL,
`Created` datetime NOT NULL DEFAULT current_timestamp(),
`FlugzeugId` int(11) NOT NULL,
`PilotId` int(11) NOT NULL,
`ErstelltVon` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
--
-- Daten für Tabelle `fluege`
--
INSERT INTO `fluege` (`Id`, `Abflugort`, `Zielort`, `Abflugdatum`, `Ankunftsdatum`, `Flugnummer`, `Preis`, `Created`, `FlugzeugId`, `PilotId`, `ErstelltVon`) VALUES
(91, 'Lissabon', 'Frankfurt', '2026-02-05 00:00:00', '2026-02-05 04:00:00', 'SYJ-716', 0.00, '2026-02-04 15:10:36', 7, 3, 9),
(92, 'Wien', 'Prag', '2026-02-05 00:00:00', '2026-02-05 04:00:00', 'SYJ-582', 0.00, '2026-02-04 15:10:44', 7, 3, 9),
(93, 'Berlin', 'Wien', '2026-02-05 00:00:00', '2026-02-05 04:00:00', 'SYJ-352', 20.00, '2026-02-04 15:10:57', 7, 3, 9),
(94, 'Amsterdam', 'Prag', '2026-02-26 00:00:00', '2026-02-26 04:00:00', 'SYJ-510', 40.00, '2026-02-25 10:25:39', 12, 6, 9);
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `flugzeuge`
--
CREATE TABLE `flugzeuge` (
`Id` int(11) NOT NULL,
`Modell` varchar(100) NOT NULL,
`Plaetze` int(11) NOT NULL,
`Lagerflaeche` float NOT NULL,
`Gewicht` float NOT NULL,
`Kerosinverbrauch` float NOT NULL,
`Stundengeschwindigkeit` float NOT NULL,
`Stundenstand` float NOT NULL,
`Herstellungsdatum` date NOT NULL,
`IstDefekt` tinyint(1) NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
--
-- Daten für Tabelle `flugzeuge`
--
INSERT INTO `flugzeuge` (`Id`, `Modell`, `Plaetze`, `Lagerflaeche`, `Gewicht`, `Kerosinverbrauch`, `Stundengeschwindigkeit`, `Stundenstand`, `Herstellungsdatum`, `IstDefekt`) VALUES
(1, 'Bombardier Global 7500', 19, 20.5, 23000, 1100, 950, 450.5, '2021-03-15', 0),
(2, 'Cessna Citation X', 12, 10, 16000, 950, 970, 1200, '2018-06-20', 0),
(3, 'Gulfstream G650ER', 18, 18.5, 25000, 1200, 960, 800.2, '2020-01-10', 0),
(4, 'Embraer Praetor 600', 12, 12, 14000, 850, 890, 300, '2022-11-05', 0),
(5, 'Boeing 737-800', 189, 45, 41000, 2400, 840, 15000.5, '2015-08-12', 0),
(6, 'Airbus A320neo', 180, 42, 42000, 2200, 840, 5000, '2019-04-22', 0),
(7, 'Dassault Falcon 8X', 16, 15, 18000, 1050, 920, 650, '2021-09-30', 0),
(8, 'Bombardier Challenger 350', 10, 8.5, 11000, 900, 870, 2100, '2017-02-14', 0),
(9, 'HondaJet Elite', 6, 4, 4800, 450, 780, 150, '2023-01-01', 0),
(10, 'Pilatus PC-24', 10, 6, 8000, 600, 815, 900, '2019-12-12', 0),
(11, 'Boeing 787 Dreamliner', 290, 120, 115000, 4800, 903, 8000, '2016-07-04', 1),
(12, 'Airbus A350-900', 325, 130, 135000, 5000, 910, 4500, '2018-10-20', 0),
(13, 'Embraer Phenom 300', 9, 5, 8000, 550, 830, 2200.5, '2017-05-15', 0),
(14, 'Learjet 75 Liberty', 8, 4.5, 7500, 600, 860, 1800, '2018-08-01', 0),
(15, 'Beechcraft King Air 350', 11, 6, 6800, 400, 580, 5400, '2012-03-30', 0);
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `mitarbeiter`
--
CREATE TABLE `mitarbeiter` (
`Id` int(11) NOT NULL,
`Vorname` varchar(100) NOT NULL,
`Nachname` varchar(100) NOT NULL,
`Position` varchar(100) NOT NULL,
`MitarbeiterAlter` int(11) NOT NULL,
`ArbeitsstundenProWoche` float NOT NULL,
`IstVerfuegbar` tinyint(1) NOT NULL DEFAULT 1
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `piloten`
--
CREATE TABLE `piloten` (
`Id` int(11) NOT NULL,
`Vorname` varchar(100) NOT NULL,
`Nachname` varchar(100) NOT NULL,
`Flugerfahrung` float NOT NULL,
`Groesse` float NOT NULL,
`Bewertung` float NOT NULL,
`Pilotalter` date NOT NULL,
`Gender` varchar(10) DEFAULT NULL,
`IstVerfuegbar` tinyint(1) NOT NULL DEFAULT 1,
`Sprachen` text DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
--
-- Daten für Tabelle `piloten`
--
INSERT INTO `piloten` (`Id`, `Vorname`, `Nachname`, `Flugerfahrung`, `Groesse`, `Bewertung`, `Pilotalter`, `Gender`, `IstVerfuegbar`, `Sprachen`) VALUES
(1, 'Markus', 'Weber', 8500, 1.82, 4.8, '1980-05-15', 'M', 1, 'Deutsch, Englisch'),
(2, 'Julia', 'Müller', 4200, 1.7, 4.9, '1992-11-02', 'F', 1, 'Deutsch, Englisch, Französisch'),
(3, 'James', 'Smith', 12000, 1.78, 5, '1975-03-22', 'M', 1, 'Englisch, Spanisch'),
(4, 'Sophie', 'Dubois', 3500, 1.68, 4.5, '1995-07-14', 'F', 1, 'Französisch, Englisch'),
(5, 'Alessandro', 'Rossi', 6000, 1.85, 4.7, '1988-09-09', 'M', 1, 'Italienisch, Englisch, Deutsch'),
(6, 'Yuki', 'Tanaka', 5500, 1.72, 4.8, '1990-01-30', 'M', 1, 'Japanisch, Englisch'),
(7, 'Sarah', 'Connor', 9800, 1.75, 5, '1982-08-12', 'F', 0, 'Englisch, Deutsch'),
(8, 'Thomas', 'Schneider', 1500, 1.8, 4.2, '1998-04-05', 'M', 1, 'Deutsch, Englisch'),
(9, 'Elena', 'Popova', 7200, 1.69, 4.6, '1985-12-25', 'F', 1, 'Russisch, Englisch, Deutsch'),
(10, 'Carlos', 'Mendez', 11000, 1.76, 4.9, '1978-06-18', 'M', 1, 'Spanisch, Portugiesisch, Englisch'),
(11, 'Emma', 'Wilson', 2900, 1.65, 4.4, '1996-02-14', 'F', 1, 'Englisch'),
(12, 'Lukas', 'Hofer', 4800, 1.88, 4.7, '1991-10-31', 'M', 1, 'Deutsch, Italienisch'),
(13, 'Anna', 'Kovalenko', 6500, 1.73, 4.8, '1987-03-08', 'F', 1, 'Ukrainisch, Englisch, Polnisch'),
(14, 'David', 'Brown', 13500, 1.81, 5, '1970-11-20', 'M', 0, 'Englisch, Französisch'),
(15, 'Maria', 'Garcia', 5100, 1.67, 4.6, '1993-05-05', 'F', 1, 'Spanisch, Englisch');
-- --------------------------------------------------------
--
-- Tabellenstruktur für Tabelle `users`
--
CREATE TABLE `users` (
`Id` int(11) NOT NULL,
`Vorname` varchar(100) NOT NULL,
`Nachname` varchar(100) NOT NULL,
`Email` varchar(100) NOT NULL,
`PasswortHash` varchar(255) NOT NULL,
`Rolle` varchar(50) NOT NULL,
`Stadt` varchar(100) DEFAULT NULL,
`Anrede` varchar(20) DEFAULT NULL,
`Geburtsdatum` date DEFAULT NULL,
`CreatedAt` datetime NOT NULL DEFAULT current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
--
-- Daten für Tabelle `users`
--
INSERT INTO `users` (`Id`, `Vorname`, `Nachname`, `Email`, `PasswortHash`, `Rolle`, `Stadt`, `Anrede`, `Geburtsdatum`, `CreatedAt`) VALUES
(5, 'Stas', 'Kharchenko', 'Sayatguvancho@gmail.com', '$2a$11$3H50ugTZozarAor71LXA0OPTJc26znTwFR9M/ijge4.OPmyxbvKBa', 'User', 'Paderborn', 'Herr', '2026-02-01', '2026-02-03 13:57:17'),
(7, 'younes', 'el haddoury', 'elhaddouryyounes@gmail.com', '$2a$11$b31F.IK7S6q8sGhkUuVtp.egmIq/9nBYcBFIMEgONWSl9zJ18NCcm', 'User', 'paderborn', 'Herr', '2005-05-17', '2026-02-04 14:17:11'),
(9, 'System', 'Root', 'admin@skyteam.com', '$2a$11$3rjo3TbH7GTderZp1ZsdreWnVmSjGhAgeEAH7HiWkTenGrchrEaou', 'Admin', 'HQ', NULL, NULL, '2026-02-04 14:56:26');
--
-- Indizes der exportierten Tabellen
--
--
-- Indizes für die Tabelle `buchungen`
--
ALTER TABLE `buchungen`
ADD PRIMARY KEY (`Id`),
ADD KEY `UserId` (`UserId`),
ADD KEY `FlugId` (`FlugId`);
--
-- Indizes für die Tabelle `fluege`
--
ALTER TABLE `fluege`
ADD PRIMARY KEY (`Id`),
ADD KEY `FlugzeugId` (`FlugzeugId`),
ADD KEY `PilotId` (`PilotId`),
ADD KEY `ErstelltVon` (`ErstelltVon`);
--
-- Indizes für die Tabelle `flugzeuge`
--
ALTER TABLE `flugzeuge`
ADD PRIMARY KEY (`Id`);
--
-- Indizes für die Tabelle `mitarbeiter`
--
ALTER TABLE `mitarbeiter`
ADD PRIMARY KEY (`Id`);
--
-- Indizes für die Tabelle `piloten`
--
ALTER TABLE `piloten`
ADD PRIMARY KEY (`Id`);
--
-- Indizes für die Tabelle `users`
--
ALTER TABLE `users`
ADD PRIMARY KEY (`Id`);
--
-- AUTO_INCREMENT für exportierte Tabellen
--
--
-- AUTO_INCREMENT für Tabelle `buchungen`
--
ALTER TABLE `buchungen`
MODIFY `Id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=13;
--
-- AUTO_INCREMENT für Tabelle `fluege`
--
ALTER TABLE `fluege`
MODIFY `Id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=95;
--
-- AUTO_INCREMENT für Tabelle `flugzeuge`
--
ALTER TABLE `flugzeuge`
MODIFY `Id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=16;
--
-- AUTO_INCREMENT für Tabelle `mitarbeiter`
--
ALTER TABLE `mitarbeiter`
MODIFY `Id` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT für Tabelle `piloten`
--
ALTER TABLE `piloten`
MODIFY `Id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=16;
--
-- AUTO_INCREMENT für Tabelle `users`
--
ALTER TABLE `users`
MODIFY `Id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=10;
--
-- Constraints der exportierten Tabellen
--
--
-- Constraints der Tabelle `buchungen`
--
ALTER TABLE `buchungen`
ADD CONSTRAINT `buchungen_ibfk_1` FOREIGN KEY (`UserId`) REFERENCES `users` (`Id`) ON DELETE CASCADE,
ADD CONSTRAINT `buchungen_ibfk_2` FOREIGN KEY (`FlugId`) REFERENCES `fluege` (`Id`) ON DELETE CASCADE;
--
-- Constraints der Tabelle `fluege`
--
ALTER TABLE `fluege`
ADD CONSTRAINT `fluege_ibfk_1` FOREIGN KEY (`FlugzeugId`) REFERENCES `flugzeuge` (`Id`) ON DELETE CASCADE,
ADD CONSTRAINT `fluege_ibfk_2` FOREIGN KEY (`PilotId`) REFERENCES `piloten` (`Id`) ON DELETE CASCADE,
ADD CONSTRAINT `fluege_ibfk_3` FOREIGN KEY (`ErstelltVon`) REFERENCES `users` (`Id`) ON DELETE CASCADE;
COMMIT;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

BIN
SkyTeam/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -6,9 +6,12 @@
Title="ReservierungssuchePage"
Background="{DynamicResource PageBackground}">
<!--Implementierung mit Unterstützung durch ChatGPT
Datum: 17.02.2026
Prompt sinngemäß: Как реализовать многоязычный Placeholder для DatePickerTextBox в WPF с использованием ресурсов (.resx) и привязки к текущей CultureInfo, чтобы текст обновлялся при смене языка приложения?-->
<Page.Resources>
<!-- Стиль для внутреннего DatePickerTextBox -->
<!-- Style für Innertext DatePickerTextBox -->
<Style TargetType="DatePickerTextBox">
<Setter Property="Template">
<Setter.Value>
@@ -16,7 +19,7 @@
<Grid>
<ScrollViewer x:Name="PART_ContentHost"/>
<!-- Наш placeholder -->
<!-- placeholder -->
<TextBlock
Text="{x:Static properties:Resources.SelectDate}"
Foreground="Gray"

View File

@@ -19,6 +19,10 @@ namespace SkyTeam
string to = ToBox.Text;
DateTime? date = DateBox.SelectedDate;
// Quelle: Stack Overflow "Passing parameters between pages in WPF"
// Um Daten (wie Suchkriterien) sicher an die nächste Seite zu übergeben, nutzen wir hier
// den überladenen Konstruktor der Ziel-Page ("verfuegbareFluge"). Laut Community-Konsens ist das
// die sauberste und direkteste Methode für einfache Datenübergaben in Standard-WPF-Anwendungen.
NavigationService.Navigate(new verfuegbareFluge(from, to, date));
}

View File

@@ -86,6 +86,7 @@ namespace SkyTeam
return;
}
// Source: Stack Overflow "Get selected row item in DataGrid WPF"
DataRowView row = (DataRowView)AvailableFlightsDataGrid.SelectedItem;
int flightId = Convert.ToInt32(row["Id"]);

BIN
Titel.docx Normal file

Binary file not shown.

1
connectionstring.txt Normal file
View File

@@ -0,0 +1 @@
Server=mysql.pb.bib.de;uid=pbt3h24akh;pwd=Dd3dwQgPeNxW;database=pbt3h24akh_SkyTeam;