feat: add AI recommendations flow for tenders
Implement company AI onboarding/recommendation models, services, repository, and tender UI integration with supporting tests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,8 +11,10 @@ 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';
|
||||
@@ -28,6 +30,9 @@ class DesktopTendersPage extends StatefulWidget {
|
||||
class _DesktopTendersPageState extends State<DesktopTendersPage> {
|
||||
late final TendersViewModel viewModel;
|
||||
|
||||
/// 0 = All, 1 = Recommended.
|
||||
int _selectedTab = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -50,47 +55,34 @@ class _DesktopTendersPageState extends State<DesktopTendersPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final userRole = context.watch<AuthViewModel>().userRole;
|
||||
final bool isAllTab = _selectedTab == 0;
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.backgroundColor,
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
const DesktopNavigationWidget(currentIndex: 1),
|
||||
SizedBox(height: 48.0.h()),
|
||||
if (userRole == Role.analyst) const _SearchBox(),
|
||||
if (userRole == Role.analyst) const _ActionButtons(),
|
||||
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: Consumer<TendersViewModel>(
|
||||
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,
|
||||
child:
|
||||
isAllTab
|
||||
? const _AllTendersTab()
|
||||
: const Center(
|
||||
child: SizedBox(
|
||||
width: 740,
|
||||
child: RecommendedTendersView(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -99,6 +91,40 @@ class _DesktopTendersPageState extends State<DesktopTendersPage> {
|
||||
}
|
||||
}
|
||||
|
||||
class _AllTendersTab extends StatelessWidget {
|
||||
const _AllTendersTab();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<TendersViewModel>(
|
||||
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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user