feat: Implement Database Service and ViewModels for Messages and Support Requests

- Added DatabaseService to handle database operations for messages and support requests.
- Created IDatabaseService interface to define the contract for database operations.
- Developed ViewModels for Dashboard, Messages, and Support pages to manage data and commands.
- Implemented XAML views for Dashboard, Messages, and Support, including data binding and UI elements.
- Created SQL script for setting up the database schema and inserting test data.
This commit is contained in:
Van Leemput Dayron
2026-01-12 18:04:10 +01:00
parent 74586c20ba
commit f9690045ea
60 changed files with 4325 additions and 0 deletions

54
.gitignore vendored Normal file
View File

@@ -0,0 +1,54 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio cache/options directory
.vs/
# Visual Studio Code
.vscode/
# Rider
.idea/
# macOS
.DS_Store
# MAUI Specific
**/AppPackages/
**/BundleArtifacts/
**/Package.StoreAssociation.xml
# Configuration sensible - NE PAS COMMIT !
# Décommentez si vous voulez ignorer votre configuration locale
# **/Configuration/AppSettings.cs

294
ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,294 @@
# Architecture de TravelMate Admin
## 🏗️ Pattern MVVM (Model-View-ViewModel)
Cette application utilise strictement le pattern MVVM avec CommunityToolkit.Mvvm pour une séparation claire des responsabilités.
```
┌─────────────────────────────────────────────────────────┐
│ VIEW │
│ (XAML + Code-behind) │
│ DashboardPage │ MessagesPage │ SupportPage │
└─────────────┬───────────────────────────────────────────┘
│ Data Binding
┌─────────────────────────────────────────────────────────┐
│ VIEWMODEL │
│ (Business Logic + State) │
│ DashboardVM │ MessagesVM │ SupportVM │
└─────────────┬───────────────────────────────────────────┘
│ Calls
┌─────────────────────────────────────────────────────────┐
│ SERVICE │
│ (Data Access Layer) │
│ DatabaseService │
└─────────────┬───────────────────────────────────────────┘
│ SQL Queries
┌─────────────────────────────────────────────────────────┐
│ DATABASE │
│ MariaDB / MySQL │
│ messages │ support_requests │
└─────────────────────────────────────────────────────────┘
```
## 📂 Structure des Dossiers
### `/Models`
**Responsabilité** : Représentation des données métier
- `Message.cs` : Représente un message de demande
- `SupportRequest.cs` : Représente une demande support
**Caractéristiques** :
- POCOs (Plain Old CLR Objects)
- Propriétés avec getters/setters
- Propriétés calculées (FullName, StatusText, etc.)
- Pas de logique métier
### `/Services`
**Responsabilité** : Couche d'accès aux données
- `IDatabaseService.cs` : Contrat du service
- `DatabaseService.cs` : Implémentation avec MySqlConnector
**Caractéristiques** :
- Méthodes asynchrones (Task<T>)
- Gestion des connexions MySQL
- Gestion des erreurs avec try/catch
- Logs de débogage
**Méthodes principales** :
```csharp
Task<List<T>> GetAllAsync()
Task<List<T>> GetFilteredAsync(bool? isDone)
Task<int> GetPendingCountAsync()
Task<int> GetDoneCountAsync()
Task<bool> UpdateStatusAsync(int id, bool done)
Task<bool> TestConnectionAsync()
```
### `/ViewModels`
**Responsabilité** : Logique de présentation et état de l'UI
- `DashboardViewModel.cs` : Gestion du dashboard
- `MessagesViewModel.cs` : Gestion de la liste des messages
- `SupportViewModel.cs` : Gestion des demandes support
**Caractéristiques** :
- Hérite de `ObservableObject` (CommunityToolkit.Mvvm)
- Utilise `[ObservableProperty]` pour les propriétés bindables
- Utilise `[RelayCommand]` pour les commandes
- Injection de dépendance via constructeur
**Exemple de propriété observable** :
```csharp
[ObservableProperty]
private bool isLoading;
// Génère automatiquement IsLoading avec INotifyPropertyChanged
```
**Exemple de commande** :
```csharp
[RelayCommand]
public async Task LoadMessagesAsync()
{
// Logique de chargement
}
// Génère automatiquement LoadMessagesCommand : ICommand
```
### `/Views`
**Responsabilité** : Interface utilisateur
- `DashboardPage.xaml/.cs`
- `MessagesPage.xaml/.cs`
- `SupportPage.xaml/.cs`
**Caractéristiques** :
- XAML pour la structure UI
- Code-behind minimal (uniquement OnAppearing)
- Data Binding vers le ViewModel
- Converters pour la présentation
### `/Converters`
**Responsabilité** : Conversion de données pour l'affichage
- `BoolConverters.cs` :
- `BoolToStatusTextConverter` : bool → "Marquer comme fait"
- `BoolToColorConverter` : bool → Color
### `/Configuration`
**Responsabilité** : Configuration de l'application
- `AppSettings.cs` : Paramètres de connexion DB
## 🔄 Flux de Données
### 1. Chargement des Données
```
User Opens Page
OnAppearing() appelé
LoadCommand.Execute()
ViewModel appelle Service
Service exécute requête SQL
Données retournées au ViewModel
ViewModel met à jour ObservableCollection
INotifyPropertyChanged déclenché
UI se met à jour automatiquement
```
### 2. Action Utilisateur (Changer statut)
```
User clique sur Button
Command bindée exécutée
ViewModel.ToggleStatusCommand
Service.UpdateStatusAsync(id, !done)
SQL UPDATE exécuté
Success → ViewModel met à jour l'objet local
INotifyPropertyChanged → UI refresh
```
## 🧩 Injection de Dépendances
Configuration dans `MauiProgram.cs` :
```csharp
// Services (Singleton car un seul pour toute l'app)
builder.Services.AddSingleton<IDatabaseService, DatabaseService>();
// ViewModels
builder.Services.AddSingleton<DashboardViewModel>(); // Singleton = même instance
builder.Services.AddTransient<MessagesViewModel>(); // Transient = nouvelle instance
// Views
builder.Services.AddSingleton<DashboardPage>();
builder.Services.AddTransient<MessagesPage>();
```
**Pourquoi Singleton vs Transient ?**
- **Singleton** : Dashboard (état partagé, chargé une fois)
- **Transient** : Pages de liste (rafraîchies à chaque visite)
## 🎨 Navigation
Gestion via `AppShell.xaml` :
```xaml
<TabBar>
<ShellContent Title="Dashboard" Route="DashboardPage" ... />
<ShellContent Title="Messages" Route="MessagesPage" ... />
<ShellContent Title="Support" Route="SupportPage" ... />
</TabBar>
```
Navigation programmatique :
```csharp
await Shell.Current.GoToAsync("//MessagesPage");
```
## 🔐 Sécurité
**Actuellement** :
- Mot de passe en clair dans AppSettings.cs
**Pour la Production** :
- Utiliser variables d'environnement
- Chiffrer la chaîne de connexion
- Utiliser Azure Key Vault ou équivalent
- Implémenter authentification utilisateur
- Logger les actions sensibles
## 📊 Performance
**Optimisations actuelles** :
- Connexions fermées automatiquement (using)
- Requêtes asynchrones (async/await)
- Filtres côté base de données (WHERE clause)
- Tri côté base de données (ORDER BY)
**Optimisations futures possibles** :
- Pagination pour grandes listes
- Mise en cache des données
- Connection pooling (déjà géré par MySqlConnector)
- Lazy loading
## 🧪 Testabilité
Architecture testable grâce à :
- Interface `IDatabaseService` (mockable)
- ViewModels découplés des Views
- Injection de dépendances
- Logique métier dans ViewModels
Exemple de test unitaire (potentiel) :
```csharp
[Test]
public async Task LoadMessages_ShouldPopulateCollection()
{
// Arrange
var mockService = new Mock<IDatabaseService>();
mockService.Setup(s => s.GetAllMessagesAsync())
.ReturnsAsync(new List<Message> { /* ... */ });
var vm = new MessagesViewModel(mockService.Object);
// Act
await vm.LoadMessagesCommand.ExecuteAsync(null);
// Assert
Assert.That(vm.Messages.Count, Is.GreaterThan(0));
}
```
## 🚀 Extensibilité
Pour ajouter une nouvelle entité :
1. **Model** : Créer `NewEntity.cs`
2. **Service** : Ajouter méthodes dans `IDatabaseService` et `DatabaseService`
3. **ViewModel** : Créer `NewEntityViewModel.cs`
4. **View** : Créer `NewEntityPage.xaml/.cs`
5. **DI** : Enregistrer dans `MauiProgram.cs`
6. **Navigation** : Ajouter dans `AppShell.xaml`
## 📝 Conventions de Code
- **Naming** :
- ViewModels : `*ViewModel.cs`
- Pages : `*Page.xaml/.cs`
- Services : `*Service.cs` avec interface `I*Service.cs`
- **Async** :
- Toujours utiliser `async/await`
- Suffixe `Async` pour les méthodes
- **MVVM** :
- Pas de logique dans code-behind (sauf OnAppearing)
- Tout passe par le ViewModel
- Binding bidirectionnel quand nécessaire
---
**Cette architecture garantit** :
- ✅ Maintenabilité
- ✅ Testabilité
- ✅ Séparation des responsabilités
- ✅ Réutilisabilité
- ✅ Extensibilité

182
CHANGELOG.md Normal file
View File

