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,
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ );
+ }
+}