add settings and edit feature to bookable spaces

This commit is contained in:
Rafeek-Khoudare
2025-07-15 15:17:43 +03:00
parent a9895f5462
commit e9b4d35f97
22 changed files with 255 additions and 55 deletions

View File

@ -3,8 +3,8 @@ import 'package:equatable/equatable.dart';
import 'package:flutter/material.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/non_bookable_spaces_params.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/params/send_bookable_spaces_to_api_params.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/non_bookable_spaces_service.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/service/send_bookable_spaces_to_api_params.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/shared/models/paginated_data_model.dart';
part 'non_bookaable_spaces_event.dart';
@ -16,11 +16,13 @@ class NonBookableSpacesBloc
List<BookableSpacemodel> selectedBookableSpaces = [];
NonBookableSpacesBloc(this.nonBookableSpacesService)
: super(NonBookableSpacesInitial()) {
on<CallInitStateEvent>(_onCallInitStateEvent);
on<LoadUnBookableSpacesEvent>(_onLoadUnBookableSpacesEvent);
on<AddToBookableSpaceEvent>(_onAddToBookableSpaceEvent);
on<RemoveFromBookableSpaceEvent>(_onRemoveFromBookableSpaceEvent);
on<SendBookableSpacesToApi>(_onSendBookableSpacesToApi);
on<CheckConfigurValidityEvent>(_onCheckConfigurValidityEvent);
on<EditModeSelected>(_onEditModeSelected);
}
TimeOfDay? get endTime =>
@ -28,6 +30,12 @@ class NonBookableSpacesBloc
TimeOfDay? get startTime =>
selectedBookableSpaces.first.spaceConfig!.bookingStartTime;
void _onCallInitStateEvent(
CallInitStateEvent event, Emitter<NonBookableSpacesState> emit) {
emit(NonBookableSpacesInitial());
}
Future<void> _onLoadUnBookableSpacesEvent(LoadUnBookableSpacesEvent event,
Emitter<NonBookableSpacesState> emit) async {
if (state is NonBookableSpacesLoaded) {
@ -117,7 +125,7 @@ class NonBookableSpacesBloc
selectedBookableSpaces,
),
);
emit(NonBookableSpacesInitial());
add(CallInitStateEvent());
} catch (e) {
emit(
NonBookableSpacesError(e.toString()),
@ -133,4 +141,10 @@ class NonBookableSpacesBloc
emit(UnValidSaveButtonState());
}
}
void _onEditModeSelected(
EditModeSelected event, Emitter<NonBookableSpacesState> emit) {
selectedBookableSpaces.clear();
selectedBookableSpaces.add(event.editingBookableSpace);
}
}

View File

