feat(userPurchases): added user purchases, fix(consultations): fixed consultations bug

This commit is contained in:
Username1548
2025-06-09 14:01:05 +07:00
parent 086fd1cac4
commit 483cf6d8ee
12 changed files with 320 additions and 5 deletions

View File

@@ -17,6 +17,7 @@ import 'package:eatvio_app/features/consultant/become_consultant/presentation/pa
import 'package:eatvio_app/features/consultant/become_consultant/presentation/pages/stepper_become_consultant_screen.dart';
import 'package:eatvio_app/features/consultations/presentation/pages/consultations_screen.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart';
import 'package:eatvio_app/features/order_info/presentation/info_by_order_screen.dart';
import 'package:eatvio_app/features/create_diet/presentation/create_diet_screen.dart';
import 'package:eatvio_app/features/create_diet/presentation/diet_preview_screen.dart';
@@ -191,6 +192,11 @@ class AppRouter {
name: ConsultantFinancesScreen.name,
builder: (context, state) => const ConsultantFinancesScreen(),
),
GoRoute(
path: UserPurchasesScreen.path,
name: UserPurchasesScreen.name,
builder: (context, state) => const UserPurchasesScreen(),
),
GoRoute(
path: '/user-finance-screen',
name: 'userFinanceScreen',

View File

@@ -31,6 +31,7 @@ import 'package:eatvio_app/features/blog_post_create/repositories/blog_post_repo
import 'package:eatvio_app/features/blog_post_create/services/blog_post_service.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/consultant_finances/consultant_finances_cubit.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart';
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
import 'package:eatvio_app/features/calendar/presentation/bloc/calendar_cubit.dart';
@@ -149,6 +150,7 @@ Future<void> init() async {
sl.registerFactory<MyRecipesCubit>(() => MyRecipesCubit(myRecipesRepository: sl(), aboutRecipeRepository: sl()));
sl.registerFactory<ConsultationsArchiveCubit>(() => ConsultationsArchiveCubit(consultationsRepository: sl()));
sl.registerFactory<ConsultantFinancesCubit>(() => ConsultantFinancesCubit(consultationsRepository: sl()));
sl.registerFactory<UserPurchasesCubit>(() => UserPurchasesCubit(consultationsRepository: sl()));
// Repository
sl.registerLazySingleton<ChatRepository>(() => ChatRepository(chatService: sl()));

View File

@@ -185,6 +185,8 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
children: List.generate(widget.consultationsModel.duration, (index) {
final day = givenDaysWithDayNumber.firstWhereOrNull((e) => e.$1 == index);
final hasActivity = day != null && day.$2.state != DiaryStateEnum.init;
final canConsultantOpenThisDay = day != null &&
(day.$2.state != DiaryStateEnum.init && day.$2.state != DiaryStateEnum.started && day.$2.state != DiaryStateEnum.lastDayStarted);
return Padding(
padding: const EdgeInsets.only(right: 12),
child: Column(
@@ -192,7 +194,7 @@ class _ConsultationCardWidgetState extends State<ConsultationCardWidget> {
children: [
InkWell(
onTap: () {
if (!hasActivity) {
if (!hasActivity || !canConsultantOpenThisDay) {
showErrorDialog(context,
title: s.you_can_not_open_this_day, description: '${widget.consultationsModel.userName} ${s.has_not_filled_it_yet}');
return;

View File

@@ -3,6 +3,7 @@ import 'package:eatvio_app/features/consultations/models/consultation_model.dart
import 'package:eatvio_app/features/consultations/services/consultation_service.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/user_purchases_model.dart';
class ConsultationsRepository {
final ConsultationService consultationService;
@@ -45,10 +46,18 @@ class ConsultationsRepository {
}
Future<List<ConsultantFinanceModel>> getConsultantFinances(String? date, FiltersComparisonEnum? dateComparison) async {
try {
try {
return await consultationService.getConsultantFinances(date, dateComparison);
} catch (e) {
rethrow;
}
}
Future<List<UserPurchasesModel>> getUserPurchases(String? date, FiltersComparisonEnum? dateComparison) async {
try {
return await consultationService.getUserPurchases(date, dateComparison);
} catch (e) {
rethrow;
}
}
}

View File

@@ -3,6 +3,7 @@ import 'package:eatvio_app/core/enums/filters_comparison_enum.dart';
import 'package:eatvio_app/features/consultations/models/consultation_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/consultant_finance_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/statistic_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/models/user_purchases_model.dart';
class ConsultationService {
final Dio dio;
@@ -86,4 +87,23 @@ class ConsultationService {
rethrow;
}
}
Future<List<UserPurchasesModel>> getUserPurchases(
String? date,
FiltersComparisonEnum? dateComparison,
) async {
try {
final response = await dio.get(
'/api/finances/my_purchases',
queryParameters: {
if (dateComparison != null && date != null) 'filters[start_date]': date,
},
);
final data = response.data as Map<String, dynamic>;
final purchases = data['data'] as List<dynamic>;
return purchases.map((purchase) => UserPurchasesModel.fromJson(purchase as Map<String, dynamic>)).toList();
} catch (e) {
rethrow;
}
}
}

View File

@@ -0,0 +1,36 @@
import 'dart:developer';
import 'package:eatvio_app/core/enums/filters_comparison_enum.dart';
import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_state.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class UserPurchasesCubit extends Cubit<UserPurchasesState> {
final ConsultationsRepository consultationsRepository;
UserPurchasesCubit({
required this.consultationsRepository,
}) : super(
UserPurchasesState(),
);
Future<void> getUserPurchases() async {
try {
emit(state.copyWith(isLoading: true));
final purchasesModels = await consultationsRepository.getUserPurchases(
state.periodFilterStatus?.value,
state.periodFilterStatus == PeriodFilterStatus.all ? null : FiltersComparisonEnum.greaterThanOrEqual,
);
emit(state.copyWith(purchasesModels: purchasesModels, isLoading: false));
} catch (e) {
emit(state.copyWith(purchasesModels: [], isLoading: false));
log(e.toString());
}
}
Future<void> setPurchasesPeriod(PeriodFilterStatus period) async {
emit(state.copyWith(periodFilterStatus: period));
await getUserPurchases();
}
}

View File

@@ -0,0 +1,15 @@
import 'package:eatvio_app/features/finances_and_consultations_archive/models/user_purchases_model.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user_purchases_state.freezed.dart';
@freezed
abstract class UserPurchasesState with _$UserPurchasesState {
factory UserPurchasesState({
@Default([]) List<UserPurchasesModel> purchasesModels,
@Default(false) isLoading,
@Default(null) String? error,
@Default(null) PeriodFilterStatus? periodFilterStatus,
}) = _UserPurchasesState;
}

View File

@@ -0,0 +1,19 @@
import 'package:eatvio_app/features/feed/models/feed_blog_post_model.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user_purchases_model.freezed.dart';
part 'user_purchases_model.g.dart';
@freezed
abstract class UserPurchasesModel with _$UserPurchasesModel {
factory UserPurchasesModel({
@JsonKey(name: 'start_date', fromJson: parseDate) required DateTime startDate,
@JsonKey(name: 'bank_id') required String? bankId,
@JsonKey(name: 'title') required String title,
@JsonKey(name: 'consultantName') required String consultantName,
@JsonKey(name: 'duration') required int duration,
@JsonKey(name: 'price') required int paymentPrice,
}) = _UserPurchasesModel;
factory UserPurchasesModel.fromJson(Map<String, dynamic> json) => _$UserPurchasesModelFromJson(json);
}

View File

@@ -38,7 +38,10 @@ class _ConsultantFinancesScreenState extends State<ConsultantFinancesScreen> {
bottom: Radius.circular(12),
),
),
title: const Text('Архив консультаций'),
title: Text(
'Финансы',
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
centerTitle: true,
),
body: state.isLoading

View File

@@ -0,0 +1,200 @@
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/custom_text_style.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/core/utils/date_time_utils.dart';
import 'package:eatvio_app/core/utils/localization_utils.dart';
import 'package:eatvio_app/core/widgets/custom_drop_down_button.dart';
import 'package:eatvio_app/core/widgets/loader.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_cubit.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/cubit/user_purchases/user_purchases_state.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/period_enum.dart';
import 'package:eatvio_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class UserPurchasesScreen extends StatefulWidget {
const UserPurchasesScreen({super.key});
static const name = 'userPurchasesScreen';
static const path = '/user-purchases-screen';
@override
State<UserPurchasesScreen> createState() => _UserPurchasesScreen();
}
class _UserPurchasesScreen extends State<UserPurchasesScreen> {
@override
Widget build(BuildContext context) {
final s = S.of(context);
return BlocProvider<UserPurchasesCubit>(
create: (context) => sl<UserPurchasesCubit>()..getUserPurchases(),
child: BlocBuilder<UserPurchasesCubit, UserPurchasesState>(
builder: (context, state) {
return Scaffold(
backgroundColor: const Color(0xFFF0F2F5),
appBar: AppBar(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadiusDirectional.vertical(
bottom: Radius.circular(12),
),
),
title: Text(
'Финансы',
style: TextStyles.headingXLSemibold.copyWith(color: appTheme.black),
),
centerTitle: true,
),
body: state.isLoading
? const Center(
child: Loader(),
)
: SingleChildScrollView(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
children: [
const SizedBox(height: 8),
Material(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.only(left: 16, right: 16, top: 16),
child: SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Column(
children: [
CustomDropDownButton<PeriodFilterStatus>(
items: PeriodFilterStatus.values
.map(
(value) => DropdownMenuItem(
value: value,
child: Text(
value.name,
style: TextStyles.textMMedium.copyWith(color: appTheme.black),
),
),
)
.toList(),
onChanged: (value) {
if (value == null) return;
context.read<UserPurchasesCubit>().setPurchasesPeriod(value);
},
value: state.periodFilterStatus ?? PeriodFilterStatus.values.first,
),
const SizedBox(height: 12),
if (state.purchasesModels.isNotEmpty)
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return Material(
color: const Color(0xFFF6F6F6),
borderRadius: BorderRadius.circular(20),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Builder(builder: (context) {
// final idLen = state.purchasesModels[index].bankId.length;
final bankId = state.purchasesModels[index].bankId;
return Text(
'$bankId',
style: TextStyles.textSMedium.copyWith(color: appTheme.black),
);
}),
Text(
state.purchasesModels[index].startDate.format('yyyy.MM.dd'),
style: const TextStyle(
color: Color(0xFF6D6F71),
height: 1,
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 8),
Text(
state.purchasesModels[index].title,
style: TextStyle(
color: appTheme.blueA400,
height: 1,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 8),
Text(
'Продавец: ${state.purchasesModels[index].consultantName}',
style: const TextStyle(
color: Color(0xFF323F4B),
height: 1,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 8),
Text(
'${state.purchasesModels[index].duration} ${getDayForm(state.purchasesModels[index].duration, s)}',
style: const TextStyle(
color: Color(0xFF323F4B),
height: 1,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 8),
Container(
height: 1,
width: double.infinity,
decoration: const BoxDecoration(
color: Color(0xFFDEDEDF),
),
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${state.purchasesModels[index].paymentPrice} ₽',
style: const TextStyle(
color: Color(0xFF323F4B),
height: 1,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
// StatusWidget(
// status: state.consultations[index].chosenPlanState,
// ),
],
),
],
),
),
);
},
separatorBuilder: (context, index) => const SizedBox(height: 12),
itemCount: state.purchasesModels.length,
)
else
const Padding(
padding: EdgeInsets.symmetric(vertical: 40.0),
child: Text(
'У вас нет таких консультаций',
style: TextStyles.textMMedium,
),
),
],
),
),
),
),
],
)),
);
},
),
);
}
}

View File

@@ -5,6 +5,7 @@ import 'package:eatvio_app/core/utils/image_constant.dart';
import 'package:eatvio_app/core/locator_service.dart';
import 'package:eatvio_app/core/theme/theme_helper.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/consultant_finances_screen.dart';
import 'package:eatvio_app/features/finances_and_consultations_archive/presentation/user_purchases_screen.dart';
import 'package:eatvio_app/features/my_diets/presentation/my_diets_screen.dart';
import 'package:eatvio_app/features/my_plans/presentation/my_plans_screen.dart';
import 'package:eatvio_app/features/profile/models/profile_model.dart';
@@ -43,8 +44,10 @@ class MenuScreen extends StatelessWidget {
onTap: () {
if (profileModel.roles.contains('consultant')) {
GoRouter.of(context).push(ConsultantFinancesScreen.path);
GoRouter.of(context).push(UserPurchasesScreen.path);
return;
}
GoRouter.of(context).push(UserPurchasesScreen.path);
},
),
_divider(context),

View File

@@ -21,9 +21,9 @@ import 'package:sentry_flutter/sentry_flutter.dart';
Future<void> main() async {
WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
await di.init();
await di.init();
await SentryFlutter.init((options) {
options.dsn = 'http://ba7f2653067776f3034e6e89850e85a1@sentry.md-desk.ru:9000/2';
options.tracesSampleRate = 1.0;