last 16-10-2025

This commit is contained in:
mageshwaran committed 2025-10-16 11:21:52 +05:30
commit 7546b7348c
269 files changed
+36404

No files matched your search

@@ -0,0 +1,932 @@
import 'package:bookmywages/consts_widgets/app_colors.dart';
import 'package:bookmywages/model/booking_modify_model.dart';
import 'package:bookmywages/model/cancel_booking.dart';
import 'package:bookmywages/routers/consts_router.dart';
import 'package:bookmywages/viewmodel/api_controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
class ServiceBooking extends ConsumerStatefulWidget {
const ServiceBooking({super.key});
@override
ConsumerState<ServiceBooking> createState() => _ServiceBookingState();
}
class _ServiceBookingState extends ConsumerState<ServiceBooking> {
@override
Widget build(BuildContext context) {
final bookingAsyncValue = ref.watch(userbookinghistorydetailsProvider);
return Scaffold(
backgroundColor: AppColors.secondprimary,
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: bookingAsyncValue.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, stack) => Center(child: Text('Error: $err')),
data: (bookings) {
return ListView.builder(
itemCount: bookings.length,
shrinkWrap: false,
itemBuilder: (context, index) {
final booking = bookings[index];
return Container(
margin: const EdgeInsets.only(bottom: 10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppColors.lightGrey),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Top Row: ID and "View order"
Padding(
padding: const EdgeInsets.only(
right: 12,
left: 12,
top: 12,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'ID : ${booking.id}',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
// Update the onTap method in the ServiceBooking class
GestureDetector(
onTap: () {
Get.toNamed(
RouterConts.detailserivce,
arguments: booking.serviceId,
);
},
child: Text(
'View order',
style: TextStyle(
color: Colors.blue.shade600,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
const Divider(),
/// Image, Company, and Status
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: (booking.images1.isNotEmpty)
? Image.network(
booking.images1[0],
width: 100,
height: 110,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) {
return child;
}
return Center(
child: CircularProgressIndicator(
value:
loadingProgress
.expectedTotalBytes !=
null
? loadingProgress
.cumulativeBytesLoaded /
(loadingProgress
.expectedTotalBytes ??
1)
: null,
),
);
},
errorBuilder:
(context, error, stackTrace) {
return const Icon(
Icons.error,
size: 100,
);
},
)
: const Icon(
Icons.image_not_supported,
size: 100,
),
),
Positioned(
top: 6,
left: 6,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 6,
height: 6,
decoration: const BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
),
),
const SizedBox(width: 4),
const Text(
'Live',
style: TextStyle(
fontSize: 12,
color: Colors.green,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
],
),
const SizedBox(width: 12),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
booking.vendorName ??
'Arun Cleaning Company',
style: const TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 16.11,
height: 14.5 / 16.11,
letterSpacing: 0.01 * 16.11,
),
),
const SizedBox(height: 4),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
booking.serviceName ??
'Cleaning service',
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w500,
fontSize: 13.91,
height: 1.3,
letterSpacing: 0.01 * 13.91,
color: Color(0xFF5A5A5A),
),
),
),
const SizedBox(width: 10),
// Updated status display code in the ServiceBooking class
Expanded(
child: Container(
width: booking.status == 3
? 70
: booking.status == 1
? 85 // Width for Scheduled
: 77.84, // Width for Pending and other statuses
height: booking.status == 3
? 25
: booking.status == 1
? 25 // Height for Scheduled
: 27.96, // Height for Pending and other statuses
decoration: BoxDecoration(
color: booking.status == 3
? const Color(
0xFFFFEEEE,
) // #FFEEEE for Cancel
: booking.status == 1
? const Color(
0xFFE6F7E6,
) // Light green for Scheduled
: const Color(
0xFFDAE9FF,
), // #DAE9FF for Pending
borderRadius: BorderRadius.circular(
6.05,
), // 6.05px border radius
),
child: Center(
child: Text(
booking.status == 3
? 'Cancel'
: booking.status == 1
? 'Scheduled' // Text for status 1
: 'Pending', // Text for other statuses
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontSize: 10.92,
fontWeight: FontWeight.w400,
color: booking.status == 3
? const Color(
0xFFFF0000,
) // Red color for Cancel
: booking.status == 1
? const Color(
0xFF2E8B57,
) // Green color for Scheduled
: const Color(
0xFF0066FF,
), // Blue color for Pending
letterSpacing: 1.0,
height: 0.98,
),
),
),
),
),
],
),
const SizedBox(height: 6),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
border: Border.all(
color: AppColors.lightGrey,
),
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
CircleAvatar(
radius: 15,
backgroundImage:
booking.profilePic != null &&
booking.profilePic!.isNotEmpty
? NetworkImage(
booking.profilePic.toString(),
)
: null,
child:
booking.profilePic == null ||
booking.profilePic!.isEmpty
? const Icon(
Icons.person,
size: 20,
)
: null,
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
booking.vendorName ??
'no data',
style: const TextStyle(
fontFamily:
'Gilroy-Medium',
fontWeight:
FontWeight.w600,
fontSize: 11,
height: 10.65 / 11.84,
letterSpacing: 0,
color: Color(
0xFF353434,
),
),
overflow:
TextOverflow.visible,
),
),
const Icon(
Icons.star,
color: Colors.orange,
size: 15,
),
const SizedBox(width: 4),
// Fixed: Changed to properly handle isRated as an integer
Text(
'${booking.isRated}',
textAlign: TextAlign.center,
style: const TextStyle(
fontFamily:
'SF UI Display',
fontWeight:
FontWeight.w800,
fontSize: 10,
height: 12.5 / 8.82,
letterSpacing: -0.31,
),
),
],
),
SizedBox(height: 7),
Text(
booking.categoryName,
style: const TextStyle(
fontFamily: 'Gilroy-Regular',
fontWeight: FontWeight.w400,
fontSize: 11.84,
height: 10.65 / 11.84,
letterSpacing: 0,
color: Color(0xFF717171),
),
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
),
],
),
),
],
),
),
const SizedBox(height: 16),
/// Date / Time / Hours
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Date :',
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
const SizedBox(height: 10),
Text(
booking.serviceDate ?? 'April 23, 2024',
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Time :',
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
const SizedBox(height: 10),
Text(
booking.serviceTime ?? '12:00 PM',
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Working hours :',
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
SizedBox(height: 10),
Text(
booking.workingHours,
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
],
),
],
),
),
const SizedBox(height: 20),
/// Canceled Booking Button
/// Buttons Section - Modified for Scheduled Status
booking.status != 3
? Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 10,
),
child: Row(
mainAxisAlignment: booking.status == 1
? MainAxisAlignment
.end // Align to the right for Scheduled status
: MainAxisAlignment
.spaceBetween, // Normal alignment for other statuses
children: [
// Only show Modification button if status is NOT Scheduled (status 1)
booking.status != 1
? Expanded(
child: SizedBox(
height: 42,
child: ElevatedButton(
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => Dialog(
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(
20,
),
),
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(
color: AppColors
.secondprimary,
borderRadius:
BorderRadius.circular(
16,
),
),
child:
BookingModificationDialog(
id: booking.id
.toString(),
serviceId: booking
.serviceId
.toString(),
),
),
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(
0xFF0066FF,
),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(20),
),
),
child: const FittedBox(
child: Text(
"Modification",
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w800,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0.289,
color: Colors.white,
),
),
),
),
),
)
: const SizedBox.shrink(), // Empty space when Scheduled
booking.status != 1
? const SizedBox(width: 8)
: const SizedBox.shrink(),
// Cancel Button - Always visible
GestureDetector(
onTap: () async {
final data = CancelBookingRequest(
id: booking.id.toString(),
serviceId: booking.serviceId.toString(),
type: booking.type == 0
? "0"
: booking.type.toString(),
);
try {
// Call the cancel API
await ref.read(
cancelbookingProvider(data).future,
);
// Show success message
Fluttertoast.showToast(
msg: 'Booking cancelled successfully',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.green,
textColor: Colors.white,
);
// Refresh the booking history data
ref.invalidate(
userbookinghistorydetailsProvider,
);
} catch (e) {
// Show error message
Fluttertoast.showToast(
msg: 'Failed to cancel booking: $e',
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.red,
textColor: Colors.white,
);
}
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
decoration: BoxDecoration(
color: Colors.red.shade50,
borderRadius: BorderRadius.circular(20),
),
child: const Text(
'Cancel Booking',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
)
: const SizedBox.shrink(),
const SizedBox(height: 20),
],
),
);
},
);
},
),
),
);
}
// Updated _loadBookingImage and related methods to handle images properly
// Check if URL is likely to cause a 500 error
}
class BookingModificationDialog extends ConsumerStatefulWidget {
final String id;
final String serviceId;
const BookingModificationDialog({
super.key,
required this.id,
required this.serviceId,
});
@override
_BookingModificationDialogState createState() =>
_BookingModificationDialogState();
}
class _BookingModificationDialogState
extends ConsumerState<BookingModificationDialog> {
int selectedDateIndex = 0;
int selectedTimeIndex = 0;
DateTime currentMonth = DateTime.now();
late List<DateTime> dateList;
List<String> times = [];
@override
void initState() {
super.initState();
_generateDateList();
_generateTimes();
}
void _generateDateList() {
final firstDay = DateTime(currentMonth.year, currentMonth.month, 1);
final lastDay = DateTime(currentMonth.year, currentMonth.month + 1, 0);
dateList = List.generate(
lastDay.day,
(index) => firstDay.add(Duration(days: index)),
);
selectedDateIndex = 0;
}
void _generateTimes() {
times = [];
for (int hour = 0; hour < 24; hour++) {
for (int minute = 0; minute < 60; minute += 30) {
final time = DateTime(0, 0, 0, hour, minute);
times.add(DateFormat('h:mm a').format(time));
}
}
}
void _goToPreviousMonth() {
final newMonth = DateTime(currentMonth.year, currentMonth.month - 1);
if (newMonth.isBefore(
DateTime(DateTime.now().year, DateTime.now().month, 1),
)) {
return;
}
setState(() {
currentMonth = newMonth;
_generateDateList();
});
}
void _goToNextMonth() {
setState(() {
currentMonth = DateTime(currentMonth.year, currentMonth.month + 1);
_generateDateList();
});
}
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
top: 20,
left: 20,
right: 20,
),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Booking Modification',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Change Date',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
Icon(Icons.calendar_today),
],
),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
onPressed: _goToPreviousMonth,
icon: Icon(Icons.arrow_back_ios),
),
Text(
DateFormat('MMMM yyyy').format(currentMonth),
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
IconButton(
onPressed: _goToNextMonth,
icon: Icon(Icons.arrow_forward_ios),
),
],
),
SizedBox(height: 10),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: List.generate(dateList.length, (index) {
final date = dateList[index];
final dayLabel =
'${DateFormat('E').format(date)}\n${DateFormat('d').format(date)}';
final isSelected = selectedDateIndex == index;
return GestureDetector(
onTap: () {
setState(() {
selectedDateIndex = index;
});
},
child: Container(
margin: EdgeInsets.symmetric(horizontal: 5),
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: isSelected ? Colors.blue : Colors.grey.shade200,
borderRadius: BorderRadius.circular(20),
),
child: Text(
dayLabel,
textAlign: TextAlign.center,
style: TextStyle(
color: isSelected ? Colors.white : Colors.black,
fontWeight: FontWeight.bold,
),
),
),
);
}),
),
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Change Time',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
Icon(Icons.access_time),
],
),
SizedBox(height: 10),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: List.generate(times.length, (index) {
final isSelected = selectedTimeIndex == index;
return GestureDetector(
onTap: () {
setState(() {
selectedTimeIndex = index;
});
},
child: Container(
margin: EdgeInsets.symmetric(horizontal: 5),
padding: EdgeInsets.symmetric(
horizontal: 20,
vertical: 10,
),
decoration: BoxDecoration(
color: isSelected ? Colors.blue : Colors.grey.shade200,
borderRadius: BorderRadius.circular(30),
),
child: Text(
times[index],
style: TextStyle(
color: isSelected ? Colors.white : Colors.black,
fontWeight: FontWeight.bold,
),
),
),
);
}),
),
),
SizedBox(height: 20),
// GestureDetector(
// onTap: () {
// Navigator.pop(context);
// },
// child: Container(
// width: double.infinity,
// padding: EdgeInsets.all(15),
// decoration: BoxDecoration(
// color: Colors.red.shade50,
// borderRadius: BorderRadius.circular(15),
// ),
// child: Center(
// child: Text(
// 'Cancel the Booking',
// style: TextStyle(
// color: Colors.red,
// fontWeight: FontWeight.bold,
// fontSize: 16,
// ),
// ),
// ),
// ),
// ),
SizedBox(height: 20),
ElevatedButton(
onPressed: () async {
final selectedDate = DateFormat(
'yyyy-MM-dd',
).format(dateList[selectedDateIndex]);
final selectedTime = times[selectedTimeIndex];
final model = BookingModifyModel(
id: widget.id,
serviceId: widget.serviceId,
servicedate: selectedDate,
servicetime: selectedTime,
);
final result = await ref
.read(modifybookingProvider(model).future)
.catchError((e) {
Fluttertoast.showToast(
msg: 'Error: ${e.toString()}',
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.red,
textColor: Colors.white,
);
return false;
});
if (result) {
Fluttertoast.showToast(
msg: 'Booking modified successfully',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.green,
textColor: Colors.white,
);
Navigator.pop(context);
ref.invalidate(userbookinghistorydetailsProvider);
} else {
Fluttertoast.showToast(
msg: 'Failed to modify booking',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.red,
textColor: Colors.white,
);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
padding: EdgeInsets.symmetric(horizontal: 100, vertical: 15),
),
child: Text('Save', style: TextStyle(fontSize: 18)),
),
SizedBox(height: 20),
],
),
),
);
}
}
@@ -0,0 +1,460 @@
import 'package:bookmywages/consts_widgets/app_colors.dart';
import 'package:bookmywages/viewmodel/api_controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
// Make sure to import your custom colors and dialog widget
// Assuming this exists
class CanceledPage extends ConsumerWidget {
// Changed to ConsumerWidget
const CanceledPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// Added WidgetRef parameter
final bookingAsyncValue = ref.watch(userbookinghistorydetailsProvider);
return Scaffold(
backgroundColor: AppColors.secondprimary,
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: bookingAsyncValue.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, stack) => Center(child: Text('Error: $err')),
data: (bookings) {
// Filter only canceled bookings (status == 3)
final canceledBookings =
bookings.where((booking) => booking.status == 3).toList();
if (canceledBookings.isEmpty) {
return const Center(
child: Text(
'No canceled bookings found',
style: TextStyle(fontSize: 16),
),
);
}
return ListView.builder(
itemCount: canceledBookings.length,
shrinkWrap: false,
itemBuilder: (context, index) {
final booking = canceledBookings[index];
return Container(
margin: const EdgeInsets.only(bottom: 10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppColors.lightGrey),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Top Row: ID and "View order"
Padding(
padding: const EdgeInsets.only(
right: 12,
left: 12,
top: 12,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'ID : ${booking.id}',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
],
),
),
const Divider(),
/// Image, Company, and Status
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child:
(booking.images1.isNotEmpty)
? Image.network(
booking.images1[0],
width: 100,
height: 110,
fit: BoxFit.cover,
loadingBuilder: (
context,
child,
loadingProgress,
) {
if (loadingProgress == null) {
return child;
}
return Center(
child: CircularProgressIndicator(
value:
loadingProgress
.expectedTotalBytes !=
null
? loadingProgress
.cumulativeBytesLoaded /
(loadingProgress
.expectedTotalBytes ??
1)
: null,
),
);
},
errorBuilder: (
context,
error,
stackTrace,
) {
return const Icon(
Icons.error,
size: 100,
);
},
)
: const Icon(
Icons.image_not_supported,
size: 100,
),
),
Positioned(
top: 6,
left: 6,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 6,
height: 6,
decoration: const BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
),
),
const SizedBox(width: 4),
const Text(
'Live',
style: TextStyle(
fontSize: 12,
color: Colors.green,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
],
),
const SizedBox(width: 12),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
booking.vendorName ?? 'No vendor name',
style: const TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 16.11,
height: 14.5 / 16.11,
letterSpacing: 0.01 * 16.11,
),
),
const SizedBox(height: 4),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
booking.serviceName ??
'No service name',
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w500,
fontSize: 13.91,
height: 12.52 / 13.91,
letterSpacing: 0.01 * 13.91,
color: Color(0xFF5A5A5A),
),
),
const SizedBox(width: 10),
Container(
width: 70,
height: 25,
decoration: BoxDecoration(
color: const Color(0xFFFFEEEE),
borderRadius: BorderRadius.circular(
6.05,
),
),
child: Center(
child: Text(
'Cancel',
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontSize: 10.92,
fontWeight: FontWeight.w400,
color: const Color(0xFFFF0000),
letterSpacing: 1.0,
height: 0.98,
),
),
),
),
],
),
const SizedBox(height: 6),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
border: Border.all(
color: AppColors.lightGrey,
),
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
CircleAvatar(
radius: 15,
backgroundImage:
booking.profilePic != null &&
booking
.profilePic!
.isNotEmpty
? NetworkImage(
booking.profilePic!,
)
: null,
child:
booking.profilePic == null ||
booking
.profilePic!
.isEmpty
? const Icon(
Icons.person,
size: 20,
)
: null,
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
booking.vendorName ??
'No data',
style: const TextStyle(
fontFamily:
'Gilroy-Medium',
fontWeight:
FontWeight.w600,
fontSize: 11,
height: 10.65 / 11.84,
letterSpacing: 0,
color: Color(
0xFF353434,
),
),
overflow:
TextOverflow.ellipsis,
),
),
const Icon(
Icons.star,
color: Colors.orange,
size: 15,
),
const SizedBox(width: 4),
Text(
'${booking.isRated ?? '0.0'}',
textAlign: TextAlign.center,
style: const TextStyle(
fontFamily:
'SF UI Display',
fontWeight:
FontWeight.w800,
fontSize: 10,
height: 12.5 / 8.82,
letterSpacing: -0.31,
),
),
],
),
const SizedBox(height: 7),
Text(
booking.categoryName ??
'No category',
style: const TextStyle(
fontFamily: 'Gilroy-Regular',
fontWeight: FontWeight.w400,
fontSize: 11.84,
height: 10.65 / 11.84,
letterSpacing: 0,
color: Color(0xFF717171),
),
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
),
],
),
),
],
),
),
const SizedBox(height: 16),
/// Date / Time / Hours
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Date :',
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
const SizedBox(height: 10),
Text(
booking.cancelDate != null
? _formatDate(booking.cancelDate)
: 'No date',
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Time :',
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
const SizedBox(height: 10),
Text(
booking.cancelDate != null
? DateFormat('h:mm a').format(
DateTime.parse(booking.cancelDate),
)
: 'No time',
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Working hours :',
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
const SizedBox(height: 10),
Text(
booking.workingHours ?? 'No hours',
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
],
),
],
),
),
const SizedBox(height: 20),
],
),
);
},
);
},
),
),
);
}
String _formatDate(String dateString) {
try {
return DateFormat('dd/MM/yyyy').format(DateTime.parse(dateString));
} catch (e) {
return dateString; // Fallback: return the original string if parsing fails
}
}
}
@@ -0,0 +1,446 @@
import 'package:bookmywages/consts_widgets/app_colors.dart';
import 'package:bookmywages/viewmodel/api_controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
// Make sure to import your custom colors and dialog widget
// Assuming this exists
class CompletedPage extends ConsumerWidget {
// Changed to ConsumerWidget
const CompletedPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// Added WidgetRef parameter
final bookingAsyncValue = ref.watch(userbookinghistorydetailsProvider);
return Scaffold(
backgroundColor: AppColors.secondprimary,
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: bookingAsyncValue.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, stack) => Center(child: Text('Error: $err')),
data: (bookings) {
// Filter only canceled bookings (status == 3)
final canceledBookings =
bookings.where((booking) => booking.status == 1).toList();
if (canceledBookings.isEmpty) {
return const Center(
child: Text(
'No canceled bookings found',
style: TextStyle(fontSize: 16),
),
);
}
return ListView.builder(
itemCount: canceledBookings.length,
shrinkWrap: false,
itemBuilder: (context, index) {
final booking = canceledBookings[index];
return Container(
margin: const EdgeInsets.only(bottom: 10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: AppColors.lightGrey),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Top Row: ID and "View order"
Padding(
padding: const EdgeInsets.only(
right: 12,
left: 12,
top: 12,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'ID : ${booking.id}',
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
],
),
),
const Divider(),
/// Image, Company, and Status
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child:
booking.images1.isNotEmpty
? Image.network(
booking.images1[0],
width: 100,
height: 110,
fit: BoxFit.cover,
loadingBuilder: (
context,
child,
loadingProgress,
) {
if (loadingProgress == null) {
return child;
} else {
return Center(
child: CircularProgressIndicator(
value:
loadingProgress
.expectedTotalBytes !=
null
? loadingProgress
.cumulativeBytesLoaded /
(loadingProgress
.expectedTotalBytes ??
1)
: null,
),
);
}
},
errorBuilder: (
context,
error,
stackTrace,
) {
return const Icon(
Icons.error,
size: 100,
);
},
)
: const Icon(
Icons.image_not_supported,
size: 100,
),
),
),
Positioned(
top: 6,
left: 6,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 6,
height: 6,
decoration: const BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
),
),
const SizedBox(width: 4),
const Text(
'Live',
style: TextStyle(
fontSize: 12,
color: Colors.green,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
],
),
const SizedBox(width: 12),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
booking.vendorName ?? 'No vendor name',
style: const TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 16.11,
height: 14.5 / 16.11,
letterSpacing: 0.01 * 16.11,
),
),
const SizedBox(height: 4),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
booking.serviceName ??
'No service name',
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w500,
fontSize: 13.91,
height: 12.52 / 13.91,
letterSpacing: 0.01 * 13.91,
color: Color(0xFF5A5A5A),
),
),
const SizedBox(width: 10),
Container(
width: 70,
height: 25,
decoration: BoxDecoration(
color: const Color(0xffFFEEDB),
borderRadius: BorderRadius.circular(
6.05,
),
),
child: Center(
child: Text(
'Completed',
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontSize: 10.92,
fontWeight: FontWeight.w400,
color: const Color(0xffFF8515),
letterSpacing: 1.0,
height: 0.98,
),
),
),
),
],
),
const SizedBox(height: 10),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const Text(
'Date :',
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
const SizedBox(height: 10),
Text(
booking.serviceDate != null
? _formatDate(
booking.serviceDate,
)
: 'No date',
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
],
),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const Text(
'Time :',
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
const SizedBox(height: 10),
Text(
booking.serviceDate != null
? DateFormat('h:mm a').format(
DateTime.parse(
booking.serviceDate,
),
)
: 'No time',
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
],
),
],
),
],
),
),
],
),
),
const SizedBox(height: 16),
/// Date / Time / Hours
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
"Mobile number : ",
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
SizedBox(width: 7),
Text(
booking.mobileNumber,
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
],
),
SizedBox(height: 10),
Row(
children: [
Text(
"E-mail ID :",
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
SizedBox(width: 7),
Text(
booking.email,
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
],
),
SizedBox(height: 10),
Row(
children: [
Text(
"Address : ",
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
SizedBox(width: 7),
Text(
booking.address,
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
],
),
SizedBox(height: 10),
Text(
"Message : ",
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
SizedBox(height: 7),
Text(
booking.message,
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 14.45,
height: 13 / 14.45,
letterSpacing: 0,
),
),
],
),
),
const SizedBox(height: 20),
],
),
);
},
);
},
),
),
);
}
String _formatDate(String dateString) {
try {
return DateFormat('dd/MM/yyyy').format(DateTime.parse(dateString));
} catch (e) {
return dateString; // Fallback: return the original string if parsing fails
}
}
}
@@ -0,0 +1,376 @@
import 'package:bookmywages/consts_widgets/app_colors.dart';
import 'package:bookmywages/viewmodel/api_controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fluttertoast/fluttertoast.dart';
class EnquriyPage extends ConsumerStatefulWidget {
const EnquriyPage({super.key});
@override
ConsumerState<EnquriyPage> createState() => _EnquriyPageState();
}
class _EnquriyPageState extends ConsumerState<EnquriyPage> {
int? expandedIndex;
@override
Widget build(BuildContext context) {
final enquiryListAsync = ref.watch(enquriylistProvider);
return Scaffold(
backgroundColor: AppColors.secondprimary,
body: enquiryListAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')),
data: (enquiries) => SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: List.generate(enquiries.length, (index) {
final enquiry = enquiries[index];
final isExpanded = expandedIndex == index;
return SizedBox(
width: double.infinity,
child: Column(
children: [
Container(
margin: const EdgeInsets.symmetric(
vertical: 10,
horizontal: 16,
),
decoration: BoxDecoration(
color: AppColors.secondprimary,
borderRadius: BorderRadius.circular(11),
border: Border.all(
color: const Color(0xFFE8E8E8),
width: 1,
),
boxShadow: const [
BoxShadow(
color: Color(0x66AEAEAE),
offset: Offset(0, 2),
blurRadius: 4,
),
],
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: (enquiry.images1.isEmpty)
? Container(
width: 89,
height: 64,
color: Colors.grey[300],
child: Icon(
Icons.image,
color: Colors.grey[600],
),
)
: Image.network(
enquiry.images1.first,
width: 89,
height: 64,
fit: BoxFit.cover,
errorBuilder:
(context, error, stackTrace) =>
Container(
width: 89,
height: 64,
color: Colors.grey[300],
child: Icon(
Icons.broken_image,
color: Colors.grey[600],
),
),
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
enquiry.vendorName ?? 'vendorName',
style: const TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 16,
height: 18.14 / 16,
letterSpacing: 0.16,
color: Colors.black,
),
),
const SizedBox(height: 12),
GestureDetector(
onTap: () {
setState(() {
expandedIndex = isExpanded
? null
: index;
});
},
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
enquiry.serviceName,
textAlign: TextAlign.left,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 16.11,
height: 17.3 / 16.11,
letterSpacing: 0.1611,
color: Colors.black,
),
),
),
const SizedBox(width: 4),
Flex(
direction: Axis.horizontal,
mainAxisSize: MainAxisSize.min,
children: [
Text(
isExpanded
? "View Less"
: "View More",
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 13.15,
height: 5.71 / 13.15,
color: Color(0xFFFF3D00),
),
),
const SizedBox(width: 5),
Icon(
isExpanded
? Icons.keyboard_arrow_up
: Icons
.keyboard_arrow_down,
color: const Color(
0xffFF3D00,
),
),
],
),
],
),
),
],
),
),
],
),
],
),
),
),
if (isExpanded)
Container(
width: double.infinity,
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16.43),
border: Border.all(
color: const Color(0xFFE8E8E8),
width: 0.82,
),
boxShadow: const [
BoxShadow(
color: Color(0xA9A9A940),
blurRadius: 3.29,
offset: Offset(0, 0.82),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text.rich(
TextSpan(
children: [
const TextSpan(
text: "Name : ",
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 13,
height: 1.239,
),
),
TextSpan(
text: enquiry.name ?? 'No Name',
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 13,
height: 1.239,
color: Color(0xFF373636),
),
),
],
),
),
IconButton(
icon: const Icon(
Icons.delete,
color: AppColors.red,
size: 20,
),
onPressed: () async {
final shouldDelete = await showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Delete Enquiry'),
content: const Text(
'Are you sure you want to delete this enquiry?',
),
actions: [
TextButton(
onPressed: () => Navigator.of(
context,
).pop(false),
child: const Text('Cancel'),
),
TextButton(
onPressed: () =>
Navigator.of(context).pop(true),
child: const Text('Delete'),
),
],
),
);
if (shouldDelete == true) {
Fluttertoast.showToast(
msg: 'Deleting enquiry...',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.grey,
textColor: Colors.white,
);
try {
final success = await ref.read(
enquriydeleteProvider(
enquiry.id.toString(),
).future,
);
if (success) {
// Reset expanded index and refresh the list
setState(() {
expandedIndex = null;
});
await Future.delayed(
const Duration(milliseconds: 300),
);
ref.invalidate(enquriylistProvider);
Fluttertoast.showToast(
msg: 'Enquiry deleted successfully',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.green,
textColor: Colors.white,
);
} else {
Fluttertoast.showToast(
msg: 'Failed to delete enquiry',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.red,
textColor: Colors.white,
);
}
} catch (e) {
Fluttertoast.showToast(
msg: 'Error: ${e.toString()}',
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.red,
textColor: Colors.white,
);
}
}
},
),
],
),
const SizedBox(height: 8),
_infoText(
"Mobile number : ",
enquiry.mobile ?? 'No Mobile',
),
const SizedBox(height: 13),
_infoText(
"E-mail Id : ",
enquiry.email ?? 'No Email',
),
const SizedBox(height: 13),
_infoText(
"Message : ",
enquiry.message ?? 'No Message',
),
],
),
),
],
),
);
}),
),
),
),
);
}
Widget _infoText(String label, String value) {
return Text.rich(
TextSpan(
children: [
TextSpan(
text: label,
style: const TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 13,
height: 1.239,
),
),
TextSpan(
text: value,
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 13,
height: 1.239,
color: Color(0xFF373636),
),
),
],
),
);
}
}
@@ -0,0 +1,175 @@
import 'package:bookmywages/consts_widgets/app_colors.dart';
import 'package:bookmywages/view/user_main_screens/history_screen/Service_Booking.dart';
import 'package:bookmywages/view/user_main_screens/history_screen/canceled_page.dart';
import 'package:bookmywages/view/user_main_screens/history_screen/completed_screen.dart';
import 'package:bookmywages/view/user_main_screens/history_screen/enquriy_page.dart';
import 'package:bookmywages/view/user_main_screens/history_screen/payment_deatils.dart';
import 'package:bookmywages/view/user_main_screens/main_contoller.dart';
import 'package:flutter/material.dart';
class HistoryScreen extends StatefulWidget {
final int? initialTabIndex;
const HistoryScreen({super.key, this.initialTabIndex});
@override
State<HistoryScreen> createState() => _HistoryScreenState();
}
class _HistoryScreenState extends State<HistoryScreen>
with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin {
int _currentIndex = 0;
TabController? _tabController;
@override
bool get wantKeepAlive => true;
final List<String> tabTitles = [
"Service Booking", // Index 0
"Enquiry list", // Index 1
"Payment Details", // Index 2
"Completed Booking", // Index 3
"Cancelation Booking", // Index 4
];
@override
void initState() {
super.initState();
// Set initial tab index from widget parameter
if (widget.initialTabIndex != null &&
widget.initialTabIndex! >= 0 &&
widget.initialTabIndex! < tabTitles.length) {
_currentIndex = widget.initialTabIndex!;
}
// Initialize TabController with the correct initial index
_tabController = TabController(
length: tabTitles.length,
vsync: this,
initialIndex: _currentIndex,
);
_tabController!.addListener(() {
if (_tabController!.indexIsChanging) {
setState(() {
_currentIndex = _tabController!.index;
});
}
});
}
@override
void dispose() {
_tabController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
super.build(context);
if (_tabController == null) {
return const Scaffold(body: Center(child: CircularProgressIndicator()));
}
return PopScope(
onPopInvoked: (didPop) {
if (didPop) {
final indexController = InheritedIndexController.of(context);
indexController?.changeIndex(0);
}
},
child: Scaffold(
backgroundColor: Colors.white,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(150.0),
child: Column(
children: [
AppBar(
backgroundColor: Colors.white,
elevation: 0,
title: const Text(
"History",
style: TextStyle(
fontFamily: 'Inter',
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
centerTitle: true,
),
Container(
margin: const EdgeInsets.only(top: 16.0),
alignment: Alignment.centerLeft,
height: 65,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: TabBar(
controller: _tabController,
isScrollable: true,
tabAlignment: TabAlignment.start,
dividerColor: Colors.transparent,
indicatorColor: Colors.transparent,
labelPadding: const EdgeInsets.symmetric(horizontal: 8.0),
tabs: List.generate(
tabTitles.length,
(index) => _buildCustomTab(
tabTitles[index],
index == _currentIndex,
),
),
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
),
),
),
],
),
),
body: TabBarView(
controller: _tabController,
physics: const NeverScrollableScrollPhysics(),
children: const [
ServiceBooking(), // Index 0
EnquriyPage(), // Index 1
PaymentDetails(), // Index 2
CompletedPage(), // Index 3
CanceledPage(), // Index 4
],
),
),
);
}
Widget _buildCustomTab(String text, bool isSelected) {
return Tab(
height: 53,
child: Container(
width: 180,
height: 45,
decoration: BoxDecoration(
color: isSelected ? AppColors.primary : AppColors.secondprimary,
border: Border.all(color: const Color(0xFFDBDBDB)),
borderRadius: BorderRadius.circular(15),
),
alignment: Alignment.center,
child: Text(
text,
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w400,
fontSize: 15,
height: 1.0,
letterSpacing: 0.5,
color: isSelected ? AppColors.secondprimary : AppColors.lightgray,
),
),
),
);
}
}
@@ -0,0 +1,448 @@
import 'package:bookmywages/consts_widgets/app_colors.dart';
import 'package:bookmywages/viewmodel/api_controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart';
class PaymentDetails extends ConsumerStatefulWidget {
const PaymentDetails({super.key});
@override
ConsumerState<PaymentDetails> createState() => _PaymentDetailsState();
}
class _PaymentDetailsState extends ConsumerState<PaymentDetails> {
List<dynamic> _localPayments = [];
@override
Widget build(BuildContext context) {
final paymentAsync = ref.watch(paymentdetailsProvider);
return Scaffold(
backgroundColor: AppColors.secondprimary,
body: paymentAsync.when(
data: (payments) {
final filteredPayments = payments.where((p) => p.type == 1).toList();
// Initialize local copy only once
if (_localPayments.isEmpty) {
_localPayments = List.from(filteredPayments);
}
if (_localPayments.isEmpty) {
return const Center(child: Text("No payment history found."));
}
return RefreshIndicator(
onRefresh: () async {
final refreshed = await ref.refresh(
paymentdetailsProvider.future,
);
setState(() {
_localPayments = refreshed.where((p) => p.type == 2).toList();
});
},
child: ListView.builder(
itemCount: _localPayments.length,
itemBuilder: (context, index) {
final payment = _localPayments[index];
return Dismissible(
key: Key(payment.id.toString()),
direction: DismissDirection.endToStart,
background: Container(
margin: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(20),
),
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 20),
child: const Icon(Icons.delete, color: Colors.white),
),
confirmDismiss: (direction) async {
return await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Confirm Delete"),
content: const Text(
"Are you sure you want to delete this payment record?",
),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text("CANCEL"),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text(
"DELETE",
style: TextStyle(color: Colors.red),
),
),
],
);
},
);
},
onDismissed: (direction) async {
Fluttertoast.showToast(
msg: 'Deleting payment...',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.grey,
textColor: Colors.white,
);
final success = await ref.read(
paymentdeleteProvider(payment.id.toString()).future,
);
if (success) {
setState(() {
_localPayments.removeAt(index);
});
Fluttertoast.showToast(
msg: '${payment.planName} deleted successfully',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.green,
textColor: Colors.white,
);
} else {
Fluttertoast.showToast(
msg: 'Failed to delete payment',
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.red,
textColor: Colors.white,
);
}
},
child: Container(
margin: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: const Color(0xFFCFCFCF),
width: 1,
),
),
child: IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Container(
width: 130,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF334E95),
borderRadius: BorderRadius.circular(12.86),
border: Border.all(
color: Colors.white,
width: 0.43,
),
boxShadow: [
BoxShadow(
color: const Color(0x40828282),
blurRadius: 1.71,
offset: const Offset(0, 2.14),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Text(
payment.planName,
style: const TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 14,
height: 1.0,
letterSpacing: 0.1371,
color: Colors.white,
),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(height: 15),
Row(
children: [
Container(
width: 6.86,
height: 6.86,
decoration: const BoxDecoration(
color: Color(0xFFF9E369),
shape: BoxShape.circle,
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
_formatDuration(payment.duration),
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 12,
height: 1.0,
letterSpacing: 0.0771,
color: Colors.white,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 10),
Row(
children: [
Container(
width: 6.86,
height: 6.86,
decoration: const BoxDecoration(
color: Color(0xFFF9E369),
shape: BoxShape.circle,
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
payment.description,
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w400,
fontSize: 12,
height: 1.0,
letterSpacing: 0.0771,
color: Colors.white,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
],
),
const Divider(color: Colors.white),
Center(
child: Text(
_formatPrice(payment.price),
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
],
),
),
const SizedBox(width: 12),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
payment.planName,
style: const TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 19.9,
height: 1.0,
letterSpacing: 0.199,
color: Colors.black,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 10),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const Text(
"Start :",
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 15.61,
height: 14.04 / 15.61,
letterSpacing: 0.0,
color: Colors.black,
),
),
const SizedBox(height: 10),
Text(
DateFormat('MMM d, y').format(
DateTime.parse(
payment.createdDate,
),
),
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w700,
fontSize: 12,
height: 14.04 / 15.61,
letterSpacing: 0.0,
color: Color(0xFF636363),
),
),
],
),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const Text(
"End :",
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 15.61,
height: 14.04 / 15.61,
letterSpacing: 0.0,
color: Colors.black,
),
),
const SizedBox(height: 10),
Text(
DateFormat('MMM d, y').format(
DateTime.parse(payment.endDate),
),
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w700,
fontSize: 12,
height: 14.04 / 15.61,
letterSpacing: 0.0,
color: Color(0xFF636363),
),
),
],
),
],
),
const SizedBox(height: 10),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const Text(
"Time :",
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 15.61,
height: 14.04 / 15.61,
letterSpacing: 0.0,
color: Colors.black,
),
),
const SizedBox(height: 10),
Text(
DateFormat('hh:mm a').format(
DateTime.parse(
payment.createdDate,
),
),
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w700,
fontSize: 12,
height: 14.04 / 15.61,
letterSpacing: 0.0,
color: Color(0xFF636363),
),
),
],
),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const Text(
"Payment :",
style: TextStyle(
fontFamily: 'Gilroy-Bold',
fontWeight: FontWeight.w700,
fontSize: 15.61,
height: 14.04 / 15.61,
letterSpacing: 0.0,
color: Colors.black,
),
),
const SizedBox(height: 10),
Text(
_formatPrice(payment.price),
style: const TextStyle(
fontFamily: 'Gilroy-Medium',
fontWeight: FontWeight.w700,
fontSize: 12,
height: 14.04 / 15.61,
letterSpacing: 0.0,
color: Color(0xFF636363),
),
),
],
),
],
),
],
),
),
),
],
),
),
),
);
},
),
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text("Error: ${e.toString()}")),
),
);
}
static String _formatDuration(int days) {
if (days == 30) return '1 Month';
if (days == 60) return '2 Months';
if (days == 90) return '3 Months';
if (days == 180) return '6 Months';
if (days == 365) return '1 Year';
return '$days Days';
}
static String _formatPrice(String price) {
if (price.startsWith('Rs.')) {
return price;
}
return 'Rs.$price';
}
}