search logic added to desktop tenders
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tm_app/core/utils/breakpoints.dart';
|
||||
|
||||
/// Utility class for device and platform detection
|
||||
class DeviceUtils {
|
||||
/// Check if the current context represents a desktop screen
|
||||
static bool isDesktop(BuildContext context) {
|
||||
final size = MediaQuery.sizeOf(context);
|
||||
return size.displaySize == DisplaySize.medium ||
|
||||
size.displaySize == DisplaySize.large;
|
||||
}
|
||||
|
||||
/// Check if the current context represents a mobile screen
|
||||
static bool isMobile(BuildContext context) {
|
||||
final size = MediaQuery.sizeOf(context);
|
||||
return size.displaySize == DisplaySize.extraSmall;
|
||||
}
|
||||
|
||||
/// Check if the current context represents a tablet screen
|
||||
static bool isTablet(BuildContext context) {
|
||||
final size = MediaQuery.sizeOf(context);
|
||||
return size.displaySize == DisplaySize.small;
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,13 @@ class TendersRepository {
|
||||
Future<Result<TendersResponse>> getTenders({
|
||||
required int limit,
|
||||
required int offset,
|
||||
String? query,
|
||||
}) async {
|
||||
return _tendersService.getTenders(limit: limit, offset: offset);
|
||||
return _tendersService.getTenders(
|
||||
limit: limit,
|
||||
offset: offset,
|
||||
query: query,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Result<FeedbackResponse>> getTenderFeedback({
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
class TendersApi {
|
||||
static const String tenders = '/api/v1/tenders';
|
||||
static String getTenders({required int limit, required int offset}) {
|
||||
return '$tenders?limit=$limit&offset=$offset';
|
||||
static String getTenders({
|
||||
required int limit,
|
||||
required int offset,
|
||||
String? query,
|
||||
}) {
|
||||
String url = '$tenders?limit=$limit&offset=$offset';
|
||||
if (query != null && query.isNotEmpty) {
|
||||
url += '&q=${Uri.encodeComponent(query)}';
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
static const String feedbackTender = '/api/v1/feedback/tender';
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import '../../meta/meta.dart';
|
||||
import '../../tender_data/tender_data.dart';
|
||||
|
||||
part 'tenders_data.freezed.dart';
|
||||
@@ -10,9 +9,8 @@ part 'tenders_data.g.dart';
|
||||
|
||||
@freezed
|
||||
abstract class TendersData with _$TendersData {
|
||||
const factory TendersData({
|
||||
required List<TenderData>? tenders,
|
||||
}) = _TendersData;
|
||||
const factory TendersData({required List<TenderData>? tenders}) =
|
||||
_TendersData;
|
||||
|
||||
factory TendersData.fromJson(Map<String, Object?> json) =>
|
||||
_$TendersDataFromJson(json);
|
||||
|
||||
@@ -17,9 +17,10 @@ class TendersService {
|
||||
Future<Result<TendersResponse>> getTenders({
|
||||
required int limit,
|
||||
required int offset,
|
||||
String? query,
|
||||
}) async {
|
||||
final result = await _networkManager.makeRequest(
|
||||
TendersApi.getTenders(limit: limit, offset: offset),
|
||||
TendersApi.getTenders(limit: limit, offset: offset, query: query),
|
||||
method: 'GET',
|
||||
(json) => TendersResponse.fromJson(json),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tm_app/core/utils/device_utils.dart';
|
||||
import 'package:tm_app/data/repositories/tenders_repository.dart';
|
||||
import 'package:tm_app/data/services/model/feedback_data/feedback_data.dart';
|
||||
import 'package:tm_app/data/services/model/feedback_response/feedback_response.dart';
|
||||
@@ -22,11 +23,7 @@ class TendersViewModel with ChangeNotifier {
|
||||
required TendersRepository tendersRepository,
|
||||
required HomeViewModel homeViewModel,
|
||||
}) : _tendersRepository = tendersRepository,
|
||||
_homeViewModel = homeViewModel {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
getTenders();
|
||||
});
|
||||
}
|
||||
_homeViewModel = homeViewModel;
|
||||
|
||||
bool _isLoading = false;
|
||||
bool _isLoadingMore = false;
|
||||
@@ -37,6 +34,7 @@ class TendersViewModel with ChangeNotifier {
|
||||
FeedbackData? _feedbackData;
|
||||
TenderApprovalsData? _tenderApprovalData;
|
||||
TenderApprovalsToggleData? _tenderApprovalToggleData;
|
||||
String? _searchQuery;
|
||||
|
||||
bool get isLoading => _isLoading;
|
||||
bool get isLoadingMore => _isLoadingMore;
|
||||
@@ -48,6 +46,7 @@ class TendersViewModel with ChangeNotifier {
|
||||
TenderApprovalsData? get tenderApprovalData => _tenderApprovalData;
|
||||
TenderApprovalsToggleData? get tenderApprovalToggleData =>
|
||||
_tenderApprovalToggleData;
|
||||
String? get searchQuery => _searchQuery;
|
||||
|
||||
List<FeedbackData> feedbacks = [];
|
||||
List<TenderApprovalsData> tenderApprovals = [];
|
||||
@@ -95,17 +94,27 @@ class TendersViewModel with ChangeNotifier {
|
||||
return approval?.status;
|
||||
}
|
||||
|
||||
Future<void> getTenders() async {
|
||||
Future<void> getTenders({String? query, BuildContext? context}) async {
|
||||
if (context != null && DeviceUtils.isDesktop(context)) {
|
||||
await getTendersDesktop(query: query);
|
||||
} else {
|
||||
await getTendersMobile(query: query);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> getTendersMobile({String? query}) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
clearAll(); // Clear all data before fetching new tenders
|
||||
|
||||
_currentOffset = 0;
|
||||
_hasMoreData = true;
|
||||
_searchQuery = query;
|
||||
|
||||
final result = await _tendersRepository.getTenders(
|
||||
limit: _pageSize,
|
||||
offset: _currentOffset,
|
||||
query: query,
|
||||
);
|
||||
switch (result) {
|
||||
case Ok<TendersResponse>():
|
||||
@@ -138,6 +147,52 @@ class TendersViewModel with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> getTendersDesktop({String? query}) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
clearAll(); // Clear all data before fetching new tenders
|
||||
|
||||
_currentOffset = 0;
|
||||
_hasMoreData = true;
|
||||
_searchQuery = query;
|
||||
|
||||
final result = await _tendersRepository.getTenders(
|
||||
limit: _pageSize,
|
||||
offset: _currentOffset,
|
||||
query: query,
|
||||
);
|
||||
switch (result) {
|
||||
case Ok<TendersResponse>():
|
||||
_tendersResponse = result.value;
|
||||
// Check if we have more data
|
||||
if (_tendersResponse?.data?.tenders != null) {
|
||||
_hasMoreData = _tendersResponse!.data!.tenders!.length >= _pageSize;
|
||||
}
|
||||
if (_tendersResponse?.data?.tenders?.isNotEmpty == true) {
|
||||
// Fetch feedback and approvals for the first tender only
|
||||
// getTenderFeedback(_tendersResponse!.data!.tenders!.first.id!);
|
||||
// getTenderApprovals(_tendersResponse!.data!.tenders!.first.id!);
|
||||
for (var tender in _tendersResponse!.data!.tenders!) {
|
||||
getTenderFeedback(tender.id!);
|
||||
}
|
||||
}
|
||||
if (_tendersResponse?.meta?.pages != null) {
|
||||
_totalPages = _tendersResponse!.meta!.pages!;
|
||||
} else {
|
||||
_totalPages = 1;
|
||||
}
|
||||
notifyListeners();
|
||||
break;
|
||||
case Error<TendersResponse>():
|
||||
_errorMessage = result.error.toString();
|
||||
break;
|
||||
}
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> loadMoreTenders() async {
|
||||
if (_isLoadingMore || !_hasMoreData) {
|
||||
return;
|
||||
@@ -151,6 +206,7 @@ class TendersViewModel with ChangeNotifier {
|
||||
final result = await _tendersRepository.getTenders(
|
||||
limit: _pageSize,
|
||||
offset: _currentOffset,
|
||||
query: _searchQuery,
|
||||
);
|
||||
|
||||
switch (result) {
|
||||
@@ -500,6 +556,15 @@ class TendersViewModel with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Search tenders with query
|
||||
Future<void> searchTenders(String query) async {
|
||||
if (query.trim().isEmpty) {
|
||||
await getTendersDesktop();
|
||||
} else {
|
||||
await getTendersDesktop(query: query.trim());
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all data (tenders, feedbacks, and approvals)
|
||||
void clearAll() {
|
||||
clearTenders();
|
||||
@@ -508,37 +573,37 @@ class TendersViewModel with ChangeNotifier {
|
||||
_tenderApprovalData = null;
|
||||
_tenderApprovalToggleData = null;
|
||||
_tenderApprovalErrorMessage = null;
|
||||
_searchQuery = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void jumpToPage(int page) async {
|
||||
final meta = tendersResponse?.meta;
|
||||
if (meta?.pages == null) {
|
||||
return;
|
||||
void jumpToPage(int page) async {
|
||||
final meta = tendersResponse?.meta;
|
||||
if (meta?.pages == null) {
|
||||
return;
|
||||
}
|
||||
if (page < 1 || page > meta!.pages!) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentPage = page;
|
||||
_currentOffset = (page - 1) * _pageSize;
|
||||
|
||||
final result = await _tendersRepository.getTenders(
|
||||
limit: _pageSize,
|
||||
offset: _currentOffset,
|
||||
query: _searchQuery,
|
||||
);
|
||||
|
||||
switch (result) {
|
||||
case Ok<TendersResponse>():
|
||||
_tendersResponse = result.value;
|
||||
break;
|
||||
case Error<TendersResponse>():
|
||||
_errorMessage = result.error.toString();
|
||||
break;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
if (page < 1 || page > meta!.pages!) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentPage = page;
|
||||
_currentOffset = (page - 1) * _pageSize;
|
||||
|
||||
final result = await _tendersRepository.getTenders(
|
||||
limit: _pageSize,
|
||||
offset: _currentOffset,
|
||||
);
|
||||
|
||||
switch (result) {
|
||||
case Ok<TendersResponse>():
|
||||
_tendersResponse = result.value;
|
||||
break;
|
||||
case Error<TendersResponse>():
|
||||
_errorMessage = result.error.toString();
|
||||
break;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -61,7 +63,9 @@ class _DesktopTendersPageState extends State<DesktopTendersPage> {
|
||||
builder: (context, vm, child) {
|
||||
if (vm.isLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(color: AppColors.jellyBean),
|
||||
child: CircularProgressIndicator(
|
||||
color: AppColors.jellyBean,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (vm.errorMessage != null) {
|
||||
@@ -105,18 +109,43 @@ class _DesktopTendersPageState extends State<DesktopTendersPage> {
|
||||
}
|
||||
}
|
||||
|
||||
class _SearchBox extends StatelessWidget {
|
||||
class _SearchBox extends StatefulWidget {
|
||||
const _SearchBox();
|
||||
|
||||
@override
|
||||
State<_SearchBox> createState() => _SearchBoxState();
|
||||
}
|
||||
|
||||
class _SearchBoxState extends State<_SearchBox> {
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
Timer? _debounceTimer;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
_debounceTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onSearchChanged(String query) {
|
||||
_debounceTimer?.cancel();
|
||||
_debounceTimer = Timer(const Duration(milliseconds: 500), () {
|
||||
final viewModel = context.read<TendersViewModel>();
|
||||
viewModel.searchTenders(query);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 48.0.h(),
|
||||
width: 680,
|
||||
child: TextField(
|
||||
style: TextStyle(fontSize: 16.0.sp(), color: AppColors.grey0),
|
||||
controller: _searchController,
|
||||
onChanged: _onSearchChanged,
|
||||
style: TextStyle(fontSize: 16.0.sp(), color: AppColors.grey60),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search',
|
||||
hintText: TendersStrings.search,
|
||||
hintStyle: TextStyle(fontSize: 16.0.sp(), color: AppColors.grey60),
|
||||
prefixIcon: Padding(
|
||||
padding: EdgeInsets.all(12.0.w()),
|
||||
@@ -221,8 +250,8 @@ class _DesktopPagination extends StatelessWidget {
|
||||
onTap: () async {
|
||||
final selectedPage = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
PageSelectionDialog(totalPages: totalPages),
|
||||
builder:
|
||||
(context) => PageSelectionDialog(totalPages: totalPages),
|
||||
);
|
||||
if (selectedPage != null) {
|
||||
onPageSelected(selectedPage);
|
||||
|
||||
@@ -1,13 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tm_app/views/shared/responsive_builder.dart';
|
||||
|
||||
import '../../../view_models/tenders_view_model.dart';
|
||||
import 'd_tenders_page.dart';
|
||||
import 'm_tenders_page.dart';
|
||||
import 't_tenders_page.dart';
|
||||
|
||||
class TendersScreen extends StatelessWidget {
|
||||
class TendersScreen extends StatefulWidget {
|
||||
const TendersScreen({super.key});
|
||||
|
||||
@override
|
||||
State<TendersScreen> createState() => _TendersScreenState();
|
||||
}
|
||||
|
||||
class _TendersScreenState extends State<TendersScreen> {
|
||||
late final TendersViewModel viewModel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
viewModel = context.read<TendersViewModel>();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
viewModel.getTenders(context: context);
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const ResponsiveBuilder(
|
||||
|
||||
@@ -10,6 +10,7 @@ class TendersStrings {
|
||||
static const String tenderDeadlineLabel = 'Deadline';
|
||||
static const String contracts = 'Contracts';
|
||||
static const String notifications = 'Notifications';
|
||||
static const String of= 'Of';
|
||||
static const String of = 'Of';
|
||||
static const String page = 'Page';
|
||||
static const String search = 'Search tenders...';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user