diff --git a/assets/icons/trailing_icon.svg b/assets/icons/trailing_icon.svg new file mode 100644 index 0000000..d7c01ac --- /dev/null +++ b/assets/icons/trailing_icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/core/constants/assets.dart b/lib/core/constants/assets.dart index 9ca138f..deccf2c 100644 --- a/lib/core/constants/assets.dart +++ b/lib/core/constants/assets.dart @@ -51,6 +51,7 @@ class AssetsManager { static const tickCircle = 'assets/icons/tick-circle.svg'; static const arrowLeft = 'assets/svgs/arrow_left.svg'; static const export = 'assets/svgs/export.svg'; + static const trailingIcon = 'assets/icons/trailing_icon.svg'; //profile page static const sun = 'assets/icons/sun.svg'; diff --git a/lib/core/constants/strings.dart b/lib/core/constants/strings.dart index abbff66..ad77dff 100644 --- a/lib/core/constants/strings.dart +++ b/lib/core/constants/strings.dart @@ -62,6 +62,17 @@ class AppStrings { //your tenders static const String yourTendersTitle = 'Your tenders'; + static const filter = 'Filter'; + static const selectDateRange = 'Select date range'; + static const status = 'Status'; + static const all = 'All'; + static const likeTenders = 'Like tenders'; + static const dislikeTenders = 'Dislike tenders'; + static const tendersSubmitted = 'Tenders submitted'; + static const confirm = 'Confirm'; + static const startDate = 'YYYY/MM/DD'; + static const endDate = 'YYYY/MM/DD'; + // Tender Detail Page static const String tenderDetailTitle = 'Tender Detail'; diff --git a/lib/view_models/your_tenders_view_model.dart b/lib/view_models/your_tenders_view_model.dart index f503129..fb49e5e 100644 --- a/lib/view_models/your_tenders_view_model.dart +++ b/lib/view_models/your_tenders_view_model.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; import 'package:tm_app/core/utils/result.dart'; import 'package:tm_app/data/repositories/your_tenders_repository.dart'; import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart'; @@ -13,6 +14,17 @@ class YourTendersViewModel with ChangeNotifier { String? errorMessage; TenderApprovalsResponse? data; + // Filter States + DateTimeRange? selectedDateRange; + String selectedStatus = 'All'; + final _fmt = DateFormat('yyyy/MM/dd'); + + String get startDateFormatted => + selectedDateRange?.start != null ? _fmt.format(selectedDateRange!.start) : 'YYYY/MM/DD'; + + String get endDateFormatted => + selectedDateRange?.end != null ? _fmt.format(selectedDateRange!.end) : 'YYYY/MM/DD'; + Future getApprovedTenders() async { isLoading = true; notifyListeners(); @@ -32,4 +44,15 @@ class YourTendersViewModel with ChangeNotifier { isLoading = false; notifyListeners(); } + + // --- Filter Actions --- + void setDateRange(DateTimeRange? range) { + selectedDateRange = range; + notifyListeners(); + } + + void setStatus(String status) { + selectedStatus = status; + notifyListeners(); + } } diff --git a/lib/views/your_tenders/pages/your_tenders_desktop_page.dart b/lib/views/your_tenders/pages/your_tenders_desktop_page.dart index 562d807..1eb932b 100644 --- a/lib/views/your_tenders/pages/your_tenders_desktop_page.dart +++ b/lib/views/your_tenders/pages/your_tenders_desktop_page.dart @@ -1,11 +1,9 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; -import 'package:tm_app/core/constants/strings.dart'; import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/view_models/your_tenders_view_model.dart'; -import 'package:tm_app/views/shared/main_tab_bar.dart'; -import 'package:tm_app/views/your_tenders/widgets/approved_tenders.dart'; +import 'package:tm_app/views/your_tenders/widgets/filter_button.dart'; import 'package:tm_app/views/your_tenders/widgets/tablet_desktop_appbar.dart'; import 'package:tm_app/views/your_tenders/widgets/tenders_submitted.dart'; @@ -73,13 +71,16 @@ class _YourTendersDesktopPageState extends State SizedBox(height: 64.0), TabletDesktopAppbar(), SizedBox(height: 20.0), - MainTabBar( - controller: controller, - titles: [ - AppStrings.approvedTenders, - AppStrings.tenderSubmitted, - ], - ), + + // MainTabBar( + // controller: controller, + // titles: [ + // AppStrings.approvedTenders, + // AppStrings.tenderSubmitted, + // ], + // ), + FilterButton(platformType: PlatformType.tabletDesktop), + Expanded( child: Consumer( builder: (context, viewModel, child) { @@ -98,7 +99,7 @@ class _YourTendersDesktopPageState extends State return TabBarView( controller: controller, children: [ - ApprovedTenders(tendersSubmitted: []), + // ApprovedTenders(tendersSubmitted: []), TendersSubmitted(approvedTenders: viewModel.data!), ], ); diff --git a/lib/views/your_tenders/pages/your_tenders_mobile_page.dart b/lib/views/your_tenders/pages/your_tenders_mobile_page.dart index 2e91d6a..657741e 100644 --- a/lib/views/your_tenders/pages/your_tenders_mobile_page.dart +++ b/lib/views/your_tenders/pages/your_tenders_mobile_page.dart @@ -3,38 +3,13 @@ import 'package:provider/provider.dart'; import 'package:tm_app/core/constants/strings.dart'; import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/view_models/your_tenders_view_model.dart'; -import 'package:tm_app/views/shared/main_tab_bar.dart'; import 'package:tm_app/views/shared/tender_app_bar.dart'; -import 'package:tm_app/views/your_tenders/widgets/approved_tenders.dart'; +import 'package:tm_app/views/your_tenders/widgets/filter_button.dart'; import 'package:tm_app/views/your_tenders/widgets/tenders_submitted.dart'; -class YourTendersMobilePage extends StatefulWidget { +class YourTendersMobilePage extends StatelessWidget { const YourTendersMobilePage({super.key}); - @override - State createState() => _YourTendersMobilePageState(); -} - -class _YourTendersMobilePageState extends State - with SingleTickerProviderStateMixin { - late TabController controller; - - @override - void initState() { - super.initState(); - controller = TabController(length: 2, vsync: this); - - WidgetsBinding.instance.addPostFrameCallback((_) { - context.read().getApprovedTenders(); - }); - } - - @override - void dispose() { - controller.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -42,10 +17,7 @@ class _YourTendersMobilePageState extends State appBar: appBar(context: context, title: AppStrings.yourTenders), body: Column( children: [ - MainTabBar( - controller: controller, - titles: [AppStrings.approvedTenders, AppStrings.tenderSubmitted], - ), + FilterButton(platformType: PlatformType.mobile), Expanded( child: Consumer( builder: (context, viewModel, child) { @@ -61,11 +33,12 @@ class _YourTendersMobilePageState extends State return Center(child: Text(viewModel.errorMessage!)); } - return TabBarView( - controller: controller, + return Column( children: [ - ApprovedTenders(tendersSubmitted: []), - TendersSubmitted(approvedTenders: viewModel.data!), + // Expanded(child: ApprovedTenders(tendersSubmitted: [])), + Expanded( + child: TendersSubmitted(approvedTenders: viewModel.data!), + ), ], ); }, diff --git a/lib/views/your_tenders/pages/your_tenders_tablet_page.dart b/lib/views/your_tenders/pages/your_tenders_tablet_page.dart index c6e319e..9e4522e 100644 --- a/lib/views/your_tenders/pages/your_tenders_tablet_page.dart +++ b/lib/views/your_tenders/pages/your_tenders_tablet_page.dart @@ -2,12 +2,10 @@ 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/strings.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'; -import 'package:tm_app/views/shared/main_tab_bar.dart'; -import 'package:tm_app/views/your_tenders/widgets/approved_tenders.dart'; +import 'package:tm_app/views/your_tenders/widgets/filter_button.dart'; import 'package:tm_app/views/your_tenders/widgets/tablet_desktop_appbar.dart'; import 'package:tm_app/views/your_tenders/widgets/tenders_submitted.dart'; @@ -101,13 +99,15 @@ class _YourTendersTabletPageState extends State child: TabletDesktopAppbar(), ), SizedBox(height: 24.0.h()), - MainTabBar( - controller: controller, - titles: [ - AppStrings.approvedTenders, - AppStrings.tenderSubmitted, - ], - ), + // MainTabBar( + // controller: controller, + // titles: [ + // AppStrings.approvedTenders, + // AppStrings.tenderSubmitted, + // ], + // ), + FilterButton(platformType: PlatformType.tabletDesktop), + Expanded( child: Consumer( builder: (context, viewModel, child) { @@ -126,7 +126,7 @@ class _YourTendersTabletPageState extends State return TabBarView( controller: controller, children: [ - ApprovedTenders(tendersSubmitted: []), + // ApprovedTenders(tendersSubmitted: []), TendersSubmitted(approvedTenders: viewModel.data!), ], ); diff --git a/lib/views/your_tenders/widgets/filter_button.dart b/lib/views/your_tenders/widgets/filter_button.dart new file mode 100644 index 0000000..9a22d9b --- /dev/null +++ b/lib/views/your_tenders/widgets/filter_button.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; + +import 'tenders_filter_dialog.dart'; + +enum PlatformType { mobile, tabletDesktop } + +class FilterButton extends StatelessWidget { + final PlatformType platformType; + const FilterButton({required this.platformType, super.key}); + + void _openFilter(BuildContext context) { + if (platformType == PlatformType.mobile) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.white, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + builder: (_) => const TendersFilterDialog(), + ); + } else { + showDialog( + context: context, + builder: + (_) => Dialog( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: const TendersFilterDialog(), + ), + ); + } + } + + @override + Widget build(BuildContext context) { + // Mobile style: container with icon and text + return InkWell( + onTap: () => _openFilter(context), + child: Container( + margin: const EdgeInsets.all(12), + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: Colors.blue.shade50, + borderRadius: BorderRadius.circular(12), + ), + child: const Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.filter_list, color: Colors.blue), + SizedBox(width: 8), + Text( + 'Filter', + style: TextStyle( + color: Colors.blue, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/your_tenders/widgets/tenders_filter_dialog.dart b/lib/views/your_tenders/widgets/tenders_filter_dialog.dart new file mode 100644 index 0000000..cfdbdf8 --- /dev/null +++ b/lib/views/your_tenders/widgets/tenders_filter_dialog.dart @@ -0,0 +1,173 @@ +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'; +import 'package:tm_app/view_models/your_tenders_view_model.dart'; +import 'package:tm_app/views/shared/base_button.dart'; + +class TendersFilterDialog extends StatelessWidget { + const TendersFilterDialog({super.key}); + + Widget _dateRangePill(BuildContext context) { + final viewModel = context.watch(); + final start = viewModel.selectedDateRange?.start; + final end = viewModel.selectedDateRange?.end; + + Future pick() async { + final picked = await showDateRangePicker( + context: context, + firstDate: DateTime(2020), + lastDate: DateTime(2035), + initialDateRange: viewModel.selectedDateRange, + ); + if (picked != null) { + viewModel.setDateRange(picked); + } + } + + 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 ? 'YYYY/MM/DD' : viewModel.startDateFormatted, + style: TextStyle( + fontSize: 16.0.sp(), + fontWeight: FontWeight.w400, + color: AppColors.grey70, + ), + ), + Text( + end == null ? 'YYYY/MM/DD' : viewModel.endDateFormatted, + style: TextStyle( + fontSize: 16.0.sp(), + fontWeight: FontWeight.w400, + color: AppColors.grey70, + ), + ), + ], + ), + ], + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Consumer( + 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( + 'Filter', + style: TextStyle( + fontSize: 20.0.sp(), + fontWeight: FontWeight.w700, + color: AppColors.grey70, + ), + ), + ), + SizedBox(height: 16.0.h()), + Text( + 'Select date range', + 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( + 'Status', + style: TextStyle( + fontSize: 16.0.sp(), + fontWeight: FontWeight.w600, + color: AppColors.grey70, + ), + ), + ...[ + 'All', + 'Like tenders', + 'Dislike tenders', + 'Approved tenders', + 'Tenders submitted', + ].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(); + }, + text: 'Confirm', + textColor: AppColors.mainBlue, + backgroundColor: AppColors.primary30, + ), + ), + ], + ), + ), + ); + }, + ); + } +}