@@ -0,0 +1,182 @@
# 📅 Changelog - TravelMate Admin
Toutes les modifications notables de ce projet seront documentées dans ce fichier.
Le format est basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/),
et ce projet adhère à [Semantic Versioning](https://semver.org/lang/fr/).
## [1.0.0] - 2026-01-12
### ✨ Ajouté
#### Architecture & Structure
- Architecture MVVM complète avec CommunityToolkit.Mvvm
- Pattern strict avec séparation Models/Views/ViewModels/Services
- Injection de dépendances configurée dans MauiProgram.cs
- Navigation Shell avec TabBar
#### Models
- `Message.cs` : Modèle pour les messages de demande avec propriétés calculées
- `SupportRequest.cs` : Modèle pour les demandes support avec propriétés calculées
#### Services
- `IDatabaseService.cs` : Interface du service de base de données
- `DatabaseService.cs` : Implémentation complète avec MySqlConnector
- Connexion à MariaDB/MySQL
- Méthodes CRUD asynchrones
- Gestion des erreurs et logs
- Test de connexion
#### ViewModels
- `DashboardViewModel.cs` : Gestion du tableau de bord avec statistiques
- `MessagesViewModel.cs` : Gestion de la liste des messages avec filtres
- `SupportViewModel.cs` : Gestion des demandes support avec filtres
- Utilisation de `[ObservableProperty]` et `[RelayCommand]`
- Commandes asynchrones pour toutes les opérations
#### Views
- `DashboardPage.xaml/.cs` : Page principale avec cartes de statistiques
- `MessagesPage.xaml/.cs` : Liste des messages avec CollectionView
- `SupportPage.xaml/.cs` : Liste des demandes support
- Design moderne avec dark theme (#1a1a1a)
- Layout responsive
- Indicateurs de chargement
#### UI/UX
- Dark theme par défaut avec palette de couleurs cohérente
- Cartes avec ombres et coins arrondis
- Animations smooth pour les interactions
- Empty states pour listes vides
- Pull-to-refresh sur les listes
- Filtres dynamiques (Tout / À faire / Fait)
#### Converters
- `BoolToStatusTextConverter` : Conversion bool → texte bouton
- `BoolToColorConverter` : Conversion bool → couleur bouton
#### Configuration
- `AppSettings.cs` : Configuration centralisée de la connexion DB
- Paramètres modifiables (Server, Port, Database, User, Password)
#### Base de Données
- Script SQL `database_setup.sql` complet :
- Création de la base `travelmateadmin`
- Table `messages` avec 8 colonnes et index
- Table `support_requests` avec 8 colonnes et index
- 4 messages de test
- 4 demandes support de test
- Encodage UTF-8 (utf8mb4)
#### Fonctionnalités
- Dashboard avec statistiques en temps réel
- Filtrage des demandes (tout/à faire/fait)
- Changement de statut d'une demande (toggle done)
- Tri par date (plus récent en haut)
- Rafraîchissement manuel des données
- Navigation entre les sections
- Indicateur de connexion DB
#### Packages NuGet
- `CommunityToolkit.Mvvm` v8.3.2
- `MySqlConnector` v2.4.0
#### Documentation
- `README.md` : Documentation complète du projet (sections détaillées)
- `QUICKSTART.md` : Guide de démarrage rapide (5 minutes)
- `CONFIGURATION.md` : Guide de configuration DB avec exemples
- `ARCHITECTURE.md` : Documentation architecture MVVM détaillée
- `COMMANDS.md` : Référence des commandes de développement
- `SQL_REFERENCE.md` : Référence complète des requêtes SQL
- `PROJECT_SUMMARY.md` : Résumé du projet et checklist
- `.gitignore` : Configuration Git adaptée .NET MAUI
#### DevOps
- Configuration csproj pour multi-plateforme (Mac/Win/Android/iOS)
- Build scripts pour toutes les plateformes
- Compilation vérifiée et réussie
### 🎯 Plateformes Supportées
- ✅ macOS (MacCatalyst) - net10.0-maccatalyst
- ✅ Windows - net10.0-windows10.0.19041.0
- ⚠️ Android - net10.0-android (non testé)
- ⚠️ iOS - net10.0-ios (non testé)
### 📊 Statistiques
- **Fichiers créés** : 35+
- **Lignes de code** : ~2000+ (C# + XAML)
- **Modèles** : 2
- **Services** : 1 (+ interface)
- **ViewModels** : 3
- **Views** : 3 (+ code-behind)
- **Documentation** : 8 fichiers MD
---
## [Unreleased] - Fonctionnalités Futures
### 🚀 Planifié pour v1.1.0
- [ ] Mode light/dark configurable
- [ ] Recherche/filtrage avancé
- [ ] Export de données (CSV, PDF)
- [ ] Pagination pour grandes listes
- [ ] Statistiques graphiques (charts)
### 🔮 Planifié pour v1.2.0
- [ ] Authentification utilisateur
- [ ] Gestion des permissions
- [ ] Historique des modifications
- [ ] Notifications push
- [ ] Multi-langue (i18n)
### 🧪 Planifié pour v1.3.0
- [ ] Tests unitaires (ViewModels)
- [ ] Tests d'intégration (Services)
- [ ] CI/CD avec GitHub Actions
- [ ] Couverture de code
### 🎨 Améliorations UI/UX
- [ ] Animations de transitions
- [ ] Thèmes personnalisables
- [ ] Accessibilité (screen readers)
- [ ] Raccourcis clavier
### 🔧 Améliorations Techniques
- [ ] Connection pooling optimisé
- [ ] Cache des données
- [ ] Offline mode
- [ ] Synchronisation automatique
- [ ] Logs structurés (Serilog)
### 🔐 Sécurité
- [ ] Chiffrement de la chaîne de connexion
- [ ] Variables d'environnement
- [ ] Azure Key Vault integration
- [ ] Audit trail
- [ ] Rate limiting
---
## Types de Changements
- `Added` ✨ : Nouvelles fonctionnalités
- `Changed` 🔄 : Modifications de fonctionnalités existantes
- `Deprecated` ⚠️ : Fonctionnalités bientôt supprimées
- `Removed` 🗑️ : Fonctionnalités supprimées
- `Fixed` 🐛 : Corrections de bugs
- `Security` 🔐 : Corrections de sécurité
---
## Format des Versions
```
[MAJOR.MINOR.PATCH] - YYYY-MM-DD
MAJOR : Changements incompatibles de l'API
MINOR : Nouvelles fonctionnalités rétrocompatibles
PATCH : Corrections de bugs rétrocompatibles
```
---
**Note** : Ce fichier sera mis à jour à chaque release avec les changements apportés.

311
COMMANDS.md Normal file
View File

@@ -0,0 +1,311 @@
# Commandes Utiles - TravelMate Admin
## 🔧 Développement
### Restaurer les packages
```bash
dotnet restore
```
### Build le projet
```bash
# MacCatalyst
dotnet build -f net10.0-maccatalyst
# Windows
dotnet build -f net10.0-windows10.0.19041.0
# Android
dotnet build -f net10.0-android
# iOS
dotnet build -f net10.0-ios
```
### Lancer l'application
```bash
# MacCatalyst
dotnet build -t:Run -f net10.0-maccatalyst
# Windows
dotnet build -t:Run -f net10.0-windows10.0.19041.0
```
### Nettoyer le projet
```bash
dotnet clean
rm -rf TravelMateAdmin/bin TravelMateAdmin/obj
```
### Rebuild complet
```bash
dotnet clean
dotnet restore
dotnet build
```
## 🗄️ Base de Données
### Créer la base de données
```bash
mysql -u root -p < database_setup.sql
```
### Se connecter à MySQL
```bash
mysql -u root -p travelmateadmin
```
### Afficher les tables
```sql
USE travelmateadmin;
SHOW TABLES;
```
### Voir les données
```sql
-- Tous les messages
SELECT * FROM messages ORDER BY created_at DESC;
-- Messages en attente
SELECT * FROM messages WHERE done = FALSE;
-- Statistiques
SELECT
COUNT(*) as total,
SUM(done = FALSE) as pending,
SUM(done = TRUE) as completed
FROM messages;
```
### Ajouter un message de test
```sql
INSERT INTO messages (nom, prenom, email, message, done, created_at)
VALUES ('Test', 'User', 'test@example.com', 'Ceci est un test', FALSE, NOW());
```
### Marquer tous les messages comme non traités
```sql
UPDATE messages SET done = FALSE;
UPDATE support_requests SET done = FALSE;
```
### Réinitialiser les données
```bash
mysql -u root -p travelmateadmin < database_setup.sql
```
### Backup de la base
```bash
mysqldump -u root -p travelmateadmin > backup_$(date +%Y%m%d_%H%M%S).sql
```
### Restore depuis un backup
```bash
mysql -u root -p travelmateadmin < backup_20260112_143000.sql
```
## 📦 NuGet Packages
### Voir les packages installés
```bash
dotnet list TravelMateAdmin/TravelMateAdmin.csproj package
```
### Mettre à jour un package
```bash
dotnet add TravelMateAdmin/TravelMateAdmin.csproj package CommunityToolkit.Mvvm
dotnet add TravelMateAdmin/TravelMateAdmin.csproj package MySqlConnector
```
### Mettre à jour tous les packages
```bash
dotnet list TravelMateAdmin/TravelMateAdmin.csproj package --outdated
```
## 🐛 Debugging
### Voir les logs
Dans le terminal où vous avez lancé l'app, les logs apparaissent via :
```csharp
System.Diagnostics.Debug.WriteLine("Mon log");
```
### Tests de connexion MySQL
```bash
# Tester si MySQL est accessible
nc -zv localhost 3306
# Voir les processus MySQL
ps aux | grep mysql
# Démarrer MySQL (Mac avec Homebrew)
brew services start mysql
# Démarrer MySQL (Linux)
sudo service mysql start
# Démarrer MySQL (MAMP)
# Utiliser l'interface MAMP
```
## 📱 Plateforme Spécifique
### MacCatalyst - Voir les logs système
```bash
log stream --predicate 'processImagePath contains "TravelMateAdmin"' --level debug
```
### Android - Voir les logs
```bash
adb logcat | grep TravelMateAdmin
```
### iOS Simulator - Voir les logs
```bash
xcrun simctl spawn booted log stream --level debug | grep TravelMateAdmin
```
## 🔍 Code Analysis
### Format le code
```bash
dotnet format TravelMateAdmin/TravelMateAdmin.csproj
```
### Analyser le code
```bash
dotnet build /p:TreatWarningsAsErrors=true
```
## 📊 Statistiques du Projet
### Compter les lignes de code
```bash
find TravelMateAdmin -name "*.cs" -not -path "*/obj/*" -not -path "*/bin/*" | xargs wc -l
```
### Voir l'arborescence
```bash
tree -I 'bin|obj' TravelMateAdmin/
```
### Taille du projet
```bash
du -sh TravelMateAdmin/
```
## 🚀 Publication
### Publish MacCatalyst
```bash
dotnet publish -f net10.0-maccatalyst -c Release -p:CreatePackage=true
```
### Publish Windows
```bash
dotnet publish -f net10.0-windows10.0.19041.0 -c Release
```
### Créer un package pour Mac App Store
```bash
dotnet publish -f net10.0-maccatalyst -c Release \
-p:RuntimeIdentifier=maccatalyst-arm64 \
-p:CreatePackage=true \
-p:CodesignKey="Apple Distribution: Your Name" \
-p:CodesignProvision="Your Provisioning Profile"
```
## 🧪 Tests (futur)
### Ajouter un projet de tests
```bash
dotnet new xunit -n TravelMateAdmin.Tests
dotnet sln add TravelMateAdmin.Tests/TravelMateAdmin.Tests.csproj
dotnet add TravelMateAdmin.Tests reference TravelMateAdmin/TravelMateAdmin.csproj
```
### Lancer les tests
```bash
dotnet test
```
## 📝 Git
### Initialiser le repo
```bash
git init
git add .
git commit -m "Initial commit: TravelMate Admin MAUI app"
```
### Ignorer les fichiers de build
Le `.gitignore` est déjà configuré pour :
- bin/, obj/
- .vs/, .vscode/
- Configuration sensible
### Créer une branche
```bash
git checkout -b feature/nouvelle-fonctionnalite
```
## 🔐 Sécurité
### Chiffrer la configuration (exemple)
```bash
# Générer une clé
openssl rand -base64 32
# Chiffrer un fichier
openssl enc -aes-256-cbc -salt -in AppSettings.cs -out AppSettings.cs.enc
# Déchiffrer
openssl enc -d -aes-256-cbc -in AppSettings.cs.enc -out AppSettings.cs
```
## 🛠️ Maintenance
### Vérifier les dépendances obsolètes
```bash
dotnet list package --outdated
```
### Mettre à jour .NET
```bash
# Vérifier la version actuelle
dotnet --version
# Télécharger la dernière version
# https://dotnet.microsoft.com/download
```
### Nettoyer NuGet cache
```bash
dotnet nuget locals all --clear
```
---
## 💡 Tips & Tricks
### Hot Reload
Lors du développement, les changements XAML sont appliqués en temps réel avec Hot Reload.
### Raccourcis Visual Studio
- `F5` : Run avec debug
- `Ctrl+F5` (Cmd+F5 Mac) : Run sans debug
- `Shift+F5` : Stop debugging
### Performance
```bash
# Build en Release pour tester les performances réelles
dotnet build -c Release -f net10.0-maccatalyst
```
### Multi-targeting
Pour cibler plusieurs plateformes en une fois :
```bash
dotnet build
# Build toutes les plateformes définies dans TargetFrameworks
```

102
CONFIGURATION.md Normal file
View File

@@ -0,0 +1,102 @@
# Configuration de l'Application TravelMate Admin
## Configuration de Base de Données
Pour configurer la connexion à votre base de données, modifiez le fichier :
`TravelMateAdmin/Configuration/AppSettings.cs`
### Exemple pour une connexion locale :
```csharp
public const string Server = "localhost";
public const string Port = "3306";
public const string Database = "travelmateadmin";
public const string User = "root";
public const string Password = "monmotdepasse";
```
### Exemple pour une connexion distante :
```csharp
public const string Server = "192.168.1.100"; // IP du serveur
public const string Port = "3306";
public const string Database = "travelmateadmin";
public const string User = "admin";
public const string Password = "motdepassesecurise";
```
### Exemple avec un serveur cloud (ex: AWS RDS, Azure Database) :
```csharp
public const string Server = "myserver.mysql.database.azure.com";
public const string Port = "3306";
public const string Database = "travelmateadmin";
public const string User = "myadmin@myserver";
public const string Password = "P@ssw0rd!";
```
## Sécurité
⚠️ **Important** : Ne commitez JAMAIS vos mots de passe dans Git !
Pour une meilleure sécurité en production :
1. Utilisez des variables d'environnement
2. Utilisez un système de gestion des secrets (Azure Key Vault, AWS Secrets Manager)
3. Chiffrez la chaîne de connexion
### Exemple avec variables d'environnement :
```csharp
public static string GetConnectionString()
{
var server = Environment.GetEnvironmentVariable("DB_SERVER") ?? Server;
var port = Environment.GetEnvironmentVariable("DB_PORT") ?? Port;
var database = Environment.GetEnvironmentVariable("DB_NAME") ?? Database;
var user = Environment.GetEnvironmentVariable("DB_USER") ?? User;
var password = Environment.GetEnvironmentVariable("DB_PASSWORD") ?? Password;
return $"Server={server};Port={port};Database={database};User={user};Password={password};";
}
```
## Paramètres Supplémentaires MySQL
Vous pouvez ajouter des paramètres supplémentaires à la chaîne de connexion :
```csharp
public static string GetConnectionString()
{
return $"Server={Server};Port={Port};Database={Database};User={User};Password={Password};" +
"SslMode=Required;" + // Pour SSL/TLS
"AllowPublicKeyRetrieval=True;" + // Pour l'authentification
"ConnectionTimeout=30;" + // Timeout en secondes
"DefaultCommandTimeout=30;"; // Timeout des commandes
}
```
## Test de Connexion
Pour tester votre connexion, lancez l'application et vérifiez :
1. Le dashboard doit afficher "✓ Connecté" en haut à droite
2. Les statistiques doivent se charger
3. Si "✗ Déconnecté", vérifiez vos paramètres et les logs de débogage
## Dépannage Connexion
### Erreur "Access denied"
- Vérifiez le nom d'utilisateur et le mot de passe
- Vérifiez que l'utilisateur a les droits sur la base de données :
```sql
GRANT ALL PRIVILEGES ON travelmateadmin.* TO 'user'@'localhost';
FLUSH PRIVILEGES;
```
### Erreur "Cannot connect to server"
- Vérifiez que MySQL est démarré
- Vérifiez le serveur et le port
- Vérifiez les règles de firewall
### Erreur "Unknown database"
- Vérifiez que la base de données existe
- Exécutez le script `database_setup.sql`

232
INDEX.md Normal file
View File

@@ -0,0 +1,232 @@
# 📚 Index de Documentation - TravelMate Admin
Guide complet pour naviguer dans la documentation du projet.
---
## 🚀 Pour Commencer
**Nouveau sur le projet ? Commencez ici :**
1. 📖 [README.md](README.md) - **Lisez-moi d'abord** - Vue d'ensemble du projet
2. ⚡ [QUICKSTART.md](QUICKSTART.md) - Installation en 5 minutes
3. 🎉 Lancez l'application !
---
## 📂 Documentation par Catégorie
### 🏗️ Architecture & Développement
| Fichier | Description | Quand le consulter |
|---------|-------------|-------------------|
| [ARCHITECTURE.md](ARCHITECTURE.md) | Architecture MVVM détaillée, flux de données, patterns | Comprendre la structure du code |
| [PROJECT_SUMMARY.md](PROJECT_SUMMARY.md) | Résumé complet, fichiers créés, checklist | Vue d'ensemble technique |
| [CHANGELOG.md](CHANGELOG.md) | Historique des versions et modifications | Voir les changements apportés |
### ⚙️ Configuration & Installation
| Fichier | Description | Quand le consulter |
|---------|-------------|-------------------|
| [QUICKSTART.md](QUICKSTART.md) | Guide rapide d'installation (5 min) | Première installation |
| [CONFIGURATION.md](CONFIGURATION.md) | Configuration de la base de données | Problèmes de connexion DB |
| [README.md](README.md) | Guide complet avec tous les détails | Documentation générale |
### 💻 Développement & Maintenance
| Fichier | Description | Quand le consulter |
|---------|-------------|-------------------|
| [COMMANDS.md](COMMANDS.md) | Commandes utiles (build, DB, debug) | Tâches de développement |
| [SQL_REFERENCE.md](SQL_REFERENCE.md) | Requêtes SQL complètes | Gérer la base de données |
### 📊 Base de Données
| Fichier | Description | Quand le consulter |
|---------|-------------|-------------------|
| [database_setup.sql](database_setup.sql) | Script SQL de création + données test | Initialiser la DB |
| [SQL_REFERENCE.md](SQL_REFERENCE.md) | Guide complet des requêtes SQL | Opérations sur les données |
---
## 🎯 Guides par Scénario
### Scénario 1 : Je veux installer l'app rapidement
1. [QUICKSTART.md](QUICKSTART.md) - Suivez les 5 étapes
2. [database_setup.sql](database_setup.sql) - Exécutez ce script
3. Modifiez `TravelMateAdmin/Configuration/AppSettings.cs`
4. Lancez l'app !
### Scénario 2 : J'ai des problèmes de connexion DB
1. [CONFIGURATION.md](CONFIGURATION.md) - Guide de dépannage
2. Vérifiez `AppSettings.cs`
3. [COMMANDS.md](COMMANDS.md) - Commandes de test MySQL
### Scénario 3 : Je veux comprendre le code
1. [ARCHITECTURE.md](ARCHITECTURE.md) - Pattern MVVM expliqué
2. [PROJECT_SUMMARY.md](PROJECT_SUMMARY.md) - Structure des fichiers
3. Lisez les commentaires dans le code source
### Scénario 4 : Je veux ajouter une fonctionnalité
1. [ARCHITECTURE.md](ARCHITECTURE.md) - Section "Extensibilité"
2. [COMMANDS.md](COMMANDS.md) - Commandes de build/test
3. Suivez le pattern existant (Models → Services → ViewModels → Views)
### Scénario 5 : Je veux gérer les données
1. [SQL_REFERENCE.md](SQL_REFERENCE.md) - Toutes les requêtes SQL
2. [COMMANDS.md](COMMANDS.md) - Section "Base de Données"
### Scénario 6 : Je veux personnaliser l'UI
1. [README.md](README.md) - Section "Personnalisation"
2. Modifiez les couleurs dans les fichiers XAML
3. Consultez les Views existantes comme exemples
---
## 📖 Structure de la Documentation
```
Documentation/
├── README.md ⭐ Point d'entrée principal
├── QUICKSTART.md 🚀 Installation rapide
├── ARCHITECTURE.md 🏗️ Architecture technique
├── CONFIGURATION.md ⚙️ Configuration DB
├── COMMANDS.md 💻 Commandes de dev
├── SQL_REFERENCE.md 📊 Référence SQL
├── PROJECT_SUMMARY.md 📝 Résumé complet
├── CHANGELOG.md 📅 Historique des versions
└── INDEX.md 📚 Ce fichier
Code Source/
├── TravelMateAdmin/
│ ├── Models/ 📦 Modèles de données
│ ├── Services/ 🔧 Couche d'accès aux données
│ ├── ViewModels/ 🎭 Logique de présentation
│ ├── Views/ 🎨 Interface utilisateur
│ ├── Converters/ 🔄 Converters XAML
│ └── Configuration/ ⚙️ Configuration app
Base de Données/
└── database_setup.sql 🗄️ Script SQL
```
---
## 🔍 Index par Mots-Clés
### A
- **Architecture MVVM** → [ARCHITECTURE.md](ARCHITECTURE.md)
- **Ajouter une fonctionnalité** → [ARCHITECTURE.md](ARCHITECTURE.md#extensibilité)
### B
- **Base de données** → [CONFIGURATION.md](CONFIGURATION.md), [SQL_REFERENCE.md](SQL_REFERENCE.md)
- **Build** → [COMMANDS.md](COMMANDS.md#développement)
- **Backup** → [COMMANDS.md](COMMANDS.md#base-de-données)
### C
- **Commandes** → [COMMANDS.md](COMMANDS.md)
- **Configuration** → [CONFIGURATION.md](CONFIGURATION.md)
- **Connexion DB** → [CONFIGURATION.md](CONFIGURATION.md)
- **CommunityToolkit.Mvvm** → [ARCHITECTURE.md](ARCHITECTURE.md)
### D
- **Dashboard** → [PROJECT_SUMMARY.md](PROJECT_SUMMARY.md)
- **DatabaseService** → [ARCHITECTURE.md](ARCHITECTURE.md)
- **Dépannage** → [CONFIGURATION.md](CONFIGURATION.md), [QUICKSTART.md](QUICKSTART.md)
### F
- **Filtres** → [ARCHITECTURE.md](ARCHITECTURE.md)
### I
- **Installation** → [QUICKSTART.md](QUICKSTART.md)
- **Injection de dépendances** → [ARCHITECTURE.md](ARCHITECTURE.md)
### M
- **MariaDB** → [CONFIGURATION.md](CONFIGURATION.md)
- **Messages** → [README.md](README.md)
- **MySQL** → [CONFIGURATION.md](CONFIGURATION.md), [SQL_REFERENCE.md](SQL_REFERENCE.md)
- **MVVM** → [ARCHITECTURE.md](ARCHITECTURE.md)
### N
- **Navigation** → [ARCHITECTURE.md](ARCHITECTURE.md)
- **NuGet** → [COMMANDS.md](COMMANDS.md)
### P
- **Performance** → [ARCHITECTURE.md](ARCHITECTURE.md), [SQL_REFERENCE.md](SQL_REFERENCE.md)
- **Problèmes** → [QUICKSTART.md](QUICKSTART.md), [CONFIGURATION.md](CONFIGURATION.md)
### Q
- **Quick Start** → [QUICKSTART.md](QUICKSTART.md)
### R
- **Requêtes SQL** → [SQL_REFERENCE.md](SQL_REFERENCE.md)
### S
- **Sécurité** → [CONFIGURATION.md](CONFIGURATION.md), [ARCHITECTURE.md](ARCHITECTURE.md)
- **Services** → [ARCHITECTURE.md](ARCHITECTURE.md)
- **SQL** → [SQL_REFERENCE.md](SQL_REFERENCE.md)
- **Support** → [PROJECT_SUMMARY.md](PROJECT_SUMMARY.md)
### T
- **Tests** → [COMMANDS.md](COMMANDS.md), [ARCHITECTURE.md](ARCHITECTURE.md)
### V
- **ViewModels** → [ARCHITECTURE.md](ARCHITECTURE.md)
- **Views** → [ARCHITECTURE.md](ARCHITECTURE.md)
### X
- **XAML** → [ARCHITECTURE.md](ARCHITECTURE.md), [README.md](README.md)
---
## 📊 Statistiques de la Documentation
| Type | Nombre | Lignes Totales |
|------|--------|----------------|
| Fichiers Markdown | 8 | ~1500+ lignes |
| Sections | 100+ | - |
| Exemples de code | 50+ | - |
| Commandes | 100+ | - |
| Requêtes SQL | 60+ | - |
---
## 🆘 Aide Rapide
**Je ne trouve pas ce que je cherche !**
1. Utilisez Ctrl+F (Cmd+F sur Mac) dans ce fichier pour chercher un mot-clé
2. Consultez la section "Guides par Scénario" ci-dessus
3. Ouvrez [README.md](README.md) pour une vue d'ensemble
4. Tous les fichiers sont en Markdown, faciles à lire avec n'importe quel éditeur
**Suggestions de documentation manquante ?**
N'hésitez pas à créer une issue ou à contribuer !
---
## 🎓 Ordre de Lecture Recommandé
### Pour les Débutants
1. [README.md](README.md) - Vue d'ensemble
2. [QUICKSTART.md](QUICKSTART.md) - Installation
3. [PROJECT_SUMMARY.md](PROJECT_SUMMARY.md) - Résumé
4. Lancez l'app et explorez !
### Pour les Développeurs
1. [ARCHITECTURE.md](ARCHITECTURE.md) - Comprendre la structure
2. [COMMANDS.md](COMMANDS.md) - Commandes de dev
3. [SQL_REFERENCE.md](SQL_REFERENCE.md) - Opérations DB
4. Code source dans TravelMateAdmin/
### Pour les Administrateurs DB
1. [CONFIGURATION.md](CONFIGURATION.md) - Configuration
2. [database_setup.sql](database_setup.sql) - Script d'installation
3. [SQL_REFERENCE.md](SQL_REFERENCE.md) - Référence complète
4. [COMMANDS.md](COMMANDS.md#base-de-données) - Maintenance
---
**Dernière mise à jour** : 12 janvier 2026
**Version de la documentation** : 1.0.0

233
PROJECT_SUMMARY.md Normal file
View File

@@ -0,0 +1,233 @@
# ✅ PROJET COMPLÉTÉ - TravelMate Admin
## 📋 Résumé
Application desktop .NET MAUI (Mac/Windows) avec architecture MVVM pour gérer les demandes clients depuis une base de données MariaDB/MySQL.
---
## 🎯 Ce qui a été créé
### 1. Structure MVVM Complète
#### **Models** (2 fichiers)
-`Message.cs` - Modèle pour les messages de demande
-`SupportRequest.cs` - Modèle pour les demandes support
#### **Services** (2 fichiers)
-`IDatabaseService.cs` - Interface du service de base de données
-`DatabaseService.cs` - Implémentation avec MySqlConnector
- Connexion à MariaDB/MySQL
- CRUD operations pour messages et support_requests
- Gestion asynchrone complète
#### **ViewModels** (3 fichiers)
-`DashboardViewModel.cs` - Gestion du tableau de bord
-`MessagesViewModel.cs` - Gestion de la liste des messages
-`SupportViewModel.cs` - Gestion des demandes support
- Tous utilisent CommunityToolkit.Mvvm (ObservableProperty, RelayCommand)
#### **Views** (3 paires XAML + Code-behind)
-`DashboardPage.xaml/.cs` - Tableau de bord avec statistiques
-`MessagesPage.xaml/.cs` - Liste des messages avec filtres
-`SupportPage.xaml/.cs` - Liste des demandes support
#### **Converters** (1 fichier)
-`BoolConverters.cs` - Conversion bool → texte et couleur pour les boutons
#### **Configuration** (1 fichier)
-`AppSettings.cs` - Configuration de la connexion DB
### 2. Configuration & Infrastructure
-`MauiProgram.cs` - Injection de dépendances configurée
-`AppShell.xaml` - Navigation Shell avec 3 onglets
-`TravelMateAdmin.csproj` - Packages NuGet ajoutés :
- CommunityToolkit.Mvvm (8.3.2)
- MySqlConnector (2.4.0)
### 3. Base de Données
-`database_setup.sql` - Script SQL complet :
- Création de la base `travelmateadmin`
- Table `messages` (8 colonnes)
- Table `support_requests` (8 colonnes)
- Données de test (4 messages + 4 demandes support)
### 4. Documentation
-`README.md` - Documentation complète du projet
-`QUICKSTART.md` - Guide de démarrage rapide (5 minutes)
-`CONFIGURATION.md` - Guide de configuration DB
-`ARCHITECTURE.md` - Documentation architecture MVVM
-`COMMANDS.md` - Commandes utiles pour le développement
-`.gitignore` - Fichiers à ignorer dans Git
---
## 🎨 Fonctionnalités Implémentées
### ✅ Dashboard
- Affichage des statistiques en temps réel
- 4 cartes : Messages (en attente/traités) + Support (en attente/traités)
- Indicateur de statut de connexion
- Bouton de rafraîchissement
- Navigation vers les pages de détail
### ✅ Page Messages
- Liste complète des messages avec scroll
- Tri par date (plus récent en haut)
- Filtre : Tout / À faire / Fait
- Affichage : Nom, Prénom, Email, Message, Date, Statut
- Bouton pour changer le statut (fait ↔ en attente)
- Rafraîchissement manuel
### ✅ Page Support
- Liste des demandes support
- Même système de filtres que Messages
- Affichage : Nom, Prénom, Account Email, Contact Email, Message, Date, Statut
- Bouton pour changer le statut
- Rafraîchissement manuel
### ✅ Design
- **Dark theme** moderne et professionnel
- Couleurs configurables (Primary, Accent, Success, Warning)
- Cartes avec shadow et coins arrondis
- Layout responsive
- Indicateurs de chargement (ActivityIndicator)
---
## 🛠️ Technologies Utilisées
| Technologie | Version | Usage |
|-------------|---------|-------|
| .NET | 10.0 | Framework principal |
| .NET MAUI | Dernière | UI multi-plateforme |
| CommunityToolkit.Mvvm | 8.3.2 | Pattern MVVM |
| MySqlConnector | 2.4.0 | Connexion MariaDB/MySQL |
| XAML | - | Interface utilisateur |
---
## 📊 Structure du Projet
```
TravelMateAdmin/
├── TravelMateAdmin/ # Projet principal
│ ├── Configuration/
│ │ └── AppSettings.cs # Config DB
│ ├── Converters/
│ │ └── BoolConverters.cs # Converters XAML
│ ├── Models/
│ │ ├── Message.cs
│ │ └── SupportRequest.cs
│ ├── Services/
│ │ ├── IDatabaseService.cs
│ │ └── DatabaseService.cs
│ ├── ViewModels/
│ │ ├── DashboardViewModel.cs
│ │ ├── MessagesViewModel.cs
│ │ └── SupportViewModel.cs
│ ├── Views/
│ │ ├── DashboardPage.xaml/.cs
│ │ ├── MessagesPage.xaml/.cs
│ │ └── SupportPage.xaml/.cs
│ ├── App.xaml/.cs
│ ├── AppShell.xaml/.cs
│ ├── MauiProgram.cs
│ └── TravelMateAdmin.csproj
├── database_setup.sql # Script SQL
├── README.md # Documentation principale
├── QUICKSTART.md # Guide rapide
├── CONFIGURATION.md # Guide configuration
├── ARCHITECTURE.md # Documentation architecture
├── COMMANDS.md # Commandes utiles
└── .gitignore # Git ignore
Total: 35+ fichiers créés/modifiés
```
---
## 🚀 État du Projet
### ✅ Complété (100%)
- [x] Architecture MVVM complète
- [x] Modèles de données (Message, SupportRequest)
- [x] Service de base de données avec toutes les méthodes
- [x] ViewModels avec CommunityToolkit.Mvvm
- [x] Vues XAML avec design moderne
- [x] Converters pour l'affichage
- [x] Injection de dépendances
- [x] Navigation Shell
- [x] Script SQL avec données de test
- [x] Documentation complète
- [x] Compilation réussie ✅
### ⏭️ Étapes Suivantes (Optionnel)
- [ ] Ajouter authentification utilisateur
- [ ] Implémenter pagination pour grandes listes
- [ ] Ajouter recherche/filtrage avancé
- [ ] Exporter les données (CSV, PDF)
- [ ] Notifications push
- [ ] Tests unitaires
- [ ] Mode light/dark configurable
- [ ] Statistiques avancées (graphiques)
---
## 📝 Pour Commencer
### Option 1 : Rapide (5 minutes)
```bash
# 1. Configurer la base de données
mysql -u root -p < database_setup.sql
# 2. Modifier AppSettings.cs avec vos identifiants MySQL
# 3. Lancer l'app
dotnet build -t:Run -f net10.0-maccatalyst # Mac
# OU
dotnet build -t:Run -f net10.0-windows10.0.19041.0 # Windows
```
### Option 2 : Détaillée
Consultez `QUICKSTART.md` pour un guide pas-à-pas complet.
---
## 🎓 Points Clés de l'Architecture
1. **Séparation des responsabilités** : MVVM strict
2. **Réactivité** : INotifyPropertyChanged automatique
3. **Asynchrone** : Toutes les opérations DB sont async
4. **Injection de dépendances** : Services injectés automatiquement
5. **Testabilité** : Interface IDatabaseService mockable
6. **Extensibilité** : Facile d'ajouter de nouvelles entités
---
## 📞 Support
- **README.md** : Documentation générale
- **QUICKSTART.md** : Installation rapide
- **CONFIGURATION.md** : Problèmes de connexion DB
- **ARCHITECTURE.md** : Comprendre le code
- **COMMANDS.md** : Commandes de développement
---
## 🎉 Félicitations !
Vous avez maintenant une application d'administration complète et professionnelle avec :
- ✅ Code propre et bien structuré
- ✅ Pattern MVVM moderne
- ✅ Interface utilisateur intuitive
- ✅ Base de données intégrée
- ✅ Documentation exhaustive
- ✅ Prête à être étendue
**Prochaine étape** : Lancez l'application et explorez les fonctionnalités ! 🚀

123
QUICKSTART.md Normal file
View File

@@ -0,0 +1,123 @@
# 🚀 Guide de Démarrage Rapide - TravelMate Admin
## Installation en 5 Minutes
### Étape 1 : Prérequis ✅
Installez si nécessaire :
- [.NET 8+ SDK](https://dotnet.microsoft.com/download)
- [MySQL/MariaDB](https://dev.mysql.com/downloads/) ou [MAMP](https://www.mamp.info/) (Mac) / [XAMPP](https://www.apachefriends.org/) (Windows)
### Étape 2 : Base de Données 🗄️
**Option A - Avec MySQL en ligne de commande :**
```bash
mysql -u root -p < database_setup.sql
```
**Option B - Avec phpMyAdmin ou MySQL Workbench :**
1. Créez une base de données nommée `travelmateadmin`
2. Importez le fichier `database_setup.sql`
**Option C - Manuellement :**
```sql
CREATE DATABASE travelmateadmin;
USE travelmateadmin;
-- Puis copiez/collez le contenu de database_setup.sql
```
### Étape 3 : Configuration ⚙️
Ouvrez `TravelMateAdmin/Configuration/AppSettings.cs` et modifiez :
```csharp
public const string Server = "localhost"; // ✏️ Votre serveur
public const string Port = "3306"; // ✏️ Votre port
public const string Database = "travelmateadmin";
public const string User = "root"; // ✏️ Votre utilisateur
public const string Password = "VOTRE_MDP"; // ✏️ IMPORTANT: Changez ici !
```
### Étape 4 : Restaurer les Packages 📦
```bash
cd TravelMateAdmin
dotnet restore
```
### Étape 5 : Lancer l'Application 🎉
**Sur Mac :**
```bash
dotnet build -t:Run -f net10.0-maccatalyst
```
**Sur Windows :**
```bash
dotnet build -t:Run -f net10.0-windows10.0.19041.0
```
**Avec Visual Studio :**
1. Ouvrez `TravelMateAdmin.sln`
2. Sélectionnez la plateforme (Mac Catalyst ou Windows)
3. Appuyez sur F5 ou cliquez sur ▶️ Run
---
## ✅ Vérification
Au lancement, vous devriez voir :
- ✅ Dashboard avec les statistiques
- ✅ "✓ Connecté" en haut à droite
- ✅ 4 messages et 4 demandes support (données de test)
## ❌ Problèmes ?
### "✗ Déconnecté" s'affiche
1. Vérifiez que MySQL est démarré
2. Vérifiez vos paramètres dans `AppSettings.cs`
3. Testez la connexion MySQL : `mysql -u root -p`
### Erreur "Cannot find project"
```bash
cd /Users/dayronvanleemput/Documents/Coding/TravelMateAdmin
dotnet restore
```
### Erreur de build
```bash
dotnet clean
dotnet restore
dotnet build
```
---
## 📚 Prochaines Étapes
1. **Tester l'application** : Naviguez entre Dashboard, Messages et Support
2. **Changer un statut** : Cliquez sur un bouton "Marquer comme fait"
3. **Filtrer** : Utilisez le menu déroulant pour filtrer les demandes
4. **Personnaliser** : Changez les couleurs dans les fichiers XAML
5. **Ajouter des données** : Ajoutez vos propres demandes dans la base
## 🎨 Captures d'Écran des Fonctionnalités
### Dashboard
- Vue d'ensemble des statistiques
- Cartes cliquables pour accéder aux détails
- Statut de connexion en temps réel
### Messages
- Liste complète des messages
- Filtre : Tout / À faire / Fait
- Action : Marquer comme fait/en attente
### Support
- Liste des demandes d'assistance
- Affichage des emails (compte et contact)
- Même système de filtres et actions
---
**Besoin d'aide ?** Consultez le [README.md](README.md) complet ou le guide de [CONFIGURATION.md](CONFIGURATION.md)

433
SQL_REFERENCE.md Normal file
View File

@@ -0,0 +1,433 @@
# 📊 SQL Queries Reference - TravelMate Admin
Requêtes SQL utiles pour gérer et interroger la base de données.
## 🔍 Requêtes de Consultation
### Messages
```sql
-- Tous les messages
SELECT * FROM messages ORDER BY created_at DESC;
-- Messages en attente uniquement
SELECT * FROM messages WHERE done = FALSE ORDER BY created_at DESC;
-- Messages traités uniquement
SELECT * FROM messages WHERE done = TRUE ORDER BY created_at DESC;
-- Compter les messages par statut
SELECT
COUNT(*) AS total,
SUM(CASE WHEN done = FALSE THEN 1 ELSE 0 END) AS en_attente,
SUM(CASE WHEN done = TRUE THEN 1 ELSE 0 END) AS traites
FROM messages;
-- Messages récents (dernières 24h)
SELECT * FROM messages
WHERE created_at > NOW() - INTERVAL 24 HOUR
ORDER BY created_at DESC;
-- Messages d'un utilisateur spécifique
SELECT * FROM messages
WHERE email = 'jean.dupont@example.com'
ORDER BY created_at DESC;
-- Recherche dans les messages
SELECT * FROM messages
WHERE message LIKE '%RGPD%'
OR nom LIKE '%Dupont%'
ORDER BY created_at DESC;
```
### Support Requests
```sql
-- Toutes les demandes support
SELECT * FROM support_requests ORDER BY created_at DESC;
-- Demandes en attente
SELECT * FROM support_requests WHERE done = FALSE ORDER BY created_at DESC;
-- Demandes traitées
SELECT * FROM support_requests WHERE done = TRUE ORDER BY created_at DESC;
-- Compter par statut
SELECT
COUNT(*) AS total,
SUM(CASE WHEN done = FALSE THEN 1 ELSE 0 END) AS en_attente,
SUM(CASE WHEN done = TRUE THEN 1 ELSE 0 END) AS traites
FROM support_requests;
-- Demandes d'un compte spécifique
SELECT * FROM support_requests
WHERE account_email = 'lucas.petit@example.com'
ORDER BY created_at DESC;
-- Recherche par email de contact
SELECT * FROM support_requests
WHERE contact_email = 'lucas.contact@example.com'
ORDER BY created_at DESC;
```
### Statistiques Globales
```sql
-- Vue d'ensemble complète
SELECT
'Messages' AS type,
COUNT(*) AS total,
SUM(CASE WHEN done = FALSE THEN 1 ELSE 0 END) AS en_attente,
SUM(CASE WHEN done = TRUE THEN 1 ELSE 0 END) AS traites
FROM messages
UNION ALL
SELECT
'Support',
COUNT(*),
SUM(CASE WHEN done = FALSE THEN 1 ELSE 0 END),
SUM(CASE WHEN done = TRUE THEN 1 ELSE 0 END)
FROM support_requests;
-- Activité par jour (7 derniers jours)
SELECT
DATE(created_at) AS date,
COUNT(*) AS messages
FROM messages
WHERE created_at > NOW() - INTERVAL 7 DAY
GROUP BY DATE(created_at)
ORDER BY date DESC;
-- Volume par heure de la journée
SELECT
HOUR(created_at) AS heure,
COUNT(*) AS nombre_messages
FROM messages
GROUP BY HOUR(created_at)
ORDER BY heure;
```
## ✏️ Requêtes de Modification
### Changer le Statut
```sql
-- Marquer un message comme traité
UPDATE messages SET done = TRUE WHERE id = 1;
-- Marquer une demande support comme traitée
UPDATE support_requests SET done = TRUE WHERE id = 1;
-- Marquer comme non traité
UPDATE messages SET done = FALSE WHERE id = 1;
-- Marquer tous les messages en attente
UPDATE messages SET done = FALSE;
-- Marquer tous comme traités
UPDATE messages SET done = TRUE;
-- Marquer les vieux messages comme traités (plus de 30 jours)
UPDATE messages
SET done = TRUE
WHERE created_at < NOW() - INTERVAL 30 DAY AND done = FALSE;
```
### Ajouter des Données
```sql
-- Nouveau message
INSERT INTO messages (nom, prenom, email, message, done, created_at)
VALUES (
'Nouveau',
'Test',
'test@example.com',
'Ceci est un nouveau message de test',
FALSE,
NOW()
);
-- Nouvelle demande support
INSERT INTO support_requests (nom, prenom, account_email, contact_email, message, done, created_at)
VALUES (
'Support',
'Test',
'account@example.com',
'contact@example.com',
'Problème de connexion',
FALSE,
NOW()
);
-- Ajouter plusieurs messages en une fois
INSERT INTO messages (nom, prenom, email, message, done, created_at) VALUES
('User1', 'Test', 'user1@test.com', 'Message 1', FALSE, NOW()),
('User2', 'Test', 'user2@test.com', 'Message 2', FALSE, NOW()),
('User3', 'Test', 'user3@test.com', 'Message 3', FALSE, NOW());
```
### Supprimer des Données
```sql
-- Supprimer un message spécifique
DELETE FROM messages WHERE id = 1;
-- Supprimer tous les messages traités
DELETE FROM messages WHERE done = TRUE;
-- Supprimer les vieux messages (plus de 90 jours)
DELETE FROM messages WHERE created_at < NOW() - INTERVAL 90 DAY;
-- Attention : Supprimer TOUTES les données
-- DELETE FROM messages;
-- DELETE FROM support_requests;
```
## 🔧 Maintenance
### Optimisation
```sql
-- Analyser les tables
ANALYZE TABLE messages;
ANALYZE TABLE support_requests;
-- Optimiser les tables
OPTIMIZE TABLE messages;
OPTIMIZE TABLE support_requests;
-- Vérifier l'état de la table
CHECK TABLE messages;
CHECK TABLE support_requests;
-- Réparer une table (si nécessaire)
REPAIR TABLE messages;
```
### Index
```sql
-- Voir les index existants
SHOW INDEX FROM messages;
SHOW INDEX FROM support_requests;
-- Créer un index sur l'email (si performance lente)
CREATE INDEX idx_messages_email ON messages(email);
CREATE INDEX idx_support_account_email ON support_requests(account_email);
-- Supprimer un index
DROP INDEX idx_messages_email ON messages;
```
### Backup
```sql
-- Exporter les données en SQL (en ligne de commande)
-- mysqldump -u root -p travelmateadmin > backup.sql
-- Exporter uniquement les messages
-- mysqldump -u root -p travelmateadmin messages > messages_backup.sql
-- Exporter sans données (structure seulement)
-- mysqldump -u root -p --no-data travelmateadmin > structure.sql
```
## 📊 Rapports et Analytics
### Analyse des Messages
```sql
-- Messages les plus fréquents par email
SELECT
email,
COUNT(*) AS nombre_messages,
MAX(created_at) AS dernier_message
FROM messages
GROUP BY email
ORDER BY nombre_messages DESC
LIMIT 10;
-- Temps moyen de traitement
SELECT
AVG(TIMESTAMPDIFF(HOUR, created_at, NOW())) AS heures_moyennes
FROM messages
WHERE done = TRUE;
-- Messages par mois
SELECT
DATE_FORMAT(created_at, '%Y-%m') AS mois,
COUNT(*) AS nombre
FROM messages
GROUP BY DATE_FORMAT(created_at, '%Y-%m')
ORDER BY mois DESC;
-- Taux de complétion
SELECT
ROUND(SUM(done) / COUNT(*) * 100, 2) AS taux_completion_pourcent
FROM messages;
```
### Analyse du Support
```sql
-- Problèmes les plus fréquents (par mots-clés)
SELECT
CASE
WHEN message LIKE '%connexion%' THEN 'Connexion'
WHEN message LIKE '%mot de passe%' THEN 'Mot de passe'
WHEN message LIKE '%synchronisation%' THEN 'Synchronisation'
WHEN message LIKE '%facturation%' THEN 'Facturation'
ELSE 'Autre'
END AS categorie,
COUNT(*) AS nombre
FROM support_requests
GROUP BY categorie
ORDER BY nombre DESC;
-- Utilisateurs avec plusieurs demandes
SELECT
account_email,
COUNT(*) AS nombre_demandes,
SUM(done) AS traitees,
COUNT(*) - SUM(done) AS en_attente
FROM support_requests
GROUP BY account_email
HAVING nombre_demandes > 1
ORDER BY nombre_demandes DESC;
```
## 🧪 Tests et Développement
### Générer des Données de Test
```sql
-- Générer 50 messages aléatoires
INSERT INTO messages (nom, prenom, email, message, done, created_at)
SELECT
CONCAT('Nom', n.n),
CONCAT('Prenom', n.n),
CONCAT('test', n.n, '@example.com'),
CONCAT('Message de test numéro ', n.n),
n.n % 3 = 0, -- Un tiers sera "done"
NOW() - INTERVAL FLOOR(RAND() * 30) DAY
FROM (
SELECT @row := @row + 1 AS n
FROM (SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) t1,
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) t2,
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) t3,
(SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3) t4,
(SELECT @row := 0) r
LIMIT 50
) n;
```
### Réinitialiser pour les Tests
```sql
-- Vider toutes les tables
TRUNCATE TABLE messages;
TRUNCATE TABLE support_requests;
-- Réinitialiser les auto-increment
ALTER TABLE messages AUTO_INCREMENT = 1;
ALTER TABLE support_requests AUTO_INCREMENT = 1;
-- Recréer les données de test
SOURCE database_setup.sql;
```
## 🔐 Sécurité et Utilisateurs
### Gestion des Utilisateurs
```sql
-- Créer un utilisateur pour l'application
CREATE USER 'travelmateapp'@'localhost' IDENTIFIED BY 'votre_mot_de_passe';
-- Donner les permissions nécessaires
GRANT SELECT, INSERT, UPDATE ON travelmateadmin.* TO 'travelmateapp'@'localhost';
-- Ne PAS donner DELETE en production pour la sécurité
-- GRANT DELETE ON travelmateadmin.* TO 'travelmateapp'@'localhost';
-- Appliquer les changements
FLUSH PRIVILEGES;
-- Voir les permissions d'un utilisateur
SHOW GRANTS FOR 'travelmateapp'@'localhost';
-- Révoquer une permission
REVOKE DELETE ON travelmateadmin.* FROM 'travelmateapp'@'localhost';
-- Supprimer un utilisateur
DROP USER 'travelmateapp'@'localhost';
```
## 📝 Informations sur la Base
```sql
-- Voir toutes les bases de données
SHOW DATABASES;
-- Utiliser la base TravelMate
USE travelmateadmin;
-- Voir toutes les tables
SHOW TABLES;
-- Voir la structure d'une table
DESCRIBE messages;
DESCRIBE support_requests;
-- Voir le CREATE TABLE original
SHOW CREATE TABLE messages;
-- Taille de la base de données
SELECT
table_schema AS 'Database',
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'Size (MB)'
FROM information_schema.tables
WHERE table_schema = 'travelmateadmin';
-- Nombre de lignes par table
SELECT
TABLE_NAME,
TABLE_ROWS
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'travelmateadmin';
```
## 🚨 Dépannage
```sql
-- Vérifier les connexions actives
SHOW PROCESSLIST;
-- Tuer une connexion bloquée (remplacer X par l'ID)
-- KILL X;
-- Voir les variables MySQL
SHOW VARIABLES LIKE 'max_connections';
SHOW VARIABLES LIKE 'wait_timeout';
-- Voir le statut du serveur
SHOW STATUS;
-- Voir les erreurs récentes
SHOW WARNINGS;
SHOW ERRORS;
```
---
## 💡 Tips
- Toujours tester vos requêtes DELETE/UPDATE avec SELECT d'abord
- Faites des backups avant toute modification importante
- Utilisez LIMIT dans vos requêtes de test
- Les index améliorent les performances SELECT mais ralentissent INSERT/UPDATE
- Utilisez EXPLAIN pour analyser les performances des requêtes
```sql
-- Exemple d'analyse de performance
EXPLAIN SELECT * FROM messages WHERE done = FALSE ORDER BY created_at DESC;
```

34
TravelMateAdmin.sln Normal file
View File

@@ -0,0 +1,34 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TravelMateAdmin", "TravelMateAdmin\TravelMateAdmin.csproj", "{760FF747-775F-4B67-A507-584C1D43D88E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{760FF747-775F-4B67-A507-584C1D43D88E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{760FF747-775F-4B67-A507-584C1D43D88E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{760FF747-775F-4B67-A507-584C1D43D88E}.Debug|x64.ActiveCfg = Debug|Any CPU
{760FF747-775F-4B67-A507-584C1D43D88E}.Debug|x64.Build.0 = Debug|Any CPU
{760FF747-775F-4B67-A507-584C1D43D88E}.Debug|x86.ActiveCfg = Debug|Any CPU
{760FF747-775F-4B67-A507-584C1D43D88E}.Debug|x86.Build.0 = Debug|Any CPU
{760FF747-775F-4B67-A507-584C1D43D88E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{760FF747-775F-4B67-A507-584C1D43D88E}.Release|Any CPU.Build.0 = Release|Any CPU
{760FF747-775F-4B67-A507-584C1D43D88E}.Release|x64.ActiveCfg = Release|Any CPU
{760FF747-775F-4B67-A507-584C1D43D88E}.Release|x64.Build.0 = Release|Any CPU
{760FF747-775F-4B67-A507-584C1D43D88E}.Release|x86.ActiveCfg = Release|Any CPU
{760FF747-775F-4B67-A507-584C1D43D88E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

14
TravelMateAdmin/App.xaml Normal file
View File

@@ -0,0 +1,14 @@
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TravelMateAdmin"
x:Class="TravelMateAdmin.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@@ -0,0 +1,16 @@
using Microsoft.Extensions.DependencyInjection;
namespace TravelMateAdmin;
public partial class App : Application
{
public App()
{
InitializeComponent();
}
protected override Window CreateWindow(IActivationState? activationState)
{
return new Window(new AppShell());
}
}

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="TravelMateAdmin.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:TravelMateAdmin.Views"
Title="TravelMate Admin"
Shell.FlyoutBehavior="Flyout">
<Shell.Resources>
<ResourceDictionary>
<Color x:Key="Primary">#1a1a1a</Color>
<Color x:Key="Accent">#5E50D9</Color>
<Style TargetType="TabBar" ApplyToDerivedTypes="True">
<Setter Property="Shell.TabBarBackgroundColor" Value="{StaticResource Primary}" />
<Setter Property="Shell.TabBarForegroundColor" Value="{StaticResource Accent}" />
</Style>
</ResourceDictionary>
</Shell.Resources>
<!-- Dashboard -->
<TabBar>
<ShellContent
Title="Dashboard"
Icon="home"
ContentTemplate="{DataTemplate views:DashboardPage}"
Route="DashboardPage" />
<!-- Messages -->
<ShellContent
Title="Messages"
Icon="mail"
ContentTemplate="{DataTemplate views:MessagesPage}"
Route="MessagesPage" />
<!-- Support -->
<ShellContent
Title="Support"
Icon="help"
ContentTemplate="{DataTemplate views:SupportPage}"
Route="SupportPage" />
</TabBar>
</Shell>

View File

@@ -0,0 +1,9 @@
namespace TravelMateAdmin;
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,17 @@
namespace TravelMateAdmin.Configuration;
public static class AppSettings
{
// Configuration de la base de données
// Modifiez ces valeurs selon votre environnement
public const string Server = "localhost";
public const string Port = "3306";
public const string Database = "travelmateadmin";
public const string User = "root";
public const string Password = "yourpassword";
public static string GetConnectionString()
{
return $"Server={Server};Port={Port};Database={Database};User={User};Password={Password};";
}
}

View File

@@ -0,0 +1,37 @@
using System.Globalization;
namespace TravelMateAdmin.Converters;
public class BoolToStatusTextConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is bool isDone)
{
return isDone ? "Marquer en attente" : "Marquer comme fait";
}
return "Changer statut";
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class BoolToColorConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is bool isDone)
{
return isDone ? Color.FromArgb("#FFA726") : Color.FromArgb("#4CAF50");
}
return Color.FromArgb("#5E50D9");
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TravelMateAdmin.MainPage">
<ScrollView>
<VerticalStackLayout
Padding="30,0"
Spacing="25">
<Image
Source="dotnet_bot.png"
HeightRequest="185"
Aspect="AspectFit"
SemanticProperties.Description="dot net bot in a submarine number ten" />
<Label
Text="Hello, World!"
Style="{StaticResource Headline}"
SemanticProperties.HeadingLevel="Level1" />
<Label
Text="Welcome to &#10;.NET Multi-platform App UI"
Style="{StaticResource SubHeadline}"
SemanticProperties.HeadingLevel="Level2"
SemanticProperties.Description="Welcome to dot net Multi platform App U I" />
<Button
x:Name="CounterBtn"
Text="Click me"
SemanticProperties.Hint="Counts the number of times you click"
Clicked="OnCounterClicked"
HorizontalOptions="Fill" />
</VerticalStackLayout>
</ScrollView>
</ContentPage>

View File

@@ -0,0 +1,23 @@
namespace TravelMateAdmin;
public partial class MainPage : ContentPage
{
int count = 0;
public MainPage()
{
InitializeComponent();
}
private void OnCounterClicked(object? sender, EventArgs e)
{
count++;
if (count == 1)
CounterBtn.Text = $"Clicked {count} time";
else
CounterBtn.Text = $"Clicked {count} times";
SemanticScreenReader.Announce(CounterBtn.Text);
}
}

View File

@@ -0,0 +1,40 @@
using Microsoft.Extensions.Logging;
using TravelMateAdmin.Services;
using TravelMateAdmin.ViewModels;
using TravelMateAdmin.Views;
namespace TravelMateAdmin;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
#if DEBUG
builder.Logging.AddDebug();
#endif
// Register Services
builder.Services.AddSingleton<IDatabaseService, DatabaseService>();
// Register ViewModels
builder.Services.AddSingleton<DashboardViewModel>();
builder.Services.AddTransient<MessagesViewModel>();
builder.Services.AddTransient<SupportViewModel>();
// Register Views
builder.Services.AddSingleton<DashboardPage>();
builder.Services.AddTransient<MessagesPage>();
builder.Services.AddTransient<SupportPage>();
return builder.Build();
}
}

View File

@@ -0,0 +1,16 @@
namespace TravelMateAdmin.Models;
public class Message
{
public int Id { get; set; }
public string Nom { get; set; } = string.Empty;
public string Prenom { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string MessageText { get; set; } = string.Empty;
public bool Done { get; set; }
public DateTime CreatedAt { get; set; }
public string FullName => $"{Prenom} {Nom}";
public string StatusText => Done ? "Traité" : "En attente";
public string CreatedAtFormatted => CreatedAt.ToString("dd/MM/yyyy HH:mm");
}

View File

@@ -0,0 +1,17 @@
namespace TravelMateAdmin.Models;
public class SupportRequest
{
public int Id { get; set; }
public string Nom { get; set; } = string.Empty;
public string Prenom { get; set; } = string.Empty;
public string AccountEmail { get; set; } = string.Empty;
public string ContactEmail { get; set; } = string.Empty;
public string MessageText { get; set; } = string.Empty;
public bool Done { get; set; }
public DateTime CreatedAt { get; set; }
public string FullName => $"{Prenom} {Nom}";
public string StatusText => Done ? "Traité" : "En attente";
public string CreatedAtFormatted => CreatedAt.ToString("dd/MM/yyyy HH:mm");
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View File

@@ -0,0 +1,10 @@
using Android.App;
using Android.Content.PM;
using Android.OS;
namespace TravelMateAdmin;
[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity
{
}

View File

@@ -0,0 +1,15 @@
using Android.App;
using Android.Runtime;
namespace TravelMateAdmin;
[Application]
public class MainApplication : MauiApplication
{
public MainApplication(IntPtr handle, JniHandleOwnership ownership)
: base(handle, ownership)
{
}
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#512BD4</color>
<color name="colorPrimaryDark">#2B0B98</color>
<color name="colorAccent">#2B0B98</color>
</resources>

View File

@@ -0,0 +1,9 @@
using Foundation;
namespace TravelMateAdmin;
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<!-- See https://aka.ms/maui-publish-app-store#add-entitlements for more information about adding entitlements.-->
<dict>
<!-- App Sandbox must be enabled to distribute a MacCatalyst app through the Mac App Store. -->
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- When App Sandbox is enabled, this value is required to open outgoing network connections. -->
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- The Mac App Store requires you specify if the app uses encryption. -->
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/itsappusesnonexemptencryption -->
<!-- <key>ITSAppUsesNonExemptEncryption</key> -->
<!-- Please indicate <true/> or <false/> here. -->
<!-- Specify the category for your app here. -->
<!-- Please consult https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationcategorytype -->
<!-- <key>LSApplicationCategoryType</key> -->
<!-- <string>public.app-category.YOUR-CATEGORY-HERE</string> -->
<key>UIDeviceFamily</key>
<array>
<integer>2</integer>
</array>
<key>LSApplicationCategoryType</key>
<string>public.app-category.lifestyle</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/appicon.appiconset</string>
</dict>
</plist>

View File

@@ -0,0 +1,15 @@
using ObjCRuntime;
using UIKit;
namespace TravelMateAdmin;
public class Program
{
// This is the main entry point of the application.
static void Main(string[] args)
{
// if you want to use a different Application Delegate class from "AppDelegate"
// you can specify it here.
UIApplication.Main(args, null, typeof(AppDelegate));
}
}

View File

@@ -0,0 +1,8 @@
<maui:MauiWinUIApplication
x:Class="TravelMateAdmin.WinUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:maui="using:Microsoft.Maui"
xmlns:local="using:TravelMateAdmin.WinUI">
</maui:MauiWinUIApplication>

View File

@@ -0,0 +1,24 @@
using Microsoft.UI.Xaml;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace TravelMateAdmin.WinUI;
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
public partial class App : MauiWinUIApplication
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
}
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
IgnorableNamespaces="uap rescap">
<Identity Name="maui-package-name-placeholder" Publisher="CN=User Name" Version="0.0.0.0" />
<mp:PhoneIdentity PhoneProductId="DF9B5AEA-A739-4C2A-8B2A-7275B002DFE5" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
<Properties>
<DisplayName>$placeholder$</DisplayName>
<PublisherDisplayName>User Name</PublisherDisplayName>
<Logo>$placeholder$.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate" />
</Resources>
<Applications>
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
<uap:VisualElements
DisplayName="$placeholder$"
Description="$placeholder$"
Square150x150Logo="$placeholder$.png"
Square44x44Logo="$placeholder$.png"
BackgroundColor="transparent">
<uap:DefaultTile Square71x71Logo="$placeholder$.png" Wide310x150Logo="$placeholder$.png" Square310x310Logo="$placeholder$.png" />
<uap:SplashScreen Image="$placeholder$.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<rescap:Capability Name="runFullTrust" />
</Capabilities>
</Package>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="TravelMateAdmin.WinUI.app"/>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<!-- The combination of below two tags have the following effect:
1) Per-Monitor for >= Windows 10 Anniversary Update
2) System < Windows 10 Anniversary Update
-->
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
</assembly>

View File

@@ -0,0 +1,9 @@
using Foundation;
namespace TravelMateAdmin;
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/appicon.appiconset</string>
</dict>
</plist>

View File

@@ -0,0 +1,15 @@
using ObjCRuntime;
using UIKit;
namespace TravelMateAdmin;
public class Program
{
// This is the main entry point of the application.
static void Main(string[] args)
{
// if you want to use a different Application Delegate class from "AppDelegate"
// you can specify it here.
UIApplication.Main(args, null, typeof(AppDelegate));
}
}

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
This is the minimum required version of the Apple Privacy Manifest for .NET MAUI apps.
The contents below are needed because of APIs that are used in the .NET framework and .NET MAUI SDK.
You are responsible for adding extra entries as needed for your application.
More information: https://aka.ms/maui-privacy-manifest
-->
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>E174.1</string>
</array>
</dict>
<!--
The entry below is only needed when you're using the Preferences API in your app.
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict> -->
</array>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
{
"profiles": {
"Windows Machine": {
"commandName": "Project",
"nativeDebugging": false
}
}
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="456" height="456" fill="#512BD4" />
</svg>

After

Width:  |  Height:  |  Size: 231 B

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

@@ -0,0 +1,15 @@
Any raw assets you want to be deployed with your application can be placed in
this directory (and child directories). Deployment of the asset to your application
is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
These files will be deployed with your package and will be accessible using Essentials:
async Task LoadMauiAsset()
{
using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
using var reader = new StreamReader(stream);
var contents = reader.ReadToEnd();
}

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="456" height="456" viewBox="0 0 456 456" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="m 105.50037,281.60863 c -2.70293,0 -5.00091,-0.90042 -6.893127,-2.70209 -1.892214,-1.84778 -2.837901,-4.04181 -2.837901,-6.58209 0,-2.58722 0.945687,-4.80389 2.837901,-6.65167 1.892217,-1.84778 4.190197,-2.77167 6.893127,-2.77167 2.74819,0 5.06798,0.92389 6.96019,2.77167 1.93749,1.84778 2.90581,4.06445 2.90581,6.65167 0,2.54028 -0.96832,4.73431 -2.90581,6.58209 -1.89221,1.80167 -4.212,2.70209 -6.96019,2.70209 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 213.56111,280.08446 H 195.99044 L 149.69953,207.0544 c -1.17121,-1.84778 -2.14037,-3.76515 -2.90581,-5.75126 h -0.40578 c 0.36051,2.12528 0.54076,6.67515 0.54076,13.6496 v 65.13172 h -15.54349 v -99.36009 h 18.71925 l 44.7374,71.29798 c 1.89222,2.95695 3.1087,4.98917 3.64945,6.09751 h 0.26996 c -0.45021,-2.6325 -0.67573,-7.09015 -0.67573,-13.37293 v -64.02256 h 15.47557 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="m 289.25134,280.08446 h -54.40052 v -99.36009 h 52.23835 v 13.99669 h -36.15411 v 28.13085 h 33.31621 v 13.9271 h -33.31621 v 29.37835 h 38.31628 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
<path d="M 366.56466,194.72106 H 338.7222 v 85.3634 h -16.08423 v -85.3634 h -27.77455 v -13.99669 h 71.70124 z" style="fill:#ffffff;fill-rule:nonzero;stroke-width:0.838376" />
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<!-- Note: For Android please see also Platforms\Android\Resources\values\colors.xml -->
<Color x:Key="Primary">#512BD4</Color>
<Color x:Key="PrimaryDark">#ac99ea</Color>
<Color x:Key="PrimaryDarkText">#242424</Color>
<Color x:Key="Secondary">#DFD8F7</Color>
<Color x:Key="SecondaryDarkText">#9880e5</Color>
<Color x:Key="Tertiary">#2B0B98</Color>
<Color x:Key="White">White</Color>
<Color x:Key="Black">Black</Color>
<Color x:Key="Magenta">#D600AA</Color>
<Color x:Key="MidnightBlue">#190649</Color>
<Color x:Key="OffBlack">#1f1f1f</Color>
<Color x:Key="Gray100">#E1E1E1</Color>
<Color x:Key="Gray200">#C8C8C8</Color>
<Color x:Key="Gray300">#ACACAC</Color>
<Color x:Key="Gray400">#919191</Color>
<Color x:Key="Gray500">#6E6E6E</Color>
<Color x:Key="Gray600">#404040</Color>
<Color x:Key="Gray900">#212121</Color>
<Color x:Key="Gray950">#141414</Color>
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource Primary}"/>
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource Secondary}"/>
<SolidColorBrush x:Key="TertiaryBrush" Color="{StaticResource Tertiary}"/>
<SolidColorBrush x:Key="WhiteBrush" Color="{StaticResource White}"/>
<SolidColorBrush x:Key="BlackBrush" Color="{StaticResource Black}"/>
<SolidColorBrush x:Key="Gray100Brush" Color="{StaticResource Gray100}"/>
<SolidColorBrush x:Key="Gray200Brush" Color="{StaticResource Gray200}"/>
<SolidColorBrush x:Key="Gray300Brush" Color="{StaticResource Gray300}"/>
<SolidColorBrush x:Key="Gray400Brush" Color="{StaticResource Gray400}"/>
<SolidColorBrush x:Key="Gray500Brush" Color="{StaticResource Gray500}"/>
<SolidColorBrush x:Key="Gray600Brush" Color="{StaticResource Gray600}"/>
<SolidColorBrush x:Key="Gray900Brush" Color="{StaticResource Gray900}"/>
<SolidColorBrush x:Key="Gray950Brush" Color="{StaticResource Gray950}"/>
</ResourceDictionary>

View File

@@ -0,0 +1,434 @@
<?xml version="1.0" encoding="UTF-8" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<Style TargetType="ActivityIndicator">
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
</Style>
<Style TargetType="IndicatorView">
<Setter Property="IndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}"/>
<Setter Property="SelectedIndicatorColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray100}}"/>
</Style>
<Style TargetType="Border">
<Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
<Setter Property="StrokeShape" Value="Rectangle"/>
<Setter Property="StrokeThickness" Value="1"/>
</Style>
<Style TargetType="BoxView">
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
</Style>
<Style TargetType="Button">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource PrimaryDarkText}}" />
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource PrimaryDark}}" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="BorderWidth" Value="0"/>
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="Padding" Value="14,10"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOver" />
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="CheckBox">
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="DatePicker">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Editor">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Entry">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray500}}" />
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="ImageButton">
<Setter Property="Opacity" Value="1" />
<Setter Property="BorderColor" Value="Transparent"/>
<Setter Property="BorderWidth" Value="0"/>
<Setter Property="CornerRadius" Value="0"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="Opacity" Value="0.5" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOver" />
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Label">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular" />
<Setter Property="FontSize" Value="14" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Label" x:Key="Headline">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" />
<Setter Property="FontSize" Value="32" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style TargetType="Label" x:Key="SubHeadline">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource MidnightBlue}, Dark={StaticResource White}}" />
<Setter Property="FontSize" Value="24" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<Style TargetType="Picker">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14" />
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
<Setter Property="TitleColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="ProgressBar">
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="ProgressColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="RadioButton">
<Setter Property="BackgroundColor" Value="Transparent"/>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="RefreshView">
<Setter Property="RefreshColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
</Style>
<Style TargetType="SearchBar">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
<Setter Property="CancelButtonColor" Value="{StaticResource Gray500}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular" />
<Setter Property="FontSize" Value="14" />
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="SearchHandler">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="PlaceholderColor" Value="{StaticResource Gray500}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontFamily" Value="OpenSansRegular" />
<Setter Property="FontSize" Value="14" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="Shadow">
<Setter Property="Radius" Value="15" />
<Setter Property="Opacity" Value="0.5" />
<Setter Property="Brush" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource White}}" />
<Setter Property="Offset" Value="10,10" />
</Style>
<Style TargetType="Slider">
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray600}}" />
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="MinimumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
<Setter Property="MaximumTrackColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="SwipeItem">
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
</Style>
<Style TargetType="Switch">
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
<Setter Property="ThumbColor" Value="{StaticResource White}" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="On">
<VisualState.Setters>
<Setter Property="OnColor" Value="{AppThemeBinding Light={StaticResource Secondary}, Dark={StaticResource Gray200}}" />
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource White}}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Off">
<VisualState.Setters>
<Setter Property="ThumbColor" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<Style TargetType="TimePicker">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource White}}" />
<Setter Property="BackgroundColor" Value="Transparent"/>
<Setter Property="FontFamily" Value="OpenSansRegular"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="MinimumHeightRequest" Value="44"/>
<Setter Property="MinimumWidthRequest" Value="44"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource Gray300}, Dark={StaticResource Gray600}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<!--
<Style TargetType="TitleBar">
<Setter Property="MinimumHeightRequest" Value="32"/>
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="TitleActiveStates">
<VisualState x:Name="TitleBarTitleActive">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="ForegroundColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource White}}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="TitleBarTitleInactive">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
<Setter Property="ForegroundColor" Value="{AppThemeBinding Light={StaticResource Gray400}, Dark={StaticResource Gray500}}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
-->
<Style TargetType="Page" ApplyToDerivedTypes="True">
<Setter Property="Padding" Value="0"/>
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
</Style>
<Style TargetType="Shell" ApplyToDerivedTypes="True">
<Setter Property="Shell.BackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
<Setter Property="Shell.ForegroundColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
<Setter Property="Shell.TitleColor" Value="{AppThemeBinding Light={StaticResource Black}, Dark={StaticResource SecondaryDarkText}}" />
<Setter Property="Shell.DisabledColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
<Setter Property="Shell.UnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray200}}" />
<Setter Property="Shell.NavBarHasShadow" Value="False" />
<Setter Property="Shell.TabBarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Black}}" />
<Setter Property="Shell.TabBarForegroundColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
<Setter Property="Shell.TabBarTitleColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
<Setter Property="Shell.TabBarUnselectedColor" Value="{AppThemeBinding Light={StaticResource Gray900}, Dark={StaticResource Gray200}}" />
</Style>
<Style TargetType="NavigationPage">
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource OffBlack}}" />
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
<Setter Property="IconColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource White}}" />
</Style>
<Style TargetType="TabbedPage">
<Setter Property="BarBackgroundColor" Value="{AppThemeBinding Light={StaticResource White}, Dark={StaticResource Gray950}}" />
<Setter Property="BarTextColor" Value="{AppThemeBinding Light={StaticResource Magenta}, Dark={StaticResource White}}" />
<Setter Property="UnselectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray200}, Dark={StaticResource Gray950}}" />
<Setter Property="SelectedTabColor" Value="{AppThemeBinding Light={StaticResource Gray950}, Dark={StaticResource Gray200}}" />
</Style>
</ResourceDictionary>

