last 16-10-2025
This commit is contained in:
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';
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user