Files
2026-05-27 03:11:45 -04:00

159 lines
6.1 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../providers/dashboard_providers.dart';
import '../../../widgets/loading_list.dart';
import '../../../widgets/error_view.dart';
import '../../../widgets/status_badge.dart';
import 'dashboard_shell.dart';
import 'customer_form_screen.dart';
class CustomersScreen extends ConsumerStatefulWidget {
const CustomersScreen({super.key});
@override
ConsumerState<CustomersScreen> createState() => _CustomersScreenState();
}
class _CustomersScreenState extends ConsumerState<CustomersScreen> {
final _searchController = TextEditingController();
String _search = '';
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
CustomerQuery get _params => (
page: 1,
pageSize: 50,
search: _search.isNotEmpty ? _search : null,
);
@override
Widget build(BuildContext context) {
final customersAsync = ref.watch(customersProvider(_params));
return DashboardShell(
floatingActionButton: FloatingActionButton(
onPressed: () async {
final result = await Navigator.of(context).push<bool>(
MaterialPageRoute(builder: (_) => const CustomerFormScreen()),
);
if (result == true) ref.invalidate(customersProvider(_params));
},
child: const Icon(Icons.person_add_outlined),
),
appBar: AppBar(
title: const Text('Customers'),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(60),
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: TextField(
controller: _searchController,
decoration: const InputDecoration(
hintText: 'Search customers...',
prefixIcon: Icon(Icons.search, size: 20),
contentPadding: EdgeInsets.symmetric(vertical: 8),
),
onChanged: (v) => setState(() => _search = v),
),
),
),
),
body: customersAsync.when(
loading: () => const LoadingList(itemHeight: 80),
error: (e, _) => ErrorView(
message: 'Failed to load customers',
onRetry: () => ref.invalidate(customersProvider(_params)),
),
data: (res) => res.data.isEmpty
? const Center(
child: Text(
'No customers found',
style: TextStyle(color: Color(0xFF9CA3AF)),
),
)
: RefreshIndicator(
onRefresh: () async =>
ref.invalidate(customersProvider(_params)),
child: ListView.separated(
padding: const EdgeInsets.all(16),
itemCount: res.data.length,
separatorBuilder: (_, _) => const SizedBox(height: 10),
itemBuilder: (_, i) {
final c = res.data[i];
return Card(
child: InkWell(
onTap: () =>
context.push('/dashboard/customers/${c.id}'),
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(14),
child: Row(
children: [
CircleAvatar(
backgroundColor: c.isFlagged
? const Color(0xFFFDE8E8)
: const Color(0xFFE1EFFE),
radius: 22,
child: Text(
c.firstName.substring(0, 1).toUpperCase(),
style: TextStyle(
color: c.isFlagged
? const Color(0xFFE02424)
: const Color(0xFF1A56DB),
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
c.fullName,
style: const TextStyle(
fontWeight: FontWeight.w600,
),
),
if (c.isFlagged) ...[
const SizedBox(width: 6),
const Icon(
Icons.flag,
size: 14,
color: Color(0xFFE02424),
),
],
],
),
if (c.email != null)
Text(
c.email!,
style: const TextStyle(
fontSize: 12,
color: Color(0xFF6B7280),
),
),
],
),
),
StatusBadge(status: c.licenseStatus),
],
),
),
),
);
},
),
),
),
);
}
}