diff --git a/lib/pages/access_management/booking_system/view/widgets/icon_text_button.dart b/lib/pages/access_management/booking_system/view/widgets/icon_text_button.dart index afccafdb..3713b7f4 100644 --- a/lib/pages/access_management/booking_system/view/widgets/icon_text_button.dart +++ b/lib/pages/access_management/booking_system/view/widgets/icon_text_button.dart @@ -12,10 +12,13 @@ class SvgTextButton extends StatelessWidget { final double borderRadius; final List boxShadow; final double svgSize; - + final double? fontSize; + final FontWeight? fontWeight; const SvgTextButton({ super.key, required this.svgAsset, + this.fontSize, + this.fontWeight, required this.label, required this.onPressed, this.backgroundColor = ColorsManager.circleRolesBackground, @@ -60,8 +63,8 @@ class SvgTextButton extends StatelessWidget { label, style: TextStyle( color: labelColor, - fontSize: 16, - fontWeight: FontWeight.w500, + fontSize: fontSize ?? 16, + fontWeight: fontWeight ?? FontWeight.w500, ), ), ], diff --git a/lib/pages/access_management/manage_bookable_spaces/data/remote_bookable_spaces_service.dart b/lib/pages/access_management/manage_bookable_spaces/data/remote_bookable_spaces_service.dart index 49563e45..44656482 100644 --- a/lib/pages/access_management/manage_bookable_spaces/data/remote_bookable_spaces_service.dart +++ b/lib/pages/access_management/manage_bookable_spaces/data/remote_bookable_spaces_service.dart @@ -10,15 +10,17 @@ import 'package:syncrow_web/utils/constants/api_const.dart'; class RemoteBookableSpacesService implements BookableSpacesService { final HTTPService _httpService; RemoteBookableSpacesService(this._httpService); - static const _defaultErrorMessage = 'Failed to load tags'; + static const _defaultErrorMessage = 'Failed to load Bookable Spaces'; @override Future> load( BookableSpacesParams param) async { try { final response = await _httpService.get( - //TODO: you have to Chage this API call Path - path: ApiEndpoints.listTags, - //*************|********** */ + path: ApiEndpoints.bookableSpaces, + queryParameters: { + 'configured': true, + 'page': param.currentPage, + }, expectedResponseModel: (json) { final result = json as Map; return PaginatedDataModel.fromJson( diff --git a/lib/pages/access_management/manage_bookable_spaces/data/remote_non_bookable_spaces.dart b/lib/pages/access_management/manage_bookable_spaces/data/remote_non_bookable_spaces.dart index 5224eb2b..cd06dd7f 100644 --- a/lib/pages/access_management/manage_bookable_spaces/data/remote_non_bookable_spaces.dart +++ b/lib/pages/access_management/manage_bookable_spaces/data/remote_non_bookable_spaces.dart @@ -18,9 +18,12 @@ class RemoteNonBookableSpaces implements NonBookableSpacesService { NonBookableSpacesParams params) async { try { final response = await _httpService.get( - //TODO: you have to Chage this API call Path - path: ApiEndpoints.listTags, - //*************|********** */ + path: ApiEndpoints.bookableSpaces, + queryParameters: { + 'configured': false, + 'page': params.currentPage, + 'search': params.searchedWords, + }, expectedResponseModel: (json) { final result = json as Map; return PaginatedDataModel.fromJson( @@ -50,7 +53,7 @@ class RemoteNonBookableSpaces implements NonBookableSpacesService { SendBookableSpacesToApiParams params) async { try { await _httpService.post( - path: ApiEndpoints.addBookableSpaces, + path: ApiEndpoints.bookableSpaces, body: params.toJson(), expectedResponseModel: (p0) {}, ); diff --git a/lib/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_config.dart b/lib/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_config.dart index 958822bd..07008a45 100644 --- a/lib/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_config.dart +++ b/lib/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_config.dart @@ -3,30 +3,28 @@ import 'package:flutter/material.dart'; class BookableSpaceConfig { String configUuid; List bookableDays; - TimeOfDay bookingStartTime; - TimeOfDay bookingEndTime; + TimeOfDay? bookingStartTime; + TimeOfDay? bookingEndTime; int cost; bool availability; BookableSpaceConfig({ required this.configUuid, required this.availability, required this.bookableDays, - required this.bookingEndTime, - required this.bookingStartTime, + this.bookingEndTime, + this.bookingStartTime, required this.cost, }); factory BookableSpaceConfig.zero() => BookableSpaceConfig( configUuid: '', bookableDays: [], availability: false, - bookingEndTime: TimeOfDay.now(), - bookingStartTime: TimeOfDay.now(), cost: -1, ); factory BookableSpaceConfig.fromJson(Map json) => BookableSpaceConfig( configUuid: json['uuid'] as String, - bookableDays: json['daysAvailable'] as List, + bookableDays: (json['daysAvailable'] as List).cast(), availability: (json['active'] as bool?) ?? false, bookingEndTime: parseTimeOfDay(json['startTime'] as String), bookingStartTime: parseTimeOfDay(json['endTime'] as String), @@ -41,5 +39,8 @@ class BookableSpaceConfig { } bool get isValid => - configUuid.isNotEmpty && bookableDays.isNotEmpty && cost > 0; + bookableDays.isNotEmpty && + cost > 0 && + bookingStartTime != null && + bookingEndTime != null; } diff --git a/lib/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart b/lib/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart index f108ab08..2c4be010 100644 --- a/lib/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart +++ b/lib/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart @@ -3,28 +3,29 @@ import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domai class BookableSpacemodel { String spaceUuid; String spaceName; - BookableSpaceConfig spaceConfig; + BookableSpaceConfig? spaceConfig; String spaceVirtualAddress; BookableSpacemodel({ required this.spaceUuid, required this.spaceName, - required this.spaceConfig, + this.spaceConfig, required this.spaceVirtualAddress, }); factory BookableSpacemodel.zero() => BookableSpacemodel( spaceUuid: '', spaceName: '', - spaceConfig: BookableSpaceConfig.zero(), spaceVirtualAddress: '', ); factory BookableSpacemodel.fromJson(Map json) => BookableSpacemodel( spaceUuid: json['uuid'] as String, spaceName: json['spaceName'] as String, - spaceConfig: BookableSpaceConfig.fromJson( - json['bookableConfig'] as Map), - spaceVirtualAddress: json['spaceVirtualAddress'] as String, + spaceConfig: json['bookableConfig'] == null + ? BookableSpaceConfig.zero() + : BookableSpaceConfig.fromJson( + json['bookableConfig'] as Map), + spaceVirtualAddress: json['virtualLocation'] as String, ); static List fromJsonList(List jsonList) => @@ -38,5 +39,6 @@ class BookableSpacemodel { spaceUuid.isNotEmpty && spaceName.isNotEmpty && spaceVirtualAddress.isNotEmpty && - spaceConfig.isValid; + spaceConfig != null && + spaceConfig!.isValid; } diff --git a/lib/pages/access_management/manage_bookable_spaces/domain/service/send_bookable_spaces_to_api_params.dart b/lib/pages/access_management/manage_bookable_spaces/domain/service/send_bookable_spaces_to_api_params.dart index 73e590eb..38f099fb 100644 --- a/lib/pages/access_management/manage_bookable_spaces/domain/service/send_bookable_spaces_to_api_params.dart +++ b/lib/pages/access_management/manage_bookable_spaces/domain/service/send_bookable_spaces_to_api_params.dart @@ -20,14 +20,14 @@ class SendBookableSpacesToApiParams { return SendBookableSpacesToApiParams( spaceUuids: bookableSpaces.map((space) => space.spaceUuid).toList(), daysAvailable: bookableSpaces - .expand((space) => space.spaceConfig.bookableDays) + .expand((space) => space.spaceConfig!.bookableDays) .toSet() .toList(), startTime: formatTimeOfDayTo24HourString( - bookableSpaces.first.spaceConfig.bookingStartTime), + bookableSpaces.first.spaceConfig!.bookingStartTime!), endTime: formatTimeOfDayTo24HourString( - bookableSpaces.first.spaceConfig.bookingEndTime), - points: bookableSpaces.first.spaceConfig.cost, + bookableSpaces.first.spaceConfig!.bookingEndTime!), + points: bookableSpaces.first.spaceConfig!.cost, ); } diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart index 8c33e54d..9d8437fe 100644 --- a/lib/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart @@ -20,27 +20,49 @@ class NonBookableSpacesBloc on(_onAddToBookableSpaceEvent); on(_onRemoveFromBookableSpaceEvent); on(_onSendBookableSpacesToApi); + on(_onCheckConfigurValidityEvent); } - TimeOfDay get endTime => - selectedBookableSpaces.first.spaceConfig.bookingEndTime; + TimeOfDay? get endTime => + selectedBookableSpaces.first.spaceConfig!.bookingEndTime; - TimeOfDay get startTime => - selectedBookableSpaces.first.spaceConfig.bookingStartTime; + TimeOfDay? get startTime => + selectedBookableSpaces.first.spaceConfig!.bookingStartTime; Future _onLoadUnBookableSpacesEvent(LoadUnBookableSpacesEvent event, Emitter emit) async { - emit(NonBookableSpacesLoading()); - try { - final nonBookableSpacesList = await nonBookableSpacesService.load( - event.nonBookableSpacesParams, - ); - emit( - NonBookableSpacesLoaded(nonBookableSpaces: nonBookableSpacesList), - ); - } catch (e) { - emit( - NonBookableSpacesError(e.toString()), - ); + if (state is NonBookableSpacesLoaded) { + final currState = state as NonBookableSpacesLoaded; + try { + emit(NonBookableSpacesLoading( + lastNonBookableSpaces: currState.nonBookableSpaces)); + + final nonBookableSpacesList = await nonBookableSpacesService.load( + event.nonBookableSpacesParams, + ); + nonBookableSpacesList.data.addAll(currState.nonBookableSpaces.data); + + emit( + NonBookableSpacesLoaded(nonBookableSpaces: nonBookableSpacesList), + ); + } catch (e) { + emit( + NonBookableSpacesError(e.toString()), + ); + } + } else { + try { + emit(const NonBookableSpacesLoading()); + final nonBookableSpacesList = await nonBookableSpacesService.load( + event.nonBookableSpacesParams, + ); + emit( + NonBookableSpacesLoaded(nonBookableSpaces: nonBookableSpacesList), + ); + } catch (e) { + emit( + NonBookableSpacesError(e.toString()), + ); + } } } @@ -50,7 +72,7 @@ class NonBookableSpacesBloc ) { if (state is NonBookableSpacesLoaded) { final currentState = state as NonBookableSpacesLoaded; - + emit(AddNonBookableSpaceIntoBookableState()); final updatedSelectedSpaces = List.from(currentState.selectedBookableSpaces) ..add(event.nonBookableSpace); @@ -70,7 +92,10 @@ class NonBookableSpacesBloc Emitter emit) { if (state is NonBookableSpacesLoaded) { final currentState = state as NonBookableSpacesLoaded; - currentState.selectedBookableSpaces.remove(event.bookableSpace); + emit(RemoveBookableSpaceIntoNonBookableState()); + if (currentState.selectedBookableSpaces.isNotEmpty) { + currentState.selectedBookableSpaces.remove(event.bookableSpace); + } selectedBookableSpaces.remove(event.bookableSpace); emit( NonBookableSpacesLoaded( @@ -83,17 +108,27 @@ class NonBookableSpacesBloc Future _onSendBookableSpacesToApi(SendBookableSpacesToApi event, Emitter emit) async { - emit(NonBookableSpacesLoading()); + emit(const NonBookableSpacesLoading()); try { await nonBookableSpacesService.sendBookableSpacesToApi( SendBookableSpacesToApiParams.fromBookableSpacesModel( selectedBookableSpaces, ), ); + emit(NonBookableSpacesInitial()); } catch (e) { emit( NonBookableSpacesError(e.toString()), ); } } + + void _onCheckConfigurValidityEvent( + CheckConfigurValidityEvent event, Emitter emit) { + if (selectedBookableSpaces.first.spaceConfig!.isValid) { + emit(ValidSaveButtonState()); + } else { + emit(UnValidSaveButtonState()); + } + } } diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_event.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_event.dart index 392cf4d4..4e204c55 100644 --- a/lib/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_event.dart +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_event.dart @@ -29,3 +29,5 @@ class RemoveFromBookableSpaceEvent extends NonBookableSpacesEvent { } class SendBookableSpacesToApi extends NonBookableSpacesEvent {} + +class CheckConfigurValidityEvent extends NonBookableSpacesEvent {} diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_state.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_state.dart index 850b114d..b8d7a518 100644 --- a/lib/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_state.dart +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_state.dart @@ -9,7 +9,12 @@ sealed class NonBookableSpacesState extends Equatable { final class NonBookableSpacesInitial extends NonBookableSpacesState {} -class NonBookableSpacesLoading extends NonBookableSpacesState {} +class NonBookableSpacesLoading extends NonBookableSpacesState { + final PaginatedDataModel? lastNonBookableSpaces; + const NonBookableSpacesLoading({ + this.lastNonBookableSpaces, + }); +} class NonBookableSpacesLoaded extends NonBookableSpacesState { final PaginatedDataModel nonBookableSpaces; @@ -24,3 +29,11 @@ class NonBookableSpacesError extends NonBookableSpacesState { final String error; const NonBookableSpacesError(this.error); } + +class AddNonBookableSpaceIntoBookableState extends NonBookableSpacesState {} + +class RemoveBookableSpaceIntoNonBookableState extends NonBookableSpacesState {} + +class ValidSaveButtonState extends NonBookableSpacesState {} + +class UnValidSaveButtonState extends NonBookableSpacesState {} diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/screens/manage_bookable_spaces_screen.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/screens/manage_bookable_spaces_screen.dart index dad161f1..9ed9cd2d 100644 --- a/lib/pages/access_management/manage_bookable_spaces/presentation/screens/manage_bookable_spaces_screen.dart +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/screens/manage_bookable_spaces_screen.dart @@ -3,13 +3,15 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.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/data/dummy_bookable_spaces_service.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/data/remote_bookable_spaces_service.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/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/pages/access_management/manage_bookable_spaces/presentation/widgets/custom_data_table.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; @@ -64,8 +66,7 @@ class _ManageBookableSpacesPageState extends State { rightBody: const NavigateHomeGridView(), scaffoldBody: BlocProvider( create: (context) => BookableSpacesBloc( - DummyBookableSpacesService(), - // RemoteBookableSpacesService(HTTPService()), + RemoteBookableSpacesService(HTTPService()), )..add(LoadBookableSpacesEvent( BookableSpacesParams(currentPage: 1), )), @@ -90,34 +91,66 @@ class ManageBookableSpacesWidget extends StatelessWidget { child: Column( children: [ Expanded( - flex: 1, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SvgTextButton( - svgAsset: Assets.backButtonIcon, - label: 'Booking Home', + flex: 10, + child: Padding( + padding: const EdgeInsetsGeometry.symmetric(vertical: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SvgTextButton( + svgSize: 15, + fontSize: 10, + fontWeight: FontWeight.bold, + svgAsset: Assets.backButtonIcon, + label: 'Booking Home', + onPressed: () { + context.pop(); + }), + SvgTextButton( + svgSize: 15, + fontSize: 10, + fontWeight: FontWeight.bold, + svgAsset: Assets.addButtonIcon, + label: 'Set Up a Bookable Spaces', onPressed: () { - context.pop(); - }), - SvgTextButton( - svgAsset: Assets.backButtonIcon, - label: 'Set Up a Bookable Spaces', - onPressed: () async => showDialog( - context: context, - builder: (context) => SetupBookableSpacesDialog(), - ), - ) - ], + final bloc = context.read(); + showDialog( + context: context, + builder: (context) => BlocProvider.value( + value: bloc, + child: SetupBookableSpacesDialog(), + ), + ); + }, + ) + ], + ), )), + const SizedBox( + height: 10, + ), Expanded( - flex: 9, + flex: 85, child: BlocBuilder( builder: (context, state) { if (state is BookableSpacesLoading) { - return const CircularProgressIndicator(); + return const Center(child: CircularProgressIndicator()); } else if (state is BookableSpacesError) { - return Text(state.error); + 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, @@ -125,19 +158,26 @@ class ManageBookableSpacesWidget extends StatelessWidget { DataCell( Padding( padding: const EdgeInsetsGeometry.only(left: 10), - child: Text(space.spaceName)), + child: Text( + space.spaceName, + style: const TextStyle(fontSize: 11), + )), ), DataCell(Padding( padding: const EdgeInsetsGeometry.only(left: 10), - child: Text(space.spaceVirtualAddress))), - DataCell(SizedBox( + 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 + children: space.spaceConfig!.bookableDays .map((day) => Text( day, - style: const TextStyle(fontSize: 12), + style: const TextStyle(fontSize: 11), )) .toList(), ), @@ -146,7 +186,9 @@ class ManageBookableSpacesWidget extends StatelessWidget { Padding( padding: const EdgeInsetsGeometry.only(left: 10), child: Text( - space.spaceConfig.bookingStartTime.format(context), + space.spaceConfig!.bookingStartTime! + .format(context), + style: const TextStyle(fontSize: 11), ), ), ), @@ -154,18 +196,31 @@ class ManageBookableSpacesWidget extends StatelessWidget { Padding( padding: const EdgeInsetsGeometry.only(left: 10), child: Text( - space.spaceConfig.bookingEndTime.format(context), + space.spaceConfig!.bookingEndTime!.format(context), + style: const TextStyle(fontSize: 11), ), ), ), DataCell(Padding( padding: const EdgeInsetsGeometry.only(left: 10), - child: Text('${space.spaceConfig.cost} Points'))), + child: Text( + '${space.spaceConfig!.cost} Points', + style: const TextStyle(fontSize: 11), + ))), DataCell(Center( child: Transform.scale( scale: 0.7, child: Switch( - value: space.spaceConfig.availability, + value: space.spaceConfig!.availability, + trackColor: WidgetStateProperty.resolveWith( + (Set states) { + return ColorsManager.blue1; + }), + inactiveTrackColor: ColorsManager.lightGrayColor, + thumbColor: WidgetStateProperty.resolveWith( + (Set states) { + return ColorsManager.whiteColors; + }), onChanged: (value) {}, ), ), @@ -173,13 +228,22 @@ class ManageBookableSpacesWidget extends StatelessWidget { DataCell(Center( child: ElevatedButton( onPressed: () {}, - child: SvgPicture.asset(Assets.settings), + 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', + 'Space', + 'Space Virtual Address', 'Bookable Days', 'Booking Start Time', 'Booking End Time', @@ -193,9 +257,166 @@ class ManageBookableSpacesWidget extends StatelessWidget { } }, ), - ) + ), + const SizedBox( + height: 5, + ), + Expanded( + flex: 5, + child: 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/screens/setup_bookable_spaces_dialog.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/screens/setup_bookable_spaces_dialog.dart index a4f32f63..b2d48b23 100644 --- a/lib/pages/access_management/manage_bookable_spaces/presentation/screens/setup_bookable_spaces_dialog.dart +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/screens/setup_bookable_spaces_dialog.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; -import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/data/dummy_non_nookable_spaces.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/data/remote_non_bookable_spaces.dart'; import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/non_bookable_spaces_params.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/blocs/steps_cubit/cubit/steps_cubit.dart'; -import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/buttons_divider_bottom_dialog_widget.dart'; -import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/space_step_part_widget.dart'; -import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/step_two_details_widget.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/details_steps_widget.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/next_first_step_button.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/save_second_step_button.dart'; import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/stepper_part_widget.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class SetupBookableSpacesDialog extends StatelessWidget { @@ -23,7 +23,7 @@ class SetupBookableSpacesDialog extends StatelessWidget { ), BlocProvider( create: (context) => NonBookableSpacesBloc( - DummyNonNookableSpaces(), + RemoteNonBookableSpaces(HTTPService()), )..add( LoadUnBookableSpacesEvent( nonBookableSpacesParams: @@ -33,13 +33,16 @@ class SetupBookableSpacesDialog extends StatelessWidget { ), ], child: AlertDialog( + backgroundColor: ColorsManager.whiteColors, contentPadding: EdgeInsets.zero, - title: Text( - 'Set Up a Bookable Spaces', - style: TextStyle( - fontWeight: FontWeight.w700, - color: ColorsManager.dialogBlueTitle, - fontSize: 15, + title: Center( + child: Text( + 'Set Up a Bookable Spaces', + style: TextStyle( + fontWeight: FontWeight.w700, + color: ColorsManager.dialogBlueTitle, + fontSize: 15, + ), ), ), content: Column( @@ -69,46 +72,14 @@ class SetupBookableSpacesDialog extends StatelessWidget { ], ), Builder(builder: (context) { - return ButtonsDividerBottomDialogWidget( - onNextPressed: () { - final stepsState = context.read().state; - final selectedSpaces = context - .read() - .selectedBookableSpaces; - if (stepsState is StepOneState) { - if (selectedSpaces.isNotEmpty) { - context.read().goToNextStep(); - } else { - ScaffoldMessenger.of(context).clearSnackBars(); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Please select at least one space.'), - ), - ); - } - } else if (stepsState is StepTwoState) { - selectedSpaces.forEach( - (e) => e.spaceConfig.cost = int.parse( - pointsController.text.isEmpty - ? '0' - : pointsController.text), - ); - if (selectedSpaces.any( - (element) => !element.isValid, - )) { - ScaffoldMessenger.of(context).clearSnackBars(); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Please fill the required fields.'), - ), - ); - } else { - print(selectedSpaces.first.spaceUuid); - } - } - }, - onCancelPressed: () => context.pop(), - ); + final stepsState = context.watch().state; + final nonBookableBloc = context.watch(); + final selectedSpaces = nonBookableBloc.selectedBookableSpaces; + return stepsState is StepOneState + ? NextFirstStepButton(selectedSpaces: selectedSpaces) + : SaveSecondStepButton( + selectedSpaces: selectedSpaces, + pointsController: pointsController); }), ], ), @@ -116,31 +87,3 @@ class SetupBookableSpacesDialog extends StatelessWidget { ); } } - -class DetailsStepsWidget extends StatelessWidget { - final TextEditingController pointsController; - const DetailsStepsWidget({ - super.key, - required this.pointsController, - }); - - @override - Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20), - child: BlocBuilder( - builder: (context, state) { - if (state is StepOneState) { - return SpacesStepDetailsWidget(); - } else if (state is StepTwoState) { - return StepTwoDetailsWidget( - pointsController: pointsController, - ); - } else { - return const SizedBox(); - } - }, - ), - ); - } -} diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/buttons_divider_bottom_dialog_widget.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/buttons_divider_bottom_dialog_widget.dart index e177b9b1..581ab89f 100644 --- a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/buttons_divider_bottom_dialog_widget.dart +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/buttons_divider_bottom_dialog_widget.dart @@ -1,13 +1,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.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/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/blocs/steps_cubit/cubit/steps_cubit.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class ButtonsDividerBottomDialogWidget extends StatelessWidget { - final void Function() onNextPressed; + final String title; + final void Function()? onNextPressed; final void Function() onCancelPressed; const ButtonsDividerBottomDialogWidget({ super.key, + required this.title, required this.onNextPressed, required this.onCancelPressed, }); @@ -43,38 +49,51 @@ class ButtonsDividerBottomDialogWidget extends StatelessWidget { ), child: const Text( 'Cancel', - style: TextStyle(color: ColorsManager.grayBorder), + style: TextStyle(color: ColorsManager.blackColor), ), ), ), ), Expanded( - child: BlocBuilder( - builder: (context, state) { - return InkWell( - borderRadius: const BorderRadius.only( - bottomRight: Radius.circular(26), - ), - onTap: onNextPressed, - child: Container( - height: 40, - alignment: Alignment.center, - decoration: const BoxDecoration( - border: Border( - right: BorderSide( - color: ColorsManager.grayBorder, + child: + BlocConsumer( + listener: (context, nonBookableState) { + if (nonBookableState is NonBookableSpacesInitial) { + context.pop(); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Spaces Added Successfully', + style: TextStyle(color: ColorsManager.activeGreen), + ), + duration: Duration(seconds: 2), + behavior: SnackBarBehavior.floating, + ), + ); + context.read().add( + LoadBookableSpacesEvent( + BookableSpacesParams(currentPage: 1), ), + ); + } else if (nonBookableState is NonBookableSpacesError) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + nonBookableState.error, + style: + const TextStyle(color: ColorsManager.activeGreen), ), - borderRadius: BorderRadius.only( - bottomRight: Radius.circular(26), - ), - ), - child: Text( - state is StepOneState ? 'Next' : 'Save', - style: const TextStyle( - color: ColorsManager.blueColor, - ), + duration: const Duration(seconds: 2), + behavior: SnackBarBehavior.floating, ), + ); + } + }, + builder: (context, nonBookableState) { + return TextButton( + onPressed: onNextPressed, + child: Text( + title, ), ); }, diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/column_title_widget.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/column_title_widget.dart index 4ff11ec6..5525d6a4 100644 --- a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/column_title_widget.dart +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/column_title_widget.dart @@ -31,7 +31,10 @@ class ColumnTitleWidget extends StatelessWidget { ), child: Text( title, - style: const TextStyle(color: ColorsManager.grayColor), + style: const TextStyle( + color: ColorsManager.grayColor, + fontSize: 12, + ), )); } -} \ No newline at end of file +} diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/details_steps_widget.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/details_steps_widget.dart new file mode 100644 index 00000000..504c25e0 --- /dev/null +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/details_steps_widget.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/steps_cubit/cubit/steps_cubit.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/space_step_part_widget.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/step_two_details_widget.dart'; + +class DetailsStepsWidget extends StatelessWidget { + final TextEditingController pointsController; + const DetailsStepsWidget({ + super.key, + required this.pointsController, + }); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20), + child: BlocBuilder( + builder: (context, state) { + if (state is StepOneState) { + return const SpacesStepDetailsWidget(); + } else if (state is StepTwoState) { + return StepTwoDetailsWidget( + pointsController: pointsController, + ); + } else { + return const SizedBox(); + } + }, + ), + ); + } +} diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/next_first_step_button.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/next_first_step_button.dart new file mode 100644 index 00000000..a404a900 --- /dev/null +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/next_first_step_button.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.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/steps_cubit/cubit/steps_cubit.dart'; +import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/buttons_divider_bottom_dialog_widget.dart'; + +class NextFirstStepButton extends StatelessWidget { + final List selectedSpaces; + + const NextFirstStepButton({ + super.key, + required this.selectedSpaces, + }); + + @override + Widget build(BuildContext context) { + return ButtonsDividerBottomDialogWidget( + title: 'Next', + onNextPressed: selectedSpaces.isEmpty + ? null + : () { + context.read().goToNextStep(); + }, + onCancelPressed: () => context.pop(), + ); + } +} diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/save_second_step_button.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/save_second_step_button.dart new file mode 100644 index 00000000..cf57e601 --- /dev/null +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/save_second_step_button.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.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/buttons_divider_bottom_dialog_widget.dart'; + +class SaveSecondStepButton extends StatelessWidget { + final List selectedSpaces; + final TextEditingController pointsController; + + const SaveSecondStepButton({ + super.key, + required this.selectedSpaces, + required this.pointsController, + }); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return ButtonsDividerBottomDialogWidget( + title: 'Save', + onNextPressed: state is UnValidSaveButtonState + ? null + : () { + if (selectedSpaces.any( + (element) => element.isValid, + )) { + context.read().add( + SendBookableSpacesToApi(), + ); + } + }, + onCancelPressed: () => context.pop(), + ); + }, + ); + } +} diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/search_unbookable_spaces_widget.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/search_unbookable_spaces_widget.dart index be9931e5..0153624f 100644 --- a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/search_unbookable_spaces_widget.dart +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/search_unbookable_spaces_widget.dart @@ -25,6 +25,7 @@ class SearchUnbookableSpacesWidget extends StatelessWidget { return Container( width: width ?? 480, height: height ?? 30, + padding: const EdgeInsets.only(top: 4), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/space_step_part_widget.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/space_step_part_widget.dart index d58d3689..a1296ece 100644 --- a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/space_step_part_widget.dart +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/space_step_part_widget.dart @@ -6,114 +6,221 @@ import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domai import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/non_bookable_spaces_params.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/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart'; import 'package:syncrow_web/utils/color_manager.dart'; -class SpacesStepDetailsWidget extends StatelessWidget { - SpacesStepDetailsWidget({ +class SpacesStepDetailsWidget extends StatefulWidget { + const SpacesStepDetailsWidget({ super.key, }); + + @override + State createState() => + _SpacesStepDetailsWidgetState(); +} + +class _SpacesStepDetailsWidgetState extends State { Timer? _debounce; + ScrollController scrollController = ScrollController(); + int currentPage = 1; + String? currentSearchTerm; + bool isLoadingMore = false; + + @override + void initState() { + super.initState(); + + scrollController.addListener(() { + if (scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 100) { + final state = context.read().state; + if (state is NonBookableSpacesLoaded && + state.nonBookableSpaces.hasNext && + !isLoadingMore) { + isLoadingMore = true; + currentPage++; + context.read().add( + LoadUnBookableSpacesEvent( + nonBookableSpacesParams: NonBookableSpacesParams( + currentPage: currentPage, + searchedWords: currentSearchTerm, + ), + ), + ); + } + } + }); + } + + @override + void dispose() { + scrollController.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - if (state is NonBookableSpacesLoading) { - return const Center(child: CircularProgressIndicator()); - } else if (state is NonBookableSpacesError) { - return Text(state.error); - } else if (state is NonBookableSpacesLoaded) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Select Space', + style: TextStyle( + fontWeight: FontWeight.w700, + color: ColorsManager.blackColor, + ), + ), + const SizedBox( + height: 20, + ), + Container( + width: 450, + height: 480, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + boxShadow: const [ + BoxShadow( + color: Color(0x40000000), + offset: Offset.zero, + blurRadius: 5, + ), + ], + ), + child: Column( children: [ - const Text( - 'Select Space', - style: TextStyle( - fontWeight: FontWeight.w700, - color: ColorsManager.blackColor, - ), - ), - const SizedBox( - height: 20, - ), Container( - width: 450, - height: 480, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(20), - boxShadow: const [ - BoxShadow( - color: Color(0x40000000), - offset: Offset(0, 4), - blurRadius: 5, - ), - ], + width: 520, + height: 70, + padding: + const EdgeInsets.symmetric(vertical: 15, horizontal: 20), + decoration: const BoxDecoration( + color: Color(0xFFF8F8F8), + borderRadius: BorderRadius.vertical( + top: Radius.circular(20), + ), ), - child: Column( - children: [ - Container( - width: 520, - height: 70, - padding: const EdgeInsets.symmetric( - vertical: 15, horizontal: 20), - decoration: const BoxDecoration( - color: Color(0xFFF8F8F8), - borderRadius: BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - child: SearchUnbookableSpacesWidget( - title: 'Search', - onChanged: (p0) { - if (_debounce?.isActive ?? false) _debounce!.cancel(); - _debounce = - Timer(const Duration(milliseconds: 500), () { - context.read().add( - LoadUnBookableSpacesEvent( - nonBookableSpacesParams: - NonBookableSpacesParams( - currentPage: 1, - searchedWords: p0, - ), - ), - ); - }); - }, - ), - ), - Expanded( - child: 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( + child: SearchUnbookableSpacesWidget( + title: 'Search', + onChanged: (p0) { + if (_debounce?.isActive ?? false) _debounce!.cancel(); + _debounce = Timer(const Duration(milliseconds: 500), () { + currentSearchTerm = p0; + currentPage = 1; + context.read().add( + LoadUnBookableSpacesEvent( + nonBookableSpacesParams: NonBookableSpacesParams( + currentPage: currentPage, + searchedWords: currentSearchTerm, + ), + ), + ); + }); + }, + ), + ), + Expanded( + child: + BlocConsumer( + listener: (context, state) { + if (state is NonBookableSpacesLoaded) { + isLoadingMore = false; + } + }, + builder: (context, state) { + if (state is NonBookableSpacesError) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(state.error), + const SizedBox( height: 5, ), - itemCount: state.nonBookableSpaces.data.length, - itemBuilder: (context, index) => CheckBoxSpaceWidget( - nonBookableSpace: - state.nonBookableSpaces.data[index], - ), - ), - ), - ) - ], + ElevatedButton( + onPressed: () { + context.read().add( + LoadUnBookableSpacesEvent( + nonBookableSpacesParams: + NonBookableSpacesParams( + currentPage: currentPage, + searchedWords: currentSearchTerm, + ), + ), + ); + }, + child: const Text('Try Again')) + ], + ); + } else if (state is NonBookableSpacesLoading) { + if (state.lastNonBookableSpaces == null) { + return const Center( + child: CircularProgressIndicator(), + ); + } else { + return UnbookableListWidget( + scrollController: scrollController, + nonBookableSpaces: state.lastNonBookableSpaces!, + ); + } + } else if (state is NonBookableSpacesLoaded) { + return UnbookableListWidget( + scrollController: scrollController, + nonBookableSpaces: state.nonBookableSpaces, + ); + } else { + return const SizedBox(); + } + }, ), ) ], - ); - } else { - return const SizedBox(); - } - }, + ), + ) + ], + ); + } +} + +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], + ); + } else { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 10), + child: Center(child: CircularProgressIndicator()), + ); + } + }, + ), ); } } @@ -159,7 +266,7 @@ class _CheckBoxSpaceWidgetState extends State { const SizedBox( width: 5, ), - Text(widget.nonBookableSpace.spaceName), + Expanded(child: Text(widget.nonBookableSpace.spaceName)), ], ); } diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/step_two_details_widget.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/step_two_details_widget.dart index dc750ebb..4c694b66 100644 --- a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/step_two_details_widget.dart +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/step_two_details_widget.dart @@ -35,8 +35,10 @@ class StepTwoDetailsWidget extends StatelessWidget { return; } final nonBookableBloc = context.read(); - if (isEndTimeAfterStartTime( - timePicked, nonBookableBloc.endTime)) { + + if (nonBookableBloc.endTime != null && + isEndTimeAfterStartTime( + timePicked, nonBookableBloc.endTime!)) { ScaffoldMessenger.of(context).clearSnackBars(); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: @@ -47,7 +49,7 @@ class StepTwoDetailsWidget extends StatelessWidget { throw Exception(); } else { nonBookableBloc.selectedBookableSpaces.forEach( - (e) => e.spaceConfig.bookingStartTime = timePicked, + (e) => e.spaceConfig!.bookingStartTime = timePicked, ); } }, @@ -62,8 +64,9 @@ class StepTwoDetailsWidget extends StatelessWidget { return; } final nonBookableBloc = context.read(); - if (isEndTimeAfterStartTime( - nonBookableBloc.startTime, timePicked)) { + if (nonBookableBloc.startTime != null && + isEndTimeAfterStartTime( + nonBookableBloc.startTime!, timePicked)) { ScaffoldMessenger.of(context).clearSnackBars(); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: @@ -74,7 +77,7 @@ class StepTwoDetailsWidget extends StatelessWidget { throw Exception(); } else { nonBookableBloc.selectedBookableSpaces.forEach( - (e) => e.spaceConfig.bookingEndTime = timePicked, + (e) => e.spaceConfig!.bookingEndTime = timePicked, ); } }, @@ -102,6 +105,22 @@ class StepTwoDetailsWidget extends StatelessWidget { ), 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(), diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/stepper_part_widget.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/stepper_part_widget.dart index e7e3d43d..622fdbe0 100644 --- a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/stepper_part_widget.dart +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/stepper_part_widget.dart @@ -24,6 +24,7 @@ class StepperPartWidget extends StatelessWidget { title: 'Space', ), Container( + padding: const EdgeInsets.only(left: 3), alignment: Alignment.centerLeft, height: 50, child: const VerticalDivider( @@ -32,7 +33,8 @@ class StepperPartWidget extends StatelessWidget { const CircleTitleStepperWidget( title: 'Settings', titleColor: ColorsManager.softGray, - circleColor: ColorsManager.softGray, + circleColor: ColorsManager.whiteColors, + borderColor: ColorsManager.textGray, ) ], ); @@ -51,9 +53,11 @@ class StepperPartWidget extends StatelessWidget { size: 12, ), circleColor: ColorsManager.trueIconGreen, - radius: 3, + radius: 15, + borderColor: ColorsManager.trueIconGreen, ), Container( + padding: const EdgeInsets.only(left: 3), alignment: Alignment.centerLeft, height: 50, child: const VerticalDivider( @@ -81,12 +85,14 @@ class CircleTitleStepperWidget extends StatelessWidget { final double? radius; final Widget? cicleIcon; final Color? circleColor; + final Color? borderColor; final Color? titleColor; final String title; const CircleTitleStepperWidget({ super.key, required this.title, this.circleColor, + this.borderColor, this.cicleIcon, this.titleColor, this.radius, @@ -96,9 +102,13 @@ class CircleTitleStepperWidget extends StatelessWidget { Widget build(BuildContext context) { return Row( children: [ - CircleAvatar( - minRadius: radius ?? 5, - backgroundColor: circleColor ?? ColorsManager.blue1, + Container( + width: radius ?? 15, + height: radius ?? 15, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: circleColor ?? ColorsManager.blue1, + border: Border.all(color: borderColor ?? ColorsManager.blue1)), child: cicleIcon, ), const SizedBox( diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/time_picker_widget.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/time_picker_widget.dart index c02491f3..1cb87ebd 100644 --- a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/time_picker_widget.dart +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/time_picker_widget.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.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/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -40,6 +42,7 @@ class _TimePickerWidgetState extends State { ); widget.onTimePicked(tempTime); timePicked = tempTime; + context.read().add(CheckConfigurValidityEvent()); setState(() {}); }, child: Row( @@ -70,9 +73,7 @@ class _TimePickerWidgetState extends State { alignment: Alignment.centerLeft, padding: const EdgeInsets.symmetric(horizontal: 8), child: Text( - timePicked == null - ? TimeOfDay.now().format(context) - : timePicked!.format(context), + timePicked == null ? 'HH:MM' : timePicked!.format(context), style: const TextStyle(color: Color(0xB2D5D5D5)), ), ), diff --git a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/week_checkbox_title_widget.dart b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/week_checkbox_title_widget.dart index 6fc142b6..c0d3c1b8 100644 --- a/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/week_checkbox_title_widget.dart +++ b/lib/pages/access_management/manage_bookable_spaces/presentation/widgets/week_checkbox_title_widget.dart @@ -42,9 +42,13 @@ class _WeekDaysCheckboxRowState extends State { for (var space in context .read() .selectedBookableSpaces) { - space.spaceConfig.bookableDays = selectedDays; + space.spaceConfig!.bookableDays = selectedDays; } }); + + context + .read() + .add(CheckConfigurValidityEvent()); }, ), ), diff --git a/lib/utils/constants/api_const.dart b/lib/utils/constants/api_const.dart index 24175a37..671a930f 100644 --- a/lib/utils/constants/api_const.dart +++ b/lib/utils/constants/api_const.dart @@ -141,5 +141,5 @@ abstract class ApiEndpoints { static const String saveSchedule = '/schedule/{deviceUuid}'; ////booking System - static const String addBookableSpaces = '/bookable-spaces'; + static const String bookableSpaces = '/bookable-spaces'; }