feat(auth): restore authenticated user profile
This commit is contained in:
@@ -1,10 +1,18 @@
|
|||||||
/// Identité simulée utilisée tant que l'authentification Azure AD n'est pas branchée.
|
import "services/auth_service.dart";
|
||||||
|
|
||||||
|
/// Identité de secours utilisée lorsque aucun profil de session n'est chargé.
|
||||||
class DemoIdentity {
|
class DemoIdentity {
|
||||||
DemoIdentity._();
|
DemoIdentity._();
|
||||||
|
|
||||||
static const userName = "Lucas Martin";
|
static String get userName =>
|
||||||
static const userInitials = "LM";
|
AuthService.currentProfile?.fullName ?? "Lucas Martin";
|
||||||
static const userSubtitle = "Étudiant · B2 Informatique · Ensitech Cergy";
|
static String get userInitials =>
|
||||||
static const campusTag = "Saint-Christophe · Cergy";
|
AuthService.currentProfile?.initials ?? "LM";
|
||||||
|
static String get userSubtitle =>
|
||||||
|
AuthService.currentProfile?.subtitle ??
|
||||||
|
"Étudiant · B2 Informatique · Ensitech Cergy";
|
||||||
|
static String get campusTag =>
|
||||||
|
AuthService.currentProfile?.campusTag ?? "Saint-Christophe · Cergy";
|
||||||
|
|
||||||
static const loginCampusTag = "Ensitech · Cergy";
|
static const loginCampusTag = "Ensitech · Cergy";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
class AuthenticatedProfile {
|
||||||
|
const AuthenticatedProfile({
|
||||||
|
required this.id,
|
||||||
|
required this.nom,
|
||||||
|
required this.prenom,
|
||||||
|
required this.email,
|
||||||
|
required this.classe,
|
||||||
|
required this.roleCode,
|
||||||
|
required this.roleLabel,
|
||||||
|
required this.campusId,
|
||||||
|
required this.campusNom,
|
||||||
|
required this.campusVille,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
final String nom;
|
||||||
|
final String prenom;
|
||||||
|
final String email;
|
||||||
|
final String? classe;
|
||||||
|
final String roleCode;
|
||||||
|
final String roleLabel;
|
||||||
|
final int campusId;
|
||||||
|
final String campusNom;
|
||||||
|
final String campusVille;
|
||||||
|
|
||||||
|
factory AuthenticatedProfile.fromApi(dynamic reponse) {
|
||||||
|
if (reponse is! Map || reponse["data"] is! Map) {
|
||||||
|
throw const FormatException("Profil utilisateur invalide.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final data = reponse["data"] as Map;
|
||||||
|
final role = data["role"];
|
||||||
|
final campus = data["campus"];
|
||||||
|
if (role is! Map || campus is! Map) {
|
||||||
|
throw const FormatException("Role ou campus utilisateur invalide.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final id = data["id"];
|
||||||
|
final campusId = campus["id"];
|
||||||
|
if (id is! int || campusId is! int) {
|
||||||
|
throw const FormatException("Identifiant utilisateur invalide.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final classe = data["classe"];
|
||||||
|
if (classe != null && classe is! String) {
|
||||||
|
throw const FormatException("Classe utilisateur invalide.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return AuthenticatedProfile(
|
||||||
|
id: id,
|
||||||
|
nom: _requiredString(data, "nom"),
|
||||||
|
prenom: _requiredString(data, "prenom"),
|
||||||
|
email: _requiredString(data, "email"),
|
||||||
|
classe: classe as String?,
|
||||||
|
roleCode: _requiredString(role, "code"),
|
||||||
|
roleLabel: _requiredString(role, "libelle"),
|
||||||
|
campusId: campusId,
|
||||||
|
campusNom: _requiredString(campus, "nom"),
|
||||||
|
campusVille: _requiredString(campus, "ville"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String get fullName => "$prenom $nom";
|
||||||
|
|
||||||
|
String get initials {
|
||||||
|
final first = prenom.trim().isEmpty ? "" : prenom.trim()[0];
|
||||||
|
final last = nom.trim().isEmpty ? "" : nom.trim()[0];
|
||||||
|
return "$first$last".toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
String get campusTag => "$campusNom · $campusVille";
|
||||||
|
|
||||||
|
String get subtitle {
|
||||||
|
final details = <String>[roleLabel];
|
||||||
|
final studentClass = classe?.trim();
|
||||||
|
if (studentClass != null && studentClass.isNotEmpty) {
|
||||||
|
details.add(studentClass);
|
||||||
|
}
|
||||||
|
details.add(campusTag);
|
||||||
|
return details.join(" · ");
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _requiredString(Map source, String key) {
|
||||||
|
final value = source[key];
|
||||||
|
if (value is! String || value.trim().isEmpty) {
|
||||||
|
throw FormatException("Champ utilisateur invalide : $key.");
|
||||||
|
}
|
||||||
|
return value.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,8 @@ class ConfirmationScreen extends StatelessWidget {
|
|||||||
final dateEmprunt = emprunt.dateEmprunt;
|
final dateEmprunt = emprunt.dateEmprunt;
|
||||||
final date =
|
final date =
|
||||||
"${_deuxChiffres(dateEmprunt.day)}/${_deuxChiffres(dateEmprunt.month)}/${dateEmprunt.year}";
|
"${_deuxChiffres(dateEmprunt.day)}/${_deuxChiffres(dateEmprunt.month)}/${dateEmprunt.year}";
|
||||||
final heure = "${_deuxChiffres(dateEmprunt.hour)}:${_deuxChiffres(dateEmprunt.minute)}";
|
final heure =
|
||||||
|
"${_deuxChiffres(dateEmprunt.hour)}:${_deuxChiffres(dateEmprunt.minute)}";
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: EnsupColors.soft,
|
backgroundColor: EnsupColors.soft,
|
||||||
@@ -48,7 +49,7 @@ class ConfirmationScreen extends StatelessWidget {
|
|||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const EnsupTopBar(
|
EnsupTopBar(
|
||||||
brandText: "EME · Confirmation",
|
brandText: "EME · Confirmation",
|
||||||
campusTag: DemoIdentity.campusTag,
|
campusTag: DemoIdentity.campusTag,
|
||||||
),
|
),
|
||||||
@@ -63,7 +64,10 @@ class ConfirmationScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 32),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 28,
|
||||||
|
vertical: 32,
|
||||||
|
),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 560),
|
constraints: const BoxConstraints(maxWidth: 560),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -75,10 +79,15 @@ class ConfirmationScreen extends StatelessWidget {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: FilledButton.icon(
|
child: FilledButton.icon(
|
||||||
onPressed: () => Navigator.of(context).popUntil(
|
onPressed: () =>
|
||||||
(route) => route.settings.name == "home",
|
Navigator.of(context).popUntil(
|
||||||
|
(route) =>
|
||||||
|
route.settings.name == "home",
|
||||||
|
),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.home_outlined,
|
||||||
|
size: 18,
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.home_outlined, size: 18),
|
|
||||||
label: Text(
|
label: Text(
|
||||||
"Retour à l'accueil",
|
"Retour à l'accueil",
|
||||||
style: GoogleFonts.darkerGrotesque(
|
style: GoogleFonts.darkerGrotesque(
|
||||||
@@ -88,9 +97,13 @@ class ConfirmationScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
backgroundColor: EnsupColors.cyan,
|
backgroundColor: EnsupColors.cyan,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 16,
|
||||||
|
),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(
|
||||||
|
10,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -118,7 +131,9 @@ class ConfirmationScreen extends StatelessWidget {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(36),
|
padding: const EdgeInsets.all(36),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: const LinearGradient(colors: [Color(0xFFF0FDF4), Color(0xFFECFDF5)]),
|
gradient: const LinearGradient(
|
||||||
|
colors: [Color(0xFFF0FDF4), Color(0xFFECFDF5)],
|
||||||
|
),
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
border: Border.all(color: const Color(0xFFBBF7D0), width: 2),
|
border: Border.all(color: const Color(0xFFBBF7D0), width: 2),
|
||||||
),
|
),
|
||||||
@@ -129,7 +144,9 @@ class ConfirmationScreen extends StatelessWidget {
|
|||||||
height: 72,
|
height: 72,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
gradient: const LinearGradient(colors: [Color(0xFF16A34A), Color(0xFF22C55E)]),
|
gradient: const LinearGradient(
|
||||||
|
colors: [Color(0xFF16A34A), Color(0xFF22C55E)],
|
||||||
|
),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: const Color(0xFF16A34A).withValues(alpha: 0.3),
|
color: const Color(0xFF16A34A).withValues(alpha: 0.3),
|
||||||
@@ -153,7 +170,10 @@ class ConfirmationScreen extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
"Votre emprunt a bien été enregistré. Vous pouvez récupérer le matériel.",
|
"Votre emprunt a bien été enregistré. Vous pouvez récupérer le matériel.",
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: GoogleFonts.titilliumWeb(fontSize: 14, color: const Color(0xFF166534)),
|
style: GoogleFonts.titilliumWeb(
|
||||||
|
fontSize: 14,
|
||||||
|
color: const Color(0xFF166534),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -191,7 +211,8 @@ class ConfirmationScreen extends StatelessWidget {
|
|||||||
_rrow("Date d'emprunt", date),
|
_rrow("Date d'emprunt", date),
|
||||||
_rrow("Heure", heure),
|
_rrow("Heure", heure),
|
||||||
_rrow("Campus", item.campus),
|
_rrow("Campus", item.campus),
|
||||||
if (note != null) _rrow("Note", note, valueColor: const Color(0xFFD97706)),
|
if (note != null)
|
||||||
|
_rrow("Note", note, valueColor: const Color(0xFFD97706)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -203,7 +224,13 @@ class ConfirmationScreen extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(cle, style: GoogleFonts.titilliumWeb(fontSize: 13, color: EnsupColors.muted)),
|
Text(
|
||||||
|
cle,
|
||||||
|
style: GoogleFonts.titilliumWeb(
|
||||||
|
fontSize: 13,
|
||||||
|
color: EnsupColors.muted,
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class HomeScreen extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const ProfileCard(
|
ProfileCard(
|
||||||
name: DemoIdentity.userName,
|
name: DemoIdentity.userName,
|
||||||
subtitle: DemoIdentity.userSubtitle,
|
subtitle: DemoIdentity.userSubtitle,
|
||||||
initials: DemoIdentity.userInitials,
|
initials: DemoIdentity.userInitials,
|
||||||
|
|||||||
@@ -21,6 +21,14 @@ class LoginScreen extends StatefulWidget {
|
|||||||
class _LoginScreenState extends State<LoginScreen> {
|
class _LoginScreenState extends State<LoginScreen> {
|
||||||
bool _connexionEnCours = false;
|
bool _connexionEnCours = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (AuthService.mode == AuthenticationMode.azure) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => _restaurerSession());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _ouvrirEtudiant() {
|
void _ouvrirEtudiant() {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
@@ -39,7 +47,44 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _identifierParQr() {
|
void _ouvrirPourRole(String roleCode) {
|
||||||
|
switch (roleCode) {
|
||||||
|
case "ETUDIANT":
|
||||||
|
_ouvrirEtudiant();
|
||||||
|
case "RESPONSABLE":
|
||||||
|
_ouvrirResponsable();
|
||||||
|
default:
|
||||||
|
throw StateError("Role utilisateur non autorise.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _restaurerSession() async {
|
||||||
|
setState(() => _connexionEnCours = true);
|
||||||
|
try {
|
||||||
|
if (!await AuthService.hasMicrosoftAccount()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final reponse = await ApiClient.get("/auth/me");
|
||||||
|
final profile = AuthService.setProfileFromApi(reponse);
|
||||||
|
if (mounted) {
|
||||||
|
_ouvrirPourRole(profile.roleCode);
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text("La session Microsoft doit etre renouvelee."),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => _connexionEnCours = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _identifierParQr() async {
|
||||||
if (_connexionEnCours) return;
|
if (_connexionEnCours) return;
|
||||||
if (AuthService.mode == AuthenticationMode.azure) {
|
if (AuthService.mode == AuthenticationMode.azure) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
@@ -52,8 +97,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthService.selectDemoStudent();
|
await _connecterMicrosoft(responsableDemo: false);
|
||||||
_ouvrirEtudiant();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _connecterMicrosoft({required bool responsableDemo}) async {
|
Future<void> _connecterMicrosoft({required bool responsableDemo}) async {
|
||||||
@@ -64,27 +108,17 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
if (AuthService.mode == AuthenticationMode.demo) {
|
if (AuthService.mode == AuthenticationMode.demo) {
|
||||||
if (responsableDemo) {
|
if (responsableDemo) {
|
||||||
AuthService.selectDemoResponsable();
|
AuthService.selectDemoResponsable();
|
||||||
_ouvrirResponsable();
|
|
||||||
} else {
|
} else {
|
||||||
AuthService.selectDemoStudent();
|
AuthService.selectDemoStudent();
|
||||||
_ouvrirEtudiant();
|
|
||||||
}
|
}
|
||||||
return;
|
} else {
|
||||||
|
await AuthService.signInWithMicrosoft();
|
||||||
}
|
}
|
||||||
|
|
||||||
await AuthService.signInWithMicrosoft();
|
|
||||||
final reponse = await ApiClient.get("/auth/me");
|
final reponse = await ApiClient.get("/auth/me");
|
||||||
final roleCode = _roleCode(reponse);
|
final profile = AuthService.setProfileFromApi(reponse);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
_ouvrirPourRole(profile.roleCode);
|
||||||
switch (roleCode) {
|
|
||||||
case "ETUDIANT":
|
|
||||||
_ouvrirEtudiant();
|
|
||||||
case "RESPONSABLE":
|
|
||||||
_ouvrirResponsable();
|
|
||||||
default:
|
|
||||||
throw StateError("Role utilisateur non autorise.");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
@@ -97,25 +131,6 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _roleCode(dynamic reponse) {
|
|
||||||
if (reponse is! Map || reponse["data"] is! Map) {
|
|
||||||
throw StateError("Profil utilisateur invalide.");
|
|
||||||
}
|
|
||||||
|
|
||||||
final data = reponse["data"] as Map;
|
|
||||||
if (data["role"] is! Map) {
|
|
||||||
throw StateError("Role utilisateur absent.");
|
|
||||||
}
|
|
||||||
|
|
||||||
final role = data["role"] as Map;
|
|
||||||
final code = role["code"];
|
|
||||||
if (code is! String) {
|
|
||||||
throw StateError("Role utilisateur invalide.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:google_fonts/google_fonts.dart";
|
import "package:google_fonts/google_fonts.dart";
|
||||||
|
|
||||||
import "../demo_identity.dart";
|
|
||||||
import "../services/auth_service.dart";
|
import "../services/auth_service.dart";
|
||||||
import "../services/responsable_service.dart";
|
import "../services/responsable_service.dart";
|
||||||
import "../theme/ensup_colors.dart";
|
import "../theme/ensup_colors.dart";
|
||||||
@@ -463,6 +462,12 @@ class _ResponsableTopBar extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final profile = AuthService.currentProfile;
|
||||||
|
final campusTag = profile?.campusTag ?? "Ensitech · Cergy";
|
||||||
|
final initials = profile?.initials ?? "KB";
|
||||||
|
final userName = profile?.fullName ?? "Karim Benali";
|
||||||
|
final roleLabel = profile?.roleLabel ?? "Responsable matériel";
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
height: 68,
|
height: 68,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
@@ -533,7 +538,7 @@ class _ResponsableTopBar extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
DemoIdentity.loginCampusTag,
|
campusTag,
|
||||||
style: GoogleFonts.titilliumWeb(
|
style: GoogleFonts.titilliumWeb(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -565,7 +570,7 @@ class _ResponsableTopBar extends StatelessWidget {
|
|||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
"KB",
|
initials,
|
||||||
style: GoogleFonts.darkerGrotesque(
|
style: GoogleFonts.darkerGrotesque(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
@@ -580,7 +585,7 @@ class _ResponsableTopBar extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Karim Benali",
|
userName,
|
||||||
style: GoogleFonts.titilliumWeb(
|
style: GoogleFonts.titilliumWeb(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
@@ -588,7 +593,7 @@ class _ResponsableTopBar extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
"Responsable matériel",
|
roleLabel,
|
||||||
style: GoogleFonts.titilliumWeb(
|
style: GoogleFonts.titilliumWeb(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: EnsupColors.muted,
|
color: EnsupColors.muted,
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ import "../widgets/brand_corners.dart";
|
|||||||
/// Résultat de la restitution (RG17-RG20) : conforme (succès) ou non conforme
|
/// Résultat de la restitution (RG17-RG20) : conforme (succès) ou non conforme
|
||||||
/// (anomalie détectée + notification au responsable).
|
/// (anomalie détectée + notification au responsable).
|
||||||
class ResultatRetourScreen extends StatelessWidget {
|
class ResultatRetourScreen extends StatelessWidget {
|
||||||
const ResultatRetourScreen({super.key, required this.emprunt, required this.anomalies});
|
const ResultatRetourScreen({
|
||||||
|
super.key,
|
||||||
|
required this.emprunt,
|
||||||
|
required this.anomalies,
|
||||||
|
});
|
||||||
|
|
||||||
final EmpruntItem emprunt;
|
final EmpruntItem emprunt;
|
||||||
final List<AnomalieRetour> anomalies;
|
final List<AnomalieRetour> anomalies;
|
||||||
@@ -48,7 +52,7 @@ class ResultatRetourScreen extends StatelessWidget {
|
|||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const EnsupTopBar(
|
EnsupTopBar(
|
||||||
brandText: "EME · Résultat du contrôle",
|
brandText: "EME · Résultat du contrôle",
|
||||||
campusTag: DemoIdentity.campusTag,
|
campusTag: DemoIdentity.campusTag,
|
||||||
),
|
),
|
||||||
@@ -63,7 +67,10 @@ class ResultatRetourScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 32),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 28,
|
||||||
|
vertical: 32,
|
||||||
|
),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 560),
|
constraints: const BoxConstraints(maxWidth: 560),
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -73,10 +80,15 @@ class ResultatRetourScreen extends StatelessWidget {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: FilledButton.icon(
|
child: FilledButton.icon(
|
||||||
onPressed: () => Navigator.of(context).popUntil(
|
onPressed: () =>
|
||||||
(route) => route.settings.name == "home",
|
Navigator.of(context).popUntil(
|
||||||
|
(route) =>
|
||||||
|
route.settings.name == "home",
|
||||||
|
),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.home_outlined,
|
||||||
|
size: 18,
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.home_outlined, size: 18),
|
|
||||||
label: Text(
|
label: Text(
|
||||||
"Retour à l'accueil",
|
"Retour à l'accueil",
|
||||||
style: GoogleFonts.darkerGrotesque(
|
style: GoogleFonts.darkerGrotesque(
|
||||||
@@ -86,9 +98,13 @@ class ResultatRetourScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
style: FilledButton.styleFrom(
|
style: FilledButton.styleFrom(
|
||||||
backgroundColor: EnsupColors.cyan,
|
backgroundColor: EnsupColors.cyan,
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 16,
|
||||||
|
),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(
|
||||||
|
10,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -116,7 +132,9 @@ class ResultatRetourScreen extends StatelessWidget {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.all(36),
|
padding: const EdgeInsets.all(36),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: const LinearGradient(colors: [Color(0xFFF0FDF4), Color(0xFFECFDF5)]),
|
gradient: const LinearGradient(
|
||||||
|
colors: [Color(0xFFF0FDF4), Color(0xFFECFDF5)],
|
||||||
|
),
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
border: Border.all(color: const Color(0xFFBBF7D0), width: 2),
|
border: Border.all(color: const Color(0xFFBBF7D0), width: 2),
|
||||||
),
|
),
|
||||||
@@ -127,7 +145,9 @@ class ResultatRetourScreen extends StatelessWidget {
|
|||||||
height: 72,
|
height: 72,
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
gradient: LinearGradient(colors: [Color(0xFF16A34A), Color(0xFF22C55E)]),
|
gradient: LinearGradient(
|
||||||
|
colors: [Color(0xFF16A34A), Color(0xFF22C55E)],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: const Icon(Icons.check, color: Colors.white, size: 36),
|
child: const Icon(Icons.check, color: Colors.white, size: 36),
|
||||||
),
|
),
|
||||||
@@ -144,7 +164,10 @@ class ResultatRetourScreen extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
"L'emprunt a été clôturé et le matériel est de nouveau disponible.",
|
"L'emprunt a été clôturé et le matériel est de nouveau disponible.",
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: GoogleFonts.titilliumWeb(fontSize: 14, color: const Color(0xFF166534)),
|
style: GoogleFonts.titilliumWeb(
|
||||||
|
fontSize: 14,
|
||||||
|
color: const Color(0xFF166534),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -164,12 +187,19 @@ class ResultatRetourScreen extends StatelessWidget {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: EnsupColors.red.withValues(alpha: 0.06),
|
color: EnsupColors.red.withValues(alpha: 0.06),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(color: EnsupColors.red.withValues(alpha: 0.2), width: 1.5),
|
border: Border.all(
|
||||||
|
color: EnsupColors.red.withValues(alpha: 0.2),
|
||||||
|
width: 1.5,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.warning_amber_rounded, color: EnsupColors.red, size: 24),
|
const Icon(
|
||||||
|
Icons.warning_amber_rounded,
|
||||||
|
color: EnsupColors.red,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -227,7 +257,10 @@ class ResultatRetourScreen extends StatelessWidget {
|
|||||||
anomalie.etatRetour,
|
anomalie.etatRetour,
|
||||||
valeurCouleur: EnsupColors.red,
|
valeurCouleur: EnsupColors.red,
|
||||||
),
|
),
|
||||||
_drow("Matériel", "${emprunt.materiel.nom} · ${emprunt.materiel.reference}"),
|
_drow(
|
||||||
|
"Matériel",
|
||||||
|
"${emprunt.materiel.nom} · ${emprunt.materiel.reference}",
|
||||||
|
),
|
||||||
_drow("Emprunteur", DemoIdentity.userName),
|
_drow("Emprunteur", DemoIdentity.userName),
|
||||||
_drow("Date détection", date),
|
_drow("Date détection", date),
|
||||||
],
|
],
|
||||||
@@ -237,7 +270,11 @@ class ResultatRetourScreen extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
"Le responsable matériel traitera cette anomalie. Vous pouvez retourner à l'accueil.",
|
"Le responsable matériel traitera cette anomalie. Vous pouvez retourner à l'accueil.",
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: GoogleFonts.titilliumWeb(fontSize: 13, color: EnsupColors.muted, height: 1.6),
|
style: GoogleFonts.titilliumWeb(
|
||||||
|
fontSize: 13,
|
||||||
|
color: EnsupColors.muted,
|
||||||
|
height: 1.6,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -249,7 +286,13 @@ class ResultatRetourScreen extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(cle, style: GoogleFonts.titilliumWeb(fontSize: 13, color: EnsupColors.muted)),
|
Text(
|
||||||
|
cle,
|
||||||
|
style: GoogleFonts.titilliumWeb(
|
||||||
|
fontSize: 13,
|
||||||
|
color: EnsupColors.muted,
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import "../models/authenticated_profile.dart";
|
||||||
import "msal_bridge.dart";
|
import "msal_bridge.dart";
|
||||||
|
|
||||||
enum AuthenticationMode { demo, azure }
|
enum AuthenticationMode { demo, azure }
|
||||||
@@ -19,8 +20,11 @@ class AuthService {
|
|||||||
static const String demoResponsableEmail = "karim.benali@ensup.eu";
|
static const String demoResponsableEmail = "karim.benali@ensup.eu";
|
||||||
|
|
||||||
static String? _demoEmail;
|
static String? _demoEmail;
|
||||||
|
static AuthenticatedProfile? _currentProfile;
|
||||||
static bool _azureInitialized = false;
|
static bool _azureInitialized = false;
|
||||||
|
|
||||||
|
static AuthenticatedProfile? get currentProfile => _currentProfile;
|
||||||
|
|
||||||
static AuthenticationMode get mode {
|
static AuthenticationMode get mode {
|
||||||
return switch (_configuredMode) {
|
return switch (_configuredMode) {
|
||||||
"demo" => AuthenticationMode.demo,
|
"demo" => AuthenticationMode.demo,
|
||||||
@@ -41,6 +45,13 @@ class AuthService {
|
|||||||
|
|
||||||
static void clearSession() {
|
static void clearSession() {
|
||||||
_demoEmail = null;
|
_demoEmail = null;
|
||||||
|
_currentProfile = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static AuthenticatedProfile setProfileFromApi(dynamic reponse) {
|
||||||
|
final profile = AuthenticatedProfile.fromApi(reponse);
|
||||||
|
_currentProfile = profile;
|
||||||
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> signInWithMicrosoft() async {
|
static Future<void> signInWithMicrosoft() async {
|
||||||
@@ -57,6 +68,7 @@ class AuthService {
|
|||||||
if (mode == AuthenticationMode.azure) {
|
if (mode == AuthenticationMode.azure) {
|
||||||
await _initializeAzure();
|
await _initializeAzure();
|
||||||
await MsalBridge.signOut();
|
await MsalBridge.signOut();
|
||||||
|
_currentProfile = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user