feat(frontend): add emprunt confirmation screen (completes borrow flow)
This commit is contained in:
@@ -4,6 +4,7 @@ import "../theme/ensup_colors.dart";
|
|||||||
import "../models/materiel_item.dart";
|
import "../models/materiel_item.dart";
|
||||||
import "../widgets/ensup_top_bar.dart";
|
import "../widgets/ensup_top_bar.dart";
|
||||||
import "../widgets/brand_corners.dart";
|
import "../widgets/brand_corners.dart";
|
||||||
|
import "confirmation_screen.dart";
|
||||||
|
|
||||||
enum _Etat { present, absent, deteriore }
|
enum _Etat { present, absent, deteriore }
|
||||||
|
|
||||||
@@ -25,8 +26,20 @@ class _ChecklistDepartScreenState extends State<ChecklistDepartScreen> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
void _valider() {
|
void _valider() {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
final anomalies = <String>[];
|
||||||
const SnackBar(content: Text("Confirmation de l'emprunt — écran à venir")),
|
for (var i = 0; i < _etats.length; i++) {
|
||||||
|
if (_etats[i] == _Etat.absent) {
|
||||||
|
anomalies.add("${widget.item.accessoires[i]} absent au départ");
|
||||||
|
} else if (_etats[i] == _Etat.deteriore) {
|
||||||
|
anomalies.add("${widget.item.accessoires[i]} détérioré au départ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final note = anomalies.isEmpty ? null : anomalies.join(", ");
|
||||||
|
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => ConfirmationScreen(item: widget.item, note: note),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,217 @@
|
|||||||
|
import "package:flutter/material.dart";
|
||||||
|
import "package:google_fonts/google_fonts.dart";
|
||||||
|
import "../theme/ensup_colors.dart";
|
||||||
|
import "../models/materiel_item.dart";
|
||||||
|
import "../widgets/ensup_top_bar.dart";
|
||||||
|
import "../widgets/brand_corners.dart";
|
||||||
|
|
||||||
|
/// Écran de succès clôturant l'emprunt : confirmation + récapitulatif.
|
||||||
|
class ConfirmationScreen extends StatelessWidget {
|
||||||
|
const ConfirmationScreen({super.key, required this.item, this.note});
|
||||||
|
|
||||||
|
final MaterielItem item;
|
||||||
|
final String? note;
|
||||||
|
|
||||||
|
static String _deuxChiffres(int valeur) => valeur.toString().padLeft(2, "0");
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final date = "${_deuxChiffres(now.day)}/${_deuxChiffres(now.month)}/${now.year}";
|
||||||
|
final heure = "${_deuxChiffres(now.hour)}:${_deuxChiffres(now.minute)}";
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: EnsupColors.soft,
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 1280, maxHeight: 820),
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.all(24),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: EnsupColors.text.withValues(alpha: 0.14),
|
||||||
|
blurRadius: 60,
|
||||||
|
offset: const Offset(0, 18),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const EnsupTopBar(
|
||||||
|
brandText: "EME · Confirmation",
|
||||||
|
campusTag: "Paris · Ensitech",
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
colors: [Color(0xFFF0FDF4), Color(0xFFF8FAFC)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 32),
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 560),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_boiteSucces(),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_recapitulatif(date, heure),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: FilledButton.icon(
|
||||||
|
onPressed: () => Navigator.of(context).popUntil(
|
||||||
|
(route) => route.settings.name == "home",
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.home_outlined, size: 18),
|
||||||
|
label: Text(
|
||||||
|
"Retour à l'accueil",
|
||||||
|
style: GoogleFonts.darkerGrotesque(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
backgroundColor: EnsupColors.cyan,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Positioned.fill(child: BrandCorners()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _boiteSucces() {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(36),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: const LinearGradient(colors: [Color(0xFFF0FDF4), Color(0xFFECFDF5)]),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(color: const Color(0xFFBBF7D0), width: 2),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 72,
|
||||||
|
height: 72,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
gradient: const LinearGradient(colors: [Color(0xFF16A34A), Color(0xFF22C55E)]),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: const Color(0xFF16A34A).withValues(alpha: 0.3),
|
||||||
|
blurRadius: 20,
|
||||||
|
offset: const Offset(0, 6),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.check, color: Colors.white, size: 36),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 18),
|
||||||
|
Text(
|
||||||
|
"Emprunt enregistré !",
|
||||||
|
style: GoogleFonts.darkerGrotesque(
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: FontWeight.w900,
|
||||||
|
color: const Color(0xFF15803D),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
"Votre emprunt a bien été enregistré. Vous pouvez récupérer le matériel.",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: GoogleFonts.titilliumWeb(fontSize: 14, color: const Color(0xFF166534)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _recapitulatif(String date, String heure) {
|
||||||
|
final note = this.note;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(18),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
border: Border.all(color: EnsupColors.line, width: 1.5),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"RÉCAPITULATIF DE L'EMPRUNT",
|
||||||
|
style: GoogleFonts.darkerGrotesque(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: EnsupColors.muted,
|
||||||
|
letterSpacing: 0.6,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
_rrow("Matériel", item.nom),
|
||||||
|
_rrow("Référence", item.reference),
|
||||||
|
_rrow("Emprunteur", "Lucas Martin"),
|
||||||
|
_rrow("Date d'emprunt", date),
|
||||||
|
_rrow("Heure", heure),
|
||||||
|
_rrow("Campus", item.campus),
|
||||||
|
if (note != null) _rrow("Note", note, valueColor: const Color(0xFFD97706)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _rrow(String cle, String valeur, {Color? valueColor}) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 7),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(cle, style: GoogleFonts.titilliumWeb(fontSize: 13, color: EnsupColors.muted)),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
valeur,
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style: GoogleFonts.titilliumWeb(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: valueColor ?? EnsupColors.text,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,10 @@ class LoginScreen extends StatelessWidget {
|
|||||||
|
|
||||||
void _identifier(BuildContext context) {
|
void _identifier(BuildContext context) {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(builder: (_) => const HomeScreen()),
|
MaterialPageRoute(
|
||||||
|
builder: (_) => const HomeScreen(),
|
||||||
|
settings: const RouteSettings(name: "home"),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -207,4 +207,4 @@ En terminal interactif (VS Code), `migrate dev` fonctionne normalement (taper `y
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Dernière mise à jour : 2026-07-06 — Frontend : parcours emprunt jusqu'à la checklist de départ.*
|
*Dernière mise à jour : 2026-07-06 — Frontend : parcours emprunt complet (jusqu'à la confirmation).*
|
||||||
|
|||||||
Reference in New Issue
Block a user