feat(frontend): add material detail screen

This commit is contained in:
SaidSoighiri94
2026-07-06 12:44:43 +02:00
parent 84bd2cc629
commit b124d7a775
4 changed files with 286 additions and 9 deletions
+10 -2
View File
@@ -1,21 +1,29 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
/// Représentation d'un matériel affiché dans le catalogue (données statiques /// Représentation d'un matériel affiché dans le catalogue et son détail
/// pour l'instant ; sera alimenté par l'API plus tard). /// (données statiques pour l'instant ; sera alimenté par l'API plus tard).
class MaterielItem { class MaterielItem {
const MaterielItem({ const MaterielItem({
required this.nom, required this.nom,
required this.categorie, required this.categorie,
required this.marque,
required this.modele,
required this.reference, required this.reference,
required this.campus, required this.campus,
required this.etatGeneral,
required this.disponible, required this.disponible,
required this.accessoires,
required this.icon, required this.icon,
}); });
final String nom; final String nom;
final String categorie; final String categorie;
final String marque;
final String modele;
final String reference; final String reference;
final String campus; final String campus;
final String etatGeneral;
final bool disponible; final bool disponible;
final List<String> accessoires;
final IconData icon; final IconData icon;
} }
+23 -6
View File
@@ -5,6 +5,7 @@ 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 "../widgets/materiel_card.dart"; import "../widgets/materiel_card.dart";
import "detail_screen.dart";
/// Catalogue matériel : recherche, filtres par catégorie et liste de matériels. /// Catalogue matériel : recherche, filtres par catégorie et liste de matériels.
class CatalogueScreen extends StatefulWidget { class CatalogueScreen extends StatefulWidget {
@@ -18,33 +19,49 @@ const _materiels = <MaterielItem>[
MaterielItem( MaterielItem(
nom: "PC Dell Latitude 5420", nom: "PC Dell Latitude 5420",
categorie: "Ordinateur", categorie: "Ordinateur",
marque: "Dell",
modele: "Latitude 5420",
reference: "ENS-PC-001", reference: "ENS-PC-001",
campus: "Paris", campus: "Paris · Ensitech",
etatGeneral: "Bon état",
disponible: true, disponible: true,
accessoires: ["Chargeur 65W", "Souris sans fil", "Housse de protection", "Câble HDMI"],
icon: Icons.laptop_mac, icon: Icons.laptop_mac,
), ),
MaterielItem( MaterielItem(
nom: "Vidéoprojecteur Epson EB-X51", nom: "Vidéoprojecteur Epson EB-X51",
categorie: "Projection", categorie: "Projection",
marque: "Epson",
modele: "EB-X51",
reference: "ENS-VP-003", reference: "ENS-VP-003",
campus: "Paris", campus: "Paris · Ensitech",
etatGeneral: "Bon état",
disponible: true, disponible: true,
accessoires: ["Télécommande", "Câble HDMI", "Câble VGA"],
icon: Icons.videocam_outlined, icon: Icons.videocam_outlined,
), ),
MaterielItem( MaterielItem(
nom: "Chargeur USB-C 65W", nom: "Chargeur USB-C 65W",
categorie: "Accessoire", categorie: "Accessoire",
marque: "Anker",
modele: "PowerPort III",
reference: "ENS-CH-012", reference: "ENS-CH-012",
campus: "Paris", campus: "Paris · Ensitech",
etatGeneral: "Bon état",
disponible: false, disponible: false,
accessoires: [],
icon: Icons.power_outlined, icon: Icons.power_outlined,
), ),
MaterielItem( MaterielItem(
nom: "PC Lenovo ThinkPad E14", nom: "PC Lenovo ThinkPad E14",
categorie: "Ordinateur", categorie: "Ordinateur",
marque: "Lenovo",
modele: "ThinkPad E14",
reference: "ENS-PC-004", reference: "ENS-PC-004",
campus: "Paris", campus: "Paris · Ensitech",
etatGeneral: "Bon état",
disponible: true, disponible: true,
accessoires: ["Chargeur 65W", "Souris sans fil", "Housse de protection"],
icon: Icons.laptop_mac, icon: Icons.laptop_mac,
), ),
]; ];
@@ -65,8 +82,8 @@ class _CatalogueScreenState extends State<CatalogueScreen> {
} }
void _selectionner(BuildContext context, MaterielItem item) { void _selectionner(BuildContext context, MaterielItem item) {
ScaffoldMessenger.of(context).showSnackBar( Navigator.of(context).push(
SnackBar(content: Text("${item.nom} — détail à venir")), MaterialPageRoute(builder: (_) => DetailScreen(item: item)),
); );
} }
+247
View File
@@ -0,0 +1,247 @@
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";
import "../widgets/status_pill.dart";
/// Détail d'un matériel : caractéristiques, accessoires du kit et lancement de
/// l'emprunt.
class DetailScreen extends StatelessWidget {
const DetailScreen({super.key, required this.item});
final MaterielItem item;
void _demarrer(BuildContext context) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Checklist de départ — écran à venir")),
);
}
@override
Widget build(BuildContext context) {
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: [
EnsupTopBar(
brandText: "Détail matériel",
campusTag: "Paris · Ensitech",
userName: "Lucas Martin",
userInitials: "LM",
onBack: () => Navigator.of(context).pop(),
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 28),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(flex: 3, child: _colonneGauche()),
const SizedBox(width: 20),
Expanded(flex: 2, child: _colonneDroite(context)),
],
),
),
),
],
),
),
),
),
const Positioned.fill(child: BrandCorners()),
],
),
);
}
Widget _colonneGauche() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.all(18),
decoration: BoxDecoration(
color: EnsupColors.soft,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: EnsupColors.line, width: 1.5),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.nom,
style: GoogleFonts.darkerGrotesque(
fontSize: 20,
fontWeight: FontWeight.w900,
color: EnsupColors.text,
),
),
const SizedBox(height: 12),
_row("Référence", item.reference),
_row("Catégorie", item.categorie),
_row("Marque", item.marque),
_row("Modèle", item.modele),
_row("État", item.etatGeneral, valueColor: EnsupColors.green),
_row("Campus", item.campus),
_rowStatut(),
],
),
),
if (item.accessoires.isNotEmpty) ...[
const SizedBox(height: 18),
Text(
"ACCESSOIRES INCLUS DANS LE KIT",
style: GoogleFonts.darkerGrotesque(
fontSize: 13,
fontWeight: FontWeight.w700,
color: EnsupColors.muted,
letterSpacing: 0.8,
),
),
const SizedBox(height: 10),
Wrap(
spacing: 6,
runSpacing: 6,
children: item.accessoires.map(_tag).toList(),
),
],
],
);
}
Widget _row(String cle, String valeur, {Color? valueColor}) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 8),
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: EnsupColors.line)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(cle, style: GoogleFonts.titilliumWeb(fontSize: 13, color: EnsupColors.muted)),
Text(
valeur,
style: GoogleFonts.titilliumWeb(
fontSize: 13,
fontWeight: FontWeight.w600,
color: valueColor ?? EnsupColors.text,
),
),
],
),
);
}
Widget _rowStatut() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Statut", style: GoogleFonts.titilliumWeb(fontSize: 13, color: EnsupColors.muted)),
StatusPill(disponible: item.disponible),
],
),
);
}
Widget _tag(String label) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
decoration: BoxDecoration(
color: EnsupColors.teal.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(6),
),
child: Text(
label,
style: GoogleFonts.darkerGrotesque(
fontSize: 13,
fontWeight: FontWeight.w600,
color: EnsupColors.teal,
),
),
);
}
Widget _colonneDroite(BuildContext context) {
return Column(
children: [
Container(
height: 180,
width: double.infinity,
decoration: BoxDecoration(
color: EnsupColors.soft,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: EnsupColors.line, width: 1.5),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(item.icon, size: 48, color: EnsupColors.muted),
const SizedBox(height: 10),
Text(
"Aperçu du matériel",
style: GoogleFonts.titilliumWeb(fontSize: 13, color: EnsupColors.muted),
),
],
),
),
const SizedBox(height: 14),
SizedBox(
width: double.infinity,
child: FilledButton.icon(
onPressed: item.disponible ? () => _demarrer(context) : null,
icon: const Icon(Icons.arrow_forward, size: 18),
label: Text(
"Démarrer l'emprunt",
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 SizedBox(height: 10),
SizedBox(
width: double.infinity,
child: OutlinedButton(
onPressed: () => Navigator.of(context).pop(),
style: OutlinedButton.styleFrom(
foregroundColor: EnsupColors.text2,
side: const BorderSide(color: EnsupColors.line, width: 1.5),
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
),
child: Text(
"Retour au catalogue",
style: GoogleFonts.darkerGrotesque(fontWeight: FontWeight.w700, fontSize: 15),
),
),
),
],
);
}
}
+6 -1
View File
@@ -187,6 +187,11 @@ En terminal interactif (VS Code), `migrate dev` fonctionne normalement (taper `y
- Nouveaux composants : `MaterielCard`, `StatusPill`, modèle `MaterielItem`. Données statiques (4 matériels de la maquette). - Nouveaux composants : `MaterielCard`, `StatusPill`, modèle `MaterielItem`. Données statiques (4 matériels de la maquette).
- Navigation : Accueil (Emprunter) -> Catalogue ; bouton retour vers l'accueil. « Sélectionner » -> placeholder (détail à venir). - Navigation : Accueil (Emprunter) -> Catalogue ; bouton retour vers l'accueil. « Sélectionner » -> placeholder (détail à venir).
### Étape 25 — Frontend : écran détail matériel
- Écran détail (`s-detail`) : caractéristiques (référence, catégorie, marque, modèle, état, campus, statut) + accessoires du kit (tags) + aperçu + boutons « Démarrer l'emprunt » / « Retour au catalogue ».
- Modèle `MaterielItem` enrichi (marque, modèle, état, accessoires) ; données statiques mises à jour.
- Navigation : Catalogue (Sélectionner) -> Détail ; « Démarrer l'emprunt » -> placeholder (checklist à venir).
--- ---
## Dette technique en attente ## Dette technique en attente
@@ -202,4 +207,4 @@ En terminal interactif (VS Code), `migrate dev` fonctionne normalement (taper `y
--- ---
*Dernière mise à jour : 2026-07-06 — Frontend : identification + accueil + catalogue matériel.* *Dernière mise à jour : 2026-07-06 — Frontend : identification + accueil + catalogue + détail matériel.*