diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/blocs/update_bookable_spaces/update_bookable_spaces_event.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/blocs/update_bookable_spaces/update_bookable_spaces_event.dart new file mode 100644 index 00000000..1076f589 --- /dev/null +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/blocs/update_bookable_spaces/update_bookable_spaces_event.dart @@ -0,0 +1,15 @@ +part of 'update_bookable_spaces_bloc.dart'; + +sealed class UpdateBookableSpaceEvent extends Equatable { + const UpdateBookableSpaceEvent(); + + @override + List get props => []; +} + +class UpdateBookableSpace extends UpdateBookableSpaceEvent { + final UpdateBookableSpaceParam updatedParams; + const UpdateBookableSpace({ + required this.updatedParams, + }); +} diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/blocs/update_bookable_spaces/update_bookable_spaces_state.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/blocs/update_bookable_spaces/update_bookable_spaces_state.dart new file mode 100644 index 00000000..e173dd21 --- /dev/null +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/blocs/update_bookable_spaces/update_bookable_spaces_state.dart @@ -0,0 +1,30 @@ +part of 'update_bookable_spaces_bloc.dart'; + +sealed class UpdateBookableSpacesState extends Equatable { + const UpdateBookableSpacesState(); + + @override + List get props => []; +} + +final class UpdateBookableSpacesInitial extends UpdateBookableSpacesState {} + +final class UpdateBookableSpaceLoading extends UpdateBookableSpacesState { + final String updatingSpaceUuid; + + const UpdateBookableSpaceLoading(this.updatingSpaceUuid); +} + +final class UpdateBookableSpaceSuccess extends UpdateBookableSpacesState { + final BookableSpaceConfig bookableSpaceConfig; + const UpdateBookableSpaceSuccess({ + required this.bookableSpaceConfig, + }); +} + +final class UpdateBookableSpaceFailure extends UpdateBookableSpacesState { + final String error; + const UpdateBookableSpaceFailure({ + required this.error, + }); +} diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/main_manage_bookable_widgets/bottom_pagination_part_widget.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/main_manage_bookable_widgets/bottom_pagination_part_widget.dart new file mode 100644 index 00000000..247d91a6 --- /dev/null +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/main_manage_bookable_widgets/bottom_pagination_part_widget.dart @@ -0,0 +1,158 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/bookable_spaces_bloc/bookable_spaces_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class BottomPaginationPartWidget extends StatelessWidget { + const BottomPaginationPartWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is BookableSpacesLoaded) { + final totalPages = state.bookableSpacesList.totalPages; + final currentPage = state.bookableSpacesList.page; + + List paginationItems = []; + + // « Two pages back + if (currentPage > 2) { + paginationItems.add( + _buildArrowButton( + label: '«', + onTap: () { + context.read().add( + LoadBookableSpacesEvent( + BookableSpacesParams(currentPage: currentPage - 2), + ), + ); + }, + ), + ); + } + + // < One page back + if (currentPage > 1) { + paginationItems.add( + _buildArrowButton( + label: '<', + onTap: () { + context.read().add( + LoadBookableSpacesEvent( + BookableSpacesParams(currentPage: currentPage - 1), + ), + ); + }, + ), + ); + } + + // Page numbers + for (int i = 1; i <= totalPages; i++) { + paginationItems.add( + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: GestureDetector( + onTap: () { + if (i != currentPage) { + context.read().add( + LoadBookableSpacesEvent( + BookableSpacesParams(currentPage: i), + ), + ); + } + }, + child: Container( + width: 30, + height: 30, + alignment: Alignment.center, + decoration: BoxDecoration( + color: i == currentPage + ? ColorsManager.dialogBlueTitle + : Colors.grey[300], + borderRadius: BorderRadius.circular(8), + ), + child: Text( + '$i', + style: TextStyle( + color: i == currentPage ? Colors.white : Colors.black, + fontWeight: i == currentPage + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ), + ), + ), + ); + } + + // > One page forward + if (currentPage < totalPages) { + paginationItems.add( + _buildArrowButton( + label: '>', + onTap: () { + context.read().add( + LoadBookableSpacesEvent( + BookableSpacesParams(currentPage: currentPage + 1), + ), + ); + }, + ), + ); + } + + // » Two pages forward + if (currentPage + 1 < totalPages) { + paginationItems.add( + _buildArrowButton( + label: '»', + onTap: () { + context.read().add( + LoadBookableSpacesEvent( + BookableSpacesParams(currentPage: currentPage + 2), + ), + ); + }, + ), + ); + } + + return Row( + mainAxisAlignment: MainAxisAlignment.end, + children: paginationItems, + ); + } else { + return const SizedBox.shrink(); + } + }, + ); + } + + Widget _buildArrowButton( + {required String label, required VoidCallback onTap}) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(8), + ), + child: Text( + label, + style: const TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/main_manage_bookable_widgets/table_part_widget.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/main_manage_bookable_widgets/table_part_widget.dart new file mode 100644 index 00000000..3534f987 --- /dev/null +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/main_manage_bookable_widgets/table_part_widget.dart @@ -0,0 +1,177 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/bookable_spaces_params.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/update_bookable_space_param.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/bookable_spaces_bloc/bookable_spaces_bloc.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/update_bookable_spaces/update_bookable_spaces_bloc.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/custom_data_table.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class TablePartWidget extends StatelessWidget { + const TablePartWidget({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is BookableSpacesLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is BookableSpacesError) { + return Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + Text(state.error), + const SizedBox( + height: 5, + ), + ElevatedButton( + onPressed: () => context + .read() + .add(LoadBookableSpacesEvent( + BookableSpacesParams(currentPage: 1), + )), + child: const Text('try Again')) + ]); + } else if (state is BookableSpacesLoaded) { + return CustomDataTable( + items: state.bookableSpacesList.data, + cellsWidgets: (space) => [ + DataCell( + Padding( + padding: const EdgeInsetsGeometry.only(left: 10), + child: Text( + space.spaceName, + style: const TextStyle(fontSize: 11), + )), + ), + DataCell(Padding( + padding: const EdgeInsetsGeometry.only(left: 10), + child: Text( + space.spaceVirtualAddress, + style: const TextStyle(fontSize: 11), + ))), + DataCell(Container( + padding: const EdgeInsetsGeometry.only(left: 10), + width: 200, + child: Wrap( + spacing: 4, + children: space.spaceConfig!.bookableDays + .map((day) => Text( + day, + style: const TextStyle(fontSize: 11), + )) + .toList(), + ), + )), + DataCell( + Padding( + padding: const EdgeInsetsGeometry.only(left: 10), + child: Text( + space.spaceConfig!.bookingStartTime!.format(context), + style: const TextStyle(fontSize: 11), + ), + ), + ), + DataCell( + Padding( + padding: const EdgeInsetsGeometry.only(left: 10), + child: Text( + space.spaceConfig!.bookingEndTime!.format(context), + style: const TextStyle(fontSize: 11), + ), + ), + ), + DataCell(Padding( + padding: const EdgeInsetsGeometry.only(left: 10), + child: Text( + '${space.spaceConfig!.cost} Points', + style: const TextStyle(fontSize: 11), + ))), + DataCell(Center( + child: Transform.scale( + scale: 0.7, + child: BlocConsumer( + listener: (context, updateState) { + if (updateState is UpdateBookableSpaceSuccess) { + context.read().add( + InsertUpdatedSpaceEvent( + bookableSpaces: state.bookableSpacesList, + bookableSpace: space, + updatedBookableSpaceConfig: + updateState.bookableSpaceConfig, + ), + ); + } + }, + builder: (context, updateState) { + final isLoading = + updateState is UpdateBookableSpaceLoading && + updateState.updatingSpaceUuid == space.spaceUuid; + if (isLoading) { + return const Center(child: CircularProgressIndicator()); + } + return Switch( + trackOutlineColor: + WidgetStateProperty.resolveWith( + (Set states) { + return ColorsManager.whiteColors; + }), + value: space.spaceConfig!.availability, + activeTrackColor: ColorsManager.blueColor, + inactiveTrackColor: ColorsManager.grayBorder, + thumbColor: WidgetStateProperty.resolveWith( + (Set states) { + return ColorsManager.whiteColors; + }), + onChanged: (value) { + context.read().add( + UpdateBookableSpace( + updatedParams: UpdateBookableSpaceParam( + spaceUuid: space.spaceUuid, + availability: value, + )), + ); + }, + ); + }, + ), + ), + )), + DataCell(Center( + child: ElevatedButton( + onPressed: () {}, + style: ElevatedButton.styleFrom( + padding: EdgeInsets.zero, + fixedSize: const Size(50, 30), + elevation: 1, + ), + child: SvgPicture.asset( + Assets.settings, + height: 15, + color: ColorsManager.blue1, + ), + ), + )), + ], + columnsTitles: const [ + 'Space', + 'Space Virtual Address', + 'Bookable Days', + 'Booking Start Time', + 'Booking End Time', + 'Cost', + 'Availability', + 'Settings', + ], + ); + } else { + return const SizedBox(); + } + }, + ); + } +} diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/main_manage_bookable_widgets/top_part_widget.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/main_manage_bookable_widgets/top_part_widget.dart new file mode 100644 index 00000000..506773dc --- /dev/null +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/main_manage_bookable_widgets/top_part_widget.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/pages/access_management/booking_system/view/widgets/icon_text_button.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/bookable_spaces_bloc/bookable_spaces_bloc.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/screens/setup_bookable_spaces_dialog.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class TopPartWidget extends StatelessWidget { + const TopPartWidget({ + super.key, + required this.pageController, + }); + + final PageController pageController; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsetsGeometry.symmetric(vertical: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + child: SvgPicture.asset( + Assets.backButtonIcon, + height: 15, + ), + onPressed: () { + pageController.jumpToPage(1); + }), + const SizedBox( + width: 10, + ), + Text( + 'Manage Bookable Spaces', + style: TextStyle( + fontSize: 18, + color: ColorsManager.vividBlue.withValues( + alpha: 0.7, + ), + fontWeight: FontWeight.w700), + ) + ], + ), + SvgTextButton( + svgSize: 15, + fontSize: 10, + fontWeight: FontWeight.bold, + svgAsset: Assets.addButtonIcon, + label: 'Set Up a Bookable Spaces', + onPressed: () { + final bloc = context.read(); + showDialog( + context: context, + builder: (context) => BlocProvider.value( + value: bloc, + child: SetupBookableSpacesDialog(), + ), + ); + }, + ) + ], + ), + ); + } +} diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/points_part_widget.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/points_part_widget.dart new file mode 100644 index 00000000..06d11127 --- /dev/null +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/points_part_widget.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/cubit/toggle_points_switch_cubit.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/search_unbookable_spaces_widget.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class PointsPartWidget extends StatelessWidget { + const PointsPartWidget({ + super.key, + required this.pointsController, + }); + + final TextEditingController pointsController; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + if (state is ActivatePointsSwitch) + Text( + '* ', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: Colors.red), + ) + else + const SizedBox( + width: 11, + ), + const Text('Points/hrs'), + ], + ), + Transform.scale( + scale: 0.7, + child: Switch( + trackOutlineColor: WidgetStateProperty.resolveWith( + (Set states) { + return ColorsManager.whiteColors; + }), + activeTrackColor: ColorsManager.blueColor, + inactiveTrackColor: ColorsManager.grayBorder, + thumbColor: WidgetStateProperty.resolveWith( + (Set states) { + return ColorsManager.whiteColors; + }), + value: context.watch().switchValue, + onChanged: (value) { + if (value) { + context + .read() + .activateSwitch(); + context + .read() + .selectedBookableSpaces + .forEach( + (e) => e.spaceConfig!.cost = -1, + ); + context + .read() + .add(CheckConfigurValidityEvent()); + } else { + context + .read() + .unActivateSwitch(); + pointsController.clear(); + context + .read() + .selectedBookableSpaces + .forEach( + (e) => e.spaceConfig!.cost = 0, + ); + context + .read() + .add(CheckConfigurValidityEvent()); + } + }, + ), + ) + ], + ), + const SizedBox( + height: 5, + ), + if (state is ActivatePointsSwitch) + SearchUnbookableSpacesWidget( + title: 'Ex: 0', + height: 40, + onChanged: (p0) { + context + .read() + .selectedBookableSpaces + .forEach( + (e) => e.spaceConfig!.cost = int.parse( + pointsController.text.isEmpty + ? '0' + : pointsController.text, + ), + ); + context + .read() + .add(CheckConfigurValidityEvent()); + }, + controller: pointsController, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + suffix: const SizedBox(), + ) + else + const SizedBox(), + ], + ); + }, + ); + } +} diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/unbookable_list_widget.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/unbookable_list_widget.dart new file mode 100644 index 00000000..92bb66e3 --- /dev/null +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/unbookable_list_widget.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/check_box_space_widget.dart'; +import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart'; + +class UnbookableListWidget extends StatelessWidget { + final PaginatedDataModel nonBookableSpaces; + const UnbookableListWidget({ + super.key, + required this.scrollController, + required this.nonBookableSpaces, + }); + + final ScrollController scrollController; + + @override + Widget build(BuildContext context) { + return Container( + width: 490, + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.vertical( + bottom: Radius.circular(20), + ), + ), + padding: const EdgeInsets.only(top: 10, left: 20, bottom: 5), + child: ListView.separated( + separatorBuilder: (context, index) => const SizedBox( + height: 5, + ), + controller: scrollController, + itemCount: nonBookableSpaces.data.length, + itemBuilder: (context, index) { + if (index < nonBookableSpaces.data.length) { + return CheckBoxSpaceWidget( + nonBookableSpace: nonBookableSpaces.data[index], + selectedSpaces: + context.read().selectedBookableSpaces, + ); + } else { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 10), + child: Center(child: CircularProgressIndicator()), + ); + } + }, + ), + ); + } +} diff --git a/lib/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart b/lib/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart index ac35975d..80fcee90 100644 --- a/lib/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart +++ b/lib/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart @@ -40,4 +40,22 @@ class PaginatedDataModel extends Equatable { totalItems, totalPages, ]; + + PaginatedDataModel copyWith({ + List? data, + int? page, + int? size, + bool? hasNext, + int? totalItems, + int? totalPages, + }) { + return PaginatedDataModel( + data: data ?? this.data, + page: page ?? this.page, + size: size ?? this.size, + hasNext: hasNext ?? this.hasNext, + totalItems: totalItems ?? this.totalItems, + totalPages: totalPages ?? this.totalPages, + ); + } }