View File

@@ -0,0 +1,317 @@
using MySqlConnector;
using TravelMateAdmin.Models;
using TravelMateAdmin.Configuration;
namespace TravelMateAdmin.Services;
public class DatabaseService : IDatabaseService
{
private readonly string _connectionString;
public DatabaseService()
{
// Utilise la configuration depuis AppSettings
_connectionString = AppSettings.GetConnectionString();
}
public DatabaseService(string connectionString)
{
_connectionString = connectionString;
}
#region Connection
public async Task<bool> TestConnectionAsync()
{
try
{
using var connection = new MySqlConnection(_connectionString);
await connection.OpenAsync();
return true;
}
catch
{
return false;
}
}
#endregion
#region Messages
public async Task<List<Message>> GetAllMessagesAsync()
{
var messages = new List<Message>();
try
{
using var connection = new MySqlConnection(_connectionString);
await connection.OpenAsync();
var query = "SELECT id, nom, prenom, email, message, done, created_at FROM messages ORDER BY created_at DESC";
using var command = new MySqlCommand(query, connection);
using var reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
messages.Add(new Message
{
Id = reader.GetInt32("id"),
Nom = reader.GetString("nom"),
Prenom = reader.GetString("prenom"),
Email = reader.GetString("email"),
MessageText = reader.GetString("message"),
Done = reader.GetBoolean("done"),
CreatedAt = reader.GetDateTime("created_at")
});
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error getting messages: {ex.Message}");
}
return messages;
}
public async Task<List<Message>> GetMessagesFilteredAsync(bool? isDone)
{
if (isDone == null)
return await GetAllMessagesAsync();
var messages = new List<Message>();
try
{
using var connection = new MySqlConnection(_connectionString);
await connection.OpenAsync();
var query = "SELECT id, nom, prenom, email, message, done, created_at FROM messages WHERE done = @done ORDER BY created_at DESC";
using var command = new MySqlCommand(query, connection);
command.Parameters.AddWithValue("@done", isDone.Value);
using var reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
messages.Add(new Message
{
Id = reader.GetInt32("id"),
Nom = reader.GetString("nom"),
Prenom = reader.GetString("prenom"),
Email = reader.GetString("email"),
MessageText = reader.GetString("message"),
Done = reader.GetBoolean("done"),
CreatedAt = reader.GetDateTime("created_at")
});
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error getting filtered messages: {ex.Message}");
}
return messages;
}
public async Task<int> GetMessagesPendingCountAsync()
{
try
{
using var connection = new MySqlConnection(_connectionString);
await connection.OpenAsync();
var query = "SELECT COUNT(*) FROM messages WHERE done = 0";
using var command = new MySqlCommand(query, connection);
var result = await command.ExecuteScalarAsync();
return Convert.ToInt32(result);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error getting pending messages count: {ex.Message}");
return 0;
}
}
public async Task<int> GetMessagesDoneCountAsync()
{
try
{
using var connection = new MySqlConnection(_connectionString);
await connection.OpenAsync();
var query = "SELECT COUNT(*) FROM messages WHERE done = 1";
using var command = new MySqlCommand(query, connection);
var result = await command.ExecuteScalarAsync();
return Convert.ToInt32(result);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error getting done messages count: {ex.Message}");
return 0;
}
}
public async Task<bool> UpdateMessageStatusAsync(int id, bool done)
{
try
{
using var connection = new MySqlConnection(_connectionString);
await connection.OpenAsync();
var query = "UPDATE messages SET done = @done WHERE id = @id";
using var command = new MySqlCommand(query, connection);
command.Parameters.AddWithValue("@done", done);
command.Parameters.AddWithValue("@id", id);
var rowsAffected = await command.ExecuteNonQueryAsync();
return rowsAffected > 0;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error updating message status: {ex.Message}");
return false;
}
}
#endregion
#region Support Requests
public async Task<List<SupportRequest>> GetAllSupportRequestsAsync()
{
var requests = new List<SupportRequest>();
try
{
using var connection = new MySqlConnection(_connectionString);
await connection.OpenAsync();
var query = "SELECT id, nom, prenom, account_email, contact_email, message, done, created_at FROM support_requests ORDER BY created_at DESC";
using var command = new MySqlCommand(query, connection);
using var reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
requests.Add(new SupportRequest
{
Id = reader.GetInt32("id"),
Nom = reader.GetString("nom"),
Prenom = reader.GetString("prenom"),
AccountEmail = reader.GetString("account_email"),
ContactEmail = reader.GetString("contact_email"),
MessageText = reader.GetString("message"),
Done = reader.GetBoolean("done"),
CreatedAt = reader.GetDateTime("created_at")
});
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error getting support requests: {ex.Message}");
}
return requests;
}
public async Task<List<SupportRequest>> GetSupportRequestsFilteredAsync(bool? isDone)
{
if (isDone == null)
return await GetAllSupportRequestsAsync();
var requests = new List<SupportRequest>();
try
{
using var connection = new MySqlConnection(_connectionString);
await connection.OpenAsync();
var query = "SELECT id, nom, prenom, account_email, contact_email, message, done, created_at FROM support_requests WHERE done = @done ORDER BY created_at DESC";
using var command = new MySqlCommand(query, connection);
command.Parameters.AddWithValue("@done", isDone.Value);
using var reader = await command.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
requests.Add(new SupportRequest
{
Id = reader.GetInt32("id"),
Nom = reader.GetString("nom"),
Prenom = reader.GetString("prenom"),
AccountEmail = reader.GetString("account_email"),
ContactEmail = reader.GetString("contact_email"),
MessageText = reader.GetString("message"),
Done = reader.GetBoolean("done"),
CreatedAt = reader.GetDateTime("created_at")
});
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error getting filtered support requests: {ex.Message}");
}
return requests;
}
public async Task<int> GetSupportRequestsPendingCountAsync()
{
try
{
using var connection = new MySqlConnection(_connectionString);
await connection.OpenAsync();
var query = "SELECT COUNT(*) FROM support_requests WHERE done = 0";
using var command = new MySqlCommand(query, connection);
var result = await command.ExecuteScalarAsync();
return Convert.ToInt32(result);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error getting pending support requests count: {ex.Message}");
return 0;
}
}
public async Task<int> GetSupportRequestsDoneCountAsync()
{
try
{
using var connection = new MySqlConnection(_connectionString);
await connection.OpenAsync();
var query = "SELECT COUNT(*) FROM support_requests WHERE done = 1";
using var command = new MySqlCommand(query, connection);
var result = await command.ExecuteScalarAsync();
return Convert.ToInt32(result);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error getting done support requests count: {ex.Message}");
return 0;
}
}
public async Task<bool> UpdateSupportRequestStatusAsync(int id, bool done)
{
try
{
using var connection = new MySqlConnection(_connectionString);
await connection.OpenAsync();
var query = "UPDATE support_requests SET done = @done WHERE id = @id";
using var command = new MySqlCommand(query, connection);
command.Parameters.AddWithValue("@done", done);
command.Parameters.AddWithValue("@id", id);
var rowsAffected = await command.ExecuteNonQueryAsync();
return rowsAffected > 0;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error updating support request status: {ex.Message}");
return false;
}
}
#endregion
}

