Update dependencies and enhance tender approval status handling
- Updated `pubspec.lock` to reflect new versions for several packages, including `async`, `fake_async`, `leak_tracker`, and `vm_service`. - Modified `TenderApprovalStatus` enum to include a new status `approved`. - Updated routing for `YourTendersRouteData` to accept a status parameter and pass it to the `YourTendersScreen`. - Adjusted API calls and view models to utilize the new status handling, ensuring proper filtering and display of tender data based on approval status. - Enhanced filter dialog to support new status options and improved user feedback in the UI for submitted and approved tenders.
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tm_app/core/theme/colors.dart';
|
||||
import 'package:tm_app/data/services/model/request_models/get_tenders_request_model/get_tenders_request_model.dart';
|
||||
import 'package:tm_app/view_models/your_tenders_view_model.dart';
|
||||
import 'package:tm_app/views/your_tenders/strings/your_tenders_strings.dart';
|
||||
|
||||
import 'your_tenders_filter_dialog.dart';
|
||||
@@ -11,6 +14,7 @@ class FilterButton extends StatelessWidget {
|
||||
const FilterButton({required this.platformType, super.key});
|
||||
|
||||
void _openFilter(BuildContext context) {
|
||||
final viewModel = context.read<YourTendersViewModel>();
|
||||
if (platformType == PlatformType.mobile) {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
@@ -19,7 +23,24 @@ class FilterButton extends StatelessWidget {
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
||||
),
|
||||
builder: (_) => const YourTendersFilterDialog(),
|
||||
builder:
|
||||
(_) => YourTendersFilterDialog(
|
||||
viewModel: viewModel,
|
||||
onFilterChanged: (dateRange, status) {
|
||||
viewModel.setDateRange(dateRange);
|
||||
viewModel.setStatus(status!);
|
||||
if (status == YourTendersStrings.all) {
|
||||
GetTendersRequestModel requestModel = GetTendersRequestModel(
|
||||
status: '',
|
||||
createdFrom: viewModel.startDateUnix,
|
||||
createdTo: viewModel.endDateUnix,
|
||||
);
|
||||
viewModel.getTenders(requestModel: requestModel);
|
||||
} else {
|
||||
viewModel.callWithFilter();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
showDialog(
|
||||
@@ -30,7 +51,24 @@ class FilterButton extends StatelessWidget {
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: const YourTendersFilterDialog(),
|
||||
child: YourTendersFilterDialog(
|
||||
viewModel: viewModel,
|
||||
onFilterChanged: (dateRange, status) {
|
||||
viewModel.setDateRange(dateRange);
|
||||
viewModel.setStatus(status!);
|
||||
if (status == YourTendersStrings.all) {
|
||||
GetTendersRequestModel requestModel =
|
||||
GetTendersRequestModel(
|
||||
status: '',
|
||||
createdFrom: viewModel.startDateUnix,
|
||||
createdTo: viewModel.endDateUnix,
|
||||
);
|
||||
viewModel.getTenders(requestModel: requestModel);
|
||||
} else {
|
||||
viewModel.callWithFilter();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tm_app/core/constants/tender_approval_status.dart';
|
||||
import 'package:tm_app/core/theme/colors.dart';
|
||||
import 'package:tm_app/core/utils/size_config.dart';
|
||||
import 'package:tm_app/view_models/your_tenders_view_model.dart';
|
||||
@@ -57,12 +58,24 @@ class _TendersSubmittedState extends State<TendersSubmitted> {
|
||||
return Consumer<YourTendersViewModel>(
|
||||
builder: (context, viewModel, child) {
|
||||
if (widget.approvedTenders.data == null) {
|
||||
return const Center(child: Text('No submitted projects yet.'));
|
||||
return Center(
|
||||
child: Text(
|
||||
viewModel.selectedStatus == TenderApprovalStatus.approved.value
|
||||
? 'No Approved data.'
|
||||
: 'No Submitted data.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final tenders = widget.approvedTenders.data!.tenders ?? [];
|
||||
if (tenders.isEmpty) {
|
||||
return const Center(child: Text('No submitted projects yet.'));
|
||||
return Center(
|
||||
child: Text(
|
||||
viewModel.selectedStatus == TenderApprovalStatus.approved.value
|
||||
? 'No Approved data.'
|
||||
: 'No Submitted data.',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final itemCount =
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:tm_app/core/constants/assets.dart';
|
||||
import 'package:tm_app/core/theme/colors.dart';
|
||||
import 'package:tm_app/core/utils/size_config.dart';
|
||||
@@ -9,171 +9,235 @@ import 'package:tm_app/view_models/your_tenders_view_model.dart';
|
||||
import 'package:tm_app/views/shared/base_button.dart';
|
||||
import 'package:tm_app/views/your_tenders/strings/your_tenders_strings.dart';
|
||||
|
||||
class YourTendersFilterDialog extends StatelessWidget {
|
||||
const YourTendersFilterDialog({super.key});
|
||||
import '../../../core/constants/tender_approval_status.dart';
|
||||
import '../../../core/constants/tender_feedback.dart';
|
||||
|
||||
Widget _dateRangePill(BuildContext context) {
|
||||
final viewModel = context.watch<YourTendersViewModel>();
|
||||
final start = viewModel.selectedDateRange?.start;
|
||||
final end = viewModel.selectedDateRange?.end;
|
||||
class YourTendersFilterDialog extends StatefulWidget {
|
||||
final YourTendersViewModel viewModel;
|
||||
final Function(DateTimeRange? dateRange, String? status) onFilterChanged;
|
||||
const YourTendersFilterDialog({
|
||||
required this.viewModel,
|
||||
required this.onFilterChanged,
|
||||
super.key,
|
||||
});
|
||||
|
||||
Future<void> pick() async {
|
||||
final picked = await showDateRangePicker(
|
||||
context: context,
|
||||
firstDate: DateTime(2020),
|
||||
lastDate: DateTime(2035),
|
||||
initialDateRange: viewModel.selectedDateRange,
|
||||
);
|
||||
if (picked != null) {
|
||||
viewModel.setDateRange(picked);
|
||||
}
|
||||
}
|
||||
@override
|
||||
State<YourTendersFilterDialog> createState() =>
|
||||
_YourTendersFilterDialogState();
|
||||
}
|
||||
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: pick,
|
||||
child: Container(
|
||||
height: 56.0.h(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grey0,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColors.grey40),
|
||||
),
|
||||
child: Center(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
width: 1,
|
||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||
color: AppColors.grey40,
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: SizedBox(
|
||||
width: 26.0.w(),
|
||||
height: 26.0.h(),
|
||||
child: SvgPicture.asset(AssetsManager.trailingIcon),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
start == null
|
||||
? YourTendersStrings.startDate
|
||||
: viewModel.startDateFormatted,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0.sp(),
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey70,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
end == null
|
||||
? YourTendersStrings.endDate
|
||||
: viewModel.endDateFormatted,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0.sp(),
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey70,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
class _YourTendersFilterDialogState extends State<YourTendersFilterDialog> {
|
||||
DateTimeRange? dateRange;
|
||||
String? status = YourTendersStrings.all;
|
||||
|
||||
final _fmt = DateFormat('yyyy/MM/dd');
|
||||
|
||||
String get startDateFormatted =>
|
||||
dateRange?.start != null ? _fmt.format(dateRange!.start) : 'YYYY/MM/DD';
|
||||
|
||||
String get endDateFormatted =>
|
||||
dateRange?.end != null ? _fmt.format(dateRange!.end) : 'YYYY/MM/DD';
|
||||
|
||||
String get startDateUnix =>
|
||||
dateRange?.start != null
|
||||
? (dateRange!.start.millisecondsSinceEpoch ~/ 1000).toString()
|
||||
: '';
|
||||
|
||||
String get endDateUnix =>
|
||||
dateRange?.end != null
|
||||
? (dateRange!.end.millisecondsSinceEpoch ~/ 1000).toString()
|
||||
: '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
dateRange = widget.viewModel.selectedDateRange;
|
||||
status = _mapApiValueToDisplayString(widget.viewModel.selectedStatus);
|
||||
}
|
||||
|
||||
Future<void> pick() async {
|
||||
final picked = await showDateRangePicker(
|
||||
context: context,
|
||||
firstDate: DateTime(2000),
|
||||
lastDate: DateTime(2050),
|
||||
initialDateRange: dateRange,
|
||||
);
|
||||
if (picked != null) {
|
||||
dateRange = picked;
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
void setStatus(String status) {
|
||||
this.status = status;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
/// Maps API values to display strings for radio buttons
|
||||
String _mapApiValueToDisplayString(String apiValue) {
|
||||
final mapping = {
|
||||
'All': YourTendersStrings.all,
|
||||
'like': YourTendersStrings.likeTenders,
|
||||
'dislike': YourTendersStrings.dislikeTenders,
|
||||
'approved': YourTendersStrings.approvedTenders,
|
||||
'submitted': YourTendersStrings.tendersSubmitted,
|
||||
};
|
||||
return mapping[apiValue] ?? YourTendersStrings.all;
|
||||
}
|
||||
|
||||
/// Maps display strings to API values for the view model
|
||||
String _mapDisplayStringToApiValue(String displayString) {
|
||||
final mapping = {
|
||||
YourTendersStrings.all: YourTendersStrings.all,
|
||||
YourTendersStrings.likeTenders: TenderFeedback.liked.value,
|
||||
YourTendersStrings.dislikeTenders: TenderFeedback.disliked.value,
|
||||
YourTendersStrings.approvedTenders: TenderApprovalStatus.approved.value,
|
||||
YourTendersStrings.tendersSubmitted: TenderApprovalStatus.submitted.value,
|
||||
};
|
||||
return mapping[displayString] ?? YourTendersStrings.all;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<YourTendersViewModel>(
|
||||
builder: (context, vm, child) {
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: Text(
|
||||
YourTendersStrings.filter,
|
||||
style: TextStyle(
|
||||
fontSize: 20.0.sp(),
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.grey70,
|
||||
),
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Center(
|
||||
child: Text(
|
||||
YourTendersStrings.filter,
|
||||
style: TextStyle(
|
||||
fontSize: 20.0.sp(),
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.grey70,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.0.h()),
|
||||
Text(
|
||||
YourTendersStrings.selectCreationRangeDate,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0.sp(),
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey80,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20.0.h()),
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onTap: pick,
|
||||
child: Container(
|
||||
height: 56.0.h(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grey0,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColors.grey40),
|
||||
),
|
||||
child: Center(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
width: 1,
|
||||
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||
color: AppColors.grey40,
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: SizedBox(
|
||||
width: 26.0.w(),
|
||||
height: 26.0.h(),
|
||||
child: SvgPicture.asset(AssetsManager.trailingIcon),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
dateRange?.start == null
|
||||
? YourTendersStrings.startDate
|
||||
: startDateFormatted,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0.sp(),
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey70,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
dateRange?.end == null
|
||||
? YourTendersStrings.endDate
|
||||
: endDateFormatted,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0.sp(),
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey70,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.0.h()),
|
||||
Text(
|
||||
YourTendersStrings.selectCreationRangeDate,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.0.h()),
|
||||
Text(
|
||||
YourTendersStrings.status,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0.sp(),
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey70,
|
||||
),
|
||||
),
|
||||
...[
|
||||
YourTendersStrings.all,
|
||||
YourTendersStrings.likeTenders,
|
||||
YourTendersStrings.dislikeTenders,
|
||||
YourTendersStrings.approvedTenders,
|
||||
YourTendersStrings.tendersSubmitted,
|
||||
].map((status) {
|
||||
return RadioListTile(
|
||||
value: status,
|
||||
groupValue: this.status,
|
||||
activeColor: AppColors.mainBlue,
|
||||
onChanged: (value) => setStatus(value!),
|
||||
title: Text(
|
||||
status,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0.sp(),
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey80,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20.0.h()),
|
||||
_dateRangePill(context),
|
||||
SizedBox(height: 16.0.h()),
|
||||
Text(
|
||||
YourTendersStrings.status,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0.sp(),
|
||||
fontWeight: FontWeight.w600,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey70,
|
||||
),
|
||||
),
|
||||
...[
|
||||
YourTendersStrings.all,
|
||||
YourTendersStrings.likeTenders,
|
||||
YourTendersStrings.dislikeTenders,
|
||||
YourTendersStrings.approvedTenders,
|
||||
YourTendersStrings.tendersSubmitted,
|
||||
].map((status) {
|
||||
return RadioListTile(
|
||||
value: status,
|
||||
groupValue: vm.selectedStatus,
|
||||
activeColor: AppColors.mainBlue,
|
||||
onChanged: (value) => vm.setStatus(value!),
|
||||
title: Text(
|
||||
status,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0.sp(),
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey70,
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
SizedBox(height: 16.0.h()),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: BaseButton(
|
||||
isEnabled: true,
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
widget.onFilterChanged(
|
||||
dateRange,
|
||||
_mapDisplayStringToApiValue(status!),
|
||||
);
|
||||
}),
|
||||
SizedBox(height: 16.0.h()),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: BaseButton(
|
||||
isEnabled: true,
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
vm.callWithFilter();
|
||||
},
|
||||
text: YourTendersStrings.confirm,
|
||||
textColor: AppColors.textBlue,
|
||||
backgroundColor: AppColors.primary20,
|
||||
),
|
||||
),
|
||||
],
|
||||
},
|
||||
text: YourTendersStrings.confirm,
|
||||
textColor: AppColors.textBlue,
|
||||
backgroundColor: AppColors.primary20,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user