build dialog with steps view

This commit is contained in:
Rafeek-Khoudare
2025-07-07 15:37:38 +03:00
parent e2d4e48875
commit 201348a9bf
7 changed files with 796 additions and 0 deletions

View File

@ -0,0 +1,191 @@
import 'package:flutter/material.dart';
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/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/utils/constants/assets.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/web_layout/web_scaffold.dart';
class ManageBookableSpacesPage extends StatefulWidget {
const ManageBookableSpacesPage({super.key});
@override
State<ManageBookableSpacesPage> createState() =>
_ManageBookableSpacesPageState();
}
class _ManageBookableSpacesPageState extends State<ManageBookableSpacesPage> {
final PageController _pageController = PageController(initialPage: 1);
int _currentPageIndex = 1;
@override
Widget build(BuildContext context) {
return WebScaffold(
enableMenuSidebar: false,
appBarTitle: Text(
'Access Management',
style: ResponsiveTextTheme.of(context).deviceManagementTitle,
),
centerBody: Row(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
onPressed: () => _switchPage(0),
child: Text(
'Access Overview',
style: context.textTheme.titleMedium?.copyWith(
color: _currentPageIndex == 0 ? Colors.white : Colors.grey,
fontWeight:
_currentPageIndex == 0 ? FontWeight.w700 : FontWeight.w400,
),
),
),
TextButton(
onPressed: () => _switchPage(1),
child: Text(
'Booking System',
style: context.textTheme.titleMedium?.copyWith(
color: _currentPageIndex == 1 ? Colors.white : Colors.grey,
fontWeight:
_currentPageIndex == 1 ? FontWeight.w700 : FontWeight.w400,
),
),
),
],
),
rightBody: const NavigateHomeGridView(),
scaffoldBody: BlocProvider(
create: (context) => BookableSpacesBloc(
DummyBookableSpacesService(),
// RemoteBookableSpacesService(HTTPService()),
)..add(LoadBookableSpacesEvent(
BookableSpacesParams(currentPage: 1),
)),
child: const ManageBookableSpacesWidget(),
),
);
}
void _switchPage(int index) {
setState(() => _currentPageIndex = index);
_pageController.jumpToPage(index);
}
}
class ManageBookableSpacesWidget extends StatelessWidget {
const ManageBookableSpacesWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Expanded(
flex: 1,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SvgTextButton(
svgAsset: Assets.backButtonIcon,
label: 'Booking Home',
onPressed: () {
context.pop();
}),
SvgTextButton(
svgAsset: Assets.backButtonIcon,
label: 'Set Up a Bookable Spaces',
onPressed: () async => showDialog(
context: context,
builder: (context) => SetupBookableSpacesDialog(),
),
)
],
)),
Expanded(
flex: 9,
child: BlocBuilder<BookableSpacesBloc, BookableSpacesState>(
builder: (context, state) {
if (state is BookableSpacesLoading) {
return const CircularProgressIndicator();
} else if (state is BookableSpacesError) {
return Text(state.error);
} else if (state is BookableSpacesLoaded) {
return CustomDataTable<BookableSpacemodel>(
items: state.bookableSpacesList.data,
cellsWidgets: (space) => [
DataCell(
Padding(
padding: const EdgeInsetsGeometry.only(left: 10),
child: Text(space.spaceName)),
),
DataCell(Padding(
padding: const EdgeInsetsGeometry.only(left: 10),
child: Text(space.spaceVirtualAddress))),
DataCell(SizedBox(
width: 200,
child: Wrap(
spacing: 4,
children: space.spaceConfig.bookableDays
.map((day) => Text(
day,
style: const TextStyle(fontSize: 12),
))
.toList(),
),
)),
DataCell(Padding(
padding: const EdgeInsetsGeometry.only(left: 10),
child: Text(space.spaceConfig.bookingStartTime))),
DataCell(Padding(
padding: const EdgeInsetsGeometry.only(left: 10),
child: Text(space.spaceConfig.bookingEndTime))),
DataCell(Padding(
padding: const EdgeInsetsGeometry.only(left: 10),
child: Text('${space.spaceConfig.cost} Points'))),
DataCell(Center(
child: Transform.scale(
scale: 0.7,
child: Switch(
value: space.spaceConfig.availability,
onChanged: (value) {},
),
),
)),
DataCell(Center(
child: ElevatedButton(
onPressed: () {},
child: SvgPicture.asset(Assets.settings),
),
)),
],
columnsTitles: const [
'space',
'space Virtual Address',
'Bookable Days',
'Booking Start Time',
'Booking End Time',
'Cost',
'Availability',
'Settings',
],
);
} else {
return const SizedBox();
}
},
),
)
],
),
);
}
}

View File

