Merge pull request 'dialogs_fix' (#212) from dialogs_fix into main
Reviewed-on: https://repo.ravanertebat.com/TM/tm_app/pulls/212
This commit is contained in:
@@ -3,7 +3,8 @@ enum TenderApprovalStatus {
|
||||
submitted,
|
||||
rejected,
|
||||
unsubmitted,
|
||||
unrejected;
|
||||
unrejected,
|
||||
approved;
|
||||
|
||||
/// Convert enum to string value for API
|
||||
String get value {
|
||||
@@ -16,6 +17,8 @@ enum TenderApprovalStatus {
|
||||
return 'unsubmitted';
|
||||
case TenderApprovalStatus.unrejected:
|
||||
return 'unrejected';
|
||||
case TenderApprovalStatus.approved:
|
||||
return 'approved';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,11 +212,12 @@ class NotificationRouteData extends GoRouteData with _$NotificationRouteData {
|
||||
|
||||
@TypedGoRoute<YourTendersRouteData>(path: '/your-tenders')
|
||||
class YourTendersRouteData extends GoRouteData with _$YourTendersRouteData {
|
||||
const YourTendersRouteData();
|
||||
final String status;
|
||||
const YourTendersRouteData({required this.status});
|
||||
|
||||
@override
|
||||
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 {
|
||||
static YourTendersRouteData _fromState(GoRouterState state) =>
|
||||
const YourTendersRouteData();
|
||||
YourTendersRouteData(status: state.uri.queryParameters['status']!);
|
||||
|
||||
YourTendersRouteData get _self => this as YourTendersRouteData;
|
||||
|
||||
@override
|
||||
String get location => GoRouteData.$location('/your-tenders');
|
||||
String get location => GoRouteData.$location(
|
||||
'/your-tenders',
|
||||
queryParams: {'status': _self.status},
|
||||
);
|
||||
|
||||
@override
|
||||
void go(BuildContext context) => context.go(location);
|
||||
|
||||
@@ -8,7 +8,7 @@ class YourTendersApi {
|
||||
required String createdTo,
|
||||
required String status,
|
||||
}) {
|
||||
String url = '$tenderApproval?limit=$limit&offset=$offset&status=submitted';
|
||||
String url = '$tenderApproval?limit=$limit&offset=$offset';
|
||||
|
||||
if (createdFrom.isNotEmpty && createdTo.isNotEmpty) {
|
||||
url += '&created_from=$createdFrom&created_to=$createdTo';
|
||||
|
||||
@@ -279,7 +279,7 @@ class YourTendersViewModel with ChangeNotifier {
|
||||
offset: _tendersOffset,
|
||||
);
|
||||
await getTenders(requestModel: requestModel, reset: true);
|
||||
} else if (selectedStatus == YourTendersStrings.tendersSubmitted) {
|
||||
} else if (selectedStatus == TenderApprovalStatus.submitted.value) {
|
||||
final requestModel = GetTendersRequestModel(
|
||||
status: TenderApprovalStatus.submitted.value,
|
||||
createdFrom: startDateUnix,
|
||||
@@ -288,16 +288,21 @@ class YourTendersViewModel with ChangeNotifier {
|
||||
offset: _tendersOffset,
|
||||
);
|
||||
await getTenders(requestModel: requestModel, reset: true);
|
||||
} else if (selectedStatus == YourTendersStrings.likeTenders) {
|
||||
} else if (selectedStatus == TenderFeedback.liked.value) {
|
||||
await getTendersFeedback(
|
||||
feedbackType: TenderFeedback.liked.value,
|
||||
reset: true,
|
||||
);
|
||||
} else if (selectedStatus == YourTendersStrings.dislikeTenders) {
|
||||
} else if (selectedStatus == TenderFeedback.disliked.value) {
|
||||
await getTendersFeedback(
|
||||
feedbackType: TenderFeedback.disliked.value,
|
||||
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/views/shared/desktop_navigation_widget.dart';
|
||||
|
||||
import '../../../core/constants/tender_approval_status.dart';
|
||||
import '../strings/home_strings.dart';
|
||||
import '../widgets.dart';
|
||||
|
||||
@@ -138,7 +139,9 @@ class DesktopHomePage extends StatelessWidget {
|
||||
width: 178,
|
||||
height: 148,
|
||||
onTap: () {
|
||||
const YourTendersRouteData().push(context);
|
||||
YourTendersRouteData(
|
||||
status: TenderApprovalStatus.submitted.value,
|
||||
).push(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -154,7 +157,9 @@ class DesktopHomePage extends StatelessWidget {
|
||||
width: 178,
|
||||
height: 148,
|
||||
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/views/shared/tender_app_bar.dart';
|
||||
|
||||
import '../../../core/constants/tender_approval_status.dart';
|
||||
import '../strings/home_strings.dart';
|
||||
import '../widgets.dart';
|
||||
|
||||
@@ -108,7 +109,9 @@ class MobileHomePage extends StatelessWidget {
|
||||
textColor: AppColors.mainBlue,
|
||||
enableTap: true,
|
||||
onTap: () {
|
||||
const YourTendersRouteData().push(context);
|
||||
YourTendersRouteData(
|
||||
status: TenderApprovalStatus.submitted.value,
|
||||
).push(context);
|
||||
},
|
||||
),
|
||||
TenderCard(
|
||||
@@ -120,7 +123,9 @@ class MobileHomePage extends StatelessWidget {
|
||||
textColor: AppColors.jellyBean,
|
||||
enableTap: true,
|
||||
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 '../../../core/constants/assets.dart';
|
||||
import '../../../core/constants/tender_approval_status.dart';
|
||||
import '../../shared/tender_app_bar.dart';
|
||||
import '../strings/home_strings.dart';
|
||||
import '../widgets.dart';
|
||||
@@ -130,7 +131,9 @@ class TabletHomePage extends StatelessWidget {
|
||||
width: double.infinity,
|
||||
height: 148,
|
||||
onTap: () {
|
||||
const YourTendersRouteData().push(context);
|
||||
YourTendersRouteData(
|
||||
status: TenderApprovalStatus.submitted.value,
|
||||
).push(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -146,7 +149,9 @@ class TabletHomePage extends StatelessWidget {
|
||||
width: double.infinity,
|
||||
height: 148,
|
||||
onTap: () {
|
||||
const YourTendersRouteData().push(context);
|
||||
YourTendersRouteData(
|
||||
status: TenderApprovalStatus.approved.value,
|
||||
).push(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
@@ -185,6 +185,7 @@ class _ActionButtons extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final viewModel = context.read<TendersViewModel>();
|
||||
return Container(
|
||||
margin: EdgeInsets.only(top: 25.0.h()),
|
||||
height: 40.0.h(),
|
||||
@@ -203,9 +204,16 @@ class _ActionButtons extends StatelessWidget {
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: const SizedBox(
|
||||
child: SizedBox(
|
||||
width: 560,
|
||||
child: TendersFilterDialog(),
|
||||
child: TendersFilterDialog(
|
||||
viewModel: viewModel,
|
||||
onFilterChanged: (dateRange, status) {
|
||||
viewModel.setDateRange(dateRange);
|
||||
viewModel.setStatus(status!);
|
||||
viewModel.callWithSortAndStatus();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -229,9 +237,15 @@ class _ActionButtons extends StatelessWidget {
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: const SizedBox(
|
||||
child: SizedBox(
|
||||
width: 560,
|
||||
child: TendersSortDialog(),
|
||||
child: TendersSortDialog(
|
||||
viewModel: viewModel,
|
||||
onConfirm: (sort) {
|
||||
viewModel.setSort(sort);
|
||||
viewModel.callWithSortAndStatus();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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';
|
||||
@@ -10,96 +10,72 @@ import 'package:tm_app/views/tenders/strings/tenders_strings.dart';
|
||||
|
||||
import '../../../view_models/tenders_view_model.dart';
|
||||
|
||||
class TendersFilterDialog extends StatelessWidget {
|
||||
const TendersFilterDialog({super.key});
|
||||
class TendersFilterDialog extends StatefulWidget {
|
||||
final TendersViewModel viewModel;
|
||||
final Function(DateTimeRange? dateRange, String? status) onFilterChanged;
|
||||
const TendersFilterDialog({
|
||||
required this.viewModel,
|
||||
required this.onFilterChanged,
|
||||
super.key,
|
||||
});
|
||||
|
||||
Widget _dateRangePill(BuildContext context) {
|
||||
final viewModel = context.watch<TendersViewModel>();
|
||||
final start = viewModel.selectedDateRange?.start;
|
||||
final end = viewModel.selectedDateRange?.end;
|
||||
@override
|
||||
State<TendersFilterDialog> createState() => _TendersFilterDialogState();
|
||||
}
|
||||
|
||||
class _TendersFilterDialogState extends State<TendersFilterDialog> {
|
||||
DateTimeRange? dateRange;
|
||||
String? status = TendersStrings.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);
|
||||
}
|
||||
|
||||
String _mapApiValueToDisplayString(String apiValue) {
|
||||
final mapping = {
|
||||
'All': TendersStrings.all,
|
||||
'Read': TendersStrings.read,
|
||||
'Unread': TendersStrings.unread,
|
||||
};
|
||||
return mapping[apiValue] ?? TendersStrings.all;
|
||||
}
|
||||
|
||||
Future<void> pick() async {
|
||||
final picked = await showDateRangePicker(
|
||||
context: context,
|
||||
firstDate: DateTime(2020),
|
||||
lastDate: DateTime(2035),
|
||||
initialDateRange: viewModel.selectedDateRange,
|
||||
firstDate: DateTime(2000),
|
||||
lastDate: DateTime(2050),
|
||||
initialDateRange: dateRange,
|
||||
);
|
||||
if (picked != null) {
|
||||
viewModel.setDateRange(picked);
|
||||
dateRange = picked;
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
width: 560,
|
||||
child: 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
|
||||
? TendersStrings.startDate
|
||||
: viewModel.startDateFormatted,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0.sp(),
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey70,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
end == null
|
||||
? TendersStrings.endDate
|
||||
: viewModel.endDateFormatted,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0.sp(),
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey70,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<TendersViewModel>(
|
||||
builder: (context, vm, child) {
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -144,7 +120,70 @@ class TendersFilterDialog extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10.0.h()),
|
||||
_dateRangePill(context),
|
||||
SizedBox(
|
||||
width: 560,
|
||||
child: 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
|
||||
? TendersStrings.startDate
|
||||
: startDateFormatted,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0.sp(),
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey70,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
dateRange?.end == null
|
||||
? TendersStrings.endDate
|
||||
: endDateFormatted,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0.sp(),
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey70,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.0.h()),
|
||||
Text(
|
||||
TendersStrings.status,
|
||||
@@ -161,9 +200,12 @@ class TendersFilterDialog extends StatelessWidget {
|
||||
].map((status) {
|
||||
return RadioListTile(
|
||||
value: status,
|
||||
groupValue: vm.selectedStatus,
|
||||
groupValue: this.status,
|
||||
activeColor: AppColors.mainBlue,
|
||||
onChanged: (value) => vm.setStatus(value!),
|
||||
onChanged:
|
||||
(value) => setState(() {
|
||||
this.status = value!;
|
||||
}),
|
||||
title: Text(
|
||||
status,
|
||||
style: TextStyle(
|
||||
@@ -184,7 +226,7 @@ class TendersFilterDialog extends StatelessWidget {
|
||||
isEnabled: true,
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
vm.callWithSortAndStatus();
|
||||
widget.onFilterChanged(dateRange, status);
|
||||
},
|
||||
text: TendersStrings.confirm,
|
||||
textColor: AppColors.textBlue,
|
||||
@@ -197,7 +239,5 @@ class TendersFilterDialog extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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:tm_app/core/constants/assets.dart';
|
||||
import 'package:tm_app/core/theme/colors.dart';
|
||||
import 'package:tm_app/core/utils/size_config.dart';
|
||||
@@ -10,13 +9,30 @@ import 'package:tm_app/views/tenders/strings/tenders_strings.dart';
|
||||
|
||||
import '../../../view_models/tenders_view_model.dart';
|
||||
|
||||
class TendersSortDialog extends StatelessWidget {
|
||||
const TendersSortDialog({super.key});
|
||||
class TendersSortDialog extends StatefulWidget {
|
||||
const TendersSortDialog({
|
||||
required this.viewModel,
|
||||
required this.onConfirm,
|
||||
super.key,
|
||||
});
|
||||
final TendersViewModel viewModel;
|
||||
final ValueChanged<String> onConfirm;
|
||||
|
||||
@override
|
||||
State<TendersSortDialog> createState() => _TendersSortDialogState();
|
||||
}
|
||||
|
||||
class _TendersSortDialogState extends State<TendersSortDialog> {
|
||||
String sort = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
sort = widget.viewModel.selectedSort;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<TendersViewModel>(
|
||||
builder: (context, vm, child) {
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
@@ -52,15 +68,17 @@ class TendersSortDialog extends StatelessWidget {
|
||||
SizedBox(height: 16.0.h()),
|
||||
Divider(color: AppColors.grey20),
|
||||
SizedBox(height: 20.0.h()),
|
||||
...[
|
||||
TendersStrings.minimumTime,
|
||||
TendersStrings.mostOfTheTime,
|
||||
].map((status) {
|
||||
...[TendersStrings.minimumTime, TendersStrings.mostOfTheTime].map((
|
||||
status,
|
||||
) {
|
||||
return RadioListTile(
|
||||
value: status,
|
||||
groupValue: vm.selectedSort,
|
||||
groupValue: sort,
|
||||
activeColor: AppColors.mainBlue,
|
||||
onChanged: (value) => vm.setSort(value!),
|
||||
onChanged:
|
||||
(value) => setState(() {
|
||||
sort = value!;
|
||||
}),
|
||||
title: Text(
|
||||
status,
|
||||
style: TextStyle(
|
||||
@@ -81,7 +99,7 @@ class TendersSortDialog extends StatelessWidget {
|
||||
isEnabled: true,
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
vm.callWithSortAndStatus();
|
||||
widget.onConfirm(sort);
|
||||
},
|
||||
text: TendersStrings.confirm,
|
||||
textColor: AppColors.textBlue,
|
||||
@@ -94,7 +112,5 @@ class TendersSortDialog extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ class _YourTendersMobilePageState extends State<YourTendersMobilePage> {
|
||||
return Column(
|
||||
children: [
|
||||
if (viewModel.selectedStatus ==
|
||||
YourTendersStrings.likeTenders &&
|
||||
TenderFeedback.liked.value &&
|
||||
viewModel.likedTendersData != null)
|
||||
Expanded(
|
||||
child: LikedDislikedTendersList(
|
||||
@@ -72,7 +72,7 @@ class _YourTendersMobilePageState extends State<YourTendersMobilePage> {
|
||||
),
|
||||
)
|
||||
else if (viewModel.selectedStatus ==
|
||||
YourTendersStrings.dislikeTenders &&
|
||||
TenderFeedback.disliked.value &&
|
||||
viewModel.likedTendersData != null)
|
||||
Expanded(
|
||||
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_tablet_page.dart';
|
||||
|
||||
import '../../../data/services/model/request_models/get_tenders_request_model/get_tenders_request_model.dart';
|
||||
|
||||
class YourTendersScreen extends StatefulWidget {
|
||||
const YourTendersScreen({super.key});
|
||||
const YourTendersScreen({required this.status, super.key});
|
||||
final String status;
|
||||
|
||||
@override
|
||||
State<YourTendersScreen> createState() => _YourTendersScreenState();
|
||||
@@ -23,8 +22,8 @@ class _YourTendersScreenState extends State<YourTendersScreen> {
|
||||
super.initState();
|
||||
viewModel = context.read<YourTendersViewModel>();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final requestModel = GetTendersRequestModel();
|
||||
viewModel.getTenders(requestModel: requestModel, reset: true);
|
||||
viewModel.setStatus(widget.status);
|
||||
viewModel.callWithFilter();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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,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/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,
|
||||
});
|
||||
|
||||
@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 {
|
||||
final picked = await showDateRangePicker(
|
||||
context: context,
|
||||
firstDate: DateTime(2020),
|
||||
lastDate: DateTime(2035),
|
||||
initialDateRange: viewModel.selectedDateRange,
|
||||
firstDate: DateTime(2000),
|
||||
lastDate: DateTime(2050),
|
||||
initialDateRange: dateRange,
|
||||
);
|
||||
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),
|
||||
onTap: pick,
|
||||
child: Container(
|
||||
@@ -64,9 +161,9 @@ class YourTendersFilterDialog extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
start == null
|
||||
dateRange?.start == null
|
||||
? YourTendersStrings.startDate
|
||||
: viewModel.startDateFormatted,
|
||||
: startDateFormatted,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0.sp(),
|
||||
fontWeight: FontWeight.w400,
|
||||
@@ -74,9 +171,9 @@ class YourTendersFilterDialog extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
end == null
|
||||
dateRange?.end == null
|
||||
? YourTendersStrings.endDate
|
||||
: viewModel.endDateFormatted,
|
||||
: endDateFormatted,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0.sp(),
|
||||
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()),
|
||||
Text(
|
||||
YourTendersStrings.status,
|
||||
@@ -142,9 +205,9 @@ class YourTendersFilterDialog extends StatelessWidget {
|
||||
].map((status) {
|
||||
return RadioListTile(
|
||||
value: status,
|
||||
groupValue: vm.selectedStatus,
|
||||
groupValue: this.status,
|
||||
activeColor: AppColors.mainBlue,
|
||||
onChanged: (value) => vm.setStatus(value!),
|
||||
onChanged: (value) => setStatus(value!),
|
||||
title: Text(
|
||||
status,
|
||||
style: TextStyle(
|
||||
@@ -162,7 +225,10 @@ class YourTendersFilterDialog extends StatelessWidget {
|
||||
isEnabled: true,
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
vm.callWithFilter();
|
||||
widget.onFilterChanged(
|
||||
dateRange,
|
||||
_mapDisplayStringToApiValue(status!),
|
||||
);
|
||||
},
|
||||
text: YourTendersStrings.confirm,
|
||||
textColor: AppColors.textBlue,
|
||||
@@ -173,7 +239,5 @@ class YourTendersFilterDialog extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
////////////////////////////
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:tm_app/data/services/model/error/error_model.dart';
|
||||
|
||||
@@ -41,9 +39,7 @@ void main() {
|
||||
details: 'Server error',
|
||||
);
|
||||
|
||||
final updated = error.copyWith(
|
||||
message: 'Timeout error',
|
||||
);
|
||||
final updated = error.copyWith(message: 'Timeout error');
|
||||
|
||||
expect(updated.message, 'Timeout error');
|
||||
expect(updated.code, '500');
|
||||
@@ -51,11 +47,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('fromJson handles null fields safely', () {
|
||||
final json = {
|
||||
'message': null,
|
||||
'code': null,
|
||||
'details': null,
|
||||
};
|
||||
final json = {'message': null, 'code': null, 'details': null};
|
||||
|
||||
final error = ErrorModel.fromJson(json);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user