Integration fonctionnalités V1 ( resas + gestion machines

This commit is contained in:
bastien
2026-07-03 19:02:48 +02:00
parent 20f383dd62
commit bf191d6396
46 changed files with 4691 additions and 654 deletions
+154 -82
View File
@@ -2,104 +2,176 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../../core/auth/auth_provider.dart';
import '../../../core/router/app_router.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/widgets/empty_state.dart';
import '../../../core/widgets/machine_widgets.dart';
import '../../establishments/data/establishment_repository.dart';
import '../../establishments/domain/establishment.dart';
import '../../wash/data/wash_repository.dart';
/// Écran d'accueil — liste des laveries à proximité.
/// Tableau de bord — vue d'ensemble inspirée WashOnline, plus moderne.
class HomeScreen extends ConsumerWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final establishmentsAsync = ref.watch(establishmentsProvider);
final washesAsync = ref.watch(washesProvider);
final user = ref.watch(authProvider).user;
return Scaffold(
appBar: AppBar(
title: const Text('Laveries'),
actions: [
IconButton(
icon: const Icon(Icons.account_balance_wallet_outlined),
onPressed: () => context.push(AppRoutes.wallet),
),
IconButton(
icon: const Icon(Icons.person_outline),
onPressed: () => context.push(AppRoutes.profile),
),
],
return establishmentsAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, _) => EmptyState(
icon: Icons.cloud_off_outlined,
title: 'Connexion impossible',
subtitle: 'Vérifiez que l\'API est démarrée\net que vous êtes sur le même réseau.',
actionLabel: 'Réessayer',
onAction: () => ref.invalidate(establishmentsProvider),
),
body: establishmentsAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, _) => Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.cloud_off, size: 48),
const SizedBox(height: 12),
Text(
'Impossible de charger les laveries.\nVérifiez que l\'API est démarrée.',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyLarge,
),
data: (establishments) {
if (establishments.isEmpty) {
return const EmptyState(
icon: Icons.storefront_outlined,
title: 'Aucune laverie',
subtitle: 'Aucun établissement disponible pour le moment.',
);
}
final activeWash = washesAsync.maybeWhen(
data: (washes) {
for (final wash in washes) {
if (wash.status == 'running' || wash.status == 'pending_start' || wash.status == 'active') {
return wash;
}
}
return null;
},
orElse: () => null,
);
return RefreshIndicator(
onRefresh: () async {
ref.invalidate(establishmentsProvider);
ref.invalidate(washesProvider);
},
child: ListView(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 24),
children: [
Text(
user?.firstName != null ? 'Bonjour ${user!.firstName} 👋' : 'Bienvenue',
style: Theme.of(context).textTheme.headlineMedium?.copyWith(fontSize: 20),
),
const SizedBox(height: 4),
Text(
'Votre laverie, dans votre poche.',
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 16),
const QuickActionsRow(),
if (activeWash != null) ...[
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => ref.invalidate(establishmentsProvider),
child: const Text('Réessayer'),
ActiveWashBanner(
machineName: activeWash.machineName,
onTap: () => context.go(AppRoutes.washes),
),
],
),
),
),
data: (establishments) {
if (establishments.isEmpty) {
return const Center(child: Text('Aucune laverie disponible'));
}
return RefreshIndicator(
onRefresh: () async => ref.invalidate(establishmentsProvider),
child: ListView.separated(
padding: const EdgeInsets.all(16),
itemCount: establishments.length,
separatorBuilder: (_, __) => const SizedBox(height: 8),
itemBuilder: (context, index) {
final establishment = establishments[index];
return Card(
child: ListTile(
leading: CircleAvatar(
child: Text(establishment.name.substring(0, 1)),
),
title: Text(establishment.name),
subtitle: Text(establishment.fullAddress),
trailing: const Icon(Icons.chevron_right),
const SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Mes laveries', style: Theme.of(context).textTheme.titleMedium),
Text(
'${establishments.length}',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: AppColors.primary,
fontWeight: FontWeight.w600,
),
),
],
),
const SizedBox(height: 10),
...establishments.map(
(establishment) => Padding(
padding: const EdgeInsets.only(bottom: 10),
child: _EstablishmentCard(
establishment: establishment,
onTap: () => context.push('/establishments/${establishment.uuid}'),
),
);
},
),
);
},
),
bottomNavigationBar: NavigationBar(
selectedIndex: 0,
onDestinationSelected: (index) {
switch (index) {
case 0:
context.go(AppRoutes.home);
case 1:
context.push(AppRoutes.bookings);
case 2:
context.push(AppRoutes.washes);
case 3:
context.push(AppRoutes.wallet);
}
},
destinations: const [
NavigationDestination(icon: Icon(Icons.store), label: 'Laveries'),
NavigationDestination(icon: Icon(Icons.event), label: 'Réservations'),
NavigationDestination(icon: Icon(Icons.local_laundry_service), label: 'Lavages'),
NavigationDestination(icon: Icon(Icons.wallet), label: 'Wallet'),
],
),
),
],
),
);
},
);
}
}
class _EstablishmentCard extends StatelessWidget {
const _EstablishmentCard({
required this.establishment,
required this.onTap,
});
final Establishment establishment;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return Card(
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
gradient: AppColors.gradientPrimary,
borderRadius: BorderRadius.circular(12),
),
child: const Icon(Icons.storefront_rounded, color: Colors.white),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(establishment.name, style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 2),
Text(
establishment.fullAddress,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 8),
Row(
children: [
Icon(Icons.grid_view_rounded, size: 14, color: AppColors.primary.withValues(alpha: 0.8)),
const SizedBox(width: 4),
Text(
'Voir les machines',
style: TextStyle(
color: AppColors.primary,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
],
),
],
),
),
const Icon(Icons.chevron_right, color: AppColors.textSecondary),
],
),
),
),
);
}