Files
app_fideliter/lib/screens/clients_screen.dart
T
Mathew 1d3ee69c6d Initial commit — app fidélité magasin (tablette)
App Flutter de programme de fidélité : login staff, clients (nom/prénom),
scan QR, factures (€→points), récompenses, réglages. Backend Supabase
(schéma SQL + RLS anti-triche dans supabase/). Icône incluse.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 21:37:06 +02:00

191 lines
6.1 KiB
Dart

import 'package:flutter/material.dart';
import '../models/client.dart';
import '../services/client_repository.dart';
import '../theme.dart';
import '../widgets/pastille_points.dart';
import 'client_detail_screen.dart';
import 'client_edit_screen.dart';
/// Onglet 1 : liste de tous les clients, avec recherche et ajout.
class ClientsScreen extends StatefulWidget {
final VoidCallback onAllerScanner;
const ClientsScreen({super.key, required this.onAllerScanner});
@override
State<ClientsScreen> createState() => _ClientsScreenState();
}
class _ClientsScreenState extends State<ClientsScreen> {
final _repo = ClientRepository.instance;
final _rechercheCtrl = TextEditingController();
String _requete = '';
@override
void dispose() {
_rechercheCtrl.dispose();
super.dispose();
}
Future<void> _ajouter() async {
final client = await Navigator.of(context).push<Client>(
MaterialPageRoute(builder: (_) => const ClientEditScreen()),
);
if (client != null && mounted) {
// On ouvre directement la fiche du nouveau client.
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => ClientDetailScreen(clientId: client.id)),
);
}
}
void _ouvrir(Client c) {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => ClientDetailScreen(clientId: c.id)),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Clients')),
floatingActionButton: FloatingActionButton.extended(
onPressed: _ajouter,
icon: const Icon(Icons.person_add_alt_1),
label: const Text('Nouveau client'),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 4, 16, 12),
child: TextField(
controller: _rechercheCtrl,
onChanged: (v) => setState(() => _requete = v),
textInputAction: TextInputAction.search,
decoration: InputDecoration(
hintText: 'Rechercher (nom, téléphone, code)',
prefixIcon: const Icon(Icons.search),
suffixIcon: _requete.isEmpty
? null
: IconButton(
icon: const Icon(Icons.close),
onPressed: () {
_rechercheCtrl.clear();
setState(() => _requete = '');
},
),
),
),
),
Expanded(
child: AnimatedBuilder(
animation: _repo,
builder: (context, _) {
final liste = _repo.rechercher(_requete);
if (_repo.clients.isEmpty) return _vide();
if (liste.isEmpty) {
return const Center(
child: Text('Aucun client ne correspond.',
style: TextStyle(color: AppTheme.grisTexte)),
);
}
return ListView.separated(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 96),
itemCount: liste.length,
separatorBuilder: (_, _) => const SizedBox(height: 10),
itemBuilder: (context, i) => _carte(liste[i]),
);
},
),
),
],
),
);
}
Widget _carte(Client c) {
return Card(
child: InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () => _ouvrir(c),
child: Padding(
padding: const EdgeInsets.all(14),
child: Row(
children: [
CircleAvatar(
radius: 24,
backgroundColor: AppTheme.accent.withValues(alpha: 0.14),
child: Text(
c.initiales,
style: const TextStyle(
color: AppTheme.accent, fontWeight: FontWeight.w700),
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
c.nomComplet,
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.w700),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
c.telephone?.isNotEmpty == true ? c.telephone! : c.code,
style: const TextStyle(
fontSize: 13, color: AppTheme.grisTexte),
),
],
),
),
const SizedBox(width: 8),
PastillePoints(c.points),
],
),
),
),
);
}
Widget _vide() {
return Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.people_outline, size: 64, color: AppTheme.grisTexte),
const SizedBox(height: 16),
const Text(
'Aucun client pour l\'instant',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w700),
),
const SizedBox(height: 8),
const Text(
'Créez une fiche client : un QR code de fidélité sera généré automatiquement.',
textAlign: TextAlign.center,
style: TextStyle(color: AppTheme.grisTexte),
),
const SizedBox(height: 24),
FilledButton.icon(
onPressed: _ajouter,
icon: const Icon(Icons.person_add_alt_1),
label: const Text('Créer le premier client'),
),
const SizedBox(height: 12),
TextButton.icon(
onPressed: widget.onAllerScanner,
icon: const Icon(Icons.qr_code_scanner),
label: const Text('Ou scanner un QR client'),
),
],
),
),
);
}
}