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:
@@ -3,7 +3,8 @@ enum TenderApprovalStatus {
|
|||||||
submitted,
|
submitted,
|
||||||
rejected,
|
rejected,
|
||||||
unsubmitted,
|
unsubmitted,
|
||||||
unrejected;
|
unrejected,
|
||||||
|
approved;
|
||||||
|
|
||||||
/// Convert enum to string value for API
|
/// Convert enum to string value for API
|
||||||
String get value {
|
String get value {
|
||||||
@@ -16,6 +17,8 @@ enum TenderApprovalStatus {
|
|||||||
return 'unsubmitted';
|
return 'unsubmitted';
|
||||||
case TenderApprovalStatus.unrejected:
|
case TenderApprovalStatus.unrejected:
|
||||||
return 'unrejected';
|
return 'unrejected';
|
||||||
|
case TenderApprovalStatus.approved:
|
||||||
|
return 'approved';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,11 +212,12 @@ class NotificationRouteData extends GoRouteData with _$NotificationRouteData {
|
|||||||
|
|
||||||
@TypedGoRoute<YourTendersRouteData>(path: '/your-tenders')
|
@TypedGoRoute<YourTendersRouteData>(path: '/your-tenders')
|
||||||
class YourTendersRouteData extends GoRouteData with _$YourTendersRouteData {
|
class YourTendersRouteData extends GoRouteData with _$YourTendersRouteData {
|
||||||
const YourTendersRouteData();
|
final String status;
|
||||||
|
const YourTendersRouteData({required this.status});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, GoRouterState state) {
|
Widget build(BuildContext context, GoRouterState state) {
|
||||||
return yourTendersProvider(child: const YourTendersScreen());
|
return yourTendersProvider(child: YourTendersScreen(status: status));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -247,10 +247,15 @@ RouteBase get $yourTendersRouteData => GoRouteData.$route(
|
|||||||
|
|
||||||
mixin _$YourTendersRouteData on GoRouteData {
|
mixin _$YourTendersRouteData on GoRouteData {
|
||||||
static YourTendersRouteData _fromState(GoRouterState state) =>
|
static YourTendersRouteData _fromState(GoRouterState state) =>
|
||||||
const YourTendersRouteData();
|
YourTendersRouteData(status: state.uri.queryParameters['status']!);
|
||||||
|
|
||||||
|
YourTendersRouteData get _self => this as YourTendersRouteData;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get location => GoRouteData.$location('/your-tenders');
|
String get location => GoRouteData.$location(
|
||||||
|
'/your-tenders',
|
||||||
|
queryParams: {'status': _self.status},
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void go(BuildContext context) => context.go(location);
|
void go(BuildContext context) => context.go(location);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class YourTendersApi {
|
|||||||
required String createdTo,
|
required String createdTo,
|
||||||
required String status,
|
required String status,
|
||||||
}) {
|
}) {
|
||||||
String url = '$tenderApproval?limit=$limit&offset=$offset&status=submitted';
|
String url = '$tenderApproval?limit=$limit&offset=$offset';
|
||||||
|
|
||||||
if (createdFrom.isNotEmpty && createdTo.isNotEmpty) {
|
if (createdFrom.isNotEmpty && createdTo.isNotEmpty) {
|
||||||
url += '&created_from=$createdFrom&created_to=$createdTo';
|
url += '&created_from=$createdFrom&created_to=$createdTo';
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ class YourTendersViewModel with ChangeNotifier {
|
|||||||
offset: _tendersOffset,
|
offset: _tendersOffset,
|
||||||
);
|
);
|
||||||
await getTenders(requestModel: requestModel, reset: true);
|
await getTenders(requestModel: requestModel, reset: true);
|
||||||
} else if (selectedStatus == YourTendersStrings.tendersSubmitted) {
|
} else if (selectedStatus == TenderApprovalStatus.submitted.value) {
|
||||||
final requestModel = GetTendersRequestModel(
|
final requestModel = GetTendersRequestModel(
|
||||||
status: TenderApprovalStatus.submitted.value,
|
status: TenderApprovalStatus.submitted.value,
|
||||||
createdFrom: startDateUnix,
|
createdFrom: startDateUnix,
|
||||||
@@ -288,16 +288,21 @@ class YourTendersViewModel with ChangeNotifier {
|
|||||||
offset: _tendersOffset,
|
offset: _tendersOffset,
|
||||||
);
|
);
|
||||||
await getTenders(requestModel: requestModel, reset: true);
|
await getTenders(requestModel: requestModel, reset: true);
|
||||||
} else if (selectedStatus == YourTendersStrings.likeTenders) {
|
} else if (selectedStatus == TenderFeedback.liked.value) {
|
||||||
await getTendersFeedback(
|
await getTendersFeedback(
|
||||||
feedbackType: TenderFeedback.liked.value,
|
feedbackType: TenderFeedback.liked.value,
|
||||||
reset: true,
|
reset: true,
|
||||||
);
|
);
|
||||||
} else if (selectedStatus == YourTendersStrings.dislikeTenders) {
|
} else if (selectedStatus == TenderFeedback.disliked.value) {
|
||||||
await getTendersFeedback(
|
await getTendersFeedback(
|
||||||
feedbackType: TenderFeedback.disliked.value,
|
feedbackType: TenderFeedback.disliked.value,
|
||||||
reset: true,
|
reset: true,
|
||||||
);
|
);
|
||||||
|
} else if (selectedStatus == TenderApprovalStatus.approved.value) {
|
||||||
|
await getTendersFeedback(
|
||||||
|
feedbackType: TenderApprovalStatus.approved.value,
|
||||||
|
reset: true,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:tm_app/core/utils/size_config.dart';
|
|||||||
import 'package:tm_app/view_models/home_view_model.dart';
|
import 'package:tm_app/view_models/home_view_model.dart';
|
||||||
import 'package:tm_app/views/shared/desktop_navigation_widget.dart';
|
import 'package:tm_app/views/shared/desktop_navigation_widget.dart';
|
||||||
|
|
||||||
|
import '../../../core/constants/tender_approval_status.dart';
|
||||||
import '../strings/home_strings.dart';
|
import '../strings/home_strings.dart';
|
||||||
import '../widgets.dart';
|
import '../widgets.dart';
|
||||||
|
|
||||||
@@ -138,7 +139,9 @@ class DesktopHomePage extends StatelessWidget {
|
|||||||
width: 178,
|
width: 178,
|
||||||
height: 148,
|
height: 148,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
const YourTendersRouteData().push(context);
|
YourTendersRouteData(
|
||||||
|
status: TenderApprovalStatus.submitted.value,
|
||||||
|
).push(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -154,7 +157,9 @@ class DesktopHomePage extends StatelessWidget {
|
|||||||
width: 178,
|
width: 178,
|
||||||
height: 148,
|
height: 148,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
const YourTendersRouteData().push(context);
|
YourTendersRouteData(
|
||||||
|
status: TenderApprovalStatus.approved.value,
|
||||||
|
).push(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:tm_app/data/services/model/tender_approvals_stats_response/tende
|
|||||||
import 'package:tm_app/view_models/home_view_model.dart';
|
import 'package:tm_app/view_models/home_view_model.dart';
|
||||||
import 'package:tm_app/views/shared/tender_app_bar.dart';
|
import 'package:tm_app/views/shared/tender_app_bar.dart';
|
||||||
|
|
||||||
|
import '../../../core/constants/tender_approval_status.dart';
|
||||||
import '../strings/home_strings.dart';
|
import '../strings/home_strings.dart';
|
||||||
import '../widgets.dart';
|
import '../widgets.dart';
|
||||||
|
|
||||||
@@ -108,7 +109,9 @@ class MobileHomePage extends StatelessWidget {
|
|||||||
textColor: AppColors.mainBlue,
|
textColor: AppColors.mainBlue,
|
||||||
enableTap: true,
|
enableTap: true,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
const YourTendersRouteData().push(context);
|
YourTendersRouteData(
|
||||||
|
status: TenderApprovalStatus.submitted.value,
|
||||||
|
).push(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
TenderCard(
|
TenderCard(
|
||||||
@@ -120,7 +123,9 @@ class MobileHomePage extends StatelessWidget {
|
|||||||
textColor: AppColors.jellyBean,
|
textColor: AppColors.jellyBean,
|
||||||
enableTap: true,
|
enableTap: true,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
const YourTendersRouteData().push(context);
|
YourTendersRouteData(
|
||||||
|
status: TenderApprovalStatus.approved.value,
|
||||||
|
).push(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:tm_app/view_models/home_view_model.dart';
|
|||||||
import 'package:tm_app/views/shared/tablet_navigation_widget.dart';
|
import 'package:tm_app/views/shared/tablet_navigation_widget.dart';
|
||||||
|
|
||||||
import '../../../core/constants/assets.dart';
|
import '../../../core/constants/assets.dart';
|
||||||
|
import '../../../core/constants/tender_approval_status.dart';
|
||||||
import '../../shared/tender_app_bar.dart';
|
import '../../shared/tender_app_bar.dart';
|
||||||
import '../strings/home_strings.dart';
|
import '../strings/home_strings.dart';
|
||||||
import '../widgets.dart';
|
import '../widgets.dart';
|
||||||
@@ -130,7 +131,9 @@ class TabletHomePage extends StatelessWidget {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 148,
|
height: 148,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
const YourTendersRouteData().push(context);
|
YourTendersRouteData(
|
||||||
|
status: TenderApprovalStatus.submitted.value,
|
||||||
|
).push(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -146,7 +149,9 @@ class TabletHomePage extends StatelessWidget {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 148,
|
height: 148,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
const YourTendersRouteData().push(context);
|
YourTendersRouteData(
|
||||||
|
status: TenderApprovalStatus.approved.value,
|
||||||
|
).push(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class _YourTendersMobilePageState extends State<YourTendersMobilePage> {
|
|||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
if (viewModel.selectedStatus ==
|
if (viewModel.selectedStatus ==
|
||||||
YourTendersStrings.likeTenders &&
|
TenderFeedback.liked.value &&
|
||||||
viewModel.likedTendersData != null)
|
viewModel.likedTendersData != null)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: LikedDislikedTendersList(
|
child: LikedDislikedTendersList(
|
||||||
@@ -72,7 +72,7 @@ class _YourTendersMobilePageState extends State<YourTendersMobilePage> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
else if (viewModel.selectedStatus ==
|
else if (viewModel.selectedStatus ==
|
||||||
YourTendersStrings.dislikeTenders &&
|
TenderFeedback.disliked.value &&
|
||||||
viewModel.likedTendersData != null)
|
viewModel.likedTendersData != null)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: LikedDislikedTendersList(
|
child: LikedDislikedTendersList(
|
||||||
|
|||||||
@@ -7,10 +7,9 @@ import 'package:tm_app/views/your_tenders/pages/your_tenders_desktop_page.dart';
|
|||||||
import 'package:tm_app/views/your_tenders/pages/your_tenders_mobile_page.dart';
|
import 'package:tm_app/views/your_tenders/pages/your_tenders_mobile_page.dart';
|
||||||
import 'package:tm_app/views/your_tenders/pages/your_tenders_tablet_page.dart';
|
import 'package:tm_app/views/your_tenders/pages/your_tenders_tablet_page.dart';
|
||||||
|
|
||||||
import '../../../data/services/model/request_models/get_tenders_request_model/get_tenders_request_model.dart';
|
|
||||||
|
|
||||||
class YourTendersScreen extends StatefulWidget {
|
class YourTendersScreen extends StatefulWidget {
|
||||||
const YourTendersScreen({super.key});
|
const YourTendersScreen({required this.status, super.key});
|
||||||
|
final String status;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<YourTendersScreen> createState() => _YourTendersScreenState();
|
State<YourTendersScreen> createState() => _YourTendersScreenState();
|
||||||
@@ -23,8 +22,8 @@ class _YourTendersScreenState extends State<YourTendersScreen> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
viewModel = context.read<YourTendersViewModel>();
|
viewModel = context.read<YourTendersViewModel>();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
final requestModel = GetTendersRequestModel();
|
viewModel.setStatus(widget.status);
|
||||||
viewModel.getTenders(requestModel: requestModel, reset: true);
|
viewModel.callWithFilter();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:tm_app/core/theme/colors.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 'package:tm_app/views/your_tenders/strings/your_tenders_strings.dart';
|
||||||
|
|
||||||
import 'your_tenders_filter_dialog.dart';
|
import 'your_tenders_filter_dialog.dart';
|
||||||
@@ -11,6 +14,7 @@ class FilterButton extends StatelessWidget {
|
|||||||
const FilterButton({required this.platformType, super.key});
|
const FilterButton({required this.platformType, super.key});
|
||||||
|
|
||||||
void _openFilter(BuildContext context) {
|
void _openFilter(BuildContext context) {
|
||||||
|
final viewModel = context.read<YourTendersViewModel>();
|
||||||
if (platformType == PlatformType.mobile) {
|
if (platformType == PlatformType.mobile) {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -19,7 +23,24 @@ class FilterButton extends StatelessWidget {
|
|||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
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 {
|
} else {
|
||||||
showDialog(
|
showDialog(
|
||||||
@@ -30,7 +51,24 @@ class FilterButton extends StatelessWidget {
|
|||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
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:flutter/material.dart';
|
||||||
import 'package:provider/provider.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/theme/colors.dart';
|
||||||
import 'package:tm_app/core/utils/size_config.dart';
|
import 'package:tm_app/core/utils/size_config.dart';
|
||||||
import 'package:tm_app/view_models/your_tenders_view_model.dart';
|
import 'package:tm_app/view_models/your_tenders_view_model.dart';
|
||||||
@@ -57,12 +58,24 @@ class _TendersSubmittedState extends State<TendersSubmitted> {
|
|||||||
return Consumer<YourTendersViewModel>(
|
return Consumer<YourTendersViewModel>(
|
||||||
builder: (context, viewModel, child) {
|
builder: (context, viewModel, child) {
|
||||||
if (widget.approvedTenders.data == null) {
|
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 ?? [];
|
final tenders = widget.approvedTenders.data!.tenders ?? [];
|
||||||
if (tenders.isEmpty) {
|
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 =
|
final itemCount =
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:go_router/go_router.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/constants/assets.dart';
|
||||||
import 'package:tm_app/core/theme/colors.dart';
|
import 'package:tm_app/core/theme/colors.dart';
|
||||||
import 'package:tm_app/core/utils/size_config.dart';
|
import 'package:tm_app/core/utils/size_config.dart';
|
||||||
@@ -9,27 +9,124 @@ 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/shared/base_button.dart';
|
||||||
import 'package:tm_app/views/your_tenders/strings/your_tenders_strings.dart';
|
import 'package:tm_app/views/your_tenders/strings/your_tenders_strings.dart';
|
||||||
|
|
||||||
class YourTendersFilterDialog extends StatelessWidget {
|
import '../../../core/constants/tender_approval_status.dart';
|
||||||
const YourTendersFilterDialog({super.key});
|
import '../../../core/constants/tender_feedback.dart';
|
||||||
|
|
||||||
Widget _dateRangePill(BuildContext context) {
|
class YourTendersFilterDialog extends StatefulWidget {
|
||||||
final viewModel = context.watch<YourTendersViewModel>();
|
final YourTendersViewModel viewModel;
|
||||||
final start = viewModel.selectedDateRange?.start;
|
final Function(DateTimeRange? dateRange, String? status) onFilterChanged;
|
||||||
final end = viewModel.selectedDateRange?.end;
|
const YourTendersFilterDialog({
|
||||||
|
required this.viewModel,
|
||||||
|
required this.onFilterChanged,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<YourTendersFilterDialog> createState() =>
|
||||||
|
_YourTendersFilterDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
Future<void> pick() async {
|
||||||
final picked = await showDateRangePicker(
|
final picked = await showDateRangePicker(
|
||||||
context: context,
|
context: context,
|
||||||
firstDate: DateTime(2020),
|
firstDate: DateTime(2000),
|
||||||
lastDate: DateTime(2035),
|
lastDate: DateTime(2050),
|
||||||
initialDateRange: viewModel.selectedDateRange,
|
initialDateRange: dateRange,
|
||||||
);
|
);
|
||||||
if (picked != null) {
|
if (picked != null) {
|
||||||
viewModel.setDateRange(picked);
|
dateRange = picked;
|
||||||
|
setState(() {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return InkWell(
|
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 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),
|
borderRadius: BorderRadius.circular(12),
|
||||||
onTap: pick,
|
onTap: pick,
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -64,9 +161,9 @@ class YourTendersFilterDialog extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
start == null
|
dateRange?.start == null
|
||||||
? YourTendersStrings.startDate
|
? YourTendersStrings.startDate
|
||||||
: viewModel.startDateFormatted,
|
: startDateFormatted,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.0.sp(),
|
fontSize: 16.0.sp(),
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
@@ -74,9 +171,9 @@ class YourTendersFilterDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
end == null
|
dateRange?.end == null
|
||||||
? YourTendersStrings.endDate
|
? YourTendersStrings.endDate
|
||||||
: viewModel.endDateFormatted,
|
: endDateFormatted,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.0.sp(),
|
fontSize: 16.0.sp(),
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
@@ -89,41 +186,7 @@ class YourTendersFilterDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@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,
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
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()),
|
|
||||||
_dateRangePill(context),
|
|
||||||
SizedBox(height: 16.0.h()),
|
SizedBox(height: 16.0.h()),
|
||||||
Text(
|
Text(
|
||||||
YourTendersStrings.status,
|
YourTendersStrings.status,
|
||||||
@@ -142,9 +205,9 @@ class YourTendersFilterDialog extends StatelessWidget {
|
|||||||
].map((status) {
|
].map((status) {
|
||||||
return RadioListTile(
|
return RadioListTile(
|
||||||
value: status,
|
value: status,
|
||||||
groupValue: vm.selectedStatus,
|
groupValue: this.status,
|
||||||
activeColor: AppColors.mainBlue,
|
activeColor: AppColors.mainBlue,
|
||||||
onChanged: (value) => vm.setStatus(value!),
|
onChanged: (value) => setStatus(value!),
|
||||||
title: Text(
|
title: Text(
|
||||||
status,
|
status,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -162,7 +225,10 @@ class YourTendersFilterDialog extends StatelessWidget {
|
|||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.pop();
|
context.pop();
|
||||||
vm.callWithFilter();
|
widget.onFilterChanged(
|
||||||
|
dateRange,
|
||||||
|
_mapDisplayStringToApiValue(status!),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
text: YourTendersStrings.confirm,
|
text: YourTendersStrings.confirm,
|
||||||
textColor: AppColors.textBlue,
|
textColor: AppColors.textBlue,
|
||||||
@@ -173,7 +239,5 @@ class YourTendersFilterDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
////////////////////////////
|
|
||||||
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:tm_app/data/services/model/error/error_model.dart';
|
import 'package:tm_app/data/services/model/error/error_model.dart';
|
||||||
|
|
||||||
@@ -41,9 +39,7 @@ void main() {
|
|||||||
details: 'Server error',
|
details: 'Server error',
|
||||||
);
|
);
|
||||||
|
|
||||||
final updated = error.copyWith(
|
final updated = error.copyWith(message: 'Timeout error');
|
||||||
message: 'Timeout error',
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(updated.message, 'Timeout error');
|
expect(updated.message, 'Timeout error');
|
||||||
expect(updated.code, '500');
|
expect(updated.code, '500');
|
||||||
@@ -51,11 +47,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('fromJson handles null fields safely', () {
|
test('fromJson handles null fields safely', () {
|
||||||
final json = {
|
final json = {'message': null, 'code': null, 'details': null};
|
||||||
'message': null,
|
|
||||||
'code': null,
|
|
||||||
'details': null,
|
|
||||||
};
|
|
||||||
|
|
||||||
final error = ErrorModel.fromJson(json);
|
final error = ErrorModel.fromJson(json);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user