diff --git a/lib/features/chat/presentation/pages/chat_screen.dart b/lib/features/chat/presentation/pages/chat_screen.dart index 6ea9f554..ee1b351f 100644 --- a/lib/features/chat/presentation/pages/chat_screen.dart +++ b/lib/features/chat/presentation/pages/chat_screen.dart @@ -194,6 +194,7 @@ class _ChatScreenState extends State { : mapConsultationProgress( state: chosenPlan.chosenPlanState, hasPersonalDiet: chosenOption?.hasPersonalDiet ?? false, + personalDietName: chosenPlan.personalDietName, ); final bannerType = resolveChatBanner( progress: progress, diff --git a/lib/features/consultations/models/consultation_model.dart b/lib/features/consultations/models/consultation_model.dart index dfd1c956..0e321e2c 100644 --- a/lib/features/consultations/models/consultation_model.dart +++ b/lib/features/consultations/models/consultation_model.dart @@ -24,6 +24,7 @@ abstract class ConsultationModel with _$ConsultationModel { @JsonKey(name: 'consultant_id') required int consultantId, required List diaries, @JsonKey(name: 'personal_diet_id') required int? personalDietId, + @JsonKey(name: 'personal_diet_name') String? personalDietName, }) = _ConsultationModel; factory ConsultationModel.fromJson(Map json) => _$ConsultationModelFromJson(json); diff --git a/lib/features/consultations/models/consultation_progress.dart b/lib/features/consultations/models/consultation_progress.dart index 044642d5..35f56f50 100644 --- a/lib/features/consultations/models/consultation_progress.dart +++ b/lib/features/consultations/models/consultation_progress.dart @@ -10,12 +10,14 @@ class ConsultationProgress { required this.showProgressPlank, required this.dietStatus, required this.phase, + this.personalDietName, }); final Set completedSteps; final bool showProgressPlank; final PersonalDietStatus dietStatus; final ConsultationPhase phase; + final String? personalDietName; bool isStepCompleted(ConsultationStep step) => completedSteps.contains(step); diff --git a/lib/features/consultations/presentation/widgets/consultation_card_widget.dart b/lib/features/consultations/presentation/widgets/consultation_card_widget.dart index 3dc0274d..8ad68bf8 100644 --- a/lib/features/consultations/presentation/widgets/consultation_card_widget.dart +++ b/lib/features/consultations/presentation/widgets/consultation_card_widget.dart @@ -97,7 +97,12 @@ class _ConsultationCardWidgetState extends State { borderRadius: BorderRadius.circular(100), ), child: Text( - s.personal_diet_proposed, + widget.consultationsModel.personalDietName == null || + widget.consultationsModel.personalDietName!.isEmpty + ? s.personal_diet_proposed + : s.personal_diet_proposed_named( + widget.consultationsModel.personalDietName!, + ), style: TextStyles.textXSSemibold.copyWith( color: appTheme.greenA700, ), diff --git a/lib/features/consultations/presentation/widgets/personal_diet_status_chip.dart b/lib/features/consultations/presentation/widgets/personal_diet_status_chip.dart index 0f33c501..559d1f39 100644 --- a/lib/features/consultations/presentation/widgets/personal_diet_status_chip.dart +++ b/lib/features/consultations/presentation/widgets/personal_diet_status_chip.dart @@ -42,8 +42,12 @@ class PersonalDietStatusChip extends StatelessWidget { return (s.payment_failed, appTheme.lightRed, appTheme.red); } if (progress.dietStatus == PersonalDietStatus.proposed) { + final dietName = progress.personalDietName; + final label = dietName == null || dietName.isEmpty + ? s.personal_diet_proposed + : s.personal_diet_proposed_named(dietName); return ( - s.personal_diet_proposed, + label, appTheme.transparentGreen, appTheme.greenA700, ); diff --git a/lib/features/consultations/utils/consultation_progress_mapper.dart b/lib/features/consultations/utils/consultation_progress_mapper.dart index 98b89d0c..c815c7a1 100644 --- a/lib/features/consultations/utils/consultation_progress_mapper.dart +++ b/lib/features/consultations/utils/consultation_progress_mapper.dart @@ -4,12 +4,14 @@ import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart'; ConsultationProgress mapConsultationProgress({ required ChosenPlanStateEnum state, required bool hasPersonalDiet, + String? personalDietName, }) { return ConsultationProgress( completedSteps: _completedSteps(state), showProgressPlank: _showProgressPlank(state), dietStatus: _dietStatus(state, hasPersonalDiet), phase: _phase(state), + personalDietName: personalDietName, ); } diff --git a/lib/features/notifications/models/notification_model.dart b/lib/features/notifications/models/notification_model.dart index b178eb91..222462cd 100644 --- a/lib/features/notifications/models/notification_model.dart +++ b/lib/features/notifications/models/notification_model.dart @@ -2174,6 +2174,56 @@ class PersonalDietIsDefinedToCustomerNotification implements BaseNotificationMod } } +class PersonalDietIsDefinedToAuthorNotification implements BaseNotificationModel { + @override + final NotificationType type; + @override + final bool isRead; + @override + final DateTime createdAt; + final int chosenPlanId; + final int dietId; + final String planName; + final String userName; + final String consultantName; + final String dietName; + final String role; + final String peerSlug; + final String peerImagePath; + + PersonalDietIsDefinedToAuthorNotification({ + required this.type, + required this.isRead, + required this.createdAt, + required this.chosenPlanId, + required this.dietId, + required this.planName, + required this.userName, + required this.consultantName, + required this.dietName, + required this.role, + required this.peerSlug, + required this.peerImagePath, + }); + + factory PersonalDietIsDefinedToAuthorNotification.fromJson(Map json, NotificationType type, {bool? isRead, String? createdAt}) { + return PersonalDietIsDefinedToAuthorNotification( + type: type, + isRead: isRead ?? false, + createdAt: parseDate(createdAt ?? json['created_at'] ?? DateTime.now().toIso8601String()), + chosenPlanId: json['chosen_plan_id'] ?? 0, + dietId: json['diet_id'] ?? 0, + planName: json['plan_name'] ?? '', + userName: json['user_name'] ?? '', + consultantName: json['consultant_name'] ?? '', + dietName: json['diet_name'] ?? '', + role: json['role']?.toString() ?? '', + peerSlug: json['peer_slug'] ?? '', + peerImagePath: json['peer_image_path'] ?? '', + ); + } +} + // New Model Classes for Comprehensive Support class ConsultantEducationUpdatedNotification implements BaseNotificationModel { @@ -2480,6 +2530,7 @@ enum NotificationType { chosenPlanFinishedToConsultant, stayOnTrackToCustomer, personalDietIsDefinedToCustomer, + personalDietIsDefinedToAuthor, // Comments entityCommented, @@ -2602,6 +2653,7 @@ enum NotificationType { 'stay-on-track-to-customer' => NotificationType.stayOnTrackToCustomer, 'diary-reminder-for-users' => NotificationType.diaryReminderForUsers, 'personal-diet-is-defined-to-customer' => NotificationType.personalDietIsDefinedToCustomer, + 'personal-diet-is-defined-to-author' => NotificationType.personalDietIsDefinedToAuthor, // New additions 'consultant-education-updated' => NotificationType.consultantEducationUpdated, diff --git a/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart b/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart index c750c513..0a30a0db 100644 --- a/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart +++ b/lib/features/notifications/presentation/notifications_cubit/notifications_cubit.dart @@ -277,6 +277,12 @@ class NotificationsCubit extends Cubit { isRead: isRead, createdAt: createdAt, ), + NotificationType.personalDietIsDefinedToAuthor => PersonalDietIsDefinedToAuthorNotification.fromJson( + notificationJson, + type, + isRead: isRead, + createdAt: createdAt, + ), NotificationType.consultantEducationUpdated => ConsultantEducationUpdatedNotification.fromJson( notificationJson, type, diff --git a/lib/features/notifications/presentation/widgets/notification_widget.dart b/lib/features/notifications/presentation/widgets/notification_widget.dart index 26714425..ed3c4dab 100644 --- a/lib/features/notifications/presentation/widgets/notification_widget.dart +++ b/lib/features/notifications/presentation/widgets/notification_widget.dart @@ -122,6 +122,9 @@ class NotificationWidget extends StatelessWidget { PersonalDietIsDefinedToCustomerNotification() => _PersonalDietIsDefinedToCustomerWidget( notification: notification as PersonalDietIsDefinedToCustomerNotification, ), + PersonalDietIsDefinedToAuthorNotification() => _PersonalDietIsDefinedToAuthorWidget( + notification: notification as PersonalDietIsDefinedToAuthorNotification, + ), // New Widgets ConsultantEducationUpdatedNotification() => _ConsultantEducationUpdatedWidget(notification: notification as ConsultantEducationUpdatedNotification), ComplaintRegisteredNotification() => _ComplaintRegisteredWidget(notification: notification as ComplaintRegisteredNotification), @@ -1273,6 +1276,27 @@ class _PersonalDietIsDefinedToCustomerWidget extends StatelessWidget { } } +class _PersonalDietIsDefinedToAuthorWidget extends StatelessWidget { + const _PersonalDietIsDefinedToAuthorWidget({required this.notification}); + final PersonalDietIsDefinedToAuthorNotification notification; + @override + Widget build(BuildContext context) { + final userName = notification.userName.isNotEmpty ? notification.userName : 'Пользователь'; + final dietName = notification.dietName.isNotEmpty ? ', выбранная диета: «${notification.dietName}»' : ''; + + return _BaseNotificationWidget( + isRead: notification.isRead, + notificationsText: 'Вы предложили персональную диету пользователю $userName$dietName', + createdAt: notification.createdAt, + onTap: notification.peerSlug.isEmpty + ? null + : () { + context.push(RouteConstants.profileScreen, extra: notification.peerSlug); + }, + ); + } +} + class _ConsultantEducationUpdatedWidget extends StatelessWidget { const _ConsultantEducationUpdatedWidget({required this.notification}); final ConsultantEducationUpdatedNotification notification; diff --git a/lib/features/notifications/service/notifications_service.dart b/lib/features/notifications/service/notifications_service.dart index c934fb53..dbe2d16c 100644 --- a/lib/features/notifications/service/notifications_service.dart +++ b/lib/features/notifications/service/notifications_service.dart @@ -413,6 +413,12 @@ class NotificationsService { isRead: readDate != null, createdAt: createdAt, ), + NotificationType.personalDietIsDefinedToAuthor => PersonalDietIsDefinedToAuthorNotification.fromJson( + notificationJson['data'], + type, + isRead: readDate != null, + createdAt: createdAt, + ), NotificationType.consultantEducationUpdated => ConsultantEducationUpdatedNotification.fromJson( notificationJson['data'], type, diff --git a/lib/features/plans/models/chosen_plan_model.dart b/lib/features/plans/models/chosen_plan_model.dart index 9ac84c85..de1e61d3 100644 --- a/lib/features/plans/models/chosen_plan_model.dart +++ b/lib/features/plans/models/chosen_plan_model.dart @@ -15,6 +15,7 @@ abstract class ChosenPlanModel with _$ChosenPlanModel { @JsonKey(name: 'last_state') required ChosenPlanStateEnum chosenPlanState, @JsonKey(name: 'chat_id') required String chatId, @JsonKey(name: 'consultant_id') required int consultantId, + @JsonKey(name: 'personal_diet_name') String? personalDietName, @JsonKey(name: 'plan') required PlanModel plan, }) = _ChosenPlanModel; diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 674fc983..8a5fffea 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -28,36 +28,38 @@ class MessageLookup extends MessageLookupByLibrary { static String m2(dietDuration, dietDayWord, planDuration, planDayWord) => "Not suitable: diet lasts ${dietDuration} ${dietDayWord}, plan lasts ${planDuration} ${planDayWord}."; - static String m3(planName) => "План «${planName}» начался"; + static String m3(name) => "Diet «${name}» proposed"; - static String m4(planName, name) => + static String m4(planName) => "План «${planName}» начался"; + + static String m5(planName, name) => "План «${planName}» с пользователем ${name} начался"; - static String m5(duration, dayWord) => "Plan for ${duration} ${dayWord}"; + static String m6(duration, dayWord) => "Plan for ${duration} ${dayWord}"; - static String m6(percent, amount) => + static String m7(percent, amount) => "Discount ${percent}% — you save ${amount} ₽"; - static String m7(date) => "Start: ${date}"; + static String m8(date) => "Start: ${date}"; - static String m8(step) => "шаг ${step} из 2"; + static String m9(step) => "шаг ${step} из 2"; - static String m9(planName, name) => + static String m10(planName, name) => "Завтра начинается план «${planName}» с пользователем ${name}"; - static String m10(planName) => + static String m11(planName) => "Завтра начнется прохождение плана «${planName}»"; - static String m11(name, planName) => + static String m12(name, planName) => "Пользователь ${name} отменил подписку на план «${planName}»"; - static String m12(name, planName) => + static String m13(name, planName) => "Пользователь ${name} оплатил план «${planName}»"; - static String m13(name, planName) => + static String m14(name, planName) => "Пользователь ${name} подписался на план «${planName}»"; - static String m14(planName) => "Вы отменили подписку на план «${planName}»"; + static String m15(planName) => "Вы отменили подписку на план «${planName}»"; final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { @@ -696,6 +698,7 @@ class MessageLookup extends MessageLookupByLibrary { "personal_diet_proposed": MessageLookupByLibrary.simpleMessage( "Personal diet proposed", ), + "personal_diet_proposed_named": m3, "personal_diet_replaces_tomorrow": MessageLookupByLibrary.simpleMessage( "The change takes effect tomorrow", ), @@ -715,18 +718,18 @@ class MessageLookup extends MessageLookupByLibrary { "Физическая форма", ), "place_of_living": MessageLookupByLibrary.simpleMessage("Место проживания"), - "planStarted": m3, + "planStarted": m4, "planWillBeConfirmedAfterThreeDays": MessageLookupByLibrary.simpleMessage( "Если вы не подтвердите, то план через три дня подтвердится автоматически", ), - "planWithUserStarted": m4, + "planWithUserStarted": m5, "plan_author": MessageLookupByLibrary.simpleMessage("Автор плана"), "plan_desc": MessageLookupByLibrary.simpleMessage("Описание плана"), "plan_desc_example": MessageLookupByLibrary.simpleMessage( "Большое подробное, привлекательное описание плана", ), "plan_duration": MessageLookupByLibrary.simpleMessage("Срок плана"), - "plan_duration_label": m5, + "plan_duration_label": m6, "plan_label_desc": MessageLookupByLibrary.simpleMessage( "План для поддержания формы", ), @@ -763,7 +766,7 @@ class MessageLookup extends MessageLookupByLibrary { "Promo code applied!", ), "promo_code": MessageLookupByLibrary.simpleMessage("Promo code"), - "promo_discount_hint": m6, + "promo_discount_hint": m7, "promo_invalid": MessageLookupByLibrary.simpleMessage( "Promo code not found or expired", ), @@ -884,13 +887,13 @@ class MessageLookup extends MessageLookupByLibrary { "Выбрана дата старта", ), "startDiet": MessageLookupByLibrary.simpleMessage("Начать диету"), - "start_date_label": m7, + "start_date_label": m8, "statistic": MessageLookupByLibrary.simpleMessage("Статистика"), "step": MessageLookupByLibrary.simpleMessage("Шаг"), "step_by_step_photo_recipe": MessageLookupByLibrary.simpleMessage( "Пошаговый фоторецепт", ), - "step_widget": m8, + "step_widget": m9, "successfully_published": MessageLookupByLibrary.simpleMessage( "успешно опубликован", ), @@ -915,8 +918,8 @@ class MessageLookup extends MessageLookupByLibrary { "toOffer": MessageLookupByLibrary.simpleMessage("Предложить"), "to_draft": MessageLookupByLibrary.simpleMessage("В черновик"), "to_order": MessageLookupByLibrary.simpleMessage("Заказать"), - "tommorowPlanWithUserWillStart": m9, - "tomorrowYourPlanWillStart": m10, + "tommorowPlanWithUserWillStart": m10, + "tomorrowYourPlanWillStart": m11, "try_again": MessageLookupByLibrary.simpleMessage("Попробовать снова"), "try_again_2": MessageLookupByLibrary.simpleMessage("Попробуйте ещё раз"), "typeAmoutOfReturn": MessageLookupByLibrary.simpleMessage( @@ -950,7 +953,7 @@ class MessageLookup extends MessageLookupByLibrary { "Верхние показатели давления:", ), "upper": MessageLookupByLibrary.simpleMessage("Верхнее"), - "userCanceldThePlanSubscription": m11, + "userCanceldThePlanSubscription": m12, "userConfirmedPlanCompletion": MessageLookupByLibrary.simpleMessage( "Пользователь подтвердил прохождения плана. В ближайшее время Вы получите", ), @@ -958,8 +961,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "Пользователь подтвердил прохождения плана.", ), - "userPaidThePlan": m12, - "userSubscribedToPlan": m13, + "userPaidThePlan": m13, + "userSubscribedToPlan": m14, "user_authorization": MessageLookupByLibrary.simpleMessage( "Аутентификация", ), @@ -1089,7 +1092,7 @@ class MessageLookup extends MessageLookupByLibrary { "youCanComplain": MessageLookupByLibrary.simpleMessage( "Если вас что-то не устроило, вы можете подать жалобу", ), - "youCanceledSubscriptionToPlan": m14, + "youCanceledSubscriptionToPlan": m15, "youShureYouWantToCancelOrder": MessageLookupByLibrary.simpleMessage( "Вы уверены, что хотите отменить заказ?", ), diff --git a/lib/generated/intl/messages_ru.dart b/lib/generated/intl/messages_ru.dart index 40408fe2..a591ebbd 100644 --- a/lib/generated/intl/messages_ru.dart +++ b/lib/generated/intl/messages_ru.dart @@ -26,12 +26,14 @@ class MessageLookup extends MessageLookupByLibrary { static String m2(dietDuration, dietDayWord, planDuration, planDayWord) => "Не подходит: диета на ${dietDuration} ${dietDayWord}, план на ${planDuration} ${planDayWord}."; - static String m5(duration, dayWord) => "План на ${duration} ${dayWord}"; + static String m3(name) => "Диета «${name}» предложена"; - static String m6(percent, amount) => + static String m6(duration, dayWord) => "План на ${duration} ${dayWord}"; + + static String m7(percent, amount) => "Скидка ${percent}% — вы экономите ${amount} ₽"; - static String m7(date) => "Старт: ${date}"; + static String m8(date) => "Старт: ${date}"; final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { @@ -340,6 +342,7 @@ class MessageLookup extends MessageLookupByLibrary { "personal_diet_proposed": MessageLookupByLibrary.simpleMessage( "Диета предложена", ), + "personal_diet_proposed_named": m3, "personal_diet_replaces_tomorrow": MessageLookupByLibrary.simpleMessage( "Замена действует со следующего дня", ), @@ -355,7 +358,7 @@ class MessageLookup extends MessageLookupByLibrary { "Большое подробное, привлекательное описание плана", ), "plan_duration": MessageLookupByLibrary.simpleMessage("Срок плана"), - "plan_duration_label": m5, + "plan_duration_label": m6, "plan_label_desc": MessageLookupByLibrary.simpleMessage( "План для поддержания формы", ), @@ -383,7 +386,7 @@ class MessageLookup extends MessageLookupByLibrary { "Промокод применён!", ), "promo_code": MessageLookupByLibrary.simpleMessage("Промокод"), - "promo_discount_hint": m6, + "promo_discount_hint": m7, "promo_invalid": MessageLookupByLibrary.simpleMessage( "Промокод не найден или истёк", ), @@ -444,7 +447,7 @@ class MessageLookup extends MessageLookupByLibrary { "Выбрана дата старта", ), "startDiet": MessageLookupByLibrary.simpleMessage("Начать диету"), - "start_date_label": m7, + "start_date_label": m8, "suggestDiet": MessageLookupByLibrary.simpleMessage("Предложить диету"), "tell_your_schedule": MessageLookupByLibrary.simpleMessage( "Расскажите в свободной форме о том как просыпаетесь, когда едите, когда ложитесь спать:", diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 1c3bf033..5f333859 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -4574,6 +4574,16 @@ class S { ); } + /// `Diet «{name}» proposed` + String personal_diet_proposed_named(String name) { + return Intl.message( + 'Diet «$name» proposed', + name: 'personal_diet_proposed_named', + desc: '', + args: [name], + ); + } + /// `Personal diet not proposed` String get personal_diet_not_proposed { return Intl.message( diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 980a696d..4249132f 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -540,6 +540,12 @@ "video_call_notification_end": "End call", "order_in_progress": "Order in progress", "personal_diet_proposed": "Personal diet proposed", + "personal_diet_proposed_named": "Diet «{name}» proposed", + "@personal_diet_proposed_named": { + "placeholders": { + "name": {"type": "String"} + } + }, "personal_diet_not_proposed": "Personal diet not proposed", "change_personal_diet": "Change personal diet", "personal_diet_replaces_tomorrow": "The change takes effect tomorrow", diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index 143c497d..17c31301 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -295,6 +295,12 @@ "choose_plan_start_date_with_personal_diet": "Выберите дату старта плана питания с учетом составления персональной диеты", "order_in_progress": "Заказ в работе", "personal_diet_proposed": "Диета предложена", + "personal_diet_proposed_named": "Диета «{name}» предложена", + "@personal_diet_proposed_named": { + "placeholders": { + "name": {"type": "String"} + } + }, "personal_diet_not_proposed": "Диета не предложена", "change_personal_diet": "Заменить диету", "personal_diet_replaces_tomorrow": "Замена действует со следующего дня", diff --git a/test/features/chat/presentation/widgets/chat_consultation_header_test.dart b/test/features/chat/presentation/widgets/chat_consultation_header_test.dart index 96c10b05..887e593e 100644 --- a/test/features/chat/presentation/widgets/chat_consultation_header_test.dart +++ b/test/features/chat/presentation/widgets/chat_consultation_header_test.dart @@ -55,6 +55,7 @@ void main() { final progress = mapConsultationProgress( state: ChosenPlanStateEnum.personalDietSet, hasPersonalDiet: true, + personalDietName: 'Индивидуальный план питания', ); expect(progress.showProgressPlank, isTrue); expect(progress.dietStatus, PersonalDietStatus.proposed); @@ -73,7 +74,10 @@ void main() { expect(find.text(s.order_in_progress), findsNWidgets(2)); expect(find.byType(PersonalDietStatusChip), findsOneWidget); - expect(find.text(s.personal_diet_proposed), findsOneWidget); + expect( + find.text(s.personal_diet_proposed_named('Индивидуальный план питания')), + findsOneWidget, + ); expect(find.byType(ConsultationProgressPlank), findsOneWidget); expect(find.byIcon(Icons.check), findsNWidgets(2)); }, diff --git a/test/features/consultations/models/consultation_model_test.dart b/test/features/consultations/models/consultation_model_test.dart index 64ac25f9..bd00e5c8 100644 --- a/test/features/consultations/models/consultation_model_test.dart +++ b/test/features/consultations/models/consultation_model_test.dart @@ -174,11 +174,13 @@ void main() { 'consultant_id': 1, 'diaries': [], 'personal_diet_id': null, + 'personal_diet_name': 'Индивидуальный план питания', }; final model = ConsultationModel.fromJson(json); expect(model.chatId, 'direct-chat-uuid'); + expect(model.personalDietName, 'Индивидуальный план питания'); }); test('fromJson allows missing chat_id', () { diff --git a/test/features/consultations/presentation/widgets/consultation_card_widget_test.dart b/test/features/consultations/presentation/widgets/consultation_card_widget_test.dart index 46c5e042..6e1a6083 100644 --- a/test/features/consultations/presentation/widgets/consultation_card_widget_test.dart +++ b/test/features/consultations/presentation/widgets/consultation_card_widget_test.dart @@ -153,6 +153,29 @@ void main() { }); }); + testWidgets('shows personal diet name in status chip', (tester) async { + final consultation = mockConsultation.copyWith( + personalDietId: 1, + personalDietName: 'Индивидуальный план питания', + ); + + await mockNetworkImagesFor(() async { + await tester.pumpWidget( + wrap(ConsultationCardWidget(consultationsModel: consultation)), + ); + await tester.pumpAndSettle(); + + final s = S.of(tester.element(find.byType(ConsultationCardWidget))); + + expect( + find.text( + s.personal_diet_proposed_named('Индивидуальный план питания'), + ), + findsOneWidget, + ); + }); + }); + testWidgets('renders diary days', (tester) async { await mockNetworkImagesFor(() async { await tester.pumpWidget( diff --git a/test/features/consultations/presentation/widgets/personal_diet_status_chip_test.dart b/test/features/consultations/presentation/widgets/personal_diet_status_chip_test.dart index d8fbb9f1..68ea86e4 100644 --- a/test/features/consultations/presentation/widgets/personal_diet_status_chip_test.dart +++ b/test/features/consultations/presentation/widgets/personal_diet_status_chip_test.dart @@ -21,11 +21,16 @@ Widget _wrap(Widget child) { ); } -Widget _chip(ChosenPlanStateEnum state, {bool hasPersonalDiet = true}) { +Widget _chip( + ChosenPlanStateEnum state, { + bool hasPersonalDiet = true, + String? personalDietName, +}) { return PersonalDietStatusChip( progress: mapConsultationProgress( state: state, hasPersonalDiet: hasPersonalDiet, + personalDietName: personalDietName, ), ); } @@ -55,6 +60,24 @@ void main() { expect(find.text(s.personal_diet_proposed), findsOneWidget); }); + testWidgets('proposed с названием показывает имя диеты', (tester) async { + await tester.pumpWidget( + _wrap( + _chip( + ChosenPlanStateEnum.stateStarted, + personalDietName: 'Индивидуальный план питания', + ), + ), + ); + + final s = S.of(tester.element(find.byType(PersonalDietStatusChip))); + + expect( + find.text(s.personal_diet_proposed_named('Индивидуальный план питания')), + findsOneWidget, + ); + }); + testWidgets('notProposed показывает personal_diet_not_proposed', ( tester, ) async { diff --git a/test/features/consultations/utils/consultation_progress_mapper_test.dart b/test/features/consultations/utils/consultation_progress_mapper_test.dart index 16c0f1c5..ac0ded6c 100644 --- a/test/features/consultations/utils/consultation_progress_mapper_test.dart +++ b/test/features/consultations/utils/consultation_progress_mapper_test.dart @@ -275,5 +275,15 @@ void main() { ); } }); + + test('personalDietName передаётся в ConsultationProgress', () { + final progress = mapConsultationProgress( + state: ChosenPlanStateEnum.personalDietSet, + hasPersonalDiet: true, + personalDietName: 'Индивидуальный план питания', + ); + + expect(progress.personalDietName, 'Индивидуальный план питания'); + }); }); } diff --git a/test/features/notifications/models/notification_model_fromjson_bulk_test.dart b/test/features/notifications/models/notification_model_fromjson_bulk_test.dart index 8ee7fe97..771518c7 100644 --- a/test/features/notifications/models/notification_model_fromjson_bulk_test.dart +++ b/test/features/notifications/models/notification_model_fromjson_bulk_test.dart @@ -92,6 +92,7 @@ void main() { DiaryReminderForUsersNotification.fromJson(base, NotificationType.diaryReminderForUsers); DiaryReminderForUsersNotification.fromJson({'created_at': c, 'data': {}}, NotificationType.diaryReminderForUsers); PersonalDietIsDefinedToCustomerNotification.fromJson(base, NotificationType.personalDietIsDefinedToCustomer); + PersonalDietIsDefinedToAuthorNotification.fromJson(base, NotificationType.personalDietIsDefinedToAuthor); ConsultantEducationUpdatedNotification.fromJson(base, NotificationType.consultantEducationUpdated); ComplaintRegisteredNotification.fromJson(base, NotificationType.complaintRegistered); ContentUnpublishedNotification.fromJson(base, NotificationType.contentUnpublished); diff --git a/test/features/notifications/models/notification_model_test.dart b/test/features/notifications/models/notification_model_test.dart index d7f809e0..0c89e2fd 100644 --- a/test/features/notifications/models/notification_model_test.dart +++ b/test/features/notifications/models/notification_model_test.dart @@ -50,6 +50,7 @@ void main() { 'stay-on-track-to-customer': NotificationType.stayOnTrackToCustomer, 'diary-reminder-for-users': NotificationType.diaryReminderForUsers, 'personal-diet-is-defined-to-customer': NotificationType.personalDietIsDefinedToCustomer, + 'personal-diet-is-defined-to-author': NotificationType.personalDietIsDefinedToAuthor, }; cases.forEach((key, expected) { @@ -671,6 +672,34 @@ void main() { expect(notification.type, NotificationType.personalizedDietIsDefined); }); + test('PersonalDietIsDefinedToAuthorNotification.fromJson parses all fields', () { + final json = { + 'chosen_plan_id': 12, + 'diet_id': 34, + 'plan_name': 'Plan', + 'user_name': 'Client', + 'consultant_name': 'Consultant', + 'diet_name': 'Personal diet', + 'role': 'consultant', + 'peer_slug': 'client-slug', + 'peer_image_path': 'avatars/client.png', + 'created_at': '2024-01-30T12:00:00Z', + }; + + final notification = PersonalDietIsDefinedToAuthorNotification.fromJson( + json, + NotificationType.personalDietIsDefinedToAuthor, + isRead: false, + ); + + expect(notification.chosenPlanId, 12); + expect(notification.dietId, 34); + expect(notification.userName, 'Client'); + expect(notification.dietName, 'Personal diet'); + expect(notification.peerSlug, 'client-slug'); + expect(notification.type, NotificationType.personalDietIsDefinedToAuthor); + }); + test('ChosenPlanFinishedToClientNotification.fromJson parses all fields', () { final json = { 'plan_name': 'Finished Plan', diff --git a/test/features/notifications/presentation/widgets/notification_widget_test.dart b/test/features/notifications/presentation/widgets/notification_widget_test.dart index 2127155b..fdb04862 100644 --- a/test/features/notifications/presentation/widgets/notification_widget_test.dart +++ b/test/features/notifications/presentation/widgets/notification_widget_test.dart @@ -1212,6 +1212,31 @@ void main() { expect(find.byType(NotificationWidget), findsOneWidget); }); + testWidgets('displays PersonalDietIsDefinedToAuthorNotification', ( + tester, + ) async { + final notification = PersonalDietIsDefinedToAuthorNotification( + type: NotificationType.personalDietIsDefinedToAuthor, + isRead: false, + createdAt: testDate, + chosenPlanId: 1, + dietId: 1, + planName: 'Plan', + userName: 'Client', + consultantName: 'Consultant', + dietName: 'Test Diet', + role: 'consultant', + peerSlug: 'client-slug', + peerImagePath: '', + ); + + await tester.pumpWidget(createTestWidget(notification)); + await tester.pumpAndSettle(); + + expect(find.textContaining('Вы предложили персональную диету пользователю Client'), findsOneWidget); + expect(find.textContaining('Test Diet'), findsOneWidget); + }); + testWidgets('displays UnknownNotification', (tester) async { final notification = UnknownNotification( type: NotificationType.unknown, diff --git a/test/features/notifications/service/notifications_service_test.dart b/test/features/notifications/service/notifications_service_test.dart index 5c2b62ab..4effb8f3 100644 --- a/test/features/notifications/service/notifications_service_test.dart +++ b/test/features/notifications/service/notifications_service_test.dart @@ -1,4 +1,5 @@ import 'package:dio/dio.dart'; +import 'package:eatvio_app/features/notifications/models/notification_model.dart'; import 'package:eatvio_app/features/notifications/service/notifications_service.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; @@ -1454,6 +1455,42 @@ void main() { expect(result, isNotNull); expect(result!['notifications'], isA()); expect(result['notifications'].length, 1); + expect(result['notifications'].first, isA()); + }); + + test('getNotifications - обрабатывает PersonalDietIsDefinedToAuthor', () async { + const page = 1; + final response = Response( + data: { + 'data': [ + { + 'type': 'personal-diet-is-defined-to-author', + 'data': { + 'diet_name': 'Test Diet', + 'diet_id': 1, + 'user_name': 'Client', + }, + 'read_at': null, + 'created_at': '2024-01-01T12:00:00Z', + }, + ], + 'current_page': 1, + 'last_page': 1, + 'per_page': 10, + 'total': 1, + }, + statusCode: 200, + requestOptions: RequestOptions(path: '/api/profile/notifications'), + ); + + when(mockDio.get(any)).thenAnswer((_) async => response); + + final result = await notificationsService.getNotifications(page: page); + + expect(result, isNotNull); + expect(result!['notifications'], isA()); + expect(result['notifications'].length, 1); + expect(result['notifications'].first, isA()); }); test('getNotifications - обрабатывает UserDailyReportCompleted', () async { diff --git a/test/features/plans/models/chosen_plan_model_test.dart b/test/features/plans/models/chosen_plan_model_test.dart index a3100298..5f53ba22 100644 --- a/test/features/plans/models/chosen_plan_model_test.dart +++ b/test/features/plans/models/chosen_plan_model_test.dart @@ -60,6 +60,7 @@ void main() { 'last_state': 'initied', 'chat_id': 'chat-1', 'consultant_id': 1, + 'personal_diet_name': 'Индивидуальный план питания', 'plan': { 'id': 1, 'slug': 'test-plan', @@ -94,6 +95,7 @@ void main() { expect(chosenPlan.optionType, 'options1'); expect(chosenPlan.chosenPlanState, ChosenPlanStateEnum.stateInited); expect(chosenPlan.chatId, 'chat-1'); + expect(chosenPlan.personalDietName, 'Индивидуальный план питания'); }); test('toJson serializes ChosenPlanModel correctly', () { @@ -207,6 +209,7 @@ void main() { 'last_state': 'initied', 'chat_id': 'chat-1', 'consultant_id': 1, + 'personal_diet_name': 'Индивидуальный план питания', 'plan': { 'id': 1, 'slug': 'test-plan',