@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class SearchUnbookableSpacesWidget extends StatelessWidget {
final String title;
final Widget? suffix;
final double? height;
final double? width;
final TextEditingController? controller;
final List<TextInputFormatter>? inputFormatters;
final void Function(String)? onChanged;
const SearchUnbookableSpacesWidget({
required this.title,
this.controller,
this.onChanged,
this.suffix,
this.height,
this.width,
this.inputFormatters,
super.key,
});
@override
Widget build(BuildContext context) {
return Container(
width: width ?? 480,
height: height ?? 30,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: const [
BoxShadow(
color: Color(0x26000000),
offset: Offset(0, 4),
blurRadius: 5,
),
],
),
child: TextField(
controller: controller,
inputFormatters: inputFormatters,
onChanged: onChanged,
decoration: InputDecoration(
contentPadding:
const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
hintText: title,
hintStyle: const TextStyle(color: Colors.grey),
border: InputBorder.none,
suffixIcon:
suffix ?? const Icon(Icons.search, size: 20, color: Colors.grey),
),
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
);
}
}

View File

@ -0,0 +1,166 @@
import 'dart:async';
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/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/utils/color_manager.dart';
class SpacesStepDetailsWidget extends StatelessWidget {
SpacesStepDetailsWidget({
super.key,
});
Timer? _debounce;
@override
Widget build(BuildContext context) {
return BlocBuilder<NonBookableSpacesBloc, NonBookableSpacesState>(
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,
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,
),
],
),
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<NonBookableSpacesBloc>().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(
height: 5,
),
itemCount: state.nonBookableSpaces.data.length,
itemBuilder: (context, index) => CheckBoxSpaceWidget(
nonBookableSpace:
state.nonBookableSpaces.data[index],
),
),
),
)
],
),
)
],
);
} else {
return const SizedBox();
}
},
);
}
}
class CheckBoxSpaceWidget extends StatefulWidget {
final BookableSpacemodel nonBookableSpace;
const CheckBoxSpaceWidget({
super.key,
required this.nonBookableSpace,
});
@override
State<CheckBoxSpaceWidget> createState() => _CheckBoxSpaceWidgetState();
}
class _CheckBoxSpaceWidgetState extends State<CheckBoxSpaceWidget> {
bool isChecked = false;
@override
Widget build(BuildContext context) {
return Row(
children: [
Checkbox(
value: isChecked,
onChanged: (value) => setState(() {
isChecked = value ?? false;
if (isChecked) {
context.read<NonBookableSpacesBloc>().add(
AddToBookableSpaceEvent(
nonBookableSpace: widget.nonBookableSpace,
),
);
} else {
context.read<NonBookableSpacesBloc>().add(
RemoveFromBookableSpaceEvent(
bookableSpace: widget.nonBookableSpace,
),
);
}
}),
),
const SizedBox(
width: 5,
),
Text(widget.nonBookableSpace.spaceName),
],
);
}
}

View File

@ -0,0 +1,119 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/blocs/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/time_picker_widget.dart';
import 'package:syncrow_web/pages/access_management/manage_bookable_spaces/presentation/widgets/week_checkbox_title_widget.dart';
class StepTwoDetailsWidget extends StatelessWidget {
final TextEditingController pointsController;
const StepTwoDetailsWidget({
super.key,
required this.pointsController,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 450,
height: 480,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const WeekDaysCheckboxRow(),
const SizedBox(
height: 20,
),
Row(
children: [
TitleAndTimePickerWidget(
title: 'Booking Start Time',
onTimePicked: (timePicked) {
final nonBookableBloc = context.read<NonBookableSpacesBloc>();
nonBookableBloc.selectedBookableSpaces.forEach(
(e) => e.spaceConfig.bookingStartTime =
timePicked!.format(context),
);
},
),
SizedBox(width: 20,),
TitleAndTimePickerWidget(
title: 'Booking End Time',
onTimePicked: (timePicked) {
final nonBookableBloc = context.read<NonBookableSpacesBloc>();
nonBookableBloc.selectedBookableSpaces.forEach(
(e) => e.spaceConfig.bookingEndTime =
timePicked!.format(context),
);
},
)
],
),
const SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'* ',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: Colors.red),
),
const Text('Points/hrs'),
],
),
const SizedBox(
height: 5,
),
SearchUnbookableSpacesWidget(
title: 'Ex: 0',
controller: pointsController,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
suffix: const SizedBox(),
),
],
),
);
}
}
class TitleAndTimePickerWidget extends StatelessWidget {
final void Function(TimeOfDay? timePicked) onTimePicked;
final String title;
const TitleAndTimePickerWidget({
super.key,
required this.onTimePicked,
required this.title,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
'* ',
style: Theme.of(context)
.textTheme
.bodyMedium!
.copyWith(color: Colors.red),
),
Text(title),
],
),
const SizedBox(
height: 5,
),
TimePickerWidget(
onTimePicked: onTimePicked,
),
],
);
}
}

