diff --git a/ios/Flutter/ephemeral/flutter_lldb_helper.py b/ios/Flutter/ephemeral/flutter_lldb_helper.py new file mode 100644 index 00000000..a88caf99 --- /dev/null +++ b/ios/Flutter/ephemeral/flutter_lldb_helper.py @@ -0,0 +1,32 @@ +# +# Generated file, do not edit. +# + +import lldb + +def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): + """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" + base = frame.register["x0"].GetValueAsAddress() + page_len = frame.register["x1"].GetValueAsUnsigned() + + # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the + # first page to see if handled it correctly. This makes diagnosing + # misconfiguration (e.g. missing breakpoint) easier. + data = bytearray(page_len) + data[0:8] = b'IHELPED!' + + error = lldb.SBError() + frame.GetThread().GetProcess().WriteMemory(base, data, error) + if not error.Success(): + print(f'Failed to write into {base}[+{page_len}]', error) + return + +def __lldb_init_module(debugger: lldb.SBDebugger, _): + target = debugger.GetDummyTarget() + # Caveat: must use BreakpointCreateByRegEx here and not + # BreakpointCreateByName. For some reasons callback function does not + # get carried over from dummy target for the later. + bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") + bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) + bp.SetAutoContinue(True) + print("-- LLDB integration loaded --") diff --git a/ios/Flutter/ephemeral/flutter_lldbinit b/ios/Flutter/ephemeral/flutter_lldbinit new file mode 100644 index 00000000..e3ba6fbe --- /dev/null +++ b/ios/Flutter/ephemeral/flutter_lldbinit @@ -0,0 +1,5 @@ +# +# Generated file, do not edit. +# + +command script import --relative-to-command-file flutter_lldb_helper.py diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c53e2b31..9c12df59 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> const ConsultantFinanceScreen(), + builder: (context, state) => const ConsultationsArchive(), ), GoRoute( path: '/user-finance-screen', diff --git a/lib/core/const.dart b/lib/core/const.dart index 31c47e9c..4f38a54e 100644 --- a/lib/core/const.dart +++ b/lib/core/const.dart @@ -1,5 +1,5 @@ -String baseUrl = 'https://eatvio.ru'; -// String baseUrl = 'https://dev2.eatvio.com'; +// String baseUrl = 'https://eatvio.ru'; +String baseUrl = 'https://dev2.eatvio.com'; const List durationValues = [7, 14, 21]; diff --git a/lib/core/enums/filters_comparison_enum.dart b/lib/core/enums/filters_comparison_enum.dart new file mode 100644 index 00000000..57957552 --- /dev/null +++ b/lib/core/enums/filters_comparison_enum.dart @@ -0,0 +1,13 @@ +enum FiltersComparisonEnum { + equal('\$eq'), + notEqual('\$ne'), + greaterThan('\$gt'), + lessThan('\$lt'), + greaterThanOrEqual('\$gte'), + lessThanOrEqual('\$lte'), + inArray('\$in'), + notInArray('\$notIn'); + + final String value; + const FiltersComparisonEnum(this.value); +} diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index fe28d2ec..5f5abeb6 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -16,6 +16,7 @@ import 'package:eatvio_app/features/complaining/services/complaining_service.dar import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart'; import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart'; import 'package:eatvio_app/features/consultations/services/consultation_service.dart'; +import 'package:eatvio_app/features/consultations_archive/cubit/consultations_archive_cubit.dart'; import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart'; import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart'; import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart'; @@ -138,6 +139,7 @@ Future init() async { sl.registerFactory(() => ConsultationsCubit(consultationsRepository: sl())); sl.registerFactory(() => ConsultantCommentsCubit(diaryRepository: sl())); sl.registerFactory(() => CancelPlanCubit(cancelPlanRepository: sl())); + sl.registerFactory(() => ConsultationsArchiveCubit(consultationsRepository: sl())); // Repository sl.registerLazySingleton(() => ChatRepository(chatService: sl())); diff --git a/lib/core/theme/theme_helper.dart b/lib/core/theme/theme_helper.dart index aa4e30a6..3b2a063d 100644 --- a/lib/core/theme/theme_helper.dart +++ b/lib/core/theme/theme_helper.dart @@ -101,8 +101,7 @@ class PrimaryColors { // Blue Color get blue400 => const Color(0XFF43A6F2); - Color get blueA400 => const Color(0XFF2F80ED); - + Color get blueA400 => const Color(0XFF2F80ED); // BlueGray Color get blueGray400 => const Color(0XFF7B8794); diff --git a/lib/features/consultations/presentation/pages/consultations_screen.dart b/lib/features/consultations/presentation/pages/consultations_screen.dart index 34f25b96..d6acb306 100644 --- a/lib/features/consultations/presentation/pages/consultations_screen.dart +++ b/lib/features/consultations/presentation/pages/consultations_screen.dart @@ -1,5 +1,6 @@ import 'package:eatvio_app/core/enums/base_state.dart'; import 'package:eatvio_app/core/locator_service.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; import 'package:eatvio_app/core/widgets/data_error_widget.dart'; import 'package:eatvio_app/core/widgets/loader.dart'; import 'package:eatvio_app/features/consultations/presentation/cubit/consultations_cubit.dart'; @@ -7,6 +8,7 @@ import 'package:eatvio_app/features/consultations/presentation/cubit/consultatio import 'package:eatvio_app/features/consultations/presentation/widgets/consultation_card_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; class ConsultationsScreen extends StatelessWidget { static const path = '/consultations-screen'; @@ -46,15 +48,49 @@ class ConsultationsContent extends StatelessWidget { context.read().loadConsultations(); }), BaseState.initial => const Loader(), - BaseState.success => ListView.separated( - padding: const EdgeInsets.symmetric(vertical: 12), - itemBuilder: (context, index) => ConsultationCardWidget( - consultationsModel: state.consultations[index], + BaseState.success => SingleChildScrollView( + child: Column( + children: [ + ListView.separated( + padding: const EdgeInsets.symmetric(vertical: 12), + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => ConsultationCardWidget( + consultationsModel: state.consultations[index], + ), + separatorBuilder: (context, index) => const SizedBox( + height: 12, + ), + shrinkWrap: true, + itemCount: state.consultations.length, + ), + const SizedBox(height: 20), + InkWell( + borderRadius: BorderRadius.circular(20), + onTap: () { + GoRouter.of(context).pushNamed('consultantFinanceScreen'); + }, + child: Ink( + width: MediaQuery.of(context).size.width - 32, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20), + color: appTheme.blueA400, + ), + child: const Center( + child: Text( + 'Архив консультаций', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ), + const SizedBox(height: 20), + ], ), - separatorBuilder: (context, index) => const SizedBox( - height: 12, - ), - itemCount: state.consultations.length, ), }); }, diff --git a/lib/features/consultations/repositories/consultations_repository.dart b/lib/features/consultations/repositories/consultations_repository.dart index 93d84f67..b4e3ba1b 100644 --- a/lib/features/consultations/repositories/consultations_repository.dart +++ b/lib/features/consultations/repositories/consultations_repository.dart @@ -1,5 +1,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/consultations/services/consultation_service.dart'; +import 'package:eatvio_app/features/consultations_archive/models/statistic_model.dart'; class ConsultationsRepository { final ConsultationService consultationService; @@ -13,4 +15,31 @@ class ConsultationsRepository { rethrow; } } + + Future> getFilteredConsultations({ + String? state, + String? date, + FiltersComparisonEnum? stateComparison, + FiltersComparisonEnum? dateComparison, + }) async { + try { + final model = await consultationService.getFilteredConsultations( + state: state, + date: date, + stateComparison: stateComparison, + dateComparison: dateComparison, + ); + return model.data; + } catch (e) { + rethrow; + } + } + + Future getStatistic() async { + try { + return await consultationService.getStatistics(); + } catch (e) { + rethrow; + } + } } diff --git a/lib/features/consultations/services/consultation_service.dart b/lib/features/consultations/services/consultation_service.dart index e5236e0f..0261204b 100644 --- a/lib/features/consultations/services/consultation_service.dart +++ b/lib/features/consultations/services/consultation_service.dart @@ -1,13 +1,51 @@ import 'package:dio/dio.dart'; +import 'package:eatvio_app/core/enums/filters_comparison_enum.dart'; import 'package:eatvio_app/features/consultations/models/consultation_model.dart'; -import 'package:retrofit/retrofit.dart'; +import 'package:eatvio_app/features/consultations_archive/models/statistic_model.dart'; -part 'consultation_service.g.dart'; +class ConsultationService { + final Dio dio; + ConsultationService(this.dio); -@RestApi() -abstract class ConsultationService { - factory ConsultationService(Dio dio, {String baseUrl}) = _ConsultationService; + Future getConsultations() async { + try { + final response = await dio.get( + '/api/consultations', + ); + return ConsultationPaginationModel.fromJson(response.data); + } catch (e) { + rethrow; + } + } - @GET('/api/consultations') - Future getConsultations(); + Future getFilteredConsultations({ + String? state, + String? date, + FiltersComparisonEnum? stateComparison, + FiltersComparisonEnum? dateComparison, + }) async { + try { + final response = await dio.get( + '/api/consultations', + queryParameters: { + if (state != null) 'filters[last_state][${stateComparison?.value ?? '\$chosen_plan_status'}]': state, + if (dateComparison != null && date != null) 'filters[start_date][${dateComparison.value}]': date, + }, + ); + return ConsultationPaginationModel.fromJson(response.data); + } catch (e) { + rethrow; + } + } + + Future getStatistics() async { + try { + final response = await dio.get( + '/api/consultations/statistics', + ); + return StatisticModel.fromJson(response.data); + } catch (e) { + rethrow; + } + } } diff --git a/lib/features/consultations_archive/cubit/consultations_archive_cubit.dart b/lib/features/consultations_archive/cubit/consultations_archive_cubit.dart new file mode 100644 index 00000000..09dff9e3 --- /dev/null +++ b/lib/features/consultations_archive/cubit/consultations_archive_cubit.dart @@ -0,0 +1,102 @@ +import 'dart:developer'; + +import 'package:eatvio_app/core/enums/filters_comparison_enum.dart'; +import 'package:eatvio_app/core/utils/date_time_utils.dart'; +import 'package:eatvio_app/features/consultations/repositories/consultations_repository.dart'; +import 'package:eatvio_app/features/consultations_archive/cubit/consultations_archive_state.dart'; +import 'package:eatvio_app/features/consultations_archive/models/statistic_model.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ConsultationsArchiveCubit extends Cubit { + final ConsultationsRepository consultationsRepository; + ConsultationsArchiveCubit({ + required this.consultationsRepository, + }) : super( + ConsultationsArchiveState( + statisticModel: StatisticModel( + total: 0, + canceled: 0, + inProgress: 0, + finished: 0, + ), + ), + ); + + Future getConsultations() async { + try { + emit(state.copyWith(isLoading: true)); + final consultations = await consultationsRepository.getFilteredConsultations( + date: state.periodFilterStatus?.value, + state: state.consultationFilterStatus?.value, + dateComparison: state.periodFilterStatus == PeriodFilterStatus.all ? null : FiltersComparisonEnum.greaterThanOrEqual, + stateComparison: state.consultationFilterStatus != ConsultationFilterStatus.inProgress && state.consultationFilterStatus == ConsultationFilterStatus.all + ? null + : FiltersComparisonEnum.equal, + ); + emit(state.copyWith(consultations: consultations, isLoading: false)); + } catch (e) { + emit(state.copyWith(isLoading: false)); + log(e.toString()); + } + } + + Future setConsultationsPeriod(PeriodFilterStatus period) async { + emit(state.copyWith(periodFilterStatus: period)); + await getConsultations(); + } + + Future setConsultationsStatus(ConsultationFilterStatus status) async { + emit(state.copyWith(consultationFilterStatus: status)); + await getConsultations(); + } + + Future getStatistic() async { + try { + emit(state.copyWith(isLoading: true)); + final statistic = await consultationsRepository.getStatistic(); + emit(state.copyWith(statisticModel: statistic, isLoading: false)); + } catch (e) { + emit(state.copyWith(isLoading: false)); + log(e.toString()); + } + } +} + +enum PeriodFilterStatus { + all, + month, + week; + + String get name => switch (this) { + PeriodFilterStatus.all => 'Все периоды', + PeriodFilterStatus.month => 'За месяц', + PeriodFilterStatus.week => 'За неделю', + }; + + String? get value => switch (this) { + PeriodFilterStatus.all => null, + PeriodFilterStatus.month => DateTime.now().subtract(const Duration(days: 30)).format('yyyy-MM-dd'), + PeriodFilterStatus.week => DateTime.now().subtract(const Duration(days: 7)).format('yyyy-MM-dd'), + }; +} + +enum ConsultationFilterStatus { + all, + canceled, + finished, + inProgress; + + String get name => switch (this) { + ConsultationFilterStatus.all => 'Все консультации', + ConsultationFilterStatus.canceled => 'Отменён', + ConsultationFilterStatus.finished => 'Завершён', + ConsultationFilterStatus.inProgress => 'В процессе', + }; + + String? get value => switch (this) { + ConsultationFilterStatus.all => null, + ConsultationFilterStatus.canceled => 'canceled_by_the_initiator', + ConsultationFilterStatus.finished => 'charge_was_collected', + ConsultationFilterStatus.inProgress => 'active', + }; +} diff --git a/lib/features/consultations_archive/cubit/consultations_archive_state.dart b/lib/features/consultations_archive/cubit/consultations_archive_state.dart new file mode 100644 index 00000000..df0e8048 --- /dev/null +++ b/lib/features/consultations_archive/cubit/consultations_archive_state.dart @@ -0,0 +1,21 @@ + +import 'package:eatvio_app/features/consultations/models/consultation_model.dart'; +import 'package:eatvio_app/features/consultations_archive/cubit/consultations_archive_cubit.dart'; +import 'package:eatvio_app/features/consultations_archive/models/statistic_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'consultations_archive_state.freezed.dart'; + +@freezed +abstract class ConsultationsArchiveState with _$ConsultationsArchiveState { + + factory ConsultationsArchiveState({ + @Default([]) List consultations, + @Default(false) isLoading, + @Default(null) String? error, + @Default(null) PeriodFilterStatus? periodFilterStatus, + @Default(null) ConsultationFilterStatus? consultationFilterStatus, + required StatisticModel statisticModel, + }) = _ConsultationsArchiveState; + +} \ No newline at end of file diff --git a/lib/features/consultations_archive/models/statistic_model.dart b/lib/features/consultations_archive/models/statistic_model.dart new file mode 100644 index 00000000..34cc748a --- /dev/null +++ b/lib/features/consultations_archive/models/statistic_model.dart @@ -0,0 +1,16 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'statistic_model.freezed.dart'; +part 'statistic_model.g.dart'; + +@freezed +abstract class StatisticModel with _$StatisticModel { + factory StatisticModel({ + required int total, + @JsonKey(name: 'in_progress') required int inProgress, + required int finished, + required int canceled, + }) = _StatisticModel; + + factory StatisticModel.fromJson(Map json) => _$StatisticModelFromJson(json); +} \ No newline at end of file diff --git a/lib/features/consultations_archive/presentation/consultant_finance_screen.dart b/lib/features/consultations_archive/presentation/consultant_finance_screen.dart new file mode 100644 index 00000000..bd3bc944 --- /dev/null +++ b/lib/features/consultations_archive/presentation/consultant_finance_screen.dart @@ -0,0 +1,281 @@ +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/consultations_archive/cubit/consultations_archive_cubit.dart'; +import 'package:eatvio_app/features/consultations_archive/cubit/consultations_archive_state.dart'; +import 'package:eatvio_app/features/consultations_archive/presentation/status_widget.dart'; +import 'package:eatvio_app/features/my_finance/entities/consultant_finance_entity.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ConsultationsArchive extends StatefulWidget { + const ConsultationsArchive({super.key}); + + @override + State createState() => _ConsultationsArchiveState(); +} + +class _ConsultationsArchiveState extends State { + @override + Widget build(BuildContext context) { + final s = S.of(context); + return BlocProvider( + create: (context) => sl() + ..getConsultations() + ..getStatistic(), + child: BlocBuilder( + builder: (context, state) { + return Scaffold( + backgroundColor: const Color(0xFFF0F2F5), + appBar: AppBar( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadiusDirectional.vertical( + bottom: Radius.circular(12), + ), + ), + title: const Text('Архив консультаций'), + centerTitle: true, + ), + body: state.isLoading + ? const Center( + child: Loader(), + ) + : SingleChildScrollView( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Column( + children: [ + Material( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + 'Всего консультаций: ${state.statisticModel.total}', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Color(0xFF6D6F71), + height: 1, + ), + ), + ), + const SizedBox(height: 12), + Row( + children: [ + const SizedBox(width: 16), + Material( + color: const Color(0xFF0BB450).withAlpha(11), + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + child: Text( + 'Завершено: ${state.statisticModel.finished}', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: Color(0xFF0BB450), + height: 1, + ), + ), + ), + ), + const SizedBox(width: 8), + Material( + color: const Color(0xFFD01717).withAlpha(11), + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + child: Text( + 'Отменено: ${state.statisticModel.canceled}', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: Color(0xFFD01717), + height: 1, + ), + ), + ), + ), + ], + ), + const SizedBox(height: 16), + ], + ), + ), + 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( + 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().setConsultationsPeriod(value); + }, + value: state.periodFilterStatus ?? PeriodFilterStatus.values.first, + ), + const SizedBox(height: 12), + CustomDropDownButton( + items: ConsultationFilterStatus.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().setConsultationsStatus(value); + }, + value: state.consultationFilterStatus, + ), + const SizedBox(height: 12), + if (state.consultations.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: [ + Text( + state.consultations[index].id.toString(), + style: const TextStyle( + color: Color(0xFF323F4B), + height: 1, + fontWeight: FontWeight.w500, + ), + ), + Text( + state.consultations[index].startDate.format('yyyy.MM.dd'), + style: const TextStyle( + color: Color(0xFF6D6F71), + height: 1, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + state.consultations[index].planTitle, + style: TextStyle( + color: appTheme.blueA400, + height: 1, + fontSize: 18, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 8), + Text( + 'Покупатель: ${state.consultations[index].userName}', + style: const TextStyle( + color: Color(0xFF323F4B), + height: 1, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 8), + Text( + '${state.consultations[index].duration} ${getDayForm(state.consultations[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.consultations[index].consultantPayment.toStringAsFixed(2)} ₽', + 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.consultations.length, + ) + else + const Padding( + padding: EdgeInsets.symmetric(vertical: 40.0), + child: Text( + 'У вас нет таких консультаций', + style: TextStyles.textMMedium, + ), + ), + ], + ), + ), + ), + ), + ], + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/features/consultations_archive/presentation/status_widget.dart b/lib/features/consultations_archive/presentation/status_widget.dart new file mode 100644 index 00000000..6559e731 --- /dev/null +++ b/lib/features/consultations_archive/presentation/status_widget.dart @@ -0,0 +1,64 @@ +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/features/plans/enums/chosen_plan_state_enum.dart'; +import 'package:flutter/material.dart'; + +class StatusWidget extends StatelessWidget { + const StatusWidget({super.key, required this.status}); + final ChosenPlanStateEnum status; + + @override + Widget build(BuildContext context) { + final activeStates = ChosenPlanStateEnum.getActiveStates(); + if (status == ChosenPlanStateEnum.stateCanceledByTheInitiator) { + return Material( + color: appTheme.lightRed, + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + child: Text( + 'Отменён', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: appTheme.strongRed, + height: 1, + ), + ), + ), + ); + } else if (status == ChosenPlanStateEnum.stateChargeWasCollected) { + return Material( + color: const Color(0xFF0BB450).withAlpha(11), + borderRadius: BorderRadius.circular(8), + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 4), + child: Text( + 'Успешно', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: Color(0xFF0BB450), + height: 1, + ), + ), + ), + ); + } + return Material( + color: appTheme.transparentBlue, + borderRadius: BorderRadius.circular(8), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), + child: Text( + 'В процессе', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: appTheme.blueA400, + height: 1, + ), + ), + ), + ); + } +} diff --git a/lib/features/my_finance/presentation/pages/consultant_finance_screen.dart b/lib/features/my_finance/presentation/pages/consultant_finance_screen.dart deleted file mode 100644 index 3bd9dc47..00000000 --- a/lib/features/my_finance/presentation/pages/consultant_finance_screen.dart +++ /dev/null @@ -1,259 +0,0 @@ -import 'package:collection/collection.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/widgets/custom_drop_down_button.dart'; -import 'package:eatvio_app/features/my_finance/entities/consultant_finance_entity.dart'; -import 'package:flutter/material.dart'; - -class ConsultantFinanceScreen extends StatefulWidget { - const ConsultantFinanceScreen({super.key}); - - @override - State createState() => _ConsultantFinanceScreenState(); -} - -class _ConsultantFinanceScreenState extends State { - final List periods = ['За всё время', 'За месяц', 'За неделю']; - final List statuses = ['Все консультации', 'Отменён', 'Завершён', 'В процессе']; - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: const Color(0xFFF0F2F5), - appBar: AppBar( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadiusDirectional.vertical( - bottom: Radius.circular(12), - ), - ), - title: const Text('Архив консультаций'), - centerTitle: true, - ), - body: SingleChildScrollView( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Column( - children: [ - Material( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Text( - 'Всего консультаций: ${testFinanceEntity.totalConsultationsCount}', - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Color(0xFF6D6F71), - height: 1, - ), - ), - ), - const SizedBox(height: 12), - Row( - children: [ - const SizedBox(width: 16), - Material( - color: const Color(0xFF0BB450).withAlpha(11), - borderRadius: BorderRadius.circular(8), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), - child: Text( - 'Завершено: ${testFinanceEntity.completedCount}', - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: Color(0xFF0BB450), - height: 1, - ), - ), - ), - ), - const SizedBox(width: 8), - Material( - color: const Color(0xFFD01717).withAlpha(11), - borderRadius: BorderRadius.circular(8), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), - child: Text( - 'Отменено: ${testFinanceEntity.canceledCount}', - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: Color(0xFFD01717), - height: 1, - ), - ), - ), - ), - ], - ), - const SizedBox(height: 16), - ], - ), - ), - 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( - items: periods - .mapIndexed( - (index, value) => DropdownMenuItem( - value: periods[index], - child: Text( - periods[index], - style: TextStyles.textMMedium.copyWith(color: appTheme.black), - ), - ), - ) - .toList(), - onChanged: (value) {}, - value: periods.first, - ), - const SizedBox(height: 12), - CustomDropDownButton( - items: statuses - .mapIndexed( - (index, value) => DropdownMenuItem( - value: statuses[index], - child: Text( - statuses[index], - style: TextStyles.textMMedium.copyWith(color: appTheme.black), - ), - ), - ) - .toList(), - onChanged: (value) {}, - value: statuses.first, - ), - const SizedBox(height: 12), - ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemBuilder: (context, index) => 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: [ - Text( - testFinanceEntity.consultationsList[index].id, - style: const TextStyle( - color: Color(0xFF323F4B), - height: 1, - fontWeight: FontWeight.w500, - ), - ), - Text( - testFinanceEntity.consultationsList[index].date.format('dd.MM.yyy', 'RU'), - style: const TextStyle( - color: Color(0xFF6D6F71), - height: 1, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - const SizedBox(height: 8), - Text( - testFinanceEntity.consultationsList[index].title, - style: TextStyle( - color: appTheme.blueA400, - height: 1, - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ), - const SizedBox(height: 8), - Text( - 'Покупатель: ${testFinanceEntity.consultationsList[index].clientName}', - style: const TextStyle( - color: Color(0xFF323F4B), - height: 1, - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 8), - Text( - '${testFinanceEntity.consultationsList[index].planDuration} дней', - 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( - '${testFinanceEntity.consultationsList[index].price} ₽', - style: const TextStyle( - color: Color(0xFF323F4B), - height: 1, - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ), - Material( - color: const Color(0xFF0BB450).withAlpha(11), - borderRadius: BorderRadius.circular(8), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), - child: Text( - testFinanceEntity.consultationsList[index].status.name, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: Color(0xFF0BB450), - height: 1, - ), - ), - ), - ), - ], - ), - ], - ), - ), - ), - separatorBuilder: (context, index) => const SizedBox(height: 12), - itemCount: testFinanceEntity.consultationsList.length, - ), - ], - ), - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/features/plans/enums/chosen_plan_state_enum.dart b/lib/features/plans/enums/chosen_plan_state_enum.dart index 308df73c..0b768d5f 100644 --- a/lib/features/plans/enums/chosen_plan_state_enum.dart +++ b/lib/features/plans/enums/chosen_plan_state_enum.dart @@ -55,4 +55,16 @@ enum ChosenPlanStateEnum { return true; } } + + static List getActiveStates() { + return [ + ChosenPlanStateEnum.statePaid, + ChosenPlanStateEnum.stateStarted, + ChosenPlanStateEnum.stateInProgress, + ChosenPlanStateEnum.stateLastDayStarted, + ChosenPlanStateEnum.stateLastDayFinished, + ChosenPlanStateEnum.stateComplained, + ChosenPlanStateEnum.stateDone + ]; + } }