View File

@@ -0,0 +1,23 @@
using TravelMateAdmin.Models;
namespace TravelMateAdmin.Services;
public interface IDatabaseService
{
// Messages
Task<List<Message>> GetAllMessagesAsync();
Task<List<Message>> GetMessagesFilteredAsync(bool? isDone);
Task<int> GetMessagesPendingCountAsync();
Task<int> GetMessagesDoneCountAsync();
Task<bool> UpdateMessageStatusAsync(int id, bool done);
// Support Requests
Task<List<SupportRequest>> GetAllSupportRequestsAsync();
Task<List<SupportRequest>> GetSupportRequestsFilteredAsync(bool? isDone);
Task<int> GetSupportRequestsPendingCountAsync();
Task<int> GetSupportRequestsDoneCountAsync();
Task<bool> UpdateSupportRequestStatusAsync(int id, bool done);
// Connection
Task<bool> TestConnectionAsync();
}

View File

@@ -0,0 +1,66 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net10.0-windows10.0.19041.0</TargetFrameworks>
<!-- Note for MacCatalyst:
The default runtime is maccatalyst-x64, except in Release config, in which case the default is maccatalyst-x64;maccatalyst-arm64.
When specifying both architectures, use the plural <RuntimeIdentifiers> instead of the singular <RuntimeIdentifier>.
The Mac App Store will NOT accept apps with ONLY maccatalyst-arm64 indicated;
either BOTH runtimes must be indicated or ONLY macatalyst-x64. -->
<!-- For example: <RuntimeIdentifiers>maccatalyst-x64;maccatalyst-arm64</RuntimeIdentifiers> -->
<OutputType>Exe</OutputType>
<RootNamespace>TravelMateAdmin</RootNamespace>
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- Display name -->
<ApplicationTitle>TravelMateAdmin</ApplicationTitle>
<!-- App Identifier -->
<ApplicationId>com.companyname.travelmateadmin</ApplicationId>
<!-- Versions -->
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
<WindowsPackageType>None</WindowsPackageType>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
</PropertyGroup>
<ItemGroup>
<!-- App Icon -->
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />
<!-- Splash Screen -->
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />
<!-- Images -->
<MauiImage Include="Resources\Images\*" />
<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185" />
<!-- Custom Fonts -->
<MauiFont Include="Resources\Fonts\*" />
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
<PackageReference Include="MySqlConnector" Version="2.4.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,76 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using TravelMateAdmin.Services;
namespace TravelMateAdmin.ViewModels;
public partial class DashboardViewModel : ObservableObject
{
private readonly IDatabaseService _databaseService;
[ObservableProperty]
private int messagesPending;
[ObservableProperty]
private int messagesDone;
[ObservableProperty]
private int supportRequestsPending;
[ObservableProperty]
private int supportRequestsDone;
[ObservableProperty]
private bool isLoading;
[ObservableProperty]
private string connectionStatus = "Non connecté";
public DashboardViewModel(IDatabaseService databaseService)
{
_databaseService = databaseService;
}
[RelayCommand]
public async Task LoadDashboardAsync()
{
IsLoading = true;
try
{
// Test connection
var isConnected = await _databaseService.TestConnectionAsync();
ConnectionStatus = isConnected ? "✓ Connecté" : "✗ Déconnecté";
if (isConnected)
{
// Load stats
MessagesPending = await _databaseService.GetMessagesPendingCountAsync();
MessagesDone = await _databaseService.GetMessagesDoneCountAsync();
SupportRequestsPending = await _databaseService.GetSupportRequestsPendingCountAsync();
SupportRequestsDone = await _databaseService.GetSupportRequestsDoneCountAsync();
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error loading dashboard: {ex.Message}");
ConnectionStatus = "✗ Erreur de connexion";
}
finally
{
IsLoading = false;
}
}
[RelayCommand]
private async Task NavigateToMessagesAsync()
{
await Shell.Current.GoToAsync("//MessagesPage");
}
[RelayCommand]
private async Task NavigateToSupportAsync()
{
await Shell.Current.GoToAsync("//SupportPage");
}
}

View File

@@ -0,0 +1,83 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using TravelMateAdmin.Models;
using TravelMateAdmin.Services;
namespace TravelMateAdmin.ViewModels;
public partial class MessagesViewModel : ObservableObject
{
private readonly IDatabaseService _databaseService;
[ObservableProperty]
private ObservableCollection<Message> messages = new();
[ObservableProperty]
private bool isLoading;
[ObservableProperty]
private string selectedFilter = "Tout";
public List<string> Filters { get; } = new() { "Tout", "À faire", "Fait" };
public MessagesViewModel(IDatabaseService databaseService)
{
_databaseService = databaseService;
}
[RelayCommand]
public async Task LoadMessagesAsync()
{
IsLoading = true;
try
{
bool? isDone = SelectedFilter switch
{
"À faire" => false,
"Fait" => true,
_ => null
};
var messagesList = await _databaseService.GetMessagesFilteredAsync(isDone);
Messages.Clear();
foreach (var message in messagesList)
{
Messages.Add(message);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error loading messages: {ex.Message}");
}
finally
{
IsLoading = false;
}
}
[RelayCommand]
private async Task ToggleMessageStatusAsync(Message message)
{
if (message == null) return;
var success = await _databaseService.UpdateMessageStatusAsync(message.Id, !message.Done);
if (success)
{
message.Done = !message.Done;
OnPropertyChanged(nameof(Messages));
}
}
[RelayCommand]
private async Task RefreshAsync()
{
await LoadMessagesAsync();
}
partial void OnSelectedFilterChanged(string value)
{
_ = LoadMessagesAsync();
}
}

View File

@@ -0,0 +1,83 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using TravelMateAdmin.Models;
using TravelMateAdmin.Services;
namespace TravelMateAdmin.ViewModels;
public partial class SupportViewModel : ObservableObject
{
private readonly IDatabaseService _databaseService;
[ObservableProperty]
private ObservableCollection<SupportRequest> supportRequests = new();
[ObservableProperty]
private bool isLoading;
[ObservableProperty]
private string selectedFilter = "Tout";
public List<string> Filters { get; } = new() { "Tout", "À faire", "Fait" };
public SupportViewModel(IDatabaseService databaseService)
{
_databaseService = databaseService;
}
[RelayCommand]
public async Task LoadSupportRequestsAsync()
{
IsLoading = true;
try
{
bool? isDone = SelectedFilter switch
{
"À faire" => false,
"Fait" => true,
_ => null
};
var requestsList = await _databaseService.GetSupportRequestsFilteredAsync(isDone);
SupportRequests.Clear();
foreach (var request in requestsList)
{
SupportRequests.Add(request);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error loading support requests: {ex.Message}");
}
finally
{
IsLoading = false;
}
}
[RelayCommand]
private async Task ToggleSupportRequestStatusAsync(SupportRequest request)
{
if (request == null) return;
var success = await _databaseService.UpdateSupportRequestStatusAsync(request.Id, !request.Done);
if (success)
{
request.Done = !request.Done;
OnPropertyChanged(nameof(SupportRequests));
}
}
[RelayCommand]
private async Task RefreshAsync()
{
await LoadSupportRequestsAsync();
}
partial void OnSelectedFilterChanged(string value)
{
_ = LoadSupportRequestsAsync();
}
}

View File

@@ -0,0 +1,165 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:TravelMateAdmin.ViewModels"
x:Class="TravelMateAdmin.Views.DashboardPage"
x:DataType="vm:DashboardViewModel"
Title="Tableau de Bord"
BackgroundColor="{StaticResource Primary}">
<ContentPage.Resources>
<ResourceDictionary>
<Color x:Key="Primary">#1a1a1a</Color>
<Color x:Key="Secondary">#2d2d2d</Color>
<Color x:Key="Accent">#5E50D9</Color>
<Color x:Key="TextPrimary">#FFFFFF</Color>
<Color x:Key="TextSecondary">#B0B0B0</Color>
<Color x:Key="Success">#4CAF50</Color>
<Color x:Key="Warning">#FFA726</Color>
</ResourceDictionary>
</ContentPage.Resources>
<ScrollView>
<VerticalStackLayout Padding="20" Spacing="20">
<!-- Header -->
<Frame BackgroundColor="{StaticResource Secondary}"
CornerRadius="10"
Padding="20"
HasShadow="True">
<Grid RowDefinitions="Auto,Auto" ColumnDefinitions="*,Auto">
<Label Text="TravelMate Admin"
FontSize="32"
FontAttributes="Bold"
TextColor="{StaticResource TextPrimary}"
Grid.Row="0" Grid.Column="0"/>
<Label Text="Panneau d'Administration"
FontSize="16"
TextColor="{StaticResource TextSecondary}"
Grid.Row="1" Grid.Column="0"/>
<Label Text="{Binding ConnectionStatus}"
FontSize="14"
TextColor="{StaticResource Success}"
VerticalOptions="Center"
Grid.Row="0" Grid.Column="1" Grid.RowSpan="2"/>
</Grid>
</Frame>
<!-- Loading Indicator -->
<ActivityIndicator IsRunning="{Binding IsLoading}"
IsVisible="{Binding IsLoading}"
Color="{StaticResource Accent}"
HeightRequest="50"/>
<!-- Stats Cards -->
<Grid ColumnDefinitions="*,*"
RowDefinitions="Auto,Auto"
ColumnSpacing="15"
RowSpacing="15">
<!-- Messages Pending -->
<Frame BackgroundColor="{StaticResource Secondary}"
CornerRadius="10"
Padding="20"
HasShadow="True"
Grid.Row="0" Grid.Column="0">
<Frame.GestureRecognizers>
<TapGestureRecognizer Command="{Binding NavigateToMessagesCommand}"/>
</Frame.GestureRecognizers>
<VerticalStackLayout Spacing="10">
<Label Text="Messages"
FontSize="16"
TextColor="{StaticResource TextSecondary}"/>
<Label Text="{Binding MessagesPending}"
FontSize="48"
FontAttributes="Bold"
TextColor="{StaticResource Warning}"/>
<Label Text="En attente"
FontSize="14"
TextColor="{StaticResource TextSecondary}"/>
</VerticalStackLayout>
</Frame>
<!-- Messages Done -->
<Frame BackgroundColor="{StaticResource Secondary}"
CornerRadius="10"
Padding="20"
HasShadow="True"
Grid.Row="0" Grid.Column="1">
<Frame.GestureRecognizers>
<TapGestureRecognizer Command="{Binding NavigateToMessagesCommand}"/>
</Frame.GestureRecognizers>
<VerticalStackLayout Spacing="10">
<Label Text="Messages"
FontSize="16"
TextColor="{StaticResource TextSecondary}"/>
<Label Text="{Binding MessagesDone}"
FontSize="48"
FontAttributes="Bold"
TextColor="{StaticResource Success}"/>
<Label Text="Traités"
FontSize="14"
TextColor="{StaticResource TextSecondary}"/>
</VerticalStackLayout>
</Frame>
<!-- Support Pending -->
<Frame BackgroundColor="{StaticResource Secondary}"
CornerRadius="10"
Padding="20"
HasShadow="True"
Grid.Row="1" Grid.Column="0">
<Frame.GestureRecognizers>
<TapGestureRecognizer Command="{Binding NavigateToSupportCommand}"/>
</Frame.GestureRecognizers>
<VerticalStackLayout Spacing="10">
<Label Text="Support"
FontSize="16"
TextColor="{StaticResource TextSecondary}"/>
<Label Text="{Binding SupportRequestsPending}"
FontSize="48"
FontAttributes="Bold"
TextColor="{StaticResource Warning}"/>
<Label Text="En attente"
FontSize="14"
TextColor="{StaticResource TextSecondary}"/>
</VerticalStackLayout>
</Frame>
<!-- Support Done -->
<Frame BackgroundColor="{StaticResource Secondary}"
CornerRadius="10"
Padding="20"
HasShadow="True"
Grid.Row="1" Grid.Column="1">
<Frame.GestureRecognizers>
<TapGestureRecognizer Command="{Binding NavigateToSupportCommand}"/>
</Frame.GestureRecognizers>
<VerticalStackLayout Spacing="10">
<Label Text="Support"
FontSize="16"
TextColor="{StaticResource TextSecondary}"/>
<Label Text="{Binding SupportRequestsDone}"
FontSize="48"
FontAttributes="Bold"
TextColor="{StaticResource Success}"/>
<Label Text="Traités"
FontSize="14"
TextColor="{StaticResource TextSecondary}"/>
</VerticalStackLayout>
</Frame>
</Grid>
<!-- Refresh Button -->
<Button Text="Actualiser"
Command="{Binding LoadDashboardCommand}"
BackgroundColor="{StaticResource Accent}"
TextColor="{StaticResource TextPrimary}"
CornerRadius="10"
HeightRequest="50"
FontSize="16"
FontAttributes="Bold"/>
</VerticalStackLayout>
</ScrollView>
</ContentPage>

View File

@@ -0,0 +1,21 @@
using TravelMateAdmin.ViewModels;
namespace TravelMateAdmin.Views;
public partial class DashboardPage : ContentPage
{
private readonly DashboardViewModel _viewModel;
public DashboardPage(DashboardViewModel viewModel)
{
InitializeComponent();
_viewModel = viewModel;
BindingContext = _viewModel;
}
protected override async void OnAppearing()
{
base.OnAppearing();
await _viewModel.LoadDashboardCommand.ExecuteAsync(null);
}
}

View File

@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:TravelMateAdmin.ViewModels"
xmlns:models="clr-namespace:TravelMateAdmin.Models"
xmlns:converters="clr-namespace:TravelMateAdmin.Converters"
x:Class="TravelMateAdmin.Views.MessagesPage"
x:DataType="vm:MessagesViewModel"
Title="Messages"
BackgroundColor="{StaticResource Primary}">
<ContentPage.Resources>
<ResourceDictionary>
<Color x:Key="Primary">#1a1a1a</Color>
<Color x:Key="Secondary">#2d2d2d</Color>
<Color x:Key="Accent">#5E50D9</Color>
<Color x:Key="TextPrimary">#FFFFFF</Color>
<Color x:Key="TextSecondary">#B0B0B0</Color>
<Color x:Key="Success">#4CAF50</Color>
<Color x:Key="Warning">#FFA726</Color>
<Color x:Key="CardBackground">#252525</Color>
<converters:BoolToStatusTextConverter x:Key="BoolToStatusTextConverter"/>
<converters:BoolToColorConverter x:Key="BoolToColorConverter"/>
</ResourceDictionary>
</ContentPage.Resources>
<Grid RowDefinitions="Auto,Auto,*" Padding="20" RowSpacing="15">
<!-- Header with Filter -->
<Grid Grid.Row="0" ColumnDefinitions="*,Auto,Auto" ColumnSpacing="10">
<Label Text="Messages"
FontSize="28"
FontAttributes="Bold"
TextColor="{StaticResource TextPrimary}"
VerticalOptions="Center"
Grid.Column="0"/>
<Picker ItemsSource="{Binding Filters}"
SelectedItem="{Binding SelectedFilter}"
TextColor="{StaticResource TextPrimary}"
BackgroundColor="{StaticResource Secondary}"
WidthRequest="150"
Grid.Column="1"/>
<Button Text="↻"
Command="{Binding RefreshCommand}"
BackgroundColor="{StaticResource Accent}"
TextColor="{StaticResource TextPrimary}"
WidthRequest="50"
HeightRequest="50"
CornerRadius="25"
FontSize="20"
Grid.Column="2"/>
</Grid>
<!-- Loading Indicator -->
<ActivityIndicator IsRunning="{Binding IsLoading}"
IsVisible="{Binding IsLoading}"
Color="{StaticResource Accent}"
HeightRequest="50"
Grid.Row="1"/>
<!-- Messages List -->
<CollectionView ItemsSource="{Binding Messages}"
Grid.Row="2"
SelectionMode="None">
<CollectionView.EmptyView>
<VerticalStackLayout HorizontalOptions="Center"
VerticalOptions="Center"
Spacing="10">
<Label Text="Aucun message"
FontSize="20"
TextColor="{StaticResource TextSecondary}"
HorizontalOptions="Center"/>
</VerticalStackLayout>
</CollectionView.EmptyView>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:Message">
<Frame BackgroundColor="{StaticResource CardBackground}"
CornerRadius="10"
Padding="15"
Margin="0,5"
HasShadow="True">
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto"
ColumnDefinitions="*,Auto"
RowSpacing="8">
<!-- Name and Date -->
<Label Text="{Binding FullName}"
FontSize="18"
FontAttributes="Bold"
TextColor="{StaticResource TextPrimary}"
Grid.Row="0" Grid.Column="0"/>
<Label Text="{Binding CreatedAtFormatted}"
FontSize="12"
TextColor="{StaticResource TextSecondary}"
Grid.Row="0" Grid.Column="1"/>
<!-- Email -->
<Label Text="{Binding Email}"
FontSize="14"
TextColor="{StaticResource Accent}"
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"/>
<!-- Message -->
<Label Text="{Binding MessageText}"
FontSize="14"
TextColor="{StaticResource TextSecondary}"
LineBreakMode="WordWrap"
MaxLines="5"
Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"/>
<!-- Separator -->
<BoxView BackgroundColor="{StaticResource Secondary}"
HeightRequest="1"
Margin="0,8"
Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2"/>
<!-- Status and Action -->
<Label Text="{Binding StatusText}"
FontSize="14"
TextColor="{StaticResource Warning}"
VerticalOptions="Center"
Grid.Row="4" Grid.Column="0"/>
<Button Text="{Binding Done, Converter={StaticResource BoolToStatusTextConverter}}"
Command="{Binding Source={RelativeSource AncestorType={x:Type vm:MessagesViewModel}}, Path=ToggleMessageStatusCommand}"
CommandParameter="{Binding .}"
BackgroundColor="{Binding Done, Converter={StaticResource BoolToColorConverter}}"
TextColor="{StaticResource TextPrimary}"
CornerRadius="8"
FontSize="14"
HeightRequest="40"
Grid.Row="4" Grid.Column="1"/>
</Grid>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
</ContentPage>

View File

@@ -0,0 +1,21 @@
using TravelMateAdmin.ViewModels;
namespace TravelMateAdmin.Views;
public partial class MessagesPage : ContentPage
{
private readonly MessagesViewModel _viewModel;
public MessagesPage(MessagesViewModel viewModel)
{
InitializeComponent();
_viewModel = viewModel;
BindingContext = _viewModel;
}
protected override async void OnAppearing()
{
base.OnAppearing();
await _viewModel.LoadMessagesCommand.ExecuteAsync(null);
}
}

View File

@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:TravelMateAdmin.ViewModels"
xmlns:models="clr-namespace:TravelMateAdmin.Models"
xmlns:converters="clr-namespace:TravelMateAdmin.Converters"
x:Class="TravelMateAdmin.Views.SupportPage"
x:DataType="vm:SupportViewModel"
Title="Support"
BackgroundColor="{StaticResource Primary}">
<ContentPage.Resources>
<ResourceDictionary>
<Color x:Key="Primary">#1a1a1a</Color>
<Color x:Key="Secondary">#2d2d2d</Color>
<Color x:Key="Accent">#5E50D9</Color>
<Color x:Key="TextPrimary">#FFFFFF</Color>
<Color x:Key="TextSecondary">#B0B0B0</Color>
<Color x:Key="Success">#4CAF50</Color>
<Color x:Key="Warning">#FFA726</Color>
<Color x:Key="CardBackground">#252525</Color>
<converters:BoolToStatusTextConverter x:Key="BoolToStatusTextConverter"/>
<converters:BoolToColorConverter x:Key="BoolToColorConverter"/>
</ResourceDictionary>
</ContentPage.Resources>
<Grid RowDefinitions="Auto,Auto,*" Padding="20" RowSpacing="15">
<!-- Header with Filter -->
<Grid Grid.Row="0" ColumnDefinitions="*,Auto,Auto" ColumnSpacing="10">
<Label Text="Demandes Support"
FontSize="28"
FontAttributes="Bold"
TextColor="{StaticResource TextPrimary}"
VerticalOptions="Center"
Grid.Column="0"/>
<Picker ItemsSource="{Binding Filters}"
SelectedItem="{Binding SelectedFilter}"
TextColor="{StaticResource TextPrimary}"
BackgroundColor="{StaticResource Secondary}"
WidthRequest="150"
Grid.Column="1"/>
<Button Text="↻"
Command="{Binding RefreshCommand}"
BackgroundColor="{StaticResource Accent}"
TextColor="{StaticResource TextPrimary}"
WidthRequest="50"
HeightRequest="50"
CornerRadius="25"
FontSize="20"
Grid.Column="2"/>
</Grid>
<!-- Loading Indicator -->
<ActivityIndicator IsRunning="{Binding IsLoading}"
IsVisible="{Binding IsLoading}"
Color="{StaticResource Accent}"
HeightRequest="50"
Grid.Row="1"/>
<!-- Support Requests List -->
<CollectionView ItemsSource="{Binding SupportRequests}"
Grid.Row="2"
SelectionMode="None">
<CollectionView.EmptyView>
<VerticalStackLayout HorizontalOptions="Center"
VerticalOptions="Center"
Spacing="10">
<Label Text="Aucune demande support"
FontSize="20"
TextColor="{StaticResource TextSecondary}"
HorizontalOptions="Center"/>
</VerticalStackLayout>
</CollectionView.EmptyView>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:SupportRequest">
<Frame BackgroundColor="{StaticResource CardBackground}"
CornerRadius="10"
Padding="15"
Margin="0,5"
HasShadow="True">
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto"
ColumnDefinitions="*,Auto"
RowSpacing="8">
<!-- Name and Date -->
<Label Text="{Binding FullName}"
FontSize="18"
FontAttributes="Bold"
TextColor="{StaticResource TextPrimary}"
Grid.Row="0" Grid.Column="0"/>
<Label Text="{Binding CreatedAtFormatted}"
FontSize="12"
TextColor="{StaticResource TextSecondary}"
Grid.Row="0" Grid.Column="1"/>
<!-- Account Email -->
<Label Text="{Binding AccountEmail, StringFormat='Compte: {0}'}"
FontSize="14"
TextColor="{StaticResource Accent}"
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"/>
<!-- Contact Email -->
<Label Text="{Binding ContactEmail, StringFormat='Contact: {0}'}"
FontSize="14"
TextColor="{StaticResource Accent}"
Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"/>
<!-- Message -->
<Label Text="{Binding MessageText}"
FontSize="14"
TextColor="{StaticResource TextSecondary}"
LineBreakMode="WordWrap"
MaxLines="5"
Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2"/>
<!-- Separator -->
<BoxView BackgroundColor="{StaticResource Secondary}"
HeightRequest="1"
Margin="0,8"
Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2"/>
<!-- Status and Action -->
<Label Text="{Binding StatusText}"
FontSize="14"
TextColor="{StaticResource Warning}"
VerticalOptions="Center"
Grid.Row="5" Grid.Column="0"/>
<Button Text="{Binding Done, Converter={StaticResource BoolToStatusTextConverter}}"
Command="{Binding Source={RelativeSource AncestorType={x:Type vm:SupportViewModel}}, Path=ToggleSupportRequestStatusCommand}"
CommandParameter="{Binding .}"
BackgroundColor="{Binding Done, Converter={StaticResource BoolToColorConverter}}"
TextColor="{StaticResource TextPrimary}"
CornerRadius="8"
FontSize="14"
HeightRequest="40"
Grid.Row="5" Grid.Column="1"/>
</Grid>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
</ContentPage>

View File

@@ -0,0 +1,21 @@
using TravelMateAdmin.ViewModels;
namespace TravelMateAdmin.Views;
public partial class SupportPage : ContentPage
{
private readonly SupportViewModel _viewModel;
public SupportPage(SupportViewModel viewModel)
{
InitializeComponent();
_viewModel = viewModel;
BindingContext = _viewModel;
}
protected override async void OnAppearing()
{
base.OnAppearing();
await _viewModel.LoadSupportRequestsCommand.ExecuteAsync(null);
}
}

52
database_setup.sql Normal file
View File

@@ -0,0 +1,52 @@
-- Script SQL pour créer la base de données TravelMate Admin
-- À exécuter dans MySQL/MariaDB
-- Créer la base de données
CREATE DATABASE IF NOT EXISTS travelmateadmin CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE travelmateadmin;
-- Table des messages
CREATE TABLE IF NOT EXISTS messages (
id INT AUTO_INCREMENT PRIMARY KEY,
nom VARCHAR(100) NOT NULL,
prenom VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL,
message TEXT NOT NULL,
done BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_done (done),
INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Table des demandes support
CREATE TABLE IF NOT EXISTS support_requests (
id INT AUTO_INCREMENT PRIMARY KEY,
nom VARCHAR(100) NOT NULL,
prenom VARCHAR(100) NOT NULL,
account_email VARCHAR(255) NOT NULL,
contact_email VARCHAR(255) NOT NULL,
message TEXT NOT NULL,
done BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_done (done),
INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Données de test pour messages
INSERT INTO messages (nom, prenom, email, message, done, created_at) VALUES
('Dupont', 'Jean', 'jean.dupont@example.com', 'Je souhaite effacer toutes mes données personnelles conformément au RGPD.', FALSE, NOW() - INTERVAL 2 DAY),
('Martin', 'Sophie', 'sophie.martin@example.com', 'Pouvez-vous supprimer mon compte et toutes les informations associées ?', FALSE, NOW() - INTERVAL 1 DAY),
('Bernard', 'Pierre', 'pierre.bernard@example.com', 'Demande de suppression de données RGPD', TRUE, NOW() - INTERVAL 5 DAY),
('Dubois', 'Marie', 'marie.dubois@example.com', 'Bonjour, je voudrais avoir des informations sur vos services.', FALSE, NOW() - INTERVAL 3 HOUR);
-- Données de test pour support_requests
INSERT INTO support_requests (nom, prenom, account_email, contact_email, message, done, created_at) VALUES
('Petit', 'Lucas', 'lucas.petit@example.com', 'lucas.contact@example.com', 'Je n''arrive pas à me connecter à mon compte depuis hier.', FALSE, NOW() - INTERVAL 1 DAY),
('Roux', 'Emma', 'emma.roux@example.com', 'emma.pro@example.com', 'Mon mot de passe ne fonctionne plus, pouvez-vous m''aider ?', FALSE, NOW() - INTERVAL 6 HOUR),
('Moreau', 'Thomas', 'thomas.moreau@example.com', 'thomas.m@example.com', 'Problème de synchronisation des données', TRUE, NOW() - INTERVAL 4 DAY),
('Simon', 'Julie', 'julie.simon@example.com', 'julie.s@example.com', 'Question sur la facturation de mon abonnement', FALSE, NOW() - INTERVAL 2 HOUR);
-- Afficher un résumé
SELECT 'Messages créés:' AS info, COUNT(*) AS count FROM messages
UNION ALL
SELECT 'Support créés:', COUNT(*) FROM support_requests;