import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.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/tenders_view_model.dart'; import 'package:tm_app/views/shared/base_button.dart'; import 'package:tm_app/views/shared/desktop_navigation_widget.dart'; import 'package:tm_app/views/tenders/strings/tenders_strings.dart'; import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart'; import 'package:tm_app/views/tenders/widgets/recommended_tenders_view.dart'; import 'package:tm_app/views/tenders/widgets/tenders_filter_dialog.dart'; import 'package:tm_app/views/tenders/widgets/tenders_sort_dialog.dart'; import 'package:tm_app/views/tenders/widgets/tenders_tab_bar.dart'; import '../../../core/constants/common_strings.dart'; import '../../../core/utils/app_toast.dart'; import '../../../view_models/auth_view_model.dart'; class DesktopTendersPage extends StatefulWidget { const DesktopTendersPage({required this.initialSelectedTab, super.key}); final int initialSelectedTab; @override State createState() => _DesktopTendersPageState(); } class _DesktopTendersPageState extends State { late final TendersViewModel viewModel; /// 0 = All, 1 = Recommended. int _selectedTab = 0; @override void initState() { super.initState(); _selectedTab = widget.initialSelectedTab; viewModel = context.read(); viewModel.addListener(_viewModelListener); } @override void didUpdateWidget(covariant DesktopTendersPage oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.initialSelectedTab != widget.initialSelectedTab) { _selectedTab = widget.initialSelectedTab; } } void _viewModelListener() { if (viewModel.feedbackErrorMessage != null) { AppToast.error(context, viewModel.feedbackErrorMessage!); } } @override void dispose() { viewModel.removeListener(_viewModelListener); super.dispose(); } @override Widget build(BuildContext context) { final userRole = context.watch().userRole; final bool isAllTab = _selectedTab == 0; return Scaffold( backgroundColor: AppColors.backgroundColor, body: SafeArea( child: Column( children: [ const DesktopNavigationWidget(currentIndex: 1), SizedBox(height: 24.0.h()), SizedBox( width: 740, child: TendersTabBar( selectedIndex: _selectedTab, onChanged: (index) => setState(() => _selectedTab = index), ), ), SizedBox(height: 24.0.h()), if (isAllTab && userRole == Role.analyst) const _SearchBox(), if (isAllTab && userRole == Role.analyst) const _ActionButtons(), Expanded( child: isAllTab ? const _AllTendersTab() : const Center( child: SizedBox( width: 740, child: RecommendedTendersView(), ), ), ), ], ), ), ); } } class _AllTendersTab extends StatelessWidget { const _AllTendersTab(); @override Widget build(BuildContext context) { return Consumer( builder: (context, vm, child) { if (vm.isLoading) { return const Center( child: CircularProgressIndicator(color: AppColors.jellyBean), ); } if (vm.errorMessage != null) { return Center(child: Text(vm.errorMessage!)); } final tenders = vm.tendersResponse?.data?.tenders ?? []; if (tenders.isEmpty) { return const Center(child: Text(CommonStrings.noData)); } return Center( child: SizedBox( width: 740, child: Padding( padding: EdgeInsets.symmetric(vertical: 4.0.h()), child: MainTendersSlider(tenders: tenders, isDesktop: true), ), ), ); }, ); } } 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(); viewModel.searchTenders(query); }); } @override Widget build(BuildContext context) { return SizedBox( height: 48.0.h(), width: 680, child: TextField( controller: _searchController, onChanged: _onSearchChanged, style: TextStyle(fontSize: 16.0.sp(), color: AppColors.grey60), decoration: InputDecoration( hintText: TendersStrings.search, hintStyle: TextStyle(fontSize: 16.0.sp(), color: AppColors.grey60), prefixIcon: Padding( padding: EdgeInsets.all(12.0.w()), child: SvgPicture.asset( AssetsManager.normalSearch, width: 24.0.w(), height: 24.0.h(), ), ), filled: true, fillColor: AppColors.grey0, contentPadding: EdgeInsets.symmetric( horizontal: 12.0.w(), vertical: 12.0.h(), ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12.0.w()), borderSide: const BorderSide(color: AppColors.mainBlue, width: 1), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12.0.w()), borderSide: BorderSide(color: AppColors.grey40, width: 1), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12.0.w()), borderSide: const BorderSide(color: AppColors.grey, width: 1.5), ), ), ), ); } } class _ActionButtons extends StatelessWidget { const _ActionButtons(); @override Widget build(BuildContext context) { final viewModel = context.read(); return Container( margin: EdgeInsets.only(top: 25.0.h()), height: 40.0.h(), width: 680, child: Row( children: [ BaseButton( width: 178.0.w(), isEnabled: true, onPressed: () { showDialog( context: context, builder: (_) => Dialog( backgroundColor: AppColors.backgroundColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: SizedBox( width: 560, child: TendersFilterDialog( viewModel: viewModel, onFilterChanged: (dateRange, status) { viewModel.setDateRange(dateRange); viewModel.setStatus(status!); viewModel.callWithSortAndStatus(); }, ), ), ), ); }, text: TendersStrings.filter, icon: AssetsManager.filter, iconColor: AppColors.textBlue, backgroundColor: AppColors.primary20, textColor: AppColors.textBlue, ), SizedBox(width: 10.0.w()), BaseButton( width: 178.0.w(), isEnabled: true, onPressed: () { showDialog( context: context, builder: (_) => Dialog( backgroundColor: AppColors.backgroundColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: SizedBox( width: 560, child: TendersSortDialog( viewModel: viewModel, onConfirm: (sort) { viewModel.setSort(sort); viewModel.callWithSortAndStatus(); }, ), ), ), ); }, text: TendersStrings.sort, icon: AssetsManager.sort, iconColor: AppColors.textBlue, backgroundColor: AppColors.primary20, textColor: AppColors.textBlue, ), ], ), ); } }