@ -7,6 +7,8 @@ sealed class NonBookableSpacesEvent extends Equatable {
List<Object> get props => [];
}
class CallInitStateEvent extends NonBookableSpacesEvent {}
class LoadUnBookableSpacesEvent extends NonBookableSpacesEvent {
final NonBookableSpacesParams nonBookableSpacesParams;
const LoadUnBookableSpacesEvent({
@ -31,3 +33,10 @@ class RemoveFromBookableSpaceEvent extends NonBookableSpacesEvent {
class SendBookableSpacesToApi extends NonBookableSpacesEvent {}
class CheckConfigurValidityEvent extends NonBookableSpacesEvent {}
class EditModeSelected extends NonBookableSpacesEvent {
final BookableSpacemodel editingBookableSpace;
const EditModeSelected({
required this.editingBookableSpace,
});
}

View File

@ -10,6 +10,10 @@ class StepsCubit extends Cubit<StepsState> {
emit(StepOneState());
}
void editValueInit() {
emit(StepTwoState());
}
void goToNextStep() {
if (state is StepOneState) {
emit(StepTwoState());

View File

@ -13,4 +13,3 @@ final class StepOneState extends StepsState {}
final class StepTwoState extends StepsState {}
final class StepEditMode extends StepsState {}

View File

@ -24,6 +24,7 @@ class UpdateBookableSpacesBloc
await updateBookableSpaceService.update(event.updatedParams);
emit(UpdateBookableSpaceSuccess(bookableSpaceConfig: updatedSpace));
event.onSuccess?.call();
} on APIException catch (e) {
emit(UpdateBookableSpaceFailure(error: e.message));
} catch (e) {

View File

@ -8,8 +8,10 @@ sealed class UpdateBookableSpaceEvent extends Equatable {
}
class UpdateBookableSpace extends UpdateBookableSpaceEvent {
final void Function()? onSuccess;
final UpdateBookableSpaceParam updatedParams;
const UpdateBookableSpace({
required this.updatedParams,
this.onSuccess,
});
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/models/bookable_space_model.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';
@ -13,23 +14,36 @@ import 'package:syncrow_web/utils/color_manager.dart';
class SetupBookableSpacesDialog extends StatelessWidget {
final TextEditingController pointsController = TextEditingController();
SetupBookableSpacesDialog({super.key});
final BookableSpacemodel? editingBookableSpace;
SetupBookableSpacesDialog({
super.key,
this.editingBookableSpace,
});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<StepsCubit>(
create: (context) => StepsCubit()..initDialogValue(),
create: editingBookableSpace == null
? (context) => StepsCubit()..initDialogValue()
: (context) => StepsCubit()..editValueInit(),
),
BlocProvider<NonBookableSpacesBloc>(
create: (context) => NonBookableSpacesBloc(
RemoteNonBookableSpaces(HTTPService()),
)..add(
LoadUnBookableSpacesEvent(
nonBookableSpacesParams:
NonBookableSpacesParams(currentPage: 1),
),
),
create: editingBookableSpace == null
? (context) => NonBookableSpacesBloc(
RemoteNonBookableSpaces(HTTPService()),
)..add(
LoadUnBookableSpacesEvent(
nonBookableSpacesParams:
NonBookableSpacesParams(currentPage: 1),
),
)
: (context) => NonBookableSpacesBloc(
RemoteNonBookableSpaces(HTTPService()),
)..add(
EditModeSelected(
editingBookableSpace: editingBookableSpace!),
),
),
],
child: AlertDialog(
@ -67,6 +81,7 @@ class SetupBookableSpacesDialog extends StatelessWidget {
flex: 7,
child: DetailsStepsWidget(
pointsController: pointsController,
editingBookableSpace: editingBookableSpace,
),
)
],
@ -79,7 +94,9 @@ class SetupBookableSpacesDialog extends StatelessWidget {
? NextFirstStepButton(selectedSpaces: selectedSpaces)
: SaveSecondStepButton(
selectedSpaces: selectedSpaces,
pointsController: pointsController);
pointsController: pointsController,
isEditingMode: editingBookableSpace != null,
);
}),
],
),

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/time_picker_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
@ -8,8 +9,10 @@ import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/string_utils.dart';
class BookingPeriodWidget extends StatelessWidget {
final BookableSpacemodel? editingBookableSpace;
const BookingPeriodWidget({
super.key,
this.editingBookableSpace,
});
@override
@ -39,22 +42,23 @@ class BookingPeriodWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TimePickerWidget(
title: 'Start Time',
title: editingBookableSpace == null
? 'Start Time'
: editingBookableSpace!.spaceConfig!.bookingStartTime!
.format(context),
onTimePicked: (timePicked) {
if (timePicked == null) {
return;
}
final nonBookableBloc =
context.read<NonBookableSpacesBloc>();
final nonBookableBloc = context.read<NonBookableSpacesBloc>();
if (nonBookableBloc.endTime != null &&
isEndTimeAfterStartTime(
timePicked, nonBookableBloc.endTime!)) {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text(
"You can't choose start Time Before End time"),
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content:
Text("You can't choose start Time Before End time"),
duration: Duration(seconds: 2),
backgroundColor: ColorsManager.red,
));
@ -71,21 +75,22 @@ class BookingPeriodWidget extends StatelessWidget {
color: ColorsManager.grayColor,
),
TimePickerWidget(
title: 'End Time',
title: editingBookableSpace == null
? 'End Time'
: editingBookableSpace!.spaceConfig!.bookingEndTime!
.format(context),
onTimePicked: (timePicked) {
if (timePicked == null) {
return;
}
final nonBookableBloc =
context.read<NonBookableSpacesBloc>();
final nonBookableBloc = context.read<NonBookableSpacesBloc>();
if (nonBookableBloc.startTime != null &&
isEndTimeAfterStartTime(
nonBookableBloc.startTime!, timePicked)) {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text(
"You can't choose End Time After Start time"),
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content:
Text("You can't choose End Time After Start time"),
duration: Duration(seconds: 2),
backgroundColor: ColorsManager.red,
));

View File

@ -4,7 +4,6 @@ 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 {
@ -63,7 +62,7 @@ class ButtonsDividerBottomDialogWidget extends StatelessWidget {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Spaces Added Successfully',
'Operation Done Successfully',
style: TextStyle(color: ColorsManager.activeGreen),
),
duration: Duration(seconds: 2),

View File

@ -1,14 +1,17 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/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;
final BookableSpacemodel? editingBookableSpace;
const DetailsStepsWidget({
super.key,
required this.pointsController,
this.editingBookableSpace,
});
@override
@ -22,6 +25,7 @@ class DetailsStepsWidget extends StatelessWidget {
} else if (state is StepTwoState) {
return StepTwoDetailsWidget(
pointsController: pointsController,
editingBookableSpace:editingBookableSpace
);
} else {
return const SizedBox();

View File

@ -1,12 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/data/remote_update_bookable_space_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/domain/params/update_bookable_space_param.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/bookable_spaces_bloc/bookable_spaces_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/update_bookable_spaces/update_bookable_spaces_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/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/services/api/http_service.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
@ -143,7 +146,28 @@ class TablePartWidget extends StatelessWidget {
)),
DataCell(Center(
child: ElevatedButton(
onPressed: () {},
onPressed: () {
final bookableBloc = context.read<BookableSpacesBloc>();
showDialog(
context: context,
builder: (context) => MultiBlocProvider(
providers: [
BlocProvider.value(
value: bookableBloc,
),
BlocProvider(
create: (context) => UpdateBookableSpacesBloc(
RemoteUpdateBookableSpaceService(HTTPService()),
),
),
],
child: SetupBookableSpacesDialog(
editingBookableSpace: space,
),
),
);
},
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero,
fixedSize: const Size(50, 30),

View File

@ -1,19 +1,36 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/cubit/toggle_points_switch_cubit.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/search_unbookable_spaces_widget.dart';
import 'package:syncrow_web/utils/color_manager.dart';
class PointsPartWidget extends StatelessWidget {
class PointsPartWidget extends StatefulWidget {
final BookableSpacemodel? editingBookableSpace;
const PointsPartWidget({
super.key,
required this.pointsController,
this.editingBookableSpace,
});
final TextEditingController pointsController;
@override
State<PointsPartWidget> createState() => _PointsPartWidgetState();
}
class _PointsPartWidgetState extends State<PointsPartWidget> {
@override
void initState() {
if (widget.editingBookableSpace != null) {
widget.pointsController.text =
widget.editingBookableSpace!.spaceConfig!.cost.toString();
}
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<TogglePointsSwitchCubit, TogglePointsSwitchState>(
@ -73,7 +90,7 @@ class PointsPartWidget extends StatelessWidget {
context
.read<TogglePointsSwitchCubit>()
.unActivateSwitch();
pointsController.clear();
widget.pointsController.clear();
context
.read<NonBookableSpacesBloc>()
.selectedBookableSpaces
@ -102,16 +119,16 @@ class PointsPartWidget extends StatelessWidget {
.selectedBookableSpaces
.forEach(
(e) => e.spaceConfig!.cost = int.parse(
pointsController.text.isEmpty
widget.pointsController.text.isEmpty
? '0'
: pointsController.text,
: widget.pointsController.text,
),
);
context
.read<NonBookableSpacesBloc>()
.add(CheckConfigurValidityEvent());
},
controller: pointsController,
controller: widget.pointsController,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
suffix: const SizedBox(),
)

View File

@ -2,17 +2,21 @@ 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/domain/params/update_bookable_space_param.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/update_bookable_spaces/update_bookable_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;
final bool isEditingMode;
const SaveSecondStepButton({
super.key,
required this.selectedSpaces,
required this.pointsController,
required this.isEditingMode,
});
@override
@ -27,9 +31,11 @@ class SaveSecondStepButton extends StatelessWidget {
if (selectedSpaces.any(
(element) => element.isValid,
)) {
context.read<NonBookableSpacesBloc>().add(
SendBookableSpacesToApi(),
);
isEditingMode
? callEditLogic(context)
: context.read<NonBookableSpacesBloc>().add(
SendBookableSpacesToApi(),
);
}
},
onCancelPressed: () => context.pop(),
@ -37,4 +43,19 @@ class SaveSecondStepButton extends StatelessWidget {
},
);
}
void callEditLogic(BuildContext context) {
context.read<UpdateBookableSpacesBloc>().add(
UpdateBookableSpace(
onSuccess: () =>
context.read<NonBookableSpacesBloc>().add(CallInitStateEvent()),
updatedParams: UpdateBookableSpaceParam.fromBookableModel(
context
.read<NonBookableSpacesBloc>()
.selectedBookableSpaces
.first,
),
),
);
}
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/cubit/toggle_points_switch_cubit.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/booking_period_widget.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/points_part_widget.dart';
@ -7,9 +8,11 @@ import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/prese
class StepTwoDetailsWidget extends StatelessWidget {
final TextEditingController pointsController;
final BookableSpacemodel? editingBookableSpace;
const StepTwoDetailsWidget({
super.key,
required this.pointsController,
this.editingBookableSpace,
});
@override
Widget build(BuildContext context) {
@ -19,17 +22,27 @@ class StepTwoDetailsWidget extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const WeekDaysCheckboxRow(),
WeekDaysCheckboxRow(
editingBookableSpace: editingBookableSpace,
),
const SizedBox(
height: 20,
),
const BookingPeriodWidget(),
BookingPeriodWidget(
editingBookableSpace: editingBookableSpace,
),
const SizedBox(
height: 20,
),
BlocProvider(
create: (context) => TogglePointsSwitchCubit()..activateSwitch(),
child: PointsPartWidget(pointsController: pointsController),
create: editingBookableSpace == null
? (context) => TogglePointsSwitchCubit()..activateSwitch()
: editingBookableSpace!.spaceConfig!.cost == 0
? (context) => TogglePointsSwitchCubit()..unActivateSwitch()
: (context) => TogglePointsSwitchCubit()..activateSwitch(),
child: PointsPartWidget(
pointsController: pointsController,
editingBookableSpace: editingBookableSpace),
)
],
),

View File

@ -68,10 +68,6 @@ class StepperPartWidget extends StatelessWidget {
)
],
);
} else if (state is StepEditMode) {
return const CircleTitleStepperWidget(
title: 'Settings',
);
} else {
return const SizedBox();
}

View File

@ -1,11 +1,9 @@
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';
class TimePickerWidget extends StatefulWidget {
final String title;

View File

@ -1,9 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/domain/models/bookable_space_model.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/non_bookable_spaces_bloc/non_bookaable_spaces_bloc.dart';
class WeekDaysCheckboxRow extends StatefulWidget {
const WeekDaysCheckboxRow({super.key});
final BookableSpacemodel? editingBookableSpace;
const WeekDaysCheckboxRow({
super.key,
this.editingBookableSpace,
});
@override
State<WeekDaysCheckboxRow> createState() => _WeekDaysCheckboxRowState();
@ -19,6 +24,19 @@ class _WeekDaysCheckboxRowState extends State<WeekDaysCheckboxRow> {
'Sat': false,
'Sun': false,
};
@override
void initState() {
super.initState();
final existingDays =
widget.editingBookableSpace?.spaceConfig?.bookableDays ?? [];
for (var day in _daysChecked.keys) {
if (existingDays.contains(day)) {
_daysChecked[day] = true;
}
}
}
@override
Widget build(BuildContext context) {