Files
car_rental_app/lib/features/dashboard/screens/reservations_screen.dart
T
2026-05-25 05:10:43 -04:00

156 lines
4.5 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/reservation_card.dart';
import '../../../widgets/loading_list.dart';
import '../../../widgets/error_view.dart';
import 'dashboard_shell.dart';
import 'new_reservation_screen.dart';
class ReservationsScreen extends ConsumerStatefulWidget {
const ReservationsScreen({super.key});
@override
ConsumerState<ReservationsScreen> createState() => _ReservationsScreenState();
}
class _ReservationsScreenState extends ConsumerState<ReservationsScreen>
with SingleTickerProviderStateMixin {
late final TabController _tabs;
final _statuses = [
null,
'DRAFT',
'CONFIRMED',
'ACTIVE',
'COMPLETED',
'CLOSED',
'CANCELLED',
];
final _labels = [
'All',
'Pending',
'Confirmed',
'Active',
'Completed',
'Closed',
'Cancelled',
];
final _searchController = TextEditingController();
String _search = '';
@override
void initState() {
super.initState();
_tabs = TabController(length: _statuses.length, vsync: this);
}
@override
void dispose() {
_tabs.dispose();
_searchController.dispose();
super.dispose();
}
Map<String, dynamic> _params(int tabIndex) => {
'page': 1,
'pageSize': 50,
if (_statuses[tabIndex] != null) 'status': _statuses[tabIndex],
if (_search.isNotEmpty) 'search': _search,
};
@override
Widget build(BuildContext context) {
return DashboardShell(
floatingActionButton: FloatingActionButton(
onPressed: () async {
final result = await Navigator.of(context).push<bool>(
MaterialPageRoute(builder: (_) => const NewReservationScreen()),
);
if (result == true) {
ref.invalidate(reservationsProvider);
setState(() {});
}
},
child: const Icon(Icons.add),
),
appBar: AppBar(
title: const Text('Reservations'),
bottom: TabBar(
controller: _tabs,
isScrollable: true,
tabs: _labels.map((l) => Tab(text: l)).toList(),
onTap: (_) => setState(() {}),
),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 0),
child: TextField(
controller: _searchController,
decoration: const InputDecoration(
hintText: 'Search reservations...',
prefixIcon: Icon(Icons.search, size: 20),
contentPadding: EdgeInsets.symmetric(vertical: 8),
),
onChanged: (v) => setState(() => _search = v),
),
),
Expanded(
child: TabBarView(
controller: _tabs,
children: List.generate(
_statuses.length,
(i) => _ReservationTab(
params: _params(i),
onTap: (id) => context.push('/dashboard/reservations/$id'),
),
),
),
),
],
),
);
}
}
class _ReservationTab extends ConsumerWidget {
final Map<String, dynamic> params;
final void Function(String id) onTap;
const _ReservationTab({required this.params, required this.onTap});
@override
Widget build(BuildContext context, WidgetRef ref) {
final async = ref.watch(reservationsProvider(params));
return async.when(
loading: () => const LoadingList(itemHeight: 110),
error: (e, _) => ErrorView(
message: 'Failed to load reservations',
onRetry: () => ref.invalidate(reservationsProvider(params)),
),
data: (res) => res.data.isEmpty
? const Center(
child: Text(
'No reservations found',
style: TextStyle(color: Color(0xFF9CA3AF)),
),
)
: RefreshIndicator(
onRefresh: () async =>
ref.invalidate(reservationsProvider(params)),
child: ListView.separated(
padding: const EdgeInsets.all(16),
itemCount: res.data.length,
separatorBuilder: (_, _) => const SizedBox(height: 10),
itemBuilder: (_, i) => ReservationCard(
reservation: res.data[i],
onTap: () => onTap(res.data[i].id),
),
),
),
);
}
}