View File

@ -0,0 +1,117 @@
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/utils/color_manager.dart';
class StepperPartWidget extends StatelessWidget {
const StepperPartWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(20),
padding: const EdgeInsetsGeometry.only(left: 20),
child: BlocBuilder<StepsCubit, StepsState>(
builder: (context, state) {
if (state is StepOneState) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(
height: 10,
),
const CircleTitleStepperWidget(
title: 'Space',
),
Container(
alignment: Alignment.centerLeft,
height: 50,
child: const VerticalDivider(
width: 8,
)),
const CircleTitleStepperWidget(
title: 'Settings',
titleColor: ColorsManager.softGray,
circleColor: ColorsManager.softGray,
)
],
);
} else if (state is StepTwoState) {
return Column(
children: [
const SizedBox(
height: 10,
),
const CircleTitleStepperWidget(
title: 'Space',
titleColor: ColorsManager.softGray,
cicleIcon: Icon(
Icons.check,
color: ColorsManager.whiteColors,
size: 12,
),
circleColor: ColorsManager.trueIconGreen,
radius: 3,
),
Container(
alignment: Alignment.centerLeft,
height: 50,
child: const VerticalDivider(
width: 8,
)),
const CircleTitleStepperWidget(
title: 'Settings',
)
],
);
} else if (state is StepEditMode) {
return const CircleTitleStepperWidget(
title: 'Settings',
);
} else {
return const SizedBox();
}
},
),
);
}
}
class CircleTitleStepperWidget extends StatelessWidget {
final double? radius;
final Widget? cicleIcon;
final Color? circleColor;
final Color? titleColor;
final String title;
const CircleTitleStepperWidget({
super.key,
required this.title,
this.circleColor,
this.cicleIcon,
this.titleColor,
this.radius,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
CircleAvatar(
minRadius: radius ?? 5,
backgroundColor: circleColor ?? ColorsManager.blue1,
child: cicleIcon,
),
const SizedBox(
width: 10,
),
Text(
title,
style: TextStyle(
fontWeight: FontWeight.w700,
color: titleColor ?? ColorsManager.blackColor,
),
),
],
);
}
}

View File

@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
class TimePickerWidget extends StatefulWidget {
const TimePickerWidget({
super.key,
required this.onTimePicked,
});
final void Function(TimeOfDay? timePicked) onTimePicked;
@override
State<TimePickerWidget> createState() => _TimePickerWidgetState();
}
class _TimePickerWidgetState extends State<TimePickerWidget> {
TimeOfDay? timePicked;
@override
Widget build(BuildContext context) {
return InkWell(
borderRadius: BorderRadius.circular(10),
onTap: () async {
timePicked = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
builder: (context, child) {
return Theme(
data: ThemeData.light().copyWith(
colorScheme: const ColorScheme.light(
primary: ColorsManager.primaryColor,
onSurface: Colors.black,
),
),
child: child!,
);
},
);
widget.onTimePicked(timePicked);
setState(() {});
},
child: Row(
children: [
Container(
width: 56,
height: 32,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
bottomLeft: Radius.circular(10),
),
),
alignment: Alignment.center,
child: SvgPicture.asset(Assets.clockIcon),
),
Container(
width: 120,
height: 32,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
alignment: Alignment.centerLeft,
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text(
timePicked == null
? TimeOfDay.now().format(context)
: timePicked!.format(context),
style: const TextStyle(color: Color(0xB2D5D5D5)),
),
),
],
),
);
}
}

View File

@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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});
@override
State<WeekDaysCheckboxRow> createState() => _WeekDaysCheckboxRowState();
}
class _WeekDaysCheckboxRowState extends State<WeekDaysCheckboxRow> {
final Map<String, bool> _daysChecked = {
'Mon': false,
'Tue': false,
'Wed': false,
'Thu': false,
'Fri': false,
'Sat': false,
'Sun': false,
};
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: _daysChecked.entries.map((entry) {
return Expanded(
child: Row(
children: [
Expanded(
child: Checkbox(
value: entry.value,
onChanged: (newValue) {
setState(() {
_daysChecked[entry.key] = newValue ?? false;
final selectedDays = _daysChecked.entries
.where((e) => e.value)
.map((e) => e.key)
.toList();
for (var space in context
.read<NonBookableSpacesBloc>()
.selectedBookableSpaces) {
space.spaceConfig.bookableDays = selectedDays;
}
});
},
),
),
Expanded(
child: Text(
entry.key,
style: const TextStyle(fontSize: 10),
)),
],
),
);
}).toList(),
);
}
}