clean the code for save and next buttons and enhance UI

This commit is contained in:
Rafeek-Khoudare
2025-07-09 15:11:27 +03:00
parent 42c410d982
commit b128618bfd
23 changed files with 793 additions and 303 deletions

View File

@ -12,10 +12,13 @@ class SvgTextButton extends StatelessWidget {
final double borderRadius; final double borderRadius;
final List<BoxShadow> boxShadow; final List<BoxShadow> boxShadow;
final double svgSize; final double svgSize;
final double? fontSize;
final FontWeight? fontWeight;
const SvgTextButton({ const SvgTextButton({
super.key, super.key,
required this.svgAsset, required this.svgAsset,
this.fontSize,
this.fontWeight,
required this.label, required this.label,
required this.onPressed, required this.onPressed,
this.backgroundColor = ColorsManager.circleRolesBackground, this.backgroundColor = ColorsManager.circleRolesBackground,
@ -60,8 +63,8 @@ class SvgTextButton extends StatelessWidget {
label, label,
style: TextStyle( style: TextStyle(
color: labelColor, color: labelColor,
fontSize: 16, fontSize: fontSize ?? 16,
fontWeight: FontWeight.w500, fontWeight: fontWeight ?? FontWeight.w500,
), ),
), ),
], ],

View File

@ -10,15 +10,17 @@ import 'package:syncrow_web/utils/constants/api_const.dart';
class RemoteBookableSpacesService implements BookableSpacesService { class RemoteBookableSpacesService implements BookableSpacesService {
final HTTPService _httpService; final HTTPService _httpService;
RemoteBookableSpacesService(this._httpService); RemoteBookableSpacesService(this._httpService);
static const _defaultErrorMessage = 'Failed to load tags'; static const _defaultErrorMessage = 'Failed to load Bookable Spaces';
@override @override
Future<PaginatedDataModel<BookableSpacemodel>> load( Future<PaginatedDataModel<BookableSpacemodel>> load(
BookableSpacesParams param) async { BookableSpacesParams param) async {
try { try {
final response = await _httpService.get( final response = await _httpService.get(
//TODO: you have to Chage this API call Path path: ApiEndpoints.bookableSpaces,
path: ApiEndpoints.listTags, queryParameters: {
//*************|********** */ 'configured': true,
'page': param.currentPage,
},
expectedResponseModel: (json) { expectedResponseModel: (json) {
final result = json as Map<String, dynamic>; final result = json as Map<String, dynamic>;
return PaginatedDataModel.fromJson( return PaginatedDataModel.fromJson(

View File

@ -18,9 +18,12 @@ class RemoteNonBookableSpaces implements NonBookableSpacesService {
NonBookableSpacesParams params) async { NonBookableSpacesParams params) async {
try { try {
final response = await _httpService.get( final response = await _httpService.get(
//TODO: you have to Chage this API call Path path: ApiEndpoints.bookableSpaces,
path: ApiEndpoints.listTags, queryParameters: {
//*************|********** */ 'configured': false,
'page': params.currentPage,
'search': params.searchedWords,
},
expectedResponseModel: (json) { expectedResponseModel: (json) {
final result = json as Map<String, dynamic>; final result = json as Map<String, dynamic>;
return PaginatedDataModel.fromJson( return PaginatedDataModel.fromJson(
@ -50,7 +53,7 @@ class RemoteNonBookableSpaces implements NonBookableSpacesService {
SendBookableSpacesToApiParams params) async { SendBookableSpacesToApiParams params) async {
try { try {
await _httpService.post( await _httpService.post(
path: ApiEndpoints.addBookableSpaces, path: ApiEndpoints.bookableSpaces,
body: params.toJson(), body: params.toJson(),
expectedResponseModel: (p0) {}, expectedResponseModel: (p0) {},
); );

View File

@ -3,30 +3,28 @@ import 'package:flutter/material.dart';
class BookableSpaceConfig { class BookableSpaceConfig {
String configUuid; String configUuid;
List<String> bookableDays; List<String> bookableDays;
TimeOfDay bookingStartTime; TimeOfDay? bookingStartTime;
TimeOfDay bookingEndTime; TimeOfDay? bookingEndTime;
int cost; int cost;
bool availability; bool availability;
BookableSpaceConfig({ BookableSpaceConfig({
required this.configUuid, required this.configUuid,
required this.availability, required this.availability,
required this.bookableDays, required this.bookableDays,
required this.bookingEndTime, this.bookingEndTime,
required this.bookingStartTime, this.bookingStartTime,
required this.cost, required this.cost,
}); });
factory BookableSpaceConfig.zero() => BookableSpaceConfig( factory BookableSpaceConfig.zero() => BookableSpaceConfig(
configUuid: '', configUuid: '',
bookableDays: [], bookableDays: [],
availability: false, availability: false,
bookingEndTime: TimeOfDay.now(),
bookingStartTime: TimeOfDay.now(),
cost: -1, cost: -1,
); );
factory BookableSpaceConfig.fromJson(Map<String, dynamic> json) => factory BookableSpaceConfig.fromJson(Map<String, dynamic> json) =>
BookableSpaceConfig( BookableSpaceConfig(
configUuid: json['uuid'] as String, configUuid: json['uuid'] as String,
bookableDays: json['daysAvailable'] as List<String>, bookableDays: (json['daysAvailable'] as List).cast<String>(),
availability: (json['active'] as bool?) ?? false, availability: (json['active'] as bool?) ?? false,
bookingEndTime: parseTimeOfDay(json['startTime'] as String), bookingEndTime: parseTimeOfDay(json['startTime'] as String),
bookingStartTime: parseTimeOfDay(json['endTime'] as String), bookingStartTime: parseTimeOfDay(json['endTime'] as String),
@ -41,5 +39,8 @@ class BookableSpaceConfig {
} }
bool get isValid => bool get isValid =>
configUuid.isNotEmpty && bookableDays.isNotEmpty && cost > 0; bookableDays.isNotEmpty &&
cost > 0 &&
bookingStartTime != null &&
bookingEndTime != null;
} }

View File

@ -3,28 +3,29 @@ import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domai
class BookableSpacemodel { class BookableSpacemodel {
String spaceUuid; String spaceUuid;
String spaceName; String spaceName;
BookableSpaceConfig spaceConfig; BookableSpaceConfig? spaceConfig;
String spaceVirtualAddress; String spaceVirtualAddress;
BookableSpacemodel({ BookableSpacemodel({
required this.spaceUuid, required this.spaceUuid,
required this.spaceName, required this.spaceName,
required this.spaceConfig, this.spaceConfig,
required this.spaceVirtualAddress, required this.spaceVirtualAddress,
}); });
factory BookableSpacemodel.zero() => BookableSpacemodel( factory BookableSpacemodel.zero() => BookableSpacemodel(
spaceUuid: '', spaceUuid: '',
spaceName: '', spaceName: '',
spaceConfig: BookableSpaceConfig.zero(),
spaceVirtualAddress: '', spaceVirtualAddress: '',
); );
factory BookableSpacemodel.fromJson(Map<String, dynamic> json) => factory BookableSpacemodel.fromJson(Map<String, dynamic> json) =>
BookableSpacemodel( BookableSpacemodel(
spaceUuid: json['uuid'] as String, spaceUuid: json['uuid'] as String,
spaceName: json['spaceName'] as String, spaceName: json['spaceName'] as String,
spaceConfig: BookableSpaceConfig.fromJson( spaceConfig: json['bookableConfig'] == null
json['bookableConfig'] as Map<String, dynamic>), ? BookableSpaceConfig.zero()
spaceVirtualAddress: json['spaceVirtualAddress'] as String, : BookableSpaceConfig.fromJson(
json['bookableConfig'] as Map<String, dynamic>),
spaceVirtualAddress: json['virtualLocation'] as String,
); );
static List<BookableSpacemodel> fromJsonList(List<dynamic> jsonList) => static List<BookableSpacemodel> fromJsonList(List<dynamic> jsonList) =>
@ -38,5 +39,6 @@ class BookableSpacemodel {
spaceUuid.isNotEmpty && spaceUuid.isNotEmpty &&
spaceName.isNotEmpty && spaceName.isNotEmpty &&
spaceVirtualAddress.isNotEmpty && spaceVirtualAddress.isNotEmpty &&
spaceConfig.isValid; spaceConfig != null &&
spaceConfig!.isValid;
} }

View File

@ -20,14 +20,14 @@ class SendBookableSpacesToApiParams {
return SendBookableSpacesToApiParams( return SendBookableSpacesToApiParams(
spaceUuids: bookableSpaces.map((space) => space.spaceUuid).toList(), spaceUuids: bookableSpaces.map((space) => space.spaceUuid).toList(),
daysAvailable: bookableSpaces daysAvailable: bookableSpaces
.expand((space) => space.spaceConfig.bookableDays) .expand((space) => space.spaceConfig!.bookableDays)
.toSet() .toSet()
.toList(), .toList(),
startTime: formatTimeOfDayTo24HourString( startTime: formatTimeOfDayTo24HourString(
bookableSpaces.first.spaceConfig.bookingStartTime), bookableSpaces.first.spaceConfig!.bookingStartTime!),
endTime: formatTimeOfDayTo24HourString( endTime: formatTimeOfDayTo24HourString(
bookableSpaces.first.spaceConfig.bookingEndTime), bookableSpaces.first.spaceConfig!.bookingEndTime!),
points: bookableSpaces.first.spaceConfig.cost, points: bookableSpaces.first.spaceConfig!.cost,
); );
} }

View File

@ -20,27 +20,49 @@ class NonBookableSpacesBloc
on<AddToBookableSpaceEvent>(_onAddToBookableSpaceEvent); on<AddToBookableSpaceEvent>(_onAddToBookableSpaceEvent);
on<RemoveFromBookableSpaceEvent>(_onRemoveFromBookableSpaceEvent); on<RemoveFromBookableSpaceEvent>(_onRemoveFromBookableSpaceEvent);
on<SendBookableSpacesToApi>(_onSendBookableSpacesToApi); on<SendBookableSpacesToApi>(_onSendBookableSpacesToApi);
on<CheckConfigurValidityEvent>(_onCheckConfigurValidityEvent);
} }
TimeOfDay get endTime => TimeOfDay? get endTime =>
selectedBookableSpaces.first.spaceConfig.bookingEndTime; selectedBookableSpaces.first.spaceConfig!.bookingEndTime;
TimeOfDay get startTime => TimeOfDay? get startTime =>
selectedBookableSpaces.first.spaceConfig.bookingStartTime; selectedBookableSpaces.first.spaceConfig!.bookingStartTime;
Future<void> _onLoadUnBookableSpacesEvent(LoadUnBookableSpacesEvent event, Future<void> _onLoadUnBookableSpacesEvent(LoadUnBookableSpacesEvent event,
Emitter<NonBookableSpacesState> emit) async { Emitter<NonBookableSpacesState> emit) async {
emit(NonBookableSpacesLoading()); if (state is NonBookableSpacesLoaded) {
try { final currState = state as NonBookableSpacesLoaded;
final nonBookableSpacesList = await nonBookableSpacesService.load( try {
event.nonBookableSpacesParams, emit(NonBookableSpacesLoading(
); lastNonBookableSpaces: currState.nonBookableSpaces));
emit(
NonBookableSpacesLoaded(nonBookableSpaces: nonBookableSpacesList), final nonBookableSpacesList = await nonBookableSpacesService.load(
); event.nonBookableSpacesParams,
} catch (e) { );
emit( nonBookableSpacesList.data.addAll(currState.nonBookableSpaces.data);
NonBookableSpacesError(e.toString()),
); 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) { if (state is NonBookableSpacesLoaded) {
final currentState = state as NonBookableSpacesLoaded; final currentState = state as NonBookableSpacesLoaded;
emit(AddNonBookableSpaceIntoBookableState());
final updatedSelectedSpaces = final updatedSelectedSpaces =
List<BookableSpacemodel>.from(currentState.selectedBookableSpaces) List<BookableSpacemodel>.from(currentState.selectedBookableSpaces)
..add(event.nonBookableSpace); ..add(event.nonBookableSpace);
@ -70,7 +92,10 @@ class NonBookableSpacesBloc
Emitter<NonBookableSpacesState> emit) { Emitter<NonBookableSpacesState> emit) {
if (state is NonBookableSpacesLoaded) { if (state is NonBookableSpacesLoaded) {
final currentState = state as 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); selectedBookableSpaces.remove(event.bookableSpace);
emit( emit(
NonBookableSpacesLoaded( NonBookableSpacesLoaded(
@ -83,17 +108,27 @@ class NonBookableSpacesBloc
Future<void> _onSendBookableSpacesToApi(SendBookableSpacesToApi event, Future<void> _onSendBookableSpacesToApi(SendBookableSpacesToApi event,
Emitter<NonBookableSpacesState> emit) async { Emitter<NonBookableSpacesState> emit) async {
emit(NonBookableSpacesLoading()); emit(const NonBookableSpacesLoading());
try { try {
await nonBookableSpacesService.sendBookableSpacesToApi( await nonBookableSpacesService.sendBookableSpacesToApi(
SendBookableSpacesToApiParams.fromBookableSpacesModel( SendBookableSpacesToApiParams.fromBookableSpacesModel(
selectedBookableSpaces, selectedBookableSpaces,
), ),
); );
emit(NonBookableSpacesInitial());
} catch (e) { } catch (e) {
emit( emit(
NonBookableSpacesError(e.toString()), NonBookableSpacesError(e.toString()),
); );
} }
} }
void _onCheckConfigurValidityEvent(
CheckConfigurValidityEvent event, Emitter<NonBookableSpacesState> emit) {
if (selectedBookableSpaces.first.spaceConfig!.isValid) {
emit(ValidSaveButtonState());
} else {
emit(UnValidSaveButtonState());
}
}
} }

View File

@ -29,3 +29,5 @@ class RemoveFromBookableSpaceEvent extends NonBookableSpacesEvent {
} }
class SendBookableSpacesToApi extends NonBookableSpacesEvent {} class SendBookableSpacesToApi extends NonBookableSpacesEvent {}
class CheckConfigurValidityEvent extends NonBookableSpacesEvent {}

View File

@ -9,7 +9,12 @@ sealed class NonBookableSpacesState extends Equatable {
final class NonBookableSpacesInitial extends NonBookableSpacesState {} final class NonBookableSpacesInitial extends NonBookableSpacesState {}
class NonBookableSpacesLoading extends NonBookableSpacesState {} class NonBookableSpacesLoading extends NonBookableSpacesState {
final PaginatedDataModel<BookableSpacemodel>? lastNonBookableSpaces;
const NonBookableSpacesLoading({
this.lastNonBookableSpaces,
});
}
class NonBookableSpacesLoaded extends NonBookableSpacesState { class NonBookableSpacesLoaded extends NonBookableSpacesState {
final PaginatedDataModel<BookableSpacemodel> nonBookableSpaces; final PaginatedDataModel<BookableSpacemodel> nonBookableSpaces;
@ -24,3 +29,11 @@ class NonBookableSpacesError extends NonBookableSpacesState {
final String error; final String error;
const NonBookableSpacesError(this.error); const NonBookableSpacesError(this.error);
} }
class AddNonBookableSpaceIntoBookableState extends NonBookableSpacesState {}
class RemoveBookableSpaceIntoNonBookableState extends NonBookableSpacesState {}
class ValidSaveButtonState extends NonBookableSpacesState {}
class UnValidSaveButtonState extends NonBookableSpacesState {}

View File

@ -3,13 +3,15 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.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/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/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/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/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/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/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/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/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart'; import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
@ -64,8 +66,7 @@ class _ManageBookableSpacesPageState extends State<ManageBookableSpacesPage> {
rightBody: const NavigateHomeGridView(), rightBody: const NavigateHomeGridView(),
scaffoldBody: BlocProvider( scaffoldBody: BlocProvider(
create: (context) => BookableSpacesBloc( create: (context) => BookableSpacesBloc(
DummyBookableSpacesService(), RemoteBookableSpacesService(HTTPService()),
// RemoteBookableSpacesService(HTTPService()),
)..add(LoadBookableSpacesEvent( )..add(LoadBookableSpacesEvent(
BookableSpacesParams(currentPage: 1), BookableSpacesParams(currentPage: 1),
)), )),
@ -90,34 +91,66 @@ class ManageBookableSpacesWidget extends StatelessWidget {
child: Column( child: Column(
children: [ children: [
Expanded( Expanded(
flex: 1, flex: 10,
child: Row( child: Padding(
mainAxisAlignment: MainAxisAlignment.spaceBetween, padding: const EdgeInsetsGeometry.symmetric(vertical: 5),
children: [ child: Row(
SvgTextButton( mainAxisAlignment: MainAxisAlignment.spaceBetween,
svgAsset: Assets.backButtonIcon, children: [
label: 'Booking Home', 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: () { onPressed: () {
context.pop(); final bloc = context.read<BookableSpacesBloc>();
}), showDialog(
SvgTextButton( context: context,
svgAsset: Assets.backButtonIcon, builder: (context) => BlocProvider.value(
label: 'Set Up a Bookable Spaces', value: bloc,
onPressed: () async => showDialog( child: SetupBookableSpacesDialog(),
context: context, ),
builder: (context) => SetupBookableSpacesDialog(), );
), },
) )
], ],
),
)), )),
const SizedBox(
height: 10,
),
Expanded( Expanded(
flex: 9, flex: 85,
child: BlocBuilder<BookableSpacesBloc, BookableSpacesState>( child: BlocBuilder<BookableSpacesBloc, BookableSpacesState>(
builder: (context, state) { builder: (context, state) {
if (state is BookableSpacesLoading) { if (state is BookableSpacesLoading) {
return const CircularProgressIndicator(); return const Center(child: CircularProgressIndicator());
} else if (state is BookableSpacesError) { } 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<BookableSpacesBloc>()
.add(LoadBookableSpacesEvent(
BookableSpacesParams(currentPage: 1),
)),
child: const Text('try Again'))
]);
} else if (state is BookableSpacesLoaded) { } else if (state is BookableSpacesLoaded) {
return CustomDataTable<BookableSpacemodel>( return CustomDataTable<BookableSpacemodel>(
items: state.bookableSpacesList.data, items: state.bookableSpacesList.data,
@ -125,19 +158,26 @@ class ManageBookableSpacesWidget extends StatelessWidget {
DataCell( DataCell(
Padding( Padding(
padding: const EdgeInsetsGeometry.only(left: 10), padding: const EdgeInsetsGeometry.only(left: 10),
child: Text(space.spaceName)), child: Text(
space.spaceName,
style: const TextStyle(fontSize: 11),
)),
), ),
DataCell(Padding( DataCell(Padding(
padding: const EdgeInsetsGeometry.only(left: 10), padding: const EdgeInsetsGeometry.only(left: 10),
child: Text(space.spaceVirtualAddress))), child: Text(
DataCell(SizedBox( space.spaceVirtualAddress,
style: const TextStyle(fontSize: 11),
))),
DataCell(Container(
padding: const EdgeInsetsGeometry.only(left: 10),
width: 200, width: 200,
child: Wrap( child: Wrap(
spacing: 4, spacing: 4,
children: space.spaceConfig.bookableDays children: space.spaceConfig!.bookableDays
.map((day) => Text( .map((day) => Text(
day, day,
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 11),
)) ))
.toList(), .toList(),
), ),
@ -146,7 +186,9 @@ class ManageBookableSpacesWidget extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsetsGeometry.only(left: 10), padding: const EdgeInsetsGeometry.only(left: 10),
child: Text( 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(
padding: const EdgeInsetsGeometry.only(left: 10), padding: const EdgeInsetsGeometry.only(left: 10),
child: Text( child: Text(
space.spaceConfig.bookingEndTime.format(context), space.spaceConfig!.bookingEndTime!.format(context),
style: const TextStyle(fontSize: 11),
), ),
), ),
), ),
DataCell(Padding( DataCell(Padding(
padding: const EdgeInsetsGeometry.only(left: 10), 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( DataCell(Center(
child: Transform.scale( child: Transform.scale(
scale: 0.7, scale: 0.7,
child: Switch( child: Switch(
value: space.spaceConfig.availability, value: space.spaceConfig!.availability,
trackColor: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> states) {
return ColorsManager.blue1;
}),
inactiveTrackColor: ColorsManager.lightGrayColor,
thumbColor: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> states) {
return ColorsManager.whiteColors;
}),
onChanged: (value) {}, onChanged: (value) {},
), ),
), ),
@ -173,13 +228,22 @@ class ManageBookableSpacesWidget extends StatelessWidget {
DataCell(Center( DataCell(Center(
child: ElevatedButton( child: ElevatedButton(
onPressed: () {}, 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 [ columnsTitles: const [
'space', 'Space',
'space Virtual Address', 'Space Virtual Address',
'Bookable Days', 'Bookable Days',
'Booking Start Time', 'Booking Start Time',
'Booking End Time', 'Booking End Time',
@ -193,9 +257,166 @@ class ManageBookableSpacesWidget extends StatelessWidget {
} }
}, },
), ),
) ),
const SizedBox(
height: 5,
),
Expanded(
flex: 5,
child: BlocBuilder<BookableSpacesBloc, BookableSpacesState>(
builder: (context, state) {
if (state is BookableSpacesLoaded) {
final totalPages = state.bookableSpacesList.totalPages;
final currentPage = state.bookableSpacesList.page;
List<Widget> paginationItems = [];
// « Two pages back
if (currentPage > 2) {
paginationItems.add(
_buildArrowButton(
label: '«',
onTap: () {
context.read<BookableSpacesBloc>().add(
LoadBookableSpacesEvent(
BookableSpacesParams(
currentPage: currentPage - 2),
),
);
},
),
);
}
// < One page back
if (currentPage > 1) {
paginationItems.add(
_buildArrowButton(
label: '<',
onTap: () {
context.read<BookableSpacesBloc>().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<BookableSpacesBloc>().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<BookableSpacesBloc>().add(
LoadBookableSpacesEvent(
BookableSpacesParams(
currentPage: currentPage + 1),
),
);
},
),
);
}
// » Two pages forward
if (currentPage + 1 < totalPages) {
paginationItems.add(
_buildArrowButton(
label: '»',
onTap: () {
context.read<BookableSpacesBloc>().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,
),
),
),
),
);
}
} }

View File

@ -1,14 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/remote_non_bookable_spaces.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/domain/params/non_bookable_spaces_params.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/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/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/details_steps_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/next_first_step_button.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/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/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'; import 'package:syncrow_web/utils/color_manager.dart';
class SetupBookableSpacesDialog extends StatelessWidget { class SetupBookableSpacesDialog extends StatelessWidget {
@ -23,7 +23,7 @@ class SetupBookableSpacesDialog extends StatelessWidget {
), ),
BlocProvider<NonBookableSpacesBloc>( BlocProvider<NonBookableSpacesBloc>(
create: (context) => NonBookableSpacesBloc( create: (context) => NonBookableSpacesBloc(
DummyNonNookableSpaces(), RemoteNonBookableSpaces(HTTPService()),
)..add( )..add(
LoadUnBookableSpacesEvent( LoadUnBookableSpacesEvent(
nonBookableSpacesParams: nonBookableSpacesParams:
@ -33,13 +33,16 @@ class SetupBookableSpacesDialog extends StatelessWidget {
), ),
], ],
child: AlertDialog( child: AlertDialog(
backgroundColor: ColorsManager.whiteColors,
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
title: Text( title: Center(
'Set Up a Bookable Spaces', child: Text(
style: TextStyle( 'Set Up a Bookable Spaces',
fontWeight: FontWeight.w700, style: TextStyle(
color: ColorsManager.dialogBlueTitle, fontWeight: FontWeight.w700,
fontSize: 15, color: ColorsManager.dialogBlueTitle,
fontSize: 15,
),
), ),
), ),
content: Column( content: Column(
@ -69,46 +72,14 @@ class SetupBookableSpacesDialog extends StatelessWidget {
], ],
), ),
Builder(builder: (context) { Builder(builder: (context) {
return ButtonsDividerBottomDialogWidget( final stepsState = context.watch<StepsCubit>().state;
onNextPressed: () { final nonBookableBloc = context.watch<NonBookableSpacesBloc>();
final stepsState = context.read<StepsCubit>().state; final selectedSpaces = nonBookableBloc.selectedBookableSpaces;
final selectedSpaces = context return stepsState is StepOneState
.read<NonBookableSpacesBloc>() ? NextFirstStepButton(selectedSpaces: selectedSpaces)
.selectedBookableSpaces; : SaveSecondStepButton(
if (stepsState is StepOneState) { selectedSpaces: selectedSpaces,
if (selectedSpaces.isNotEmpty) { pointsController: pointsController);
context.read<StepsCubit>().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(),
);
}), }),
], ],
), ),
@ -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<StepsCubit, StepsState>(
builder: (context, state) {
if (state is StepOneState) {
return SpacesStepDetailsWidget();
} else if (state is StepTwoState) {
return StepTwoDetailsWidget(
pointsController: pointsController,
);
} else {
return const SizedBox();
}
},
),
);
}
}

View File

@ -1,13 +1,19 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/pages/access_management/manage_bookable_spaces/presentation/blocs/steps_cubit/cubit/steps_cubit.dart';
import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/color_manager.dart';
class ButtonsDividerBottomDialogWidget extends StatelessWidget { class ButtonsDividerBottomDialogWidget extends StatelessWidget {
final void Function() onNextPressed; final String title;
final void Function()? onNextPressed;
final void Function() onCancelPressed; final void Function() onCancelPressed;
const ButtonsDividerBottomDialogWidget({ const ButtonsDividerBottomDialogWidget({
super.key, super.key,
required this.title,
required this.onNextPressed, required this.onNextPressed,
required this.onCancelPressed, required this.onCancelPressed,
}); });
@ -43,38 +49,51 @@ class ButtonsDividerBottomDialogWidget extends StatelessWidget {
), ),
child: const Text( child: const Text(
'Cancel', 'Cancel',
style: TextStyle(color: ColorsManager.grayBorder), style: TextStyle(color: ColorsManager.blackColor),
), ),
), ),
), ),
), ),
Expanded( Expanded(
child: BlocBuilder<StepsCubit, StepsState>( child:
builder: (context, state) { BlocConsumer<NonBookableSpacesBloc, NonBookableSpacesState>(
return InkWell( listener: (context, nonBookableState) {
borderRadius: const BorderRadius.only( if (nonBookableState is NonBookableSpacesInitial) {
bottomRight: Radius.circular(26), context.pop();
), ScaffoldMessenger.of(context).showSnackBar(
onTap: onNextPressed, const SnackBar(
child: Container( content: Text(
height: 40, 'Spaces Added Successfully',
alignment: Alignment.center, style: TextStyle(color: ColorsManager.activeGreen),
decoration: const BoxDecoration( ),
border: Border( duration: Duration(seconds: 2),
right: BorderSide( behavior: SnackBarBehavior.floating,
color: ColorsManager.grayBorder, ),
);
context.read<BookableSpacesBloc>().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( duration: const Duration(seconds: 2),
bottomRight: Radius.circular(26), behavior: SnackBarBehavior.floating,
),
),
child: Text(
state is StepOneState ? 'Next' : 'Save',
style: const TextStyle(
color: ColorsManager.blueColor,
),
), ),
);
}
},
builder: (context, nonBookableState) {
return TextButton(
onPressed: onNextPressed,
child: Text(
title,
), ),
); );
}, },

View File

@ -31,7 +31,10 @@ class ColumnTitleWidget extends StatelessWidget {
), ),
child: Text( child: Text(
title, title,
style: const TextStyle(color: ColorsManager.grayColor), style: const TextStyle(
color: ColorsManager.grayColor,
fontSize: 12,
),
)); ));
} }
} }

View File

@ -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<StepsCubit, StepsState>(
builder: (context, state) {
if (state is StepOneState) {
return const SpacesStepDetailsWidget();
} else if (state is StepTwoState) {
return StepTwoDetailsWidget(
pointsController: pointsController,
);
} else {
return const SizedBox();
}
},
),
);
}
}

View File

@ -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<BookableSpacemodel> selectedSpaces;
const NextFirstStepButton({
super.key,
required this.selectedSpaces,
});
@override
Widget build(BuildContext context) {
return ButtonsDividerBottomDialogWidget(
title: 'Next',
onNextPressed: selectedSpaces.isEmpty
? null
: () {
context.read<StepsCubit>().goToNextStep();
},
onCancelPressed: () => context.pop(),
);
}
}

View File

@ -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<BookableSpacemodel> selectedSpaces;
final TextEditingController pointsController;
const SaveSecondStepButton({
super.key,
required this.selectedSpaces,
required this.pointsController,
});
@override
Widget build(BuildContext context) {
return BlocBuilder<NonBookableSpacesBloc, NonBookableSpacesState>(
builder: (context, state) {
return ButtonsDividerBottomDialogWidget(
title: 'Save',
onNextPressed: state is UnValidSaveButtonState
? null
: () {
if (selectedSpaces.any(
(element) => element.isValid,
)) {
context.read<NonBookableSpacesBloc>().add(
SendBookableSpacesToApi(),
);
}
},
onCancelPressed: () => context.pop(),
);
},
);
}
}

View File

@ -25,6 +25,7 @@ class SearchUnbookableSpacesWidget extends StatelessWidget {
return Container( return Container(
width: width ?? 480, width: width ?? 480,
height: height ?? 30, height: height ?? 30,
padding: const EdgeInsets.only(top: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),

View File

@ -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/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/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/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'; import 'package:syncrow_web/utils/color_manager.dart';
class SpacesStepDetailsWidget extends StatelessWidget { class SpacesStepDetailsWidget extends StatefulWidget {
SpacesStepDetailsWidget({ const SpacesStepDetailsWidget({
super.key, super.key,
}); });
@override
State<SpacesStepDetailsWidget> createState() =>
_SpacesStepDetailsWidgetState();
}
class _SpacesStepDetailsWidgetState extends State<SpacesStepDetailsWidget> {
Timer? _debounce; 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<NonBookableSpacesBloc>().state;
if (state is NonBookableSpacesLoaded &&
state.nonBookableSpaces.hasNext &&
!isLoadingMore) {
isLoadingMore = true;
currentPage++;
context.read<NonBookableSpacesBloc>().add(
LoadUnBookableSpacesEvent(
nonBookableSpacesParams: NonBookableSpacesParams(
currentPage: currentPage,
searchedWords: currentSearchTerm,
),
),
);
}
}
});
}
@override
void dispose() {
scrollController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<NonBookableSpacesBloc, NonBookableSpacesState>( return Column(
builder: (context, state) { crossAxisAlignment: CrossAxisAlignment.start,
if (state is NonBookableSpacesLoading) { children: [
return const Center(child: CircularProgressIndicator()); const Text(
} else if (state is NonBookableSpacesError) { 'Select Space',
return Text(state.error); style: TextStyle(
} else if (state is NonBookableSpacesLoaded) { fontWeight: FontWeight.w700,
return Column( color: ColorsManager.blackColor,
crossAxisAlignment: CrossAxisAlignment.start, ),
),
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: [ children: [
const Text(
'Select Space',
style: TextStyle(
fontWeight: FontWeight.w700,
color: ColorsManager.blackColor,
),
),
const SizedBox(
height: 20,
),
Container( Container(
width: 450, width: 520,
height: 480, height: 70,
decoration: BoxDecoration( padding:
color: Colors.white, const EdgeInsets.symmetric(vertical: 15, horizontal: 20),
borderRadius: BorderRadius.circular(20), decoration: const BoxDecoration(
boxShadow: const [ color: Color(0xFFF8F8F8),
BoxShadow( borderRadius: BorderRadius.vertical(
color: Color(0x40000000), top: Radius.circular(20),
offset: Offset(0, 4), ),
blurRadius: 5,
),
],
), ),
child: Column( child: SearchUnbookableSpacesWidget(
children: [ title: 'Search',
Container( onChanged: (p0) {
width: 520, if (_debounce?.isActive ?? false) _debounce!.cancel();
height: 70, _debounce = Timer(const Duration(milliseconds: 500), () {
padding: const EdgeInsets.symmetric( currentSearchTerm = p0;
vertical: 15, horizontal: 20), currentPage = 1;
decoration: const BoxDecoration( context.read<NonBookableSpacesBloc>().add(
color: Color(0xFFF8F8F8), LoadUnBookableSpacesEvent(
borderRadius: BorderRadius.vertical( nonBookableSpacesParams: NonBookableSpacesParams(
top: Radius.circular(20), currentPage: currentPage,
), searchedWords: currentSearchTerm,
), ),
child: SearchUnbookableSpacesWidget( ),
title: 'Search', );
onChanged: (p0) { });
if (_debounce?.isActive ?? false) _debounce!.cancel(); },
_debounce = ),
Timer(const Duration(milliseconds: 500), () { ),
context.read<NonBookableSpacesBloc>().add( Expanded(
LoadUnBookableSpacesEvent( child:
nonBookableSpacesParams: BlocConsumer<NonBookableSpacesBloc, NonBookableSpacesState>(
NonBookableSpacesParams( listener: (context, state) {
currentPage: 1, if (state is NonBookableSpacesLoaded) {
searchedWords: p0, isLoadingMore = false;
), }
), },
); builder: (context, state) {
}); if (state is NonBookableSpacesError) {
}, return Column(
), mainAxisAlignment: MainAxisAlignment.center,
), children: [
Expanded( Text(state.error),
child: Container( const SizedBox(
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, height: 5,
), ),
itemCount: state.nonBookableSpaces.data.length, ElevatedButton(
itemBuilder: (context, index) => CheckBoxSpaceWidget( onPressed: () {
nonBookableSpace: context.read<NonBookableSpacesBloc>().add(
state.nonBookableSpaces.data[index], 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<BookableSpacemodel> 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<CheckBoxSpaceWidget> {
const SizedBox( const SizedBox(
width: 5, width: 5,
), ),
Text(widget.nonBookableSpace.spaceName), Expanded(child: Text(widget.nonBookableSpace.spaceName)),
], ],
); );
} }

View File

@ -35,8 +35,10 @@ class StepTwoDetailsWidget extends StatelessWidget {
return; return;
} }
final nonBookableBloc = context.read<NonBookableSpacesBloc>(); final nonBookableBloc = context.read<NonBookableSpacesBloc>();
if (isEndTimeAfterStartTime(
timePicked, nonBookableBloc.endTime)) { if (nonBookableBloc.endTime != null &&
isEndTimeAfterStartTime(
timePicked, nonBookableBloc.endTime!)) {
ScaffoldMessenger.of(context).clearSnackBars(); ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(const SnackBar( ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: content:
@ -47,7 +49,7 @@ class StepTwoDetailsWidget extends StatelessWidget {
throw Exception(); throw Exception();
} else { } else {
nonBookableBloc.selectedBookableSpaces.forEach( nonBookableBloc.selectedBookableSpaces.forEach(
(e) => e.spaceConfig.bookingStartTime = timePicked, (e) => e.spaceConfig!.bookingStartTime = timePicked,
); );
} }
}, },
@ -62,8 +64,9 @@ class StepTwoDetailsWidget extends StatelessWidget {
return; return;
} }
final nonBookableBloc = context.read<NonBookableSpacesBloc>(); final nonBookableBloc = context.read<NonBookableSpacesBloc>();
if (isEndTimeAfterStartTime( if (nonBookableBloc.startTime != null &&
nonBookableBloc.startTime, timePicked)) { isEndTimeAfterStartTime(
nonBookableBloc.startTime!, timePicked)) {
ScaffoldMessenger.of(context).clearSnackBars(); ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(const SnackBar( ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: content:
@ -74,7 +77,7 @@ class StepTwoDetailsWidget extends StatelessWidget {
throw Exception(); throw Exception();
} else { } else {
nonBookableBloc.selectedBookableSpaces.forEach( nonBookableBloc.selectedBookableSpaces.forEach(
(e) => e.spaceConfig.bookingEndTime = timePicked, (e) => e.spaceConfig!.bookingEndTime = timePicked,
); );
} }
}, },
@ -102,6 +105,22 @@ class StepTwoDetailsWidget extends StatelessWidget {
), ),
SearchUnbookableSpacesWidget( SearchUnbookableSpacesWidget(
title: 'Ex: 0', title: 'Ex: 0',
height: 40,
onChanged: (p0) {
context
.read<NonBookableSpacesBloc>()
.selectedBookableSpaces
.forEach(
(e) => e.spaceConfig!.cost = int.parse(
pointsController.text.isEmpty
? '0'
: pointsController.text,
),
);
context
.read<NonBookableSpacesBloc>()
.add(CheckConfigurValidityEvent());
},
controller: pointsController, controller: pointsController,
inputFormatters: [FilteringTextInputFormatter.digitsOnly], inputFormatters: [FilteringTextInputFormatter.digitsOnly],
suffix: const SizedBox(), suffix: const SizedBox(),

View File

@ -24,6 +24,7 @@ class StepperPartWidget extends StatelessWidget {
title: 'Space', title: 'Space',
), ),
Container( Container(
padding: const EdgeInsets.only(left: 3),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
height: 50, height: 50,
child: const VerticalDivider( child: const VerticalDivider(
@ -32,7 +33,8 @@ class StepperPartWidget extends StatelessWidget {
const CircleTitleStepperWidget( const CircleTitleStepperWidget(
title: 'Settings', title: 'Settings',
titleColor: ColorsManager.softGray, titleColor: ColorsManager.softGray,
circleColor: ColorsManager.softGray, circleColor: ColorsManager.whiteColors,
borderColor: ColorsManager.textGray,
) )
], ],
); );
@ -51,9 +53,11 @@ class StepperPartWidget extends StatelessWidget {
size: 12, size: 12,
), ),
circleColor: ColorsManager.trueIconGreen, circleColor: ColorsManager.trueIconGreen,
radius: 3, radius: 15,
borderColor: ColorsManager.trueIconGreen,
), ),
Container( Container(
padding: const EdgeInsets.only(left: 3),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
height: 50, height: 50,
child: const VerticalDivider( child: const VerticalDivider(
@ -81,12 +85,14 @@ class CircleTitleStepperWidget extends StatelessWidget {
final double? radius; final double? radius;
final Widget? cicleIcon; final Widget? cicleIcon;
final Color? circleColor; final Color? circleColor;
final Color? borderColor;
final Color? titleColor; final Color? titleColor;
final String title; final String title;
const CircleTitleStepperWidget({ const CircleTitleStepperWidget({
super.key, super.key,
required this.title, required this.title,
this.circleColor, this.circleColor,
this.borderColor,
this.cicleIcon, this.cicleIcon,
this.titleColor, this.titleColor,
this.radius, this.radius,
@ -96,9 +102,13 @@ class CircleTitleStepperWidget extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Row(
children: [ children: [
CircleAvatar( Container(
minRadius: radius ?? 5, width: radius ?? 15,
backgroundColor: circleColor ?? ColorsManager.blue1, height: radius ?? 15,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: circleColor ?? ColorsManager.blue1,
border: Border.all(color: borderColor ?? ColorsManager.blue1)),
child: cicleIcon, child: cicleIcon,
), ),
const SizedBox( const SizedBox(

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.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/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/assets.dart';
@ -40,6 +42,7 @@ class _TimePickerWidgetState extends State<TimePickerWidget> {
); );
widget.onTimePicked(tempTime); widget.onTimePicked(tempTime);
timePicked = tempTime; timePicked = tempTime;
context.read<NonBookableSpacesBloc>().add(CheckConfigurValidityEvent());
setState(() {}); setState(() {});
}, },
child: Row( child: Row(
@ -70,9 +73,7 @@ class _TimePickerWidgetState extends State<TimePickerWidget> {
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text( child: Text(
timePicked == null timePicked == null ? 'HH:MM' : timePicked!.format(context),
? TimeOfDay.now().format(context)
: timePicked!.format(context),
style: const TextStyle(color: Color(0xB2D5D5D5)), style: const TextStyle(color: Color(0xB2D5D5D5)),
), ),
), ),

View File

@ -42,9 +42,13 @@ class _WeekDaysCheckboxRowState extends State<WeekDaysCheckboxRow> {
for (var space in context for (var space in context
.read<NonBookableSpacesBloc>() .read<NonBookableSpacesBloc>()
.selectedBookableSpaces) { .selectedBookableSpaces) {
space.spaceConfig.bookableDays = selectedDays; space.spaceConfig!.bookableDays = selectedDays;
} }
}); });
context
.read<NonBookableSpacesBloc>()
.add(CheckConfigurValidityEvent());
}, },
), ),
), ),

View File

@ -141,5 +141,5 @@ abstract class ApiEndpoints {
static const String saveSchedule = '/schedule/{deviceUuid}'; static const String saveSchedule = '/schedule/{deviceUuid}';
////booking System ////booking System
static const String addBookableSpaces = '/bookable-spaces'; static const String bookableSpaces = '/bookable-spaces';
} }